/** * The page for the LIVE ORM measurement - what `e2e/run.ts` still does not measure. * * The 42-check harness injects the REAL `RdfOrmSubscription.getOrCreate` since * `1.0.0-dev.6` (that is what `useShapeQuery` reads through), so a live ORM read is no * longer unreachable from there. What stays here is the PUBLISHED `useShape` half: the * collection an application reads off `data`, and a `.delete()` on it reaching the * document. That is a different surface from the snapshot `useShapeQuery` publishes, and * it has no other measurement. * * ── 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 builds the SAME result object the shipped adapter builds, out of the same * `getOrCreate(...)`, which is what a React render reaches the broker with, without a React * tree to mount. What it must NOT do is return a substitute for that object: it did until * 2026-09-24, and that is the reason a capability filter which handed three of that * object's members back unfiltered could not fail anything here. */ import { ng as realNg, init as realInit } from "@ng-org/web"; import { initNg as ormInitNg, normalizeConf, RdfOrmSubscription, type Scope as OrmScope } from "@ng-org/orm"; import type { RdfOrmConfig } from "@ng-org/orm/all"; 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 result envelope the published `useShape` hands back. Internal path, like every // other piece of machinery this page reaches for: an application never writes it down. import type { ShapeResult } from "../src/model/types"; // ── 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< ShapeType, BaseType, ReturnType> > >; let liveSubscription: LiveSubscription | null = null; /** * The injected hook, returning the SHIPPED shape rather than a substitute for it. * * Until 2026-09-24 this returned `sub.signalObject` - a bare collection, which is what the * hook returned three releases ago and is NOT what the installed one returns. That is why * the capability filter could hand three members of the real result back unfiltered and * still pass every gate in this repository: no fake anywhere returned the shape the SDK * ships, so no test could fail on it. * * What it returns now is the envelope the installed * `dist/frontendAdapters/react/useShape.js` builds, expression for expression, minus the * two pieces that are React's and only React's: `useDeepSignal` (re-render bookkeeping * over the same `signalObject`) and the `useState` that flips once `readyPromise` resolves * - here the subscription's own `isReady` answers the same question. `isLoading` is * upstream's own expression and is reproduced as written, including the fact that it * CONTRADICTS its own documentation, which says `true` while no data is available yet: a * fake that quietly fixed the target would be measuring something nobody ships. * * The two page commands are `undefined`, which is what the shipped hook sets them to * outside a paginated mode - this read asks for no `orderBy`, so there is no page to turn. */ function realOrmUseShape( shapeType: ShapeType, conf: RdfOrmConfig | string | undefined, ): ShapeResult { // Upstream: an undefined config opens nothing, and the three members that would carry // the data are `undefined` with `isLoading` false. if (conf === undefined) { liveSubscription = null; return { data: undefined, isLoading: false, promise: undefined, subscription: undefined, nextPage: undefined, previousPage: undefined, }; } // `normalizeConf` is the SDK's OWN, imported rather than restated: it is the line the // shipped hook runs before `getOrCreate`, and a hand-written stand-in for it is the // class of substitute this whole file exists to stop making. const sub = RdfOrmSubscription.getOrCreate(shapeType, normalizeConf(conf)); liveSubscription = sub; return { data: sub.isReady ? sub.signalObject : undefined, isLoading: sub.isReady, promise: sub.readyPromise, subscription: sub, nextPage: undefined, previousPage: undefined, }; } 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; startError: string | null; readyMs: number | null; deleteError: string | null; } let probe: LiveProbe | null = null; /** * The collection the published `useShape` hands back RIGHT NOW, by calling it again. * * Calling again is what a React render does, and it is what the result envelope requires: * `data` is `undefined` until the initial payload has arrived, and the envelope captured * at the first call cannot learn that it has - which is why the shipped hook re-runs on a * `useState` flip. `getOrCreate` pools on the shape and the scope, so asking again is the * same subscription and costs nothing. * * This is also the one place this page reads the published surface the way an application * does: through `data`, the member the SDK's own example destructures first. */ function liveView(): Set | undefined { if (probe === null) return undefined; const data = libUseShape(LIVE_SHAPE, { graphs: [probe.doc] }).data; return data === undefined ? undefined : (data as Set); } /** 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; const t0 = Date.now(); try { libUseShape(LIVE_SHAPE, { graphs: [doc] }); } catch (e) { startError = e instanceof Error ? e.message : String(e); probe = { doc, subject, startError, readyMs: null, deleteError: null }; return { doc, subject, seeded, startError }; } probe = { doc, subject, 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: () => liveView()?.size ?? -1, /** Milliseconds from the `useShape` call to `readyPromise`, or null while it is pending. */ liveReadyMs: () => probe?.readyMs ?? null, liveItems: () => [...(liveView() ?? [])].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 view = liveView(); if (view === undefined) return { deleted: false, error: "no data yet" }; const item = [...view][0]; if (item === undefined) return { deleted: false, error: "view is empty" }; try { const deleted = 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 }; } }, /** 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); } })();