@exponential_app/engine 0.0.0-stage → 0.1.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.
Files changed (84) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +54 -0
  3. package/README.md +35 -2
  4. package/dist/capabilities.js +29 -0
  5. package/dist/capabilities.js.map +9 -0
  6. package/dist/chunks/index-4ntgtkmb.js +6783 -0
  7. package/dist/chunks/index-4ntgtkmb.js.map +13 -0
  8. package/dist/chunks/index-ans80t68.js +382 -0
  9. package/dist/chunks/index-ans80t68.js.map +9 -0
  10. package/dist/chunks/index-ax4aay30.js +315 -0
  11. package/dist/chunks/index-ax4aay30.js.map +10 -0
  12. package/dist/chunks/index-e49v2qpf.js +2177 -0
  13. package/dist/chunks/index-e49v2qpf.js.map +9 -0
  14. package/dist/chunks/index-m2845dn6.js +72 -0
  15. package/dist/chunks/index-m2845dn6.js.map +10 -0
  16. package/dist/chunks/index-md3hz6r2.js +3015 -0
  17. package/dist/chunks/index-md3hz6r2.js.map +19 -0
  18. package/dist/chunks/index-n0acffvs.js +366 -0
  19. package/dist/chunks/index-n0acffvs.js.map +10 -0
  20. package/dist/chunks/index-nrr8x2kq.js +259 -0
  21. package/dist/chunks/index-nrr8x2kq.js.map +10 -0
  22. package/dist/chunks/index-s3xjr701.js +170 -0
  23. package/dist/chunks/index-s3xjr701.js.map +10 -0
  24. package/dist/chunks/index-t89cvabm.js +2301 -0
  25. package/dist/chunks/index-t89cvabm.js.map +10 -0
  26. package/dist/chunks/index-zw0575tv.js +126 -0
  27. package/dist/chunks/index-zw0575tv.js.map +10 -0
  28. package/dist/consent.js +24 -0
  29. package/dist/consent.js.map +9 -0
  30. package/dist/index.js +526 -0
  31. package/dist/index.js.map +11 -0
  32. package/dist/lifecycle.js +11 -0
  33. package/dist/lifecycle.js.map +9 -0
  34. package/dist/outbox.js +27 -0
  35. package/dist/outbox.js.map +9 -0
  36. package/dist/pdf.js +15 -0
  37. package/dist/pdf.js.map +9 -0
  38. package/dist/providers.js +26 -0
  39. package/dist/providers.js.map +9 -0
  40. package/dist/runner.js +24 -0
  41. package/dist/runner.js.map +9 -0
  42. package/dist/tools.js +38 -0
  43. package/dist/tools.js.map +9 -0
  44. package/dist/types/domain-contract/src/app-records.d.ts +3148 -0
  45. package/dist/types/domain-contract/src/app-share.d.ts +222 -0
  46. package/dist/types/domain-contract/src/apps.d.ts +594 -0
  47. package/dist/types/eengine/fixtures/providers.json +55 -0
  48. package/dist/types/eengine/src/broker.d.ts +80 -0
  49. package/dist/types/eengine/src/capabilities.d.ts +86 -0
  50. package/dist/types/eengine/src/consent.d.ts +50 -0
  51. package/dist/types/eengine/src/index.d.ts +11 -0
  52. package/dist/types/eengine/src/instance.d.ts +122 -0
  53. package/dist/types/eengine/src/lifecycle.d.ts +64 -0
  54. package/dist/types/eengine/src/outbox.d.ts +3 -0
  55. package/dist/types/eengine/src/pdf.d.ts +36 -0
  56. package/dist/types/eengine/src/providers.d.ts +38 -0
  57. package/dist/types/eengine/src/runner.d.ts +82 -0
  58. package/dist/types/eengine/src/stores.d.ts +125 -0
  59. package/dist/types/eengine/src/tools.d.ts +79 -0
  60. package/dist/types/eengine/src/web/index.d.ts +20 -0
  61. package/dist/types/eengine/src/web/mcp.d.ts +27 -0
  62. package/dist/types/eengine/src/web/platform.d.ts +91 -0
  63. package/dist/types/eengine/src/web/stores.d.ts +125 -0
  64. package/dist/types/emcp/fixtures/automations.json +521 -0
  65. package/dist/types/emcp/fixtures/hub.json +498 -0
  66. package/dist/types/emcp/src/access.d.ts +55 -0
  67. package/dist/types/emcp/src/audit.d.ts +22 -0
  68. package/dist/types/emcp/src/automations.d.ts +111 -0
  69. package/dist/types/emcp/src/exposes.d.ts +242 -0
  70. package/dist/types/emcp/src/hub.d.ts +63 -0
  71. package/dist/types/emcp/src/index.d.ts +7 -0
  72. package/dist/types/emcp/src/pure.d.ts +11 -0
  73. package/dist/types/emcp/src/upstream.d.ts +288 -0
  74. package/dist/types/emcp/src/vault.d.ts +34 -0
  75. package/dist/web/index.js +791 -0
  76. package/dist/web/index.js.map +13 -0
  77. package/fixtures/bundle.json +1165 -0
  78. package/fixtures/capabilities.json +373 -0
  79. package/fixtures/consent.json +146 -0
  80. package/fixtures/lifecycle.json +125 -0
  81. package/fixtures/outbox.json +8 -0
  82. package/fixtures/providers.json +55 -0
  83. package/fixtures/runner.json +2167 -0
  84. package/package.json +92 -4
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../packages/eengine/src/tools.ts"],
4
+ "sourcesContent": [
5
+ "// VAPP-265: capabilities v1. The tools' schemas, the JSON-schema subset that\n// checks them, and THE BRIDGE between the broker's `call` verdict and a host's\n// native provider (the same for app UI and agents). Pure and browser-safe;\n// locked by `fixtures/capabilities.json` (`tools`, `validateCases`, `bridgeCases`).\n\nimport type { HubOutcome } from \"@exp/emcp\"\n\nimport fixture from \"../fixtures/capabilities.json\" with { type: \"json\" }\nimport { renderPdf } from \"./pdf\"\n\nexport type JsonSchema = Record<string, unknown>\n\nexport interface CapabilityTool {\n name: string\n runs: `native` | `engine`\n description: string\n inputSchema: JsonSchema\n nativeOutputSchema: JsonSchema | null\n outputSchema: JsonSchema\n}\n\nexport const CAPABILITY_TOOLS = fixture.tools as unknown as readonly CapabilityTool[]\nexport const MIME_BY_EXTENSION: Readonly<Record<string, string>> = fixture.v1.mimeByExtension\n\nexport const toolDef = (name: string): CapabilityTool | null => CAPABILITY_TOOLS.find((t) => t.name === name) ?? null\n\n/** A host's native provider of one capability (`name`, like the crate's\n * `Capability`); `call` gets the bare tool (`pick`) and the mapped native\n * arguments, and answers an MCP call result (`structuredContent` per\n * `nativeOutputSchema`, or `isError`). */\nexport interface NativeCapability {\n name: string\n call(tool: string, args: Record<string, unknown>): Promise<unknown>\n}\n\n/** `bridgeCall`'s `native` over a host's capabilities: `photos_pick` → the\n * `photos` provider's `pick`; a capability the host lacks answers `unavailable`. */\nexport const nativeCaller =\n (capabilities: readonly NativeCapability[]) =>\n async (tool: string, args: Record<string, unknown>): Promise<unknown> => {\n const at = tool.indexOf(`_`)\n const capability = capabilities.find((c) => c.name === tool.slice(0, at))\n if (!capability) return { content: [{ type: `text`, text: `unavailable` }], isError: true }\n return capability.call(tool.slice(at + 1), args)\n }\n\n// ---------------------------------------------------------------------------\n// The schema subset\n\nconst isObject = (v: unknown): v is Record<string, unknown> => typeof v === `object` && v !== null && !Array.isArray(v)\n\nfunction typeMatches(type: string, v: unknown): boolean {\n switch (type) {\n case `object`:\n return isObject(v)\n case `array`:\n return Array.isArray(v)\n case `string`:\n return typeof v === `string`\n case `boolean`:\n return typeof v === `boolean`\n case `null`:\n return v === null\n case `number`:\n return typeof v === `number` && Number.isFinite(v)\n case `integer`:\n return typeof v === `number` && Number.isInteger(v)\n default:\n return false\n }\n}\n\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((x, i) => jsonEqual(x, b[i]))\n if (isObject(a) && isObject(b)) {\n const ka = Object.keys(a)\n return ka.length === Object.keys(b).length && ka.every((k) => k in b && jsonEqual(a[k], b[k]))\n }\n return false\n}\n\nconst pointer = (path: readonly string[]): string => (path.length ? path.map((p) => `/${p.replace(/~/g, `~0`).replace(/\\//g, `~1`)}`).join(``) : `/`)\n\nconst DATE_TIME = /^(\\d{4})-(\\d{2})-(\\d{2})[Tt](\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?([Zz]|[+-](\\d{2}):(\\d{2}))$/\nconst TOKEN = /^[A-Za-z0-9._-]{1,64}$/\nconst BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/\n\nfunction dateTimeValid(s: string): boolean {\n const m = DATE_TIME.exec(s)\n if (!m) return false\n const [mo, d, h, mi, se] = [m[2], m[3], m[4], m[5], m[6]].map(Number)\n if (mo < 1 || mo > 12 || d < 1 || d > 31 || h > 23 || mi > 59 || se > 60) return false\n if (m[9] !== undefined && (Number(m[9]) > 23 || Number(m[10]) > 59)) return false\n return true\n}\n\nfunction uriValid(s: string): boolean {\n if (!/^https?:\\/\\/[^\\s]+$/i.test(s) || /[\\u0000-\\u001f\\u007f]/.test(s)) return false\n try {\n const url = new URL(s)\n return (url.protocol === `http:` || url.protocol === `https:`) && url.hostname !== ``\n } catch {\n return false\n }\n}\n\nfunction formatValid(format: string, s: string): boolean {\n switch (format) {\n case `date-time`:\n return dateTimeValid(s)\n case `token`:\n return TOKEN.test(s)\n case `uri`:\n return uriValid(s)\n case `base64`:\n return base64Decode(s) !== null\n default:\n return true\n }\n}\n\n/** The first violation of `value` against `schema` as `<pointer> <keyword>`, or null. */\nexport function schemaViolation(schema: JsonSchema, value: unknown, path: readonly string[] = []): string | null {\n const at = (keyword: string, p: readonly string[] = path) => `${pointer(p)} ${keyword}`\n if (schema.type !== undefined) {\n const types = Array.isArray(schema.type) ? (schema.type as string[]) : [schema.type as string]\n if (!types.some((t) => typeMatches(t, value))) return at(`type`)\n }\n if (isObject(value)) {\n const properties = (schema.properties ?? {}) as Record<string, JsonSchema>\n for (const key of (schema.required ?? []) as string[]) if (!(key in value)) return at(`required`, [...path, key])\n if (schema.additionalProperties === false) {\n const unknown = Object.keys(value).filter((k) => !(k in properties)).sort()\n if (unknown.length) return at(`additionalProperties`, [...path, unknown[0]])\n }\n for (const [key, sub] of Object.entries(properties)) {\n if (!(key in value)) continue\n const v = schemaViolation(sub, value[key], [...path, key])\n if (v) return v\n }\n for (const [key, needs] of Object.entries((schema.dependentRequired ?? {}) as Record<string, string[]>)) {\n if (!(key in value)) continue\n for (const need of needs) if (!(need in value)) return at(`dependentRequired`, [...path, need])\n }\n }\n if (schema.anyOf && !(schema.anyOf as JsonSchema[]).some((s) => schemaViolation(s, value, path) === null)) return at(`anyOf`)\n if (schema.oneOf && (schema.oneOf as JsonSchema[]).filter((s) => schemaViolation(s, value, path) === null).length !== 1) return at(`oneOf`)\n if (Array.isArray(value)) {\n if (typeof schema.minItems === `number` && value.length < schema.minItems) return at(`minItems`)\n if (typeof schema.maxItems === `number` && value.length > schema.maxItems) return at(`maxItems`)\n if (schema.items) {\n for (let i = 0; i < value.length; i++) {\n const v = schemaViolation(schema.items as JsonSchema, value[i], [...path, String(i)])\n if (v) return v\n }\n }\n }\n if (typeof value === `number`) {\n if (typeof schema.minimum === `number` && value < schema.minimum) return at(`minimum`)\n if (typeof schema.maximum === `number` && value > schema.maximum) return at(`maximum`)\n }\n if (typeof value === `string`) {\n const length = [...value].length\n if (typeof schema.minLength === `number` && length < schema.minLength) return at(`minLength`)\n if (typeof schema.maxLength === `number` && length > schema.maxLength) return at(`maxLength`)\n }\n if (schema.enum && !(schema.enum as unknown[]).some((e) => jsonEqual(e, value))) return at(`enum`)\n if (typeof value === `string` && typeof schema.format === `string` && !formatValid(schema.format, value)) return at(`format`)\n return null\n}\n\nexport type ValidateResult = { arguments: Record<string, unknown> } | { error: string }\n\n/** A call's arguments against the tool's `inputSchema`; a missing top-level\n * property with a `default` gets it. */\nexport function validateToolArguments(tool: string, args: unknown): ValidateResult {\n const def = toolDef(tool)\n if (!def) return { error: `unknownTool` }\n const violation = schemaViolation(def.inputSchema, args)\n if (violation) return { error: `invalidParams: ${violation}` }\n const out: Record<string, unknown> = { ...(args as Record<string, unknown>) }\n for (const [key, sub] of Object.entries((def.inputSchema.properties ?? {}) as Record<string, JsonSchema>)) {\n if (!(key in out) && `default` in sub) out[key] = structuredClone(sub.default)\n }\n return { arguments: out }\n}\n\n// ---------------------------------------------------------------------------\n// Bytes\n\nconst B64 = `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`\n\nexport function base64Encode(bytes: Uint8Array): string {\n let out = ``\n for (let i = 0; i < bytes.length; i += 3) {\n const n = (bytes[i] << 16) | ((bytes[i + 1] ?? 0) << 8) | (bytes[i + 2] ?? 0)\n out += B64[(n >> 18) & 63] + B64[(n >> 12) & 63]\n out += i + 1 < bytes.length ? B64[(n >> 6) & 63] : `=`\n out += i + 2 < bytes.length ? B64[n & 63] : `=`\n }\n return out\n}\n\n/** Strict: the standard alphabet, padded, no whitespace, zero trailing bits; null otherwise. */\nexport function base64Decode(s: string): Uint8Array | null {\n if (!BASE64.test(s)) return null\n const pad = s.endsWith(`==`) ? 2 : s.endsWith(`=`) ? 1 : 0\n const out = new Uint8Array((s.length / 4) * 3 - pad)\n let o = 0\n for (let i = 0; i < s.length; i += 4) {\n const c = [0, 1, 2, 3].map((k) => (s[i + k] === `=` ? 0 : B64.indexOf(s[i + k])))\n const n = (c[0] << 18) | (c[1] << 12) | (c[2] << 6) | c[3]\n const last = i + 4 === s.length\n if (last && pad === 2 && (n & 0xffff) !== 0) return null\n if (last && pad === 1 && (n & 0xff) !== 0) return null\n out[o++] = (n >> 16) & 255\n if (o < out.length) out[o++] = (n >> 8) & 255\n if (o < out.length) out[o++] = n & 255\n }\n return out\n}\n\nconst startsWith = (bytes: Uint8Array, prefix: readonly number[]) => prefix.every((b, i) => bytes[i] === b)\nconst PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] as const\n\n/** The type the bytes tell (PDF, JPEG, PNG, GIF), or null. */\nexport function sniffMime(bytes: Uint8Array): string | null {\n if (startsWith(bytes, [0x25, 0x50, 0x44, 0x46, 0x2d])) return `application/pdf`\n if (startsWith(bytes, [0xff, 0xd8, 0xff])) return `image/jpeg`\n if (startsWith(bytes, PNG_SIGNATURE)) return `image/png`\n if (startsWith(bytes, [0x47, 0x49, 0x46, 0x38])) return `image/gif`\n return null\n}\n\n/** A file's MIME type: the given one, else its name's extension, else its bytes,\n * else text/plain for a text and application/octet-stream. */\nexport function mimeFor(name: string, bytes: Uint8Array, given: string | null | undefined, isText: boolean): string {\n if (given) return given\n const dot = name.lastIndexOf(`.`)\n const ext = dot >= 0 ? name.slice(dot + 1).toLowerCase() : ``\n if (ext && Object.hasOwn(MIME_BY_EXTENSION, ext)) return MIME_BY_EXTENSION[ext]\n return sniffMime(bytes) ?? (isText ? `text/plain` : `application/octet-stream`)\n}\n\n/** A notification id the app did not name: `n-` + 32 lowercase hex. */\nexport function newNotificationId(): string {\n const bytes = new Uint8Array(16)\n globalThis.crypto.getRandomValues(bytes)\n return `n-${[...bytes].map((b) => b.toString(16).padStart(2, `0`)).join(``)}`\n}\n\n// ---------------------------------------------------------------------------\n// The bridge\n\nexport interface BlobStore {\n get(app: string, id: string): Promise<Uint8Array | null>\n /** Stores the bytes as an app file; answers its id. */\n put(app: string, name: string, mimeType: string, bytes: Uint8Array): Promise<string>\n}\n\nexport interface BridgeContext {\n app: string\n screens: readonly string[]\n}\n\nexport interface McpCallResult {\n content: { type: `text`; text: string }[]\n structuredContent?: unknown\n isError?: true\n}\n\nexport interface BridgeInput {\n tool: string\n arguments: unknown\n context: BridgeContext\n blobs: BlobStore\n native: (tool: string, args: Record<string, unknown>) => Promise<unknown>\n newId?: () => string\n}\n\nexport interface BridgeOutput {\n result: McpCallResult\n outcome: HubOutcome\n}\n\nexport const toolResult = (structuredContent: unknown): McpCallResult => ({ content: [{ type: `text`, text: JSON.stringify(structuredContent) }], structuredContent })\nexport const toolError = (text: string): McpCallResult => ({ content: [{ type: `text`, text }], isError: true })\n\nclass BridgeError extends Error {\n constructor(\n readonly reason: string,\n readonly outcome: HubOutcome,\n ) {\n super(reason)\n }\n}\n\nconst utf8 = (s: string) => new TextEncoder().encode(s)\n\nasync function appFile(input: BridgeInput, id: string): Promise<Uint8Array> {\n const bytes = await input.blobs.get(input.context.app, id)\n if (!bytes) throw new BridgeError(`notFound: ${id}`, `isError`)\n return bytes\n}\n\n/** The validated arguments → the native's arguments. */\nasync function nativeArguments(input: BridgeInput, args: Record<string, unknown>): Promise<Record<string, unknown>> {\n const { app } = input.context\n switch (input.tool) {\n case `files_save`: {\n const name = args.name as string\n const isText = typeof args.text === `string`\n const bytes = isText ? utf8(args.text as string) : await appFile(input, args.fileId as string)\n return { name, mimeType: mimeFor(name, bytes, args.mimeType as string | undefined, isText), data: base64Encode(bytes) }\n }\n case `share_send`: {\n const out: Record<string, unknown> = {}\n for (const key of [`title`, `text`, `url`]) if (key in args) out[key] = args[key]\n if (typeof args.fileId === `string`) {\n const bytes = await appFile(input, args.fileId)\n const name = args.name as string\n out.file = { name, mimeType: mimeFor(name, bytes, args.mimeType as string | undefined, false), data: base64Encode(bytes) }\n }\n return out\n }\n case `print_print`: {\n const bytes = await appFile(input, args.fileId as string)\n const mimeType = sniffMime(bytes)\n const ext = mimeType === `application/pdf` ? `pdf` : mimeType === `image/png` ? `png` : mimeType === `image/jpeg` ? `jpg` : null\n if (!ext) throw new BridgeError(`unsupportedFile`, `isError`)\n return { name: (args.name as string | undefined) ?? `document.${ext}`, mimeType, data: base64Encode(bytes) }\n }\n case `notifications_post`: {\n const screen = args.screen as string | undefined\n if (screen !== undefined && !input.context.screens.includes(screen)) throw new BridgeError(`invalidParams: /screen enum`, `invalidParams`)\n return {\n id: `${app}/${args.id as string}`,\n title: args.title,\n body: args.body ?? null,\n at: args.at ?? null,\n tap: screen === undefined ? null : { type: `notificationTap`, app, screen, params: args.params ?? {} },\n }\n }\n case `notifications_cancel`:\n return { id: `${app}/${args.id as string}` }\n default:\n return args\n }\n}\n\n/** Stores every object carrying `data` (depth first, in order): it loses `data`, gains `size` and `fileId`. */\nasync function storeFiles(input: BridgeInput, value: unknown): Promise<unknown> {\n if (Array.isArray(value)) {\n const out: unknown[] = []\n for (const item of value) out.push(await storeFiles(input, item))\n return out\n }\n if (!isObject(value)) return value\n let out: Record<string, unknown> = {}\n let bytes: Uint8Array | null = null\n if (typeof value.data === `string`) {\n bytes = base64Decode(value.data)\n if (!bytes) throw new BridgeError(`badResult: data format`, `isError`)\n }\n const fileId = bytes ? await input.blobs.put(input.context.app, value.name as string, value.mimeType as string, bytes) : null\n for (const [key, v] of Object.entries(value)) if (!(bytes && key === `data`)) out[key] = await storeFiles(input, v)\n if (bytes) out = { ...out, size: bytes.length, fileId }\n return out\n}\n\n/** One capability call from the broker's `call` verdict to its answer. */\nexport async function bridgeCall(input: BridgeInput): Promise<BridgeOutput> {\n const validated = validateToolArguments(input.tool, input.arguments)\n if (`error` in validated) return { result: toolError(validated.error), outcome: validated.error === `unknownTool` ? `unknown` : `invalidParams` }\n const def = toolDef(input.tool) as CapabilityTool\n const args = validated.arguments\n try {\n if (input.tool === `print_pdf`) {\n const rendered = await renderPdf(args as never, (id) => input.blobs.get(input.context.app, id))\n if (`error` in rendered) return { result: toolError(rendered.error), outcome: `isError` }\n const name = (args.name as string | undefined) ?? pdfFileName(args.title as string)\n const fileId = await input.blobs.put(input.context.app, name, `application/pdf`, rendered.bytes)\n return { result: toolResult({ file: { name, mimeType: `application/pdf`, size: rendered.bytes.length, fileId }, pages: rendered.pages }), outcome: `ok` }\n }\n if (input.tool === `notifications_post` && args.id === undefined) args.id = (input.newId ?? newNotificationId)()\n const nativeArgs = await nativeArguments(input, args)\n let answer: unknown\n try {\n answer = await input.native(input.tool, nativeArgs)\n } catch (error) {\n return { result: toolError(`nativeFailed: ${error instanceof Error ? error.message : String(error)}`), outcome: `rpcError` }\n }\n if (isObject(answer) && answer.isError === true) return { result: answer as unknown as McpCallResult, outcome: `isError` }\n const structured = isObject(answer) ? answer.structuredContent : undefined\n const violation = schemaViolation(def.nativeOutputSchema as JsonSchema, structured)\n if (violation) return { result: toolError(`badResult: ${violation}`), outcome: `isError` }\n if (input.tool === `notifications_post`) return { result: toolResult({ id: args.id }), outcome: `ok` }\n return { result: toolResult(await storeFiles(input, structured)), outcome: `ok` }\n } catch (error) {\n if (error instanceof BridgeError) return { result: toolError(error.reason), outcome: error.outcome }\n throw error\n }\n}\n\n/** print.pdf's default file name: the title with `/\\:*?\"<>|` and controls as `-`, + `.pdf`. */\nexport const pdfFileName = (title: string): string => `${title.replace(/[/\\\\:*?\"<>|\\u0000-\\u001f\\u007f-\\u009f]/g, `-`)}.pdf`\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "EC2CD4A2199C0CA164756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,259 @@
1
+ import {
2
+ canonicalJson,
3
+ scopeDef2
4
+ } from "./index-md3hz6r2.js";
5
+ // packages/eengine/fixtures/consent.json
6
+ var consent_default = {
7
+ $comment: "VAPP-260 (Engine E0, epic VAPP-259, docs/spec/apps.md §13.2): CONSENT. The package's capability manifest (format 5 `capabilities: [{id, why, origins?}]`) is the CEILING; the person's decision comes next (capabilities.json `brokerDecide`); the audit row last. GRANTS are per app: `{<scope id>: granted | denied}` (absent = undecided), kept ONLY for `ask` scopes (a `picker` scope's consent is the platform picker, every call). THE SHEET (`sheetCases`, `consentSheet`): when the broker answers `ask`, the Engine shows ONE genUI surface built from existing components only (Drawer + Text + ChoiceTiles + Button; no new primitive), the same on every platform and the default `ConsentUi` (a host may override the painter, never the rule): a bottom Drawer titled `Allow {app} to {verb}?` (`copy.title`; the scope's `verb`, an `origins` scope's `{origins}` = its origins joined with `, `), the manifest's `why` as a Text, ChoiceTiles `answer` (one column) with `copy.options` in order (`allow`, `once`, `deny`) starting on `allow`, and a Button `copy.confirm` (the default variant) whose press sends the event `consent.answer` with the picked value; closing the Drawer (Escape, the backdrop, a swipe) answers `dismiss`. THE ANSWER (`answerCases`, `consentAnswer`): `allow` = the call goes ahead and the grant is stored `granted`; `once` = the call goes ahead and nothing is stored (the next call asks again); `deny` = the call is refused `consentDenied` and `denied` is stored; `dismiss` = refused `consentDismissed`, nothing stored. MANAGE (`manageCases`, `manageSheet`): the app's Permissions surface (its … menu, the runner's app menu) = a Drawer `copy.manageTitle` with one Switch per DECLARED `ask` scope in manifest order (label = the capability's `label`, description = the `why`, checked = granted; event `consent.toggle` with the scope) and no Switch for `picker` scopes; flipping one is the gesture: on = `granted`, off = REVOKE = the grant removed (undecided, so the next call asks). WRITES (`writeCases`, `grantWrite`): a grant changes ONLY through the ConsentUi after a user gesture (`via: consentUi`, `gesture: true`); anything else (a hub tool, an agent, the package, the host config, a sync) is refused `onlyConsentUi`, a ConsentUi write without a gesture `noGesture`, an undeclared or `picker` or `hostOnly` scope `notAsk`. PERSISTENCE (`storeCases`, `grantsRecord` / `grantsOnPackage`): the Vault (emcp `Vault`: Keychain, Keystore, libsecret, the server's sealed column) holds key `vaultKey` with `{app}` = the package id, value = canonical JSON `{grants, origins, signer, v}`: `v` = `recordVersion`, `signer` = the package signature's public key, `origins` = the `http.fetch` origins the grant was given for (sorted). On every package load the Engine reconciles (`grantsOnPackage`): another signer = a different author, every grant dropped; a scope the new version no longer declares (or now declares as non-ask) is dropped; an `http.fetch` grant whose declared origins are no longer a subset of the granted ones is dropped (new origins ask again); an unreadable record or another `v` = no grants (ask again, never fail open).",
8
+ recordVersion: 1,
9
+ vaultKey: "eengine/grants/{app}",
10
+ copy: {
11
+ title: "Allow {app} to {verb}?",
12
+ options: [
13
+ { label: "Allow", value: "allow" },
14
+ { label: "Allow once", value: "once" },
15
+ { label: "Don't allow", value: "deny" }
16
+ ],
17
+ confirm: "Continue",
18
+ manageTitle: "Permissions"
19
+ },
20
+ answerCases: [
21
+ { scope: "photos.read", grants: {}, answer: "allow", expected: { verdict: "call", reason: null, grants: { "photos.read": "granted" } } },
22
+ { scope: "photos.read", grants: {}, answer: "once", expected: { verdict: "call", reason: null, grants: {} } },
23
+ { scope: "photos.read", grants: { "camera.capture": "granted" }, answer: "deny", expected: { verdict: "denied", reason: "consentDenied", grants: { "camera.capture": "granted", "photos.read": "denied" } } },
24
+ { scope: "photos.read", grants: {}, answer: "dismiss", expected: { verdict: "denied", reason: "consentDismissed", grants: {} } }
25
+ ],
26
+ writeCases: [
27
+ { name: "a switch flipped on grants", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "consentUi", gesture: true, scope: "photos.read", value: "granted" }, expected: { grants: { "photos.read": "granted" }, refused: null } },
28
+ { name: "a switch flipped off revokes", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: { "photos.read": "granted" }, write: { via: "consentUi", gesture: true, scope: "photos.read", value: null }, expected: { grants: {}, refused: null } },
29
+ { name: "no gesture, no change", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "consentUi", gesture: false, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "noGesture" } },
30
+ { name: "an agent cannot grant itself", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "agent", gesture: true, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "onlyConsentUi" } },
31
+ { name: "a hub tool cannot grant", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "hubTool", gesture: false, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "onlyConsentUi" } },
32
+ { name: "the package cannot grant", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "package", gesture: false, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "onlyConsentUi" } },
33
+ { name: "the host config cannot grant", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "hostConfig", gesture: false, scope: "photos.read", value: "granted" }, expected: { grants: {}, refused: "onlyConsentUi" } },
34
+ { name: "an undeclared scope cannot be granted", manifest: [{ id: "photos.read", why: "Pick the photos." }], grants: {}, write: { via: "consentUi", gesture: true, scope: "camera.capture", value: "granted" }, expected: { grants: {}, refused: "notAsk" } },
35
+ { name: "a picker scope keeps no grant", manifest: [{ id: "share.send", why: "Share an album." }], grants: {}, write: { via: "consentUi", gesture: true, scope: "share.send", value: "granted" }, expected: { grants: {}, refused: "notAsk" } },
36
+ { name: "a host-only scope is never granted", manifest: [{ id: "shell.edit", why: "Rearrange the panel." }], grants: {}, write: { via: "consentUi", gesture: true, scope: "shell.edit", value: "granted" }, expected: { grants: {}, refused: "notAsk" } }
37
+ ],
38
+ storeCases: [
39
+ {
40
+ name: "the record round-trips as canonical JSON",
41
+ app: "at.exponential.photobook",
42
+ signer: "pk1",
43
+ manifest: [{ id: "photos.read", why: "Pick the photos." }, { id: "http.fetch", why: "Rates.", origins: ["https://b.example.com", "https://a.example.com"] }],
44
+ grants: { "photos.read": "granted", "http.fetch": "granted" },
45
+ expected: {
46
+ key: "eengine/grants/at.exponential.photobook",
47
+ value: '{"grants":{"http.fetch":"granted","photos.read":"granted"},"origins":["https://a.example.com","https://b.example.com"],"signer":"pk1","v":1}'
48
+ }
49
+ }
50
+ ],
51
+ reconcileCases: [
52
+ {
53
+ name: "the same signer keeps what is still declared",
54
+ stored: '{"grants":{"camera.capture":"granted","photos.read":"denied"},"origins":[],"signer":"pk1","v":1}',
55
+ signer: "pk1",
56
+ manifest: [{ id: "photos.read", why: "Pick the photos." }],
57
+ expected: { "photos.read": "denied" }
58
+ },
59
+ {
60
+ name: "another signer drops every grant",
61
+ stored: '{"grants":{"photos.read":"granted"},"origins":[],"signer":"pk1","v":1}',
62
+ signer: "pk2",
63
+ manifest: [{ id: "photos.read", why: "Pick the photos." }],
64
+ expected: {}
65
+ },
66
+ {
67
+ name: "new origins ask again",
68
+ stored: '{"grants":{"http.fetch":"granted"},"origins":["https://a.example.com"],"signer":"pk1","v":1}',
69
+ signer: "pk1",
70
+ manifest: [{ id: "http.fetch", why: "Rates.", origins: ["https://a.example.com", "https://b.example.com"] }],
71
+ expected: {}
72
+ },
73
+ {
74
+ name: "fewer origins keep the grant",
75
+ stored: '{"grants":{"http.fetch":"granted"},"origins":["https://a.example.com","https://b.example.com"],"signer":"pk1","v":1}',
76
+ signer: "pk1",
77
+ manifest: [{ id: "http.fetch", why: "Rates.", origins: ["https://b.example.com"] }],
78
+ expected: { "http.fetch": "granted" }
79
+ },
80
+ {
81
+ name: "a scope that became a picker scope is dropped",
82
+ stored: '{"grants":{"share.send":"granted"},"origins":[],"signer":"pk1","v":1}',
83
+ signer: "pk1",
84
+ manifest: [{ id: "share.send", why: "Share." }],
85
+ expected: {}
86
+ },
87
+ {
88
+ name: "another record version asks again",
89
+ stored: '{"grants":{"photos.read":"granted"},"origins":[],"signer":"pk1","v":2}',
90
+ signer: "pk1",
91
+ manifest: [{ id: "photos.read", why: "Pick the photos." }],
92
+ expected: {}
93
+ },
94
+ {
95
+ name: "an unreadable record asks again",
96
+ stored: "not json",
97
+ signer: "pk1",
98
+ manifest: [{ id: "photos.read", why: "Pick the photos." }],
99
+ expected: {}
100
+ },
101
+ {
102
+ name: "no record yet",
103
+ stored: null,
104
+ signer: "pk1",
105
+ manifest: [{ id: "photos.read", why: "Pick the photos." }],
106
+ expected: {}
107
+ }
108
+ ],
109
+ sheetCases: [
110
+ {
111
+ name: "the photos sheet",
112
+ app: "Photobook",
113
+ entry: { id: "photos.read", why: "Pick the photos for an album." },
114
+ expected: [
115
+ { id: "root", component: "Drawer", title: "Allow Photobook to access your photos?", open: true, side: "bottom", children: ["why", "answer"], slots: { footer: "confirm" }, on: { change: { event: { name: "consent.answer", context: { answer: "dismiss" } } } } },
116
+ { id: "why", component: "Text", text: "Pick the photos for an album." },
117
+ { id: "answer", component: "ChoiceTiles", name: "answer", columns: 1, value: { "@path": "/answer" }, options: [{ label: "Allow", value: "allow" }, { label: "Allow once", value: "once" }, { label: "Don't allow", value: "deny" }] },
118
+ { id: "confirm", component: "Button", label: "Continue", on: { press: { event: { name: "consent.answer", context: { answer: { "@path": "/answer" } } } } } }
119
+ ],
120
+ data: { answer: "allow" }
121
+ },
122
+ {
123
+ name: "an origins scope names its origins",
124
+ app: "Rates",
125
+ entry: { id: "http.fetch", why: "Load exchange rates.", origins: ["https://api.example.com", "https://backup.example.com"] },
126
+ expected: [
127
+ { id: "root", component: "Drawer", title: "Allow Rates to connect to https://api.example.com, https://backup.example.com?", open: true, side: "bottom", children: ["why", "answer"], slots: { footer: "confirm" }, on: { change: { event: { name: "consent.answer", context: { answer: "dismiss" } } } } },
128
+ { id: "why", component: "Text", text: "Load exchange rates." },
129
+ { id: "answer", component: "ChoiceTiles", name: "answer", columns: 1, value: { "@path": "/answer" }, options: [{ label: "Allow", value: "allow" }, { label: "Allow once", value: "once" }, { label: "Don't allow", value: "deny" }] },
130
+ { id: "confirm", component: "Button", label: "Continue", on: { press: { event: { name: "consent.answer", context: { answer: { "@path": "/answer" } } } } } }
131
+ ],
132
+ data: { answer: "allow" }
133
+ }
134
+ ],
135
+ manageCases: [
136
+ {
137
+ name: "one switch per declared ask scope",
138
+ manifest: [
139
+ { id: "photos.read", why: "Pick the photos for an album." },
140
+ { id: "share.send", why: "Share a finished album." },
141
+ { id: "notifications.post", why: "Remind you of due pages." }
142
+ ],
143
+ grants: { "photos.read": "granted", "notifications.post": "denied" },
144
+ expected: [
145
+ { id: "root", component: "Drawer", title: "Permissions", open: true, side: "bottom", children: ["scope_0", "scope_1"] },
146
+ { id: "scope_0", component: "Switch", label: "Photos", description: "Pick the photos for an album.", checked: true, on: { change: { event: { name: "consent.toggle", context: { scope: "photos.read" } } } } },
147
+ { id: "scope_1", component: "Switch", label: "Notifications", description: "Remind you of due pages.", checked: false, on: { change: { event: { name: "consent.toggle", context: { scope: "notifications.post" } } } } }
148
+ ]
149
+ }
150
+ ]
151
+ };
152
+
153
+ // packages/eengine/src/consent.ts
154
+ var CONSENT_COPY2 = consent_default.copy;
155
+ var GRANTS_RECORD_VERSION2 = consent_default.recordVersion;
156
+ var sortGrants = (grants) => Object.fromEntries(Object.entries(grants).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
157
+ function consentAnswer2(grants, scope, answer) {
158
+ if (answer === `allow`)
159
+ return { verdict: `call`, reason: null, grants: sortGrants({ ...grants, [scope]: `granted` }) };
160
+ if (answer === `once`)
161
+ return { verdict: `call`, reason: null, grants: sortGrants({ ...grants }) };
162
+ if (answer === `deny`)
163
+ return { verdict: `denied`, reason: `consentDenied`, grants: sortGrants({ ...grants, [scope]: `denied` }) };
164
+ return { verdict: `denied`, reason: `consentDismissed`, grants: sortGrants({ ...grants }) };
165
+ }
166
+ function declaredAsk(manifest, id) {
167
+ const def = scopeDef2(id);
168
+ return !!def && !def.scope.hostOnly && def.scope.consent === `ask` && manifest.some((m) => m.id === id);
169
+ }
170
+ function grantWrite2(manifest, grants, write) {
171
+ if (write.via !== `consentUi`)
172
+ return { grants, refused: `onlyConsentUi` };
173
+ if (!write.gesture)
174
+ return { grants, refused: `noGesture` };
175
+ if (!declaredAsk(manifest, write.scope))
176
+ return { grants, refused: `notAsk` };
177
+ const next = { ...grants };
178
+ if (write.value === null)
179
+ delete next[write.scope];
180
+ else
181
+ next[write.scope] = write.value;
182
+ return { grants: sortGrants(next), refused: null };
183
+ }
184
+ var httpOrigins = (manifest) => [...manifest.find((m) => m.id === `http.fetch`)?.origins ?? []].sort();
185
+ function grantsRecord2(app, signer, manifest, grants) {
186
+ return {
187
+ key: consent_default.vaultKey.replace(`{app}`, app),
188
+ value: canonicalJson({ grants: sortGrants(grants), origins: httpOrigins(manifest), signer, v: GRANTS_RECORD_VERSION2 })
189
+ };
190
+ }
191
+ function grantsOnPackage2(stored, signer, manifest) {
192
+ if (stored === null)
193
+ return {};
194
+ let record;
195
+ try {
196
+ record = JSON.parse(stored);
197
+ } catch {
198
+ return {};
199
+ }
200
+ if (!record || typeof record !== `object`)
201
+ return {};
202
+ const r = record;
203
+ if (r.v !== GRANTS_RECORD_VERSION2 || r.signer !== signer || !r.grants || typeof r.grants !== `object`)
204
+ return {};
205
+ const granted = Array.isArray(r.origins) ? r.origins.filter((o) => typeof o === `string`) : [];
206
+ const out = {};
207
+ for (const [id, value] of Object.entries(r.grants)) {
208
+ if (value !== `granted` && value !== `denied`)
209
+ continue;
210
+ if (!declaredAsk(manifest, id))
211
+ continue;
212
+ if (scopeDef2(id)?.scope.origins && !httpOrigins(manifest).every((o) => granted.includes(o)))
213
+ continue;
214
+ out[id] = value;
215
+ }
216
+ return sortGrants(out);
217
+ }
218
+ function consentSheet2(app, entry) {
219
+ const def = scopeDef2(entry.id);
220
+ const verb = (def?.scope.verb ?? entry.id).replace(`{origins}`, (entry.origins ?? []).join(`, `));
221
+ const title = consent_default.copy.title.replace(`{app}`, app).replace(`{verb}`, verb);
222
+ return {
223
+ components: [
224
+ {
225
+ id: `root`,
226
+ component: `Drawer`,
227
+ title,
228
+ open: true,
229
+ side: `bottom`,
230
+ children: [`why`, `answer`],
231
+ slots: { footer: `confirm` },
232
+ on: { change: { event: { name: `consent.answer`, context: { answer: `dismiss` } } } }
233
+ },
234
+ { id: `why`, component: `Text`, text: entry.why },
235
+ { id: `answer`, component: `ChoiceTiles`, name: `answer`, columns: 1, value: { "@path": `/answer` }, options: consent_default.copy.options.map((o) => ({ ...o })) },
236
+ { id: `confirm`, component: `Button`, label: consent_default.copy.confirm, on: { press: { event: { name: `consent.answer`, context: { answer: { "@path": `/answer` } } } } } }
237
+ ],
238
+ data: { answer: `allow` }
239
+ };
240
+ }
241
+ function manageSheet2(manifest, grants) {
242
+ const rows = manifest.filter((m) => declaredAsk(manifest, m.id));
243
+ return [
244
+ { id: `root`, component: `Drawer`, title: consent_default.copy.manageTitle, open: true, side: `bottom`, children: rows.map((_, i) => `scope_${i}`) },
245
+ ...rows.map((m, i) => ({
246
+ id: `scope_${i}`,
247
+ component: `Switch`,
248
+ label: scopeDef2(m.id).capability.label,
249
+ description: m.why,
250
+ checked: grants[m.id] === `granted`,
251
+ on: { change: { event: { name: `consent.toggle`, context: { scope: m.id } } } }
252
+ }))
253
+ ];
254
+ }
255
+
256
+ export { CONSENT_COPY2, GRANTS_RECORD_VERSION2, consentAnswer2, grantWrite2, grantsRecord2, grantsOnPackage2, consentSheet2, manageSheet2 };
257
+
258
+ //# debugId=45BA72763170257B64756E2164756E21
259
+ //# sourceMappingURL=index-nrr8x2kq.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../packages/eengine/src/consent.ts"],
4
+ "sourcesContent": [
5
+ "// VAPP-260: consent. The answer to the sheet, who may write a grant, the\n// Vault record and its reconcile on every package load, and the two genUI\n// surfaces (the ask sheet, the Permissions sheet) built from existing\n// components only. Locked by `fixtures/consent.json`.\n\nimport { canonicalJson } from \"@exp/emcp\"\n\nimport fixture from \"../fixtures/consent.json\" with { type: \"json\" }\nimport { type BrokerVerdictKind, type Grants, type GrantValue, type ManifestEntry, scopeDef } from \"./capabilities\"\n\nexport type ConsentAnswer = `allow` | `once` | `deny` | `dismiss`\nexport type GrantVia = `consentUi` | `agent` | `hubTool` | `package` | `hostConfig` | `sync`\n\nexport const CONSENT_COPY = fixture.copy\nexport const GRANTS_RECORD_VERSION: number = fixture.recordVersion\n\n/** A genUI component (the flat A2UI list a surface carries). */\nexport type SurfaceComponent = Record<string, unknown> & { id: string; component: string }\n\nconst sortGrants = (grants: Grants): Grants => Object.fromEntries(Object.entries(grants).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) as Grants\n\n/** The sheet's answer → the call's verdict and the grants to store. */\nexport function consentAnswer(grants: Grants, scope: string, answer: ConsentAnswer): { verdict: BrokerVerdictKind; reason: string | null; grants: Grants } {\n if (answer === `allow`) return { verdict: `call`, reason: null, grants: sortGrants({ ...grants, [scope]: `granted` }) }\n if (answer === `once`) return { verdict: `call`, reason: null, grants: sortGrants({ ...grants }) }\n if (answer === `deny`) return { verdict: `denied`, reason: `consentDenied`, grants: sortGrants({ ...grants, [scope]: `denied` }) }\n return { verdict: `denied`, reason: `consentDismissed`, grants: sortGrants({ ...grants }) }\n}\n\n/** Is `id` a declared `ask` scope of the manifest (one a grant is kept for)? */\nfunction declaredAsk(manifest: readonly ManifestEntry[], id: string): boolean {\n const def = scopeDef(id)\n return !!def && !def.scope.hostOnly && def.scope.consent === `ask` && manifest.some((m) => m.id === id)\n}\n\n/** A grant write: only the ConsentUi, only after a gesture, only an ask scope. */\nexport function grantWrite(\n manifest: readonly ManifestEntry[],\n grants: Grants,\n write: { via: GrantVia; gesture: boolean; scope: string; value: GrantValue | null },\n): { grants: Grants; refused: string | null } {\n if (write.via !== `consentUi`) return { grants, refused: `onlyConsentUi` }\n if (!write.gesture) return { grants, refused: `noGesture` }\n if (!declaredAsk(manifest, write.scope)) return { grants, refused: `notAsk` }\n const next = { ...grants }\n if (write.value === null) delete next[write.scope]\n else next[write.scope] = write.value\n return { grants: sortGrants(next), refused: null }\n}\n\nconst httpOrigins = (manifest: readonly ManifestEntry[]): string[] => [...(manifest.find((m) => m.id === `http.fetch`)?.origins ?? [])].sort()\n\n/** The Vault key and canonical JSON value of an app's grants. */\nexport function grantsRecord(app: string, signer: string, manifest: readonly ManifestEntry[], grants: Grants): { key: string; value: string } {\n return {\n key: fixture.vaultKey.replace(`{app}`, app),\n value: canonicalJson({ grants: sortGrants(grants), origins: httpOrigins(manifest), signer, v: GRANTS_RECORD_VERSION }),\n }\n}\n\n/** The grants that survive a package load (`stored` = the Vault value, or null). */\nexport function grantsOnPackage(stored: string | null, signer: string, manifest: readonly ManifestEntry[]): Grants {\n if (stored === null) return {}\n let record: unknown\n try {\n record = JSON.parse(stored)\n } catch {\n return {}\n }\n if (!record || typeof record !== `object`) return {}\n const r = record as { v?: unknown; signer?: unknown; grants?: unknown; origins?: unknown }\n if (r.v !== GRANTS_RECORD_VERSION || r.signer !== signer || !r.grants || typeof r.grants !== `object`) return {}\n const granted = Array.isArray(r.origins) ? r.origins.filter((o): o is string => typeof o === `string`) : []\n const out: Grants = {}\n for (const [id, value] of Object.entries(r.grants as Record<string, unknown>)) {\n if (value !== `granted` && value !== `denied`) continue\n if (!declaredAsk(manifest, id)) continue\n if (scopeDef(id)?.scope.origins && !httpOrigins(manifest).every((o) => granted.includes(o))) continue\n out[id] = value\n }\n return sortGrants(out)\n}\n\n/** The ask sheet for one manifest entry: the surface's components and data. */\nexport function consentSheet(app: string, entry: ManifestEntry): { components: SurfaceComponent[]; data: { answer: ConsentAnswer } } {\n const def = scopeDef(entry.id)\n const verb = (def?.scope.verb ?? entry.id).replace(`{origins}`, (entry.origins ?? []).join(`, `))\n const title = fixture.copy.title.replace(`{app}`, app).replace(`{verb}`, verb)\n return {\n components: [\n {\n id: `root`,\n component: `Drawer`,\n title,\n open: true,\n side: `bottom`,\n children: [`why`, `answer`],\n slots: { footer: `confirm` },\n on: { change: { event: { name: `consent.answer`, context: { answer: `dismiss` } } } },\n },\n { id: `why`, component: `Text`, text: entry.why },\n { id: `answer`, component: `ChoiceTiles`, name: `answer`, columns: 1, value: { \"@path\": `/answer` }, options: fixture.copy.options.map((o) => ({ ...o })) },\n { id: `confirm`, component: `Button`, label: fixture.copy.confirm, on: { press: { event: { name: `consent.answer`, context: { answer: { \"@path\": `/answer` } } } } } },\n ],\n data: { answer: `allow` },\n }\n}\n\n/** The Permissions sheet: one Switch per declared ask scope, in manifest order. */\nexport function manageSheet(manifest: readonly ManifestEntry[], grants: Grants): SurfaceComponent[] {\n const rows = manifest.filter((m) => declaredAsk(manifest, m.id))\n return [\n { id: `root`, component: `Drawer`, title: fixture.copy.manageTitle, open: true, side: `bottom`, children: rows.map((_, i) => `scope_${i}`) },\n ...rows.map((m, i) => ({\n id: `scope_${i}`,\n component: `Switch`,\n label: scopeDef(m.id)!.capability.label,\n description: m.why,\n checked: grants[m.id] === `granted`,\n on: { change: { event: { name: `consent.toggle`, context: { scope: m.id } } } },\n })),\n ]\n}\n"
6
+ ],
7
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaO,IAAM,gBAAe,gBAAQ;AAC7B,IAAM,yBAAgC,gBAAQ;AAKrD,IAAM,aAAa,CAAC,WAA2B,OAAO,YAAY,OAAO,QAAQ,MAAM,EAAE,KAAK,EAAE,KAAK,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE,CAAC;AAGlI,SAAS,cAAa,CAAC,QAAgB,OAAe,QAA8F;AAAA,EACzJ,IAAI,WAAW;AAAA,IAAS,OAAO,EAAE,SAAS,QAAQ,QAAQ,MAAM,QAAQ,WAAW,KAAK,SAAS,QAAQ,UAAU,CAAC,EAAE;AAAA,EACtH,IAAI,WAAW;AAAA,IAAQ,OAAO,EAAE,SAAS,QAAQ,QAAQ,MAAM,QAAQ,WAAW,KAAK,OAAO,CAAC,EAAE;AAAA,EACjG,IAAI,WAAW;AAAA,IAAQ,OAAO,EAAE,SAAS,UAAU,QAAQ,iBAAiB,QAAQ,WAAW,KAAK,SAAS,QAAQ,SAAS,CAAC,EAAE;AAAA,EACjI,OAAO,EAAE,SAAS,UAAU,QAAQ,oBAAoB,QAAQ,WAAW,KAAK,OAAO,CAAC,EAAE;AAAA;AAI5F,SAAS,WAAW,CAAC,UAAoC,IAAqB;AAAA,EAC5E,MAAM,MAAM,UAAS,EAAE;AAAA,EACvB,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,MAAM,YAAY,IAAI,MAAM,YAAY,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA;AAIjG,SAAS,WAAU,CACxB,UACA,QACA,OAC4C;AAAA,EAC5C,IAAI,MAAM,QAAQ;AAAA,IAAa,OAAO,EAAE,QAAQ,SAAS,gBAAgB;AAAA,EACzE,IAAI,CAAC,MAAM;AAAA,IAAS,OAAO,EAAE,QAAQ,SAAS,YAAY;AAAA,EAC1D,IAAI,CAAC,YAAY,UAAU,MAAM,KAAK;AAAA,IAAG,OAAO,EAAE,QAAQ,SAAS,SAAS;AAAA,EAC5E,MAAM,OAAO,KAAK,OAAO;AAAA,EACzB,IAAI,MAAM,UAAU;AAAA,IAAM,OAAO,KAAK,MAAM;AAAA,EAC5C;AAAA,IAAK,KAAK,MAAM,SAAS,MAAM;AAAA,EAC/B,OAAO,EAAE,QAAQ,WAAW,IAAI,GAAG,SAAS,KAAK;AAAA;AAGnD,IAAM,cAAc,CAAC,aAAiD,CAAC,GAAI,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,YAAY,GAAG,WAAW,CAAC,CAAE,EAAE,KAAK;AAGtI,SAAS,aAAY,CAAC,KAAa,QAAgB,UAAoC,QAAgD;AAAA,EAC5I,OAAO;AAAA,IACL,KAAK,gBAAQ,SAAS,QAAQ,SAAS,GAAG;AAAA,IAC1C,OAAO,cAAc,EAAE,QAAQ,WAAW,MAAM,GAAG,SAAS,YAAY,QAAQ,GAAG,QAAQ,GAAG,uBAAsB,CAAC;AAAA,EACvH;AAAA;AAIK,SAAS,gBAAe,CAAC,QAAuB,QAAgB,UAA4C;AAAA,EACjH,IAAI,WAAW;AAAA,IAAM,OAAO,CAAC;AAAA,EAC7B,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,SAAS,KAAK,MAAM,MAAM;AAAA,IAC1B,MAAM;AAAA,IACN,OAAO,CAAC;AAAA;AAAA,EAEV,IAAI,CAAC,UAAU,OAAO,WAAW;AAAA,IAAU,OAAO,CAAC;AAAA,EACnD,MAAM,IAAI;AAAA,EACV,IAAI,EAAE,MAAM,0BAAyB,EAAE,WAAW,UAAU,CAAC,EAAE,UAAU,OAAO,EAAE,WAAW;AAAA,IAAU,OAAO,CAAC;AAAA,EAC/G,MAAM,UAAU,MAAM,QAAQ,EAAE,OAAO,IAAI,EAAE,QAAQ,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,EAC1G,MAAM,MAAc,CAAC;AAAA,EACrB,YAAY,IAAI,UAAU,OAAO,QAAQ,EAAE,MAAiC,GAAG;AAAA,IAC7E,IAAI,UAAU,aAAa,UAAU;AAAA,MAAU;AAAA,IAC/C,IAAI,CAAC,YAAY,UAAU,EAAE;AAAA,MAAG;AAAA,IAChC,IAAI,UAAS,EAAE,GAAG,MAAM,WAAW,CAAC,YAAY,QAAQ,EAAE,MAAM,CAAC,MAAM,QAAQ,SAAS,CAAC,CAAC;AAAA,MAAG;AAAA,IAC7F,IAAI,MAAM;AAAA,EACZ;AAAA,EACA,OAAO,WAAW,GAAG;AAAA;AAIhB,SAAS,aAAY,CAAC,KAAa,OAA2F;AAAA,EACnI,MAAM,MAAM,UAAS,MAAM,EAAE;AAAA,EAC7B,MAAM,QAAQ,KAAK,MAAM,QAAQ,MAAM,IAAI,QAAQ,cAAc,MAAM,WAAW,CAAC,GAAG,KAAK,IAAI,CAAC;AAAA,EAChG,MAAM,QAAQ,gBAAQ,KAAK,MAAM,QAAQ,SAAS,GAAG,EAAE,QAAQ,UAAU,IAAI;AAAA,EAC7E,OAAO;AAAA,IACL,YAAY;AAAA,MACV;AAAA,QACE,IAAI;AAAA,QACJ,WAAW;AAAA,QACX;AAAA,QACA,MAAM;AAAA,QACN,MAAM;AAAA,QACN,UAAU,CAAC,OAAO,QAAQ;AAAA,QAC1B,OAAO,EAAE,QAAQ,UAAU;AAAA,QAC3B,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,SAAS,EAAE,QAAQ,UAAU,EAAE,EAAE,EAAE;AAAA,MACtF;AAAA,MACA,EAAE,IAAI,OAAO,WAAW,QAAQ,MAAM,MAAM,IAAI;AAAA,MAChD,EAAE,IAAI,UAAU,WAAW,eAAe,MAAM,UAAU,SAAS,GAAG,OAAO,EAAE,SAAS,UAAU,GAAG,SAAS,gBAAQ,KAAK,QAAQ,IAAI,CAAC,OAAO,KAAK,EAAE,EAAE,EAAE;AAAA,MAC1J,EAAE,IAAI,WAAW,WAAW,UAAU,OAAO,gBAAQ,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,SAAS,EAAE,QAAQ,EAAE,SAAS,UAAU,EAAE,EAAE,EAAE,EAAE,EAAE;AAAA,IACvK;AAAA,IACA,MAAM,EAAE,QAAQ,QAAQ;AAAA,EAC1B;AAAA;AAIK,SAAS,YAAW,CAAC,UAAoC,QAAoC;AAAA,EAClG,MAAM,OAAO,SAAS,OAAO,CAAC,MAAM,YAAY,UAAU,EAAE,EAAE,CAAC;AAAA,EAC/D,OAAO;AAAA,IACL,EAAE,IAAI,QAAQ,WAAW,UAAU,OAAO,gBAAQ,KAAK,aAAa,MAAM,MAAM,MAAM,UAAU,UAAU,KAAK,IAAI,CAAC,GAAG,MAAM,SAAS,GAAG,EAAE;AAAA,IAC3I,GAAG,KAAK,IAAI,CAAC,GAAG,OAAO;AAAA,MACrB,IAAI,SAAS;AAAA,MACb,WAAW;AAAA,MACX,OAAO,UAAS,EAAE,EAAE,EAAG,WAAW;AAAA,MAClC,aAAa,EAAE;AAAA,MACf,SAAS,OAAO,EAAE,QAAQ;AAAA,MAC1B,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;AAAA,IAChF,EAAE;AAAA,EACJ;AAAA;",
8
+ "debugId": "45BA72763170257B64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,170 @@
1
+ // packages/eengine/src/stores.ts
2
+ class StoreError2 extends Error {
3
+ code;
4
+ constructor(code, message) {
5
+ super(message);
6
+ this.code = code;
7
+ this.name = `StoreError`;
8
+ }
9
+ }
10
+ function applyBatch2(rows, batch, by) {
11
+ const next = rows.map((r) => ({ ...r }));
12
+ const delta = { upserts: [], deletes: [] };
13
+ const events = [];
14
+ const ids = [];
15
+ for (const w of batch.writes) {
16
+ const at = next.findIndex((r) => r.id === w.id);
17
+ if (w.op === `upsert`) {
18
+ if (at >= 0) {
19
+ if (w.create)
20
+ throw new StoreError2(`CONFLICT`, `${w.id} exists`);
21
+ const row = next[at];
22
+ const before = row.data;
23
+ next[at] = { ...row, data: { ...row.data ?? {}, ...w.data }, updatedAt: batch.at };
24
+ delta.upserts.push(next[at]);
25
+ events.push({ recordId: w.id, op: `update`, actor: by, writeId: batch.writeId, patch: w.data, before, at: batch.at });
26
+ } else {
27
+ if (!w.create)
28
+ throw new StoreError2(`NOT_FOUND`, `no record ${w.id}`);
29
+ const row = { id: w.id, kind: w.kind, data: { ...w.data }, createdBy: by, authorId: by, createdAt: batch.at, updatedAt: batch.at };
30
+ next.push(row);
31
+ delta.upserts.push(row);
32
+ events.push({ recordId: w.id, op: `create`, actor: by, writeId: batch.writeId, patch: w.data, before: null, at: batch.at });
33
+ }
34
+ } else {
35
+ if (at < 0)
36
+ throw new StoreError2(`NOT_FOUND`, `no record ${w.id}`);
37
+ const [gone] = next.splice(at, 1);
38
+ delta.deletes.push(w.id);
39
+ events.push({ recordId: w.id, op: `delete`, actor: by, writeId: batch.writeId, patch: null, before: gone.data, at: batch.at });
40
+ }
41
+ ids.push(w.id);
42
+ }
43
+ return { rows: next, ack: { ids, replayed: false }, delta, events };
44
+ }
45
+
46
+ class LocalRecordStore2 {
47
+ persistence;
48
+ cache = new Map;
49
+ listeners = new Map;
50
+ queue = Promise.resolve();
51
+ constructor(persistence) {
52
+ this.persistence = persistence;
53
+ }
54
+ loaded(app) {
55
+ let rows = this.cache.get(app);
56
+ if (!rows) {
57
+ rows = this.persistence.load(app);
58
+ this.cache.set(app, rows);
59
+ rows.catch(() => this.cache.delete(app));
60
+ }
61
+ return rows;
62
+ }
63
+ async rows(app) {
64
+ return [...await this.loaded(app)];
65
+ }
66
+ write(app, batch, by) {
67
+ const run = async () => {
68
+ const known = await this.persistence.ack(app, batch.writeId);
69
+ if (known)
70
+ return { ids: known, replayed: true };
71
+ const applied = applyBatch2(await this.loaded(app), batch, by);
72
+ await this.persistence.commit(app, { writeId: batch.writeId, ids: applied.ack.ids, upserts: applied.delta.upserts, deletes: applied.delta.deletes, events: applied.events });
73
+ this.cache.set(app, Promise.resolve(applied.rows));
74
+ for (const on of this.listeners.get(app) ?? [])
75
+ on(applied.delta);
76
+ return applied.ack;
77
+ };
78
+ const result = this.queue.then(run, run);
79
+ this.queue = result.catch(() => {});
80
+ return result;
81
+ }
82
+ history(app, id) {
83
+ return this.persistence.history(app, id);
84
+ }
85
+ subscribe(app, on) {
86
+ const set = this.listeners.get(app) ?? new Set;
87
+ set.add(on);
88
+ this.listeners.set(app, set);
89
+ return () => void set.delete(on);
90
+ }
91
+ }
92
+
93
+ class MemoryPersistence2 {
94
+ rows = new Map;
95
+ acks = new Map;
96
+ events = new Map;
97
+ async load(app) {
98
+ return [...this.rows.get(app) ?? []];
99
+ }
100
+ async ack(app, writeId) {
101
+ return this.acks.get(`${app}
102
+ ${writeId}`) ?? null;
103
+ }
104
+ async commit(app, commit) {
105
+ let rows = (this.rows.get(app) ?? []).filter((r) => !commit.deletes.includes(r.id));
106
+ for (const row of commit.upserts) {
107
+ const i = rows.findIndex((r) => r.id === row.id);
108
+ rows = i >= 0 ? rows.map((r, j) => j === i ? row : r) : [...rows, row];
109
+ }
110
+ this.rows.set(app, rows);
111
+ this.acks.set(`${app}
112
+ ${commit.writeId}`, commit.ids);
113
+ for (const e of commit.events)
114
+ this.events.set(`${app}
115
+ ${e.recordId}`, [...this.events.get(`${app}
116
+ ${e.recordId}`) ?? [], e]);
117
+ }
118
+ async history(app, id) {
119
+ return [...this.events.get(`${app}
120
+ ${id}`) ?? []];
121
+ }
122
+ }
123
+
124
+ class MemoryRecordStore2 extends LocalRecordStore2 {
125
+ constructor() {
126
+ super(new MemoryPersistence2);
127
+ }
128
+ }
129
+
130
+ class MemoryVault2 {
131
+ values = new Map;
132
+ async read(key) {
133
+ return this.values.get(key) ?? null;
134
+ }
135
+ async write(key, value) {
136
+ this.values.set(key, value);
137
+ }
138
+ async delete(key) {
139
+ this.values.delete(key);
140
+ }
141
+ }
142
+
143
+ class MemoryBlobStore2 {
144
+ files = new Map;
145
+ async put(app, name, mimeType, bytes) {
146
+ const id = crypto.randomUUID();
147
+ this.files.set(`${app}/${id}`, { name, mimeType, bytes: bytes.slice() });
148
+ return id;
149
+ }
150
+ async get(app, id) {
151
+ return this.files.get(`${app}/${id}`)?.bytes.slice() ?? null;
152
+ }
153
+ async delete(app, id) {
154
+ this.files.delete(`${app}/${id}`);
155
+ }
156
+ }
157
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
158
+ async function localDeviceId2(vault, key) {
159
+ const stored = (await vault.read(key))?.trim();
160
+ if (stored && UUID.test(stored))
161
+ return stored;
162
+ const id = crypto.randomUUID();
163
+ await vault.write(key, id);
164
+ return id;
165
+ }
166
+
167
+ export { StoreError2, applyBatch2, LocalRecordStore2, MemoryPersistence2, MemoryRecordStore2, MemoryVault2, MemoryBlobStore2, localDeviceId2 };
168
+
169
+ //# debugId=964FE18B6622157D64756E2164756E21
170
+ //# sourceMappingURL=index-s3xjr701.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../packages/eengine/src/stores.ts"],
4
+ "sourcesContent": [
5
+ "// VAPP-269: the record store an AppInstance writes through, the twin of the\n// crate's `providers::RecordStore` + `stores.rs`. One store serves every app\n// of a host, keyed by app id: a batch applies all or nothing, a known\n// `writeId` answers its first ack (`replayed`), every write leaves one history\n// event per record. `LocalRecordStore` keeps each app's rows in memory over a\n// `RecordPersistence` (memory here; SQLite-WASM on OPFS and IndexedDB in\n// `./web/stores`), so a read never waits on storage.\n\nimport type { RecordRow } from \"@exponential_app/eui/host\"\n\nimport type { Failure, Vault } from \"./broker\"\nimport type { BlobStore } from \"./tools\"\n\n/** One write of a batch (the store's spelling of the outbox writes). */\nexport type RecordWrite = { op: `upsert`; id: string; kind: string; data: Record<string, unknown>; create: boolean } | { op: `delete`; id: string }\n\n/** One call's writes, all or nothing, with its idempotency key. */\nexport interface WriteBatch {\n writeId: string\n writes: RecordWrite[]\n /** ISO time of the write (createdAt / updatedAt of what it touches). */\n at: string\n}\n\n/** The ids in write order; `replayed` = the writeId was applied before. */\nexport interface WriteAck {\n ids: string[]\n replayed: boolean\n}\n\n/** One history event of a record. */\nexport interface RecordEvent {\n recordId: string\n op: `create` | `update` | `delete`\n actor: string | null\n writeId: string\n patch: unknown\n before: unknown\n at: string\n}\n\nexport interface RecordsDelta {\n upserts: RecordRow[]\n deletes: string[]\n}\n\n/** Where an app's records live. Rejects with a `StoreError`. */\nexport interface EngineRecordStore {\n rows(app: string): Promise<RecordRow[]>\n write(app: string, batch: WriteBatch, by: string | null): Promise<WriteAck>\n history(app: string, id: string): Promise<RecordEvent[]>\n /** Called with every change to `app`'s rows; returns the cancel. */\n subscribe(app: string, on: (delta: RecordsDelta) => void): () => void\n}\n\n/** A store's refusal as an Error carrying the Failure. */\nexport class StoreError extends Error implements Failure {\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(message)\n this.name = `StoreError`\n }\n}\n\n/** Apply one batch to `rows` (all or nothing: throws with nothing changed).\n * Answers the next rows, the ack, the delta and the events. */\nexport function applyBatch(rows: readonly RecordRow[], batch: WriteBatch, by: string | null): { rows: RecordRow[]; ack: WriteAck; delta: RecordsDelta; events: RecordEvent[] } {\n const next = rows.map((r) => ({ ...r }))\n const delta: RecordsDelta = { upserts: [], deletes: [] }\n const events: RecordEvent[] = []\n const ids: string[] = []\n for (const w of batch.writes) {\n const at = next.findIndex((r) => r.id === w.id)\n if (w.op === `upsert`) {\n if (at >= 0) {\n if (w.create) throw new StoreError(`CONFLICT`, `${w.id} exists`)\n const row = next[at]!\n const before = row.data\n next[at] = { ...row, data: { ...(row.data ?? {}), ...w.data }, updatedAt: batch.at }\n delta.upserts.push(next[at]!)\n events.push({ recordId: w.id, op: `update`, actor: by, writeId: batch.writeId, patch: w.data, before, at: batch.at })\n } else {\n if (!w.create) throw new StoreError(`NOT_FOUND`, `no record ${w.id}`)\n const row: RecordRow = { id: w.id, kind: w.kind, data: { ...w.data }, createdBy: by, authorId: by, createdAt: batch.at, updatedAt: batch.at }\n next.push(row)\n delta.upserts.push(row)\n events.push({ recordId: w.id, op: `create`, actor: by, writeId: batch.writeId, patch: w.data, before: null, at: batch.at })\n }\n } else {\n if (at < 0) throw new StoreError(`NOT_FOUND`, `no record ${w.id}`)\n const [gone] = next.splice(at, 1)\n delta.deletes.push(w.id)\n events.push({ recordId: w.id, op: `delete`, actor: by, writeId: batch.writeId, patch: null, before: gone!.data, at: batch.at })\n }\n ids.push(w.id)\n }\n return { rows: next, ack: { ids, replayed: false }, delta, events }\n}\n\n/** One committed batch, what a persistence stores in one transaction. */\nexport interface RecordCommit {\n writeId: string\n ids: string[]\n upserts: RecordRow[]\n deletes: string[]\n events: RecordEvent[]\n}\n\n/** Where a `LocalRecordStore` keeps its rows. */\nexport interface RecordPersistence {\n load(app: string): Promise<RecordRow[]>\n /** The ids of an applied writeId, or null. */\n ack(app: string, writeId: string): Promise<string[] | null>\n /** One batch, all or nothing. */\n commit(app: string, commit: RecordCommit): Promise<void>\n history(app: string, id: string): Promise<RecordEvent[]>\n}\n\n/** Every app's rows in memory over a persistence; writes run one at a time. */\nexport class LocalRecordStore implements EngineRecordStore {\n private cache = new Map<string, Promise<RecordRow[]>>()\n private listeners = new Map<string, Set<(delta: RecordsDelta) => void>>()\n private queue: Promise<unknown> = Promise.resolve()\n\n constructor(readonly persistence: RecordPersistence) {}\n\n private loaded(app: string): Promise<RecordRow[]> {\n let rows = this.cache.get(app)\n if (!rows) {\n rows = this.persistence.load(app)\n this.cache.set(app, rows)\n rows.catch(() => this.cache.delete(app))\n }\n return rows\n }\n\n async rows(app: string): Promise<RecordRow[]> {\n return [...(await this.loaded(app))]\n }\n\n write(app: string, batch: WriteBatch, by: string | null): Promise<WriteAck> {\n const run = async (): Promise<WriteAck> => {\n const known = await this.persistence.ack(app, batch.writeId)\n if (known) return { ids: known, replayed: true }\n const applied = applyBatch(await this.loaded(app), batch, by)\n await this.persistence.commit(app, { writeId: batch.writeId, ids: applied.ack.ids, upserts: applied.delta.upserts, deletes: applied.delta.deletes, events: applied.events })\n this.cache.set(app, Promise.resolve(applied.rows))\n for (const on of this.listeners.get(app) ?? []) on(applied.delta)\n return applied.ack\n }\n const result = this.queue.then(run, run)\n this.queue = result.catch(() => {})\n return result\n }\n\n history(app: string, id: string): Promise<RecordEvent[]> {\n return this.persistence.history(app, id)\n }\n\n subscribe(app: string, on: (delta: RecordsDelta) => void): () => void {\n const set = this.listeners.get(app) ?? new Set()\n set.add(on)\n this.listeners.set(app, set)\n return () => void set.delete(on)\n }\n}\n\n/** The persistence of tests and previews. */\nexport class MemoryPersistence implements RecordPersistence {\n private rows = new Map<string, RecordRow[]>()\n private acks = new Map<string, string[]>()\n private events = new Map<string, RecordEvent[]>()\n\n async load(app: string): Promise<RecordRow[]> {\n return [...(this.rows.get(app) ?? [])]\n }\n\n async ack(app: string, writeId: string): Promise<string[] | null> {\n return this.acks.get(`${app}\\n${writeId}`) ?? null\n }\n\n async commit(app: string, commit: RecordCommit): Promise<void> {\n let rows = (this.rows.get(app) ?? []).filter((r) => !commit.deletes.includes(r.id))\n for (const row of commit.upserts) {\n const i = rows.findIndex((r) => r.id === row.id)\n rows = i >= 0 ? rows.map((r, j) => (j === i ? row : r)) : [...rows, row]\n }\n this.rows.set(app, rows)\n this.acks.set(`${app}\\n${commit.writeId}`, commit.ids)\n for (const e of commit.events) this.events.set(`${app}\\n${e.recordId}`, [...(this.events.get(`${app}\\n${e.recordId}`) ?? []), e])\n }\n\n async history(app: string, id: string): Promise<RecordEvent[]> {\n return [...(this.events.get(`${app}\\n${id}`) ?? [])]\n }\n}\n\n/** Every app's rows in memory (tests, previews). */\nexport class MemoryRecordStore extends LocalRecordStore {\n constructor() {\n super(new MemoryPersistence())\n }\n}\n\n/** A process-local vault (tests, previews). */\nexport class MemoryVault implements Vault {\n readonly values = new Map<string, string>()\n\n async read(key: string): Promise<string | null> {\n return this.values.get(key) ?? null\n }\n\n async write(key: string, value: string): Promise<void> {\n this.values.set(key, value)\n }\n\n async delete(key: string): Promise<void> {\n this.values.delete(key)\n }\n}\n\n/** Record files in memory (tests, previews). */\nexport class MemoryBlobStore implements BlobStore {\n readonly files = new Map<string, { name: string; mimeType: string; bytes: Uint8Array }>()\n\n async put(app: string, name: string, mimeType: string, bytes: Uint8Array): Promise<string> {\n const id = crypto.randomUUID()\n this.files.set(`${app}/${id}`, { name, mimeType, bytes: bytes.slice() })\n return id\n }\n\n async get(app: string, id: string): Promise<Uint8Array | null> {\n return this.files.get(`${app}/${id}`)?.bytes.slice() ?? null\n }\n\n async delete(app: string, id: string): Promise<void> {\n this.files.delete(`${app}/${id}`)\n }\n}\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\n/** The `local` identity's device uuid under `key` in the Vault, created once. */\nexport async function localDeviceId(vault: Vault, key: string): Promise<string> {\n const stored = (await vault.read(key))?.trim()\n if (stored && UUID.test(stored)) return stored\n const id = crypto.randomUUID()\n await vault.write(key, id)\n return id\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "964FE18B6622157D64756E2164756E21",
9
+ "names": []
10
+ }