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:
+2
-2
@@ -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
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
+5
-3
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
# Vision & principles of the `@ng-eventually/sdk` polyfill
|
||||
# Vision & principles of `@ng-eventually/polyfill`
|
||||
|
||||
## Purpose
|
||||
|
||||
|
||||
@@ -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__`.
|
||||
|
||||
@@ -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,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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"DOM"
|
||||
],
|
||||
"paths": {
|
||||
"@ng-eventually/sdk": [
|
||||
"../../packages/sdk/src/index.ts"
|
||||
"@ng-eventually/polyfill": [
|
||||
"../../packages/polyfill/src/index.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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";
|
||||
+1
-1
@@ -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
|
||||
Reference in New Issue
Block a user