import { mock } from "bun:test"; import type { Nuri, UnionSubject } from "../src/port"; /** * `@ng-eventually/polyfill` itself, in memory — NOT a mock returning constants. * * `fake-nextgraph.ts` stands in one layer higher, behind `NextGraphPort`, so it * exercises the indexing rules and leaves `polyfill-adapter.ts` untouched. This * one stands BELOW the adapter, so the real adapter runs on top of it and every * behavioural test becomes a test of the wiring too. * * ## Why a real implementation and not a recorder returning `[]` * * The previous gate mocked `readUnion` to a constant `[]`. That single constant * was an escape hatch: a destructive statement guarded by * `if ((await readUnion([graph])).length > 0)` never ran, so it was never * recorded, and the gate passed. A mock that always answers the same thing tests * one state of the world. This answers what was actually written, so a curation * run that adds an entry to a document already holding a descriptor DOES take the * non-empty branch. * * ## Why the SPARQL is EXECUTED and not pattern-matched * * {@link applyInsertData} is a strict parser for the one statement this package * writes. It is not a regex looking for `DELETE`: it is an engine that can only * ADD, so a removal is not "detected", it is **unrunnable**. `DELETE WHERE …` * fails at the first keyword; `INSERT DATA { … } ; DROP GRAPH <…>` fails on the * trailing statement; and a keyword hidden inside a literal stays inside the * literal, because a parser tokenises where a regex only matches. A split literal * (`"DEL" + "ETE …"`) is one string by the time it arrives, so the split buys * nothing. * * ## Every rule below is one the polyfill actually enforces * * - only a document's owner writes to it (`docs.sparqlUpdate`'s `assertMayWrite`); * - anyone may DEPOSIT into a document's inbox, but reading one throws for anyone * but its owner (`inbox.read`'s `assertOwnInbox`); * - depositing into a document whose owner never opened an inbox THROWS * (`inbox.postToDocument`), rather than going nowhere; * - a document with no inbox READS as `[]` — a state, not an error * (`depositsForDocument`); * - opening an inbox is refused to anyone but the document's owner * (`openDocumentInbox`), and so is watching one, since being told what landed in an * inbox is reading it; * - watching lasts exactly as long as the identity stays connected: signing in as * somebody else stops every watch the previous identity had opened * (`contract_polyfill-surface`, "Guarantees"); * - `readUnion` swallows a failing document into `[]` (`readDoc`'s * `try {…} catch { return [] }`), and may also reject outright; * - `readUnion` builds each subject's props as a plain object literal filled by * `(props[p] ??= []).push(o)` — inherited members and all. * * ## What is NOT modelled, and what happens then * * Anything the adapter reaches for on `docs`, `inbox` or `storeRegistry` that is * absent here THROWS by name (see {@link namespace}) instead of returning * `undefined` — so routing a query through `docs.sparqlQuery` is a red test with a * message that says so. A brand-new TOP-LEVEL import is the one case that degrades: * Bun's `mock.module` materialises the module namespace from own keys, so a Proxy * there is lost and an unmodelled top-level export arrives as `undefined`. Still * red (`undefined is not a function`), just with a duller message. */ // --- the deposit, as the polyfill publishes it ---------------------------- /** Mirrors `inbox.Deposit`: sender when identified, opaque payload, ms timestamp. */ interface Deposit { readonly from: string | null; readonly payload: unknown; readonly ts: number; } /** Mirrors `inbox.PostOptions` for the two fields this package can reach. */ interface PostOptions { readonly payload?: unknown; readonly from?: string | null; } // --- the one statement this fake can execute ------------------------------ /** A triple as this package writes them: two IRIs and one string literal. */ interface Triple { readonly subject: string; readonly predicate: string; readonly value: string; } const IRI_BREAKERS = new Set(['"', "<", ">", "\\", "^", "`", "{", "|", "}"]); /** * A strict reader for `INSERT DATA {

"v" . … }` and NOTHING else. * * Deliberately whole-string: it consumes the query to its end, so a second * statement smuggled after the closing brace is a parse failure rather than * something the engine quietly ignores. */ class Reader { #at = 0; constructor(private readonly query: string) {} refuse(what: string): never { throw new Error( `[fake-polyfill] refused to execute: ${what} at offset ${this.#at}. ` + "This fake implements ONE statement — an anchored `INSERT DATA` of literal " + "triples — because that is the only thing this package may ever emit. " + `Query: ${JSON.stringify(this.query)}`, ); } #skipSpace(): void { while (this.#at < this.query.length && /\s/.test(this.query[this.#at] ?? "")) this.#at += 1; } keyword(word: string): void { this.#skipSpace(); const slice = this.query.slice(this.#at, this.#at + word.length); const next = this.query[this.#at + word.length] ?? " "; if (slice.toUpperCase() !== word || /[A-Za-z0-9_]/.test(next)) { this.refuse(`expected the keyword ${word}, found ${JSON.stringify(slice)}`); } this.#at += word.length; } symbol(character: string): void { if (!this.trySymbol(character)) this.refuse(`expected ${JSON.stringify(character)}`); } trySymbol(character: string): boolean { this.#skipSpace(); if (this.query[this.#at] !== character) return false; this.#at += 1; return true; } /** An IRI between angle brackets — no character that could close it early. */ iri(): string { this.#skipSpace(); if (this.query[this.#at] === "G" || this.query[this.#at] === "g") { // Named after the shape it is refusing, because this one is a CONVENTION and // not a broker limit: `packages/polyfill/e2e/` verified an anchored // `GRAPH ` write round-trips upstream too. The polyfill writes the // anchored DEFAULT graph with no wrapper and calls that the canonical, // always-safe shape (`src/surface/inbox.ts`), so this layer writes it too — // two layers writing the same data two ways is diagnosis work bought for later. this.refuse( "an explicit GRAPH clause — `docs.sparqlUpdate(sid, update, anchor)` already " + "scopes the write to the anchor's default graph, and the polyfill writes " + "that shape with no GRAPH wrapper", ); } this.symbol("<"); let out = ""; while (true) { const character = this.query[this.#at]; if (character === undefined) this.refuse("an IRI that is never closed"); this.#at += 1; if (character === ">") return out; if (IRI_BREAKERS.has(character) || (character.codePointAt(0) ?? 0) <= 0x20) { this.refuse(`${JSON.stringify(character)} inside an IRI`); } out += character; } } /** A quoted literal, unescaped back to the string the caller passed in. */ literal(): string { this.#skipSpace(); this.symbol('"'); let out = ""; while (true) { const character = this.query[this.#at]; if (character === undefined) this.refuse("a literal that is never closed"); this.#at += 1; if (character === '"') return out; if (character !== "\\") { out += character; continue; } const escaped = this.query[this.#at]; this.#at += 1; if (escaped === undefined) this.refuse("a trailing backslash"); const known: Record = { n: "\n", r: "\r", t: "\t", "\\": "\\", '"': '"' }; const decoded = Object.hasOwn(known, escaped) ? known[escaped] : undefined; if (decoded === undefined) this.refuse(`the escape \\${escaped}`); out += decoded; } } end(): void { this.#skipSpace(); if (this.#at !== this.query.length) this.refuse("a second statement"); } } /** Reads the ONLY query form this package emits. Everything else throws. */ export function applyInsertData(query: string): Triple[] { const reader = new Reader(query); reader.keyword("INSERT"); reader.keyword("DATA"); reader.symbol("{"); const triples: Triple[] = []; while (!reader.trySymbol("}")) { const subject = reader.iri(); const predicate = reader.iri(); const value = reader.literal(); triples.push({ subject, predicate, value }); reader.trySymbol("."); } reader.end(); return triples; } // --- the lexical tripwire, kept as a SECOND and independent layer --------- /** * Blanks every SPARQL string literal, so a keyword INSIDE one reads as what it is: * inert text. If escaping ever breaks, the injected statement lands OUTSIDE a * literal and survives this — which is the whole point. */ export function blankLiterals(value: string): string { let out = ""; let inside = false; for (let i = 0; i < value.length; i += 1) { const character = value[i]; if (inside && character === "\\") { i += 1; // an escaped character can never close the literal continue; } if (character === '"') { inside = !inside; out += '"'; continue; } if (!inside) out += character; } return out; } /** * Every SPARQL 1.1 form that can destroy or displace data, plus the modifiers that * introduce one. For a tripwire a false positive is far cheaper than a miss. */ export const DESTRUCTIVE = /\b(DELETE|DROP|CLEAR|MOVE|COPY|ADD|LOAD|MODIFY|WITH|SILENT)\b/i; // --- what was called ------------------------------------------------------ export interface RecordedCall { /** `docs.sparqlUpdate`, `inbox.postToDocument`, … */ readonly entry: string; readonly args: readonly unknown[]; } interface StoredDocument { readonly nuri: Nuri; readonly owner: string; readonly subjects: Map>; /** `undefined` until the owner opens one — the state `postToDocument` refuses. */ deposits: Deposit[] | undefined; } export interface FakePolyfill { /** Every call the adapter made into the polyfill, in order. */ readonly calls: readonly RecordedCall[]; /** Acts under this identity from now on — a polyfill session IS one identity. */ signIn(user: string): void; /** The session id for the signed-in identity, as `init`'s callback hands it over. */ sessionId(): string; /** * The broker can no longer answer about this document. `readDoc` catches and * yields `[]`, so it arrives indistinguishable from an empty document — which is * exactly the confusion `resolution.ts` exists to refuse. */ breakReadsOf(doc: string, reason: string): void; healReadsOf(doc: string): void; /** `readUnion` REJECTS outright — its session-level failure, not a per-document one. */ breakReadUnion(reason: string): void; healReadUnion(): void; /** * Hands over every inbox notification the broker was holding, and waits for the * watching session to finish with each — the callback is declared `void` upstream, * so production never waits for it, and this does only because a test needs a point * at which the work is over. */ deliverNotifications(): Promise; /** Every subject in a document, read from outside the adapter. */ contentsOf(doc: string): { subject: string; predicate: string; values: string[] }[]; } /** * Installs the fake in place of `@ng-eventually/polyfill` and hands back the * handle on it. Call at the TOP of a test file: the adapter must be imported * after this runs, so import it with `await import(…)` inside the tests. */ export function installFakePolyfill(): FakePolyfill { const documents = new Map(); const unreachable = new Map(); const calls: RecordedCall[] = []; /** address → the document whose inbox it is. Nothing else resolves one. */ const inboxAddresses = new Map(); let watches: { readonly doc: string; readonly onDeposits: (d: Deposit[]) => unknown }[] = []; let undelivered: { readonly doc: string; readonly onDeposits: (d: Deposit[]) => unknown }[] = []; let unionFailure: string | undefined; let currentUser = "nobody"; let documentCount = 0; let clock = 0; function require(doc: string): StoredDocument { const stored = documents.get(doc); if (stored === undefined) throw new Error(`cannot open ${doc}`); return stored; } function readOne(doc: string): UnionSubject[] { const broken = unreachable.get(doc); if (broken !== undefined) throw new Error(`cannot reach ${doc}: ${broken}`); const stored = require(doc); const out: UnionSubject[] = []; for (const [subject, properties] of stored.subjects) { // Built EXACTLY as `readUnion` builds it — a plain object literal filled by // `(props[p] ??= []).push(o)`. Neither detail is cosmetic: the literal // inherits from `Object.prototype`, and `??=` does NOT assign over an // inherited truthy member, so a predicate named `constructor` or `toString` // leaves `.push` undefined and the read THROWS. const props: Record = {}; for (const [predicate, values] of properties) { for (const value of values) { (props[predicate] ??= []).push(value); } } out.push({ subject, graph: doc as Nuri, props }); } return out; } const docsImpl = { async sparqlUpdate(sessionId: unknown, query: unknown, anchor?: unknown): Promise { if (typeof query !== "string") throw new Error("[fake-polyfill] the query must be a string"); const writer = userOfSession(sessionId); if (writer !== currentUser) { // A session belongs to ONE identity upstream, so these cannot disagree. throw new Error( `[fake-polyfill] session of ${writer} used while ${currentUser} is signed in`, ); } if (typeof anchor !== "string") { throw new Error( "[fake-polyfill] docs.sparqlUpdate without an anchor: the write would not be " + "scoped to a document. This package always anchors — see polyfill-adapter.ts.", ); } const stored = require(anchor); if (stored.owner !== writer) { throw new Error( `${writer} may not write ${anchor}: only a document's owner writes to it, and ` + "holding its read key never grants a write", ); } for (const triple of applyInsertData(query)) { let properties = stored.subjects.get(triple.subject); if (properties === undefined) { properties = new Map(); stored.subjects.set(triple.subject, properties); } const values = properties.get(triple.predicate); // An INSERT of a triple already present is a no-op in RDF: a graph is a set. if (values === undefined) properties.set(triple.predicate, [triple.value]); else if (!values.includes(triple.value)) values.push(triple.value); } return []; }, async sparqlQuery(_sessionId: unknown, query: unknown): Promise { // Reached only if this package starts querying, which it does not: it reads // through `readUnion`. The message splits the two reasons someone lands here, // because one of them is an attempt to write through the read door. if (typeof query === "string" && DESTRUCTIVE.test(blankLiterals(query))) { throw new Error( "[fake-polyfill] a destructive statement was routed through docs.sparqlQuery. " + "An index only ever grows — there is no door for this. " + `Query: ${JSON.stringify(query)}`, ); } throw new Error( "[fake-polyfill] docs.sparqlQuery is not modelled — this package reads through " + "`readUnion`. Model it READ-ONLY here before using it.", ); }, }; const inboxImpl = { async postToDocument(doc: unknown, options: unknown): Promise { const stored = require(String(doc)); if (stored.deposits === undefined) { throw new Error( `[ng-eventually] inbox.postToDocument: this document has no inbox — either its ` + `owner never opened one, or you cannot read the document: ${JSON.stringify(doc)}`, ); } const opts = (options ?? {}) as PostOptions; // "Defaults to the current polyfill user when the property is entirely absent." const from = Object.hasOwn(opts, "from") ? (opts.from ?? null) : currentUser; clock += 1; stored.deposits.push({ from, payload: opts.payload ?? null, ts: clock }); // Stored first, told afterwards — and told over the wire, which is why the // notification waits for `deliverNotifications` rather than firing inline. for (const watch of watches) if (watch.doc === stored.nuri) undelivered.push(watch); }, watch(targetInbox: unknown, onDeposits: unknown): () => void { const doc = inboxAddresses.get(String(targetInbox)); if (doc === undefined) { // "`inbox.post` refuses a target that is not an inbox" — so does watching one, // and nothing outside `openDocumentInbox` ever hands an address out. throw new Error(`[fake-polyfill] not an inbox address: ${String(targetInbox)}`); } if (require(doc).owner !== currentUser) { throw new Error( `${currentUser} may not watch the inbox of ${doc}: you may DEPOSIT into ` + "anyone's inbox, you may only READ your own", ); } if (typeof onDeposits !== "function") { throw new Error("[fake-polyfill] inbox.watch takes a callback"); } const watch = { doc, onDeposits: onDeposits as (d: Deposit[]) => unknown }; watches.push(watch); return () => { watches = watches.filter((w) => w !== watch); }; }, async readForDocument(doc: unknown): Promise { const stored = require(String(doc)); // No inbox → there is no address to read, which is a state and not an error. if (stored.deposits === undefined) return []; if (stored.owner !== currentUser) { throw new Error( `${currentUser} may not read the inbox of ${String(doc)}: you may DEPOSIT into ` + "anyone's inbox, you may only READ your own", ); } return [...stored.deposits].sort((a, b) => a.ts - b.ts); }, }; const storeRegistryImpl = { async createEntityDoc(scope: unknown): Promise { if (scope !== "public" && scope !== "mine") { throw new Error(`[fake-polyfill] unknown scope ${JSON.stringify(scope)}`); } documentCount += 1; const nuri = `did:ng:o:doc-${documentCount}` as Nuri; documents.set(nuri, { nuri, owner: currentUser, subjects: new Map(), deposits: undefined }); return nuri; }, async openDocumentInbox(doc: unknown): Promise { const stored = require(String(doc)); if (stored.owner !== currentUser) { throw new Error( `${currentUser} may not open an inbox on ${String(doc)}: opening one publishes ` + "the document's address, so a non-owner would route the owner's deposits to itself", ); } stored.deposits ??= []; // Idempotent within a page: asking again for a document that already has one // resolves that same address rather than adding a second inbox. const address = `${stored.nuri}:inbox`; inboxAddresses.set(address, stored.nuri); return address; }, async listMyEntityDocs(scope: unknown): Promise { if (scope !== "public" && scope !== "mine") { throw new Error(`[fake-polyfill] unknown scope ${JSON.stringify(scope)}`); } // "listMyEntityDocs returns a listing whose documents you can open, or it throws." const mine: Nuri[] = []; for (const stored of documents.values()) { if (stored.owner === currentUser) mine.push(stored.nuri); } return mine; }, }; async function readUnionImpl(docsLike: unknown): Promise { if (unionFailure !== undefined) throw new Error(unionFailure); const list = Array.isArray(docsLike) ? docsLike : []; const out: UnionSubject[] = []; for (const doc of [...new Set(list.filter(Boolean))]) { try { out.push(...readOne(String(doc))); } catch (error) { // `readDoc` is `try {…} catch { return [] }`: a failing document is skipped // and never aborts the batch, so failure and emptiness arrive identical. console.error("[fake-polyfill] read failed for", doc, String(error)); } } return out; } function userOfSession(sessionId: unknown): string { const id = String(sessionId); const user = id.startsWith("session:") ? id.slice("session:".length) : undefined; if (user === undefined) throw new Error(`[fake-polyfill] not a session id: ${id}`); return user; } function record(entry: string, args: readonly unknown[]): void { calls.push({ entry, args }); } /** * A namespace whose every member is recorded, and whose UNKNOWN members throw by * name rather than arriving as `undefined`. `Object.hasOwn` and not `impl[name]`, * because a plain object literal inherits `toString` and friends — the same trap * `valuesOf` guards against in `src/index-document.ts`. */ function namespace(name: string, impl: Record unknown>): unknown { return new Proxy(impl, { get(target, property) { if (typeof property === "symbol" || property === "then") return undefined; const entry = `${name}.${property}`; return (...args: unknown[]) => { record(entry, args); if (!Object.hasOwn(target, property)) { throw new Error( `[fake-polyfill] ${entry} is not modelled. The adapter reached for a polyfill ` + "entry this fake knows nothing about, so nothing here can vouch for what it " + "does. Model it (read-only if it queries) before using it.", ); } return (target[property] as (...a: unknown[]) => unknown)(...args); }; }, }); } mock.module("@ng-eventually/polyfill", () => ({ docs: namespace("docs", docsImpl), inbox: namespace("inbox", inboxImpl), storeRegistry: namespace("storeRegistry", storeRegistryImpl), async readUnion(docsLike: unknown) { record("readUnion", [docsLike]); return readUnionImpl(docsLike); }, })); return { calls, signIn(user: string) { // "It lasts exactly as long as that identity stays connected — changing identity // or clearing it stops it." This fake has ONE signed-in identity at a time, as a // page does, so a watch cannot outlive the identity that opened it. Signing in as // the same identity again is not a change, and stops nothing. if (user !== currentUser) { watches = []; undelivered = []; } currentUser = user; }, async deliverNotifications() { while (undelivered.length > 0) { const batch = undelivered; undelivered = []; for (const watch of batch) { const stored = documents.get(watch.doc); await watch.onDeposits([...(stored?.deposits ?? [])]); } } }, sessionId() { return `session:${currentUser}`; }, breakReadsOf(doc: string, reason: string) { unreachable.set(doc, reason); }, healReadsOf(doc: string) { unreachable.delete(doc); }, breakReadUnion(reason: string) { unionFailure = reason; }, healReadUnion() { unionFailure = undefined; }, contentsOf(doc: string) { const stored = require(doc); const out: { subject: string; predicate: string; values: string[] }[] = []; for (const [subject, properties] of stored.subjects) { for (const [predicate, values] of properties) out.push({ subject, predicate, values }); } return out; }, }; }