feat: le polyfill possède la session et la normalisation des identités

Pour démarrer, une application devait écrire une promesse autour du callback
d'init(), attraper l'événement loggedin, puis fournir un thunk getSession qui
dépiaute session_id et les trois identifiants de store dans notre forme. Plus un
normalizeId. C'est précisément la plomberie que ce paquet existe pour absorber :
chaque application la réécrirait à l'identique, et c'est elle qui a produit deux
défauts aujourd'hui — un blocage et un partage cassé en silence.

En amont, une session est RENDUE ; une application n'en assemble jamais une à
partir de champs bruts. Et les identités virtuelles sont une invention du
polyfill, donc leur normalisation lui appartient.

Le wrapper init() enveloppe désormais le callback de l'appelant : il capture
l'événement, en dérive la session, puis appelle le callback avec le même
événement. Le paquet n'appelle jamais init de sa propre initiative — il
l'enveloppe. Sans callback, il capture quand même.

getSession et normalizeId quittent la surface publiée. Le chemin d'injection
reste pour les harnais, mais inatteignable depuis l'entrée : vérifié par un
import à l'exécution et par un configure() refusé à la compilation.

Défaut trouvé et corrigé en route : le broker envoie session_id en NOMBRE, et le
convertir en chaîne faisait refuser tous les appels par le binding wasm. La
valeur ne fait que transiter, elle est relayée telle quelle. Reste que toute la
chaîne la type string — inexactitude antérieure à ce commit, à traiter à part.

Une application écrit maintenant : configure({ ng, useShape, init, sharedWallet }).
This commit is contained in:
Sylvain Duchesne
2026-08-12 17:39:12 +02:00
parent 7a4d9b492f
commit cc8a95d303
20 changed files with 501 additions and 141 deletions
+21 -9
View File
@@ -26,8 +26,6 @@ Per the design principle (`README.md` § *Design principle*): an absent implemen
export interface EventuallyConfig {
ng: NgLike; // the REAL @ng-org/web ng
useShape: UseShapeLike; // the REAL @ng-org/orm useShape
getSession?: () => Promise<RegistrySession>; // the wallet session (a thunk)
normalizeId?: (id: string) => string;
pointerGuard?: { attempts?: number; baseMs?: number; maxStepMs?: number };
sharedWallet?: SharedWalletConfig; // the gate's, § 2bis
debugAccessLog?: boolean;
@@ -46,7 +44,16 @@ export function configure(c: EventuallyConfig): void;
| `connectedUser` | `ensureIdentity` awaits it. Upstream, opening the session IS the connection — no application awaits a second call |
| `getConfig`, `getStoreRegistryDeps`, `resetConfig`, `resetStoreRegistry` | internal wiring and test resets, reached by their internal path (2026-08-07, with the entry merge) |
So an application's whole bootstrap is `configure({ … })` plus `await ensureIdentity()` — and the second of those keeps its call site after migration.
**And two FIELDS of that one call, on 2026-08-12.** The count was already one; what was left inside it still made an application build things the target never asks anyone to build:
| Was published | Where it went |
|---|---|
| `getSession` (and the `RegistrySession` type with it, § 12) | the package's. Upstream a session is **returned**`init()`'s callback delivers `{ status: "loggedin", session }` (`@ng-org/web` `dist/ngweb.js:124`, VERIFIED) and `session_start` hands one back; nowhere does an application ASSEMBLE one out of `session_id` / `private_store_id` / …. Every consumer wrapped `init()` in a promise and wrote the same unwrapping thunk, with nothing to migrate it to. The lib's `init` wrapper captures the event on its way through (§ 2) and holds the session (`shared-wallet/session.ts`) |
| `normalizeId` | the package's, as `normalizeIdentityId` — trim, strip a leading `@`, lowercase. The identities it keys are the shared wallet's own virtual users, so there was never a decision here for a consumer to make; and one rule in one place is what stops the barrier, the URL and storage keying onto three different spaces |
Both remain substitutable through `configureStoreRegistry` (`shared-wallet/bootstrap.ts`), which the published entry does not re-export: the unit suites have no browser and the e2e harness holds a session the broker handed it directly, and neither is an application.
So an application's whole bootstrap is `configure({ ng, useShape, init, sharedWallet })` plus `await ensureIdentity()` — and the second of those keeps its call site after migration.
### Target
@@ -59,7 +66,8 @@ So an application's whole bootstrap is `configure({ … })` plus `await ensureId
### Today — `@ng-eventually/polyfill`
```ts
// lifecycle.ts:11 — settles the identity, then forwards to the real @ng-org/web init injected at configure()
// lifecycle.ts:11 — settles the identity, wraps the callback, then forwards to the real
// @ng-org/web init injected at configure()
export function init(...args: any[]): any;
// lifecycle.ts:18 — forwards to the real @ng-org/orm initNg injected at configure()
export function initNg(...args: any[]): any;
@@ -67,7 +75,7 @@ export function initNg(...args: any[]): any;
### Target
**PASSTHROUGH, VERIFIED at both levels.** The wrapper's `...args: any[]` is deliberately shapeless; the real signatures it forwards to are:
**PASSTHROUGH, VERIFIED at both levels — with one argument touched, deliberately.** `init`'s callback in position 0 is wrapped since 2026-08-12: the wrapper reads the event, keeps the session it carries (§ 1), and calls the caller's callback with that same event, unchanged. Everything else — the remaining arguments, the return value, what the callback observes — passes straight through, so an application's call site is what it would write against the real SDK. This is the only place the capture can sit: it is the only one that knows both what the caller asked and what the SDK will answer, and the alternative was every consumer re-implementing it (which is what it replaces). The real signatures forwarded to are:
```ts
// level 2 — @ng-org/web: index.d.ts:108, source sdk/js/web/src/index.ts:51
@@ -519,7 +527,8 @@ interface VirtualUserRecord {
docProtected: Nuri;
docPrivate: Nuri;
}
export interface RegistrySession {
// RegistrySession is INTERNAL since 2026-08-12 (shape kept here for the ruling below).
interface RegistrySession {
sessionId: string;
privateStoreId: string;
protectedStoreId?: string;
@@ -542,7 +551,9 @@ export async function openDocumentInbox(doc: NuriLike): Promise<Nuri>;
// userStoreDoc, userInbox, documentInboxAddress, isOwnInbox, myInboxes,
// addLink, readLinks, resolveAccount, ensureAccount, reservedAccount,
// resetRegistryCache, and the VirtualUserRecord type.
// `RegistrySession` IS published: a consumer types its injected `getSession` with it.
// `RegistrySession` joined them on 2026-08-12: it was published for ONE reason — a consumer
// typed the session thunk it injected with it — and that thunk is gone (§ 1). Upstream a
// session is RETURNED, never assembled, so no application has a session shape to declare.
```
### Target — split by what each piece maps to
@@ -662,14 +673,15 @@ Exported, but not SDK surface. Coding against these builds knowledge that migrat
### `@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
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, EventuallyConfig, NG, NgLike, Nuri, NuriLike, PrincipalId, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, configure, docChangeType, ensureIdentity, init, initNg, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape
docs: docCreate, sparqlQuery, sparqlUpdate
inbox: Deposit, PostOptions, materialize, post, postToDocument, processInbox, read, readForDocument, readSynced, share, watch
storeRegistry: createEntityDoc, listMyEntityDocs, openDocumentInbox, resolveScopeGraph, resolveWriteGraph
```
**Of these, exactly ONE is polyfill-era with no target counterpart**`configure` (plus
the types `EventuallyConfig`, `RegistrySession`). It is the deletion list, and
the type `EventuallyConfig`; `RegistrySession` left the surface on 2026-08-12 with the
session thunk that was its only reason to be there). It is the deletion list, and
`src/index.ts` groups it under a heading that says so. `ensureIdentity` is a second in
substance — the shared-wallet gate — but its *call site* survives (§ 2bis).