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. |