@ai-matrx/alchemy 0.12.3 → 0.14.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/CHANGELOG.md +46 -0
- package/dist/actions.cjs.map +1 -1
- package/dist/actions.d.cts +19 -24
- package/dist/actions.d.ts +19 -24
- package/dist/actions.js.map +1 -1
- package/dist/checks.d.cts +2 -0
- package/dist/checks.d.ts +2 -0
- package/dist/core.cjs +4 -4
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +8 -58
- package/dist/core.d.ts +8 -58
- package/dist/core.js +4 -4
- package/dist/core.js.map +1 -1
- package/dist/declare/schema.json +3 -0
- package/dist/declare.cjs +1 -1
- package/dist/declare.cjs.map +1 -1
- package/dist/declare.d.cts +26 -1
- package/dist/declare.d.ts +26 -1
- package/dist/declare.js +1 -1
- package/dist/declare.js.map +1 -1
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -58
- package/dist/index.d.ts +8 -58
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/menu.d.cts +17 -22
- package/dist/menu.d.ts +17 -22
- package/dist/operate-document.cjs +2 -2
- package/dist/operate-document.cjs.map +1 -1
- package/dist/operate-document.d.cts +4 -42
- package/dist/operate-document.d.ts +4 -42
- package/dist/operate-document.js +2 -2
- package/dist/operate-document.js.map +1 -1
- package/dist/operate-rich-html.cjs +1 -1
- package/dist/operate-rich-html.cjs.map +1 -1
- package/dist/operate-rich-html.d.cts +3 -42
- package/dist/operate-rich-html.d.ts +3 -42
- package/dist/operate-rich-html.js +1 -1
- package/dist/operate-rich-html.js.map +1 -1
- package/dist/operate-server.cjs +1 -1
- package/dist/operate-server.cjs.map +1 -1
- package/dist/operate-server.d.cts +8 -46
- package/dist/operate-server.d.ts +8 -46
- package/dist/operate-server.js +1 -1
- package/dist/operate-server.js.map +1 -1
- package/dist/operate-xlsx.cjs +2 -2
- package/dist/operate-xlsx.cjs.map +1 -1
- package/dist/operate-xlsx.d.cts +4 -42
- package/dist/operate-xlsx.d.ts +4 -42
- package/dist/operate-xlsx.js +2 -2
- package/dist/operate-xlsx.js.map +1 -1
- package/dist/operate.cjs +4 -4
- package/dist/operate.cjs.map +1 -1
- package/dist/operate.d.cts +8 -58
- package/dist/operate.d.ts +8 -58
- package/dist/operate.js +4 -4
- package/dist/operate.js.map +1 -1
- package/dist/ports.d.cts +11 -15
- package/dist/ports.d.ts +11 -15
- package/dist/react-bar.cjs.map +1 -1
- package/dist/react-bar.d.cts +3 -1
- package/dist/react-bar.d.ts +3 -1
- package/dist/react-bar.js.map +1 -1
- package/dist/react-host.d.cts +17 -22
- package/dist/react-host.d.ts +17 -22
- package/dist/react-menu.cjs.map +1 -1
- package/dist/react-menu.d.cts +3 -1
- package/dist/react-menu.d.ts +3 -1
- package/dist/react-menu.js.map +1 -1
- package/dist/react-overflow.cjs.map +1 -1
- package/dist/react-overflow.d.cts +17 -22
- package/dist/react-overflow.d.ts +17 -22
- package/dist/react-overflow.js.map +1 -1
- package/dist/react-palette.cjs.map +1 -1
- package/dist/react-palette.d.cts +3 -1
- package/dist/react-palette.d.ts +3 -1
- package/dist/react-palette.js.map +1 -1
- package/dist/react-selection.cjs.map +1 -1
- package/dist/react-selection.d.cts +3 -1
- package/dist/react-selection.d.ts +3 -1
- package/dist/react-selection.js.map +1 -1
- package/dist/react-sheet.cjs.map +1 -1
- package/dist/react-sheet.d.cts +3 -1
- package/dist/react-sheet.d.ts +3 -1
- package/dist/react-sheet.js.map +1 -1
- package/dist/react-workspace.cjs +7 -0
- package/dist/react-workspace.cjs.map +1 -0
- package/dist/react-workspace.d.cts +441 -0
- package/dist/react-workspace.d.ts +441 -0
- package/dist/react-workspace.js +7 -0
- package/dist/react-workspace.js.map +1 -0
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/surface.cjs +1 -1
- package/dist/surface.cjs.map +1 -1
- package/dist/surface.d.cts +222 -16
- package/dist/surface.d.ts +222 -16
- package/dist/surface.js +1 -1
- package/dist/surface.js.map +1 -1
- package/package.json +14 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/operate-server.ts","../src/operate/core.ts","../src/operate/server.ts"],"sourcesContent":["export * from \"./operate/server\";\n","import { stringifyJson } from \"@ai-matrx/kit/json-format\";\nimport { escapeHtml } from \"@ai-matrx/kit/html-escape\";\nimport type {\n Artifact,\n CapturedAllMatching,\n CapturedSource,\n Column,\n Draft,\n ExportProjection,\n Json,\n Omission,\n Payload,\n RawSnapshot,\n Snapshot,\n Source,\n Transport,\n TransferScope,\n TransferLimits,\n TransferPreferences,\n SurfaceHandle,\n} from \"./types\";\nexport * from \"./types\";\nexport * from \"./agent-payload\";\n\nconst TRANSFER_ERROR = Symbol.for(\"ai-matrx.alchemy.operate.transfer-error\");\nexport class ContentTransferError extends Error {\n readonly code: string;\n readonly path: string | undefined;\n\n constructor(\n code: string,\n message: string,\n path?: string,\n ) {\n super(message);\n this.code = code;\n this.path = path;\n (this as unknown as Record<symbol, true>)[TRANSFER_ERROR] = true;\n }\n\n /**\n * One error class across every loaded copy: while `@ai-matrx/kit/content-transfer`\n * still bundles this module for its not-yet-repointed consumers (ALC-16), an\n * error raised by one copy is `instanceof` the other.\n */\n static override [Symbol.hasInstance](value: unknown): boolean {\n return typeof value === \"object\" && value !== null && (value as Record<symbol, unknown>)[TRANSFER_ERROR] === true;\n }\n}\n/** A realm-wide brand (Symbol.for), so a binding made by one loaded copy is recognised by another. */\nconst bound: unique symbol = Symbol.for(\"ai-matrx.alchemy.operate.bound-all-matching\");\nexport type BoundAllMatching = {\n readonly [bound]: true;\n readonly source: CapturedAllMatching;\n readonly projection: ExportProjection;\n readonly columns: readonly Column[];\n readonly limits?: TransferLimits;\n};\nconst stop = (s: AbortSignal) => {\n if (s.aborted) throw new DOMException(\"Cancelled\", \"AbortError\");\n};\nconst enc = (x: string) => x.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\nconst dec = (x: string) => x.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\nexport type NormalizeTransferJsonOptions = {\n omitUndefinedObjectProperties?: boolean;\n};\nfunction clone<T extends Json>(\n v: T,\n path = \"\",\n seen = new WeakSet<object>(),\n policy: NormalizeTransferJsonOptions = {},\n): T {\n if (v === null || typeof v === \"string\" || typeof v === \"boolean\") return v;\n if (typeof v === \"number\") {\n if (!Number.isFinite(v))\n throw new ContentTransferError(\n \"unsupported-value\",\n \"Only finite numbers are transferable\",\n path,\n );\n return v;\n }\n if (typeof v !== \"object\")\n throw new ContentTransferError(\n \"unsupported-value\",\n \"Unsupported value\",\n path,\n );\n if (seen.has(v))\n throw new ContentTransferError(\n \"circular-value\",\n \"Circular values cannot be transferred\",\n path,\n );\n if (\n !Array.isArray(v) &&\n Object.getPrototypeOf(v) !== Object.prototype &&\n Object.getPrototypeOf(v) !== null\n )\n throw new ContentTransferError(\n \"unsupported-value\",\n \"Only plain JSON objects are transferable\",\n path,\n );\n seen.add(v);\n if (Array.isArray(v)) {\n for (let index = 0; index < v.length; index += 1)\n if (!Object.hasOwn(v, index))\n throw new ContentTransferError(\n \"unsupported-value\",\n \"Sparse arrays cannot be transferred\",\n `${path}/${index}`,\n );\n }\n const out: Json = Array.isArray(v)\n ? v.map((x, i) => clone(x, `${path}/${i}`, seen, policy))\n : Object.fromEntries(\n Object.entries(v)\n .filter(([, x]) => !policy.omitUndefinedObjectProperties || x !== undefined)\n .map(([k, x]) => [\n k,\n clone(x, `${path}/${enc(k)}`, seen, policy),\n ]),\n );\n seen.delete(v);\n return out as T;\n}\nconst maxSurfacePointerSegmentLength = 64;\nconst maxSurfacePointerLength = 192;\nconst truncateSurfacePointer = (value: string, maxLength: number) => {\n const characters = Array.from(value);\n return characters.length > maxLength\n ? `${characters.slice(0, maxLength - 1).join(\"\")}…`\n : value;\n};\nfunction displaySurfacePointer(path: string | undefined): string {\n if (!path) return \"/\";\n const safe = path\n .split(\"/\")\n .slice(1)\n .map((segment) => {\n const controlSafe = segment.replace(/[\\u0000-\\u001F\\u007F-\\u009F]/g, (character) =>\n `\\\\u${character.charCodeAt(0).toString(16).padStart(4, \"0\")}`,\n );\n return truncateSurfacePointer(controlSafe, maxSurfacePointerSegmentLength);\n })\n .join(\"/\");\n const pointer = `/${safe}`;\n return truncateSurfacePointer(pointer, maxSurfacePointerLength);\n}\nfunction cloneSurfaceValue(value: unknown, path: string): Json {\n try {\n return normalizeTransferJsonAtPath(value, path, {\n omitUndefinedObjectProperties: true,\n });\n } catch (error) {\n if (error instanceof ContentTransferError)\n throw new ContentTransferError(\n error.code,\n `Surface capture rejected value at ${displaySurfacePointer(error.path)}: ${error.message}`,\n error.path,\n );\n throw error;\n }\n}\n/**\n * Validates arbitrary host data at the transfer boundary and returns a detached\n * JSON value. This deliberately shares the capture validator: undefined,\n * functions, dates, non-finite numbers, and cycles are rejected rather than\n * silently coerced by a JSON stringify round trip.\n */\nfunction normalizeTransferJsonAtPath(\n value: unknown,\n path: string,\n options: NormalizeTransferJsonOptions = {},\n): Json {\n return clone(value as Json, path, new WeakSet<object>(), options);\n}\nexport function normalizeTransferJson(\n value: unknown,\n options: NormalizeTransferJsonOptions = {},\n): Json {\n return normalizeTransferJsonAtPath(value, \"\", options);\n}\nfunction deepFreeze<T>(v: T): T {\n if (ArrayBuffer.isView(v)) return v;\n if (v && typeof v === \"object\" && !Object.isFrozen(v)) {\n Object.freeze(v);\n for (const x of Object.values(v as object)) deepFreeze(x);\n }\n return v;\n}\nfunction at(v: Json, path: string): Json | undefined {\n validatePointer(path);\n if (path === \"\") return v;\n if (!path.startsWith(\"/\"))\n throw new ContentTransferError(\n \"invalid-path\",\n \"Expected RFC 6901 pointer\",\n path,\n );\n let cur: Json | undefined = v;\n for (const part of path.slice(1).split(\"/\").map(dec)) {\n if (Array.isArray(cur)) cur = cur[Number(part)];\n else if (cur && typeof cur === \"object\")\n cur = Object.hasOwn(cur, part) ? cur[part] : undefined;\n else return undefined;\n }\n return cur;\n}\nfunction validatePointer(path: string): void {\n if ((path !== \"\" && !path.startsWith(\"/\")) || /~(?![01])/u.test(path))\n throw new ContentTransferError(\n \"invalid-path\",\n \"Expected an RFC 6901 JSON pointer\",\n path,\n );\n}\n/** Column ids are identities, never an alternative data address. */\nexport function readTransferCell(\n row: Record<string, Json>,\n column: Column,\n): Json | undefined {\n return at(row, column.path);\n}\nfunction without(\n v: Json,\n paths: readonly string[],\n omit: Omission[],\n path = \"\",\n): Json | undefined {\n if (paths.includes(path)) {\n omit.push({ path, reason: \"excluded\" });\n return undefined;\n }\n if (Array.isArray(v))\n return v\n .map((x, i) => without(x, paths, omit, `${path}/${i}`))\n .filter((x): x is Json => x !== undefined);\n if (v && typeof v === \"object\") {\n const out: Record<string, Json> = Object.create(null);\n for (const [k, x] of Object.entries(v)) {\n const y = without(x, paths, omit, `${path}/${enc(k)}`);\n if (y !== undefined) out[k] = y;\n }\n return out;\n }\n return v;\n}\nfunction projectValue(\n v: Json,\n rules: ExportProjection[\"rules\"],\n target: \"payload\" | \"row\",\n omit: Omission[],\n): Json {\n rules.forEach((rule) => validatePointer(rule.path));\n return (\n without(\n clone(v),\n rules\n .filter(\n (r) =>\n r.target === target &&\n (!r.exportable || r.classification !== \"ordinary\"),\n )\n .map((r) => r.path),\n omit,\n ) ?? null\n );\n}\nfunction excludedColumn(column: Column): boolean {\n return (\n column.exportable === false ||\n column.classification === \"secret\" ||\n column.classification === \"credential\"\n );\n}\nfunction rowProjectionExcludesColumnSchema(\n column: Column,\n rules: ExportProjection[\"rules\"],\n): boolean {\n return rules.some(\n (rule) =>\n rule.target === \"row\" &&\n (!rule.exportable || rule.classification !== \"ordinary\") &&\n (rule.path === column.path || column.path.startsWith(`${rule.path}/`)),\n );\n}\n/** Project the schema with the same payload rules that govern column metadata. */\nfunction projectColumnSchema(\n column: Column,\n rules: ExportProjection[\"rules\"],\n omit: Omission[],\n): Column[\"schema\"] | undefined {\n if (column.schema === undefined) return undefined;\n const root = `/columns/${enc(column.id)}/schema`;\n const schemaRules = rules\n .filter((rule) => rule.target === \"payload\" && (rule.path === root || rule.path.startsWith(`${root}/`)))\n .map((rule) => ({ ...rule, path: rule.path.slice(root.length) }));\n const projected = projectValue(column.schema, schemaRules, \"payload\", omit);\n if (schemaRules.some((rule) => rule.path === \"\" && (!rule.exportable || rule.classification !== \"ordinary\")))\n return undefined;\n return projected as Column[\"schema\"];\n}\nfunction normalize(\n p: Payload,\n projection: ExportProjection,\n omit: Omission[],\n): Payload {\n if (p.kind === \"text\" || p.kind === \"markdown\") {\n if (typeof p.text !== \"string\")\n throw new ContentTransferError(\n \"unsupported-value\",\n \"Text payload must contain a string\",\n \"/text\",\n );\n const value = projectValue(p.text, projection.rules, \"payload\", omit);\n const text = typeof value === \"string\" ? value : \"\";\n // A code block keeps its language (downloads take its extension; Markdown fences it).\n return p.kind === \"text\" && typeof p.language === \"string\" && p.language.trim()\n ? { kind: \"text\", text, language: p.language.trim() }\n : { kind: p.kind, text };\n }\n if (p.kind === \"json\")\n return {\n kind: \"json\",\n value: projectValue(p.value, projection.rules, \"payload\", omit),\n };\n if (p.kind === \"registered\")\n return {\n kind: \"registered\",\n format: p.format,\n value: projectValue(p.value, projection.rules, \"payload\", omit),\n };\n const rowsPayload = p as Extract<Payload, { kind: \"rows\" }>;\n if (p.kind !== \"rows\")\n throw new ContentTransferError(\n \"invalid-payload\",\n \"Unsupported payload kind\",\n );\n const sourceColumns = rowsPayload.columns.map((column) => ({ ...column }));\n const columns = sourceColumns\n .filter((column) => !excludedColumn(column))\n .map((column) => {\n // Do not spread source schema back after an explicit schema or row exclusion.\n const { schema: _sourceSchema, ...withoutSchema } = column;\n const schema = rowProjectionExcludesColumnSchema(column, projection.rules)\n ? undefined\n : projectColumnSchema(column, projection.rules, omit);\n return {\n ...withoutSchema,\n ...(schema === undefined ? {} : { schema }),\n };\n });\n resolveTransferRegistry(columns);\n columns.forEach((column) => validatePointer(column.path));\n const rules = [\n ...projection.rules,\n ...sourceColumns\n .filter(excludedColumn)\n .map((c) => ({\n target: \"row\" as const,\n path: c.path,\n exportable: c.exportable ?? true,\n classification: c.classification ?? (\"ordinary\" as const),\n })),\n ];\n return {\n kind: \"rows\",\n columns,\n rows: rowsPayload.rows.map((row, index) => {\n if (!row || typeof row !== \"object\" || Array.isArray(row))\n throw new ContentTransferError(\n \"invalid-row\",\n \"A table row must be a JSON object\",\n `/rows/${index}`,\n );\n const projected = projectValue(row, rules, \"row\", omit);\n return projected === null ? {} : (projected as Record<string, Json>);\n }),\n };\n}\nexport function projectSnapshot(\n raw: RawSnapshot,\n projection: ExportProjection,\n): Snapshot {\n const omissions = raw.omissions.map((o) => ({ ...o }));\n const normalized = normalize(raw.payload, projection, omissions);\n const payload = raw.limits\n ? limitPayload(normalized, raw.limits, omissions)\n : normalized;\n const included =\n payload.kind === \"rows\" ? payload.rows.length : raw.coverage.included;\n return deepFreeze({\n ...raw,\n payload,\n coverage: {\n ...raw.coverage,\n included,\n ...(included < raw.coverage.included\n ? { status: \"partial\" as const, reason: \"Source export limit applied\" }\n : {}),\n },\n omissions,\n sections: raw.sections\n .filter(\n (section) =>\n !projection.rules.some(\n (rule) =>\n rule.target === \"payload\" &&\n (!rule.exportable || rule.classification !== \"ordinary\") &&\n (section.path === rule.path ||\n section.path.startsWith(`${rule.path}/`)),\n ),\n )\n .map((x) => ({ ...x })),\n ...(raw.limits ? { limits: { ...raw.limits } } : {}),\n } as Snapshot);\n}\nconst uid = () =>\n globalThis.crypto?.randomUUID?.() ?? `capture-${Date.now()}-${Math.random()}`;\nexport function directSource(\n payload: Payload,\n options: Partial<\n Pick<\n RawSnapshot,\n | \"id\"\n | \"sourceId\"\n | \"revision\"\n | \"label\"\n | \"coverage\"\n | \"unsavedChanges\"\n | \"sections\"\n | \"limits\"\n > & { projection: ExportProjection }\n > = {},\n): CapturedSource {\n const id = options.id ?? uid();\n return {\n projection: options.projection ?? { rules: [] },\n snapshot: {\n id,\n sourceId: options.sourceId ?? id,\n revision: options.revision ?? uid(),\n capturedAt: new Date().toISOString(),\n label: options.label ?? \"Content\",\n payload,\n coverage: options.coverage ?? {\n status: \"complete\",\n included: payload.kind === \"rows\" ? payload.rows.length : 1,\n total: payload.kind === \"rows\" ? payload.rows.length : 1,\n },\n unsavedChanges: options.unsavedChanges ?? false,\n omissions: [],\n sections: options.sections ?? [],\n ...(options.limits ? { limits: { ...options.limits } } : {}),\n },\n };\n}\nexport async function capture(\n source: Source | Payload,\n signal: AbortSignal,\n scope: TransferScope = \"target\",\n): Promise<{ snapshot: Snapshot; allMatching?: BoundAllMatching }> {\n stop(signal);\n const captured =\n \"kind\" in source\n ? directSource(source)\n : await source.capture({ scope, signal });\n stop(signal);\n const all = captured.allMatching;\n if (\n all &&\n (all.query.sourceId !== captured.snapshot.sourceId ||\n all.query.revision !== captured.snapshot.revision ||\n all.query.order.length === 0)\n )\n throw new ContentTransferError(\n \"query-mismatch\",\n \"All-matching query must be captured with stable ordering\",\n );\n const snapshot = projectSnapshot(captured.snapshot, captured.projection);\n if (!all) return { snapshot };\n return {\n snapshot,\n allMatching: deepFreeze({\n [bound]: true as const,\n source: {\n ...all,\n query: {\n ...all.query,\n query: clone(all.query.query),\n order: all.query.order.map((x) => ({ ...x })),\n },\n },\n projection: { rules: captured.projection.rules.map((x) => ({ ...x })) },\n // Page rows must be normalized against the original declaration so excluded\n // column values are removed before stable IDs or any caller can observe them.\n columns:\n captured.snapshot.payload.kind === \"rows\"\n ? captured.snapshot.payload.columns.map((column) => ({ ...column }))\n : [],\n ...(snapshot.limits ? { limits: { ...snapshot.limits } } : {}),\n }),\n };\n}\nexport function createDraft(s: Snapshot): Draft {\n return deepFreeze({\n snapshotId: s.id,\n sourceId: s.sourceId,\n sourceRevision: s.revision,\n revision: 0,\n payload: normalize(s.payload, { rules: [] }, []),\n omissions: [...s.omissions],\n manuallyEdited: false,\n provenance: [{ kind: \"original\", description: \"Captured source snapshot\" }],\n });\n}\nfunction revise(\n d: Draft,\n p: Payload,\n o: Omission[],\n description: string,\n manual = d.manuallyEdited,\n kind: Draft[\"provenance\"][number][\"kind\"] = \"deterministic\",\n baseOmissions: readonly Omission[] = d.omissions,\n): Draft {\n const { history: _history, ...previous } = d;\n return deepFreeze({\n ...d,\n revision: d.revision + 1,\n payload: normalize(p, { rules: [] }, []),\n omissions: [...baseOmissions, ...o],\n manuallyEdited: manual,\n provenance: [...d.provenance, { kind, description }],\n history: [...(d.history ?? []), previous],\n });\n}\nexport const editDraft = (d: Draft, p: Payload) =>\n revise(d, p, [], \"Manual edit\", true, \"manual\");\n/**\n * A deterministic revision of `d` (the structure trees, PP-06/07): same snapshot, next revision,\n * the given omissions added. `baseOmissions` replaces the draft's own when a step re-projects from\n * the snapshot (a section selection), exactly as `applySectionSelection` does.\n */\nexport const deriveDraft = (\n d: Draft,\n p: Payload,\n o: Omission[],\n description: string,\n baseOmissions: readonly Omission[] = d.omissions,\n) => revise(d, p, o, description, d.manuallyEdited, \"deterministic\", baseOmissions);\nexport const resetDraft = (s: Snapshot) => createDraft(s);\nexport const sourceChanged = (d: Draft, s: Snapshot) =>\n d.sourceId !== s.sourceId || d.sourceRevision !== s.revision;\nexport function undoDraft(d: Draft): Draft {\n const previous = d.history?.at(-1);\n if (!previous) return d;\n return deepFreeze({\n ...previous,\n revision: d.revision + 1,\n history: d.history!.slice(0, -1),\n });\n}\n/** Keep and regenerate are explicit choices; observing fresh data never overwrites edits. */\nexport function refreshDraft(\n draft: Draft,\n snapshot: Snapshot,\n choice: \"keep\" | \"regenerate\",\n): Draft {\n return choice === \"keep\" ? draft : createDraft(snapshot);\n}\nexport function applySectionSelection(\n d: Draft,\n s: Snapshot,\n ids: readonly string[],\n): Draft {\n if (\n d.snapshotId !== s.id ||\n d.sourceId !== s.sourceId ||\n d.sourceRevision !== s.revision\n )\n throw new ContentTransferError(\n \"snapshot-mismatch\",\n \"Section selection requires the draft's captured source snapshot\",\n );\n const selected = new Set(ids),\n omit: Omission[] = [],\n paths = s.sections\n .filter((x) => !selected.has(x.id))\n .map((x) => {\n omit.push({ path: x.path, reason: \"excluded\" });\n return x.path;\n }),\n // Selection is a replacement projection from the snapshot, so only the\n // snapshot's omissions remain authoritative. Retaining draft omissions\n // would preserve stale transforms after the snapshot payload is rebuilt.\n baseOmissions = s.omissions;\n if (s.payload.kind === \"json\")\n return revise(\n d,\n { kind: \"json\", value: without(s.payload.value, paths, []) ?? null },\n omit,\n \"Selected sections\",\n d.manuallyEdited,\n \"deterministic\",\n baseOmissions,\n );\n if (s.payload.kind === \"registered\")\n return revise(\n d,\n {\n kind: \"registered\",\n format: s.payload.format,\n value: without(s.payload.value, paths, []) ?? null,\n },\n omit,\n \"Selected sections\",\n d.manuallyEdited,\n \"deterministic\",\n baseOmissions,\n );\n if (s.payload.kind === \"markdown\" || s.payload.kind === \"text\") {\n // A prose section is a character range of the text: `/text/<start>-<end>`.\n const text = s.payload.text;\n const ranges = paths\n .map((path) => {\n const m = /^\\/text\\/(\\d+)-(\\d+)$/.exec(path);\n const start = m ? Number(m[1]) : NaN,\n end = m ? Number(m[2]) : NaN;\n if (!m || start > end || end > text.length)\n throw new ContentTransferError(\n \"invalid-section\",\n `Section \"${path}\" is not a range of this text. Declare a prose section as /text/<start>-<end> within the text.`,\n );\n return [start, end] as const;\n })\n .sort((a, b) => b[0] - a[0]);\n let kept = text;\n for (const [start, end] of ranges) kept = kept.slice(0, start) + kept.slice(end);\n return revise(\n d,\n { ...s.payload, text: kept },\n omit,\n \"Selected sections\",\n d.manuallyEdited,\n \"deterministic\",\n baseOmissions,\n );\n }\n return revise(\n d,\n s.payload,\n omit,\n \"Selected sections\",\n d.manuallyEdited,\n \"deterministic\",\n baseOmissions,\n );\n}\nexport function transformRows(\n d: Draft,\n o: {\n search?: string;\n sort?: { column: string; direction: \"asc\" | \"desc\" };\n filters?: Readonly<Record<string, string>>;\n /** Positions in this call's input draft rows; unlike ids, no guessed key is used. */\n selectedIndices?: readonly number[];\n selectedIds?: readonly string[];\n idColumn?: string;\n visibleColumns?: readonly string[];\n structuredProjection?: \"full\" | \"visible\";\n top?: number | null;\n },\n): Draft {\n if (d.payload.kind !== \"rows\") return d;\n // Validate before deciding whether truncation is necessary. A negative/NaN\n // top is an invalid request even for an empty result set.\n if (o.top != null) assertLimit(o.top, \"maxRows\");\n const cols = o.visibleColumns\n ? o.visibleColumns\n .map((id) =>\n d.payload.kind === \"rows\"\n ? d.payload.columns.find((c) => c.id === id)\n : undefined,\n )\n .filter((c): c is Column => Boolean(c))\n : d.payload.columns.filter((c) => c.visible);\n const get = readTransferCell;\n let rows = [...d.payload.rows],\n omit: Omission[] = [];\n const before = rows.length;\n if (o.selectedIndices !== undefined) {\n const selected = new Set<number>();\n for (const index of o.selectedIndices) {\n if (!Number.isSafeInteger(index) || index < 0 || index >= d.payload.rows.length)\n throw new ContentTransferError(\n \"invalid-selection-index\",\n \"Selected row indexes must be non-negative positions in the input draft rows\",\n );\n selected.add(index);\n }\n rows = d.payload.rows.filter((_, index) => selected.has(index));\n }\n if (o.selectedIds && o.idColumn) {\n const set = new Set(o.selectedIds);\n const idColumn = d.payload.columns.find((c) => c.id === o.idColumn);\n rows = rows.filter((r) =>\n set.has(String(idColumn ? get(r, idColumn) : r[o.idColumn!])),\n );\n }\n if (o.search) {\n const q = o.search.toLowerCase();\n rows = rows.filter((r) =>\n cols.some((c) =>\n String(get(r, c) ?? \"\")\n .toLowerCase()\n .includes(q),\n ),\n );\n }\n for (const [id, q] of Object.entries(o.filters ?? {})) {\n const c = cols.find((x) => x.id === id);\n if (c)\n rows = rows.filter((r) =>\n String(get(r, c) ?? \"\")\n .toLowerCase()\n .includes(q.toLowerCase()),\n );\n }\n if (rows.length !== before)\n omit.push({\n path: \"/rows\",\n reason: \"filtered\",\n count: before - rows.length,\n });\n if (o.sort) {\n const c = cols.find((x) => x.id === o.sort!.column);\n if (c)\n rows.sort((a, b) => {\n const left = get(a, c);\n const right = get(b, c);\n const compare =\n typeof left === \"number\" && typeof right === \"number\"\n ? left - right\n : String(left ?? \"\").localeCompare(String(right ?? \"\"));\n return compare * (o.sort!.direction === \"asc\" ? 1 : -1);\n });\n }\n if (o.top != null && rows.length > o.top) {\n omit.push({\n path: \"/rows\",\n reason: \"truncated\",\n count: rows.length - o.top,\n });\n rows = rows.slice(0, o.top);\n }\n if (o.structuredProjection === \"visible\") {\n rows = rows.map((r) =>\n Object.fromEntries(cols.map((c) => [c.id, get(r, c) ?? null])),\n );\n const excludedColumns = d.payload.columns.length - cols.length;\n if (excludedColumns > 0)\n omit.push({\n path: \"/columns\",\n reason: \"excluded\",\n count: excludedColumns,\n });\n }\n return revise(\n d,\n {\n kind: \"rows\",\n rows,\n columns: cols.map((c) => ({\n ...c,\n visible: true,\n ...(o.structuredProjection === \"visible\"\n ? { path: `/${enc(c.id)}` }\n : {}),\n })),\n },\n omit,\n \"Prepared row subset\",\n );\n}\nexport function reduceJson(\n draft: Draft,\n options: {\n exclude?: readonly string[];\n maxDepth?: number | null;\n maxStringChars?: number | null;\n maxArrayItems?: number | null;\n },\n): Draft {\n if (draft.payload.kind !== \"json\" && draft.payload.kind !== \"registered\")\n return draft;\n const omissions: Omission[] = [];\n options.exclude?.forEach(validatePointer);\n const filtered =\n without(clone(draft.payload.value), options.exclude ?? [], omissions) ??\n null;\n const payload = limitPayload(\n { ...draft.payload, value: filtered },\n options,\n omissions,\n );\n return revise(draft, payload, omissions, \"Reduced JSON\");\n}\nexport async function collectAllMatching(\n c: BoundAllMatching,\n signal: AbortSignal,\n): Promise<{\n rows: Record<string, Json>[];\n coverage: RawSnapshot[\"coverage\"];\n omissions: Omission[];\n error?: ContentTransferError;\n}> {\n const rows: Record<string, Json>[] = [],\n omissions: Omission[] = [],\n ids = new Set<string>(),\n cursors = new Set<string>();\n let cursor: string | null = null,\n token: string | null | undefined,\n last: RawSnapshot[\"coverage\"] = {\n status: \"unknown\",\n included: 0,\n total: null,\n };\n let certifiedTotal: number | null | undefined;\n const maxRows = c.limits?.maxRows;\n if (maxRows != null) assertLimit(maxRows, \"maxRows\");\n const finishAtLimit = (total: number | null, knownOmitted?: number) => ({\n rows: deepFreeze(rows),\n omissions: deepFreeze([\n ...omissions,\n {\n path: \"/rows\",\n reason: \"truncated\" as const,\n ...(knownOmitted === undefined || knownOmitted <= 0\n ? {}\n : { count: knownOmitted }),\n },\n ]),\n coverage: deepFreeze({\n status: \"partial\" as const,\n included: rows.length,\n total,\n reason: \"Source export limit applied\",\n }),\n });\n try {\n // A zero source cap is still a real cap: do not request a page merely to\n // discover data that the caller is not allowed to receive.\n if (maxRows === 0) return finishAtLimit(null);\n do {\n stop(signal);\n const page = await c.source.page({\n query: c.source.query,\n cursor,\n consistencyToken: token ?? null,\n signal,\n });\n stop(signal);\n if (token === undefined) token = page.consistencyToken;\n else if (page.consistencyToken !== token)\n throw new ContentTransferError(\n \"consistency-token-changed\",\n \"All-matching export changed during collection\",\n );\n if (page.coverage.included !== page.rows.length)\n throw new ContentTransferError(\n \"invalid-page-coverage\",\n \"Page count does not match returned rows\",\n );\n if (\n page.coverage.total !== null &&\n (!Number.isSafeInteger(page.coverage.total) || page.coverage.total < 0)\n )\n throw new ContentTransferError(\n \"invalid-page-coverage\",\n \"Page total must be a non-negative integer or null\",\n );\n if (certifiedTotal === undefined) certifiedTotal = page.coverage.total;\n else if (page.coverage.total !== certifiedTotal)\n throw new ContentTransferError(\n \"inconsistent-total\",\n \"All-matching source changed its reported total during collection\",\n );\n const projected = (\n normalize(\n { kind: \"rows\", rows: page.rows, columns: [...c.columns] },\n c.projection,\n omissions,\n ) as Extract<Payload, { kind: \"rows\" }>\n ).rows;\n let uniqueBeyondLimit = 0;\n for (const row of projected) {\n // The callback is part of the transfer boundary, so it receives only\n // the projected row. Sources requiring identity must declare an\n // exportable opaque ID; raw credential/non-exportable fields never\n // cross this seam merely for de-duplication.\n const id = c.source.getRowId(row);\n if (!id)\n throw new ContentTransferError(\n \"invalid-row-id\",\n \"All-matching source returned empty projected ID\",\n );\n if (!ids.has(id)) {\n ids.add(id);\n if (maxRows == null || rows.length < maxRows)\n rows.push(\n (\n limitPayload(\n { kind: \"rows\", rows: [row], columns: [...c.columns] },\n { ...c.limits, maxRows: null },\n omissions,\n ) as Extract<Payload, { kind: \"rows\" }>\n ).rows[0]!,\n );\n else uniqueBeyondLimit += 1;\n } else omissions.push({ path: `/rows/${enc(id)}`, reason: \"duplicate\" });\n }\n // Check after de-duplication and before the source-cap return: ids also\n // includes unique rows that the cap intentionally did not retain.\n if (page.coverage.total !== null && page.coverage.total < ids.size)\n throw new ContentTransferError(\n \"invalid-page-coverage\",\n \"Page total is smaller than the accumulated unique row count\",\n );\n last = page.coverage;\n if (\n maxRows != null &&\n rows.length >= maxRows &&\n (uniqueBeyondLimit > 0 || page.nextCursor !== null)\n )\n return finishAtLimit(\n page.coverage.total,\n page.nextCursor === null\n ? uniqueBeyondLimit\n : page.coverage.total != null && page.coverage.total > rows.length\n ? page.coverage.total - rows.length\n : undefined,\n );\n cursor = page.nextCursor;\n if (cursor !== null && (!cursor || cursors.has(cursor)))\n throw new ContentTransferError(\n \"non-advancing-cursor\",\n \"All-matching source repeated a cursor\",\n );\n if (cursor) cursors.add(cursor);\n } while (cursor !== null);\n const complete =\n c.source.stableSnapshot === true &&\n last.status === \"complete\" &&\n last.total === rows.length &&\n token !== undefined;\n return {\n rows: deepFreeze(rows),\n omissions: deepFreeze(omissions),\n coverage: deepFreeze(\n complete\n ? { ...last, included: rows.length }\n : {\n ...last,\n status: \"partial\",\n included: rows.length,\n reason:\n last.reason ??\n \"Source did not certify a stable complete snapshot\",\n },\n ),\n };\n } catch (e) {\n const error =\n e instanceof ContentTransferError\n ? e\n : new ContentTransferError(\n signal.aborted ? \"cancelled\" : \"page-failed\",\n e instanceof Error ? e.message : \"Page collection failed\",\n );\n return {\n rows: deepFreeze(rows),\n omissions: deepFreeze(omissions),\n coverage: deepFreeze({\n status: \"partial\",\n included: rows.length,\n total: last.total,\n reason: error.message,\n }),\n error,\n };\n }\n}\n// An EMPTY cell is empty text. `null` is how a row carries \"nothing here\"\n// (hosts normalise a missing cell to JSON null so the row keeps its shape), and\n// until 2026-09-21 it serialized as the word \"null\" in every plain / CSV / TSV\n// / Markdown copy — six \"null\"s across a copied row of a data table.\nconst textValue = (value: Json | undefined): string =>\n value === undefined || value === null\n ? \"\"\n : typeof value === \"string\"\n ? value\n : stringifyJson(value, { style: \"compact\" });\n\n/** Preserve artifact-byte identity even if a consumer mutates a returned view. */\nexport function sealTransferArtifact(artifact: Artifact): Artifact {\n if (!artifact.file)\n return deepFreeze({\n ...artifact,\n omissions: artifact.omissions.map((o) => ({ ...o })),\n });\n const bytes = artifact.file.bytes.slice();\n const file = {\n filename: artifact.file.filename,\n mime: artifact.file.mime,\n get bytes() {\n return bytes.slice();\n },\n };\n return deepFreeze({\n ...artifact,\n file,\n omissions: artifact.omissions.map((o) => ({ ...o })),\n });\n}\n\n/** The columns a text serialization writes: visible, exportable, never secret or credential. */\nexport function exportedColumns(columns: readonly Column[]): Column[] {\n return columns.filter(\n (c) =>\n c.visible &&\n c.exportable !== false &&\n c.classification !== \"secret\" &&\n c.classification !== \"credential\",\n );\n}\n\nexport function serialize(\n draft: Draft,\n format:\n \"plain\" | \"markdown\" | \"json\" | \"compact-json\" | \"csv\" | \"tsv\" | \"html\",\n options: { spreadsheetSafe?: boolean; filename?: string } = {},\n): Artifact {\n const payload = draft.payload;\n const omissions = draft.omissions.map((o) => ({ ...o }));\n let plainText = \"\",\n html: string | undefined;\n let mime = \"text/plain;charset=utf-8\",\n extension = \"txt\";\n const columnCell = (row: Record<string, Json>, column: Column) =>\n textValue(readTransferCell(row, column));\n if ((format === \"csv\" || format === \"tsv\") && payload.kind !== \"rows\") {\n throw new ContentTransferError(\n \"unsupported-format\",\n `${format.toUpperCase()} requires tabular data`,\n );\n }\n if (format === \"json\" || format === \"compact-json\") {\n const value =\n payload.kind === \"rows\"\n ? payload.rows\n : payload.kind === \"text\" || payload.kind === \"markdown\"\n ? payload.text\n : payload.value;\n plainText = stringifyJson(value, {\n style: format === \"json\" ? \"pretty\" : \"compact\",\n });\n mime = \"application/json;charset=utf-8\";\n extension = \"json\";\n } else if (payload.kind === \"rows\") {\n const columns = exportedColumns(payload.columns);\n if (format === \"markdown\") {\n const escapeCell = (value: string) =>\n escapeHtml(value)\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\\|/g, \"\\\\|\")\n .replace(/\\r\\n|\\r|\\n/g, \"<br>\");\n plainText = [\n `| ${columns.map((c) => escapeCell(c.label)).join(\" | \")} |`,\n `| ${columns.map(() => \"---\").join(\" | \")} |`,\n ...payload.rows.map(\n (row) =>\n `| ${columns.map((c) => escapeCell(columnCell(row, c))).join(\" | \")} |`,\n ),\n ].join(\"\\n\");\n mime = \"text/markdown;charset=utf-8\";\n extension = \"md\";\n } else {\n const delimiter = format === \"csv\" ? \",\" : \"\\t\";\n const safe = options.spreadsheetSafe ?? format === \"tsv\";\n const encode = (value: string, path: string, isString = true) => {\n let text = value;\n if (safe && isString && /^[\\t\\r\\n ]*[=+\\-@]/.test(text)) {\n text = `'${text}`;\n omissions.push({ path, reason: \"escaped\", count: 1 });\n }\n return text.includes(delimiter) || /[\"\\n\\r]/.test(text)\n ? `\"${text.replace(/\"/g, '\"\"')}\"`\n : text;\n };\n plainText = [\n columns\n .map((c) => encode(c.label, `/columns/${enc(c.id)}`))\n .join(delimiter),\n ...payload.rows.map((row, index) =>\n columns\n .map((c) =>\n encode(\n columnCell(row, c),\n `/rows/${index}${c.path}`,\n typeof readTransferCell(row, c) === \"string\",\n ),\n )\n .join(delimiter),\n ),\n ].join(\"\\n\");\n if (format === \"csv\") {\n mime = \"text/csv;charset=utf-8\";\n extension = \"csv\";\n }\n if (format === \"tsv\") {\n mime = \"text/tab-separated-values;charset=utf-8\";\n extension = \"tsv\";\n }\n }\n if (format === \"html\")\n html = `<table><thead><tr>${columns.map((c) => `<th>${escapeHtml(c.label)}</th>`).join(\"\")}</tr></thead><tbody>${payload.rows.map((row) => `<tr>${columns.map((c) => `<td>${escapeHtml(columnCell(row, c)).replace(/\\r\\n|\\r|\\n/g, \"<br>\")}</td>`).join(\"\")}</tr>`).join(\"\")}</tbody></table>`;\n } else {\n plainText =\n payload.kind === \"text\" || payload.kind === \"markdown\"\n ? payload.text\n : stringifyJson(payload.value, { style: \"pretty\" });\n if (format === \"markdown\") {\n mime = \"text/markdown;charset=utf-8\";\n extension = \"md\";\n }\n if (format === \"html\") html = `<pre>${escapeHtml(plainText)}</pre>`;\n }\n if (format === \"html\") {\n mime = \"text/html;charset=utf-8\";\n extension = \"html\";\n }\n return sealTransferArtifact({\n snapshotId: draft.snapshotId,\n draftRevision: draft.revision,\n format,\n plainText,\n ...(html === undefined ? {} : { html }),\n file: {\n filename: options.filename ?? `export.${extension}`,\n mime,\n bytes: new TextEncoder().encode(\n format === \"html\" ? (html ?? plainText) : plainText,\n ),\n },\n omissions,\n });\n}\nexport function createBrowserTransport(): Transport {\n return {\n async copy(a, signal) {\n const cancelled = () =>\n signal.aborted ? { status: \"cancelled\" as const } : undefined;\n try {\n if (cancelled()) return cancelled()!;\n const c = globalThis.navigator?.clipboard;\n if (!c?.write && !c?.writeText)\n return {\n status: \"error\",\n code: \"clipboard-unavailable\",\n message: \"Clipboard writing is unavailable\",\n retryable: false,\n };\n if (\n a.html &&\n c.write &&\n typeof globalThis.ClipboardItem === \"function\"\n ) {\n try {\n await c.write([\n new ClipboardItem({\n \"text/plain\": new Blob([a.plainText], { type: \"text/plain\" }),\n \"text/html\": new Blob([a.html], { type: \"text/html\" }),\n }),\n ]);\n if (cancelled()) return cancelled()!;\n return {\n status: \"success\",\n delivered: \"clipboard\",\n mimeTypes: [\"text/plain\", \"text/html\"],\n };\n } catch {\n if (cancelled()) return cancelled()!;\n }\n }\n if (c.writeText) {\n await c.writeText(a.plainText);\n if (cancelled()) return cancelled()!;\n return a.html\n ? {\n status: \"degraded\",\n delivered: \"plain-text\",\n reason: \"Rich clipboard write was unavailable or rejected\",\n }\n : {\n status: \"success\",\n delivered: \"clipboard\",\n mimeTypes: [\"text/plain\"],\n };\n }\n return {\n status: \"error\",\n code: \"clipboard-rejected\",\n message: \"Clipboard rejected the write\",\n retryable: true,\n };\n } catch (e) {\n return (\n cancelled() ?? {\n status: \"error\",\n code: \"clipboard-rejected\",\n message:\n e instanceof Error ? e.message : \"Clipboard rejected the write\",\n retryable: true,\n }\n );\n }\n },\n async download(a, signal) {\n if (signal.aborted) return { status: \"cancelled\" };\n if (!a.file || typeof document === \"undefined\" || !URL?.createObjectURL)\n return {\n status: \"error\",\n code: \"download-unavailable\",\n message: \"Download is unavailable\",\n retryable: false,\n };\n let url: string | undefined;\n try {\n url = URL.createObjectURL(\n new Blob([a.file.bytes.slice().buffer as ArrayBuffer], {\n type: a.file.mime,\n }),\n );\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = a.file.filename;\n link.click();\n return signal.aborted\n ? { status: \"cancelled\" }\n : {\n status: \"success\",\n delivered: \"download-started\",\n mimeTypes: [a.file.mime],\n };\n } catch (e) {\n return signal.aborted\n ? { status: \"cancelled\" }\n : {\n status: \"error\",\n code: \"download-failed\",\n message: e instanceof Error ? e.message : \"Download failed\",\n retryable: true,\n };\n } finally {\n if (url) setTimeout(() => URL.revokeObjectURL(url!), 0);\n }\n },\n };\n}\n\nfunction assertLimit(value: number | null | undefined, name: string): void {\n if (value != null && (!Number.isSafeInteger(value) || value < 0)) {\n throw new ContentTransferError(\n \"invalid-limit\",\n `${name} must be a non-negative safe integer or null`,\n );\n }\n}\nexport type TransferPreferenceOverrides = Partial<\n Omit<TransferPreferences, \"limits\">\n> & { limits?: Partial<TransferLimits> };\nconst limitKeys = [\n \"maxRows\",\n \"maxStringChars\",\n \"maxDepth\",\n \"maxArrayItems\",\n \"targetTokens\",\n] as const;\n/** Restrictions intersect: neither a menu nor a provider can relax a source limit. */\nexport function resolveTransferPreferences(\n provider: TransferPreferenceOverrides = {},\n menu: TransferPreferenceOverrides = {},\n sourceLimits: Partial<TransferLimits> = {},\n): TransferPreferences {\n const limits: TransferLimits = {\n maxRows: null,\n maxStringChars: null,\n maxDepth: null,\n maxArrayItems: null,\n targetTokens: null,\n };\n for (const key of limitKeys) {\n const candidates = [\n provider.limits?.[key],\n menu.limits?.[key],\n sourceLimits[key],\n ];\n candidates.forEach((value) => assertLimit(value, key));\n const finite = candidates.filter((value): value is number => value != null);\n limits[key] = finite.length ? Math.min(...finite) : null;\n }\n return deepFreeze({\n initialFormat: menu.initialFormat ?? provider.initialFormat ?? \"plain\",\n initialPreset: menu.initialPreset ?? provider.initialPreset ?? \"full\",\n initialEnvelope: menu.initialEnvelope ?? provider.initialEnvelope ?? \"ai\",\n limits,\n });\n}\n/**\n * True for the objects React uses to represent a component or an element —\n * `forwardRef`, `memo`, `lazy`, portals, elements. They are all tagged with the\n * `$$typeof` symbol, which is exactly the test React uses itself.\n */\nfunction isReactElementType(value: object): boolean {\n const tag = (value as { $$typeof?: unknown }).$$typeof;\n return typeof tag === \"symbol\";\n}\n\nfunction cloneTransferRegistration(\n value: unknown,\n path = \"registration\",\n seen = new WeakSet<object>(),\n): unknown {\n if (value === null || (typeof value !== \"object\" && typeof value !== \"function\"))\n return value;\n // Registered behavior remains callable by identity. Freezing a caller-owned\n // function would mutate it and its attached state.\n if (typeof value === \"function\") return value;\n // A React component or element is registered BEHAVIOUR too — it is rendered,\n // never inspected — and `React.forwardRef` / `React.memo` produce OBJECTS\n // rather than functions. React's development build defines their\n // `displayName` as an ACCESSOR, so walking into one threw \"cannot use\n // accessors\" at `registration/icon/displayName` and took EVERY dashboard page\n // that renders a transfer menu down behind its error boundary (2026-09-14;\n // every AI variant the admin dashboard registers carries a lucide `icon`).\n // Recognised the way React itself recognises them.\n if (isReactElementType(value)) return value;\n if (seen.has(value))\n throw new ContentTransferError(\n \"unsupported-registration-metadata\",\n \"Circular transfer registration metadata is unsupported\",\n path,\n );\n seen.add(value);\n if (Array.isArray(value)) {\n const copy = value.map((item, index) =>\n cloneTransferRegistration(item, `${path}/${index}`, seen),\n );\n seen.delete(value);\n return Object.freeze(copy);\n }\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null)\n throw new ContentTransferError(\n \"unsupported-registration-metadata\",\n \"Transfer registration metadata must use plain objects and arrays\",\n path,\n );\n const copy = Object.create(prototype) as Record<PropertyKey, unknown>;\n for (const key of Reflect.ownKeys(value)) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key)!;\n if (!(\"value\" in descriptor))\n throw new ContentTransferError(\n \"unsupported-registration-metadata\",\n \"Transfer registration metadata cannot use accessors\",\n `${path}/${String(key)}`,\n );\n Object.defineProperty(copy, key, {\n value: cloneTransferRegistration(\n descriptor.value,\n `${path}/${String(key)}`,\n seen,\n ),\n enumerable: descriptor.enumerable === true,\n writable: false,\n configurable: false,\n });\n }\n seen.delete(value);\n return Object.freeze(copy);\n}\n\n/** One stable id per registered format or action; ambiguous overrides fail loudly. */\nexport function resolveTransferRegistry<T extends { id: string }>(\n ...groups: readonly (readonly T[])[]\n): readonly T[] {\n const ids = new Set<string>();\n const result: T[] = [];\n for (const group of groups)\n for (const item of group) {\n const copy = cloneTransferRegistration(item) as T;\n if (!copy.id.trim() || ids.has(copy.id))\n throw new ContentTransferError(\n \"duplicate-registration\",\n `Transfer id ${JSON.stringify(copy.id)} is empty or registered more than once`,\n );\n ids.add(copy.id);\n result.push(copy);\n }\n return Object.freeze(result);\n}\n\nexport type TransferSurfaceDeclaration = {\n surfaceName: string;\n label?: string;\n values: readonly {\n name: string;\n label: string;\n exportable?: boolean;\n classification?: \"ordinary\" | \"secret\" | \"credential\";\n includedByDefault?: boolean;\n }[];\n};\n/** A structural adapter to the actual mounted surface, never a global name lookup. */\nexport function createSurfaceTransferHandle(options: {\n instanceId: string;\n manifest: TransferSurfaceDeclaration;\n isMounted(): boolean;\n getScope(): Record<string, unknown> | Promise<Record<string, unknown>>;\n getRevision?(): string;\n unsavedChanges?(): boolean;\n projection?: ExportProjection;\n limits?: TransferLimits;\n}): SurfaceHandle {\n const declarations = resolveTransferRegistry(\n options.manifest.values.map((value) => ({ ...value, id: value.name })),\n );\n return {\n instanceId: options.instanceId,\n surfaceName: options.manifest.surfaceName,\n isMounted: options.isMounted,\n async capture(signal) {\n stop(signal);\n if (!options.isMounted())\n throw new ContentTransferError(\n \"surface-unmounted\",\n \"The captured surface is no longer mounted\",\n );\n const revisionBefore = options.getRevision?.();\n const scope = await options.getScope();\n stop(signal);\n if (!options.isMounted())\n throw new ContentTransferError(\n \"surface-unmounted\",\n \"The surface closed during capture\",\n );\n if (\n revisionBefore !== undefined &&\n options.getRevision?.() !== revisionBefore\n )\n throw new ContentTransferError(\n \"surface-changed\",\n \"The surface changed during capture; capture again\",\n );\n const value: Record<string, Json> = {};\n for (const declaration of declarations) {\n if (\n Object.hasOwn(scope, declaration.name) &&\n scope[declaration.name] !== undefined\n ) {\n Object.defineProperty(value, declaration.name, {\n value: cloneSurfaceValue(\n scope[declaration.name],\n `/${enc(declaration.name)}`,\n ),\n enumerable: true,\n writable: true,\n configurable: true,\n });\n }\n }\n return directSource(\n { kind: \"json\", value },\n {\n sourceId: options.instanceId,\n revision: revisionBefore ?? uid(),\n label: options.manifest.label ?? options.manifest.surfaceName,\n unsavedChanges: options.unsavedChanges?.() ?? false,\n sections: declarations\n .filter((d) => Object.hasOwn(value, d.name))\n .map((d) => ({\n id: d.name,\n label: d.label,\n path: `/${enc(d.name)}`,\n includedByDefault: d.includedByDefault ?? true,\n })),\n projection: {\n rules: [\n ...(options.projection?.rules ?? []),\n ...declarations.map((d) => ({\n target: \"payload\" as const,\n path: `/${enc(d.name)}`,\n exportable: d.exportable ?? true,\n classification: d.classification ?? (\"ordinary\" as const),\n })),\n ],\n },\n ...(options.limits ? { limits: options.limits } : {}),\n },\n );\n },\n };\n}\n\nfunction limitJson(\n value: Json,\n limits: Partial<TransferLimits>,\n omissions: Omission[],\n path = \"\",\n depth = 0,\n): Json {\n if (limits.maxDepth != null && depth > limits.maxDepth) {\n omissions.push({ path, reason: \"truncated\" });\n return null;\n }\n if (\n typeof value === \"string\" &&\n limits.maxStringChars != null &&\n Array.from(value).length > limits.maxStringChars\n ) {\n const chars = Array.from(value);\n omissions.push({\n path,\n reason: \"truncated\",\n count: chars.length - limits.maxStringChars,\n });\n return chars.slice(0, limits.maxStringChars).join(\"\");\n }\n if (Array.isArray(value)) {\n const count = Math.min(value.length, limits.maxArrayItems ?? value.length);\n if (count < value.length)\n omissions.push({\n path,\n reason: \"truncated\",\n count: value.length - count,\n });\n return value\n .slice(0, count)\n .map((item, index) =>\n limitJson(item, limits, omissions, `${path}/${index}`, depth + 1),\n );\n }\n if (value && typeof value === \"object\")\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [\n key,\n limitJson(item, limits, omissions, `${path}/${enc(key)}`, depth + 1),\n ]),\n );\n return value;\n}\nfunction limitPayload(\n payload: Payload,\n limits: Partial<TransferLimits>,\n omissions: Omission[],\n): Payload {\n limitKeys.forEach((key) => assertLimit(limits[key], key));\n if (payload.kind === \"rows\") {\n const count = Math.min(\n payload.rows.length,\n limits.maxRows ?? payload.rows.length,\n );\n if (count < payload.rows.length)\n omissions.push({\n path: \"/rows\",\n reason: \"truncated\",\n count: payload.rows.length - count,\n });\n return {\n ...payload,\n rows: payload.rows\n .slice(0, count)\n .map(\n (row, index) =>\n limitJson(row, limits, omissions, `/rows/${index}`) as Record<\n string,\n Json\n >,\n ),\n };\n }\n if (payload.kind === \"json\" || payload.kind === \"registered\")\n return { ...payload, value: limitJson(payload.value, limits, omissions) };\n return {\n ...payload,\n text: limitJson(payload.text, limits, omissions) as string,\n };\n}\n/** Applies explicit preparation/source limits with exact omissions; token target is advisory. */\nexport function applyTransferLimits(\n draft: Draft,\n limits: Partial<TransferLimits>,\n): Draft {\n const omissions: Omission[] = [];\n const payload = limitPayload(draft.payload, limits, omissions);\n return omissions.length\n ? revise(draft, payload, omissions, \"Applied preparation limits\")\n : draft;\n}\n","/**\n * `@ai-matrx/alchemy/operate/server` — server-run formats (CONTRACT §2.3 `ServerOperation`, ALC-16).\n *\n * Work the client cannot do — Office generation and conversion, server-rendered letters — is\n * REGISTERED here, never run here: this wave ships the type and the registry only. An operation is\n * offered only when the host binds a `ServerPort` (a missing port = the capability is absent, never\n * a dead item), and every one carries the sentence telling the person the content leaves the device.\n * The dispatch contract (one operation route with receipts and streaming) is CONTRACT R10.\n */\n\nimport { resolveTransferRegistry } from \"./core\";\nimport type { ServerOperation } from \"./contract\";\nimport type { Draft, Payload } from \"./types\";\nimport type { ServerPort } from \"../ports\";\n\nconst REGISTRY = Symbol.for(\"ai-matrx.alchemy.operate.server-operations\");\n\nfunction registry(): ServerOperation[] {\n const g = globalThis as unknown as Record<symbol, ServerOperation[] | undefined>;\n g[REGISTRY] ??= [];\n return g[REGISTRY] as ServerOperation[];\n}\n\n/** Register a server operation. A duplicate id is refused; returns the unregister function. */\nexport function registerServerOperation(operation: ServerOperation): () => void {\n const current = registry();\n resolveTransferRegistry<ServerOperation>(current, [operation]);\n current.push(operation);\n return () => {\n const i = current.indexOf(operation);\n if (i >= 0) current.splice(i, 1);\n };\n}\n\n/** Every registered operation (read-only view). */\nexport function serverOperations(): readonly ServerOperation[] {\n return Object.freeze([...registry()]);\n}\n\n/** The operations to offer for a payload: none at all without a server port. */\nexport function eligibleServerOperations(payload: Payload, server: ServerPort | undefined): readonly ServerOperation[] {\n if (!server) return [];\n return registry().filter((op) => op.supports(payload));\n}\n\nconst LEAVES_DEVICE = \"This is built on the AI Matrx server, so the content is sent there to make the file.\";\n\n/** The Office specs the server's `POST /office/generate` renders (DocumentSpec / PresentationSpec / SpreadsheetSpec). */\nexport const OFFICE_SPEC_FORMATS = [\"office-document-spec\", \"office-presentation-spec\", \"office-spreadsheet-spec\"] as const;\n\n/**\n * Office generation (aidream `/office/generate`: a structured spec → a real .docx / .pptx / .xlsx).\n * Registered by default; offered only when a `ServerPort` is bound.\n */\nexport const OFFICE_GENERATE_OPERATION: ServerOperation = Object.freeze({\n id: \"office.generate\",\n label: \"Office file\",\n runsOn: \"server\" as const,\n statement: LEAVES_DEVICE,\n supports: (payload: Payload) =>\n payload.kind === \"registered\" && (OFFICE_SPEC_FORMATS as readonly string[]).includes(payload.format),\n toInput: (draft: Draft) => (draft.payload.kind === \"registered\" ? { format: draft.payload.format, spec: draft.payload.value } : null),\n});\n\nif (!registry().some((op) => op.id === OFFICE_GENERATE_OPERATION.id)) registry().push(OFFICE_GENERATE_OPERATION);\n\nexport type { ServerOperation };\n"],"mappings":"wpBAAA,yWCAA,uBAA8B,qCAC9B,mBAA2B,qCAuBrB,eAAiB,OAAO,IAAI,yCAAyC,EAC9D,qBAAN,cAAmC,KAAM,CACrC,KACA,KAET,YACE,KACA,QACA,KACA,CACA,MAAM,OAAO,EACb,KAAK,KAAO,KACZ,KAAK,KAAO,KACX,KAAyC,cAAc,EAAI,EAC9D,CAOA,OAAiB,OAAO,WAAW,EAAE,MAAyB,CAC5D,OAAO,OAAO,OAAU,UAAY,QAAU,MAAS,MAAkC,cAAc,IAAM,EAC/G,CACF,EA6vCA,SAAS,mBAAmB,MAAwB,CAElD,OAAO,OADM,MAAiC,UACxB,QACxB,CAEA,SAAS,0BACP,MACA,KAAO,eACP,KAAO,IAAI,QACF,CAcT,GAbI,QAAU,MAAS,OAAO,OAAU,UAAY,OAAO,OAAU,YAIjE,OAAO,OAAU,YASjB,mBAAmB,KAAK,EAAG,OAAO,MACtC,GAAI,KAAK,IAAI,KAAK,EAChB,MAAM,IAAI,qBACR,oCACA,yDACA,IACF,EAEF,GADA,KAAK,IAAI,KAAK,EACV,MAAM,QAAQ,KAAK,EAAG,CACxB,IAAMA,MAAO,MAAM,IAAI,CAAC,KAAM,QAC5B,0BAA0B,KAAM,GAAG,IAAI,IAAI,KAAK,GAAI,IAAI,CAC1D,EACA,YAAK,OAAO,KAAK,EACV,OAAO,OAAOA,KAAI,CAC3B,CACA,IAAM,UAAY,OAAO,eAAe,KAAK,EAC7C,GAAI,YAAc,OAAO,WAAa,YAAc,KAClD,MAAM,IAAI,qBACR,oCACA,mEACA,IACF,EACF,IAAM,KAAO,OAAO,OAAO,SAAS,EACpC,QAAW,OAAO,QAAQ,QAAQ,KAAK,EAAG,CACxC,IAAM,WAAa,OAAO,yBAAyB,MAAO,GAAG,EAC7D,GAAI,EAAE,UAAW,YACf,MAAM,IAAI,qBACR,oCACA,sDACA,GAAG,IAAI,IAAI,OAAO,GAAG,CAAC,EACxB,EACF,OAAO,eAAe,KAAM,IAAK,CAC/B,MAAO,0BACL,WAAW,MACX,GAAG,IAAI,IAAI,OAAO,GAAG,CAAC,GACtB,IACF,EACA,WAAY,WAAW,aAAe,GACtC,SAAU,GACV,aAAc,EAChB,CAAC,CACH,CACA,YAAK,OAAO,KAAK,EACV,OAAO,OAAO,IAAI,CAC3B,CAGO,SAAS,2BACX,OACW,CACd,IAAM,IAAM,IAAI,IACV,OAAc,CAAC,EACrB,QAAW,SAAS,OAClB,QAAW,QAAQ,MAAO,CACxB,IAAM,KAAO,0BAA0B,IAAI,EAC3C,GAAI,CAAC,KAAK,GAAG,KAAK,GAAK,IAAI,IAAI,KAAK,EAAE,EACpC,MAAM,IAAI,qBACR,yBACA,eAAe,KAAK,UAAU,KAAK,EAAE,CAAC,wCACxC,EACF,IAAI,IAAI,KAAK,EAAE,EACf,OAAO,KAAK,IAAI,CAClB,CACF,OAAO,OAAO,OAAO,MAAM,CAC7B,CCr3CA,IAAM,SAAW,OAAO,IAAI,4CAA4C,EAExE,SAAS,UAA8B,CACrC,IAAM,EAAI,WACV,SAAE,QAAQ,IAAM,CAAC,EACV,EAAE,QAAQ,CACnB,CAGO,SAAS,wBAAwB,UAAwC,CAC9E,IAAM,QAAU,SAAS,EACzB,+BAAyC,QAAS,CAAC,SAAS,CAAC,EAC7D,QAAQ,KAAK,SAAS,EACf,IAAM,CACX,IAAM,EAAI,QAAQ,QAAQ,SAAS,EAC/B,GAAK,GAAG,QAAQ,OAAO,EAAG,CAAC,CACjC,CACF,CAGO,SAAS,kBAA+C,CAC7D,OAAO,OAAO,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CACtC,CAGO,SAAS,yBAAyB,QAAkB,OAA4D,CACrH,OAAK,OACE,SAAS,EAAE,OAAQ,IAAO,GAAG,SAAS,OAAO,CAAC,EADjC,CAAC,CAEvB,CAEA,IAAM,cAAgB,uFAGT,oBAAsB,CAAC,uBAAwB,2BAA4B,yBAAyB,EAMpG,0BAA6C,OAAO,OAAO,CACtE,GAAI,kBACJ,MAAO,cACP,OAAQ,SACR,UAAW,cACX,SAAW,SACT,QAAQ,OAAS,cAAiB,oBAA0C,SAAS,QAAQ,MAAM,EACrG,QAAU,OAAkB,MAAM,QAAQ,OAAS,aAAe,CAAE,OAAQ,MAAM,QAAQ,OAAQ,KAAM,MAAM,QAAQ,KAAM,EAAI,IAClI,CAAC,EAEI,SAAS,EAAE,KAAM,IAAO,GAAG,KAAO,0BAA0B,EAAE,GAAG,SAAS,EAAE,KAAK,yBAAyB","names":["copy"]}
|
|
1
|
+
{"version":3,"sources":["../src/operate-server.ts","../src/operate/core.ts","../src/operate/server.ts"],"sourcesContent":["export * from \"./operate/server\";\n","import { stringifyJson } from \"@ai-matrx/kit/json-format\";\nimport { escapeHtml } from \"@ai-matrx/kit/html-escape\";\nimport {\n ContentTransferError,\n cloneTransferJson,\n normalizeTransferJson,\n normalizeTransferJsonAtPath,\n} from \"@ai-matrx/kit/transfer-json\";\nexport {\n ContentTransferError,\n normalizeTransferJson,\n type NormalizeTransferJsonOptions,\n} from \"@ai-matrx/kit/transfer-json\";\nimport type {\n Artifact,\n CapturedAllMatching,\n CapturedSource,\n Column,\n Draft,\n ExportProjection,\n Json,\n Omission,\n Payload,\n RawSnapshot,\n Snapshot,\n Source,\n Transport,\n TransferScope,\n TransferLimits,\n TransferPreferences,\n SurfaceHandle,\n} from \"./types\";\nexport * from \"./types\";\nexport * from \"./agent-payload\";\n\n/** A realm-wide brand (Symbol.for), so a binding made by one loaded copy is recognised by another. */\nconst bound: unique symbol = Symbol.for(\"ai-matrx.alchemy.operate.bound-all-matching\");\nexport type BoundAllMatching = {\n readonly [bound]: true;\n readonly source: CapturedAllMatching;\n readonly projection: ExportProjection;\n readonly columns: readonly Column[];\n readonly limits?: TransferLimits;\n};\nconst stop = (s: AbortSignal) => {\n if (s.aborted) throw new DOMException(\"Cancelled\", \"AbortError\");\n};\nconst enc = (x: string) => x.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\nconst dec = (x: string) => x.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\nconst maxSurfacePointerSegmentLength = 64;\nconst maxSurfacePointerLength = 192;\nconst truncateSurfacePointer = (value: string, maxLength: number) => {\n const characters = Array.from(value);\n return characters.length > maxLength\n ? `${characters.slice(0, maxLength - 1).join(\"\")}…`\n : value;\n};\nfunction displaySurfacePointer(path: string | undefined): string {\n if (!path) return \"/\";\n const safe = path\n .split(\"/\")\n .slice(1)\n .map((segment) => {\n const controlSafe = segment.replace(/[\\u0000-\\u001F\\u007F-\\u009F]/g, (character) =>\n `\\\\u${character.charCodeAt(0).toString(16).padStart(4, \"0\")}`,\n );\n return truncateSurfacePointer(controlSafe, maxSurfacePointerSegmentLength);\n })\n .join(\"/\");\n const pointer = `/${safe}`;\n return truncateSurfacePointer(pointer, maxSurfacePointerLength);\n}\nfunction cloneSurfaceValue(value: unknown, path: string): Json {\n try {\n return normalizeTransferJsonAtPath(value, path, {\n omitUndefinedObjectProperties: true,\n });\n } catch (error) {\n if (error instanceof ContentTransferError)\n throw new ContentTransferError(\n error.code,\n `Surface capture rejected value at ${displaySurfacePointer(error.path)}: ${error.message}`,\n error.path,\n );\n throw error;\n }\n}\nfunction deepFreeze<T>(v: T): T {\n if (ArrayBuffer.isView(v)) return v;\n if (v && typeof v === \"object\" && !Object.isFrozen(v)) {\n Object.freeze(v);\n for (const x of Object.values(v as object)) deepFreeze(x);\n }\n return v;\n}\nfunction at(v: Json, path: string): Json | undefined {\n validatePointer(path);\n if (path === \"\") return v;\n if (!path.startsWith(\"/\"))\n throw new ContentTransferError(\n \"invalid-path\",\n \"Expected RFC 6901 pointer\",\n path,\n );\n let cur: Json | undefined = v;\n for (const part of path.slice(1).split(\"/\").map(dec)) {\n if (Array.isArray(cur)) cur = cur[Number(part)];\n else if (cur && typeof cur === \"object\")\n cur = Object.hasOwn(cur, part) ? cur[part] : undefined;\n else return undefined;\n }\n return cur;\n}\nfunction validatePointer(path: string): void {\n if ((path !== \"\" && !path.startsWith(\"/\")) || /~(?![01])/u.test(path))\n throw new ContentTransferError(\n \"invalid-path\",\n \"Expected an RFC 6901 JSON pointer\",\n path,\n );\n}\n/** Column ids are identities, never an alternative data address. */\nexport function readTransferCell(\n row: Record<string, Json>,\n column: Column,\n): Json | undefined {\n return at(row, column.path);\n}\nfunction without(\n v: Json,\n paths: readonly string[],\n omit: Omission[],\n path = \"\",\n): Json | undefined {\n if (paths.includes(path)) {\n omit.push({ path, reason: \"excluded\" });\n return undefined;\n }\n if (Array.isArray(v))\n return v\n .map((x, i) => without(x, paths, omit, `${path}/${i}`))\n .filter((x): x is Json => x !== undefined);\n if (v && typeof v === \"object\") {\n const out: Record<string, Json> = Object.create(null);\n for (const [k, x] of Object.entries(v)) {\n const y = without(x, paths, omit, `${path}/${enc(k)}`);\n if (y !== undefined) out[k] = y;\n }\n return out;\n }\n return v;\n}\nfunction projectValue(\n v: Json,\n rules: ExportProjection[\"rules\"],\n target: \"payload\" | \"row\",\n omit: Omission[],\n): Json {\n rules.forEach((rule) => validatePointer(rule.path));\n return (\n without(\n cloneTransferJson(v),\n rules\n .filter(\n (r) =>\n r.target === target &&\n (!r.exportable || r.classification !== \"ordinary\"),\n )\n .map((r) => r.path),\n omit,\n ) ?? null\n );\n}\nfunction excludedColumn(column: Column): boolean {\n return (\n column.exportable === false ||\n column.classification === \"secret\" ||\n column.classification === \"credential\"\n );\n}\nfunction rowProjectionExcludesColumnSchema(\n column: Column,\n rules: ExportProjection[\"rules\"],\n): boolean {\n return rules.some(\n (rule) =>\n rule.target === \"row\" &&\n (!rule.exportable || rule.classification !== \"ordinary\") &&\n (rule.path === column.path || column.path.startsWith(`${rule.path}/`)),\n );\n}\n/** Project the schema with the same payload rules that govern column metadata. */\nfunction projectColumnSchema(\n column: Column,\n rules: ExportProjection[\"rules\"],\n omit: Omission[],\n): Column[\"schema\"] | undefined {\n if (column.schema === undefined) return undefined;\n const root = `/columns/${enc(column.id)}/schema`;\n const schemaRules = rules\n .filter((rule) => rule.target === \"payload\" && (rule.path === root || rule.path.startsWith(`${root}/`)))\n .map((rule) => ({ ...rule, path: rule.path.slice(root.length) }));\n const projected = projectValue(column.schema, schemaRules, \"payload\", omit);\n if (schemaRules.some((rule) => rule.path === \"\" && (!rule.exportable || rule.classification !== \"ordinary\")))\n return undefined;\n return projected as Column[\"schema\"];\n}\nfunction normalize(\n p: Payload,\n projection: ExportProjection,\n omit: Omission[],\n): Payload {\n if (p.kind === \"text\" || p.kind === \"markdown\") {\n if (typeof p.text !== \"string\")\n throw new ContentTransferError(\n \"unsupported-value\",\n \"Text payload must contain a string\",\n \"/text\",\n );\n const value = projectValue(p.text, projection.rules, \"payload\", omit);\n const text = typeof value === \"string\" ? value : \"\";\n // A code block keeps its language (downloads take its extension; Markdown fences it).\n return p.kind === \"text\" && typeof p.language === \"string\" && p.language.trim()\n ? { kind: \"text\", text, language: p.language.trim() }\n : { kind: p.kind, text };\n }\n if (p.kind === \"json\")\n return {\n kind: \"json\",\n value: projectValue(p.value, projection.rules, \"payload\", omit),\n };\n if (p.kind === \"registered\")\n return {\n kind: \"registered\",\n format: p.format,\n value: projectValue(p.value, projection.rules, \"payload\", omit),\n };\n const rowsPayload = p as Extract<Payload, { kind: \"rows\" }>;\n if (p.kind !== \"rows\")\n throw new ContentTransferError(\n \"invalid-payload\",\n \"Unsupported payload kind\",\n );\n const sourceColumns = rowsPayload.columns.map((column) => ({ ...column }));\n const columns = sourceColumns\n .filter((column) => !excludedColumn(column))\n .map((column) => {\n // Do not spread source schema back after an explicit schema or row exclusion.\n const { schema: _sourceSchema, ...withoutSchema } = column;\n const schema = rowProjectionExcludesColumnSchema(column, projection.rules)\n ? undefined\n : projectColumnSchema(column, projection.rules, omit);\n return {\n ...withoutSchema,\n ...(schema === undefined ? {} : { schema }),\n };\n });\n resolveTransferRegistry(columns);\n columns.forEach((column) => validatePointer(column.path));\n const rules = [\n ...projection.rules,\n ...sourceColumns\n .filter(excludedColumn)\n .map((c) => ({\n target: \"row\" as const,\n path: c.path,\n exportable: c.exportable ?? true,\n classification: c.classification ?? (\"ordinary\" as const),\n })),\n ];\n return {\n kind: \"rows\",\n columns,\n rows: rowsPayload.rows.map((row, index) => {\n if (!row || typeof row !== \"object\" || Array.isArray(row))\n throw new ContentTransferError(\n \"invalid-row\",\n \"A table row must be a JSON object\",\n `/rows/${index}`,\n );\n const projected = projectValue(row, rules, \"row\", omit);\n return projected === null ? {} : (projected as Record<string, Json>);\n }),\n };\n}\nexport function projectSnapshot(\n raw: RawSnapshot,\n projection: ExportProjection,\n): Snapshot {\n const omissions = raw.omissions.map((o) => ({ ...o }));\n const normalized = normalize(raw.payload, projection, omissions);\n const payload = raw.limits\n ? limitPayload(normalized, raw.limits, omissions)\n : normalized;\n const included =\n payload.kind === \"rows\" ? payload.rows.length : raw.coverage.included;\n return deepFreeze({\n ...raw,\n payload,\n coverage: {\n ...raw.coverage,\n included,\n ...(included < raw.coverage.included\n ? { status: \"partial\" as const, reason: \"Source export limit applied\" }\n : {}),\n },\n omissions,\n sections: raw.sections\n .filter(\n (section) =>\n !projection.rules.some(\n (rule) =>\n rule.target === \"payload\" &&\n (!rule.exportable || rule.classification !== \"ordinary\") &&\n (section.path === rule.path ||\n section.path.startsWith(`${rule.path}/`)),\n ),\n )\n .map((x) => ({ ...x })),\n ...(raw.limits ? { limits: { ...raw.limits } } : {}),\n } as Snapshot);\n}\nconst uid = () =>\n globalThis.crypto?.randomUUID?.() ?? `capture-${Date.now()}-${Math.random()}`;\nexport function directSource(\n payload: Payload,\n options: Partial<\n Pick<\n RawSnapshot,\n | \"id\"\n | \"sourceId\"\n | \"revision\"\n | \"label\"\n | \"coverage\"\n | \"unsavedChanges\"\n | \"sections\"\n | \"limits\"\n > & { projection: ExportProjection }\n > = {},\n): CapturedSource {\n const id = options.id ?? uid();\n return {\n projection: options.projection ?? { rules: [] },\n snapshot: {\n id,\n sourceId: options.sourceId ?? id,\n revision: options.revision ?? uid(),\n capturedAt: new Date().toISOString(),\n label: options.label ?? \"Content\",\n payload,\n coverage: options.coverage ?? {\n status: \"complete\",\n included: payload.kind === \"rows\" ? payload.rows.length : 1,\n total: payload.kind === \"rows\" ? payload.rows.length : 1,\n },\n unsavedChanges: options.unsavedChanges ?? false,\n omissions: [],\n sections: options.sections ?? [],\n ...(options.limits ? { limits: { ...options.limits } } : {}),\n },\n };\n}\nexport async function capture(\n source: Source | Payload,\n signal: AbortSignal,\n scope: TransferScope = \"target\",\n): Promise<{ snapshot: Snapshot; allMatching?: BoundAllMatching }> {\n stop(signal);\n const captured =\n \"kind\" in source\n ? directSource(source)\n : await source.capture({ scope, signal });\n stop(signal);\n const all = captured.allMatching;\n if (\n all &&\n (all.query.sourceId !== captured.snapshot.sourceId ||\n all.query.revision !== captured.snapshot.revision ||\n all.query.order.length === 0)\n )\n throw new ContentTransferError(\n \"query-mismatch\",\n \"All-matching query must be captured with stable ordering\",\n );\n const snapshot = projectSnapshot(captured.snapshot, captured.projection);\n if (!all) return { snapshot };\n return {\n snapshot,\n allMatching: deepFreeze({\n [bound]: true as const,\n source: {\n ...all,\n query: {\n ...all.query,\n query: cloneTransferJson(all.query.query),\n order: all.query.order.map((x) => ({ ...x })),\n },\n },\n projection: { rules: captured.projection.rules.map((x) => ({ ...x })) },\n // Page rows must be normalized against the original declaration so excluded\n // column values are removed before stable IDs or any caller can observe them.\n columns:\n captured.snapshot.payload.kind === \"rows\"\n ? captured.snapshot.payload.columns.map((column) => ({ ...column }))\n : [],\n ...(snapshot.limits ? { limits: { ...snapshot.limits } } : {}),\n }),\n };\n}\nexport function createDraft(s: Snapshot): Draft {\n return deepFreeze({\n snapshotId: s.id,\n sourceId: s.sourceId,\n sourceRevision: s.revision,\n revision: 0,\n payload: normalize(s.payload, { rules: [] }, []),\n omissions: [...s.omissions],\n manuallyEdited: false,\n provenance: [{ kind: \"original\", description: \"Captured source snapshot\" }],\n });\n}\nfunction revise(\n d: Draft,\n p: Payload,\n o: Omission[],\n description: string,\n manual = d.manuallyEdited,\n kind: Draft[\"provenance\"][number][\"kind\"] = \"deterministic\",\n baseOmissions: readonly Omission[] = d.omissions,\n): Draft {\n const { history: _history, ...previous } = d;\n return deepFreeze({\n ...d,\n revision: d.revision + 1,\n payload: normalize(p, { rules: [] }, []),\n omissions: [...baseOmissions, ...o],\n manuallyEdited: manual,\n provenance: [...d.provenance, { kind, description }],\n history: [...(d.history ?? []), previous],\n });\n}\nexport const editDraft = (d: Draft, p: Payload) =>\n revise(d, p, [], \"Manual edit\", true, \"manual\");\n/**\n * A deterministic revision of `d` (the structure trees, PP-06/07): same snapshot, next revision,\n * the given omissions added. `baseOmissions` replaces the draft's own when a step re-projects from\n * the snapshot (a section selection), exactly as `applySectionSelection` does.\n */\nexport const deriveDraft = (\n d: Draft,\n p: Payload,\n o: Omission[],\n description: string,\n baseOmissions: readonly Omission[] = d.omissions,\n) => revise(d, p, o, description, d.manuallyEdited, \"deterministic\", baseOmissions);\nexport const resetDraft = (s: Snapshot) => createDraft(s);\nexport const sourceChanged = (d: Draft, s: Snapshot) =>\n d.sourceId !== s.sourceId || d.sourceRevision !== s.revision;\nexport function undoDraft(d: Draft): Draft {\n const previous = d.history?.at(-1);\n if (!previous) return d;\n return deepFreeze({\n ...previous,\n revision: d.revision + 1,\n history: d.history!.slice(0, -1),\n });\n}\n/** Keep and regenerate are explicit choices; observing fresh data never overwrites edits. */\nexport function refreshDraft(\n draft: Draft,\n snapshot: Snapshot,\n choice: \"keep\" | \"regenerate\",\n): Draft {\n return choice === \"keep\" ? draft : createDraft(snapshot);\n}\nexport function applySectionSelection(\n d: Draft,\n s: Snapshot,\n ids: readonly string[],\n): Draft {\n if (\n d.snapshotId !== s.id ||\n d.sourceId !== s.sourceId ||\n d.sourceRevision !== s.revision\n )\n throw new ContentTransferError(\n \"snapshot-mismatch\",\n \"Section selection requires the draft's captured source snapshot\",\n );\n const selected = new Set(ids),\n omit: Omission[] = [],\n paths = s.sections\n .filter((x) => !selected.has(x.id))\n .map((x) => {\n omit.push({ path: x.path, reason: \"excluded\" });\n return x.path;\n }),\n // Selection is a replacement projection from the snapshot, so only the\n // snapshot's omissions remain authoritative. Retaining draft omissions\n // would preserve stale transforms after the snapshot payload is rebuilt.\n baseOmissions = s.omissions;\n if (s.payload.kind === \"json\")\n return revise(\n d,\n { kind: \"json\", value: without(s.payload.value, paths, []) ?? null },\n omit,\n \"Selected sections\",\n d.manuallyEdited,\n \"deterministic\",\n baseOmissions,\n );\n if (s.payload.kind === \"registered\")\n return revise(\n d,\n {\n kind: \"registered\",\n format: s.payload.format,\n value: without(s.payload.value, paths, []) ?? null,\n },\n omit,\n \"Selected sections\",\n d.manuallyEdited,\n \"deterministic\",\n baseOmissions,\n );\n if (s.payload.kind === \"markdown\" || s.payload.kind === \"text\") {\n // A prose section is a character range of the text: `/text/<start>-<end>`.\n const text = s.payload.text;\n const ranges = paths\n .map((path) => {\n const m = /^\\/text\\/(\\d+)-(\\d+)$/.exec(path);\n const start = m ? Number(m[1]) : NaN,\n end = m ? Number(m[2]) : NaN;\n if (!m || start > end || end > text.length)\n throw new ContentTransferError(\n \"invalid-section\",\n `Section \"${path}\" is not a range of this text. Declare a prose section as /text/<start>-<end> within the text.`,\n );\n return [start, end] as const;\n })\n .sort((a, b) => b[0] - a[0]);\n let kept = text;\n for (const [start, end] of ranges) kept = kept.slice(0, start) + kept.slice(end);\n return revise(\n d,\n { ...s.payload, text: kept },\n omit,\n \"Selected sections\",\n d.manuallyEdited,\n \"deterministic\",\n baseOmissions,\n );\n }\n return revise(\n d,\n s.payload,\n omit,\n \"Selected sections\",\n d.manuallyEdited,\n \"deterministic\",\n baseOmissions,\n );\n}\nexport function transformRows(\n d: Draft,\n o: {\n search?: string;\n sort?: { column: string; direction: \"asc\" | \"desc\" };\n filters?: Readonly<Record<string, string>>;\n /** Positions in this call's input draft rows; unlike ids, no guessed key is used. */\n selectedIndices?: readonly number[];\n selectedIds?: readonly string[];\n idColumn?: string;\n visibleColumns?: readonly string[];\n structuredProjection?: \"full\" | \"visible\";\n top?: number | null;\n },\n): Draft {\n if (d.payload.kind !== \"rows\") return d;\n // Validate before deciding whether truncation is necessary. A negative/NaN\n // top is an invalid request even for an empty result set.\n if (o.top != null) assertLimit(o.top, \"maxRows\");\n const cols = o.visibleColumns\n ? o.visibleColumns\n .map((id) =>\n d.payload.kind === \"rows\"\n ? d.payload.columns.find((c) => c.id === id)\n : undefined,\n )\n .filter((c): c is Column => Boolean(c))\n : d.payload.columns.filter((c) => c.visible);\n const get = readTransferCell;\n let rows = [...d.payload.rows],\n omit: Omission[] = [];\n const before = rows.length;\n if (o.selectedIndices !== undefined) {\n const selected = new Set<number>();\n for (const index of o.selectedIndices) {\n if (!Number.isSafeInteger(index) || index < 0 || index >= d.payload.rows.length)\n throw new ContentTransferError(\n \"invalid-selection-index\",\n \"Selected row indexes must be non-negative positions in the input draft rows\",\n );\n selected.add(index);\n }\n rows = d.payload.rows.filter((_, index) => selected.has(index));\n }\n if (o.selectedIds && o.idColumn) {\n const set = new Set(o.selectedIds);\n const idColumn = d.payload.columns.find((c) => c.id === o.idColumn);\n rows = rows.filter((r) =>\n set.has(String(idColumn ? get(r, idColumn) : r[o.idColumn!])),\n );\n }\n if (o.search) {\n const q = o.search.toLowerCase();\n rows = rows.filter((r) =>\n cols.some((c) =>\n String(get(r, c) ?? \"\")\n .toLowerCase()\n .includes(q),\n ),\n );\n }\n for (const [id, q] of Object.entries(o.filters ?? {})) {\n const c = cols.find((x) => x.id === id);\n if (c)\n rows = rows.filter((r) =>\n String(get(r, c) ?? \"\")\n .toLowerCase()\n .includes(q.toLowerCase()),\n );\n }\n if (rows.length !== before)\n omit.push({\n path: \"/rows\",\n reason: \"filtered\",\n count: before - rows.length,\n });\n if (o.sort) {\n const c = cols.find((x) => x.id === o.sort!.column);\n if (c)\n rows.sort((a, b) => {\n const left = get(a, c);\n const right = get(b, c);\n const compare =\n typeof left === \"number\" && typeof right === \"number\"\n ? left - right\n : String(left ?? \"\").localeCompare(String(right ?? \"\"));\n return compare * (o.sort!.direction === \"asc\" ? 1 : -1);\n });\n }\n if (o.top != null && rows.length > o.top) {\n omit.push({\n path: \"/rows\",\n reason: \"truncated\",\n count: rows.length - o.top,\n });\n rows = rows.slice(0, o.top);\n }\n if (o.structuredProjection === \"visible\") {\n rows = rows.map((r) =>\n Object.fromEntries(cols.map((c) => [c.id, get(r, c) ?? null])),\n );\n const excludedColumns = d.payload.columns.length - cols.length;\n if (excludedColumns > 0)\n omit.push({\n path: \"/columns\",\n reason: \"excluded\",\n count: excludedColumns,\n });\n }\n return revise(\n d,\n {\n kind: \"rows\",\n rows,\n columns: cols.map((c) => ({\n ...c,\n visible: true,\n ...(o.structuredProjection === \"visible\"\n ? { path: `/${enc(c.id)}` }\n : {}),\n })),\n },\n omit,\n \"Prepared row subset\",\n );\n}\nexport function reduceJson(\n draft: Draft,\n options: {\n exclude?: readonly string[];\n maxDepth?: number | null;\n maxStringChars?: number | null;\n maxArrayItems?: number | null;\n },\n): Draft {\n if (draft.payload.kind !== \"json\" && draft.payload.kind !== \"registered\")\n return draft;\n const omissions: Omission[] = [];\n options.exclude?.forEach(validatePointer);\n const filtered =\n without(cloneTransferJson(draft.payload.value), options.exclude ?? [], omissions) ??\n null;\n const payload = limitPayload(\n { ...draft.payload, value: filtered },\n options,\n omissions,\n );\n return revise(draft, payload, omissions, \"Reduced JSON\");\n}\nexport async function collectAllMatching(\n c: BoundAllMatching,\n signal: AbortSignal,\n): Promise<{\n rows: Record<string, Json>[];\n coverage: RawSnapshot[\"coverage\"];\n omissions: Omission[];\n error?: ContentTransferError;\n}> {\n const rows: Record<string, Json>[] = [],\n omissions: Omission[] = [],\n ids = new Set<string>(),\n cursors = new Set<string>();\n let cursor: string | null = null,\n token: string | null | undefined,\n last: RawSnapshot[\"coverage\"] = {\n status: \"unknown\",\n included: 0,\n total: null,\n };\n let certifiedTotal: number | null | undefined;\n const maxRows = c.limits?.maxRows;\n if (maxRows != null) assertLimit(maxRows, \"maxRows\");\n const finishAtLimit = (total: number | null, knownOmitted?: number) => ({\n rows: deepFreeze(rows),\n omissions: deepFreeze([\n ...omissions,\n {\n path: \"/rows\",\n reason: \"truncated\" as const,\n ...(knownOmitted === undefined || knownOmitted <= 0\n ? {}\n : { count: knownOmitted }),\n },\n ]),\n coverage: deepFreeze({\n status: \"partial\" as const,\n included: rows.length,\n total,\n reason: \"Source export limit applied\",\n }),\n });\n try {\n // A zero source cap is still a real cap: do not request a page merely to\n // discover data that the caller is not allowed to receive.\n if (maxRows === 0) return finishAtLimit(null);\n do {\n stop(signal);\n const page = await c.source.page({\n query: c.source.query,\n cursor,\n consistencyToken: token ?? null,\n signal,\n });\n stop(signal);\n if (token === undefined) token = page.consistencyToken;\n else if (page.consistencyToken !== token)\n throw new ContentTransferError(\n \"consistency-token-changed\",\n \"All-matching export changed during collection\",\n );\n if (page.coverage.included !== page.rows.length)\n throw new ContentTransferError(\n \"invalid-page-coverage\",\n \"Page count does not match returned rows\",\n );\n if (\n page.coverage.total !== null &&\n (!Number.isSafeInteger(page.coverage.total) || page.coverage.total < 0)\n )\n throw new ContentTransferError(\n \"invalid-page-coverage\",\n \"Page total must be a non-negative integer or null\",\n );\n if (certifiedTotal === undefined) certifiedTotal = page.coverage.total;\n else if (page.coverage.total !== certifiedTotal)\n throw new ContentTransferError(\n \"inconsistent-total\",\n \"All-matching source changed its reported total during collection\",\n );\n const projected = (\n normalize(\n { kind: \"rows\", rows: page.rows, columns: [...c.columns] },\n c.projection,\n omissions,\n ) as Extract<Payload, { kind: \"rows\" }>\n ).rows;\n let uniqueBeyondLimit = 0;\n for (const row of projected) {\n // The callback is part of the transfer boundary, so it receives only\n // the projected row. Sources requiring identity must declare an\n // exportable opaque ID; raw credential/non-exportable fields never\n // cross this seam merely for de-duplication.\n const id = c.source.getRowId(row);\n if (!id)\n throw new ContentTransferError(\n \"invalid-row-id\",\n \"All-matching source returned empty projected ID\",\n );\n if (!ids.has(id)) {\n ids.add(id);\n if (maxRows == null || rows.length < maxRows)\n rows.push(\n (\n limitPayload(\n { kind: \"rows\", rows: [row], columns: [...c.columns] },\n { ...c.limits, maxRows: null },\n omissions,\n ) as Extract<Payload, { kind: \"rows\" }>\n ).rows[0]!,\n );\n else uniqueBeyondLimit += 1;\n } else omissions.push({ path: `/rows/${enc(id)}`, reason: \"duplicate\" });\n }\n // Check after de-duplication and before the source-cap return: ids also\n // includes unique rows that the cap intentionally did not retain.\n if (page.coverage.total !== null && page.coverage.total < ids.size)\n throw new ContentTransferError(\n \"invalid-page-coverage\",\n \"Page total is smaller than the accumulated unique row count\",\n );\n last = page.coverage;\n if (\n maxRows != null &&\n rows.length >= maxRows &&\n (uniqueBeyondLimit > 0 || page.nextCursor !== null)\n )\n return finishAtLimit(\n page.coverage.total,\n page.nextCursor === null\n ? uniqueBeyondLimit\n : page.coverage.total != null && page.coverage.total > rows.length\n ? page.coverage.total - rows.length\n : undefined,\n );\n cursor = page.nextCursor;\n if (cursor !== null && (!cursor || cursors.has(cursor)))\n throw new ContentTransferError(\n \"non-advancing-cursor\",\n \"All-matching source repeated a cursor\",\n );\n if (cursor) cursors.add(cursor);\n } while (cursor !== null);\n const complete =\n c.source.stableSnapshot === true &&\n last.status === \"complete\" &&\n last.total === rows.length &&\n token !== undefined;\n return {\n rows: deepFreeze(rows),\n omissions: deepFreeze(omissions),\n coverage: deepFreeze(\n complete\n ? { ...last, included: rows.length }\n : {\n ...last,\n status: \"partial\",\n included: rows.length,\n reason:\n last.reason ??\n \"Source did not certify a stable complete snapshot\",\n },\n ),\n };\n } catch (e) {\n const error =\n e instanceof ContentTransferError\n ? e\n : new ContentTransferError(\n signal.aborted ? \"cancelled\" : \"page-failed\",\n e instanceof Error ? e.message : \"Page collection failed\",\n );\n return {\n rows: deepFreeze(rows),\n omissions: deepFreeze(omissions),\n coverage: deepFreeze({\n status: \"partial\",\n included: rows.length,\n total: last.total,\n reason: error.message,\n }),\n error,\n };\n }\n}\n// An EMPTY cell is empty text. `null` is how a row carries \"nothing here\"\n// (hosts normalise a missing cell to JSON null so the row keeps its shape), and\n// until 2026-09-21 it serialized as the word \"null\" in every plain / CSV / TSV\n// / Markdown copy — six \"null\"s across a copied row of a data table.\nconst textValue = (value: Json | undefined): string =>\n value === undefined || value === null\n ? \"\"\n : typeof value === \"string\"\n ? value\n : stringifyJson(value, { style: \"compact\" });\n\n/** Preserve artifact-byte identity even if a consumer mutates a returned view. */\nexport function sealTransferArtifact(artifact: Artifact): Artifact {\n if (!artifact.file)\n return deepFreeze({\n ...artifact,\n omissions: artifact.omissions.map((o) => ({ ...o })),\n });\n const bytes = artifact.file.bytes.slice();\n const file = {\n filename: artifact.file.filename,\n mime: artifact.file.mime,\n get bytes() {\n return bytes.slice();\n },\n };\n return deepFreeze({\n ...artifact,\n file,\n omissions: artifact.omissions.map((o) => ({ ...o })),\n });\n}\n\n/** The columns a text serialization writes: visible, exportable, never secret or credential. */\nexport function exportedColumns(columns: readonly Column[]): Column[] {\n return columns.filter(\n (c) =>\n c.visible &&\n c.exportable !== false &&\n c.classification !== \"secret\" &&\n c.classification !== \"credential\",\n );\n}\n\nexport function serialize(\n draft: Draft,\n format:\n \"plain\" | \"markdown\" | \"json\" | \"compact-json\" | \"csv\" | \"tsv\" | \"html\",\n options: { spreadsheetSafe?: boolean; filename?: string } = {},\n): Artifact {\n const payload = draft.payload;\n const omissions = draft.omissions.map((o) => ({ ...o }));\n let plainText = \"\",\n html: string | undefined;\n let mime = \"text/plain;charset=utf-8\",\n extension = \"txt\";\n const columnCell = (row: Record<string, Json>, column: Column) =>\n textValue(readTransferCell(row, column));\n if ((format === \"csv\" || format === \"tsv\") && payload.kind !== \"rows\") {\n throw new ContentTransferError(\n \"unsupported-format\",\n `${format.toUpperCase()} requires tabular data`,\n );\n }\n if (format === \"json\" || format === \"compact-json\") {\n const value =\n payload.kind === \"rows\"\n ? payload.rows\n : payload.kind === \"text\" || payload.kind === \"markdown\"\n ? payload.text\n : payload.value;\n plainText = stringifyJson(value, {\n style: format === \"json\" ? \"pretty\" : \"compact\",\n });\n mime = \"application/json;charset=utf-8\";\n extension = \"json\";\n } else if (payload.kind === \"rows\") {\n const columns = exportedColumns(payload.columns);\n if (format === \"markdown\") {\n const escapeCell = (value: string) =>\n escapeHtml(value)\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\\|/g, \"\\\\|\")\n .replace(/\\r\\n|\\r|\\n/g, \"<br>\");\n plainText = [\n `| ${columns.map((c) => escapeCell(c.label)).join(\" | \")} |`,\n `| ${columns.map(() => \"---\").join(\" | \")} |`,\n ...payload.rows.map(\n (row) =>\n `| ${columns.map((c) => escapeCell(columnCell(row, c))).join(\" | \")} |`,\n ),\n ].join(\"\\n\");\n mime = \"text/markdown;charset=utf-8\";\n extension = \"md\";\n } else {\n const delimiter = format === \"csv\" ? \",\" : \"\\t\";\n const safe = options.spreadsheetSafe ?? format === \"tsv\";\n const encode = (value: string, path: string, isString = true) => {\n let text = value;\n if (safe && isString && /^[\\t\\r\\n ]*[=+\\-@]/.test(text)) {\n text = `'${text}`;\n omissions.push({ path, reason: \"escaped\", count: 1 });\n }\n return text.includes(delimiter) || /[\"\\n\\r]/.test(text)\n ? `\"${text.replace(/\"/g, '\"\"')}\"`\n : text;\n };\n plainText = [\n columns\n .map((c) => encode(c.label, `/columns/${enc(c.id)}`))\n .join(delimiter),\n ...payload.rows.map((row, index) =>\n columns\n .map((c) =>\n encode(\n columnCell(row, c),\n `/rows/${index}${c.path}`,\n typeof readTransferCell(row, c) === \"string\",\n ),\n )\n .join(delimiter),\n ),\n ].join(\"\\n\");\n if (format === \"csv\") {\n mime = \"text/csv;charset=utf-8\";\n extension = \"csv\";\n }\n if (format === \"tsv\") {\n mime = \"text/tab-separated-values;charset=utf-8\";\n extension = \"tsv\";\n }\n }\n if (format === \"html\")\n html = `<table><thead><tr>${columns.map((c) => `<th>${escapeHtml(c.label)}</th>`).join(\"\")}</tr></thead><tbody>${payload.rows.map((row) => `<tr>${columns.map((c) => `<td>${escapeHtml(columnCell(row, c)).replace(/\\r\\n|\\r|\\n/g, \"<br>\")}</td>`).join(\"\")}</tr>`).join(\"\")}</tbody></table>`;\n } else {\n plainText =\n payload.kind === \"text\" || payload.kind === \"markdown\"\n ? payload.text\n : stringifyJson(payload.value, { style: \"pretty\" });\n if (format === \"markdown\") {\n mime = \"text/markdown;charset=utf-8\";\n extension = \"md\";\n }\n if (format === \"html\") html = `<pre>${escapeHtml(plainText)}</pre>`;\n }\n if (format === \"html\") {\n mime = \"text/html;charset=utf-8\";\n extension = \"html\";\n }\n return sealTransferArtifact({\n snapshotId: draft.snapshotId,\n draftRevision: draft.revision,\n format,\n plainText,\n ...(html === undefined ? {} : { html }),\n file: {\n filename: options.filename ?? `export.${extension}`,\n mime,\n bytes: new TextEncoder().encode(\n format === \"html\" ? (html ?? plainText) : plainText,\n ),\n },\n omissions,\n });\n}\nexport function createBrowserTransport(): Transport {\n return {\n async copy(a, signal) {\n const cancelled = () =>\n signal.aborted ? { status: \"cancelled\" as const } : undefined;\n try {\n if (cancelled()) return cancelled()!;\n const c = globalThis.navigator?.clipboard;\n if (!c?.write && !c?.writeText)\n return {\n status: \"error\",\n code: \"clipboard-unavailable\",\n message: \"Clipboard writing is unavailable\",\n retryable: false,\n };\n if (\n a.html &&\n c.write &&\n typeof globalThis.ClipboardItem === \"function\"\n ) {\n try {\n await c.write([\n new ClipboardItem({\n \"text/plain\": new Blob([a.plainText], { type: \"text/plain\" }),\n \"text/html\": new Blob([a.html], { type: \"text/html\" }),\n }),\n ]);\n if (cancelled()) return cancelled()!;\n return {\n status: \"success\",\n delivered: \"clipboard\",\n mimeTypes: [\"text/plain\", \"text/html\"],\n };\n } catch {\n if (cancelled()) return cancelled()!;\n }\n }\n if (c.writeText) {\n await c.writeText(a.plainText);\n if (cancelled()) return cancelled()!;\n return a.html\n ? {\n status: \"degraded\",\n delivered: \"plain-text\",\n reason: \"Rich clipboard write was unavailable or rejected\",\n }\n : {\n status: \"success\",\n delivered: \"clipboard\",\n mimeTypes: [\"text/plain\"],\n };\n }\n return {\n status: \"error\",\n code: \"clipboard-rejected\",\n message: \"Clipboard rejected the write\",\n retryable: true,\n };\n } catch (e) {\n return (\n cancelled() ?? {\n status: \"error\",\n code: \"clipboard-rejected\",\n message:\n e instanceof Error ? e.message : \"Clipboard rejected the write\",\n retryable: true,\n }\n );\n }\n },\n async download(a, signal) {\n if (signal.aborted) return { status: \"cancelled\" };\n if (!a.file || typeof document === \"undefined\" || !URL?.createObjectURL)\n return {\n status: \"error\",\n code: \"download-unavailable\",\n message: \"Download is unavailable\",\n retryable: false,\n };\n let url: string | undefined;\n try {\n url = URL.createObjectURL(\n new Blob([a.file.bytes.slice().buffer as ArrayBuffer], {\n type: a.file.mime,\n }),\n );\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = a.file.filename;\n link.click();\n return signal.aborted\n ? { status: \"cancelled\" }\n : {\n status: \"success\",\n delivered: \"download-started\",\n mimeTypes: [a.file.mime],\n };\n } catch (e) {\n return signal.aborted\n ? { status: \"cancelled\" }\n : {\n status: \"error\",\n code: \"download-failed\",\n message: e instanceof Error ? e.message : \"Download failed\",\n retryable: true,\n };\n } finally {\n if (url) setTimeout(() => URL.revokeObjectURL(url!), 0);\n }\n },\n };\n}\n\nfunction assertLimit(value: number | null | undefined, name: string): void {\n if (value != null && (!Number.isSafeInteger(value) || value < 0)) {\n throw new ContentTransferError(\n \"invalid-limit\",\n `${name} must be a non-negative safe integer or null`,\n );\n }\n}\nexport type TransferPreferenceOverrides = Partial<\n Omit<TransferPreferences, \"limits\">\n> & { limits?: Partial<TransferLimits> };\nconst limitKeys = [\n \"maxRows\",\n \"maxStringChars\",\n \"maxDepth\",\n \"maxArrayItems\",\n \"targetTokens\",\n] as const;\n/** Restrictions intersect: neither a menu nor a provider can relax a source limit. */\nexport function resolveTransferPreferences(\n provider: TransferPreferenceOverrides = {},\n menu: TransferPreferenceOverrides = {},\n sourceLimits: Partial<TransferLimits> = {},\n): TransferPreferences {\n const limits: TransferLimits = {\n maxRows: null,\n maxStringChars: null,\n maxDepth: null,\n maxArrayItems: null,\n targetTokens: null,\n };\n for (const key of limitKeys) {\n const candidates = [\n provider.limits?.[key],\n menu.limits?.[key],\n sourceLimits[key],\n ];\n candidates.forEach((value) => assertLimit(value, key));\n const finite = candidates.filter((value): value is number => value != null);\n limits[key] = finite.length ? Math.min(...finite) : null;\n }\n return deepFreeze({\n initialFormat: menu.initialFormat ?? provider.initialFormat ?? \"plain\",\n initialPreset: menu.initialPreset ?? provider.initialPreset ?? \"full\",\n initialEnvelope: menu.initialEnvelope ?? provider.initialEnvelope ?? \"ai\",\n limits,\n });\n}\n/**\n * True for the objects React uses to represent a component or an element —\n * `forwardRef`, `memo`, `lazy`, portals, elements. They are all tagged with the\n * `$$typeof` symbol, which is exactly the test React uses itself.\n */\nfunction isReactElementType(value: object): boolean {\n const tag = (value as { $$typeof?: unknown }).$$typeof;\n return typeof tag === \"symbol\";\n}\n\nfunction cloneTransferRegistration(\n value: unknown,\n path = \"registration\",\n seen = new WeakSet<object>(),\n): unknown {\n if (value === null || (typeof value !== \"object\" && typeof value !== \"function\"))\n return value;\n // Registered behavior remains callable by identity. Freezing a caller-owned\n // function would mutate it and its attached state.\n if (typeof value === \"function\") return value;\n // A React component or element is registered BEHAVIOUR too — it is rendered,\n // never inspected — and `React.forwardRef` / `React.memo` produce OBJECTS\n // rather than functions. React's development build defines their\n // `displayName` as an ACCESSOR, so walking into one threw \"cannot use\n // accessors\" at `registration/icon/displayName` and took EVERY dashboard page\n // that renders a transfer menu down behind its error boundary (2026-09-14;\n // every AI variant the admin dashboard registers carries a lucide `icon`).\n // Recognised the way React itself recognises them.\n if (isReactElementType(value)) return value;\n if (seen.has(value))\n throw new ContentTransferError(\n \"unsupported-registration-metadata\",\n \"Circular transfer registration metadata is unsupported\",\n path,\n );\n seen.add(value);\n if (Array.isArray(value)) {\n const copy = value.map((item, index) =>\n cloneTransferRegistration(item, `${path}/${index}`, seen),\n );\n seen.delete(value);\n return Object.freeze(copy);\n }\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null)\n throw new ContentTransferError(\n \"unsupported-registration-metadata\",\n \"Transfer registration metadata must use plain objects and arrays\",\n path,\n );\n const copy = Object.create(prototype) as Record<PropertyKey, unknown>;\n for (const key of Reflect.ownKeys(value)) {\n const descriptor = Object.getOwnPropertyDescriptor(value, key)!;\n if (!(\"value\" in descriptor))\n throw new ContentTransferError(\n \"unsupported-registration-metadata\",\n \"Transfer registration metadata cannot use accessors\",\n `${path}/${String(key)}`,\n );\n Object.defineProperty(copy, key, {\n value: cloneTransferRegistration(\n descriptor.value,\n `${path}/${String(key)}`,\n seen,\n ),\n enumerable: descriptor.enumerable === true,\n writable: false,\n configurable: false,\n });\n }\n seen.delete(value);\n return Object.freeze(copy);\n}\n\n/** One stable id per registered format or action; ambiguous overrides fail loudly. */\nexport function resolveTransferRegistry<T extends { id: string }>(\n ...groups: readonly (readonly T[])[]\n): readonly T[] {\n const ids = new Set<string>();\n const result: T[] = [];\n for (const group of groups)\n for (const item of group) {\n const copy = cloneTransferRegistration(item) as T;\n if (!copy.id.trim() || ids.has(copy.id))\n throw new ContentTransferError(\n \"duplicate-registration\",\n `Transfer id ${JSON.stringify(copy.id)} is empty or registered more than once`,\n );\n ids.add(copy.id);\n result.push(copy);\n }\n return Object.freeze(result);\n}\n\nexport type TransferSurfaceDeclaration = {\n surfaceName: string;\n label?: string;\n values: readonly {\n name: string;\n label: string;\n exportable?: boolean;\n classification?: \"ordinary\" | \"secret\" | \"credential\";\n includedByDefault?: boolean;\n }[];\n};\n/** A structural adapter to the actual mounted surface, never a global name lookup. */\nexport function createSurfaceTransferHandle(options: {\n instanceId: string;\n manifest: TransferSurfaceDeclaration;\n isMounted(): boolean;\n getScope(): Record<string, unknown> | Promise<Record<string, unknown>>;\n getRevision?(): string;\n unsavedChanges?(): boolean;\n projection?: ExportProjection;\n limits?: TransferLimits;\n}): SurfaceHandle {\n const declarations = resolveTransferRegistry(\n options.manifest.values.map((value) => ({ ...value, id: value.name })),\n );\n return {\n instanceId: options.instanceId,\n surfaceName: options.manifest.surfaceName,\n isMounted: options.isMounted,\n async capture(signal) {\n stop(signal);\n if (!options.isMounted())\n throw new ContentTransferError(\n \"surface-unmounted\",\n \"The captured surface is no longer mounted\",\n );\n const revisionBefore = options.getRevision?.();\n const scope = await options.getScope();\n stop(signal);\n if (!options.isMounted())\n throw new ContentTransferError(\n \"surface-unmounted\",\n \"The surface closed during capture\",\n );\n if (\n revisionBefore !== undefined &&\n options.getRevision?.() !== revisionBefore\n )\n throw new ContentTransferError(\n \"surface-changed\",\n \"The surface changed during capture; capture again\",\n );\n const value: Record<string, Json> = {};\n for (const declaration of declarations) {\n if (\n Object.hasOwn(scope, declaration.name) &&\n scope[declaration.name] !== undefined\n ) {\n Object.defineProperty(value, declaration.name, {\n value: cloneSurfaceValue(\n scope[declaration.name],\n `/${enc(declaration.name)}`,\n ),\n enumerable: true,\n writable: true,\n configurable: true,\n });\n }\n }\n return directSource(\n { kind: \"json\", value },\n {\n sourceId: options.instanceId,\n revision: revisionBefore ?? uid(),\n label: options.manifest.label ?? options.manifest.surfaceName,\n unsavedChanges: options.unsavedChanges?.() ?? false,\n sections: declarations\n .filter((d) => Object.hasOwn(value, d.name))\n .map((d) => ({\n id: d.name,\n label: d.label,\n path: `/${enc(d.name)}`,\n includedByDefault: d.includedByDefault ?? true,\n })),\n projection: {\n rules: [\n ...(options.projection?.rules ?? []),\n ...declarations.map((d) => ({\n target: \"payload\" as const,\n path: `/${enc(d.name)}`,\n exportable: d.exportable ?? true,\n classification: d.classification ?? (\"ordinary\" as const),\n })),\n ],\n },\n ...(options.limits ? { limits: options.limits } : {}),\n },\n );\n },\n };\n}\n\nfunction limitJson(\n value: Json,\n limits: Partial<TransferLimits>,\n omissions: Omission[],\n path = \"\",\n depth = 0,\n): Json {\n if (limits.maxDepth != null && depth > limits.maxDepth) {\n omissions.push({ path, reason: \"truncated\" });\n return null;\n }\n if (\n typeof value === \"string\" &&\n limits.maxStringChars != null &&\n Array.from(value).length > limits.maxStringChars\n ) {\n const chars = Array.from(value);\n omissions.push({\n path,\n reason: \"truncated\",\n count: chars.length - limits.maxStringChars,\n });\n return chars.slice(0, limits.maxStringChars).join(\"\");\n }\n if (Array.isArray(value)) {\n const count = Math.min(value.length, limits.maxArrayItems ?? value.length);\n if (count < value.length)\n omissions.push({\n path,\n reason: \"truncated\",\n count: value.length - count,\n });\n return value\n .slice(0, count)\n .map((item, index) =>\n limitJson(item, limits, omissions, `${path}/${index}`, depth + 1),\n );\n }\n if (value && typeof value === \"object\")\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [\n key,\n limitJson(item, limits, omissions, `${path}/${enc(key)}`, depth + 1),\n ]),\n );\n return value;\n}\nfunction limitPayload(\n payload: Payload,\n limits: Partial<TransferLimits>,\n omissions: Omission[],\n): Payload {\n limitKeys.forEach((key) => assertLimit(limits[key], key));\n if (payload.kind === \"rows\") {\n const count = Math.min(\n payload.rows.length,\n limits.maxRows ?? payload.rows.length,\n );\n if (count < payload.rows.length)\n omissions.push({\n path: \"/rows\",\n reason: \"truncated\",\n count: payload.rows.length - count,\n });\n return {\n ...payload,\n rows: payload.rows\n .slice(0, count)\n .map(\n (row, index) =>\n limitJson(row, limits, omissions, `/rows/${index}`) as Record<\n string,\n Json\n >,\n ),\n };\n }\n if (payload.kind === \"json\" || payload.kind === \"registered\")\n return { ...payload, value: limitJson(payload.value, limits, omissions) };\n return {\n ...payload,\n text: limitJson(payload.text, limits, omissions) as string,\n };\n}\n/** Applies explicit preparation/source limits with exact omissions; token target is advisory. */\nexport function applyTransferLimits(\n draft: Draft,\n limits: Partial<TransferLimits>,\n): Draft {\n const omissions: Omission[] = [];\n const payload = limitPayload(draft.payload, limits, omissions);\n return omissions.length\n ? revise(draft, payload, omissions, \"Applied preparation limits\")\n : draft;\n}\n","/**\n * `@ai-matrx/alchemy/operate/server` — server-run formats (CONTRACT §2.3 `ServerOperation`, ALC-16).\n *\n * Work the client cannot do — Office generation and conversion, server-rendered letters — is\n * REGISTERED here, never run here: this wave ships the type and the registry only. An operation is\n * offered only when the host binds a `ServerPort` (a missing port = the capability is absent, never\n * a dead item), and every one carries the sentence telling the person the content leaves the device.\n * The dispatch contract (one operation route with receipts and streaming) is CONTRACT R10.\n */\n\nimport { resolveTransferRegistry } from \"./core\";\nimport type { ServerOperation } from \"./contract\";\nimport type { Draft, Payload } from \"./types\";\nimport type { ServerPort } from \"../ports\";\n\nconst REGISTRY = Symbol.for(\"ai-matrx.alchemy.operate.server-operations\");\n\nfunction registry(): ServerOperation[] {\n const g = globalThis as unknown as Record<symbol, ServerOperation[] | undefined>;\n g[REGISTRY] ??= [];\n return g[REGISTRY] as ServerOperation[];\n}\n\n/** Register a server operation. A duplicate id is refused; returns the unregister function. */\nexport function registerServerOperation(operation: ServerOperation): () => void {\n const current = registry();\n resolveTransferRegistry<ServerOperation>(current, [operation]);\n current.push(operation);\n return () => {\n const i = current.indexOf(operation);\n if (i >= 0) current.splice(i, 1);\n };\n}\n\n/** Every registered operation (read-only view). */\nexport function serverOperations(): readonly ServerOperation[] {\n return Object.freeze([...registry()]);\n}\n\n/** The operations to offer for a payload: none at all without a server port. */\nexport function eligibleServerOperations(payload: Payload, server: ServerPort | undefined): readonly ServerOperation[] {\n if (!server) return [];\n return registry().filter((op) => op.supports(payload));\n}\n\nconst LEAVES_DEVICE = \"This is built on the AI Matrx server, so the content is sent there to make the file.\";\n\n/** The Office specs the server's `POST /office/generate` renders (DocumentSpec / PresentationSpec / SpreadsheetSpec). */\nexport const OFFICE_SPEC_FORMATS = [\"office-document-spec\", \"office-presentation-spec\", \"office-spreadsheet-spec\"] as const;\n\n/**\n * Office generation (aidream `/office/generate`: a structured spec → a real .docx / .pptx / .xlsx).\n * Registered by default; offered only when a `ServerPort` is bound.\n */\nexport const OFFICE_GENERATE_OPERATION: ServerOperation = Object.freeze({\n id: \"office.generate\",\n label: \"Office file\",\n runsOn: \"server\" as const,\n statement: LEAVES_DEVICE,\n supports: (payload: Payload) =>\n payload.kind === \"registered\" && (OFFICE_SPEC_FORMATS as readonly string[]).includes(payload.format),\n toInput: (draft: Draft) => (draft.payload.kind === \"registered\" ? { format: draft.payload.format, spec: draft.payload.value } : null),\n});\n\nif (!registry().some((op) => op.id === OFFICE_GENERATE_OPERATION.id)) registry().push(OFFICE_GENERATE_OPERATION);\n\nexport type { ServerOperation };\n"],"mappings":"wpBAAA,yWCAA,uBAA8B,qCAC9B,mBAA2B,qCAC3B,qBAKO,uCACPA,sBAIO,uCAgsCP,SAAS,mBAAmB,MAAwB,CAElD,OAAO,OADM,MAAiC,UACxB,QACxB,CAEA,SAAS,0BACP,MACA,KAAO,eACP,KAAO,IAAI,QACF,CAcT,GAbI,QAAU,MAAS,OAAO,OAAU,UAAY,OAAO,OAAU,YAIjE,OAAO,OAAU,YASjB,mBAAmB,KAAK,EAAG,OAAO,MACtC,GAAI,KAAK,IAAI,KAAK,EAChB,MAAM,IAAI,0CACR,oCACA,yDACA,IACF,EAEF,GADA,KAAK,IAAI,KAAK,EACV,MAAM,QAAQ,KAAK,EAAG,CACxB,IAAMC,MAAO,MAAM,IAAI,CAAC,KAAM,QAC5B,0BAA0B,KAAM,GAAG,IAAI,IAAI,KAAK,GAAI,IAAI,CAC1D,EACA,YAAK,OAAO,KAAK,EACV,OAAO,OAAOA,KAAI,CAC3B,CACA,IAAM,UAAY,OAAO,eAAe,KAAK,EAC7C,GAAI,YAAc,OAAO,WAAa,YAAc,KAClD,MAAM,IAAI,0CACR,oCACA,mEACA,IACF,EACF,IAAM,KAAO,OAAO,OAAO,SAAS,EACpC,QAAW,OAAO,QAAQ,QAAQ,KAAK,EAAG,CACxC,IAAM,WAAa,OAAO,yBAAyB,MAAO,GAAG,EAC7D,GAAI,EAAE,UAAW,YACf,MAAM,IAAI,0CACR,oCACA,sDACA,GAAG,IAAI,IAAI,OAAO,GAAG,CAAC,EACxB,EACF,OAAO,eAAe,KAAM,IAAK,CAC/B,MAAO,0BACL,WAAW,MACX,GAAG,IAAI,IAAI,OAAO,GAAG,CAAC,GACtB,IACF,EACA,WAAY,WAAW,aAAe,GACtC,SAAU,GACV,aAAc,EAChB,CAAC,CACH,CACA,YAAK,OAAO,KAAK,EACV,OAAO,OAAO,IAAI,CAC3B,CAGO,SAAS,2BACX,OACW,CACd,IAAM,IAAM,IAAI,IACV,OAAc,CAAC,EACrB,QAAW,SAAS,OAClB,QAAW,QAAQ,MAAO,CACxB,IAAM,KAAO,0BAA0B,IAAI,EAC3C,GAAI,CAAC,KAAK,GAAG,KAAK,GAAK,IAAI,IAAI,KAAK,EAAE,EACpC,MAAM,IAAI,0CACR,yBACA,eAAe,KAAK,UAAU,KAAK,EAAE,CAAC,wCACxC,EACF,IAAI,IAAI,KAAK,EAAE,EACf,OAAO,KAAK,IAAI,CAClB,CACF,OAAO,OAAO,OAAO,MAAM,CAC7B,CCpxCA,IAAM,SAAW,OAAO,IAAI,4CAA4C,EAExE,SAAS,UAA8B,CACrC,IAAM,EAAI,WACV,SAAE,QAAQ,IAAM,CAAC,EACV,EAAE,QAAQ,CACnB,CAGO,SAAS,wBAAwB,UAAwC,CAC9E,IAAM,QAAU,SAAS,EACzB,+BAAyC,QAAS,CAAC,SAAS,CAAC,EAC7D,QAAQ,KAAK,SAAS,EACf,IAAM,CACX,IAAM,EAAI,QAAQ,QAAQ,SAAS,EAC/B,GAAK,GAAG,QAAQ,OAAO,EAAG,CAAC,CACjC,CACF,CAGO,SAAS,kBAA+C,CAC7D,OAAO,OAAO,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CACtC,CAGO,SAAS,yBAAyB,QAAkB,OAA4D,CACrH,OAAK,OACE,SAAS,EAAE,OAAQ,IAAO,GAAG,SAAS,OAAO,CAAC,EADjC,CAAC,CAEvB,CAEA,IAAM,cAAgB,uFAGT,oBAAsB,CAAC,uBAAwB,2BAA4B,yBAAyB,EAMpG,0BAA6C,OAAO,OAAO,CACtE,GAAI,kBACJ,MAAO,cACP,OAAQ,SACR,UAAW,cACX,SAAW,SACT,QAAQ,OAAS,cAAiB,oBAA0C,SAAS,QAAQ,MAAM,EACrG,QAAU,OAAkB,MAAM,QAAQ,OAAS,aAAe,CAAE,OAAQ,MAAM,QAAQ,OAAQ,KAAM,MAAM,QAAQ,KAAM,EAAI,IAClI,CAAC,EAEI,SAAS,EAAE,KAAM,IAAO,GAAG,KAAO,0BAA0B,EAAE,GAAG,SAAS,EAAE,KAAK,yBAAyB","names":["import_transfer_json","copy"]}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Payload, Json as Json$1 } from '@ai-matrx/kit/transfer-json';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* `@ai-matrx/alchemy/ports` — the host ports (Matrx Alchemy CONTRACT §2.2).
|
|
3
5
|
*
|
|
@@ -60,8 +62,8 @@
|
|
|
60
62
|
* Types only: this entry has no runtime code.
|
|
61
63
|
*/
|
|
62
64
|
/** JSON as it crosses a port. */
|
|
63
|
-
type Json
|
|
64
|
-
[key: string]: Json
|
|
65
|
+
type Json = null | boolean | number | string | Json[] | {
|
|
66
|
+
[key: string]: Json;
|
|
65
67
|
};
|
|
66
68
|
type ServerResult = {
|
|
67
69
|
status: "bytes";
|
|
@@ -82,54 +84,14 @@ type ServerResult = {
|
|
|
82
84
|
};
|
|
83
85
|
};
|
|
84
86
|
interface ServerPort {
|
|
85
|
-
call(operationId: string, input: Json
|
|
87
|
+
call(operationId: string, input: Json, signal: AbortSignal): Promise<ServerResult>;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
91
|
* Framework-neutral transfer contracts (Alchemy Operate, CONTRACT §2.3). All data crossing this boundary is JSON.
|
|
90
|
-
* Moved
|
|
92
|
+
* Moved in ALC-16; `Json`/`Payload`/`Column` now live in `@ai-matrx/kit/transfer-json`; the format-adapter contract lives in `./contract`.
|
|
91
93
|
*/
|
|
92
|
-
|
|
93
|
-
[key: string]: Json;
|
|
94
|
-
};
|
|
95
|
-
type Payload =
|
|
96
|
-
/** A text payload may name its language (code blocks): downloads take its extension, Markdown fences it. */
|
|
97
|
-
{
|
|
98
|
-
kind: "text";
|
|
99
|
-
text: string;
|
|
100
|
-
language?: string;
|
|
101
|
-
} | {
|
|
102
|
-
kind: "markdown";
|
|
103
|
-
text: string;
|
|
104
|
-
} | {
|
|
105
|
-
kind: "json";
|
|
106
|
-
value: Json;
|
|
107
|
-
} | {
|
|
108
|
-
kind: "rows";
|
|
109
|
-
rows: Record<string, Json>[];
|
|
110
|
-
columns: Column[];
|
|
111
|
-
} | {
|
|
112
|
-
kind: "registered";
|
|
113
|
-
format: string;
|
|
114
|
-
value: Json;
|
|
115
|
-
};
|
|
116
|
-
type Column = {
|
|
117
|
-
id: string;
|
|
118
|
-
label: string;
|
|
119
|
-
path: string;
|
|
120
|
-
visible: boolean;
|
|
121
|
-
exportable?: boolean;
|
|
122
|
-
classification?: "ordinary" | "secret" | "credential";
|
|
123
|
-
/** Declared field shape suitable for a projected table-schema export. */
|
|
124
|
-
schema?: {
|
|
125
|
-
dataType?: string;
|
|
126
|
-
order?: number;
|
|
127
|
-
required?: boolean;
|
|
128
|
-
defaultValue?: Json;
|
|
129
|
-
validation?: Json;
|
|
130
|
-
metadata?: Json;
|
|
131
|
-
};
|
|
132
|
-
};
|
|
94
|
+
|
|
133
95
|
type Omission = {
|
|
134
96
|
path: string;
|
|
135
97
|
reason: "excluded" | "filtered" | "truncated" | "unsupported" | "escaped" | "duplicate";
|
|
@@ -172,7 +134,7 @@ interface ServerOperation {
|
|
|
172
134
|
statement: string;
|
|
173
135
|
supports(payload: Payload): boolean;
|
|
174
136
|
/** Projected draft only; sensitivity already applied. */
|
|
175
|
-
toInput(draft: Draft): Json;
|
|
137
|
+
toInput(draft: Draft): Json$1;
|
|
176
138
|
}
|
|
177
139
|
|
|
178
140
|
/**
|
package/dist/operate-server.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Payload, Json as Json$1 } from '@ai-matrx/kit/transfer-json';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* `@ai-matrx/alchemy/ports` — the host ports (Matrx Alchemy CONTRACT §2.2).
|
|
3
5
|
*
|
|
@@ -60,8 +62,8 @@
|
|
|
60
62
|
* Types only: this entry has no runtime code.
|
|
61
63
|
*/
|
|
62
64
|
/** JSON as it crosses a port. */
|
|
63
|
-
type Json
|
|
64
|
-
[key: string]: Json
|
|
65
|
+
type Json = null | boolean | number | string | Json[] | {
|
|
66
|
+
[key: string]: Json;
|
|
65
67
|
};
|
|
66
68
|
type ServerResult = {
|
|
67
69
|
status: "bytes";
|
|
@@ -82,54 +84,14 @@ type ServerResult = {
|
|
|
82
84
|
};
|
|
83
85
|
};
|
|
84
86
|
interface ServerPort {
|
|
85
|
-
call(operationId: string, input: Json
|
|
87
|
+
call(operationId: string, input: Json, signal: AbortSignal): Promise<ServerResult>;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
91
|
* Framework-neutral transfer contracts (Alchemy Operate, CONTRACT §2.3). All data crossing this boundary is JSON.
|
|
90
|
-
* Moved
|
|
92
|
+
* Moved in ALC-16; `Json`/`Payload`/`Column` now live in `@ai-matrx/kit/transfer-json`; the format-adapter contract lives in `./contract`.
|
|
91
93
|
*/
|
|
92
|
-
|
|
93
|
-
[key: string]: Json;
|
|
94
|
-
};
|
|
95
|
-
type Payload =
|
|
96
|
-
/** A text payload may name its language (code blocks): downloads take its extension, Markdown fences it. */
|
|
97
|
-
{
|
|
98
|
-
kind: "text";
|
|
99
|
-
text: string;
|
|
100
|
-
language?: string;
|
|
101
|
-
} | {
|
|
102
|
-
kind: "markdown";
|
|
103
|
-
text: string;
|
|
104
|
-
} | {
|
|
105
|
-
kind: "json";
|
|
106
|
-
value: Json;
|
|
107
|
-
} | {
|
|
108
|
-
kind: "rows";
|
|
109
|
-
rows: Record<string, Json>[];
|
|
110
|
-
columns: Column[];
|
|
111
|
-
} | {
|
|
112
|
-
kind: "registered";
|
|
113
|
-
format: string;
|
|
114
|
-
value: Json;
|
|
115
|
-
};
|
|
116
|
-
type Column = {
|
|
117
|
-
id: string;
|
|
118
|
-
label: string;
|
|
119
|
-
path: string;
|
|
120
|
-
visible: boolean;
|
|
121
|
-
exportable?: boolean;
|
|
122
|
-
classification?: "ordinary" | "secret" | "credential";
|
|
123
|
-
/** Declared field shape suitable for a projected table-schema export. */
|
|
124
|
-
schema?: {
|
|
125
|
-
dataType?: string;
|
|
126
|
-
order?: number;
|
|
127
|
-
required?: boolean;
|
|
128
|
-
defaultValue?: Json;
|
|
129
|
-
validation?: Json;
|
|
130
|
-
metadata?: Json;
|
|
131
|
-
};
|
|
132
|
-
};
|
|
94
|
+
|
|
133
95
|
type Omission = {
|
|
134
96
|
path: string;
|
|
135
97
|
reason: "excluded" | "filtered" | "truncated" | "unsupported" | "escaped" | "duplicate";
|
|
@@ -172,7 +134,7 @@ interface ServerOperation {
|
|
|
172
134
|
statement: string;
|
|
173
135
|
supports(payload: Payload): boolean;
|
|
174
136
|
/** Projected draft only; sensitivity already applied. */
|
|
175
|
-
toInput(draft: Draft): Json;
|
|
137
|
+
toInput(draft: Draft): Json$1;
|
|
176
138
|
}
|
|
177
139
|
|
|
178
140
|
/**
|
package/dist/operate-server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{stringifyJson}from"@ai-matrx/kit/json-format";import{escapeHtml}from"@ai-matrx/kit/html-escape";
|
|
1
|
+
import{stringifyJson}from"@ai-matrx/kit/json-format";import{escapeHtml}from"@ai-matrx/kit/html-escape";import{ContentTransferError,cloneTransferJson,normalizeTransferJsonAtPath}from"@ai-matrx/kit/transfer-json";import{ContentTransferError as ContentTransferError2,normalizeTransferJson as normalizeTransferJson2}from"@ai-matrx/kit/transfer-json";function isReactElementType(value){return typeof value.$$typeof=="symbol"}function cloneTransferRegistration(value,path="registration",seen=new WeakSet){if(value===null||typeof value!="object"&&typeof value!="function"||typeof value=="function"||isReactElementType(value))return value;if(seen.has(value))throw new ContentTransferError("unsupported-registration-metadata","Circular transfer registration metadata is unsupported",path);if(seen.add(value),Array.isArray(value)){let copy2=value.map((item,index)=>cloneTransferRegistration(item,`${path}/${index}`,seen));return seen.delete(value),Object.freeze(copy2)}let prototype=Object.getPrototypeOf(value);if(prototype!==Object.prototype&&prototype!==null)throw new ContentTransferError("unsupported-registration-metadata","Transfer registration metadata must use plain objects and arrays",path);let copy=Object.create(prototype);for(let key of Reflect.ownKeys(value)){let descriptor=Object.getOwnPropertyDescriptor(value,key);if(!("value"in descriptor))throw new ContentTransferError("unsupported-registration-metadata","Transfer registration metadata cannot use accessors",`${path}/${String(key)}`);Object.defineProperty(copy,key,{value:cloneTransferRegistration(descriptor.value,`${path}/${String(key)}`,seen),enumerable:descriptor.enumerable===!0,writable:!1,configurable:!1})}return seen.delete(value),Object.freeze(copy)}function resolveTransferRegistry(...groups){let ids=new Set,result=[];for(let group of groups)for(let item of group){let copy=cloneTransferRegistration(item);if(!copy.id.trim()||ids.has(copy.id))throw new ContentTransferError("duplicate-registration",`Transfer id ${JSON.stringify(copy.id)} is empty or registered more than once`);ids.add(copy.id),result.push(copy)}return Object.freeze(result)}var REGISTRY=Symbol.for("ai-matrx.alchemy.operate.server-operations");function registry(){let g=globalThis;return g[REGISTRY]??=[],g[REGISTRY]}function registerServerOperation(operation){let current=registry();return resolveTransferRegistry(current,[operation]),current.push(operation),()=>{let i=current.indexOf(operation);i>=0&¤t.splice(i,1)}}function serverOperations(){return Object.freeze([...registry()])}function eligibleServerOperations(payload,server){return server?registry().filter(op=>op.supports(payload)):[]}var LEAVES_DEVICE="This is built on the AI Matrx server, so the content is sent there to make the file.",OFFICE_SPEC_FORMATS=["office-document-spec","office-presentation-spec","office-spreadsheet-spec"],OFFICE_GENERATE_OPERATION=Object.freeze({id:"office.generate",label:"Office file",runsOn:"server",statement:LEAVES_DEVICE,supports:payload=>payload.kind==="registered"&&OFFICE_SPEC_FORMATS.includes(payload.format),toInput:draft=>draft.payload.kind==="registered"?{format:draft.payload.format,spec:draft.payload.value}:null});registry().some(op=>op.id===OFFICE_GENERATE_OPERATION.id)||registry().push(OFFICE_GENERATE_OPERATION);export{OFFICE_GENERATE_OPERATION,OFFICE_SPEC_FORMATS,eligibleServerOperations,registerServerOperation,serverOperations};
|
|
2
2
|
//# sourceMappingURL=operate-server.js.map
|