Skip to content

Latest commit

 

History

History
88 lines (68 loc) · 15.4 KB

File metadata and controls

88 lines (68 loc) · 15.4 KB

XFTY Roadmap

What is built, what is left, for this C# port.

Legend: ✅ built and working · ⚠️ built but with a real limitation versus the Apex original · ❌ not ported (see reference/known-issues for why) · 💡 idea, not designed · 🧪 preview proof-of-concept - works, but not a considered, general-purpose package yet · 🚫 considered and declined - a deliberate non-goal, not a gap.

Built

Feature Tests Docs Notes / limits
Multi-variant Providers — flavour lookup keys, WithVariant, the lookup-key constructor, DiscriminatorLookupKey (discriminator-field variants) LookupKeyTest, MultiVariantProviderTest, VariantResolutionTest, DiscriminatorLookupKeyTest use, extend, detail ✅
Context-aware values — CopyFromSiblingExpression, CopyFromAncestorExpression (multi-hop), custom IContextAwareExpression, context.SiblingValue RecordFactoryTest, Xfty.Test/Values/*, ContextAwareExpressionTest use, extend, detail ✅
Loud guard for a mis-ordered sibling read ContextAwareExpressionTest, CopyFromSiblingExpressionTest use ✅
Per-call relationship control — IncludeOptional(field | path), ExcludeRelationship RecordFactoryTest, AncestorCycleTest use ✅
Path-scoped value overrides — Put(List<PropertyInfo>, …) into a generated ancestor PathValueTest use, detail ✅
Downward generation — With / WithChildren / ChildProvider, nested grandchildren ChildProviderTest, PerformanceTest (3-level fan-out) use ✅
Deferred / depth-batched resolution — Deferred + registry, .DepthBatched() DeferredInserterTest, DeferredInsertBufferTest, DepthBatchedInserterTest, PersistenceGatewayTest use, detail ✅ Flush(gateway) and Now+.DepthBatched() both insert for real through a configured IPersistenceGateway; proven against SQLite and a real Postgres container in Xfty.EntityFrameworkCore.Test.
Descendant (up-flowing) value reads — CopyFromDescendantExpression, multi-hop via its path-list constructor CopyFromDescendantExpressionTest use, detail ⚠️ First matching child at every hop - no way to read an aggregate across siblings (a custom IDeferredExpression using DeferredGraph.ChildIndicesOf directly can, if needed).
Shared ancestors — SharedAncestor.Put/Get, flat + deep auto-detected, nested, cycle guards, SharedAncestorProvider per-record config, ISharedAncestorDefaults packaged defaults, Disable / ManualResolutionOnly / batch ResolveNow / ResetAllForTesting SharedAncestorTest, SharedAncestorHierarchyTest, SharedAncestorResetTest, SharedAncestorConcurrencyTest use, extend, detail ⚠️ Static registry does not reset between xUnit test methods automatically, unlike Apex — three ways to handle it ([IsolatesSharedAncestor], ResetAllForTesting(), or unique names), none automatic; see reference/salesforce-considerations. Safe under concurrent access regardless of which you pick - was a real, reproduced crash risk, now fixed (ConcurrentDictionary + a resolver-level lock).
[IsolatesSharedAncestor] — resets SharedAncestor before/after a test class or method, via xUnit's BeforeAfterTestAttribute IsolatesSharedAncestorAttributeTest, SharedAncestorLeaksWithoutIsolationTest use/shared-ancestors ✅ Separate opt-in package (Xfty.Xunit) - the same reset as ResetAllForTesting(), wired up automatically instead of by hand.
Enrichment — bundle.Inject(field, config) / InjectAll / InjectAllParents / InjectAllChildren, InjectConfig, standalone RecordInjector BundleEnricherTest, RecordInjectorTest, EnrichmentSelectionTest, EnrichmentIntegrationTest use, injector ✅ — reflection sets any property directly, so there's no serialization round-trip and no field-type special-casing needed
Predicates — FieldPredicateFactory, PredicateFactory (AND/OR/NOT), custom IRecordPredicate Xfty.Test/Predicates/* extend/provider-variants ✅
Realistic fake data — FakeFullNameExpression, FakeEmailAddressExpression, FakeStreetAddressExpression, FakeParagraphExpression, wrapping Bogus Xfty.Bogus.Test/* comparison.md ✅ Separate opt-in package (Xfty.Bogus), not core Xfty — the base library has no dependency on Bogus.
Vector-embedding fields — RandomVectorExpression(int dimensions, float min, float max, bool normalize), KnownEmbeddingDimensions Xfty.VectorDatabases.Test/* vector-databases.md ✅ Separate opt-in package (Xfty.VectorDatabases); structurally a vector, not a semantically meaningful embedding — see the detail page for why that's out of scope.
pgvector persistence — a Vector-typed column through the existing, unmodified EfPersistenceGateway PgVectorPersistenceTest (Xfty.EntityFrameworkCore.Test) vector-databases.md ✅ No new gateway code - just a Pgvector.EntityFrameworkCore reference, a demo entity, and a pgvector/pgvector:pg16 container image instead of plain postgres:16-alpine.
Classic EF6 persistence — Ef6PersistenceGateway, the same IPersistenceGateway convenience as EfPersistenceGateway but for System.Data.Entity.DbContext (the EntityFramework NuGet package, "EF6") rather than Microsoft.EntityFrameworkCore.DbContext Xfty.EntityFramework6.Test/* (SQLite), Ef6SmokeTest (Xfty.NetStandardCompat.Test, net472) package README ✅ Separate opt-in package (Xfty.EntityFramework6) - the two DbContext types are unrelated, so this is a distinct implementation, not a variant of the EF Core one. Needed no source changes beyond that: DbContext.Set(Type).Add(object)/SaveChangesAsync() have been stable since EF6's first release.
AutoFixture pairing — XftyCustomization/XftySpecimenBuilder (point fixture.Create<T>() at a registered RecordProvider), IUnsetFieldFiller/AutoFixtureUnsetFieldFiller (let AutoFixture fill fields a Master Template never configured) Xfty.AutoFixture.Test/*, UnsetFieldFillerTest (core contract) use, detail ✅ Separate opt-in package (Xfty.AutoFixture); core Xfty gains only the dependency-free IUnsetFieldFiller extension point, not a dependency on AutoFixture itself.
AutoBogus pairing — XftyAutoBogus/XftyAutoBogusOverride (point faker.Generate<T>() at a registered RecordProvider), AutoBogusUnsetFieldFiller (the same IUnsetFieldFiller extension point, backed by IAutoFaker) Xfty.AutoBogus.Test/*, UnsetFieldFillerTest (core contract) use ✅ Separate opt-in package (Xfty.AutoBogus); mirrors the AutoFixture pairing exactly, reusing the same core IUnsetFieldFiller extension point - no further core change needed. AutoBogus self-limits recursion depth instead of throwing, unlike AutoFixture's default, so its filler needs no recursion-exception handling.
Typed RecordProvider<TRecord>/ChildProvider<TChild> — composed generic wrappers mirroring MasterTemplate<TRecord>, plus LookupKey.Get<TRecord>()/FlavouredLookupKey.Get<TRecord>(flavour)/lookup.Get<TRecord>() RecordProviderOfTTest, ChildProviderOfTTest, LookupKeyTest — ✅ Typed Supply()/SupplyList() (no cast), a MasterTemplate<TRecord>-style object-initializer indexer, and full fluent forwarding.
Fully async persistence, end to end — Supply()/SupplyList()/SupplyBundle() and everything reachable from them (IPersistenceGateway.Insert, DeferredInserter.Flush, SharedAncestor's resolution methods, both vector-database gateways) are genuinely Task-based The whole suite - every test that calls any of these use/getting-started ✅ No Async suffix on any of it - see CHANGELOG.md for the full reasoning.
Multi-targeting — netstandard2.0/net8.0/net10.0 across every package, reaching .NET Framework 4.6.1+/Mono/Xamarin/older .NET Core alongside modern .NET Xfty.NetStandardCompat.Test (a real net472 project - netstandard2.0 isn't itself runnable; also proves Xfty.EntityFrameworkCore's EF Core 3.1.x pairing and Xfty.EntityFramework6's net461 asset against real SQLite) — ✅ Was core Xfty only; every package now matches, with two dependency-driven exceptions: Xfty.EntityFrameworkCore (its netstandard2.0 build rides EF Core 3.1.x - EOL, but offering the TFM is a statement about what a consumer can target, not a promise to backport fixes Microsoft no longer will) and Xfty.EntityFramework6 (net461;netstandard2.1 instead - classic EF6 ships no netstandard2.0 asset of its own at all; net461 rather than net472 specifically because that's the actual floor Xfty's own netstandard2.0 dependency allows, reaching a full Framework generation further back than the rest of this repo's net472 proof venue does).
Xfty.FSharpAsync — Async<'T> wrappers (RecordProviderAsync, TypedRecordProviderAsync, DeferredInserterAsync) for F# code on the original async { } workflow Xfty.FSharpAsync.Test/* package README ✅ Separate opt-in package; not needed at all if your F# code already uses the newer task { } computation expression, which consumes Xfty's Task-returning API directly.

Not ported — genuine capability gaps

Feature Why
Seeding a long-lived, shared environment A different job from generating/inserting data for one test run - deliberately out of scope. sandbox-seeding.md.
Record-type schema auto-detection XFTY does match an override template to a registered variant automatically (a FlavouredLookupKey/DiscriminatorLookupKey with a condition on the record). What's gone is Apex's zero-registration version, which read Salesforce's schema to know the variants without you declaring them — there's no schema to read here.
Test-user helpers (an admin-equivalent user, role/profile lookups) No role/profile-style schema for such a lookup to resolve against.
CPU-time/row-count budget tracking No fixed per-run resource quota exists to track against — see reference/volume-and-limits for what replaces it.
Namespace / package distribution Salesforce-specific distribution concept — namespace-appexchange.md.

Ideas under consideration — not gaps, not committed to

Idea Status Detail
Embedded/denormalized document relationships (a document database's native nested-array shape, distinct from the FK-reference relationships XFTY models today) 💡 embedded-documents.md
A netstandard1.x floor for core Xfty (lower than today's netstandard2.0) 💡 netstandard1x-support.md - scoped and measured (~16 call sites, a day or two), but the remaining addressable platforms (Windows Phone 8.1-era) are effectively extinct and nothing has asked for it - not worth building against zero known demand.
A persistence gateway for pre-Code-First EF (ObjectContext, EF 1.0-4.0) 💡 ef-objectcontext-support.md - a genuinely different API (no Type-keyed entity access, no async at all - predates C# 5), not a variant of Xfty.EntityFramework6; a narrower, even-less-asked-for audience than the netstandard1.x idea above. EF 4.1-5.0 isn't a gap at all - Xfty.EntityFramework6 already reaches it via ordinary NuGet version resolution.

Preview proof-of-concept packages — work, but not a general-availability commitment yet

Versioned 0.x-preview.*, not 1.0.0-beta.1 like the rest of this solution's packages, on purpose. Read the package's own README before relying on one of these for anything beyond the question it was built to answer.

Package Question it answers Detail
Xfty.VectorDatabases.Qdrant — QdrantPersistenceGateway, via Qdrant's own client directly Is a dedicated vector-database IPersistenceGateway a trivial wrapper or real design work, without going through an abstraction layer? (Answer: real work, but less than the MEVD path - compiled and passed on the first attempt.) vector-databases.md, package README
Xfty.VectorDatabases.MicrosoftExtensionsVectorData — MevdPersistenceGateway, via any Microsoft.Extensions.VectorData connector Is the extra abstraction worth it - and can one gateway really work with any vector store, not just the one it's tested against? (Answer: the abstraction didn't pay for itself on this simple a record shape, but the genericity claim holds by construction - GetDynamicCollection etc. are on MEVD's abstract base class, not Qdrant-specific.) Kept in a separate package from the Qdrant-direct gateway from day one - see the detail page for why. vector-databases.md, package README

Explicitly declined — deliberate non-goals

Considered and turned down on purpose, not gaps waiting to be filled.

Idea Why declined Detail
Calling a real embedding API (OpenAI, Cohere, …) to produce a semantically meaningful vector Breaks XFTY's offline/no-network/no-credential contract that every other value expression, Xfty.Bogus included, holds to. A project that needs real embeddings is better served by its own small helper than by XFTY adopting a paid-API pattern it uses nowhere else. vector-databases.md

Standing constraints (facts, not tasks)

  • Branch coverage cannot be measured by tooling — hand-checked on every change. coverage-standards.md.
  • Static state does not reset between xUnit test methods, the opposite of Apex's per-test-method reset. Documented, and mitigated by a naming/cleanup convention rather than "fixed" (it is inherent to how .NET statics work). salesforce-considerations.md.