refactor: le paquet s'appelle polyfill, « SDK » désigne celui de NextGraph

Le nom @ng-eventually/sdk entrait en collision avec le SDK de NextGraph, dont
ce paquet est justement un polyfill. Impossible d'écrire « le SDK » sans lever
l'ambiguïté à chaque phrase — et le contrat publié, lu par une application,
était le pire endroit pour laisser traîner ça.

packages/sdk → packages/polyfill, @ng-eventually/sdk → @ng-eventually/polyfill,
contract_sdk-surface → contract_polyfill-surface, e2e/sdk-entry.ts →
e2e/polyfill-entry.ts, docs/sdk-reference.md → docs/polyfill-reference.md.

Les occurrences de « SDK » qui désignent celui de NextGraph restent intactes,
y compris les chemins dans nextgraph-rs (sdk/js/orm, sdk/js/web). Le tri s'est
fait occurrence par occurrence, pas par substitution.

Le contrat énonce désormais son identité en une phrase : « This package is a
polyfill of NextGraph's SDK. »
This commit is contained in:
Sylvain Duchesne
2026-08-10 17:14:25 +02:00
parent 49b046268e
commit 737729c9ce
88 changed files with 122 additions and 106 deletions
+2 -2
View File
@@ -5,6 +5,6 @@ dist/
bun.lockb
# SDK e2e real-broker harness — dedicated wallet profile + bundle (never committed)
packages/sdk/e2e/.playwright-profile-lib/
packages/sdk/e2e/.dist/
packages/polyfill/e2e/.playwright-profile-lib/
packages/polyfill/e2e/.dist/
*.ngw
+1 -1
View File
@@ -4,7 +4,7 @@
```text
«app-contract»
`reference` a NURI that names a document and grants nothing — what an application circulates (never: link, lien, share-link · see: contract_sdk-surface)
`reference` a NURI that names a document and grants nothing — what an application circulates (never: link, lien, share-link · see: contract_polyfill-surface)
`ReadCap` upstream's word for what opens a document — a reference carrying its secret (never: token, credential, permission)
`polyfill-era` a published symbol with no counterpart in the target SDK, deleted at migration (never: transitional, shim-only · see: knowledge_what-an-app-deletes-at-migration)
```
+14
View File
@@ -0,0 +1,14 @@
# Doc-debt — app-contract
> Presence of a block = doc to update. Processed → delete the block; no blocks left → delete this file.
> One block = one "big change": `why` + `files` + `verify` (leaves to review).
## The broker redirect is stated as the application's obligation, and it is not
**why**`contract_polyfill-surface.md` lists, under `### Deployment requirements`, that the application must "be opened through the broker redirect". No such obligation belongs to the caller: the redirect is a step of the shared-wallet sign-in, which lives in this package (`access-gate.ts`, "the whole shared-wallet sign-in, moved out of consumer applications"). An application carrying it would have to know there is a broker, an iframe, and a redirect — three things the target SDK will never show it, and three things it would have to delete at migration.
The clause was written from an absent implementation: nothing in `src/` navigates to the redirect today, and that gap was recorded as a division of roles. `rule_no-divergence-from-nextgraph` forbids exactly that inference.
**files** — the clause is in the contract; the fix is in the package: `ensureIdentity()` triggers the redirect itself once the identity is settled and `?ng-id=` is written into the URL, and does nothing when already inside the iframe.
**verify**`contract_polyfill-surface.md` (`### Deployment requirements` keeps only the wallet file/password and the `ensureIdentity()` await), `knowledge_what-an-app-deletes-at-migration.md` (the redirect is one more thing that evaporates), `_overview.md` if the surface list changes.
+7 -7
View File
@@ -1,19 +1,19 @@
---
type: overview
summary: What an application may rely on from @ng-eventually/sdk, and what it will have to delete
summary: What an application may rely on from @ng-eventually/polyfill, and what it will have to delete
triggers:
keywords: [polyfill, sdk, surface, contract, publish, published, entry, export, migration, unlearn, consumer, app-facing]
paths:
- "packages/sdk/src/index.ts"
- "packages/sdk/src/surface/**"
- "packages/sdk/README.md"
- "packages/polyfill/src/index.ts"
- "packages/polyfill/src/surface/**"
- "packages/polyfill/README.md"
- "examples/notebook/**"
- "docs/api-contract.md"
vocabulary:
- term: reference
gloss: a NURI that names a document and grants nothing — what an application circulates
not: [link, lien, share-link]
see: contract_sdk-surface
see: contract_polyfill-surface
- term: ReadCap
gloss: upstream's word for what opens a document — a reference carrying its secret
not: [token, credential, permission]
@@ -29,10 +29,10 @@ This library exists so an application can be **written today against the NextGra
The distinguishing question, asked at every choice — published signature as much as internal mechanism: **does the target do this?** If it does it differently, we do it their way, whatever that costs. *Would a caller have to unlearn it?* comes second, and measures how bad a divergence is — never whether one is allowed. See `rule_no-divergence-from-nextgraph`.
This repo is the **provider** of `contract_sdk-surface`; consuming applications live in other repos and pull it. The per-symbol ruling, with an epistemic label on every target-side claim, stays here in `docs/api-contract.md` — that is maintainer material, not the engagement.
This repo is the **provider** of `contract_polyfill-surface`; consuming applications live in other repos and pull it. The per-symbol ruling, with an epistemic label on every target-side claim, stays here in `docs/api-contract.md` — that is maintainer material, not the engagement.
## Read first
- `contract_sdk-surface` — the engagement itself, written from the caller's point of view.
- `contract_polyfill-surface` — the engagement itself, written from the caller's point of view.
- `rule_no-divergence-from-nextgraph` — the test that decides what may be built at all.
- `knowledge_what-an-app-deletes-at-migration` — the two fates a published symbol can have.
@@ -1,12 +1,14 @@
---
type: contract
summary: The API @ng-eventually/sdk exposes to an application — signatures, guaranteed behaviour, and what it does not offer
summary: The API @ng-eventually/polyfill exposes to an application — signatures, guaranteed behaviour, and what it does not offer
---
# contract_sdk-surface — `@ng-eventually/sdk`
# contract_polyfill-surface — `@ng-eventually/polyfill`
## Scope
This package is a polyfill of NextGraph's SDK.
This package covers placement (creating and listing an application's documents by scope), reading (a document's subjects, one-shot or reactive), sharing a document with a named user, and depositing into inboxes. It does not cover user management, display names, transport, or the operation of a deployment.
### Deployment requirements
@@ -19,7 +21,7 @@ An application using this package must:
## Surface
Full typed shape: the package's `types` entry, `@ng-eventually/sdk`. A type is published only when a published signature uses it. The load-bearing signatures:
Full typed shape: the package's `types` entry, `@ng-eventually/polyfill`. A type is published only when a published signature uses it. The load-bearing signatures:
```ts
// ── bootstrap ────────────────────────────────────────────────────────────
@@ -7,7 +7,7 @@ summary: The two fates a published symbol can have, and why the deletion list is
Every published symbol has exactly one of two fates, and knowing which is the whole point of this boundary.
**Replaced in place.** The build alias resolving `@ng-eventually/sdk` is removed, the import resolves to the real SDK, and the application's code is unchanged. This is almost everything: `ng`, `useShape`, `watchShape`, `init`, `initNg`, `readUnion`, `subscribeDoc(s)`, `docs.*`, `inbox.*`, `storeRegistry.*`, and the types.
**Replaced in place.** The build alias resolving `@ng-eventually/polyfill` is removed, the import resolves to the real SDK, and the application's code is unchanged. This is almost everything: `ng`, `useShape`, `watchShape`, `init`, `initNg`, `readUnion`, `subscribeDoc(s)`, `docs.*`, `inbox.*`, `storeRegistry.*`, and the types.
**Deleted.** The symbol has no counterpart at any level of the target, exists only because one shared wallet hosts every user, and goes with its call site. Today that is `configure` and its config type.
+3 -3
View File
@@ -1,11 +1,11 @@
# Inter-repo contracts. `publish:` is this project's engagement toward its consumers —
# listing a leaf here IS the act of publishing it; an unlisted `contract_` leaf is a draft.
#
# This project is a PROVIDER only for now: the applications that consume the SDK live in
# their own repos and pull `sdk-surface` from here. Nothing is consumed in the other
# This project is a PROVIDER only for now: the applications that consume the polyfill live in
# their own repos and pull `polyfill-surface` from here. Nothing is consumed in the other
# direction — the NextGraph clone this library aligns on is a read-only reference we
# verify against, not a contract anyone stamps.
publish:
# paths are relative to `.project/`
sdk-surface: concepts/app-contract/contract_sdk-surface.md
polyfill-surface: concepts/app-contract/contract_polyfill-surface.md
+3 -3
View File
@@ -67,7 +67,7 @@ is needed), and how this lib emulates it today.
| Package | Role |
|---|---|
| `@ng-eventually/sdk` *(was `@ng-eventually/client` until 2026-08-07)* | The SDK-identical wrapper the app imports instead of `@ng-org/web` / `@ng-org/orm`. It adds the polyfills the broker/verifier will do natively (shared-wallet identity, capability enforcement, anticipated cap/inbox methods). As NextGraph matures, the app points back at the real SDK (build alias removed) and this package falls away. |
| `@ng-eventually/polyfill` *(was `@ng-eventually/client` until 2026-08-07)* | The SDK-identical wrapper the app imports instead of `@ng-org/web` / `@ng-org/orm`. It adds the polyfills the broker/verifier will do natively (shared-wallet identity, capability enforcement, anticipated cap/inbox methods). As NextGraph matures, the app points back at the real SDK (build alias removed) and this package falls away. |
A global-index package is deferred. Data common to all of an application's users comes from a **singleton app**: a document or store shared by all users and hardcoded in the app, write-owned by the developer and delegable — but never to all users, so user contributions reach it **through an inbox** (nothing in NextGraph is freely writable by everyone). That is the direction the NextGraph developer has named; it is **not implemented**, and several points are still open (what exactly is hardcoded, how delegation travels, who materializes the inbox). So there is no second package for now — it will be introduced once the mechanism exists, and it will be separate from the SDK wrapper. See [`docs/nextgraph-current-state.md`](docs/nextgraph-current-state.md) § Apps & services.
@@ -169,8 +169,8 @@ the unused list" is not a reason to investigate it.
emulation, reads the deposits back (`read` / `materialize` / `watch`) in place
of the recipient's own inbox processing.
- Tests of the polyfill (against a real broker) live in this repo, in **two** suites,
and the split is deliberate: `packages/sdk/e2e/run.ts` (`test:e2e`) characterises the
primitives and the platform contracts, while `packages/sdk/e2e/notebook.ts`
and the split is deliberate: `packages/polyfill/e2e/run.ts` (`test:e2e`) characterises the
primitives and the platform contracts, while `packages/polyfill/e2e/notebook.ts`
(`test:e2e:app`) drives the example application through the DOM, one browser page per
identity. Only the second can tell whether an application is *writable* — a harness
can pass a value between two identities through a variable, and an application cannot.
+17 -17
View File
@@ -1,10 +1,10 @@
# API contract — what `@ng-eventually/sdk` exposes today, and what the future SDK should expose per subject
# API contract — what `@ng-eventually/polyfill` exposes today, and what the future SDK should expose per subject
> **Updated 2026-08-03, after the source layout was reorganised by migration fate** (`docs/source-layout-by-fate.md`). Paths, and three names, changed under this document: `readModel` became the directly-exported `readUnion`; `accounts` / `AccountRecord` / `AccountStorage` became `virtualUsers` / `VirtualUserRecord` / `VirtualUserStorage` (module `shared-wallet/virtual-users.ts`); `store-registry-api.ts` became `surface/placement.ts`. Two modules were created and are covered here: `emulated-verifier/branch-registers.ts` (the four durable registers, split out of the shim) and `shared-wallet/bootstrap.ts` (the injection store, split out of the `/polyfill` entry). The subject-by-subject rulings below are unaffected — what moved is where the code lives, not what it promises.
**Scope: the APP-FACING contract only.** Everything reachable from the two published entry points, and nothing else. The library's internal modules — the shim machinery, the read paths, the boundary guards — are held to the same standard (as close as possible to what NextGraph does or plans) but have their own document, `docs/internal-contract.md`: a consumer never reads that one, a maintainer does. This split was made on 2026-08-03, together with the export change described in § 15.
**Scope.** The real exported surface of `@ng-eventually/sdk` (verified against the `export` statements in `packages/sdk/src/index.ts` and `packages/sdk/src/polyfill.ts``package.json` maps exactly two entry points, `.` and `./polyfill`), and, for each subject, the target signature the future NextGraph JS SDK is expected to expose. Written 2026-08-03, verified against the `nextgraph-rs` clone (HEAD `213338f6`, 2026-05-16) and the installed `@ng-org/web@0.1.2-alpha.13` type declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
**Scope.** The real exported surface of `@ng-eventually/polyfill` (verified against the `export` statements in `packages/polyfill/src/index.ts` and `packages/polyfill/src/polyfill.ts``package.json` maps exactly two entry points, `.` and `./polyfill`), and, for each subject, the target signature the future NextGraph JS SDK is expected to expose. Written 2026-08-03, verified against the `nextgraph-rs` clone (HEAD `213338f6`, 2026-05-16) and the installed `@ng-org/web@0.1.2-alpha.13` type declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
**How to read the epistemic labels.** Every target-side claim carries one of:
@@ -19,7 +19,7 @@ Per the design principle (`README.md` § *Design principle*): an absent implemen
## 1. Bootstrap and configuration
### Today — `@ng-eventually/sdk`: **one call**
### Today — `@ng-eventually/polyfill`: **one call**
```ts
// shared-wallet/bootstrap.ts
@@ -57,7 +57,7 @@ So an application's whole bootstrap is `configure({ … })` plus `await ensureId
## 2. Lifecycle
### Today — `@ng-eventually/sdk`
### Today — `@ng-eventually/polyfill`
```ts
// lifecycle.ts:11 — forwards to the real @ng-org/web init injected at configure()
@@ -94,7 +94,7 @@ Divergence: none in behaviour (pure forwarding), but the wrapper erases the para
## 2bis. The access gate — `ensureIdentity`
### Today — `@ng-eventually/sdk`
### Today — `@ng-eventually/polyfill`
```ts
export async function ensureIdentity(): Promise<PrincipalId>; // shared-wallet/access-gate.ts
@@ -123,7 +123,7 @@ Why it lives here and not in the consumer application: the first consumer had ~3
## 3. The `ng` object
### Today — `@ng-eventually/sdk`
### Today — `@ng-eventually/polyfill`
```ts
// index.ts:55
@@ -147,7 +147,7 @@ The two overrides:
## 4. Reactive typed reads — `useShape`
### Today — `@ng-eventually/sdk`
### Today — `@ng-eventually/polyfill`
```ts
// use-shape.ts:12
@@ -185,7 +185,7 @@ Divergence to note: the wrapper types everything `unknown`, losing the generic `
## 5. Reactive typed reads with load state — `watchShape`
### Today — `@ng-eventually/sdk`
### Today — `@ng-eventually/polyfill`
```ts
// watch-shape.ts:73
@@ -226,7 +226,7 @@ So the constraint on the bet: the target can already answer "synced?" (`readyPro
## 6. One-shot listing — the read-model
### Today — `@ng-eventually/sdk`
### Today — `@ng-eventually/polyfill`
```ts
// read-model.ts:60
@@ -264,7 +264,7 @@ The anchored-read mechanics are level-1 VERIFIED: an anchor restricts the query
## 7. Raw document / SPARQL primitives — `docs.*`
### Today — `@ng-eventually/sdk` (namespace `docs`)
### Today — `@ng-eventually/polyfill` (namespace `docs`)
```ts
// docs.ts:46
@@ -324,7 +324,7 @@ So the target's direction for scope placement is **already visible in the source
## 8. Per-document subscription — `subscribeDoc`
### Today — `@ng-eventually/sdk`
### Today — `@ng-eventually/polyfill`
```ts
// subscribe.ts:47,60,79
@@ -362,7 +362,7 @@ declare function doc_subscribe(repo_o: string, session_id: any, callback: Functi
## 9. Inbox — deposits, and cap delivery
### Today — `@ng-eventually/sdk` (namespace `inbox`)
### Today — `@ng-eventually/polyfill` (namespace `inbox`)
```ts
export interface Deposit {
@@ -417,7 +417,7 @@ Consequences per function:
### Today
```ts
// @ng-eventually/sdk — model/types.ts. The published cap surface is now ONE type.
// @ng-eventually/polyfill — model/types.ts. The published cap surface is now ONE type.
export type Nuri = `did:ng:${string}`;
export type NuriLike = Nuri | string;
@@ -504,7 +504,7 @@ The escaping helpers were published until the surface was narrowed. Their remova
## 12. Scope resolution, per-entity documents, and the store registry
### Today — `@ng-eventually/sdk` (namespace `storeRegistry`) — plus `Scope` from `types.ts`
### Today — `@ng-eventually/polyfill` (namespace `storeRegistry`) — plus `Scope` from `types.ts`
> **Narrowed twice.** 2026-08-03 the entry stopped re-exporting the whole `store-registry` module and kept an app-facing slice (`src/surface/placement.ts`). 2026-08-05 that slice lost its two inbox-ADDRESS functions as well: an application deposits with `inbox.postToDocument(doc, …)` and shares with `inbox.share(doc, toUser)` — always naming a document or a person, never an address, because upstream an address is resolved from a profile and never handled by a caller. **Five functions remain published**, listed first below; everything after them is kept for the record and is covered by `docs/internal-contract.md`.
@@ -599,7 +599,7 @@ declare function user_disconnect(user_id: string): Promise<void>;
## 14. Type re-exports
`@ng-eventually/sdk` re-exports, type-only (erased at build, `src/index.ts`):
`@ng-eventually/polyfill` re-exports, type-only (erased at build, `src/index.ts`):
```ts
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
@@ -658,9 +658,9 @@ Exported, but not SDK surface. Coding against these builds knowledge that migrat
## Appendix — full export inventory (for diffing)
*Generated from the `export` statements, and pinned by `packages/sdk/test/vocabulary.test.ts` — if this list and the code disagree, that test fails. It went stale once, still listing `storeRegistry`'s shim internals after the entry had been narrowed, which is what a hand-maintained inventory does.*
*Generated from the `export` statements, and pinned by `packages/polyfill/test/vocabulary.test.ts` — if this list and the code disagree, that test fails. It went stale once, still listing `storeRegistry`'s shim internals after the entry had been narrowed, which is what a hand-maintained inventory does.*
### `@ng-eventually/sdk` — `src/index.ts` (the only entry since 2026-08-07)
### `@ng-eventually/polyfill` — `src/index.ts` (the only entry since 2026-08-07)
```text
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, EventuallyConfig, NG, NgLike, Nuri, NuriLike, PrincipalId, RegistrySession, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, configure, docChangeType, ensureIdentity, init, initNg, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape
+1 -1
View File
@@ -47,7 +47,7 @@ breaks writes with `RepoNotFound`). See the scope rule in
*The decision stands; the mechanism named in it has been replaced.* Opening was
`orm_start_graph` when this was written. It is now `ensureRepoOpen``doc_subscribe`
plus a wait for the first `State` (`packages/sdk/src/emulated-verifier/open-repo.ts:167`) — after
plus a wait for the first `State` (`packages/polyfill/src/emulated-verifier/open-repo.ts:167`) — after
`orm_start_graph` was found to hang on a fan-out (`subscribe.ts:28,181`). What must be
read here is the invariant *"open the repo, by its store NURI, before writing"*, not the
call that used to implement it.
+3 -3
View File
@@ -70,13 +70,13 @@ The surface consequence: the *act* — obtain a link, circulate it — is the sa
## 5. Recommendation for the polyfill
**SUPERSEDED on its first point, 2026-08-06 — `linkTo` was REMOVED, and the removal stands.** This section used to open: *"The surface already exists: `linkTo(doc: NuriLike): ReadCap` … `inbox.share(doc, toUser)` for directed delivery. **Keep `linkTo` — the act is the right one**"*. What that recommendation missed is the single thing § 0 of `docs/readcap-and-nuri-model.md` exists to hold: a call that returns a document's **key** where a caller asked for its **reference** converts *"whoever has the reference AND the key reads"* into *"whoever has the reference reads"* — for that document and for every document it mentions — so confidentiality can no longer be composed inside anything one circulates. The *act* (circulate a value that opens a document) may still be right; making it the answer to "give me the link to my document" was not. The reasoning is recorded where the function was, in the `No linkTo here` block of `packages/sdk/src/surface/placement.ts`.
**SUPERSEDED on its first point, 2026-08-06 — `linkTo` was REMOVED, and the removal stands.** This section used to open: *"The surface already exists: `linkTo(doc: NuriLike): ReadCap` … `inbox.share(doc, toUser)` for directed delivery. **Keep `linkTo` — the act is the right one**"*. What that recommendation missed is the single thing § 0 of `docs/readcap-and-nuri-model.md` exists to hold: a call that returns a document's **key** where a caller asked for its **reference** converts *"whoever has the reference AND the key reads"* into *"whoever has the reference reads"* — for that document and for every document it mentions — so confidentiality can no longer be composed inside anything one circulates. The *act* (circulate a value that opens a document) may still be right; making it the answer to "give me the link to my document" was not. The reasoning is recorded where the function was, in the `No linkTo here` block of `packages/polyfill/src/surface/placement.ts`.
What an application does instead, today: it names a document with the bare reference it already holds — every published call returns one — and grants access with `inbox.share(doc, toUser)` (`packages/sdk/src/surface/inbox.ts`). The four points below are kept and re-read against that: 1 and 4 stand as written for **any** future link-producing surface; 2 is void with the function; 3 is unchanged and still open.
What an application does instead, today: it names a document with the bare reference it already holds — every published call returns one — and grants access with `inbox.share(doc, toUser)` (`packages/polyfill/src/surface/inbox.ts`). The four points below are kept and re-read against that: 1 and 4 stand as written for **any** future link-producing surface; 2 is void with the function; 3 is unchanged and still open.
1. **Label any such call LEVEL-1 SHAPE in `docs/api-contract.md`.** What supports it: the `NgLink` family and its stated sharing flow, the `PermaShare` permission, the exercised object-URL and profile-QR precedents, and the PO doctrine that circulation is the only distribution. The model's own stated flows are unusable without *some* produce-a-link affordance, which is as much confidence as an unbuilt feature allows. What cannot be promised: the SDK's name for it, sync vs async (upstream link-building needs overlay + peers from the session, so async is plausible — same adapter-sized delta class as `subscribeDoc`'s sync unsubscribe), and whether the value is a NURI string or a structured link. Therefore: **the returned value is opaque**; a consumer that stores it, transmits it, and hands it back unmodified learns nothing to unlearn; a consumer that parses it does.
2. ~~**Fix the comment-vs-code mismatch in `linkTo`.**~~ **VOID — the function is gone.** The observation it rested on survives and is worth keeping: `RepoLinkV0`-with-key IS the protected-document link, and circulating it out-of-band is the documented normal case (`:5059`), so a protected link carrying its key is not in itself a violation — with the § 4 durability caveat. What made `linkTo` wrong was not that the value carried a key; it was that a caller got one **by asking for a reference**. Handing over a key must be its own act, which is what `inbox.share` is.
3. **The recipient verb is missing.** Nothing exported ingests an out-of-band link: `learn` is reached only by inbox processing and the connection drain (`packages/sdk/src/surface/inbox.ts:410`, `packages/sdk/src/emulated-verifier/connect.ts:68`), and `getCaps()` is documented machinery (api-contract §15). The model names the recipient act precisely — open the link: load the repo from its read cap, file `AddLink` durably on the User branch, subscribe (`:5059`; `engine/repo/src/types.rs:1934-1950`; `verifier.rs:2237`). Suggested surface, same epistemic label as point 1: `openLink(link: string): Promise<Nuri>` — files the cap in the emulated registers and returns the cap-less target for use in reads. Without it, path 2 has a producer and no consumer, and the multi-actor test where Bob *obtains* the document through calls (never through a shared variable) cannot be written — the exact failure mode `rules/engineering/multi-actor-tests-obtain-not-receive.md` records.
3. **The recipient verb is missing.** Nothing exported ingests an out-of-band link: `learn` is reached only by inbox processing and the connection drain (`packages/polyfill/src/surface/inbox.ts:410`, `packages/polyfill/src/emulated-verifier/connect.ts:68`), and `getCaps()` is documented machinery (api-contract §15). The model names the recipient act precisely — open the link: load the repo from its read cap, file `AddLink` durably on the User branch, subscribe (`:5059`; `engine/repo/src/types.rs:1934-1950`; `verifier.rs:2237`). Suggested surface, same epistemic label as point 1: `openLink(link: string): Promise<Nuri>` — files the cap in the emulated registers and returns the cap-less target for use in reads. Without it, path 2 has a producer and no consumer, and the multi-actor test where Bob *obtains* the document through calls (never through a shared variable) cannot be written — the exact failure mode `rules/engineering/multi-actor-tests-obtain-not-receive.md` records.
4. **Do not add**: link options (expiry, audience, revoke-this-link), per-reader introspection for public documents, or any API that parses or inspects a link's insides — nothing upstream supports any of them, and each teaches a lever the model does not have.
## 6. The question for the NextGraph developer
@@ -37,7 +37,7 @@ Interpretation (**plausible mechanism, not settled**): the write was pushed into
## What the SDK exposes but does not consume
`disconnections_subscribe` **does fire** on this failure — but neither the polyfill (`@ng-eventually/sdk`) nor the consumer app subscribes to it. The signal exists, nobody listens to it; on the app side, no mechanism retries or warns the user.
`disconnections_subscribe` **does fire** on this failure — but neither the polyfill (`@ng-eventually/polyfill`) nor the consumer app subscribes to it. The signal exists, nobody listens to it; on the app side, no mechanism retries or warns the user.
## Scope & not reproduced
+2 -2
View File
@@ -1,8 +1,8 @@
# Internal contract — what `@ng-eventually/sdk` keeps off its surface, and what NextGraph does or would do about each subject
# Internal contract — what `@ng-eventually/polyfill` keeps off its surface, and what NextGraph does or would do about each subject
> **Updated 2026-08-03, after the source layout was reorganised by migration fate** (`docs/source-layout-by-fate.md`). Paths, and three names, changed under this document: `readModel` became the directly-exported `readUnion`; `accounts` / `AccountRecord` / `AccountStorage` became `virtualUsers` / `VirtualUserRecord` / `VirtualUserStorage` (module `shared-wallet/virtual-users.ts`); `store-registry-api.ts` became `surface/placement.ts`. Two modules were created and are covered here: `emulated-verifier/branch-registers.ts` (the four durable registers, split out of the shim) and `shared-wallet/bootstrap.ts` (the injection store, split out of the `/polyfill` entry). The subject-by-subject rulings below are unaffected — what moved is where the code lives, not what it promises.
**Scope.** The complement of [`docs/api-contract.md`](./api-contract.md): every module export under `packages/sdk/src/` that is NOT reachable from the two published entry points (`package.json` maps exactly `.``src/index.ts` and `./polyfill``src/polyfill.ts`). A consumer never reads this document; a maintainer does. The internal code is held to the same standard as the surface — as close as possible to what NextGraph does or plans — so every subject below carries the same target-side analysis. Written 2026-08-04, verified against the `nextgraph-rs` clone (HEAD `213338f6`) and the installed `@ng-org/web@0.1.2-alpha.13` declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
**Scope.** The complement of [`docs/api-contract.md`](./api-contract.md): every module export under `packages/polyfill/src/` that is NOT reachable from the two published entry points (`package.json` maps exactly `.``src/index.ts` and `./polyfill``src/polyfill.ts`). A consumer never reads this document; a maintainer does. The internal code is held to the same standard as the surface — as close as possible to what NextGraph does or plans — so every subject below carries the same target-side analysis. Written 2026-08-04, verified against the `nextgraph-rs` clone (HEAD `213338f6`) and the installed `@ng-org/web@0.1.2-alpha.13` declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
**How the boundary was computed — mechanically, from the `export` statements.** `index.ts` re-exports wholesale (`export *` / `export * as ns`) from `types.ts`, `inbox.ts`, `docs.ts`, `surface/read-model.ts`, and by name everything `surface/use-shape.ts`, `surface/watch-shape.ts`, `lifecycle.ts`, `sparql.ts` export, and `subscribeDoc`/`subscribeDocs`/`docChangeType` (+ types) from `subscribe.ts`; its `storeRegistry` namespace is the **`surface/placement.ts` slice only** (`createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `openDocumentInbox`). `model/nuri.ts` is now internal in full — the guards `isNuri`/`hasReadCap` stopped being published when the entries became permissive-in (`NuriLike` validated at the door). *(The second entry, `polyfill.ts`, was merged into `index.ts` on 2026-08-07, and the polyfill-era block was then cut to ONE published call, `configure`. `configureStoreRegistry`, `setCurrentUser` and `connectedUser` became internal the same day — folded, replaced by the gate, and awaited inside it respectively; the machinery accessors and test resets are internal too. All four are inventoried below.)* Everything else that carries `export` in a `src/` module is internal and inventoried here. Eight modules are internal in their entirety: `shared-wallet/access-log.ts`, `emulated-verifier/machinery.ts`, `surface/ng-proxy.ts`, `emulated-verifier/open-repo.ts`, `shared-wallet/outbox-log.ts`, `shared-wallet/physical.ts`, `emulated-verifier/reach.ts`, `emulated-verifier/read-filter.ts`. Four are internal in part: `nuri.ts`, `emulated-verifier/connect.ts`, `subscribe.ts`, `shared-wallet/account-registry.ts`.
+1 -1
View File
@@ -99,7 +99,7 @@ The consumer application imports `@ng-org/web` / `@ng-org/orm` resolved to this
via a build alias during the polyfill period. Removing the alias makes those imports
resolve to the real SDK — the `ng`/`useShape`/`inbox` surface is SDK-identical, so
no consumer code changes. The one non-SDK call — `configure(...)` /
the POLYFILL-ERA block of `@ng-eventually/sdk` — is deleted. The lib itself disappears.
the POLYFILL-ERA block of `@ng-eventually/polyfill` — is deleted. The lib itself disappears.
## The one break already taken: `declareConnections`
+5 -5
View File
@@ -1,6 +1,6 @@
# Current-state NextGraph — what the SDK/broker do and do NOT expose
**Owner:** this library. `@ng-eventually/sdk` exists because the *current*
**Owner:** this library. `@ng-eventually/polyfill` exists because the *current*
NextGraph JS SDK is immature. This file is the authoritative reference on what
today's SDK/broker actually give us — the ground truth every polyfill in this
lib compensates for. Read [`simulation.md`](./simulation.md) for how we emulate
@@ -333,7 +333,7 @@ has both**:
- **Subscribable with a sync BARRIER.** `doc_subscribe(nuri)` delivers `TabInfo` then
an initial **`State`** (`verifier.rs:470`/`:476`); that first `State` is the sync
barrier — **after it, presence is guaranteed and absence is definitive** (pinned
empirically by CONTRACT 3 in `packages/sdk/e2e/`). But this barrier exists only
empirically by CONTRACT 3 in `packages/polyfill/e2e/`). But this barrier exists only
for a repo `doc_subscribe` can open, i.e. a `did:ng:o:<RepoID>` repo. A **store-root
has no first-`State` barrier**: an anchored read on it can return 0 rows during
sync-lag with no signal distinguishing "still syncing" from "genuinely empty".
@@ -757,7 +757,7 @@ chain never runs and consumers keep a stale value until the next connection
delivers a fresh initial `State`. REMOTE commits DO push correctly (verified:
cross-browser reactive update works). Verdict pending a live instrumented run.
Full write-up (suspect link, instrumentation, planned polyfill-side fix):
[`../packages/sdk/docs/sdk-reference.md`](../packages/sdk/docs/sdk-reference.md)
[`../packages/polyfill/docs/polyfill-reference.md`](../packages/polyfill/docs/polyfill-reference.md)
§ *Current emulation status*.
### Cold-start anchored read returns 0 rows instead of an error — symptom VERIFIED, mechanism INFERRED, healed polyfill-side
@@ -766,7 +766,7 @@ On a FRESH session over the SAME persistent wallet (reconnect, new page, re-logi
anchored `sparql_query` against a document written in an earlier session comes back with
**0 rows and no error** — persisted documents read as empty. Observed on every anchored
reader of the polyfill and healed identically in each (`ensureRepoOpen` before the read,
`packages/sdk/src/emulated-verifier/open-repo.ts`): the user's own documents,
`packages/polyfill/src/emulated-verifier/open-repo.ts`): the user's own documents,
the user's store (`shared-wallet/account-registry.ts` `readUserStore`), the by-need doc batch
(`surface/read-model.ts` `readUnion`), and the store-root pointer read (`shared-wallet/account-registry.ts`
`resolvePointer`). The heal is `doc_subscribe(nuri)` → await the first `State` (the sync
@@ -802,7 +802,7 @@ is a single account subject carrying MULTIPLE values for one scope predicate (ob
five `shim:docPublic`), after which a writer and a later reader can resolve DIFFERENT
scope docs and the reader's anchored read returns 0.
Two polyfill-side guards, both in `packages/sdk/src/shared-wallet/account-registry.ts`: `ensureInFlight`
Two polyfill-side guards, both in `packages/polyfill/src/shared-wallet/account-registry.ts`: `ensureInFlight`
(a bounded promise map keyed by account, so concurrent `ensureAccount` calls share ONE
resolve-or-provision) prevents new forks; `canonicalDoc` (pick the lexicographically
smallest NURI among all distinct values for a scope predicate — NURIs are
+3 -3
View File
@@ -5,7 +5,7 @@ shared wallet. This is a design decision, grounded entirely in the query
capability documented in
[`nextgraph-current-state.md`](./nextgraph-current-state.md) § *The query
capability*. The consumer application never sees any of this: it asks
`@ng-eventually/sdk` for its lists by need and trusts the answer — the whole
`@ng-eventually/polyfill` for its lists by need and trusts the answer — the whole
read mechanism lives here, in the polyfill.
> The rule in one line: read each by-need doc with its own anchored
@@ -111,7 +111,7 @@ Keep the reactive ORM strictly to already-opened single stores; it is a change
## The boundary with the consumer application
The consumer application asks the SDK for its lists by need and trusts the returned
The consumer application asks the polyfill for its lists by need and trusts the returned
set. It never constructs a NURI, never picks the union-vs-anchor mode, never touches
the ORM. The domain-shaped list helpers (e.g. "my meeting points", "events") live in
the consumer application, not the lib; the lib exposes the generic by-need read.
@@ -172,7 +172,7 @@ and never iterates the other named graphs. (A repo absent from `self.repos` thro
`RepoNotFound` and is skipped per-doc, see the VERIFIED note above — the read cannot
sync an unknown repo.)
> **Re-confirmed by the standing e2e harness (`packages/sdk/e2e/`, broker
> **Re-confirmed by the standing e2e harness (`packages/polyfill/e2e/`, broker
> `@ng-org/web 0.1.2-alpha.13`).** The `docRoundTrip` check measures all three shapes
> anchored to a doc D: (a) a no-GRAPH default-graph write round-trips; (b) an explicit
> `INSERT DATA { GRAPH <D> {…} }` — a **constant** plain NURI — **also** round-trips
+1 -1
View File
@@ -378,7 +378,7 @@ One thing a consumer must not conclude from the emulation: that a public documen
## 5. What the polyfill emulates (caps.ts) — and where it still diverges
**Realigned 2026-07-28 (batch P1a).** `packages/sdk/src/emulated-verifier/caps.ts` used to model `readers: Map<Nuri, Set<PrincipalId>>` + `grantRead(doc, grantee)` — a per-document **ACL of principals**, the exact INVERSION of the real model. It now records, **per identity**, the caps that identity holds (`Map<Nuri, ReadCap>`) — whose only question is `capFor(nuri)` — and `nuri.ts` carries the cap-less / cap-bearing distinction on the `r:` segment. The durable registers are emulated in `shared-wallet/account-registry.ts` (`readCap` on the Store branch, `link` on the User branch); this in-memory record is their cache.
**Realigned 2026-07-28 (batch P1a).** `packages/polyfill/src/emulated-verifier/caps.ts` used to model `readers: Map<Nuri, Set<PrincipalId>>` + `grantRead(doc, grantee)` — a per-document **ACL of principals**, the exact INVERSION of the real model. It now records, **per identity**, the caps that identity holds (`Map<Nuri, ReadCap>`) — whose only question is `capFor(nuri)` — and `nuri.ts` carries the cap-less / cap-bearing distinction on the `r:` segment. The durable registers are emulated in `shared-wallet/account-registry.ts` (`readCap` on the Store branch, `link` on the User branch); this in-memory record is their cache.
| | Real NextGraph | caps.ts emulation (post-P1a) |
|---|---|---|
+4 -4
View File
@@ -11,7 +11,7 @@
> file for *how* each emulation works; read those two for *what is fake* and *what
> replaces it*.
The consumer application writes against `@ng-eventually/sdk` as if NextGraph
The consumer application writes against `@ng-eventually/polyfill` as if NextGraph
already shipped per-entity documents in public/protected/private stores, capabilities
and inboxes. It hasn't (see [`nextgraph-current-state.md`](./nextgraph-current-state.md)).
This file is the lib's own engineering doctrine on how it fabricates that mature
@@ -187,7 +187,7 @@ virtual/physical distinction dissolves (see [`migration-guide.md`](./migration-g
### SDK-shaped scope resolvers — the consumer application holds no store-id
The consumer application must never construct a `did:ng:${store_id}` NURI itself:
physical placement is the lib's job (the whole point of the SDK boundary). Two
physical placement is the lib's job (the whole point of the polyfill boundary). Two
resolvers turn a logical scope into an opaque graph NURI without exposing any
store-id:
@@ -452,7 +452,7 @@ the target's behaviour.
The old item-level application-visibility filter (the since-deleted `isolation.ts`
`applyIsolation`, a `Set`-of-records filter keyed on owner+scope) is retired
from the consumer path: the application carries no access logic — it declares its
identity and shares caps, and trusts the SDK. Its matrix functions are
identity and shares caps, and trusts the polyfill. Its matrix functions are
dead scaffolding kept for reference and removed at migration. There is no longer a
second, coexisting app-layer filter to reconcile — the single axis is the
per-document cap, exactly as in the target.
@@ -639,5 +639,5 @@ document trust root):
(`did:ng:...`): validates and throws on IRI-breaking chars rather than emitting
a malformed/injected query.
These are re-exported from `@ng-eventually/sdk` so the consumer application
These are re-exported from `@ng-eventually/polyfill` so the consumer application
reuses the same escaping when it builds SPARQL.
+2 -2
View File
@@ -1,6 +1,6 @@
# Source layout by migration fate — analysis and recommendation
**Status: ADOPTED.** The layout this document recommends is the one in the tree — `model/`, `surface/`, `emulated-verifier/`, `shared-wallet/`. Read the rest as the reasoning that produced it, not as a proposal. *(Two things have moved under it since: the package became `packages/sdk` / `@ng-eventually/sdk` on 2026-08-07 — it was `packages/client` / `@ng-eventually/client`, kept here as a search keyword — and `emulated-verifier/public-store.ts` was added on 2026-08-06. Paths below point at where the files are now.)*
**Status: ADOPTED.** The layout this document recommends is the one in the tree — `model/`, `surface/`, `emulated-verifier/`, `shared-wallet/`. Read the rest as the reasoning that produced it, not as a proposal. *(Two things have moved under it since: the package became `packages/polyfill` / `@ng-eventually/polyfill` on 2026-08-10 — it was `packages/client` / `@ng-eventually/client`, kept here as a search keyword — and `emulated-verifier/public-store.ts` was added on 2026-08-06. Paths below point at where the files are now.)*
Written 2026-08-04, when nothing had yet been moved, from the source of `src/` (25 modules), the two contracts (`docs/api-contract.md`, `docs/internal-contract.md`) used as the export-level inventory, and the read-only `nextgraph-rs` clone (HEAD `213338f6`); the upstream facts this analysis leans on were re-verified at the source and are cited with layer numbers per `README.md` § *The three references* (1 = engine, 2 = wasm binding, 3 = JS ORM).
@@ -136,7 +136,7 @@ Full mixed list: `docs.ts`, `surface/read-model.ts`, `inbox.ts`, `subscribe.ts`,
- All 23 non-entry `src/` modules import each other relatively; every moved file churns its importers' paths (mechanical, type-checked).
- 19 of 19 unit-test files deep-import `../src/*` — 66 static import lines (heaviest: `store-registry` ×16, `polyfill` ×14) **plus 3 dynamic `await import("../src/…")` sites** (`test/isolation-active.test.ts:359`, `test/reach.test.ts:201-202`) that a naive static-import codemod will miss and that fail only at runtime.
- The e2e harness deep-imports twice (`e2e/sdk-entry.ts:41-42`: `../src/store-registry`, `../src/accounts`); its package-name imports resolve through the exports map and are immune. `e2e/tsconfig.json` includes `"."` only — path-agnostic.
- The e2e harness deep-imports twice (`e2e/polyfill-entry.ts:41-42`: `../src/store-registry`, `../src/accounts`); its package-name imports resolve through the exports map and are immune. `e2e/tsconfig.json` includes `"."` only — path-agnostic.
- Decision to make alongside: whether `test/` mirrors the new folders (keeps the module↔spec correspondence at more churn) or stays flat.
**Silent-breakage candidates — the ones tests may not catch:**
+1 -1
View File
@@ -1,4 +1,4 @@
# Vision & principles of the `@ng-eventually/sdk` polyfill
# Vision & principles of `@ng-eventually/polyfill`
## Purpose
+4 -4
View File
@@ -1,12 +1,12 @@
# Notebook — the library's example application
A minimal application written against `@ng-eventually/sdk`, in plain DOM.
A minimal application written against `@ng-eventually/polyfill`, in plain DOM.
It exists for two reasons, and the second is the one that matters.
**It shows how to use the library.** Every call in `app.ts` is what a real consumer writes. There is no test scaffolding, no privileged import, no reaching into the library's internals — it resolves `@ng-eventually/sdk` as an external consumer does. If something reads awkwardly here, it reads awkwardly for everyone.
**It shows how to use the library.** Every call in `app.ts` is what a real consumer writes. There is no test scaffolding, no privileged import, no reaching into the library's internals — it resolves `@ng-eventually/polyfill` as an external consumer does. If something reads awkwardly here, it reads awkwardly for everyone.
**It is what the applicative e2e suite drives** (`packages/sdk/e2e/notebook.ts`, `bun run test:e2e:app`). The other suite talks to a bag of methods on `window.__sdk`, which proves the functions run but never that an application can be written with them — and that gap shipped a real defect once: a document's inbox was green in tests and unusable in practice, because the harness handed an address across an identity boundary through a variable, something no application can do. Here each identity is its own browser page, and the only values that cross between them are the ones that cross in life: a note's reference, copied off one screen, and an identifier typed into a field.
**It is what the applicative e2e suite drives** (`packages/polyfill/e2e/notebook.ts`, `bun run test:e2e:app`). The other suite talks to a bag of methods on `window.__sdk`, which proves the functions run but never that an application can be written with them — and that gap shipped a real defect once: a document's inbox was green in tests and unusable in practice, because the harness handed an address across an identity boundary through a variable, something no application can do. Here each identity is its own browser page, and the only values that cross between them are the ones that cross in life: a note's reference, copied off one screen, and an identifier typed into a field.
It has already paid for itself: writing it surfaced that `UnionSubject` returned `string` where the values are always document references (so a consumer had to cast whatever it had just read before passing it back), and that the access gate normalized what a user typed but not what the URL carried.
@@ -27,4 +27,4 @@ It has also found three defects of its own, each one the harness could not see.
## Running it
`cd packages/sdk && bun run test:e2e:app` builds it, serves it, and drives it against the real broker. To open it by hand you need a wallet: serve the folder with a bundled `app.js` and a `/shared-wallet.ngw`, and set `__NOTEBOOK_WALLET_PASSWORD__`.
`cd packages/polyfill && bun run test:e2e:app` builds it, serves it, and drives it against the real broker. To open it by hand you need a wallet: serve the folder with a bundled `app.js` and a `/shared-wallet.ngw`, and set `__NOTEBOOK_WALLET_PASSWORD__`.
+3 -3
View File
@@ -1,5 +1,5 @@
/**
* Notebook a minimal application written against `@ng-eventually/sdk`.
* Notebook a minimal application written against `@ng-eventually/polyfill`.
*
* It exists for two reasons, and the second is the one that matters:
*
@@ -7,7 +7,7 @@
* writes; there is no test scaffolding, no privileged import, no reaching into the
* library's internals. If something is awkward here, it is awkward for everyone.
*
* 2. **It is what the applicative e2e suite drives** (`packages/sdk/e2e/notebook.ts`).
* 2. **It is what the applicative e2e suite drives** (`packages/polyfill/e2e/notebook.ts`).
* The other suite talks to a bag of methods on `window.__sdk`, which proves the
* functions run but never that an application could be written with them and that
* gap shipped a real defect: a document's inbox was green in tests and unusable in
@@ -40,7 +40,7 @@ import {
type Scope,
// Polyfill-era — ONE call, and it is the whole of what goes away.
configure,
} from "@ng-eventually/sdk";
} from "@ng-eventually/polyfill";
import { ng as realNg, init as realInit } from "@ng-org/web";
// --- the domain, such as it is ---------------------------------------------
+2 -2
View File
@@ -2,9 +2,9 @@
"name": "@ng-eventually/example-notebook",
"private": true,
"type": "module",
"description": "A minimal application written against @ng-eventually/sdk \u2014 the library's usage example, and what the e2e suite drives.",
"description": "A minimal application written against @ng-eventually/polyfill \u2014 the library's usage example, and what the e2e suite drives.",
"dependencies": {
"@ng-eventually/sdk": "workspace:*",
"@ng-eventually/polyfill": "workspace:*",
"@ng-org/web": "0.1.2-alpha.13"
}
}
+2 -2
View File
@@ -10,8 +10,8 @@
"DOM"
],
"paths": {
"@ng-eventually/sdk": [
"../../packages/sdk/src/index.ts"
"@ng-eventually/polyfill": [
"../../packages/polyfill/src/index.ts"
]
}
},
+1 -1
View File
@@ -9,7 +9,7 @@
],
"scripts": {
"test": "bun test",
"typecheck": "bunx tsc --noEmit -p packages/sdk/tsconfig.json"
"typecheck": "bunx tsc --noEmit -p packages/polyfill/tsconfig.json"
},
"devDependencies": {
"@types/bun": "latest",
@@ -1,4 +1,4 @@
# @ng-eventually/sdk
# @ng-eventually/polyfill
One entry point. Most of what it publishes has the same signature as the future SDK —
`ng`, `useShape`, `watchShape`, `docs`, `inbox`, `storeRegistry`, `readUnion` (+ types) —
@@ -30,7 +30,7 @@ import {
ensureIdentity, storeRegistry, inbox, readUnion, docs,
// Polyfill-era — one call, and it is the whole of what goes away.
configure,
} from "@ng-eventually/sdk";
} from "@ng-eventually/polyfill";
configure({ ng: realNg, useShape: realUseShape, getSession, sharedWallet });
await ensureIdentity(); // who I am (returned), connection work awaited
@@ -72,7 +72,7 @@ which the e2e suite drives.
## How a document is reached — the three acts, and no others
```ts
import { storeRegistry, inbox, readUnion } from "@ng-eventually/sdk";
import { storeRegistry, inbox, readUnion } from "@ng-eventually/polyfill";
// 1. CREATE — you hold its cap, with nothing to declare. No identity parameter: a
// session belongs to one user, exactly as the target's own `doc_create` assumes.
@@ -1,10 +1,10 @@
# SDK reference — reading data with `@ng-eventually/sdk`
# Polyfill reference — reading data with `@ng-eventually/polyfill`
**Audience:** anyone using `@ng-eventually/sdk` (the app that consumes it, and
the lib itself when honoring the contract). This is the reference on the SDK's
**Audience:** anyone using `@ng-eventually/polyfill` (the app that consumes it, and
the lib itself when honoring the contract). This is the reference on the polyfill's
**read/reactivity surface** — how you read data and how a read stays live.
`@ng-eventually/sdk` is written and consumed as if NextGraph were a **finished,
`@ng-eventually/polyfill` is written and consumed as if NextGraph were a **finished,
mature SDK**: documents per entity placed by scope, capabilities, inboxes, and a
**reactive ORM**. This file documents that finished-SDK contract. Where today's
emulation does not yet deliver it, that is called out in one clearly-separated
@@ -27,7 +27,7 @@ cited by `file:symbol` throughout so a future agent can re-verify cheaply.
> reads are the exception, not the rule.**
```ts
import { useShape } from "@ng-eventually/sdk";
import { useShape } from "@ng-eventually/polyfill";
import { EventShapeType } from "…/shapes/orm/…";
function EventList() {
@@ -108,9 +108,9 @@ and every subsequent patch to a `DeepSignalSet`
`useDeepSignal` (`@ng-org/alien-deepsignals/react`). Vue and Svelte adapters exist
alongside the React one (`sdk/js/orm/src/frontendAdapters/{vue,svelte}/`).
`@ng-eventually/sdk` re-exports `useShape` from
`@ng-eventually/polyfill` re-exports `useShape` from
[`../src/surface/use-shape.ts`](../src/surface/use-shape.ts); import it from the SDK
(`@ng-eventually/sdk`), never from `@ng-org/orm` directly.
(`@ng-eventually/polyfill`), never from `@ng-org/orm` directly.
### What you get, in order
@@ -161,7 +161,7 @@ computes a result and returns once (`sparql_query`,
`sdk/js/lib-wasm/src/lib.rs:352`/`553`; no "subscribe to a query" exists —
`sparql_query` is not reactive).
In `@ng-eventually/sdk` the one-shot read is exposed as:
In `@ng-eventually/polyfill` the one-shot read is exposed as:
- **`docs.sparqlQuery(sid, query, base?, anchor?)`** — a raw anchored SPARQL query
([`../src/surface/docs.ts`](../src/surface/docs.ts)). `anchor` = the document NURI to read; the
@@ -1,12 +1,12 @@
/**
* Real-broker plumbing for the SDK e2e harness a DEDICATED test wallet for
* `@ng-eventually/sdk`, fully separate from any consumer app's profile.
* `@ng-eventually/polyfill`, fully separate from any consumer app's profile.
*
* Adapted from the Festipod app's `src/shared/support/hooks.ts` (the reference
* real-broker Playwright flow): headless wallet CREATION on nextgraph.eu, broker
* redirect via nextgraph.net, iframe handling. Here it authenticates a wallet
* created FOR THIS LIB (distinct name + distinct profile dir), and loads the
* minimal SDK page (sdk-entry.ts) inside the broker iframe.
* minimal polyfill page (polyfill-entry.ts) inside the broker iframe.
*/
import { chromium, type BrowserContext, type Page, type Frame } from "playwright";
@@ -25,8 +25,8 @@ const WALLET_READY_MARKER = path.join(PROFILE_DIR, ".wallet-ready");
export const WALLET_NAME = "ng-eventually-e2e";
export const WALLET_PASSWORD = "ng-eventually-e2e";
const ENTRY = path.resolve(__dirname, "sdk-entry.ts");
const BUNDLE_OUT = path.resolve(__dirname, ".dist", "sdk-entry.js");
const ENTRY = path.resolve(__dirname, "polyfill-entry.ts");
const BUNDLE_OUT = path.resolve(__dirname, ".dist", "polyfill-entry.js");
const LAUNCH_ARGS = [
"--disable-features=PrivateNetworkAccessRespectPreflightResults,BlockInsecurePrivateNetworkRequests,PrivateNetworkAccessForWorkers,PrivateNetworkAccessForNavigations",
@@ -52,9 +52,9 @@ export function buildBundle(): void {
export function serveHarness(): Promise<{ url: string; close: () => void }> {
const bundle = fs.readFileSync(BUNDLE_OUT, "utf-8");
const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>ng-eventually sdk e2e</title></head><body><div id="root"></div><script type="module" src="/sdk-entry.js"></script></body></html>`;
const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>ng-eventually polyfill e2e</title></head><body><div id="root"></div><script type="module" src="/polyfill-entry.js"></script></body></html>`;
const server = http.createServer((req, res) => {
if (req.url === "/sdk-entry.js") {
if (req.url === "/polyfill-entry.js") {
res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
res.end(bundle);
} else {
@@ -1,7 +1,7 @@
/**
* SDK e2e harness entry the MINIMAL page loaded inside the broker iframe.
* Polyfill e2e harness entry the MINIMAL page loaded inside the broker iframe.
*
* It imports the REAL `@ng-org/web` `ng` + this package (`@ng-eventually/sdk`),
* It imports the REAL `@ng-org/web` `ng` + this package (`@ng-eventually/polyfill`),
* configures the polyfill session injection exactly the way a consumer does
* (`configure` + `configureStoreRegistry`), waits for the real broker to hand back
* a session, then exposes `window.__sdk`: a flat bag of async methods the
@@ -25,7 +25,7 @@ import {
storeRegistry,
useShape as libUseShape,
watchShape,
} from "@ng-eventually/sdk";
} from "@ng-eventually/polyfill";
// The harness tests the LIBRARY, so it legitimately reaches machinery a consumer
// application must not — but through the internal path, never the published entry.
// `storeRegistry` above is the app-facing slice; these are the shim internals.
@@ -42,11 +42,11 @@ import {
} from "../src/shared-wallet/bootstrap";
import { connectedUser } from "../src/emulated-verifier/connect";
import * as virtualUsers from "../src/shared-wallet/virtual-users";
import { ensureIdentity } from "@ng-eventually/sdk";
import { ensureIdentity } from "@ng-eventually/polyfill";
// The harness narrows for its OWN assertions; a consumer never has to (the entries take
// plain strings and validate inside). Internal path, like the rest of its machinery.
import { isNuri } from "../src/model/nuri";
import type { Nuri, ShapeObservable, ShapeQuery } from "@ng-eventually/sdk";
import type { Nuri, ShapeObservable, ShapeQuery } from "@ng-eventually/polyfill";
const { IdentityStore } = virtualUsers;
@@ -21,7 +21,7 @@
*
* Standalone (NOT `bun test`). Run:
* bun run e2e/reactivity-doc-subscribe.ts
* (or `bun run test:e2e:reactivity` from packages/sdk)
* (or `bun run test:e2e:reactivity` from packages/polyfill)
*
* It reuses the exact real-broker plumbing of run.ts / broker.ts: the dedicated lib
* wallet, the broker iframe, `window.__sdk`. The CROSS case opens a SECOND page on
@@ -1,9 +1,9 @@
/**
* Real-broker e2e runner for `@ng-eventually/sdk` the polyfill's OWN suite,
* Real-broker e2e runner for `@ng-eventually/polyfill` the polyfill's OWN suite,
* in the SDK domain (no application concepts), with a DEDICATED wallet.
*
* Standalone (NOT `bun test`), so it never mixes into the fake-ng unit suite.
* Run: `bun run e2e/run.ts` (or `bun run test:e2e` from packages/sdk).
* Run: `bun run e2e/run.ts` (or `bun run test:e2e` from packages/polyfill).
*
* It: builds the SDK page bundle, creates/reuses the dedicated lib wallet, opens
* the broker iframe on the real broker with that wallet, waits for `window.__sdk`
@@ -1,8 +1,8 @@
{
"name": "@ng-eventually/sdk",
"name": "@ng-eventually/polyfill",
"version": "0.0.0",
"type": "module",
"description": "SDK-identical wrapper over @ng-org/web + @ng-org/orm with emulated capabilities and inbox. Drop-in; remove at migration.",
"description": "Polyfill of the NextGraph JS SDK over @ng-org/web + @ng-org/orm, with emulated capabilities and inbox. Drop-in; remove at migration.",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
@@ -1,5 +1,5 @@
/**
* `@ng-eventually/sdk` the one door. Everything an application imports, it imports
* `@ng-eventually/polyfill` the one door. Everything an application imports, it imports
* from here.
*
* What the single entry costs, and how that cost is paid
@@ -78,7 +78,7 @@ export { readUnion } from "./surface/read-model";
export type { UnionSubject } from "./surface/read-model";
export * as storeRegistry from "./surface/placement";
// SDK type re-exports — so the app imports these from @ng-eventually/sdk too, not from
// SDK type re-exports — so the app imports these from @ng-eventually/polyfill too, not from
// @ng-org. `export type` is ERASED at build, so this adds NO runtime @ng-org import to
// the lib (no risk of a duplicate SDK copy in the bundle).
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
@@ -31,7 +31,7 @@
* (BARE-encoded Rust structs, base64url'd); decoding them to report concrete
* write TARGETS (topics/docs) would mean duplicating the WASM verifier's wire
* format in this polyfill, which is explicitly out of scope (SDK internals live
* in the `@ng-eventually/sdk`-independent core repo, per this repo's
* in the `@ng-eventually/polyfill`-independent core repo, per this repo's
* doctrine) so only the pending COUNT is reported, never fabricated targets.
* `sessionStorage` access itself can throw (sandboxed iframe, disabled storage
* see the exact error string handled in the core repo's `main.ts`
@@ -169,7 +169,7 @@ export async function post(targetInboxLike: NuriLike, opts: PostOptions): Promis
// default graph (same shape as read-model.ts readDoc/readUnion). This is the
// CANONICAL, always-safe shape and the one the anchored default-graph read
// queries. (Not a round-trip necessity on the current broker: the e2e harness
// `packages/sdk/e2e/` verified that an anchored `GRAPH <plainNuri>` write
// `packages/polyfill/e2e/` verified that an anchored `GRAPH <plainNuri>` write
// ALSO round-trips here — it resolves to the same repo graph, no phantom graph.
// The no-GRAPH form is kept as a simplicity/safety convention; re-verify with
// that harness if the broker version changes.)
@@ -1,6 +1,6 @@
/**
* Lifecycle re-exports SDK-shaped forwarders so the app imports `init` /
* `initNg` from `@ng-eventually/sdk` rather than from `@ng-org/*`. They
* `initNg` from `@ng-eventually/polyfill` rather than from `@ng-org/*`. They
* delegate to the REAL functions injected at `configure()`. Passthrough today;
* a hook point later (e.g. opening the shared wallet on `init`).
*/
@@ -52,7 +52,7 @@ export type DocChange = unknown;
* `AppResponse` payload (`{ V0: { State | Patch | TabInfo | … } }`). It is NOT a
* closed enum: the platform may push other variants, so this is a bare `string`
* (e.g. `"State"`, `"Patch"`, `"TabInfo"`), or `undefined` when the shape can't
* be read. Verified against the CONTRACT-3 e2e probe (`e2e/sdk-entry.ts`): the
* be read. Verified against the CONTRACT-3 e2e probe (`e2e/polyfill-entry.ts`): the
* variant is `Object.keys(resp.V0)[0]`. Exposed so a caller that needs the SYNC
* BARRIER (the first `State`, per CONTRACT 3) can distinguish it from the earlier
* `TabInfo`/`Patch` pushes see `open-repo.ts`. Most callers ignore it and use