import { mock } from "bun:test"; import type { Nuri } 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 constants * * A mock that always answers the same thing tests one state of the world, and a * statement guarded by what a read returned would never run under it. This one * answers what was actually written, so `contentsOf` and `depositsIn` report what * the adapter really left behind rather than what a test arranged. * * ## 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 (`inbox.postToDocument`); * - depositing into a document whose owner never opened an inbox THROWS, rather * than going nowhere; * - opening an inbox is refused to anyone but the document's owner * (`openDocumentInbox`: doing so publishes the document's address); * - a document with no inbox has no address to read — a state, not an error. * * ## 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. This package reads nothing at all, so `readUnion` is not * modelled either: reaching for it is the same red. 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; /** Every triple in a document, read from outside the adapter. */ contentsOf(doc: string): { subject: string; predicate: string; values: string[] }[]; /** * What is waiting in a document's inbox, oldest first — or `null` when its owner * never opened one. An INSPECTION, not an entry of the polyfill: this package * never reads an inbox, so nothing it does may depend on being able to. */ depositsIn(doc: string): readonly Deposit[] | null; } /** * 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 calls: RecordedCall[] = []; 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; } 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 // nothing at all. 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 does not read. " + "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 }); }, }; 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 opening a second inbox. The address // goes no further — this package drops it, which is the point of `openInbox` // returning nothing. return `${stored.nuri}:inbox`; }, }; 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, and an * inherited member is not a modelled entry. */ 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), })); return { calls, signIn(user: string) { // This fake has ONE signed-in identity at a time, as a page does: a polyfill // session IS one identity, and no call takes an identifier. currentUser = user; }, sessionId() { return `session:${currentUser}`; }, depositsIn(doc: string) { const stored = require(doc); if (stored.deposits === undefined) return null; return [...stored.deposits].sort((a, b) => a.ts - b.ts); }, 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; }, }; }