import type { QxObjectRef } from "./references.js"; import type { RelationshipCollection, RelationshipEntry } from "./index.js"; type Key = string | boolean | bigint; type Port = { collection(): Promise>; replace(entries: RelationshipEntry[], expectedRevision: bigint): Promise>; }; const checked = async (port: Port, revision: bigint) => { const snapshot = await port.collection(); if (snapshot.revision !== revision) throw new Error("STALE_COLLECTION_REVISION"); return snapshot; }; /** Helpers never retry a failed CAS or silently overwrite concurrent edits. */ export const relationshipMap = (port: Port) => ({ read: () => port.collection(), async get(key: K) { const snapshot = await port.collection(); return { revision: snapshot.revision, value: snapshot.entries.find((entry) => entry.key === key)?.target }; }, async set(key: K, target: T, expectedRevision: bigint) { const snapshot = await checked(port, expectedRevision); const entries = snapshot.entries.filter((entry) => entry.key !== key); const existing = snapshot.entries.find((entry) => entry.key === key && entry.target.equals(target)); entries.push(existing ?? { key, target }); return port.replace(entries, expectedRevision); }, async delete(key: K, expectedRevision: bigint) { const snapshot = await checked(port, expectedRevision); return port.replace( snapshot.entries.filter((entry) => entry.key !== key), expectedRevision, ); }, }); export const relationshipList = (port: Port) => ({ read: () => port.collection(), async insert(index: number, target: T, expectedRevision: bigint) { const snapshot = await checked(port, expectedRevision); if (!Number.isSafeInteger(index) || index < 0 || index > snapshot.entries.length) throw new Error("List index out of bounds"); snapshot.entries.splice(index, 0, { target }); return port.replace(snapshot.entries, expectedRevision); }, async move(edgeId: string, index: number, expectedRevision: bigint) { const snapshot = await checked(port, expectedRevision); const prior = snapshot.entries.findIndex((entry) => entry.edgeId === edgeId); if (prior < 0 || !Number.isSafeInteger(index) || index < 0 || index >= snapshot.entries.length) throw new Error("Unknown list entry or invalid index"); const [entry] = snapshot.entries.splice(prior, 1); snapshot.entries.splice(index, 0, entry); return port.replace(snapshot.entries, expectedRevision); }, async delete(edgeId: string, expectedRevision: bigint) { const snapshot = await checked(port, expectedRevision); if (!snapshot.entries.some((entry) => entry.edgeId === edgeId)) throw new Error("Unknown list entry"); return port.replace( snapshot.entries.filter((entry) => entry.edgeId !== edgeId), expectedRevision, ); }, }); export const relationshipSet = (port: Port) => ({ read: () => port.collection(), async add(target: T, expectedRevision: bigint) { const snapshot = await checked(port, expectedRevision); if (snapshot.entries.some((entry) => entry.target.equals(target))) return snapshot; return port.replace([...snapshot.entries, { target }], expectedRevision); }, async delete(target: T, expectedRevision: bigint) { const snapshot = await checked(port, expectedRevision); return port.replace( snapshot.entries.filter((entry) => !entry.target.equals(target)), expectedRevision, ); }, });