refactor: renommer client → sdk, et fusionner les deux portes en une
Deux mouvements de surface, aucun changement de comportement. **`packages/client` → `packages/sdk`, `@ng-eventually/client` → `@ng-eventually/sdk`.** « client » ne disait rien : ce paquet EST le SDK que l'application appelle, et c'est tout ce qu'elle appelle. L'ancien nom reste comme mot-clé de recherche dans `docs/source-layout-by-fate.md` et le tableau des paquets du README. **Une seule entrée.** L'entrée `./polyfill` disparaît ; ses symboles applicatifs — `configure`, `configureStoreRegistry`, `setCurrentUser`, `connectedUser` et leurs types — vivent dans un bloc `POLYFILL-ERA` de `src/index.ts`. Ce que la seconde porte portait mérite d'être nommé avant d'être retiré : *ce qu'on importe de ce chemin est exactement ce qu'on supprimera à la migration*. Une seule porte perd ce signal — rien à la ligne d'import ne distingue `configure`, qui part, de `docs`, que le vrai SDK remplace sur place. Trois choses le portent désormais : le bloc lui-même, l'inventaire d'exports de `docs/api-contract.md` (épinglé par `test/vocabulary.test.ts`, donc il ne peut pas rancir en silence), et le contrôle de vocabulaire sur les noms publiés. **Six symboles quittent la surface au passage**, et la fusion est ce qui a rendu le choix visible plutôt qu'hérité : - `getConfig` / `getStoreRegistryDeps` — câblage interne, atteint par `shared-wallet/bootstrap` ; - `resetConfig` / `resetStoreRegistry` / `resetCaps` — remises à zéro de test, atteintes par leur chemin interne, ce qui est leur raison d'être ; - le `share` direct — `inbox.share` a toujours été la même fonction, et la publier deux fois brouillait la frontière qu'elle servait à marquer. Corrections d'affirmations fausses trouvées en chemin : le contrat annonçait `isNuri` / `hasReadCap` sur la porte SDK alors qu'ils ne sont plus exportés depuis le passage au permissif en entrée (`NuriLike` validé à la porte) ; le README du paquet documentait `capFor`, `shareCap`, `getCaps` et `publishRepoLink`, dont aucun n'existe ; et le README de l'app d'exemple affirmait que la suite e2e la pilote, ce qui reste à faire. 179 tests unitaires, typecheck bibliothèque / exemple / harnais, e2e 42/42 contre le broker en ligne — mesuré une fois après le renommage, une fois après la fusion.
This commit is contained in:
+54
-46
@@ -1,10 +1,10 @@
|
||||
# API contract — what `@ng-eventually/client` exposes today, and what the future SDK should expose per subject
|
||||
# API contract — what `@ng-eventually/sdk` 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/client` (verified against the `export` statements in `packages/client/src/index.ts` and `packages/client/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/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`).
|
||||
|
||||
**How to read the epistemic labels.** Every target-side claim carries one of:
|
||||
|
||||
@@ -19,10 +19,10 @@ Per the design principle (`README.md` § *Design principle*): an absent implemen
|
||||
|
||||
## 1. Bootstrap and configuration
|
||||
|
||||
### Today — `@ng-eventually/client/polyfill` (everything here is removed at migration)
|
||||
### Today — `@ng-eventually/sdk` (the POLYFILL-ERA block of `src/index.ts`; everything here is removed at migration)
|
||||
|
||||
```ts
|
||||
// polyfill.ts:44
|
||||
// shared-wallet/bootstrap.ts
|
||||
export interface EventuallyConfig {
|
||||
ng: NgLike;
|
||||
useShape: UseShapeLike;
|
||||
@@ -32,23 +32,23 @@ export interface EventuallyConfig {
|
||||
init?: (...args: any[]) => any;
|
||||
initNg?: (...args: any[]) => any;
|
||||
}
|
||||
// polyfill.ts:99
|
||||
// shared-wallet/bootstrap.ts
|
||||
export function configure(c: EventuallyConfig): void;
|
||||
// polyfill.ts:113 — tests only
|
||||
// shared-wallet/bootstrap.ts — tests only
|
||||
export function resetConfig(): void;
|
||||
|
||||
// polyfill.ts:24
|
||||
// shared-wallet/bootstrap.ts
|
||||
export interface StoreRegistryDeps {
|
||||
getSession: () => Promise<RegistrySession>;
|
||||
normalizeId?: (id: string) => string;
|
||||
pointerGuard?: { attempts?: number; baseMs?: number; maxStepMs?: number };
|
||||
}
|
||||
// polyfill.ts:124
|
||||
// shared-wallet/bootstrap.ts
|
||||
export function configureStoreRegistry(deps: StoreRegistryDeps): void;
|
||||
// polyfill.ts:161 — tests only
|
||||
// shared-wallet/bootstrap.ts — tests only
|
||||
export function resetStoreRegistry(): void;
|
||||
|
||||
// polyfill.ts:106 / :153 — both tagged @internal, exported so the SDK-shaped wrappers can reach the injected SDK
|
||||
// shared-wallet/bootstrap.ts — both tagged @internal, exported so the SDK-shaped wrappers can reach the injected SDK
|
||||
export function getConfig(): EventuallyConfig;
|
||||
export function getStoreRegistryDeps(): ResolvedRegistryDeps;
|
||||
```
|
||||
@@ -61,7 +61,7 @@ export function getStoreRegistryDeps(): ResolvedRegistryDeps;
|
||||
|
||||
## 2. Lifecycle
|
||||
|
||||
### Today — `@ng-eventually/client`
|
||||
### Today — `@ng-eventually/sdk`
|
||||
|
||||
```ts
|
||||
// lifecycle.ts:11 — forwards to the real @ng-org/web init injected at configure()
|
||||
@@ -98,7 +98,7 @@ Divergence: none in behaviour (pure forwarding), but the wrapper erases the para
|
||||
|
||||
## 3. The `ng` object
|
||||
|
||||
### Today — `@ng-eventually/client`
|
||||
### Today — `@ng-eventually/sdk`
|
||||
|
||||
```ts
|
||||
// index.ts:55
|
||||
@@ -122,7 +122,7 @@ The two overrides:
|
||||
|
||||
## 4. Reactive typed reads — `useShape`
|
||||
|
||||
### Today — `@ng-eventually/client`
|
||||
### Today — `@ng-eventually/sdk`
|
||||
|
||||
```ts
|
||||
// use-shape.ts:12
|
||||
@@ -160,7 +160,7 @@ Divergence to note: the wrapper types everything `unknown`, losing the generic `
|
||||
|
||||
## 5. Reactive typed reads with load state — `watchShape`
|
||||
|
||||
### Today — `@ng-eventually/client`
|
||||
### Today — `@ng-eventually/sdk`
|
||||
|
||||
```ts
|
||||
// watch-shape.ts:73
|
||||
@@ -201,7 +201,7 @@ So the constraint on the bet: the target can already answer "synced?" (`readyPro
|
||||
|
||||
## 6. One-shot listing — the read-model
|
||||
|
||||
### Today — `@ng-eventually/client`
|
||||
### Today — `@ng-eventually/sdk`
|
||||
|
||||
```ts
|
||||
// read-model.ts:59
|
||||
@@ -237,7 +237,7 @@ The anchored-read mechanics are level-1 VERIFIED: an anchor restricts the query
|
||||
|
||||
## 7. Raw document / SPARQL primitives — `docs.*`
|
||||
|
||||
### Today — `@ng-eventually/client` (namespace `docs`)
|
||||
### Today — `@ng-eventually/sdk` (namespace `docs`)
|
||||
|
||||
```ts
|
||||
// docs.ts:46
|
||||
@@ -298,7 +298,7 @@ So the target's direction for scope placement is **already visible in the source
|
||||
|
||||
## 8. Per-document subscription — `subscribeDoc`
|
||||
|
||||
### Today — `@ng-eventually/client`
|
||||
### Today — `@ng-eventually/sdk`
|
||||
|
||||
```ts
|
||||
// subscribe.ts:47,60,79
|
||||
@@ -336,7 +336,7 @@ declare function doc_subscribe(repo_o: string, session_id: any, callback: Functi
|
||||
|
||||
## 9. Inbox — deposits, and cap delivery
|
||||
|
||||
### Today — `@ng-eventually/client` (namespace `inbox`; `share` also re-exported from `/polyfill`)
|
||||
### Today — `@ng-eventually/sdk` (namespace `inbox`)
|
||||
|
||||
```ts
|
||||
// inbox.ts:48,58
|
||||
@@ -397,21 +397,21 @@ Consequences per function:
|
||||
### Today
|
||||
|
||||
```ts
|
||||
// @ng-eventually/client — nuri.ts:50,60 (type guards; the only doors from string to typed)
|
||||
export function isNuri(s: string): s is Nuri;
|
||||
export function hasReadCap(s: string): s is ReadCap;
|
||||
|
||||
// @ng-eventually/client — types.ts:11,34
|
||||
// @ng-eventually/sdk — model/types.ts. The types are the whole published cap surface.
|
||||
export type Nuri = `did:ng:${string}`;
|
||||
export type ReadCap = `did:ng:${string}:r:${string}`;
|
||||
export type NuriLike = Nuri | string;
|
||||
|
||||
// @ng-eventually/client/polyfill
|
||||
// tests / fresh wallet only — the registry itself is NOT published, and neither is any
|
||||
// "do I hold this?" predicate (`hasCap`, removed 2026-08-06: it read like "may I read
|
||||
// this?", and a document in a public store answers `false` until something asks for it).
|
||||
export function resetCaps(): void;
|
||||
// NOT published, each deliberately:
|
||||
// isNuri / hasReadCap — the type guards (`model/nuri.ts`). Unpublished since the
|
||||
// permissive-in change: every entry takes `NuriLike` and validates at the door, so
|
||||
// a consumer holding a plain string narrows nothing. Publishing a guard would
|
||||
// invite the cast it exists to prevent.
|
||||
// hasCap(doc) — removed 2026-08-06. It read like "may I read this?", and a
|
||||
// document in a public store answers `false` until something asks for its cap.
|
||||
// getCaps / CapRegistry / resetCaps — the emulation's engine room and its test reset.
|
||||
|
||||
// @ng-eventually/client/polyfill — caps.ts:59 (class CapRegistry)
|
||||
// INTERNAL — `emulated-verifier/caps.ts` (class CapRegistry). Never published; listed for the maintainer.
|
||||
constructor(holder?: () => PrincipalId | null);
|
||||
mint(nuri: Nuri): ReadCap;
|
||||
learn(cap: ReadCap): void;
|
||||
@@ -446,7 +446,7 @@ The `CapRegistry` class itself is machinery (the in-memory record of what the co
|
||||
|
||||
## 11. NURI and SPARQL string utilities
|
||||
|
||||
### Today — `@ng-eventually/client`
|
||||
### Today — `@ng-eventually/sdk`
|
||||
|
||||
```ts
|
||||
// sparql.ts:32,66,95
|
||||
@@ -455,7 +455,7 @@ export function escapeIri(value: string): string;
|
||||
export function assertNuri<T extends string>(nuri: T): T;
|
||||
```
|
||||
|
||||
(`isNuri` / `hasReadCap` are in § 10; `targetOf`, `parseNuri`, `mintCap` exist in `nuri.ts` but are **not** exported from either entry point — deliberately: nothing on the surface turns a bare reference into a cap.)
|
||||
(`isNuri`, `hasReadCap`, `targetOf`, `parseNuri`, `toNuri` and `mintCap` all exist in `model/nuri.ts` and **none** is exported — deliberately: nothing on the surface turns a bare reference into a cap, and validation happens at the door rather than in the caller's hands.)
|
||||
|
||||
### Target
|
||||
|
||||
@@ -465,9 +465,9 @@ export function assertNuri<T extends string>(nuri: T): T;
|
||||
|
||||
## 12. Scope resolution, per-entity documents, and the store registry
|
||||
|
||||
### Today — `@ng-eventually/client` (namespace `storeRegistry`) — plus `Scope` from `types.ts`
|
||||
### Today — `@ng-eventually/sdk` (namespace `storeRegistry`) — plus `Scope` from `types.ts`
|
||||
|
||||
> **Narrowed 2026-08-03.** The entry used to re-export the WHOLE `store-registry` module. It now re-exports an app-facing slice (`src/surface/placement.ts`): `createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `userInbox`, `openDocumentInbox`, `documentInboxAddress`. The rest — `userStoreDoc`, `isOwnInbox`, `myInboxes`, `addLink`, `readLinks`, `resolveAccount`, `ensureAccount`, `reservedAccount`, `resetRegistryCache`, and the `VirtualUserRecord` / `RegistrySession` types — is **no longer importable from `@ng-eventually/client`** and is covered by `docs/internal-contract.md`. The signatures below are kept for the record, marked accordingly.
|
||||
> **Narrowed 2026-08-03.** The entry used to re-export the WHOLE `store-registry` module. It now re-exports an app-facing slice (`src/surface/placement.ts`): `createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `userInbox`, `openDocumentInbox`, `documentInboxAddress`. The rest — `userStoreDoc`, `isOwnInbox`, `myInboxes`, `addLink`, `readLinks`, `resolveAccount`, `ensureAccount`, `reservedAccount`, `resetRegistryCache`, and the `VirtualUserRecord` / `RegistrySession` types — is **no longer importable from `@ng-eventually/sdk`** and is covered by `docs/internal-contract.md`. The signatures below are kept for the record, marked accordingly.
|
||||
|
||||
```ts
|
||||
// types.ts:38 — NB: NOT the ORM's Scope (a graphs/subjects filter); this is the store scope
|
||||
@@ -530,7 +530,7 @@ export function resetRegistryCache(): void;
|
||||
### Today
|
||||
|
||||
```ts
|
||||
// @ng-eventually/client — virtualUsers.ts (namespace accounts)
|
||||
// @ng-eventually/sdk — virtualUsers.ts (namespace accounts)
|
||||
export const ACCOUNT_STORAGE_KEY = "ng-eventually.account.id"; // :18
|
||||
export interface VirtualUserStorage { // :26
|
||||
getItem(key: string): string | null;
|
||||
@@ -545,9 +545,9 @@ export class IdentityStore { // :37
|
||||
}
|
||||
export function browserIdentityStore(key?: string): IdentityStore; // :89
|
||||
|
||||
// @ng-eventually/client/polyfill
|
||||
export function setCurrentUser(id: PrincipalId | null): void; // polyfill.ts:171
|
||||
export function getCurrentUser(): PrincipalId | null; // polyfill.ts:187
|
||||
// @ng-eventually/sdk — the POLYFILL-ERA block
|
||||
export function setCurrentUser(id: PrincipalId | null): void; // shared-wallet/bootstrap.ts
|
||||
export function getCurrentUser(): PrincipalId | null; // shared-wallet/bootstrap.ts
|
||||
export async function connectedUser(): Promise<void>; // connect.ts:52
|
||||
```
|
||||
|
||||
@@ -571,7 +571,7 @@ declare function user_disconnect(user_id: string): Promise<void>;
|
||||
|
||||
## 14. Type re-exports
|
||||
|
||||
`@ng-eventually/client` re-exports, type-only (erased at build, `index.ts:48-50`):
|
||||
`@ng-eventually/sdk` re-exports, type-only (erased at build, `index.ts:48-50`):
|
||||
|
||||
```ts
|
||||
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
|
||||
@@ -600,7 +600,8 @@ Exported, but not SDK surface. Coding against these builds knowledge that migrat
|
||||
### Places the current surface teaches something to unlearn
|
||||
|
||||
- ~~**The SDK entry is not as pure as its header claims.**~~ **FIXED 2026-08-03.** The header claimed the entry "exposes ONLY what `@ng-org/web` / `@ng-org/orm` expose" while also shipping `virtualUsers` and the whole `store-registry` module. Both are gone from it, and the header now states what the entry actually promises: *every symbol here has a target-SDK counterpart, verified or assumed, listed in this document*. It still exports `docs`, `readUnion`, `watchShape`, `subscribeDoc(s)`, the SPARQL helpers and the NURI guards — justified inventions, documented per subject above — so the promise is no longer "@ng-org surface only", which was never true, but "nothing here is machinery".
|
||||
- **`share` is importable from both entries** (`inbox.share` on the SDK entry via `export * as inbox`, and a named re-export on `/polyfill`). The polyfill re-export exists "so the cap vocabulary stays on the polyfill side" — but the namespace export undoes that. Harmless functionally; blurs the same boundary.
|
||||
- ~~**`share` is importable from both entries.**~~ **FIXED 2026-08-07** with the entry merge: there is one entry and one `share`, under `inbox`.
|
||||
- **One entry means the import line no longer says what disappears.** Until 2026-08-07 a second import path (`/polyfill`) WAS the deletion list. It is now the `POLYFILL-ERA` block in `src/index.ts`, this appendix's note above, and the per-subject rulings in this document. That is a documentation-carried signal where it used to be a mechanical one — the appendix is pinned by a test, the grouping is not.
|
||||
- **`inbox.read`/`materialize` as a mailbox** — enumerating raw deposits is emulation detail (§ 9); the durable contract is deposit-and-it-gets-applied. An app building UI on the deposit list should expect that surface to change shape entirely.
|
||||
- **`watchShape`'s "planned `useShape` upgrade"** — stated in the module header with no provenance in this repo or the clone (§ 5). The load-state *distinction* is safe; the claim that NextGraph plans this exact hook shape is an assumption and must not be cited as an announced API.
|
||||
- **`UnionSubject` property bags** — polyfill read-model shape, not a target type; map them into app types at the boundary (which `watchShape`'s design already assumes).
|
||||
@@ -610,19 +611,26 @@ 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/client/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/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.*
|
||||
|
||||
### `@ng-eventually/client` — `src/index.ts`
|
||||
### `@ng-eventually/sdk` — `src/index.ts` (the only entry since 2026-08-07)
|
||||
|
||||
```text
|
||||
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, InboxScope, NG, NgLike, Nuri, NuriLike, PrincipalId, ReadCap, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, docChangeType, ensureIdentity, init, initNg, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape
|
||||
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, EventuallyConfig, InboxScope, NG, NgLike, Nuri, NuriLike, PrincipalId, ReadCap, RegistrySession, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, StoreRegistryDeps, UnionSubject, Unsubscribe, UseShapeLike, configure, configureStoreRegistry, connectedUser, docChangeType, ensureIdentity, init, initNg, ng, readUnion, setCurrentUser, subscribeDoc, subscribeDocs, useShape, watchShape
|
||||
docs: depositInto, docCreate, sparqlQuery, sparqlUpdate
|
||||
inbox: Deposit, PostOptions, materialize, post, postToDocument, processInbox, read, readForDocument, readSynced, share, watch
|
||||
storeRegistry: createEntityDoc, listMyEntityDocs, openDocumentInbox, resolveScopeGraph, resolveWriteGraph
|
||||
```
|
||||
|
||||
### `@ng-eventually/client/polyfill` — `src/polyfill.ts`
|
||||
**Of these, four are POLYFILL-ERA and have no target counterpart** — `configure`,
|
||||
`configureStoreRegistry`, `setCurrentUser`, `connectedUser` (plus the types
|
||||
`EventuallyConfig`, `StoreRegistryDeps`, `RegistrySession`). They are the deletion list,
|
||||
and `src/index.ts` groups them under a heading that says so. `ensureIdentity` is a fifth
|
||||
in substance — the shared-wallet gate — but the *call site* survives (§ 2bis).
|
||||
|
||||
```text
|
||||
direct: EventuallyConfig, RegistrySession, StoreRegistryDeps, VirtualUserRecord, configure, configureStoreRegistry, connectedUser, getConfig, getStoreRegistryDeps, resetCaps, resetConfig, resetStoreRegistry, setCurrentUser, share
|
||||
```
|
||||
Six symbols the previous `/polyfill` entry published are gone from the surface entirely:
|
||||
`getConfig` and `getStoreRegistryDeps` (internal wiring, reached through
|
||||
`shared-wallet/bootstrap`), `resetConfig` / `resetStoreRegistry` / `resetCaps` (test
|
||||
resets, reached by their internal path), and the direct `share` re-export — `inbox.share`
|
||||
was always the same function, and publishing it twice blurred the boundary it was meant
|
||||
to mark.
|
||||
|
||||
Reference in New Issue
Block a user