Pds: provide a persistent store package (ATProtoNet.Pds.EntityFrameworkCore) for IAccountStore/IRepoStore #38
Labels
No labels
breaking-change
bug
documentation
duplicate
enhancement
good first issue
help wanted
performance
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/ATProto.NET#38
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
ATProtoNet.Pdsships onlyInMemoryAccountStore/InMemoryRepoStore— fine for development, but every production consumer has to hand-roll persistentIAccountStore/IRepoStoreimplementations.For Updraft we wrote Postgres-backed stores over EF Core (
DbAccountStore,DbRepoStorein https://git.grandiras.net/Grandiras/Updraft, src/Updraft.Pds/Stores) — entities mirroringPdsAccount/RepoRecord/RepoBlob, cursor pagination on rkey, content-addressed blob dedup.Suggestion: an
ATProtoNet.Pds.EntityFrameworkCorepackage (parallel to the existingATProtoNet.Server.EntityFrameworkCoretoken store): aPdsDbContext(or model-configuration hook for an existing context), EF-backed store implementations, and anAddAtProtoPdsEfCoreStores<TContext>()registration. Our implementation can serve as a starting point.One design note from our build: if columns like handle/email are encrypted at rest with a non-deterministic scheme, equality lookups (
GetByHandleAsync) must happen in memory — worth keeping the store contract documented as "implementations may load-and-filter".🤖 On it — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
Implemented on branch
claude/issue-38(compare).One deviation from the suggestion, deliberate
The issue asks for a separate
ATProtoNet.Pds.EntityFrameworkCorepackage, parallel toATProtoNet.Server.EntityFrameworkCore. That parallel no longer holds: issue #33 mergedATProtoNet.Server.EntityFrameworkCore(andATProtoNet.Aspire) intoATProtoNet.Server, cutting the published set from 8 packages to 6. So the stores ship insideATProtoNet.Pds, under theATProtoNet.Pds.EntityFrameworkCorenamespace and aStores/EntityFrameworkCore/folder — exactly the shape the token store has today. Consumers get the same API you'd have imported, just without a second<PackageReference>.ATProtoNet.Pdsnow depends onMicrosoft.EntityFrameworkCore.Relational; you pick the provider package.What's there
AddAtProtoPdsEfCoreStores<TContext>(Action<PdsEfCoreStoreOptions>?)— registers both stores over anIDbContextFactory<TContext>and replaces the in-memory defaults. Order-independent relative toAddAtProtoPds(): the extension removes any prior registration, andAddAtProtoPds()now usesTryAddSingletonfor its in-memory fallbacks (small behaviour change, noted under Changed).PdsDbContext— standalone context, plusPdsDbContext.ConfigurePdsModel(modelBuilder)as the model-configuration hook for folding the four entities into an existing application context.EfCoreAccountStore<TContext>— case-insensitive handle/email lookups, matchingInMemoryAccountStore; translated asLOWER(col) = @p.EfCoreRepoStore<TContext>— keyset (seek) pagination onrkeywith the same exclusive-cursor contract asInMemoryRepoStore, and content-addressed blob dedup: bytes in onePdsBlobsrow per CID, aPdsBlobRefsrow per uploading account carrying its own MIME type. Deleting drops the caller's reference; the shared content goes only when the last reference does, so one account can't destroy another's copy.DeleteAllAsyncdeletes by key stub, so record values and blob bytes are never loaded just to be deleted.IAccountStore's XML docs now state that onlyGetByDidAsyncis guaranteed keyed and thatGetByHandleAsync/GetByEmailAsync/HandleExistsAsyncmay load and filter; andPdsEfCoreStoreOptions.ClientSideAccountLookupimplements that path (streams accounts, compares post-decryption), withMaxClientSideLookupRowsto bound the scan.docs/pds.mdgains a Persistent Storage (EF Core) section (setup, own-DbContext, encrypted columns); the old Custom Store Implementations sample was using a non-existentAccountInfotype, now corrected toPdsAccount. CHANGELOG updated under[Unreleased].Verified
Solution builds clean (0 errors, no new warnings from the Pds project). 1095/1095 unit tests pass, including 62 new ones. The store suite is an abstract base run against two providers: the EF in-memory provider and SQLite — the SQLite run is what proves
Handle.ToLower()and theRkey.CompareTo(cursor)cursor predicate actually translate to SQL rather than being silently client-evaluated (addedMicrosoft.EntityFrameworkCore.Sqliteto the test project only). One test pages the EF store andInMemoryRepoStoreside by side, forwards and reversed, asserting identical rkeys and cursors at every step.Not done: no migrations are checked in (the schema depends on your provider), and
PdsSamplestill uses the in-memory stores.→ Opened PR: #62
⏱ 10m 53s · 56 turns · 📜 Full session transcript
(reply recovered from the transcript — the posting step in runs before v12.3 was broken by a shell-quoting bug)