Repository navigation
writer: NextBackend on the mdbase-next SDK (opt-in) - #16
Draft
callumalpass wants to merge 8 commits into
Draft
callumalpass wants to merge 8 commits into
callumalpass wants to merge 8 commits into
Conversation
Replace the Connect MdbaseRecordLease/MdbaseRecordSessionSnapshot types in WriterBackend and the workspace with a small structural interface that Connect's sessions already satisfy, so a backend on another SDK can supply its own sessions.
The mdbase-next TS SDK is not published yet. Depend on a packed build through a file: spec until it is.
Discovery is one live query without bodies feeding the existing
CollectionStore; bodies are read on demand with get(id, {body: true});
record sessions save with update(view, {patch, body}) so concurrent edits
merge on the replica, with pending/confirmed receipts, conflict and hold
states mapped onto the workspace's conflict UI; creates retry at numbered
paths on conflict/path_taken; files are listed and downloaded and kept
current from the change feed. The 15 SDK error codes map to Writer text and
a recovery, including a waiting-for-a-device state.
…oute connectNext() loads this browser's client key and connects over the relay. The grant and route come from one small NextControlPlane interface; proposedControlPlane() implements it against a proposed endpoint (GET /v1/next/collections/:id/route) that the control workstream has not built yet. The grant is passed in until consent registers client_pk.
?demo=next serves the bundled demo manuscripts from the SDK's MemoryReplica through NextBackend. ?next&collection=…&grant=… (development builds, or VITE_WRITER_NEXT=1) connects to a real replica, with a waiting-for-a-device screen and a sync status line. The default backend stays Connect, and the default production bundle does not include the SDK.
… it late A create the replica accepted but the log later rejected with conflict/path_taken is retried as a fresh create (new record and mutation IDs) at the next numbered path, under the same 20-attempt policy, carrying any text written into the optimistic record meanwhile. NextBackend reports the move through WriterBackend.onRecordMoved: the workspace drops the rolled back record's session and draft and re-points its embed, and the app reopens a moved manuscript. Other late rejections show on the status line instead of being sent as a collection-load problem.
Contributor
Author
|
D3 (mdbase-next#167), late path collision on create: 04c4e94
I've removed this item from the "Stubbed" list. |
callumalpass
marked this pull request as draft
October 4, 2026 15:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the mdbase-next ship (sdk workstream). Adds
NextBackend, aWriterBackendon the new TS SDK@mdbase-dev/sdk. It is opt-in: Connect stays the default backend, and the default production bundle is byte-identical (the next modes are compiled out unlessDEV,VITE_WRITER_DEMO=1orVITE_WRITER_NEXT=1).SDK dependency: the SDK is not published yet. A packed build (
c573c96) is vendored atapps/writer/vendor/mdbase-dev-sdk-c573c96.tgzand used through afile:spec. This switches to a published version once one exists.What's ported
live({}, {effective: true})query without bodies. Its snapshot and diffs feed the existingCollectionStore. This replaces watch +readManyrefetch. A later snapshot (reset or reconnect) reconciles.get(id, {body: true}), only for open records, comment threads in scope and a source's annotations (8 reads at a time).readBodyis cached until a live diff touches the path.NextRecordSessionbehind the workspace's session interface. It saves withupdate(view, {patch, body}), so the SDK sendsbasefields and body edits and the replica merges concurrent edits. It uses the optimisticWrite.recordsas the next base;savinglasts untilWrite.confirmed.conflictreceipt, a confirmed receipt withstatus: conflicted, or a hold on the record (onHolds) becomes the workspace's existingconflictstate, withremoteset. The existing compare/resolve UI works. Resolve dismisses receipt conflicts or callsresolveHold.outcome_unknownrecoverystate: the receipt is looked up by mutation ID before anything new is sent.create, andupdate(view, {patch}).createat a numbered path onconflict/path_taken, replacing the/exist/regex. This also applies when the log rejects the path after the replica accepted it: a fresh create goes to the next numbered path, carrying any text written meanwhile, andonRecordMovedmoves the open manuscript or chapter embed to the new path.files.list()once,files.download()on demand. Kept current fromwatchChanges(withreset), which is the fallback feed.backend/next-errors.ts).unavailable/no_device_onlineis a waiting for one of your devices state on the gate and in a sync status line, not an error.Small refactor first (c09c322):
WriterBackend.recordsand the workspace now use a small structuralRecordSessioninterface instead of Connect'sMdbaseRecordLeaseclass types. Connect's sessions still satisfy it unchanged.Stubbed / no equivalent yet
signing: unavailable), andreviewIdentityAccessis not offered.query-metadata-v1selectprojections): discovery carries whole frontmatter (without bodies), not a projection.describe(): when a replica reports contract implementations they are used. Otherwise (MemoryReplica, and replicas untildescribecarries contracts) Writer falls back to the starter type names (writer-manuscript,reader-source, …).RecordViewhas none, so manuscript "last modified" is not shown.Blocked on the control workstream
How a web app gets a grant (registering
client_pkat consent) and a route does not exist yet. Both sit behind one interface,NextControlPlane(src/connect/next-control.ts):route()is implemented against a proposed endpoint:GET {serverUrl}/v1/next/collections/:id/route→{targets: [{url, device, noise_pk}]}. An empty list means no device is online.grant()is a stub that returns?grant=from the URL. Consent does not register the browser's key yet;loadOrCreateClientKey("mdbase-writer")creates the key that consent would register.Try it
pnpm dev, then openhttp://127.0.0.1:5320/?demo=next. This is the bundled demo collection on the SDK'sMemoryReplica, throughNextBackend. Writes stay pending for 400 ms, then confirm.const o = await writer.backend.otherClient(); const v = await o.get({path: "chapters/…"}, {body: true}); await o.update(v, {body: "…"}). The open chapter updates live. If you have unsaved text instead, the rejected merge shows the conflict dialog (MemoryReplica doesn't merge; a real replica does).?next&collection=<id>&grant=<id>[&server=…](development builds, orVITE_WRITER_NEXT=1). This needs the control-plane endpoint above.Tests
src/backend/next.test.tsandsrc/connect/next-control.test.tsrun againstMemoryReplica. They cover:getcalls, no bodies in the live set);ManuscriptWorkspaceonNextBackend;pnpm typecheckandpnpm test(app) pass: 34 files, 208 tests. Existing tests are unchanged.vite buildpasses with and without the next modes enabled.No deploys.