/** * The page for the LIVE ORM measurement — the one thing `e2e/polyfill-entry.ts` cannot do. * * That harness injects a hand-written Set-like into `configure({ useShape })`, because the * read filter is pure and does not need the real ORM to be exercised. What it therefore * never touches is the only hop that reaches the broker: `ng.orm_start_graph`, the streamed * method `OrmSubscription` opens and the one `bug_useshape-live-read-needs-a-newer-broker` * is about. So this page injects the REAL `@ng-org/orm` — nothing else about the bootstrap * differs — and the published `useShape` runs over a live subscription. * * ── The dependency, stated because it is not installed by default ────────── * `@ng-org/orm` is an OPTIONAL PEER of this package (`package.json`, * `peerDependenciesMeta`), so `bun install` does not fetch it and `bun.lock` records it * under `optionalPeers` with no resolution. This page is the only thing in the repository * that imports it: `e2e/run.ts` and its harness do not, so a checkout without it still * builds and runs the 42-check suite. Here the bundle simply will not build, and the * runner says so rather than reporting a broker verdict it did not measure. * * ── What the framework adapter contributes, and why it is not here ───────── * Upstream's `useShape` is React's (`sdk/js/orm/src/frontendAdapters/react/useShape.ts`): * it calls `OrmSubscription.getOrCreate(shape, scope)`, hands `signalObject` to * `useDeepSignal`, and closes the subscription on unmount. The broker sees the first of * those three and nothing else — `useDeepSignal` is re-render bookkeeping. So the hook * injected below IS `getOrCreate(...).signalObject`, which is what a React render reaches * the broker with, without a React tree to mount. */ import { ng as realNg, init as realInit } from "@ng-org/web"; import { initNg as ormInitNg, RdfOrmSubscription, type Scope as OrmScope } from "@ng-org/orm"; import { configure, docs, init, useShape as libUseShape } from "@ng-eventually/polyfill"; import type { BaseType, Nuri, ShapeType } from "@ng-eventually/polyfill"; import { adoptCurrentUser, configureStoreRegistry } from "../src/shared-wallet/bootstrap"; // ── the shape under measurement ───────────────────────────────────────────── /** * The smallest shape that can be matched at all, written by hand rather than generated. * * `rdf-orm build` turns a `.shex` file into exactly this object — `{ schema, shape }` * (`sdk/js/shex-orm/src/types.ts:ShapeType`) — and the generator is not what is under * measurement here. Two predicates, both `minCardinality: 1`, because that is what * `engine/verifier/src/orm/graph/query.rs:schema_shape_to_sparql` turns into required * triple patterns: a shape with none of them would match every subject in the document. */ const LIVE_SHAPE = { shape: "did:ng:z:E2ELiveShape", schema: { "did:ng:z:E2ELiveShape": { iri: "did:ng:z:E2ELiveShape", predicates: [ { iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", readablePredicate: "@type", dataTypes: [{ valType: "iri", literals: ["did:ng:z:E2ELive"] }], minCardinality: 1, maxCardinality: 1, }, { iri: "did:ng:z:liveMark", readablePredicate: "liveMark", dataTypes: [{ valType: "string" }], minCardinality: 1, maxCardinality: 1, }, ], }, }, } satisfies ShapeType; const TYPE_IRI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; // ── the broker session ────────────────────────────────────────────────────── interface BrokerSession { session_id: string; private_store_id: string; protected_store_id: string; public_store_id: string; [k: string]: unknown; } let session: BrokerSession | null = null; let sessionResolve!: (s: BrokerSession) => void; const sessionReady = new Promise((r) => (sessionResolve = r)); /** * The injected hook — the real ORM's broker-facing half. * * It keeps the subscription aside so the probe can await `readyPromise`, which is the ORM's * own answer to "did the initial payload arrive": it resolves on that path and on no other * (`sdk/js/orm/src/connector/GraphOrmSubscription.ts:handleInitialResponse`), so a start * the broker refused leaves it pending for ever — the silence this measurement is here to * break. */ /** * The handle this page keeps, DERIVED from `getOrCreate` rather than written by hand. * * `RdfOrmSubscription` (renamed from `OrmSubscription` in `@ng-org/orm@0.1.2-alpha.21`) * now takes its config as a type parameter and `getOrCreate` returns a narrowed * `RdfOrmSubscriptionFor<...>` - the paging methods are `Omit`ted when no `pageSize` is * asked for. Naming the class directly would restate that narrowing and get it wrong the * next time it moves; `ReturnType` asks the package what it actually hands back. */ type LiveSubscription = ReturnType< typeof RdfOrmSubscription.getOrCreate, BaseType, OrmScope> >; let liveSubscription: LiveSubscription | null = null; function realOrmUseShape(shapeType: unknown, scope: unknown): unknown { const sub = RdfOrmSubscription.getOrCreate(shapeType as ShapeType, scope as OrmScope); liveSubscription = sub; return sub.signalObject; } configure({ ng: realNg, useShape: realOrmUseShape, init: realInit, sharedWallet: { fileUrl: "/harness-not-used.ngw", password: "harness" }, }); adoptCurrentUser("e2e-orm-live"); configureStoreRegistry({ getSession: async () => { const s = session ?? (await sessionReady); return { sessionId: s.session_id, privateStoreId: s.private_store_id, protectedStoreId: s.protected_store_id, publicStoreId: s.public_store_id, }; }, normalizeId: (id: string) => id.trim().replace(/^@/, "").toLowerCase(), pointerGuard: { attempts: 8, baseMs: 150, maxStepMs: 2000 }, }); // ── the probe ─────────────────────────────────────────────────────────────── const state: { status: string; error?: string } = { status: "connecting" }; interface LiveProbe { doc: Nuri; subject: string; /** What the published `useShape` returned — a Set-like of the matched objects. */ view: Set; startError: string | null; readyMs: number | null; deleteError: string | null; } let probe: LiveProbe | null = null; /** Count the triples the document still holds for the seeded subject. */ async function seededTripleCount(doc: Nuri, subject: string): Promise { const s = await sessionReady; const res = await docs.sparqlQuery( s.session_id, `SELECT ?p ?o WHERE { <${subject}> ?p ?o }`, undefined, doc, ); const rows = Array.isArray(res) ? res : ((res as { results?: { bindings?: unknown[] } })?.results?.bindings ?? []); return rows.length; } (window as unknown as { __sdk: Record }).__sdk = { status: () => state.status, error: () => state.error ?? null, /** * Seed a document with one shape-matching object, then open the live read through the * PUBLISHED `useShape`. * * The subject IRI follows the ORM's own generator * (`GraphOrmSubscription.ts:signalObjectPropGenerator`): the document NURI's first 53 * characters, then `:q:`, then a name. Nothing requires that form of a hand-written * SPARQL insert, and using it removes one way for this measurement to differ from what * the ORM itself would have written. */ async liveStart() { const s = await sessionReady; const doc = (await docs.docCreate( s.session_id, "Graph", "data:graph", "store", undefined, )) as Nuri; const subject = `${doc.substring(0, 53)}:q:e2eLiveRead`; await docs.sparqlUpdate( s.session_id, `INSERT DATA { <${subject}> <${TYPE_IRI}> ; "measured" }`, doc, ); const seeded = await seededTripleCount(doc, subject); // The ORM reaches the SDK through its own module-level promise, resolved once. ormInitNg(realNg, s as unknown as Parameters[1]); let startError: string | null = null; let view: Set; const t0 = Date.now(); try { view = libUseShape(LIVE_SHAPE, { graphs: [doc] }) as Set; } catch (e) { startError = e instanceof Error ? e.message : String(e); probe = { doc, subject, view: new Set(), startError, readyMs: null, deleteError: null }; return { doc, subject, seeded, startError }; } probe = { doc, subject, view, startError: null, readyMs: null, deleteError: null }; // Un-awaited on purpose: a refused start never resolves this, and the runner's own // bound is what turns that silence into a verdict. void liveSubscription?.readyPromise.then(() => { if (probe) probe.readyMs = Date.now() - t0; }); return { doc, subject, seeded, startError: null }; }, /** How many objects the published `useShape` view holds right now. */ liveCount: () => (probe ? probe.view.size : -1), /** Milliseconds from the `useShape` call to `readyPromise`, or null while it is pending. */ liveReadyMs: () => probe?.readyMs ?? null, liveItems: () => probe ? [...probe.view].map((i) => ({ id: i["@id"] as string, graph: i["@graph"] as string, mark: i.liveMark as string, })) : [], /** * Delete the seeded object through the set the hook returned — the gesture the consuming * application reports as the shape for removal. */ liveDelete() { if (!probe) return { deleted: false, error: "no probe" }; const item = [...probe.view][0]; if (item === undefined) return { deleted: false, error: "view is empty" }; try { const deleted = probe.view.delete(item); return { deleted, error: null }; } catch (e) { const error = e instanceof Error ? e.message : String(e); probe.deleteError = error; return { deleted: false, error }; } }, /** * CHARACTERISATION of the deployed broker's `orm_start_graph` ARITY. * * The failure the runner reports named a parameter - `config` - that the client of * the day did not send. Two upstream arrangements could put one there, and they differ * by POSITION: * - config LAST, after the callback (upstream 2026-04-12 to 2026-04-21) — an * unsupplied `config`, never a Function; * - config at index 4, BEFORE the callback: the 5-argument call of the client of * the day then lands its callback exactly there, which is what the error described. * * THE UPDATE ANSWERED THAT QUESTION. `@ng-org/web@0.1.2-alpha.14` declares six * arguments with `config` at index 4 and routes the stream callback from index 5 * (`dist/ngweb.js`: `orm_start_graph: 5`), and `@ng-org/orm@0.1.2-alpha.21` sends six * to match (`dist/connector/RdfOrmSubscription.js:159`). So the second arrangement was * the right reading, and the ordinary `liveStart` path now exercises it. * * This is therefore kept as a FALLBACK, not as the live question: the runner only asks * it when `liveStart` did not become ready, to say whether the engine still refuses on * `config` when the client no longer can be blamed. It calls the SDK member directly * rather than through the ORM. The app-side proxy takes index 5 as the stream callback, * so the `{}` at index 4 is read as a config and nothing usable comes back; what is * being read is only whether the start is refused. */ async liveStartWithConfig() { const s = await sessionReady; const doc = (await docs.docCreate( s.session_id, "Graph", "data:graph", "store", undefined, )) as Nuri; // Called against the PUBLISHED declaration, which now has these six arguments, so the // widening this used to need is gone: the arity is checked here rather than asserted. // What remains unknowable at compile time is the DEPLOYED engine's arity, which is // the question this asks. try { await realNg.orm_start_graph([doc], [], LIVE_SHAPE, s.session_id, {}, () => {}); return { doc, error: null }; } catch (e) { return { doc, error: e instanceof Error ? e.message : String(e) }; } }, /** The document's own answer: how many triples the seeded subject still has. */ async liveSeededTriples() { if (!probe) return -1; return seededTripleCount(probe.doc, probe.subject); }, }; (async () => { try { await init( (event: { session: BrokerSession }) => { session = event.session; state.status = "connected"; sessionResolve(session); }, true, [], ); } catch (e) { state.status = "error"; state.error = e instanceof Error ? e.message : String(e); } })();