Source-verified against nextgraph-rs: - nextgraph-current-state.md: NextGraph keeps ONE local oxigraph store per session; each synced repo is a named graph. sparql_query with NO anchor (UserSite/None) queries the UNION of all synced graphs (set_default_graph_as _union); with an anchor it is restricted to one repo. Union is read-only (updates need a doc anchor). No reactive SPARQL (one-shot). Root cause of the ORM fan-out hang: orm_start_graph opens every graph in scope; a fresh/unsynced per-entity doc → RepoNotFound aborts the subscription → the 75s never-fires. - read-model.md (new): the read model — events via the global index (the one enumeration hack); everything else by following a shared graph, opened/synced, then listed via a single anchorless union sparql_query (never the ORM per-doc fan-out); reactivity via re-query on a doc_subscribe/ORM change signal. Plus the minimal broker probe to confirm the union behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.8 KiB
ng-eventually
A generic polyfill layer over the NextGraph JS SDK.
NextGraph's JS SDK does not yet expose cross-wallet reads, capabilities, inboxes
or group stores. ng-eventually lets an app behave as if those existed today, by
emulating them on top of a single shared wallet / broker. It is generic:
it contains no application domain — the consumer injects its shapes and the
acts of granting access.
The name: eventually NextGraph will ship these features; until then this layer fills the gap (and nods at eventual consistency / events).
The boundary — mature face out, compensation in
The asymmetry is the whole point. Consumers write SDK-shaped code as if NextGraph were finished: per-entity documents in public/protected/private stores, capabilities, inboxes. This library owns all the current-state NextGraph knowledge and the simulation that fabricates that mature face — a shared-wallet emulation — so the application never sees it. When NextGraph matures, only this library changes; the consumer's code does not.
Docs (this library's own engineering doctrine, under docs/):
docs/nextgraph-current-state.md— the authoritative reference on what the CURRENT SDK/broker do and do NOT expose (the ground truth every polyfill compensates for).docs/simulation.md— how this lib emulates the mature behaviour on ONE shared wallet (shim, per-document ReadCaps, emulated inbox+curator, write guard, faux login, the two axes, the double-proxy constraint).docs/read-model.md— the READ MODEL the polyfill implements: events via the global index, everything else by following a shared graph; listing via a one-shot unionsparql_query(never the ORM fan-out, which hangs); reactivity via re-query on a change signal.docs/decisions/— historical current-SDK ADRs (private-store scope, SPARQL delete, shared-wallet login, discovery mechanism).docs/fork-inbox-fallback.md— the Rust-patch / self-host inbox path NOT taken (kept as fallback).docs/migration-guide.md— the checklist for when real NextGraph matures.
Packages
| Package | Role | At migration |
|---|---|---|
@ng-eventually/client |
SDK-identical wrapper the app imports instead of @ng-org/web / @ng-org/orm. Adds the polyfills the broker/verifier will do natively (shared-wallet login, capability enforcement, anticipated cap/inbox methods). |
Disappears: the app points back at the real SDK (build alias removed). |
A global-index curator package is deferred. NextGraph is mono-user with no global data (apps/services see only what the user shares; there is no multi-user backend). A global index would come from a singleton app (a global document administered by the developer) — not implemented and uncertain, and simpler paths may exist. So no second package for now; it will be (re)introduced once the global-index mechanism is decided. The curator must never be bundled in the client → it will be a separate package when it lands.
Design principle
The application code is written as if the target NextGraph existed. All compensation lives here, beside the app. Migration = remove this layer; the app code (SDK-shaped) is unchanged.
- SDK-identical surface: the client wraps the real
ng(a Proxy that forwards everything and overrides only what must be emulated) anduseShape. The real SDK is injected viaconfigure()(no hard import → build-alias safe + testable). - Authorization = emulated capabilities: documents carry grants; the client enforces them generically (read filter + write guard). The app attaches grants via cap operations — same as it will in the target. No policy is injected.
- Inbox: the client
inboxnamespace deposits (post) and, in the shared-wallet emulation, also plays the curator inline (read/materialize/watch). At migration the read side moves to a separate curator package, deferred with the global-index mechanism — see the note above. - Tests of the polyfill (against a real broker) live in this repo, so the consuming app can test its features against a mocked, clean API.
Status
Implemented. The polyfill mechanisms are wired against a real broker, not stubbed:
- Shared-wallet shim —
store-registry.ts((account, scope) → document NURI,createEntityDoc/listEntityDocs+ per-scope index, cross-device via the RDF shim anchored in the private store). - Document / SPARQL primitive —
docs.ts, calling the real injectedngdirectly (avoids the@ng-orgdouble-proxyDataCloneError). - Emulated ReadCaps —
caps.ts(CapRegistry, per-document) + read filterread-filter.ts(reactive-setProxyview), applied byuse-shape.tsonly when a policy is declared. - Write guard —
ng-proxy.ts(sparql_updateoverride, emulated write cap). - Inbox —
inbox.ts(post/read/materialize/watch, emulated curator inline). - Isolation —
isolation.ts(pure social-visibility matrix, distinct axis from ReadCaps). - Accounts —
accounts.ts(faux username login, injected storage). - SPARQL hardening —
sparql.ts(escapeLiteral/escapeIri/assertNuri).
Remaining TODO markers are narrow: the shared-wallet credential passthrough in
the login/session_start proxy branch, and the anticipated cap/inbox SDK
signatures to reconcile if the official API differs. See
docs/simulation.md for what each piece does and
docs/migration-guide.md for what is removed at
migration.