@sanity-labs/client-fake-for-test 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +435 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -26
- package/dist/index.d.ts +226 -26
- package/dist/index.js +436 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/access-control.ts","../src/patch.ts","../src/store.ts","../src/transaction.ts","../src/client.ts"],"sourcesContent":["/**\n * Optional access-control simulation for the in-memory test client.\n *\n * When configured, the TestClient does two things a real `@sanity/client`\n * does over the wire but the bare in-memory client otherwise skips:\n *\n * 1. **Exposes `client.request({ url })`** that serves:\n * - `GET /users/me` → the configured `CurrentUser`\n * - `GET <aclPath>` → the configured `Grant[]`\n * This is what the engine reads from to resolve `WorkflowAccess`\n * when no override is passed.\n *\n * 2. **Gates writes through the configured grants.** Every mutation\n * (`create`, `createOrReplace`, `createIfNotExists`, `delete`,\n * `patch`, and per-op inside `transaction`) checks whether the\n * configured grants allow the relevant `DocumentValuePermission`\n * on the target document. On denial the client throws a\n * `WriteAccessDeniedError` shaped like Sanity's 403 — the same\n * failure mode a production write would hit, surfaced in tests.\n *\n * When `accessControl` is omitted, the client behaves as before:\n * `request` is undefined, writes are unconditional, and the engine\n * falls through to whatever override the caller (typically the\n * bench's `ALL_ACCESS`) provides.\n *\n * The grant evaluation runs the GROQ filter on a singleton dataset\n * containing the doc being checked — same shape as the engine's\n * `permissions.matchesFilter`, lifted from canvas.\n */\n\nimport type { SanityDocumentLike } from \"@sanity/types\";\nimport { evaluate, parse } from \"groq-js\";\n\nexport type DocumentValuePermission = \"create\" | \"read\" | \"update\";\n\nexport interface CurrentUser {\n id: string;\n name?: string;\n email?: string;\n /** @deprecated singular `role` on legacy responses */\n role?: string;\n roles?: { name: string; title?: string; description?: string }[];\n}\n\nexport interface Grant {\n filter: string;\n permissions: DocumentValuePermission[];\n}\n\nexport interface TestClientAccessControl {\n /** What `client.request({ url: \"/users/me\" })` returns. */\n currentUser: CurrentUser;\n /** What `client.request({ url: aclPath })` returns. */\n grants: Grant[];\n /**\n * URL path the engine's access resolver hits to fetch grants.\n * Defaults to `/projects/<projectId>/datasets/<dataset>/acl`.\n */\n aclPath?: string;\n}\n\n/**\n * Sanity-style 403 raised when a configured-grants check rejects a\n * write. The thrown object carries the doc id, the attempted\n * permission, and the count of matching grants (for parity with the\n * engine's `permission-denied` disabled-reason).\n */\nexport class WriteAccessDeniedError extends Error {\n readonly documentId: string;\n readonly permission: DocumentValuePermission;\n readonly matchingGrants: number;\n\n constructor(args: {\n documentId: string;\n permission: DocumentValuePermission;\n matchingGrants: number;\n }) {\n super(\n `Sanity (test): permission denied — actor lacks \"${args.permission}\" on document \"${args.documentId}\" ` +\n `(${args.matchingGrants} grant${args.matchingGrants === 1 ? \"\" : \"s\"} matched the document but ` +\n `none included \"${args.permission}\").`,\n );\n this.name = \"WriteAccessDeniedError\";\n this.documentId = args.documentId;\n this.permission = args.permission;\n this.matchingGrants = args.matchingGrants;\n }\n}\n\n/**\n * Predicate variant of `evaluateWriteAccess` — same logic but only\n * returns the boolean. Used by read-gating paths\n * (`fetch`/`getDocument`/`getDocuments`) where the documented Sanity\n * behavior is \"invisible doc → 404\", not \"throw 403\". So callers\n * can filter the dataset before GROQ runs and return `null` from\n * single-doc reads, instead of leaking a denial through the read\n * surface.\n */\nexport async function canAccess(args: {\n document: SanityDocumentLike & { _id?: string; _type?: string };\n grants: Grant[];\n permission: DocumentValuePermission;\n userId: string;\n}): Promise<boolean> {\n const result = await evaluateWriteAccess(args);\n return result.allowed;\n}\n\n// Parsed-filter cache. The GROQ AST is cheap to reuse; only the\n// per-document evaluation needs to re-run.\nconst parsedFilters = new Map<string, ReturnType<typeof parse>>();\n\nasync function matchesFilter(args: {\n document: SanityDocumentLike & { _id?: string; _type?: string };\n filter: string;\n userId: string;\n}): Promise<boolean> {\n const { document, filter, userId } = args;\n if (!parsedFilters.has(filter)) {\n parsedFilters.set(filter, parse(`*[${filter}]`));\n }\n const ast = parsedFilters.get(filter)!;\n const result = await evaluate(ast, {\n dataset: [document as Record<string, unknown>],\n identity: userId,\n });\n const data = await result.get();\n return Array.isArray(data) && data.length === 1;\n}\n\n/**\n * Resolve whether `grants` allow `permission` on `document` for the\n * given user. Same logic the engine uses in `permissions.grantsPermissionOn`\n * (most-permissive wins; multiple grants compose).\n *\n * Returns `{ allowed: boolean, matchingGrants: number }` so callers\n * can build the structured error without re-counting.\n */\nexport async function evaluateWriteAccess(args: {\n document: SanityDocumentLike & { _id?: string; _type?: string };\n grants: Grant[];\n permission: DocumentValuePermission;\n userId: string;\n}): Promise<{ allowed: boolean; matchingGrants: number }> {\n const { document, grants, permission, userId } = args;\n let matching = 0;\n for (const grant of grants) {\n if (\n await matchesFilter({\n document,\n filter: grant.filter,\n userId,\n })\n ) {\n matching++;\n if (grant.permissions.includes(permission)) {\n return { allowed: true, matchingGrants: matching };\n }\n }\n }\n return { allowed: false, matchingGrants: matching };\n}\n\n/**\n * Throw a `WriteAccessDeniedError` if `grants` don't permit the\n * supplied `permission` on `document`. No-op when allowed. Use this\n * from every mutation entrypoint that wants to simulate Sanity's\n * write boundary.\n */\nexport async function enforceWriteAccess(args: {\n document: SanityDocumentLike & { _id?: string; _type?: string };\n grants: Grant[];\n permission: DocumentValuePermission;\n userId: string;\n}): Promise<void> {\n const { document, grants, permission, userId } = args;\n const { allowed, matchingGrants } = await evaluateWriteAccess({\n document,\n grants,\n permission,\n userId,\n });\n if (allowed) return;\n if (typeof document._id !== \"string\") {\n throw new WriteAccessDeniedError({\n documentId: \"(unknown)\",\n permission,\n matchingGrants,\n });\n }\n throw new WriteAccessDeniedError({\n documentId: document._id,\n permission,\n matchingGrants,\n });\n}\n\n/**\n * Default ACL path the engine pulls grants from. Mirrors Sanity's\n * project/dataset ACL endpoint. Callers can override via\n * `accessControl.aclPath`.\n */\nexport function defaultAclPath(projectId: string, dataset: string): string {\n return `/projects/${projectId}/datasets/${dataset}/acl`;\n}\n\n/**\n * Internal helper: turn the `accessControl` config into the bound\n * `request` function the client exposes, plus a helper to gate writes.\n * Returns `undefined` when no config — leaves `request` off so the\n * engine's \"no token available\" path triggers (which is the cue for\n * the bench to supply an `access` override).\n */\nexport function makeRequestImpl(\n accessControl: TestClientAccessControl,\n projectId: string,\n dataset: string,\n): <T>(opts: { url?: string; uri?: string; signal?: AbortSignal; tag?: string }) => Promise<T> {\n const aclPath = accessControl.aclPath ?? defaultAclPath(projectId, dataset);\n return async <T>(opts: {\n url?: string;\n uri?: string;\n signal?: AbortSignal;\n tag?: string;\n }): Promise<T> => {\n // `@sanity/client` distinguishes `uri` (project-scoped, e.g.\n // `/users/me`) from `url` (raw). Studio uses `uri` for global\n // endpoints. Treat both identically for the mock.\n const path = opts.uri ?? opts.url;\n if (path === \"/users/me\") {\n return accessControl.currentUser as unknown as T;\n }\n if (path === aclPath) {\n return accessControl.grants as unknown as T;\n }\n throw new Error(\n `TestClient.request: no handler for path \"${path}\". The TestClient only serves ` +\n `\"/users/me\" and \"${aclPath}\". Configure additional endpoints via createTestClient({ accessControl }).`,\n );\n };\n}\n","import type { SanityDocument } from \"@sanity/types\";\n\n/**\n * Patch operations the test client supports.\n *\n * Subset of `@sanity/client`'s PatchOperations. Each entry is one\n * mutation step applied in order at commit time.\n */\nexport type PatchOp =\n | { kind: \"set\"; props: Record<string, unknown> }\n | { kind: \"setIfMissing\"; props: Record<string, unknown> }\n | { kind: \"unset\"; paths: string[] }\n | { kind: \"inc\"; props: Record<string, number> }\n | { kind: \"dec\"; props: Record<string, number> }\n | {\n kind: \"insert\";\n position: \"before\" | \"after\" | \"replace\";\n anchor: string;\n items: unknown[];\n };\n\nexport interface PatchHandle {\n set: (props: Record<string, unknown>) => PatchHandle;\n setIfMissing: (props: Record<string, unknown>) => PatchHandle;\n unset: (paths: string[]) => PatchHandle;\n inc: (props: Record<string, number>) => PatchHandle;\n dec: (props: Record<string, number>) => PatchHandle;\n insert: (\n position: \"before\" | \"after\" | \"replace\",\n anchor: string,\n items: unknown[],\n ) => PatchHandle;\n ifRevisionId: (rev: string) => PatchHandle;\n commit: () => Promise<SanityDocument>;\n /** Test-only introspection — not on real Patch */\n toJSON: () => { id: string; ops: PatchOp[]; ifRev?: string };\n}\n\nexport interface PatchExecutor {\n apply: (documentId: string, ops: PatchOp[], ifRev: string | undefined) => Promise<SanityDocument>;\n}\n\nexport function createPatch(documentId: string, executor: PatchExecutor): PatchHandle {\n const ops: PatchOp[] = [];\n let ifRev: string | undefined;\n\n const handle: PatchHandle = {\n set: (props) => {\n ops.push({ kind: \"set\", props });\n return handle;\n },\n setIfMissing: (props) => {\n ops.push({ kind: \"setIfMissing\", props });\n return handle;\n },\n unset: (paths) => {\n ops.push({ kind: \"unset\", paths });\n return handle;\n },\n inc: (props) => {\n ops.push({ kind: \"inc\", props });\n return handle;\n },\n dec: (props) => {\n ops.push({ kind: \"dec\", props });\n return handle;\n },\n insert: (position, anchor, items) => {\n ops.push({ kind: \"insert\", position, anchor, items });\n return handle;\n },\n ifRevisionId: (rev) => {\n ifRev = rev;\n return handle;\n },\n commit: () => executor.apply(documentId, ops, ifRev),\n toJSON: () => ({\n id: documentId,\n ops: [...ops],\n ...(ifRev !== undefined ? { ifRev } : {}),\n }),\n };\n\n return handle;\n}\n\n/**\n * Apply an ordered list of patch ops to a document, returning the new doc.\n *\n * The path semantics are deliberately a small subset:\n *\n * - **Dotted paths** (`a.b.c`) for nested object fields.\n * - **Array index** (`a.b[0]`) for positional access into arrays.\n * - **Keyed lookup** (`a.b[_key==\"x\"]`) for finding an array entry by `_key`.\n *\n * Anything more exotic (recursive `..`, GROQ filter expressions, multi-match)\n * is intentionally out of scope and will throw — the workflow engine only\n * needs the three forms above.\n */\nexport function applyPatchOps(doc: SanityDocument, ops: PatchOp[]): SanityDocument {\n let next: SanityDocument = structuredClone(doc);\n for (const op of ops) {\n next = applyOne(next, op);\n }\n return next;\n}\n\nfunction applyOne(doc: SanityDocument, op: PatchOp): SanityDocument {\n switch (op.kind) {\n case \"set\":\n for (const [path, value] of Object.entries(op.props)) {\n setAtPath(doc, path, value);\n }\n return doc;\n case \"setIfMissing\":\n for (const [path, value] of Object.entries(op.props)) {\n if (getAtPath(doc, path) === undefined) {\n setAtPath(doc, path, value);\n }\n }\n return doc;\n case \"unset\":\n for (const path of op.paths) {\n unsetAtPath(doc, path);\n }\n return doc;\n case \"inc\":\n for (const [path, delta] of Object.entries(op.props)) {\n const current = getAtPath(doc, path);\n const base = typeof current === \"number\" ? current : 0;\n setAtPath(doc, path, base + delta);\n }\n return doc;\n case \"dec\":\n for (const [path, delta] of Object.entries(op.props)) {\n const current = getAtPath(doc, path);\n const base = typeof current === \"number\" ? current : 0;\n setAtPath(doc, path, base - delta);\n }\n return doc;\n case \"insert\": {\n insertIntoArray(doc, op.position, op.anchor, op.items);\n return doc;\n }\n }\n}\n\ninterface PathSegment {\n key: string;\n // Optional array selector. Either a numeric index or a key=value match.\n selector?: { kind: \"index\"; index: number } | { kind: \"keyed\"; field: string; value: string };\n}\n\nfunction parsePath(path: string): PathSegment[] {\n // Examples:\n // \"currentStageId\"\n // \"taskStatus[0].status\"\n // \"taskStatus[_key==\\\"approve\\\"].status\"\n // \"context[key==\\\"releaseId\\\"].value\"\n const segments: PathSegment[] = [];\n for (const raw of path.split(\".\")) {\n const match = /^([A-Za-z_$][\\w$]*)(?:\\[(.+)\\])?$/.exec(raw);\n if (!match) throw new Error(`Unsupported patch path segment: ${raw}`);\n const key = match[1] as string;\n const selectorRaw = match[2];\n if (selectorRaw === undefined) {\n segments.push({ key });\n continue;\n }\n const indexMatch = /^(\\d+)$/.exec(selectorRaw);\n if (indexMatch) {\n segments.push({ key, selector: { kind: \"index\", index: Number(indexMatch[1]) } });\n continue;\n }\n const keyedMatch = /^([A-Za-z_$][\\w$]*)\\s*==\\s*\"([^\"]*)\"$/.exec(selectorRaw);\n if (keyedMatch) {\n segments.push({\n key,\n selector: { kind: \"keyed\", field: keyedMatch[1] as string, value: keyedMatch[2] as string },\n });\n continue;\n }\n throw new Error(`Unsupported array selector in patch path: ${selectorRaw}`);\n }\n return segments;\n}\n\nfunction resolveSelector(array: unknown[], selector: NonNullable<PathSegment[\"selector\"]>): number {\n if (selector.kind === \"index\") return selector.index;\n const idx = array.findIndex(\n (entry) =>\n typeof entry === \"object\" &&\n entry !== null &&\n (entry as Record<string, unknown>)[selector.field] === selector.value,\n );\n if (idx === -1) {\n throw new Error(`No array entry matched ${selector.field}==\"${selector.value}\" in patch path`);\n }\n return idx;\n}\n\nfunction getAtPath(doc: SanityDocument, path: string): unknown {\n const segments = parsePath(path);\n let cursor: unknown = doc;\n for (const seg of segments) {\n if (cursor === undefined || cursor === null) return undefined;\n cursor = (cursor as Record<string, unknown>)[seg.key];\n if (seg.selector !== undefined) {\n if (!Array.isArray(cursor)) return undefined;\n const idx = resolveSelector(cursor, seg.selector);\n cursor = cursor[idx];\n }\n }\n return cursor;\n}\n\nfunction setAtPath(doc: SanityDocument, path: string, value: unknown): void {\n const segments = parsePath(path);\n if (segments.length === 0) throw new Error(`Empty patch path`);\n let cursor: Record<string, unknown> = doc as unknown as Record<string, unknown>;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i] as PathSegment;\n let next = cursor[seg.key];\n if (next === undefined) {\n next = seg.selector?.kind === \"index\" || seg.selector?.kind === \"keyed\" ? [] : {};\n cursor[seg.key] = next;\n }\n if (seg.selector !== undefined) {\n if (!Array.isArray(next)) {\n throw new Error(`Patch path crosses non-array at segment ${seg.key}`);\n }\n const idx = resolveSelector(next, seg.selector);\n cursor = next[idx] as Record<string, unknown>;\n } else {\n cursor = next as Record<string, unknown>;\n }\n }\n const last = segments[segments.length - 1] as PathSegment;\n if (last.selector !== undefined) {\n const arr = cursor[last.key];\n if (!Array.isArray(arr)) {\n throw new Error(`Patch path crosses non-array at segment ${last.key}`);\n }\n const idx = resolveSelector(arr, last.selector);\n arr[idx] = value;\n } else {\n cursor[last.key] = value;\n }\n}\n\nfunction unsetAtPath(doc: SanityDocument, path: string): void {\n const segments = parsePath(path);\n if (segments.length === 0) return;\n let cursor: Record<string, unknown> = doc as unknown as Record<string, unknown>;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i] as PathSegment;\n let next = cursor[seg.key];\n if (next === undefined || next === null) return;\n if (seg.selector !== undefined) {\n if (!Array.isArray(next)) return;\n const idx = resolveSelector(next, seg.selector);\n next = next[idx];\n }\n cursor = next as Record<string, unknown>;\n }\n const last = segments[segments.length - 1] as PathSegment;\n if (last.selector !== undefined) {\n const arr = cursor[last.key];\n if (!Array.isArray(arr)) return;\n const idx = resolveSelector(arr, last.selector);\n arr.splice(idx, 1);\n } else {\n delete cursor[last.key];\n }\n}\n\nfunction insertIntoArray(\n doc: SanityDocument,\n position: \"before\" | \"after\" | \"replace\",\n anchor: string,\n items: unknown[],\n): void {\n // anchor is a path that must terminate at an array entry (selector required).\n // For \"replace\", we replace the entry; for \"before\"/\"after\", we insert\n // adjacent to it.\n const segments = parsePath(anchor);\n if (segments.length === 0) throw new Error(`Empty insert anchor`);\n const last = segments[segments.length - 1] as PathSegment;\n if (last.selector === undefined) {\n throw new Error(`Insert anchor must end with an array selector: ${anchor}`);\n }\n\n let cursor: Record<string, unknown> = doc as unknown as Record<string, unknown>;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i] as PathSegment;\n const next = cursor[seg.key];\n if (next === undefined || next === null) {\n throw new Error(`Insert anchor traverses missing field: ${seg.key}`);\n }\n if (seg.selector !== undefined) {\n if (!Array.isArray(next)) {\n throw new Error(`Insert anchor traverses non-array at: ${seg.key}`);\n }\n cursor = next[resolveSelector(next, seg.selector)] as Record<string, unknown>;\n } else {\n cursor = next as Record<string, unknown>;\n }\n }\n const arr = cursor[last.key];\n if (!Array.isArray(arr)) {\n throw new Error(`Insert anchor target is not an array: ${last.key}`);\n }\n const idx = resolveSelector(arr, last.selector);\n if (position === \"replace\") {\n arr.splice(idx, 1, ...items);\n } else if (position === \"before\") {\n arr.splice(idx, 0, ...items);\n } else {\n arr.splice(idx + 1, 0, ...items);\n }\n}\n","import type { SanityDocument, SanityDocumentLike } from \"@sanity/types\";\n\n/**\n * In-memory document store backing a TestClient. Closure-scoped — created\n * fresh per test, no global state.\n *\n * The shape mirrors what `groq-js` evaluates against: an array of\n * documents. Internally we keep a Map by `_id` for O(1) lookup, and\n * project to an array on each query.\n */\nexport interface TestStore {\n /** Replace the entire dataset with the given documents. Existing docs are dropped. */\n seed: (documents: SanityDocumentLike[]) => void;\n /** Add documents without dropping existing ones. Conflicts (same _id) overwrite. */\n add: (documents: SanityDocumentLike[]) => void;\n /** Remove all documents. */\n reset: () => void;\n /** All documents as an array, in insertion order. */\n all: () => SanityDocument[];\n /** Get one document by _id, or undefined. */\n get: (id: string) => SanityDocument | undefined;\n /** Insert a fully-formed document by _id. Replaces if it already exists. */\n put: (doc: SanityDocument) => void;\n /** Remove a document by _id. Returns the removed doc or undefined. */\n remove: (id: string) => SanityDocument | undefined;\n /** Snapshot of all docs, useful for assertions. */\n snapshot: () => readonly SanityDocument[];\n}\n\n/**\n * Create a fresh, isolated store. The returned object closes over a private\n * Map; two stores created from two calls do not see each other.\n */\nexport function createTestStore(initial?: SanityDocumentLike[]): TestStore {\n const docs = new Map<string, SanityDocument>();\n\n const promote = (doc: SanityDocumentLike): SanityDocument => {\n if (typeof doc._id !== \"string\" || doc._id.length === 0) {\n throw new Error(`document is missing _id: ${JSON.stringify(doc)}`);\n }\n if (typeof doc._type !== \"string\" || doc._type.length === 0) {\n throw new Error(`document ${doc._id} is missing _type`);\n }\n const now = new Date().toISOString();\n return {\n ...doc,\n _id: doc._id,\n _type: doc._type,\n _rev: doc._rev ?? cryptoRandomKey(),\n _createdAt: doc._createdAt ?? now,\n _updatedAt: doc._updatedAt ?? now,\n } as SanityDocument;\n };\n\n const seed = (documents: SanityDocumentLike[]) => {\n docs.clear();\n for (const d of documents) {\n const promoted = promote(d);\n docs.set(promoted._id, promoted);\n }\n };\n\n if (initial) seed(initial);\n\n return {\n seed,\n add: (documents) => {\n for (const d of documents) {\n const promoted = promote(d);\n docs.set(promoted._id, promoted);\n }\n },\n reset: () => docs.clear(),\n all: () => [...docs.values()],\n get: (id) => docs.get(id),\n put: (doc) => {\n docs.set(doc._id, doc);\n },\n remove: (id) => {\n const doc = docs.get(id);\n if (doc) docs.delete(id);\n return doc;\n },\n snapshot: () => [...docs.values()],\n };\n}\n\n/**\n * Generate a stable-ish random key without pulling in a dependency.\n * Use cases: synthesising `_rev` for newly-stored documents.\n */\nexport function cryptoRandomKey(length = 12): string {\n const bytes = new Uint8Array(length);\n // globalThis.crypto is available in Node 22+ and all modern browsers.\n globalThis.crypto.getRandomValues(bytes);\n return [...bytes]\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\")\n .slice(0, length);\n}\n","import type { SanityDocument, SanityDocumentLike } from \"@sanity/types\";\nimport type { PatchHandle, PatchOp } from \"./patch.ts\";\n\n/**\n * A transaction collects mutations and applies them atomically at commit\n * time. Mutations are applied in declaration order; if any one fails, the\n * whole transaction throws and no writes land in the store.\n *\n * Subset of `@sanity/client`'s Transaction. Same shape, narrower surface.\n */\nexport type TxnMutation =\n | { kind: \"create\"; doc: SanityDocumentLike }\n | { kind: \"createOrReplace\"; doc: SanityDocumentLike }\n | { kind: \"createIfNotExists\"; doc: SanityDocumentLike }\n | { kind: \"delete\"; id: string }\n | { kind: \"patch\"; documentId: string; ops: PatchOp[]; ifRev?: string };\n\nexport interface TransactionHandle {\n create: (doc: SanityDocumentLike) => TransactionHandle;\n createOrReplace: (doc: SanityDocumentLike) => TransactionHandle;\n createIfNotExists: (doc: SanityDocumentLike) => TransactionHandle;\n delete: (id: string) => TransactionHandle;\n /** Accept either a Patch builder (drained at commit) or an inline doc id + ops. */\n patch: (\n documentIdOrPatch: string | PatchHandle,\n ops?: PatchOp[],\n ifRev?: string,\n ) => TransactionHandle;\n commit: (options?: TxnCommitOptions) => Promise<TxnCommitResult>;\n /** Test-only introspection — not on real Transaction. */\n toJSON: () => TxnMutation[];\n}\n\nexport interface TxnCommitOptions {\n /** If true, returns the resulting documents instead of just ids. */\n returnDocuments?: boolean;\n /** With `returnDocuments`, return only the first updated doc. */\n returnFirst?: boolean;\n}\n\nexport type TxnCommitResult =\n | {\n transactionId: string;\n results: { id: string; operation: \"create\" | \"update\" | \"delete\" }[];\n }\n | SanityDocument\n | SanityDocument[];\n\nexport interface TxnExecutor {\n apply: (mutations: TxnMutation[], options?: TxnCommitOptions) => Promise<TxnCommitResult>;\n}\n\nexport function createTransaction(executor: TxnExecutor): TransactionHandle {\n const mutations: TxnMutation[] = [];\n\n const handle: TransactionHandle = {\n create: (doc) => {\n mutations.push({ kind: \"create\", doc });\n return handle;\n },\n createOrReplace: (doc) => {\n mutations.push({ kind: \"createOrReplace\", doc });\n return handle;\n },\n createIfNotExists: (doc) => {\n mutations.push({ kind: \"createIfNotExists\", doc });\n return handle;\n },\n delete: (id) => {\n mutations.push({ kind: \"delete\", id });\n return handle;\n },\n patch: (documentIdOrPatch, ops, ifRev) => {\n if (typeof documentIdOrPatch === \"string\") {\n mutations.push({\n kind: \"patch\",\n documentId: documentIdOrPatch,\n ops: ops ?? [],\n ...(ifRev !== undefined ? { ifRev } : {}),\n });\n } else {\n const json = documentIdOrPatch.toJSON();\n mutations.push({\n kind: \"patch\",\n documentId: json.id,\n ops: json.ops,\n ...(json.ifRev !== undefined ? { ifRev: json.ifRev } : {}),\n });\n }\n return handle;\n },\n commit: (options) => executor.apply(mutations, options),\n toJSON: () => [...mutations],\n };\n\n return handle;\n}\n","import type { SanityDocument, SanityDocumentLike } from \"@sanity/types\";\nimport { evaluate, parse } from \"groq-js\";\nimport {\n canAccess,\n enforceWriteAccess,\n makeRequestImpl,\n type DocumentValuePermission,\n type TestClientAccessControl,\n} from \"./access-control.ts\";\nimport { applyPatchOps, createPatch, type PatchHandle, type PatchOp } from \"./patch.ts\";\nimport { createTestStore, cryptoRandomKey, type TestStore } from \"./store.ts\";\nimport {\n createTransaction,\n type TransactionHandle,\n type TxnCommitOptions,\n type TxnCommitResult,\n type TxnMutation,\n} from \"./transaction.ts\";\n\n/**\n * Data-plane subset of `@sanity/client`'s surface, with multi-dataset support.\n *\n * One `createTestClient()` call creates a registry of stores keyed by\n * `(projectId, dataset)`. The returned client points at one of them.\n * `withConfig({ projectId, dataset })` returns a new handle into the same\n * registry but pointing at a different store — useful for workflows where\n * definitions live in one dataset and instances live in another.\n *\n * Two separate `createTestClient()` calls have separate registries, so\n * tests stay isolated from each other.\n *\n * Methods not on the real client:\n *\n * - `seed`, `add`, `reset`, `snapshot`, `store` — bulk-load helpers,\n * scoped to the current dataset.\n * - `datasets()` — lists known dataset keys in this registry.\n *\n * Methods deliberately omitted:\n *\n * - Project / agent / asset / live / listen APIs.\n * - Perspective handling.\n */\nexport interface TestClient {\n // --- query ---\n fetch: <T = unknown>(query: string, params?: Record<string, unknown>) => Promise<T>;\n getDocument: <T = SanityDocument>(id: string) => Promise<T | null>;\n getDocuments: <T = SanityDocument>(ids: string[]) => Promise<(T | null)[]>;\n /**\n * Sanity-style HTTP request hook. Present iff the client was\n * created with `accessControl`. The engine's access resolver pokes\n * `/users/me` and the configured ACL path through this — without it\n * the engine throws on any verb that needs an actor (which is the\n * cue for the bench to supply an `access` override).\n *\n * Test cases that want to exercise the engine's token-resolution\n * path should configure `accessControl`; cases that want bench-style\n * always-allow should leave it off and pass `access` through the\n * bench.\n */\n request?: <T>(opts: {\n url?: string;\n uri?: string;\n signal?: AbortSignal;\n tag?: string;\n }) => Promise<T>;\n\n // --- mutate ---\n create: <T extends SanityDocumentLike>(\n doc: T,\n options?: MutationOptions,\n ) => Promise<T & SystemFields>;\n createOrReplace: <T extends SanityDocumentLike>(\n doc: T,\n options?: MutationOptions,\n ) => Promise<T & SystemFields>;\n createIfNotExists: <T extends SanityDocumentLike>(\n doc: T,\n options?: MutationOptions,\n ) => Promise<T & SystemFields>;\n delete: (id: string, options?: MutationOptions) => Promise<{ _id: string } | null>;\n patch: (documentId: string) => PatchHandle;\n transaction: () => TransactionHandle;\n\n // --- config ---\n /** Current `(projectId, dataset)`. */\n config: () => { projectId: string; dataset: string };\n /**\n * Return a new client handle pointing at a different `(projectId, dataset)`\n * within the SAME backing registry. Either field can be omitted to keep\n * the current value. Two clients produced by `withConfig` see each\n * other's writes if they point at the same `(projectId, dataset)`.\n */\n withConfig: (config: Partial<{ projectId: string; dataset: string }>) => TestClient;\n\n // --- test helpers (not on real SanityClient) ---\n /** Replace the current dataset with the given documents. */\n seed: (documents: SanityDocumentLike[]) => void;\n /** Add documents to the current dataset without dropping existing ones. */\n add: (documents: SanityDocumentLike[]) => void;\n /** Drop all documents in the current dataset. */\n reset: () => void;\n /** Read-only snapshot of all documents in the current dataset. */\n snapshot: () => readonly SanityDocument[];\n /** Direct store access for the current dataset. */\n store: () => TestStore;\n /** List `(projectId, dataset)` pairs known in this registry. */\n datasets: () => { projectId: string; dataset: string }[];\n}\n\nexport interface MutationOptions {\n ifRevisionId?: string;\n}\n\nexport interface SystemFields {\n _rev: string;\n _createdAt: string;\n _updatedAt: string;\n}\n\nexport interface CreateTestClientOptions {\n /** Default project id. Defaults to `\"test\"`. */\n projectId?: string;\n /** Default dataset name. Defaults to `\"test\"`. */\n dataset?: string;\n /** Documents to seed into the default `(projectId, dataset)` store. */\n documents?: SanityDocumentLike[];\n /**\n * Optional: seed multiple datasets at once. Keys are `\"projectId:dataset\"`\n * pairs. Useful when workflows span multiple datasets.\n */\n datasets?: Record<string, SanityDocumentLike[]>;\n /**\n * Opt-in access-control simulation. When set, the client exposes\n * `request({ url })` that serves `/users/me` + the configured ACL\n * path, AND every mutation is gated through the supplied `grants`\n * — writes that the grants don't permit throw a `WriteAccessDeniedError`\n * shaped like Sanity's 403. Lets tests demonstrate the full\n * \"engine soft-gate + lake hard-gate\" contract end-to-end without\n * a real HTTP round-trip.\n *\n * When omitted, `request` is undefined and all writes proceed\n * unconditionally — the legacy behavior.\n */\n accessControl?: TestClientAccessControl;\n}\n\nexport type { TestClientAccessControl, DocumentValuePermission } from \"./access-control.ts\";\nexport { WriteAccessDeniedError } from \"./access-control.ts\";\n\nconst DEFAULT_PROJECT_ID = \"test\";\nconst DEFAULT_DATASET = \"test\";\n\ntype Registry = Map<string, TestStore>;\n\nconst registryKey = (projectId: string, dataset: string) => `${projectId}:${dataset}`;\n\nconst parseRegistryKey = (key: string): { projectId: string; dataset: string } => {\n const [projectId, dataset] = key.split(\":\") as [string, string];\n return { projectId, dataset };\n};\n\n/**\n * Create a fresh, isolated test client backed by a private multi-dataset\n * registry. `withConfig` returns a sibling handle into the same registry.\n *\n * ```\n * const client = createTestClient({\n * datasets: {\n * \"test:definitions\": [...],\n * \"test:instances\": [...],\n * },\n * });\n * const defs = client.withConfig({ dataset: \"definitions\" });\n * const inst = client.withConfig({ dataset: \"instances\" });\n * ```\n */\nexport function createTestClient(options: CreateTestClientOptions = {}): TestClient {\n const defaultProjectId = options.projectId ?? DEFAULT_PROJECT_ID;\n const defaultDataset = options.dataset ?? DEFAULT_DATASET;\n\n const registry: Registry = new Map();\n\n const ensureStore = (projectId: string, dataset: string): TestStore => {\n const key = registryKey(projectId, dataset);\n let store = registry.get(key);\n if (store === undefined) {\n store = createTestStore();\n registry.set(key, store);\n }\n return store;\n };\n\n // Seed defaults.\n if (options.documents) {\n ensureStore(defaultProjectId, defaultDataset).seed(options.documents);\n }\n if (options.datasets) {\n for (const [key, docs] of Object.entries(options.datasets)) {\n const { projectId, dataset } = parseRegistryKey(key);\n ensureStore(projectId, dataset).seed(docs);\n }\n }\n\n return makeHandle(registry, defaultProjectId, defaultDataset, options.accessControl);\n}\n\nfunction makeHandle(\n registry: Registry,\n projectId: string,\n dataset: string,\n accessControl: TestClientAccessControl | undefined,\n): TestClient {\n const store = (() => {\n const key = registryKey(projectId, dataset);\n let s = registry.get(key);\n if (s === undefined) {\n s = createTestStore();\n registry.set(key, s);\n }\n return s;\n })();\n\n const promote = (doc: SanityDocumentLike): SanityDocument & SystemFields => {\n const now = new Date().toISOString();\n if (typeof doc._id !== \"string\" || doc._id.length === 0) {\n throw new Error(`document is missing _id`);\n }\n if (typeof doc._type !== \"string\" || doc._type.length === 0) {\n throw new Error(`document ${doc._id} is missing _type`);\n }\n return {\n ...doc,\n _id: doc._id,\n _type: doc._type,\n _rev: cryptoRandomKey(),\n _createdAt: doc._createdAt ?? now,\n _updatedAt: now,\n } as SanityDocument & SystemFields;\n };\n\n const checkRevision = (id: string, ifRevisionId: string | undefined) => {\n if (ifRevisionId === undefined) return;\n const existing = store.get(id);\n if (existing === undefined) {\n throw new Error(`ifRevisionId check failed: document ${id} does not exist`);\n }\n if (existing._rev !== ifRevisionId) {\n throw new Error(\n `ifRevisionId check failed: document ${id} has _rev=${existing._rev}, expected ${ifRevisionId}`,\n );\n }\n };\n\n /**\n * Gate a mutation through the configured `accessControl.grants`. No-op\n * when accessControl is absent (legacy bench behavior). On denial\n * throws `WriteAccessDeniedError` — same failure mode tests get from\n * a real Sanity 403.\n *\n * Pass the doc as-it-would-be at evaluation time: for `create` /\n * `createOrReplace`-as-create / `createIfNotExists`, that's the new\n * doc with its incoming fields; for `update` / `patch` / `delete`,\n * that's the existing doc (the grant filter identifies what the\n * actor can touch).\n */\n const gate = async (\n document: SanityDocumentLike & { _id?: string; _type?: string },\n permission: DocumentValuePermission,\n ): Promise<void> => {\n if (accessControl === undefined) return;\n await enforceWriteAccess({\n document,\n grants: accessControl.grants,\n permission,\n userId: accessControl.currentUser.id,\n });\n };\n\n /**\n * Read-gate predicate. Sanity surfaces invisible docs as \"doesn't\n * exist\" on the wire (404 / null) rather than throwing 403 — so\n * reads filter rather than refuse.\n *\n * - `fetch` filters the dataset before running GROQ.\n * - `getDocument` / `getDocuments` return null for invisible docs.\n * - `snapshot` is a test-helper that bypasses the read gate\n * intentionally (tests need raw inspection).\n *\n * Returns `true` when accessControl is absent (no gating).\n */\n const canRead = async (\n document: SanityDocumentLike & { _id?: string; _type?: string },\n ): Promise<boolean> => {\n if (accessControl === undefined) return true;\n return canAccess({\n document,\n grants: accessControl.grants,\n permission: \"read\",\n userId: accessControl.currentUser.id,\n });\n };\n\n const applyPatch = async (\n documentId: string,\n ops: PatchOp[],\n ifRev: string | undefined,\n ): Promise<SanityDocument> => {\n const existing = store.get(documentId);\n if (existing === undefined) {\n throw new Error(`Cannot patch document ${documentId}: not found`);\n }\n checkRevision(documentId, ifRev);\n // Gate against the existing doc — that's what the grant filter\n // matches: \"what docs can this actor update?\".\n await gate(existing, \"update\");\n const patched = applyPatchOps(existing, ops);\n const now = new Date().toISOString();\n const next: SanityDocument = {\n ...patched,\n _rev: cryptoRandomKey(),\n _updatedAt: now,\n };\n store.put(next);\n return next;\n };\n\n const applyTransaction = async (\n mutations: TxnMutation[],\n txnOptions?: TxnCommitOptions,\n ): Promise<TxnCommitResult> => {\n const snapshot = new Map<string, SanityDocument | undefined>();\n const touchedIds: string[] = [];\n const operations: { id: string; operation: \"create\" | \"update\" | \"delete\" }[] = [];\n\n const remember = (id: string) => {\n if (!snapshot.has(id)) snapshot.set(id, store.get(id));\n };\n\n try {\n for (const mut of mutations) {\n switch (mut.kind) {\n case \"create\": {\n remember(mut.doc._id as string);\n const promoted = promote(mut.doc);\n if (store.get(promoted._id) !== undefined) {\n throw new Error(`create failed: document ${promoted._id} already exists`);\n }\n await gate(promoted, \"create\");\n store.put(promoted);\n touchedIds.push(promoted._id);\n operations.push({ id: promoted._id, operation: \"create\" });\n break;\n }\n case \"createOrReplace\": {\n remember(mut.doc._id as string);\n const promoted = promote(mut.doc);\n const existing = store.get(promoted._id);\n // For an existing doc, \"update\" is the relevant permission;\n // for a net-new one, \"create\".\n await gate(existing ?? promoted, existing !== undefined ? \"update\" : \"create\");\n const wasPresent = existing !== undefined;\n store.put(promoted);\n touchedIds.push(promoted._id);\n operations.push({ id: promoted._id, operation: wasPresent ? \"update\" : \"create\" });\n break;\n }\n case \"createIfNotExists\": {\n remember(mut.doc._id as string);\n if (store.get(mut.doc._id as string) === undefined) {\n const promoted = promote(mut.doc);\n await gate(promoted, \"create\");\n store.put(promoted);\n touchedIds.push(promoted._id);\n operations.push({ id: promoted._id, operation: \"create\" });\n }\n break;\n }\n case \"delete\": {\n remember(mut.id);\n const existing = store.get(mut.id);\n // Sanity's permission model has no separate \"delete\" —\n // delete is gated as an update against the existing doc.\n if (existing !== undefined) await gate(existing, \"update\");\n const removed = store.remove(mut.id);\n if (removed) {\n touchedIds.push(mut.id);\n operations.push({ id: mut.id, operation: \"delete\" });\n }\n break;\n }\n case \"patch\": {\n remember(mut.documentId);\n // applyPatch already gates internally.\n await applyPatch(mut.documentId, mut.ops, mut.ifRev);\n touchedIds.push(mut.documentId);\n operations.push({ id: mut.documentId, operation: \"update\" });\n break;\n }\n }\n }\n } catch (error) {\n for (const [id, doc] of snapshot) {\n if (doc === undefined) {\n store.remove(id);\n } else {\n store.put(doc);\n }\n }\n throw error;\n }\n\n if (txnOptions?.returnDocuments) {\n const docs = touchedIds\n .map((id) => store.get(id))\n .filter((d): d is SanityDocument => d !== undefined);\n return txnOptions.returnFirst ? (docs[0] as SanityDocument) : docs;\n }\n\n return {\n transactionId: cryptoRandomKey(),\n results: operations,\n };\n };\n\n /**\n * Filter the store's docs to only those `canRead` returns true\n * for. When accessControl is unset, returns the full set (no\n * filtering cost). Used by `fetch` so GROQ runs against the\n * actor-visible subset, matching Sanity's per-token query behavior.\n */\n const visibleDocs = async (): Promise<SanityDocument[]> => {\n const all = store.all();\n if (accessControl === undefined) return all;\n const out: SanityDocument[] = [];\n for (const doc of all) {\n if (await canRead(doc)) out.push(doc);\n }\n return out;\n };\n\n const client: TestClient = {\n fetch: async (query, params) => {\n const tree = parse(query, params ? { params } : undefined);\n const value = await evaluate(tree, {\n dataset: await visibleDocs(),\n ...(params !== undefined ? { params } : {}),\n });\n return (await value.get()) as never;\n },\n\n getDocument: async (id) => {\n const doc = store.get(id);\n if (doc === undefined) return null;\n if (!(await canRead(doc))) return null;\n return doc as never;\n },\n\n getDocuments: async (ids) => {\n const result: (SanityDocument | null)[] = [];\n for (const id of ids) {\n const doc = store.get(id);\n if (doc === undefined) result.push(null);\n else if (await canRead(doc)) result.push(doc);\n else result.push(null);\n }\n return result as never;\n },\n\n create: async (doc, opts) => {\n checkRevision(doc._id as string, opts?.ifRevisionId);\n if (store.get(doc._id as string) !== undefined) {\n throw new Error(`create failed: document ${doc._id} already exists`);\n }\n const promoted = promote(doc);\n await gate(promoted, \"create\");\n store.put(promoted);\n return promoted as typeof doc & SystemFields;\n },\n\n createOrReplace: async (doc, opts) => {\n checkRevision(doc._id as string, opts?.ifRevisionId);\n const promoted = promote(doc);\n const existing = store.get(doc._id as string);\n await gate(existing ?? promoted, existing !== undefined ? \"update\" : \"create\");\n store.put(promoted);\n return promoted as typeof doc & SystemFields;\n },\n\n createIfNotExists: async (doc, opts) => {\n checkRevision(doc._id as string, opts?.ifRevisionId);\n const existing = store.get(doc._id as string);\n if (existing !== undefined) return existing as typeof doc & SystemFields;\n const promoted = promote(doc);\n await gate(promoted, \"create\");\n store.put(promoted);\n return promoted as typeof doc & SystemFields;\n },\n\n delete: async (id, opts) => {\n checkRevision(id, opts?.ifRevisionId);\n const existing = store.get(id);\n if (existing !== undefined) await gate(existing, \"update\");\n const removed = store.remove(id);\n return removed ? { _id: id } : null;\n },\n\n patch: (documentId) => createPatch(documentId, { apply: applyPatch }),\n\n transaction: () => createTransaction({ apply: applyTransaction }),\n\n ...(accessControl !== undefined\n ? { request: makeRequestImpl(accessControl, projectId, dataset) }\n : {}),\n\n config: () => ({ projectId, dataset }),\n\n withConfig: (config) =>\n makeHandle(registry, config.projectId ?? projectId, config.dataset ?? dataset, accessControl),\n\n seed: (documents) => store.seed(documents),\n add: (documents) => store.add(documents),\n reset: () => store.reset(),\n snapshot: () => store.snapshot(),\n store: () => store,\n datasets: () => [...registry.keys()].map(parseRegistryKey),\n };\n\n return client;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/access-control.ts","../src/store.ts","../src/patch.ts","../src/actions.ts","../src/perspective.ts","../src/transaction.ts","../src/client.ts"],"sourcesContent":["/**\n * Optional access-control simulation for the in-memory test client.\n *\n * When configured, the TestClient does two things a real `@sanity/client`\n * does over the wire but the bare in-memory client otherwise skips:\n *\n * 1. **Exposes `client.request({ url })`** that serves:\n * - `GET /users/me` → the configured `CurrentUser`\n * - `GET <aclPath>` → the configured `Grant[]`\n * This is what the engine reads from to resolve `WorkflowAccess`\n * when no override is passed.\n *\n * 2. **Gates writes through the configured grants.** Every mutation\n * (`create`, `createOrReplace`, `createIfNotExists`, `delete`,\n * `patch`, and per-op inside `transaction`) checks whether the\n * configured grants allow the relevant `DocumentValuePermission`\n * on the target document. On denial the client throws a\n * `WriteAccessDeniedError` shaped like Sanity's 403 — the same\n * failure mode a production write would hit, surfaced in tests.\n *\n * When `accessControl` is omitted, the client behaves as before:\n * `request` is undefined, writes are unconditional, and the engine\n * falls through to whatever override the caller (typically the\n * bench's `ALL_ACCESS`) provides.\n *\n * The grant evaluation runs the GROQ filter on a singleton dataset\n * containing the doc being checked — same shape as the engine's\n * `permissions.matchesFilter`, lifted from canvas.\n */\n\nimport type { SanityDocumentLike } from \"@sanity/types\";\nimport { evaluate, parse } from \"groq-js\";\n\nexport type DocumentValuePermission = \"create\" | \"read\" | \"update\";\n\nexport interface CurrentUser {\n id: string;\n name?: string;\n email?: string;\n /** @deprecated singular `role` on legacy responses */\n role?: string;\n roles?: { name: string; title?: string; description?: string }[];\n}\n\nexport interface Grant {\n filter: string;\n permissions: DocumentValuePermission[];\n}\n\nexport interface TestClientAccessControl {\n /** What `client.request({ url: \"/users/me\" })` returns. */\n currentUser: CurrentUser;\n /** What `client.request({ url: aclPath })` returns. */\n grants: Grant[];\n /**\n * URL path the engine's access resolver hits to fetch grants.\n * Defaults to `/projects/<projectId>/datasets/<dataset>/acl`.\n */\n aclPath?: string;\n}\n\n/**\n * Sanity-style 403 raised when a configured-grants check rejects a\n * write. The thrown object carries the doc id, the attempted\n * permission, and the count of matching grants (for parity with the\n * engine's `permission-denied` disabled-reason).\n */\nexport class WriteAccessDeniedError extends Error {\n readonly documentId: string;\n readonly permission: DocumentValuePermission;\n readonly matchingGrants: number;\n\n constructor(args: {\n documentId: string;\n permission: DocumentValuePermission;\n matchingGrants: number;\n }) {\n super(\n `Sanity (test): permission denied — actor lacks \"${args.permission}\" on document \"${args.documentId}\" ` +\n `(${args.matchingGrants} grant${args.matchingGrants === 1 ? \"\" : \"s\"} matched the document but ` +\n `none included \"${args.permission}\").`,\n );\n this.name = \"WriteAccessDeniedError\";\n this.documentId = args.documentId;\n this.permission = args.permission;\n this.matchingGrants = args.matchingGrants;\n }\n}\n\n/**\n * Predicate variant of `evaluateWriteAccess` — same logic but only\n * returns the boolean. Used by read-gating paths\n * (`fetch`/`getDocument`/`getDocuments`) where the documented Sanity\n * behavior is \"invisible doc → 404\", not \"throw 403\". So callers\n * can filter the dataset before GROQ runs and return `null` from\n * single-doc reads, instead of leaking a denial through the read\n * surface.\n */\nexport async function canAccess(args: {\n document: SanityDocumentLike & { _id?: string; _type?: string };\n grants: Grant[];\n permission: DocumentValuePermission;\n userId: string;\n}): Promise<boolean> {\n const result = await evaluateWriteAccess(args);\n return result.allowed;\n}\n\n// Parsed-filter cache. The GROQ AST is cheap to reuse; only the\n// per-document evaluation needs to re-run.\nconst parsedFilters = new Map<string, ReturnType<typeof parse>>();\n\nasync function matchesFilter(args: {\n document: SanityDocumentLike & { _id?: string; _type?: string };\n filter: string;\n userId: string;\n}): Promise<boolean> {\n const { document, filter, userId } = args;\n if (!parsedFilters.has(filter)) {\n parsedFilters.set(filter, parse(`*[${filter}]`));\n }\n const ast = parsedFilters.get(filter)!;\n const result = await evaluate(ast, {\n dataset: [document as Record<string, unknown>],\n identity: userId,\n });\n const data = await result.get();\n return Array.isArray(data) && data.length === 1;\n}\n\n/**\n * Resolve whether `grants` allow `permission` on `document` for the\n * given user. Same logic the engine uses in `permissions.grantsPermissionOn`\n * (most-permissive wins; multiple grants compose).\n *\n * Returns `{ allowed: boolean, matchingGrants: number }` so callers\n * can build the structured error without re-counting.\n */\nexport async function evaluateWriteAccess(args: {\n document: SanityDocumentLike & { _id?: string; _type?: string };\n grants: Grant[];\n permission: DocumentValuePermission;\n userId: string;\n}): Promise<{ allowed: boolean; matchingGrants: number }> {\n const { document, grants, permission, userId } = args;\n let matching = 0;\n for (const grant of grants) {\n if (\n await matchesFilter({\n document,\n filter: grant.filter,\n userId,\n })\n ) {\n matching++;\n if (grant.permissions.includes(permission)) {\n return { allowed: true, matchingGrants: matching };\n }\n }\n }\n return { allowed: false, matchingGrants: matching };\n}\n\n/**\n * Throw a `WriteAccessDeniedError` if `grants` don't permit the\n * supplied `permission` on `document`. No-op when allowed. Use this\n * from every mutation entrypoint that wants to simulate Sanity's\n * write boundary.\n */\nexport async function enforceWriteAccess(args: {\n document: SanityDocumentLike & { _id?: string; _type?: string };\n grants: Grant[];\n permission: DocumentValuePermission;\n userId: string;\n}): Promise<void> {\n const { document, grants, permission, userId } = args;\n const { allowed, matchingGrants } = await evaluateWriteAccess({\n document,\n grants,\n permission,\n userId,\n });\n if (allowed) return;\n if (typeof document._id !== \"string\") {\n throw new WriteAccessDeniedError({\n documentId: \"(unknown)\",\n permission,\n matchingGrants,\n });\n }\n throw new WriteAccessDeniedError({\n documentId: document._id,\n permission,\n matchingGrants,\n });\n}\n\n/**\n * Default ACL path the engine pulls grants from. Mirrors Sanity's\n * project/dataset ACL endpoint. Callers can override via\n * `accessControl.aclPath`.\n */\nexport function defaultAclPath(projectId: string, dataset: string): string {\n return `/projects/${projectId}/datasets/${dataset}/acl`;\n}\n\n/**\n * Internal helper: turn the `accessControl` config into the bound\n * `request` function the client exposes, plus a helper to gate writes.\n * Returns `undefined` when no config — leaves `request` off so the\n * engine's \"no token available\" path triggers (which is the cue for\n * the bench to supply an `access` override).\n */\nexport function makeRequestImpl(\n accessControl: TestClientAccessControl,\n projectId: string,\n dataset: string,\n): <T>(opts: { url?: string; uri?: string; signal?: AbortSignal; tag?: string }) => Promise<T> {\n const aclPath = accessControl.aclPath ?? defaultAclPath(projectId, dataset);\n return async <T>(opts: {\n url?: string;\n uri?: string;\n signal?: AbortSignal;\n tag?: string;\n }): Promise<T> => {\n // `@sanity/client` distinguishes `uri` (project-scoped, e.g.\n // `/users/me`) from `url` (raw). Studio uses `uri` for global\n // endpoints. Treat both identically for the mock.\n const path = opts.uri ?? opts.url;\n if (path === \"/users/me\") {\n return accessControl.currentUser as unknown as T;\n }\n if (path === aclPath) {\n return accessControl.grants as unknown as T;\n }\n throw new Error(\n `TestClient.request: no handler for path \"${path}\". The TestClient only serves ` +\n `\"/users/me\" and \"${aclPath}\". Configure additional endpoints via createTestClient({ accessControl }).`,\n );\n };\n}\n","import type { SanityDocument, SanityDocumentLike } from \"@sanity/types\";\n\n/**\n * In-memory document store backing a TestClient. Closure-scoped — created\n * fresh per test, no global state.\n *\n * The shape mirrors what `groq-js` evaluates against: an array of\n * documents. Internally we keep a Map by `_id` for O(1) lookup, and\n * project to an array on each query.\n */\nexport interface TestStore {\n /** Replace the entire dataset with the given documents. Existing docs are dropped. */\n seed: (documents: SanityDocumentLike[]) => void;\n /** Add documents without dropping existing ones. Conflicts (same _id) overwrite. */\n add: (documents: SanityDocumentLike[]) => void;\n /** Remove all documents. */\n reset: () => void;\n /** All documents as an array, in insertion order. */\n all: () => SanityDocument[];\n /** Get one document by _id, or undefined. */\n get: (id: string) => SanityDocument | undefined;\n /** Insert a fully-formed document by _id. Replaces if it already exists. */\n put: (doc: SanityDocument) => void;\n /** Remove a document by _id. Returns the removed doc or undefined. */\n remove: (id: string) => SanityDocument | undefined;\n /** Snapshot of all docs, useful for assertions. */\n snapshot: () => readonly SanityDocument[];\n}\n\n/**\n * Create a fresh, isolated store. The returned object closes over a private\n * Map; two stores created from two calls do not see each other.\n */\nexport function createTestStore(initial?: SanityDocumentLike[]): TestStore {\n const docs = new Map<string, SanityDocument>();\n\n const promote = (doc: SanityDocumentLike): SanityDocument => {\n if (typeof doc._id !== \"string\" || doc._id.length === 0) {\n throw new Error(`document is missing _id: ${JSON.stringify(doc)}`);\n }\n if (typeof doc._type !== \"string\" || doc._type.length === 0) {\n throw new Error(`document ${doc._id} is missing _type`);\n }\n const now = new Date().toISOString();\n return {\n ...doc,\n _id: doc._id,\n _type: doc._type,\n _rev: doc._rev ?? cryptoRandomKey(),\n _createdAt: doc._createdAt ?? now,\n _updatedAt: doc._updatedAt ?? now,\n } as SanityDocument;\n };\n\n const seed = (documents: SanityDocumentLike[]) => {\n docs.clear();\n for (const d of documents) {\n const promoted = promote(d);\n docs.set(promoted._id, promoted);\n }\n };\n\n if (initial) seed(initial);\n\n return {\n seed,\n add: (documents) => {\n for (const d of documents) {\n const promoted = promote(d);\n docs.set(promoted._id, promoted);\n }\n },\n reset: () => docs.clear(),\n all: () => [...docs.values()],\n get: (id) => docs.get(id),\n put: (doc) => {\n docs.set(doc._id, doc);\n },\n remove: (id) => {\n const doc = docs.get(id);\n if (doc) docs.delete(id);\n return doc;\n },\n snapshot: () => [...docs.values()],\n };\n}\n\n/**\n * Generate a stable-ish random key without pulling in a dependency.\n * Use cases: synthesising `_rev` for newly-stored documents.\n */\nexport function cryptoRandomKey(length = 12): string {\n const bytes = new Uint8Array(length);\n // globalThis.crypto is available in Node 22+ and all modern browsers.\n globalThis.crypto.getRandomValues(bytes);\n return [...bytes]\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\")\n .slice(0, length);\n}\n","import type { SanityDocument } from \"@sanity/types\";\n\n/**\n * Patch operations the test client supports.\n *\n * Subset of `@sanity/client`'s PatchOperations. Each entry is one\n * mutation step applied in order at commit time.\n */\nexport type PatchOp =\n | { kind: \"set\"; props: Record<string, unknown> }\n | { kind: \"setIfMissing\"; props: Record<string, unknown> }\n | { kind: \"unset\"; paths: string[] }\n | { kind: \"inc\"; props: Record<string, number> }\n | { kind: \"dec\"; props: Record<string, number> }\n | {\n kind: \"insert\";\n position: \"before\" | \"after\" | \"replace\";\n anchor: string;\n items: unknown[];\n };\n\nexport interface PatchHandle {\n set: (props: Record<string, unknown>) => PatchHandle;\n setIfMissing: (props: Record<string, unknown>) => PatchHandle;\n unset: (paths: string[]) => PatchHandle;\n inc: (props: Record<string, number>) => PatchHandle;\n dec: (props: Record<string, number>) => PatchHandle;\n insert: (\n position: \"before\" | \"after\" | \"replace\",\n anchor: string,\n items: unknown[],\n ) => PatchHandle;\n ifRevisionId: (rev: string) => PatchHandle;\n commit: () => Promise<SanityDocument>;\n /** Test-only introspection — not on real Patch */\n toJSON: () => { id: string; ops: PatchOp[]; ifRev?: string };\n}\n\nexport interface PatchExecutor {\n apply: (documentId: string, ops: PatchOp[], ifRev: string | undefined) => Promise<SanityDocument>;\n}\n\nexport function createPatch(documentId: string, executor: PatchExecutor): PatchHandle {\n const ops: PatchOp[] = [];\n let ifRev: string | undefined;\n\n const handle: PatchHandle = {\n set: (props) => {\n ops.push({ kind: \"set\", props });\n return handle;\n },\n setIfMissing: (props) => {\n ops.push({ kind: \"setIfMissing\", props });\n return handle;\n },\n unset: (paths) => {\n ops.push({ kind: \"unset\", paths });\n return handle;\n },\n inc: (props) => {\n ops.push({ kind: \"inc\", props });\n return handle;\n },\n dec: (props) => {\n ops.push({ kind: \"dec\", props });\n return handle;\n },\n insert: (position, anchor, items) => {\n ops.push({ kind: \"insert\", position, anchor, items });\n return handle;\n },\n ifRevisionId: (rev) => {\n ifRev = rev;\n return handle;\n },\n commit: () => executor.apply(documentId, ops, ifRev),\n toJSON: () => ({\n id: documentId,\n ops: [...ops],\n ...(ifRev !== undefined ? { ifRev } : {}),\n }),\n };\n\n return handle;\n}\n\n/**\n * Apply an ordered list of patch ops to a document, returning the new doc.\n *\n * The path semantics are deliberately a small subset:\n *\n * - **Dotted paths** (`a.b.c`) for nested object fields.\n * - **Array index** (`a.b[0]`) for positional access into arrays.\n * - **Keyed lookup** (`a.b[_key==\"x\"]`) for finding an array entry by `_key`.\n *\n * Anything more exotic (recursive `..`, GROQ filter expressions, multi-match)\n * is intentionally out of scope and will throw — the workflow engine only\n * needs the three forms above.\n */\nexport function applyPatchOps(doc: SanityDocument, ops: PatchOp[]): SanityDocument {\n let next: SanityDocument = structuredClone(doc);\n for (const op of ops) {\n next = applyOne(next, op);\n }\n return next;\n}\n\nfunction applyOne(doc: SanityDocument, op: PatchOp): SanityDocument {\n switch (op.kind) {\n case \"set\":\n for (const [path, value] of Object.entries(op.props)) {\n setAtPath(doc, path, value);\n }\n return doc;\n case \"setIfMissing\":\n for (const [path, value] of Object.entries(op.props)) {\n if (getAtPath(doc, path) === undefined) {\n setAtPath(doc, path, value);\n }\n }\n return doc;\n case \"unset\":\n for (const path of op.paths) {\n unsetAtPath(doc, path);\n }\n return doc;\n case \"inc\":\n for (const [path, delta] of Object.entries(op.props)) {\n const current = getAtPath(doc, path);\n const base = typeof current === \"number\" ? current : 0;\n setAtPath(doc, path, base + delta);\n }\n return doc;\n case \"dec\":\n for (const [path, delta] of Object.entries(op.props)) {\n const current = getAtPath(doc, path);\n const base = typeof current === \"number\" ? current : 0;\n setAtPath(doc, path, base - delta);\n }\n return doc;\n case \"insert\": {\n insertIntoArray(doc, op.position, op.anchor, op.items);\n return doc;\n }\n }\n}\n\ninterface PathSegment {\n key: string;\n // Optional array selector. Either a numeric index or a key=value match.\n selector?: { kind: \"index\"; index: number } | { kind: \"keyed\"; field: string; value: string };\n}\n\nfunction parsePath(path: string): PathSegment[] {\n // Examples:\n // \"currentStageId\"\n // \"taskStatus[0].status\"\n // \"taskStatus[_key==\\\"approve\\\"].status\"\n // \"context[key==\\\"releaseId\\\"].value\"\n const segments: PathSegment[] = [];\n for (const raw of path.split(\".\")) {\n const match = /^([A-Za-z_$][\\w$]*)(?:\\[(.+)\\])?$/.exec(raw);\n if (!match) throw new Error(`Unsupported patch path segment: ${raw}`);\n const key = match[1] as string;\n const selectorRaw = match[2];\n if (selectorRaw === undefined) {\n segments.push({ key });\n continue;\n }\n const indexMatch = /^(\\d+)$/.exec(selectorRaw);\n if (indexMatch) {\n segments.push({ key, selector: { kind: \"index\", index: Number(indexMatch[1]) } });\n continue;\n }\n const keyedMatch = /^([A-Za-z_$][\\w$]*)\\s*==\\s*\"([^\"]*)\"$/.exec(selectorRaw);\n if (keyedMatch) {\n segments.push({\n key,\n selector: { kind: \"keyed\", field: keyedMatch[1] as string, value: keyedMatch[2] as string },\n });\n continue;\n }\n throw new Error(`Unsupported array selector in patch path: ${selectorRaw}`);\n }\n return segments;\n}\n\nfunction resolveSelector(array: unknown[], selector: NonNullable<PathSegment[\"selector\"]>): number {\n if (selector.kind === \"index\") return selector.index;\n const idx = array.findIndex(\n (entry) =>\n typeof entry === \"object\" &&\n entry !== null &&\n (entry as Record<string, unknown>)[selector.field] === selector.value,\n );\n if (idx === -1) {\n throw new Error(`No array entry matched ${selector.field}==\"${selector.value}\" in patch path`);\n }\n return idx;\n}\n\nfunction getAtPath(doc: SanityDocument, path: string): unknown {\n const segments = parsePath(path);\n let cursor: unknown = doc;\n for (const seg of segments) {\n if (cursor === undefined || cursor === null) return undefined;\n cursor = (cursor as Record<string, unknown>)[seg.key];\n if (seg.selector !== undefined) {\n if (!Array.isArray(cursor)) return undefined;\n const idx = resolveSelector(cursor, seg.selector);\n cursor = cursor[idx];\n }\n }\n return cursor;\n}\n\nfunction setAtPath(doc: SanityDocument, path: string, value: unknown): void {\n const segments = parsePath(path);\n if (segments.length === 0) throw new Error(`Empty patch path`);\n let cursor: Record<string, unknown> = doc as unknown as Record<string, unknown>;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i] as PathSegment;\n let next = cursor[seg.key];\n if (next === undefined) {\n next = seg.selector?.kind === \"index\" || seg.selector?.kind === \"keyed\" ? [] : {};\n cursor[seg.key] = next;\n }\n if (seg.selector !== undefined) {\n if (!Array.isArray(next)) {\n throw new Error(`Patch path crosses non-array at segment ${seg.key}`);\n }\n const idx = resolveSelector(next, seg.selector);\n cursor = next[idx] as Record<string, unknown>;\n } else {\n cursor = next as Record<string, unknown>;\n }\n }\n const last = segments[segments.length - 1] as PathSegment;\n if (last.selector !== undefined) {\n const arr = cursor[last.key];\n if (!Array.isArray(arr)) {\n throw new Error(`Patch path crosses non-array at segment ${last.key}`);\n }\n const idx = resolveSelector(arr, last.selector);\n arr[idx] = value;\n } else {\n cursor[last.key] = value;\n }\n}\n\nfunction unsetAtPath(doc: SanityDocument, path: string): void {\n const segments = parsePath(path);\n if (segments.length === 0) return;\n let cursor: Record<string, unknown> = doc as unknown as Record<string, unknown>;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i] as PathSegment;\n let next = cursor[seg.key];\n if (next === undefined || next === null) return;\n if (seg.selector !== undefined) {\n if (!Array.isArray(next)) return;\n const idx = resolveSelector(next, seg.selector);\n next = next[idx];\n }\n cursor = next as Record<string, unknown>;\n }\n const last = segments[segments.length - 1] as PathSegment;\n if (last.selector !== undefined) {\n const arr = cursor[last.key];\n if (!Array.isArray(arr)) return;\n const idx = resolveSelector(arr, last.selector);\n arr.splice(idx, 1);\n } else {\n delete cursor[last.key];\n }\n}\n\nfunction insertIntoArray(\n doc: SanityDocument,\n position: \"before\" | \"after\" | \"replace\",\n anchor: string,\n items: unknown[],\n): void {\n // anchor is a path that must terminate at an array entry (selector required).\n // For \"replace\", we replace the entry; for \"before\"/\"after\", we insert\n // adjacent to it.\n const segments = parsePath(anchor);\n if (segments.length === 0) throw new Error(`Empty insert anchor`);\n const last = segments[segments.length - 1] as PathSegment;\n if (last.selector === undefined) {\n throw new Error(`Insert anchor must end with an array selector: ${anchor}`);\n }\n\n let cursor: Record<string, unknown> = doc as unknown as Record<string, unknown>;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i] as PathSegment;\n const next = cursor[seg.key];\n if (next === undefined || next === null) {\n throw new Error(`Insert anchor traverses missing field: ${seg.key}`);\n }\n if (seg.selector !== undefined) {\n if (!Array.isArray(next)) {\n throw new Error(`Insert anchor traverses non-array at: ${seg.key}`);\n }\n cursor = next[resolveSelector(next, seg.selector)] as Record<string, unknown>;\n } else {\n cursor = next as Record<string, unknown>;\n }\n }\n const arr = cursor[last.key];\n if (!Array.isArray(arr)) {\n throw new Error(`Insert anchor target is not an array: ${last.key}`);\n }\n const idx = resolveSelector(arr, last.selector);\n if (position === \"replace\") {\n arr.splice(idx, 1, ...items);\n } else if (position === \"before\") {\n arr.splice(idx, 0, ...items);\n } else {\n arr.splice(idx + 1, 0, ...items);\n }\n}\n","/**\n * Content Releases action handlers — `client.action()` plus the\n * `client.releases.*` and `client.createVersion` / `client.unpublishVersion`\n * helpers.\n *\n * Mirrors enough of the real `@sanity/client` action surface for tests\n * to exercise release-aware workflows. Transient states\n * (`scheduling`/`publishing`/`unscheduling`) are skipped — releases\n * transition synchronously between settled states.\n */\n\nimport type { SanityDocument, SanityDocumentLike } from \"@sanity/types\";\nimport type { TestStore } from \"./store.ts\";\nimport { cryptoRandomKey } from \"./store.ts\";\nimport { applyPatchOps, type PatchOp } from \"./patch.ts\";\n\n/**\n * Document body accepted by `version.create` / `createVersion`. The id\n * is derived from `releaseId + publishedId` so callers may omit `_id`\n * here. `version.replace` requires `_id` since it identifies the doc\n * to replace.\n */\nexport interface DocumentBody {\n _id?: string;\n _type: string;\n [field: string]: unknown;\n}\n\nexport type ReleaseState = \"active\" | \"scheduled\" | \"published\" | \"archived\";\n\nexport type ReleaseType = \"asap\" | \"scheduled\" | \"undecided\";\n\nexport interface ReleaseMetadata {\n title?: string;\n description?: string;\n releaseType?: ReleaseType;\n intendedPublishAt?: string;\n}\n\nexport interface ReleaseDocument extends SanityDocument {\n _type: \"system.release\";\n name: string;\n state: ReleaseState;\n metadata: ReleaseMetadata;\n publishAt?: string;\n}\n\nexport type ReleaseAction =\n | {\n actionType: \"sanity.action.release.create\";\n releaseId: string;\n metadata?: Partial<ReleaseMetadata>;\n }\n | {\n actionType: \"sanity.action.release.edit\";\n releaseId: string;\n patch: { set?: Record<string, unknown>; unset?: string[] };\n }\n | { actionType: \"sanity.action.release.schedule\"; releaseId: string; publishAt: string }\n | { actionType: \"sanity.action.release.unschedule\"; releaseId: string }\n | { actionType: \"sanity.action.release.publish\"; releaseId: string }\n | { actionType: \"sanity.action.release.archive\"; releaseId: string }\n | { actionType: \"sanity.action.release.unarchive\"; releaseId: string }\n | { actionType: \"sanity.action.release.delete\"; releaseId: string };\n\nexport type VersionAction =\n | {\n actionType: \"sanity.action.document.version.create\";\n publishedId: string;\n document: DocumentBody;\n releaseId?: string; // inferred from document._id when omitted\n }\n | {\n actionType: \"sanity.action.document.version.replace\";\n document: SanityDocumentLike;\n }\n | {\n actionType: \"sanity.action.document.version.discard\";\n versionId: string;\n }\n | {\n actionType: \"sanity.action.document.version.unpublish\";\n versionId: string;\n publishedId: string;\n };\n\nexport type Action = ReleaseAction | VersionAction;\n\nexport interface ActionResult {\n transactionId: string;\n}\n\nconst RELEASE_DOC_PREFIX = \"_.releases.\";\nconst VERSIONS_PREFIX = \"versions.\";\n\nconst releaseDocId = (releaseId: string): string => `${RELEASE_DOC_PREFIX}${releaseId}`;\nconst versionDocId = (releaseId: string, publishedId: string): string =>\n `${VERSIONS_PREFIX}${releaseId}.${publishedId}`;\n\n/** Parse `versions.<release>.<id>` → `{ releaseId, publishedId }`. Throws otherwise. */\nfunction parseVersionId(versionId: string): { releaseId: string; publishedId: string } {\n if (!versionId.startsWith(VERSIONS_PREFIX)) {\n throw new Error(`Not a version id: ${versionId}`);\n }\n const rest = versionId.slice(VERSIONS_PREFIX.length);\n const dot = rest.indexOf(\".\");\n if (dot === -1) {\n throw new Error(`Not a version id: ${versionId}`);\n }\n return { releaseId: rest.slice(0, dot), publishedId: rest.slice(dot + 1) };\n}\n\nfunction getRelease(store: TestStore, releaseId: string): ReleaseDocument {\n const doc = store.get(releaseDocId(releaseId)) as ReleaseDocument | undefined;\n if (doc === undefined) {\n throw new Error(`Release not found: ${releaseId}`);\n }\n return doc;\n}\n\nfunction maybeRelease(store: TestStore, releaseId: string): ReleaseDocument | undefined {\n return store.get(releaseDocId(releaseId)) as ReleaseDocument | undefined;\n}\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction promote(doc: SanityDocumentLike): SanityDocument {\n const now = nowIso();\n return {\n ...doc,\n _id: doc._id as string,\n _type: doc._type as string,\n _rev: cryptoRandomKey(),\n _createdAt: doc._createdAt ?? now,\n _updatedAt: now,\n } as SanityDocument;\n}\n\nfunction bumpRev(doc: SanityDocument, patch: Partial<SanityDocument>): SanityDocument {\n return {\n ...doc,\n ...patch,\n _rev: cryptoRandomKey(),\n _updatedAt: nowIso(),\n } as SanityDocument;\n}\n\nfunction requireActive(release: ReleaseDocument): void {\n if (release.state !== \"active\") {\n throw new Error(\n `Release ${release.name} is ${release.state}; only active releases accept version writes`,\n );\n }\n}\n\n/**\n * Apply a single action to the store. Returns a synthetic transactionId.\n * Throws on any invariant violation — callers wrap multi-action arrays\n * with a snapshot/rollback to keep atomicity.\n */\nexport function applyAction(store: TestStore, action: Action): ActionResult {\n switch (action.actionType) {\n case \"sanity.action.release.create\": {\n if (maybeRelease(store, action.releaseId) !== undefined) {\n throw new Error(`Release ${action.releaseId} already exists`);\n }\n const now = nowIso();\n const metadata: ReleaseMetadata = {\n releaseType: \"undecided\",\n ...action.metadata,\n };\n const doc: ReleaseDocument = {\n _id: releaseDocId(action.releaseId),\n _type: \"system.release\",\n _rev: cryptoRandomKey(),\n _createdAt: now,\n _updatedAt: now,\n name: action.releaseId,\n state: \"active\",\n metadata,\n };\n store.put(doc);\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.release.edit\": {\n const release = getRelease(store, action.releaseId);\n const patchOps: PatchOp[] = [];\n if (action.patch.set !== undefined) {\n patchOps.push({ kind: \"set\", props: action.patch.set });\n }\n if (action.patch.unset !== undefined) {\n patchOps.push({ kind: \"unset\", paths: action.patch.unset });\n }\n const patched = applyPatchOps(release, patchOps);\n store.put(bumpRev(patched, {}));\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.release.schedule\": {\n const release = getRelease(store, action.releaseId);\n if (release.state !== \"active\") {\n throw new Error(`Cannot schedule release in state ${release.state}`);\n }\n store.put(\n bumpRev(release, {\n state: \"scheduled\",\n publishAt: action.publishAt,\n } as Partial<SanityDocument>),\n );\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.release.unschedule\": {\n const release = getRelease(store, action.releaseId);\n if (release.state !== \"scheduled\") {\n throw new Error(`Cannot unschedule release in state ${release.state}`);\n }\n const next: ReleaseDocument = { ...release, state: \"active\" };\n delete (next as { publishAt?: string }).publishAt;\n store.put(bumpRev(next, {}));\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.release.publish\": {\n const release = getRelease(store, action.releaseId);\n if (release.state === \"published\") {\n // Idempotent.\n return { transactionId: cryptoRandomKey() };\n }\n if (release.state !== \"active\" && release.state !== \"scheduled\") {\n throw new Error(`Cannot publish release in state ${release.state}`);\n }\n // Materialise versions onto published ids, then drop the versions.\n const prefix = `${VERSIONS_PREFIX}${action.releaseId}.`;\n const sources = store.all().filter((d) => d._id.startsWith(prefix));\n for (const v of sources) {\n const pubId = v._id.slice(prefix.length);\n const isUnpublishStub = (v as { _system?: { delete?: boolean } })._system?.delete === true;\n if (isUnpublishStub) {\n store.remove(pubId);\n } else {\n const body: SanityDocumentLike = {\n ...(v as SanityDocumentLike),\n _id: pubId,\n };\n // Strip transient fields we projected through the version.\n delete (body as { _originalId?: string })._originalId;\n delete (body as { _system?: unknown })._system;\n const promoted = promote(body);\n // Preserve _createdAt of the existing published doc if present.\n const existing = store.get(pubId);\n if (existing !== undefined) {\n promoted._createdAt = existing._createdAt;\n }\n store.put(promoted);\n }\n store.remove(v._id);\n }\n store.put(bumpRev(release, { state: \"published\" } as Partial<SanityDocument>));\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.release.archive\": {\n const release = getRelease(store, action.releaseId);\n if (release.state !== \"active\" && release.state !== \"scheduled\") {\n throw new Error(`Cannot archive release in state ${release.state}`);\n }\n store.put(bumpRev(release, { state: \"archived\" } as Partial<SanityDocument>));\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.release.unarchive\": {\n const release = getRelease(store, action.releaseId);\n if (release.state !== \"archived\") {\n throw new Error(`Cannot unarchive release in state ${release.state}`);\n }\n store.put(bumpRev(release, { state: \"active\" } as Partial<SanityDocument>));\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.release.delete\": {\n const release = getRelease(store, action.releaseId);\n if (release.state !== \"archived\" && release.state !== \"published\") {\n throw new Error(\n `Cannot delete release in state ${release.state}; archive or publish it first`,\n );\n }\n // Clean up any leftover version docs (archived releases keep them around).\n const prefix = `${VERSIONS_PREFIX}${action.releaseId}.`;\n for (const v of store.all()) {\n if (v._id.startsWith(prefix)) store.remove(v._id);\n }\n store.remove(release._id);\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.document.version.create\": {\n const releaseId =\n action.releaseId ??\n (action.document._id !== undefined\n ? parseVersionId(action.document._id as string).releaseId\n : undefined);\n if (releaseId === undefined) {\n throw new Error(\"version.create needs releaseId or a document._id that encodes it\");\n }\n const release = getRelease(store, releaseId);\n requireActive(release);\n const vid = versionDocId(releaseId, action.publishedId);\n const doc: SanityDocumentLike = {\n ...action.document,\n _id: vid,\n };\n const existing = store.get(vid);\n const promoted = promote(doc);\n if (existing !== undefined) {\n promoted._createdAt = existing._createdAt;\n }\n store.put(promoted);\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.document.version.replace\": {\n const id = action.document._id;\n if (typeof id !== \"string\") {\n throw new Error(\"version.replace needs document._id\");\n }\n const { releaseId } = parseVersionId(id);\n const release = getRelease(store, releaseId);\n requireActive(release);\n const existing = store.get(id);\n const promoted = promote(action.document);\n if (existing !== undefined) {\n promoted._createdAt = existing._createdAt;\n }\n store.put(promoted);\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.document.version.discard\": {\n const { releaseId } = parseVersionId(action.versionId);\n const release = getRelease(store, releaseId);\n requireActive(release);\n store.remove(action.versionId);\n return { transactionId: cryptoRandomKey() };\n }\n\n case \"sanity.action.document.version.unpublish\": {\n const { releaseId } = parseVersionId(action.versionId);\n const release = getRelease(store, releaseId);\n requireActive(release);\n const doc: SanityDocumentLike = {\n _id: action.versionId,\n _type: \"stub\",\n _system: { delete: true },\n } as SanityDocumentLike;\n store.put(promote(doc));\n return { transactionId: cryptoRandomKey() };\n }\n }\n}\n\n/**\n * Apply an action or a list of actions atomically. On any failure all\n * mutations within the batch are rolled back to the pre-batch snapshot.\n */\nexport function applyActions(store: TestStore, actions: Action | Action[]): ActionResult {\n const list = Array.isArray(actions) ? actions : [actions];\n const snapshot = new Map<string, SanityDocument | undefined>();\n const remember = (id: string) => {\n if (!snapshot.has(id)) snapshot.set(id, store.get(id));\n };\n\n try {\n let last: ActionResult = { transactionId: cryptoRandomKey() };\n for (const a of list) {\n // Pre-record everything we might touch so rollback is precise.\n switch (a.actionType) {\n case \"sanity.action.release.create\":\n case \"sanity.action.release.edit\":\n case \"sanity.action.release.schedule\":\n case \"sanity.action.release.unschedule\":\n case \"sanity.action.release.archive\":\n case \"sanity.action.release.unarchive\":\n case \"sanity.action.release.delete\":\n remember(`${RELEASE_DOC_PREFIX}${a.releaseId}`);\n break;\n case \"sanity.action.release.publish\": {\n remember(`${RELEASE_DOC_PREFIX}${a.releaseId}`);\n const prefix = `${VERSIONS_PREFIX}${a.releaseId}.`;\n for (const d of store.all()) {\n if (d._id.startsWith(prefix)) {\n remember(d._id);\n remember(d._id.slice(prefix.length));\n }\n }\n break;\n }\n case \"sanity.action.document.version.create\": {\n // version.create derives its id from releaseId + publishedId,\n // so we don't rely on document._id being set.\n const releaseId =\n a.releaseId ??\n (typeof a.document._id === \"string\"\n ? parseVersionId(a.document._id).releaseId\n : undefined);\n if (releaseId !== undefined) {\n remember(versionDocId(releaseId, a.publishedId));\n }\n break;\n }\n case \"sanity.action.document.version.replace\":\n if (typeof a.document._id === \"string\") remember(a.document._id);\n break;\n case \"sanity.action.document.version.discard\":\n case \"sanity.action.document.version.unpublish\":\n remember(a.versionId);\n break;\n }\n last = applyAction(store, a);\n }\n return last;\n } catch (err) {\n for (const [id, doc] of snapshot) {\n if (doc === undefined) {\n store.remove(id);\n } else {\n store.put(doc);\n }\n }\n throw err;\n }\n}\n","/**\n * Perspective layering — read-side filtering that mirrors Sanity's\n * `perspective` query/client option.\n *\n * The store always holds raw documents. Each fetch builds a *view* of\n * the store filtered by the active perspective before passing the\n * dataset to `groq-js`.\n *\n * Reference: https://www.sanity.io/docs/content-lake/perspectives\n */\n\nimport type { SanityDocument } from \"@sanity/types\";\n\nexport type Perspective = \"raw\" | \"published\" | \"drafts\" | string[];\n\nconst DRAFTS_PREFIX = \"drafts.\";\nconst VERSIONS_PREFIX = \"versions.\";\nconst SYSTEM_PREFIX = \"_.\";\nconst RELEASE_DOC_PREFIX = \"_.releases.\";\n\n/** Strip `drafts.` / `versions.<name>.` prefix to recover the published id. */\nexport function publishedIdOf(id: string): string {\n if (id.startsWith(DRAFTS_PREFIX)) return id.slice(DRAFTS_PREFIX.length);\n if (id.startsWith(VERSIONS_PREFIX)) {\n // versions.<name>.<id> — split off the prefix + name\n const rest = id.slice(VERSIONS_PREFIX.length);\n const dot = rest.indexOf(\".\");\n if (dot === -1) return rest;\n return rest.slice(dot + 1);\n }\n return id;\n}\n\n/** Extract the release name from a `versions.<name>.<id>` id, else undefined. */\nexport function releaseNameOf(id: string): string | undefined {\n if (!id.startsWith(VERSIONS_PREFIX)) return undefined;\n const rest = id.slice(VERSIONS_PREFIX.length);\n const dot = rest.indexOf(\".\");\n if (dot === -1) return undefined;\n return rest.slice(0, dot);\n}\n\nexport const isDraftId = (id: string): boolean => id.startsWith(DRAFTS_PREFIX);\nexport const isVersionId = (id: string): boolean => id.startsWith(VERSIONS_PREFIX);\nexport const isSystemId = (id: string): boolean => id.startsWith(SYSTEM_PREFIX);\nexport const isReleaseDocId = (id: string): boolean => id.startsWith(RELEASE_DOC_PREFIX);\n\n/**\n * Apply a perspective to a raw set of documents and return the view\n * groq-js should evaluate against.\n *\n * For non-raw perspectives, each output document for a non-system id\n * gets `_id` projected to its published id and `_originalId` carrying\n * the source id (matching the wire shape of the real Content Lake).\n */\nexport function applyPerspective(\n docs: readonly SanityDocument[],\n perspective: Perspective,\n): SanityDocument[] {\n if (perspective === \"raw\") return [...docs];\n\n if (perspective === \"published\") {\n return docs.filter((d) => !isDraftId(d._id) && !isVersionId(d._id) && !isSystemId(d._id));\n }\n\n if (perspective === \"drafts\") {\n return mergeStack(docs, [], { includeDrafts: true, includeReleaseDocs: false });\n }\n\n // string[] — release stack with optional \"drafts\" entry\n const stack: string[] = [];\n let includeDrafts = false;\n for (const entry of perspective) {\n if (entry === \"drafts\") {\n includeDrafts = true;\n } else {\n stack.push(entry);\n }\n }\n return mergeStack(docs, stack, { includeDrafts, includeReleaseDocs: true });\n}\n\ninterface MergeOptions {\n includeDrafts: boolean;\n includeReleaseDocs: boolean;\n}\n\n/**\n * Walk the raw docs once, group by published id, then for each group\n * pick the highest-priority source according to `releaseStack` and\n * `includeDrafts`. Project the chosen doc to the published id.\n */\nfunction mergeStack(\n docs: readonly SanityDocument[],\n releaseStack: readonly string[],\n options: MergeOptions,\n): SanityDocument[] {\n // Group docs by published id. Track which sources exist.\n type Group = {\n published?: SanityDocument;\n draft?: SanityDocument;\n versions: Map<string, SanityDocument>;\n };\n const groups = new Map<string, Group>();\n const systemReleases: SanityDocument[] = [];\n\n for (const doc of docs) {\n if (isReleaseDocId(doc._id)) {\n if (options.includeReleaseDocs) systemReleases.push(doc);\n continue;\n }\n if (isSystemId(doc._id)) continue; // other system docs are hidden\n const pubId = publishedIdOf(doc._id);\n let group = groups.get(pubId);\n if (group === undefined) {\n group = { versions: new Map() };\n groups.set(pubId, group);\n }\n if (isDraftId(doc._id)) {\n group.draft = doc;\n } else if (isVersionId(doc._id)) {\n const releaseName = releaseNameOf(doc._id);\n if (releaseName !== undefined) group.versions.set(releaseName, doc);\n } else {\n group.published = doc;\n }\n }\n\n const out: SanityDocument[] = [];\n for (const [pubId, group] of groups) {\n // Walk the release stack first, then drafts (if enabled, in its\n // slot — here we model \"drafts after the stack\" matching the\n // typical `[r1, r2, \"drafts\"]` shape), then fall through to\n // published.\n let chosen: SanityDocument | undefined;\n for (const releaseName of releaseStack) {\n const v = group.versions.get(releaseName);\n if (v !== undefined) {\n // version.unpublish stub: hide the doc entirely\n if (isUnpublishStub(v)) {\n chosen = undefined;\n break;\n }\n chosen = v;\n break;\n }\n }\n if (chosen === undefined && options.includeDrafts && group.draft !== undefined) {\n chosen = group.draft;\n }\n if (chosen === undefined && group.published !== undefined) {\n // skip if any release in the stack carried an unpublish stub\n const blocked = releaseStack.some((name) => {\n const v = group.versions.get(name);\n return v !== undefined && isUnpublishStub(v);\n });\n if (!blocked) chosen = group.published;\n }\n if (chosen !== undefined) {\n out.push(projectToPublishedId(chosen, pubId));\n }\n }\n\n return [...out, ...systemReleases];\n}\n\nfunction isUnpublishStub(doc: SanityDocument): boolean {\n const sys = (doc as { _system?: { delete?: boolean } })._system;\n return sys?.delete === true;\n}\n\nfunction projectToPublishedId(doc: SanityDocument, pubId: string): SanityDocument {\n if (doc._id === pubId) return doc;\n return { ...doc, _id: pubId, _originalId: doc._id } as SanityDocument;\n}\n\n/**\n * `getDocument(id)` resolution under a perspective. Returns the chosen\n * source's *raw* doc (not projected), so the caller can decide whether\n * to expose `_originalId` etc. For perspective=\"raw\", this is just\n * `docs.find(d => d._id === id)`.\n */\nexport function resolveDocument(\n docs: readonly SanityDocument[],\n id: string,\n perspective: Perspective,\n): SanityDocument | undefined {\n if (perspective === \"raw\") {\n return docs.find((d) => d._id === id);\n }\n // Match the rules of applyPerspective: resolve to the same chosen\n // doc but key by the requested id (which the caller treated as a\n // published id under non-raw perspectives).\n const view = applyPerspective(docs, perspective);\n return view.find((d) => d._id === id);\n}\n","import type { SanityDocument, SanityDocumentLike } from \"@sanity/types\";\nimport type { PatchHandle, PatchOp } from \"./patch.ts\";\n\n/**\n * A transaction collects mutations and applies them atomically at commit\n * time. Mutations are applied in declaration order; if any one fails, the\n * whole transaction throws and no writes land in the store.\n *\n * Subset of `@sanity/client`'s Transaction. Same shape, narrower surface.\n */\nexport type TxnMutation =\n | { kind: \"create\"; doc: SanityDocumentLike }\n | { kind: \"createOrReplace\"; doc: SanityDocumentLike }\n | { kind: \"createIfNotExists\"; doc: SanityDocumentLike }\n | { kind: \"delete\"; id: string }\n | { kind: \"patch\"; documentId: string; ops: PatchOp[]; ifRev?: string };\n\nexport interface TransactionHandle {\n create: (doc: SanityDocumentLike) => TransactionHandle;\n createOrReplace: (doc: SanityDocumentLike) => TransactionHandle;\n createIfNotExists: (doc: SanityDocumentLike) => TransactionHandle;\n delete: (id: string) => TransactionHandle;\n /** Accept either a Patch builder (drained at commit) or an inline doc id + ops. */\n patch: (\n documentIdOrPatch: string | PatchHandle,\n ops?: PatchOp[],\n ifRev?: string,\n ) => TransactionHandle;\n commit: (options?: TxnCommitOptions) => Promise<TxnCommitResult>;\n /** Test-only introspection — not on real Transaction. */\n toJSON: () => TxnMutation[];\n}\n\nexport interface TxnCommitOptions {\n /** If true, returns the resulting documents instead of just ids. */\n returnDocuments?: boolean;\n /** With `returnDocuments`, return only the first updated doc. */\n returnFirst?: boolean;\n}\n\nexport type TxnCommitResult =\n | {\n transactionId: string;\n results: { id: string; operation: \"create\" | \"update\" | \"delete\" }[];\n }\n | SanityDocument\n | SanityDocument[];\n\nexport interface TxnExecutor {\n apply: (mutations: TxnMutation[], options?: TxnCommitOptions) => Promise<TxnCommitResult>;\n}\n\nexport function createTransaction(executor: TxnExecutor): TransactionHandle {\n const mutations: TxnMutation[] = [];\n\n const handle: TransactionHandle = {\n create: (doc) => {\n mutations.push({ kind: \"create\", doc });\n return handle;\n },\n createOrReplace: (doc) => {\n mutations.push({ kind: \"createOrReplace\", doc });\n return handle;\n },\n createIfNotExists: (doc) => {\n mutations.push({ kind: \"createIfNotExists\", doc });\n return handle;\n },\n delete: (id) => {\n mutations.push({ kind: \"delete\", id });\n return handle;\n },\n patch: (documentIdOrPatch, ops, ifRev) => {\n if (typeof documentIdOrPatch === \"string\") {\n mutations.push({\n kind: \"patch\",\n documentId: documentIdOrPatch,\n ops: ops ?? [],\n ...(ifRev !== undefined ? { ifRev } : {}),\n });\n } else {\n const json = documentIdOrPatch.toJSON();\n mutations.push({\n kind: \"patch\",\n documentId: json.id,\n ops: json.ops,\n ...(json.ifRev !== undefined ? { ifRev: json.ifRev } : {}),\n });\n }\n return handle;\n },\n commit: (options) => executor.apply(mutations, options),\n toJSON: () => [...mutations],\n };\n\n return handle;\n}\n","import type { SanityDocument, SanityDocumentLike } from \"@sanity/types\";\nimport { evaluate, parse } from \"groq-js\";\nimport {\n canAccess,\n enforceWriteAccess,\n makeRequestImpl,\n type DocumentValuePermission,\n type TestClientAccessControl,\n} from \"./access-control.ts\";\nimport {\n applyActions,\n type Action,\n type ActionResult,\n type DocumentBody,\n type ReleaseDocument,\n type ReleaseMetadata,\n} from \"./actions.ts\";\nimport { applyPerspective, resolveDocument, type Perspective } from \"./perspective.ts\";\nimport { applyPatchOps, createPatch, type PatchHandle, type PatchOp } from \"./patch.ts\";\nimport { createTestStore, cryptoRandomKey, type TestStore } from \"./store.ts\";\nimport {\n createTransaction,\n type TransactionHandle,\n type TxnCommitOptions,\n type TxnCommitResult,\n type TxnMutation,\n} from \"./transaction.ts\";\n\n/**\n * Data-plane subset of `@sanity/client`'s surface, with multi-dataset support.\n *\n * One `createTestClient()` call creates a registry of stores keyed by\n * `(projectId, dataset)`. The returned client points at one of them.\n * `withConfig({ projectId, dataset })` returns a new handle into the same\n * registry but pointing at a different store — useful for workflows where\n * definitions live in one dataset and instances live in another.\n *\n * Two separate `createTestClient()` calls have separate registries, so\n * tests stay isolated from each other.\n *\n * Methods not on the real client:\n *\n * - `seed`, `add`, `reset`, `snapshot`, `store` — bulk-load helpers,\n * scoped to the current dataset.\n * - `datasets()` — lists known dataset keys in this registry.\n *\n * Methods deliberately omitted:\n *\n * - Project / agent / asset / live / listen APIs.\n */\nexport interface FetchOptions {\n /** Override the client's configured perspective for this read. */\n perspective?: Perspective;\n}\n\nexport interface TestClient {\n // --- query ---\n fetch: <T = unknown>(\n query: string,\n params?: Record<string, unknown>,\n options?: FetchOptions,\n ) => Promise<T>;\n getDocument: <T = SanityDocument>(id: string, options?: FetchOptions) => Promise<T | null>;\n getDocuments: <T = SanityDocument>(\n ids: string[],\n options?: FetchOptions,\n ) => Promise<(T | null)[]>;\n /**\n * Sanity-style HTTP request hook. Present iff the client was\n * created with `accessControl`. The engine's access resolver pokes\n * `/users/me` and the configured ACL path through this — without it\n * the engine throws on any verb that needs an actor (which is the\n * cue for the bench to supply an `access` override).\n *\n * Test cases that want to exercise the engine's token-resolution\n * path should configure `accessControl`; cases that want bench-style\n * always-allow should leave it off and pass `access` through the\n * bench.\n */\n request?: <T>(opts: {\n url?: string;\n uri?: string;\n signal?: AbortSignal;\n tag?: string;\n }) => Promise<T>;\n\n // --- mutate ---\n create: <T extends SanityDocumentLike>(\n doc: T,\n options?: MutationOptions,\n ) => Promise<T & SystemFields>;\n createOrReplace: <T extends SanityDocumentLike>(\n doc: T,\n options?: MutationOptions,\n ) => Promise<T & SystemFields>;\n createIfNotExists: <T extends SanityDocumentLike>(\n doc: T,\n options?: MutationOptions,\n ) => Promise<T & SystemFields>;\n delete: (id: string, options?: MutationOptions) => Promise<{ _id: string } | null>;\n patch: (documentId: string) => PatchHandle;\n transaction: () => TransactionHandle;\n\n // --- actions / releases ---\n /**\n * Dispatch one or more release / version actions against the store.\n * Returns a synthetic `transactionId`. Multi-action arrays are applied\n * atomically — any failure rolls back the batch.\n */\n action: (action: Action | Action[]) => Promise<ActionResult>;\n /** Helpers mirroring `@sanity/client`'s `client.releases.*` surface. */\n releases: ReleasesHandle;\n /** Helper for `sanity.action.document.version.create`. */\n createVersion: (params: {\n releaseId: string;\n publishedId: string;\n document: DocumentBody;\n }) => Promise<ActionResult>;\n /** Helper for `sanity.action.document.version.unpublish`. */\n unpublishVersion: (params: { releaseId: string; publishedId: string }) => Promise<ActionResult>;\n\n // --- config ---\n /** Current `(projectId, dataset)` and active perspective. */\n config: () => { projectId: string; dataset: string; perspective: Perspective };\n /**\n * Return a new client handle pointing at a different `(projectId, dataset)`\n * within the SAME backing registry. Either field can be omitted to keep\n * the current value. Two clients produced by `withConfig` see each\n * other's writes if they point at the same `(projectId, dataset)`.\n */\n withConfig: (\n config: Partial<{ projectId: string; dataset: string; perspective: Perspective }>,\n ) => TestClient;\n\n // --- test helpers (not on real SanityClient) ---\n /** Replace the current dataset with the given documents. */\n seed: (documents: SanityDocumentLike[]) => void;\n /** Add documents to the current dataset without dropping existing ones. */\n add: (documents: SanityDocumentLike[]) => void;\n /** Drop all documents in the current dataset. */\n reset: () => void;\n /** Read-only snapshot of all documents in the current dataset. */\n snapshot: () => readonly SanityDocument[];\n /** Direct store access for the current dataset. */\n store: () => TestStore;\n /** List `(projectId, dataset)` pairs known in this registry. */\n datasets: () => { projectId: string; dataset: string }[];\n}\n\nexport interface ReleasesHandle {\n create: (params: {\n releaseId: string;\n metadata?: Partial<ReleaseMetadata>;\n }) => Promise<ActionResult & { releaseId: string; metadata: ReleaseMetadata }>;\n edit: (params: {\n releaseId: string;\n patch: { set?: Record<string, unknown>; unset?: string[] };\n }) => Promise<ActionResult>;\n get: (params: { releaseId: string }) => Promise<ReleaseDocument | undefined>;\n fetchDocuments: (params: { releaseId: string }) => Promise<SanityDocument[]>;\n schedule: (params: { releaseId: string; publishAt: string }) => Promise<ActionResult>;\n unschedule: (params: { releaseId: string }) => Promise<ActionResult>;\n publish: (params: { releaseId: string }) => Promise<ActionResult>;\n archive: (params: { releaseId: string }) => Promise<ActionResult>;\n unarchive: (params: { releaseId: string }) => Promise<ActionResult>;\n delete: (params: { releaseId: string }) => Promise<ActionResult>;\n}\n\nexport interface MutationOptions {\n ifRevisionId?: string;\n}\n\nexport interface SystemFields {\n _rev: string;\n _createdAt: string;\n _updatedAt: string;\n}\n\nexport interface CreateTestClientOptions {\n /** Default project id. Defaults to `\"test\"`. */\n projectId?: string;\n /** Default dataset name. Defaults to `\"test\"`. */\n dataset?: string;\n /** Documents to seed into the default `(projectId, dataset)` store. */\n documents?: SanityDocumentLike[];\n /**\n * Optional: seed multiple datasets at once. Keys are `\"projectId:dataset\"`\n * pairs. Useful when workflows span multiple datasets.\n */\n datasets?: Record<string, SanityDocumentLike[]>;\n /**\n * Opt-in access-control simulation. When set, the client exposes\n * `request({ url })` that serves `/users/me` + the configured ACL\n * path, AND every mutation is gated through the supplied `grants`\n * — writes that the grants don't permit throw a `WriteAccessDeniedError`\n * shaped like Sanity's 403. Lets tests demonstrate the full\n * \"engine soft-gate + lake hard-gate\" contract end-to-end without\n * a real HTTP round-trip.\n *\n * When omitted, `request` is undefined and all writes proceed\n * unconditionally — the legacy behavior.\n */\n accessControl?: TestClientAccessControl;\n /**\n * Default perspective applied to reads. Per-fetch `options.perspective`\n * overrides this. `withConfig({ perspective })` returns a sibling\n * handle with a different default. Defaults to `\"raw\"` so existing\n * tests stay green.\n */\n perspective?: Perspective;\n}\n\nexport type { Perspective } from \"./perspective.ts\";\nexport type {\n Action,\n ActionResult,\n DocumentBody,\n ReleaseAction,\n ReleaseDocument,\n ReleaseMetadata,\n ReleaseState,\n ReleaseType,\n VersionAction,\n} from \"./actions.ts\";\n\nexport type { TestClientAccessControl, DocumentValuePermission } from \"./access-control.ts\";\nexport { WriteAccessDeniedError } from \"./access-control.ts\";\n\nconst DEFAULT_PROJECT_ID = \"test\";\nconst DEFAULT_DATASET = \"test\";\n\ntype Registry = Map<string, TestStore>;\n\nconst registryKey = (projectId: string, dataset: string) => `${projectId}:${dataset}`;\n\nconst parseRegistryKey = (key: string): { projectId: string; dataset: string } => {\n const [projectId, dataset] = key.split(\":\") as [string, string];\n return { projectId, dataset };\n};\n\n/**\n * Create a fresh, isolated test client backed by a private multi-dataset\n * registry. `withConfig` returns a sibling handle into the same registry.\n *\n * ```\n * const client = createTestClient({\n * datasets: {\n * \"test:definitions\": [...],\n * \"test:instances\": [...],\n * },\n * });\n * const defs = client.withConfig({ dataset: \"definitions\" });\n * const inst = client.withConfig({ dataset: \"instances\" });\n * ```\n */\nexport function createTestClient(options: CreateTestClientOptions = {}): TestClient {\n const defaultProjectId = options.projectId ?? DEFAULT_PROJECT_ID;\n const defaultDataset = options.dataset ?? DEFAULT_DATASET;\n\n const registry: Registry = new Map();\n\n const ensureStore = (projectId: string, dataset: string): TestStore => {\n const key = registryKey(projectId, dataset);\n let store = registry.get(key);\n if (store === undefined) {\n store = createTestStore();\n registry.set(key, store);\n }\n return store;\n };\n\n // Seed defaults.\n if (options.documents) {\n ensureStore(defaultProjectId, defaultDataset).seed(options.documents);\n }\n if (options.datasets) {\n for (const [key, docs] of Object.entries(options.datasets)) {\n const { projectId, dataset } = parseRegistryKey(key);\n ensureStore(projectId, dataset).seed(docs);\n }\n }\n\n return makeHandle(\n registry,\n defaultProjectId,\n defaultDataset,\n options.accessControl,\n options.perspective ?? \"raw\",\n );\n}\n\nfunction makeHandle(\n registry: Registry,\n projectId: string,\n dataset: string,\n accessControl: TestClientAccessControl | undefined,\n perspective: Perspective,\n): TestClient {\n const store = (() => {\n const key = registryKey(projectId, dataset);\n let s = registry.get(key);\n if (s === undefined) {\n s = createTestStore();\n registry.set(key, s);\n }\n return s;\n })();\n\n const promote = (doc: SanityDocumentLike): SanityDocument & SystemFields => {\n const now = new Date().toISOString();\n if (typeof doc._id !== \"string\" || doc._id.length === 0) {\n throw new Error(`document is missing _id`);\n }\n if (typeof doc._type !== \"string\" || doc._type.length === 0) {\n throw new Error(`document ${doc._id} is missing _type`);\n }\n return {\n ...doc,\n _id: doc._id,\n _type: doc._type,\n _rev: cryptoRandomKey(),\n _createdAt: doc._createdAt ?? now,\n _updatedAt: now,\n } as SanityDocument & SystemFields;\n };\n\n const checkRevision = (id: string, ifRevisionId: string | undefined) => {\n if (ifRevisionId === undefined) return;\n const existing = store.get(id);\n if (existing === undefined) {\n throw new Error(`ifRevisionId check failed: document ${id} does not exist`);\n }\n if (existing._rev !== ifRevisionId) {\n throw new Error(\n `ifRevisionId check failed: document ${id} has _rev=${existing._rev}, expected ${ifRevisionId}`,\n );\n }\n };\n\n /**\n * Gate a mutation through the configured `accessControl.grants`. No-op\n * when accessControl is absent (legacy bench behavior). On denial\n * throws `WriteAccessDeniedError` — same failure mode tests get from\n * a real Sanity 403.\n *\n * Pass the doc as-it-would-be at evaluation time: for `create` /\n * `createOrReplace`-as-create / `createIfNotExists`, that's the new\n * doc with its incoming fields; for `update` / `patch` / `delete`,\n * that's the existing doc (the grant filter identifies what the\n * actor can touch).\n */\n const gate = async (\n document: SanityDocumentLike & { _id?: string; _type?: string },\n permission: DocumentValuePermission,\n ): Promise<void> => {\n if (accessControl === undefined) return;\n await enforceWriteAccess({\n document,\n grants: accessControl.grants,\n permission,\n userId: accessControl.currentUser.id,\n });\n };\n\n /**\n * Read-gate predicate. Sanity surfaces invisible docs as \"doesn't\n * exist\" on the wire (404 / null) rather than throwing 403 — so\n * reads filter rather than refuse.\n *\n * - `fetch` filters the dataset before running GROQ.\n * - `getDocument` / `getDocuments` return null for invisible docs.\n * - `snapshot` is a test-helper that bypasses the read gate\n * intentionally (tests need raw inspection).\n *\n * Returns `true` when accessControl is absent (no gating).\n */\n const canRead = async (\n document: SanityDocumentLike & { _id?: string; _type?: string },\n ): Promise<boolean> => {\n if (accessControl === undefined) return true;\n return canAccess({\n document,\n grants: accessControl.grants,\n permission: \"read\",\n userId: accessControl.currentUser.id,\n });\n };\n\n const applyPatch = async (\n documentId: string,\n ops: PatchOp[],\n ifRev: string | undefined,\n ): Promise<SanityDocument> => {\n const existing = store.get(documentId);\n if (existing === undefined) {\n throw new Error(`Cannot patch document ${documentId}: not found`);\n }\n checkRevision(documentId, ifRev);\n // Gate against the existing doc — that's what the grant filter\n // matches: \"what docs can this actor update?\".\n await gate(existing, \"update\");\n const patched = applyPatchOps(existing, ops);\n const now = new Date().toISOString();\n const next: SanityDocument = {\n ...patched,\n _rev: cryptoRandomKey(),\n _updatedAt: now,\n };\n store.put(next);\n return next;\n };\n\n const applyTransaction = async (\n mutations: TxnMutation[],\n txnOptions?: TxnCommitOptions,\n ): Promise<TxnCommitResult> => {\n const snapshot = new Map<string, SanityDocument | undefined>();\n const touchedIds: string[] = [];\n const operations: { id: string; operation: \"create\" | \"update\" | \"delete\" }[] = [];\n\n const remember = (id: string) => {\n if (!snapshot.has(id)) snapshot.set(id, store.get(id));\n };\n\n try {\n for (const mut of mutations) {\n switch (mut.kind) {\n case \"create\": {\n remember(mut.doc._id as string);\n const promoted = promote(mut.doc);\n if (store.get(promoted._id) !== undefined) {\n throw new Error(`create failed: document ${promoted._id} already exists`);\n }\n await gate(promoted, \"create\");\n store.put(promoted);\n touchedIds.push(promoted._id);\n operations.push({ id: promoted._id, operation: \"create\" });\n break;\n }\n case \"createOrReplace\": {\n remember(mut.doc._id as string);\n const promoted = promote(mut.doc);\n const existing = store.get(promoted._id);\n // For an existing doc, \"update\" is the relevant permission;\n // for a net-new one, \"create\".\n await gate(existing ?? promoted, existing !== undefined ? \"update\" : \"create\");\n const wasPresent = existing !== undefined;\n store.put(promoted);\n touchedIds.push(promoted._id);\n operations.push({ id: promoted._id, operation: wasPresent ? \"update\" : \"create\" });\n break;\n }\n case \"createIfNotExists\": {\n remember(mut.doc._id as string);\n if (store.get(mut.doc._id as string) === undefined) {\n const promoted = promote(mut.doc);\n await gate(promoted, \"create\");\n store.put(promoted);\n touchedIds.push(promoted._id);\n operations.push({ id: promoted._id, operation: \"create\" });\n }\n break;\n }\n case \"delete\": {\n remember(mut.id);\n const existing = store.get(mut.id);\n // Sanity's permission model has no separate \"delete\" —\n // delete is gated as an update against the existing doc.\n if (existing !== undefined) await gate(existing, \"update\");\n const removed = store.remove(mut.id);\n if (removed) {\n touchedIds.push(mut.id);\n operations.push({ id: mut.id, operation: \"delete\" });\n }\n break;\n }\n case \"patch\": {\n remember(mut.documentId);\n // applyPatch already gates internally.\n await applyPatch(mut.documentId, mut.ops, mut.ifRev);\n touchedIds.push(mut.documentId);\n operations.push({ id: mut.documentId, operation: \"update\" });\n break;\n }\n }\n }\n } catch (error) {\n for (const [id, doc] of snapshot) {\n if (doc === undefined) {\n store.remove(id);\n } else {\n store.put(doc);\n }\n }\n throw error;\n }\n\n if (txnOptions?.returnDocuments) {\n const docs = touchedIds\n .map((id) => store.get(id))\n .filter((d): d is SanityDocument => d !== undefined);\n return txnOptions.returnFirst ? (docs[0] as SanityDocument) : docs;\n }\n\n return {\n transactionId: cryptoRandomKey(),\n results: operations,\n };\n };\n\n /**\n * Filter the store's docs to only those `canRead` returns true\n * for. When accessControl is unset, returns the full set (no\n * filtering cost). Used by `fetch` so GROQ runs against the\n * actor-visible subset, matching Sanity's per-token query behavior.\n */\n const accessVisibleDocs = async (): Promise<SanityDocument[]> => {\n const all = store.all();\n if (accessControl === undefined) return all;\n const out: SanityDocument[] = [];\n for (const doc of all) {\n if (await canRead(doc)) out.push(doc);\n }\n return out;\n };\n\n /**\n * Apply both access-control gating and perspective layering. The order\n * matters: access first (an invisible doc shouldn't even be a candidate\n * for perspective layering), perspective second (which can hide / merge\n * docs that the actor would otherwise see).\n */\n const visibleDocs = async (override?: Perspective): Promise<SanityDocument[]> => {\n const accessible = await accessVisibleDocs();\n return applyPerspective(accessible, override ?? perspective);\n };\n\n const client: TestClient = {\n fetch: async (query, params, opts) => {\n const tree = parse(query, params ? { params } : undefined);\n const value = await evaluate(tree, {\n dataset: await visibleDocs(opts?.perspective),\n ...(params !== undefined ? { params } : {}),\n });\n return (await value.get()) as never;\n },\n\n getDocument: async (id, opts) => {\n const effective = opts?.perspective ?? perspective;\n if (effective === \"raw\") {\n const doc = store.get(id);\n if (doc === undefined) return null;\n if (!(await canRead(doc))) return null;\n return doc as never;\n }\n const view = await visibleDocs(effective);\n const found = resolveDocument(view, id, effective);\n return (found ?? null) as never;\n },\n\n getDocuments: async (ids, opts) => {\n const effective = opts?.perspective ?? perspective;\n if (effective === \"raw\") {\n const result: (SanityDocument | null)[] = [];\n for (const id of ids) {\n const doc = store.get(id);\n if (doc === undefined) result.push(null);\n else if (await canRead(doc)) result.push(doc);\n else result.push(null);\n }\n return result as never;\n }\n const view = await visibleDocs(effective);\n return ids.map((id) => resolveDocument(view, id, effective) ?? null) as never;\n },\n\n create: async (doc, opts) => {\n checkRevision(doc._id as string, opts?.ifRevisionId);\n if (store.get(doc._id as string) !== undefined) {\n throw new Error(`create failed: document ${doc._id} already exists`);\n }\n const promoted = promote(doc);\n await gate(promoted, \"create\");\n store.put(promoted);\n return promoted as typeof doc & SystemFields;\n },\n\n createOrReplace: async (doc, opts) => {\n checkRevision(doc._id as string, opts?.ifRevisionId);\n const promoted = promote(doc);\n const existing = store.get(doc._id as string);\n await gate(existing ?? promoted, existing !== undefined ? \"update\" : \"create\");\n store.put(promoted);\n return promoted as typeof doc & SystemFields;\n },\n\n createIfNotExists: async (doc, opts) => {\n checkRevision(doc._id as string, opts?.ifRevisionId);\n const existing = store.get(doc._id as string);\n if (existing !== undefined) return existing as typeof doc & SystemFields;\n const promoted = promote(doc);\n await gate(promoted, \"create\");\n store.put(promoted);\n return promoted as typeof doc & SystemFields;\n },\n\n delete: async (id, opts) => {\n checkRevision(id, opts?.ifRevisionId);\n const existing = store.get(id);\n if (existing !== undefined) await gate(existing, \"update\");\n const removed = store.remove(id);\n return removed ? { _id: id } : null;\n },\n\n patch: (documentId) => createPatch(documentId, { apply: applyPatch }),\n\n transaction: () => createTransaction({ apply: applyTransaction }),\n\n action: async (action) => applyActions(store, action),\n\n releases: {\n create: async ({ releaseId, metadata }) => {\n const result = applyActions(store, {\n actionType: \"sanity.action.release.create\",\n releaseId,\n ...(metadata !== undefined ? { metadata } : {}),\n });\n const doc = store.get(`_.releases.${releaseId}`) as ReleaseDocument | undefined;\n return {\n ...result,\n releaseId,\n metadata: doc?.metadata ?? { releaseType: \"undecided\" },\n };\n },\n edit: async ({ releaseId, patch }) =>\n applyActions(store, { actionType: \"sanity.action.release.edit\", releaseId, patch }),\n get: async ({ releaseId }) => {\n const doc = store.get(`_.releases.${releaseId}`) as ReleaseDocument | undefined;\n return doc;\n },\n fetchDocuments: async ({ releaseId }) => {\n const prefix = `versions.${releaseId}.`;\n return store.all().filter((d) => d._id.startsWith(prefix));\n },\n schedule: async ({ releaseId, publishAt }) =>\n applyActions(store, {\n actionType: \"sanity.action.release.schedule\",\n releaseId,\n publishAt,\n }),\n unschedule: async ({ releaseId }) =>\n applyActions(store, { actionType: \"sanity.action.release.unschedule\", releaseId }),\n publish: async ({ releaseId }) =>\n applyActions(store, { actionType: \"sanity.action.release.publish\", releaseId }),\n archive: async ({ releaseId }) =>\n applyActions(store, { actionType: \"sanity.action.release.archive\", releaseId }),\n unarchive: async ({ releaseId }) =>\n applyActions(store, { actionType: \"sanity.action.release.unarchive\", releaseId }),\n delete: async ({ releaseId }) =>\n applyActions(store, { actionType: \"sanity.action.release.delete\", releaseId }),\n },\n\n createVersion: async ({ releaseId, publishedId, document }) =>\n applyActions(store, {\n actionType: \"sanity.action.document.version.create\",\n releaseId,\n publishedId,\n document,\n }),\n\n unpublishVersion: async ({ releaseId, publishedId }) =>\n applyActions(store, {\n actionType: \"sanity.action.document.version.unpublish\",\n versionId: `versions.${releaseId}.${publishedId}`,\n publishedId,\n }),\n\n ...(accessControl !== undefined\n ? { request: makeRequestImpl(accessControl, projectId, dataset) }\n : {}),\n\n config: () => ({ projectId, dataset, perspective }),\n\n withConfig: (config) =>\n makeHandle(\n registry,\n config.projectId ?? projectId,\n config.dataset ?? dataset,\n accessControl,\n config.perspective ?? perspective,\n ),\n\n seed: (documents) => store.seed(documents),\n add: (documents) => store.add(documents),\n reset: () => store.reset(),\n snapshot: () => store.snapshot(),\n store: () => store,\n datasets: () => [...registry.keys()].map(parseRegistryKey),\n };\n\n return client;\n}\n"],"names":["promote","RELEASE_DOC_PREFIX","VERSIONS_PREFIX"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity-labs/client-fake-for-test",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "In-memory fake of @sanity/client for tests. Implements the data-plane subset (fetch / getDocument / patch / createOrReplace / transaction) against a groq-js evaluator and a closure-scoped document store.",
|
|
6
6
|
"keywords": [
|