/** * 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 * bare `DeepSignalSet` an application gets from the ORM hook, 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 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 }; } }, /** 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); } })();