iterate 0.2.7 → 0.4.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 (73) hide show
  1. package/README.md +173 -81
  2. package/dist/api.d.ts +643 -0
  3. package/dist/api.mjs +0 -0
  4. package/dist/app-server.d.ts +51 -0
  5. package/dist/app-server.mjs +481 -0
  6. package/dist/app-server.mjs.map +1 -0
  7. package/dist/app-session.d.ts +49 -0
  8. package/dist/app-session.mjs +235 -0
  9. package/dist/app-session.mjs.map +1 -0
  10. package/dist/app.d.ts +29 -0
  11. package/dist/app.mjs +180 -0
  12. package/dist/app.mjs.map +1 -0
  13. package/dist/client/live-state.d.ts +63 -0
  14. package/dist/client/oauth.d.ts +17 -0
  15. package/dist/client/react.d.ts +77 -0
  16. package/dist/client/socket.d.ts +7 -0
  17. package/dist/client.mjs +156 -0
  18. package/dist/client.mjs.map +1 -0
  19. package/dist/expression.d.ts +88 -0
  20. package/dist/expression.mjs +301 -0
  21. package/dist/expression.mjs.map +1 -0
  22. package/dist/lib-BWr-5mFO.mjs +36 -0
  23. package/dist/lib-BWr-5mFO.mjs.map +1 -0
  24. package/dist/lib.d.ts +70 -0
  25. package/dist/lib.mjs +228 -0
  26. package/dist/lib.mjs.map +1 -0
  27. package/dist/node.d.ts +15 -0
  28. package/dist/node.mjs +47 -0
  29. package/dist/node.mjs.map +1 -0
  30. package/dist/oauth-scopes.d.ts +32 -0
  31. package/dist/oauth-scopes.mjs +40 -0
  32. package/dist/oauth-scopes.mjs.map +1 -0
  33. package/dist/oauth.mjs +41 -0
  34. package/dist/oauth.mjs.map +1 -0
  35. package/dist/principal.d.ts +8 -0
  36. package/dist/principal.mjs +8 -0
  37. package/dist/principal.mjs.map +1 -0
  38. package/dist/project-ingress.d.ts +58 -0
  39. package/dist/project-ingress.mjs +104 -0
  40. package/dist/project-ingress.mjs.map +1 -0
  41. package/dist/react.mjs +285 -0
  42. package/dist/react.mjs.map +1 -0
  43. package/dist/sdk/auth.d.ts +25 -0
  44. package/dist/sdk/index.d.ts +155 -0
  45. package/dist/sdk/record-pipelined-steps.d.ts +19 -0
  46. package/dist/sdk.mjs +245 -0
  47. package/dist/sdk.mjs.map +1 -0
  48. package/dist/stream/processor.d.ts +383 -0
  49. package/dist/stream/processor.mjs +605 -0
  50. package/dist/stream/processor.mjs.map +1 -0
  51. package/dist/stream/run.d.ts +61 -0
  52. package/dist/stream/run.mjs +45 -0
  53. package/dist/stream/run.mjs.map +1 -0
  54. package/dist/stream/test-support.d.ts +45 -0
  55. package/dist/stream/test-support.mjs +196 -0
  56. package/dist/stream/test-support.mjs.map +1 -0
  57. package/dist/usingCtx-inzbY1Qz.mjs +57 -0
  58. package/package.json +93 -30
  59. package/bin/iterate.js +0 -86
  60. package/dist/cli-DMS4kJph.mjs +0 -868
  61. package/dist/cli-DMS4kJph.mjs.map +0 -1
  62. package/dist/config-DtnR7Lv7.mjs +0 -170
  63. package/dist/config-DtnR7Lv7.mjs.map +0 -1
  64. package/dist/index.d.mts +0 -5
  65. package/dist/index.d.mts.map +0 -1
  66. package/dist/index.mjs +0 -8
  67. package/dist/index.mjs.map +0 -1
  68. package/dist/stream-tui/agent-chat-terminal.d.mts +0 -1
  69. package/dist/stream-tui/agent-chat-terminal.mjs +0 -933
  70. package/dist/stream-tui/agent-chat-terminal.mjs.map +0 -1
  71. package/dist/worker.d.mts +0 -33
  72. package/dist/worker.mjs +0 -18
  73. package/dist/worker.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expression.mjs","names":["#dispatchItxExpressionSteps"],"sources":["../src/expression.ts"],"sourcesContent":["// expression.ts — THE expression codec: the STRING half (itx.facets.get(\"core\")) ⇄ the\n// STRUCTURED half ([\"itx\", \"facets\", [\"get\", \"core\"]]). Args are ONE JSON5 grammar, comments included\n// (no hand-rolled number/object parser; __proto__-safe); expressions are persisted NAMES, so deleting\n// one IS revocation. One more concept rides with the codec, because user code speaks it too:\n// invoke handle — `InvokeHandle` + the prototype hop: the DOTTED SURFACE, every unknown chain one\n// `invoke(expression)` — what `itx.facets.get(name)` and every other mid-chain\n// capability of api.ts is, and what a connector builds over a remote API\n// What the PLATFORM does with an expression is apps/os: the rewrite rules (match, rank, rewrite) are\n// src/context/itx-expression-rewriting.ts, and executing a rewritten call against a live object graph\n// (`walkSteps`, the answer a context hands back) is src/context/dispatch.ts.\nimport JSON5 from \"json5\";\nimport { RpcTarget } from \"capnweb\";\nimport { codedError, jsonEqual } from \"./lib.ts\";\n\n/** A STRING expression is for what a person types: short. Anything bigger — a worker's source, a large\n * literal — rides the PARSED form (`[\"itx\",\"workers\",[\"get\",{ source }]]`), which is plain data and never\n * meets json5. The cap is O(1), before any parsing (stock json5 allocates per character and a\n * multi-megabyte literal kills a 128 MiB isolate). */\nconst ITX_EXPRESSION_STRING_MAX_CHARS = 2048;\n\n/** One step: a property read (string) or a call (`[method, ...args]`). Args are plain JSON. The\n * method `\"\"` is the ANONYMOUS call — call the value itself: `itx.builtins.rpcStubs.get('cam')(1, 2)`\n * is `[\"itx\",\"builtins\",\"rpcStubs\",[\"get\",\"cam\"],[\"\",1,2]]` — what a `provide(stub)` rule spells when\n * the lent stub is called with args. */\nexport type ItxExpressionStep = string | [method: string, ...args: unknown[]];\n/** An itx expression as data: the scope root (`itx`) then get/call steps. THE parsed form every\n * dispatching method works on. */\nexport type ItxExpression = ItxExpressionStep[];\n/** THE dispatch target, in EITHER codec half — a dotted string that starts with the scope root\n * (`\"itx.facets.get('core')\"`) OR the parsed structured form (`[\"itx\",\"facets\",[\"get\",\"core\"]]`).\n * Both carry call args (the string via `.method(args)`), and `normalizedItxExpression` normalizes\n * either to the structured form — so either works wherever one works, in every method that dispatches. */\nexport type ItxExpressionInput = string | ItxExpression;\n/** An itx-expression PREFIX — a rewrite rule's `match`: dotted names, any of which may be a call step\n * PINNING literal args — `itx.ai.run` or `itx.ai.run('gpt-5')` or `itx.repo.get('main').files`. A\n * pinned arg must equal the call's arg at that position for the rule to match, and is CONSUMED by\n * the match (partial application): `itx.ai.run('gpt-5') ⇒ itx.openai.chat` makes\n * `itx.ai.run('gpt-5', inputs)` into `itx.openai.chat(inputs)`. */\nexport type ItxExpressionPrefix = ItxExpression;\n/** The name a step carries: the property itself, or a call step's method. */\nexport const itxExpressionStepName = (step: ItxExpressionStep | undefined): string | undefined =>\n Array.isArray(step) ? step[0] : step;\n\nconst IDENT = /^[A-Za-z_$][A-Za-z0-9_$-]*/;\nconst RESERVED = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n// ── `@`, THE CALLER'S INPUT — a rewrite rule's target may hold it, nothing else may ──\n// In the string half a bare `@` outside a string literal is the marker (`'@cf/…'` inside quotes is a\n// string like any other); `...@` as an object-literal entry is the merge form. In the array half the\n// marker is ONE reserved literal, `{ \"@\": true }`, and the merge entry the key `\"...@\"` with the value\n// `true` — so the stored form is plain JSON, and those two spellings are unspellable as literals in a\n// target (the codec's one reservation). What `@` MEANS is `fillItxExpressionHoles` in\n// apps/os/src/context/itx-expression-rewriting.ts; here it is only lexed (parse, targets only) and\n// printed back (print, targets only).\n/** The marker's array-half spelling, the one reserved literal. */\nconst ITX_EXPRESSION_HOLE = { \"@\": true } as const;\n/** The merge entry's key — `...@` — read by apps/os `fillItxExpressionHoles`. */\nexport const ITX_EXPRESSION_MERGE_KEY = \"...@\";\n\n/** A single- or double-quoted string literal (escapes honored) or a JSON5 comment (block or line):\n * THE one pattern every walk that must skip what is inside them is built from — the marker lex, the\n * marker print, the paren matcher. In an alternation a span is consumed whole, so nothing inside one\n * (a quote in a comment, an `@` in a string) is ever seen by the other alternatives. */\nconst STRING_OR_COMMENT = String.raw`\"(?:[^\"\\\\]|\\\\[\\s\\S])*\"|'(?:[^'\\\\]|\\\\[\\s\\S])*'|/\\*[\\s\\S]*?\\*/|//[^\\n]*`;\nconst isStringOrComment = (match: string): boolean =>\n match[0] === '\"' || match[0] === \"'\" || match[0] === \"/\";\n/** In call args: a literal (kept verbatim) or a marker — `...@` before `@`, so the merge form wins. */\nconst MARKERS_IN_ARGS = new RegExp(`${STRING_OR_COMMENT}|\\\\.\\\\.\\\\.@|@`, \"g\");\n/** In JSON5's printed output: the marker literal `{'@':true}` and the merge entry `'...@':true` are\n * spelled with a single-quoted key and matched on those exact boundaries — listed BEFORE the literal\n * alternative so the entry's `'...@'` is read as the entry, not as a string. A user's string that\n * merely contains those characters is emitted by JSON5 as a longer (double-quoted) literal and is\n * consumed whole. */\nconst MARKERS_IN_PRINT = new RegExp(`\\\\{'@':true\\\\}|'\\\\.\\\\.\\\\.@':true|${STRING_OR_COMMENT}`, \"g\");\n/** A bracket outside a literal. */\nconst BRACKETS = new RegExp(`${STRING_OR_COMMENT}|[()[\\\\]{}]`, \"g\");\n\n/** Is `value` the marker literal `{ \"@\": true }`? */\nexport const isItxExpressionHole = (value: unknown): boolean =>\n jsonEqual(value, ITX_EXPRESSION_HOLE);\n\n/** Does `value` (a step, an arg tree, a whole expression) hold the marker or a merge entry anywhere? */\nexport function containsItxExpressionHole(value: unknown): boolean {\n if (isItxExpressionHole(value)) return true;\n if (Array.isArray(value)) return value.some(containsItxExpressionHole);\n // oxlint-disable-next-line iterate/simple-truthiness-check -- `value` is `unknown`; the typeof separates real objects from primitives (a bare truthiness check would recurse into strings/numbers)\n if (value !== null && typeof value === \"object\")\n return (\n (value as Record<string, unknown>)[ITX_EXPRESSION_MERGE_KEY] === true ||\n Object.values(value).some(containsItxExpressionHole)\n );\n return false;\n}\n\n/** Index of the `)` closing the `(` at `open`; tracks bracket depth, skipping quoted string args. */\nfunction matchingParen(source: string, open: number): number {\n let depth = 0;\n BRACKETS.lastIndex = open;\n for (let bracket = BRACKETS.exec(source); bracket; bracket = BRACKETS.exec(source)) {\n if (isStringOrComment(bracket[0])) continue;\n if (\"([{\".includes(bracket[0])) depth++;\n else if (--depth === 0) return bracket.index;\n }\n throw new Error(`expression: unbalanced \"(\" in ${JSON.stringify(source)}`);\n}\n\n/** Parse the STRING half: dotted names + `.method(args)` calls (args JSON5-parsed); rejects reserved\n * names + bare scope calls. `holes: true` — a rewrite rule's TARGET only — lexes `@` / `...@` into\n * the marker literals; anywhere else a bare `@` is refused. */\nexport function parse(source: string, options?: { holes?: boolean }): ItxExpression {\n if (source.length > ITX_EXPRESSION_STRING_MAX_CHARS)\n throw codedError(\n \"EXPRESSION_TOO_LONG\",\n `itx expression: ${source.length} chars is over the ${ITX_EXPRESSION_STRING_MAX_CHARS}-char limit for the string form — a string expression is for what a person types; pass the parsed form instead: [\"itx\",\"workers\",[\"get\",{ source: … }]]`,\n );\n const s = source.trim();\n const steps: ItxExpression = [];\n let i = 0;\n function fail(m: string): never {\n throw new Error(`expression: ${m} in ${JSON.stringify(source)}`); // decl, not arrow: TS never-narrows\n }\n const readName = (): string => {\n const m = IDENT.exec(s.slice(i));\n if (!m) fail(`name expected at ${i}`);\n if (RESERVED.has(m[0])) fail(`reserved name \"${m[0]}\"`);\n i += m[0].length;\n return m[0];\n };\n steps.push(readName()); // the scope root (itx)\n while (i < s.length) {\n const c = s[i];\n if (/\\s/.test(c)) i++;\n else if (c === \".\") steps.push((i++, readName()));\n else if (c === \"(\") {\n const end = matchingParen(s, i);\n const raw = s.slice(i + 1, end).trim();\n // `@` outside a string literal: the marker (targets only), a refusal everywhere else.\n const inner = raw.replace(MARKERS_IN_ARGS, (match) => {\n if (isStringOrComment(match)) return match;\n if (!options?.holes)\n fail(\"`@` (the caller's input) is legal only in a rewrite rule's target\");\n return match === \"@\"\n ? JSON.stringify(ITX_EXPRESSION_HOLE)\n : `${JSON.stringify(ITX_EXPRESSION_MERGE_KEY)}:true`;\n });\n let args: unknown[] = [];\n try {\n if (inner !== \"\") args = JSON5.parse(`[${inner}]`) as unknown[];\n } catch (e) {\n fail(`call args are not JSON5 (${(e as Error).message})`);\n }\n const previous = steps.at(-1);\n if (Array.isArray(previous))\n steps.push([\"\", ...args]); // `f(x)(y)`: call the result itself\n else {\n const name = steps.pop();\n if (typeof name !== \"string\") fail(\"a call must follow a name\");\n if (steps.length === 0) fail(\"cannot call the scope symbol itself\");\n steps.push([name, ...args]);\n }\n i = end + 1;\n } else fail(`unexpected ${JSON.stringify(c)} at ${i}`);\n }\n return steps;\n}\n\n/** The array half, checked the way the parser checks the string half — every name step an identifier\n * that is not reserved, every call step `[method, ...args]` with an identifier method (or `\"\"`, the\n * anonymous call, only right after a call) — WITHOUT printing and re-parsing: a stored target carries a worker's whole source as\n * data, and that data must never meet the string codec (the 2 KiB cap, json5). Throws in the\n * parser's words. */\nfunction assertItxExpressionShape(expression: ItxExpression): void {\n const fail = (m: string): never => {\n throw new Error(`expression: ${m} in ${JSON.stringify(expression).slice(0, 200)}`);\n };\n // oxlint-disable-next-line iterate/simple-truthiness-check -- runtime shape validation of wire/stored data; the ItxExpression array type is a claim here, not a guarantee\n if (!Array.isArray(expression) || expression.length === 0)\n fail(\"an expression is a non-empty array\");\n const name = (step: string, what: string) => {\n if (!IDENT.test(step) || IDENT.exec(step)![0] !== step)\n fail(`${what} ${JSON.stringify(step)} is not an identifier`);\n if (RESERVED.has(step)) fail(`reserved name \"${step}\"`);\n };\n expression.forEach((step, i) => {\n if (typeof step === \"string\") {\n name(step, i === 0 ? \"the root\" : \"a name step\");\n return;\n }\n // oxlint-disable-next-line iterate/simple-truthiness-check -- runtime shape validation of wire/stored data; the step's static array type is a claim here, not a guarantee\n if (!Array.isArray(step) || typeof step[0] !== \"string\")\n fail(`step ${i} is neither a name nor [method, ...args]`);\n const [method] = step;\n if (method === \"\") {\n if (i === 0 || !Array.isArray(expression[i - 1]))\n fail(\"the anonymous call `f(x)(y)` follows a call\");\n } else name(method, \"a method\");\n if (i === 0) fail(\"a call on the root itself\");\n });\n // No hole check on the array half: `{ \"@\": true }` carried as DATA is data (edge#6) — only the\n // STRING form lexes a bare `@` into the marker, and only for a rule's target.\n}\n\n/** THE ONE NORMALIZER: either half, normalized to the array half and checked — a string is\n * parsed (short by rule), an array is shape-checked in place. Every function that takes an\n * `ItxExpressionInput` (the edge `invoke`, the resolver, the event builders, the prefix parser\n * below) enters through it. */\nexport function normalizedItxExpression(\n input: ItxExpressionInput,\n options?: { holes?: boolean },\n): ItxExpression {\n if (typeof input === \"string\") return parse(input, options);\n assertItxExpressionShape(input);\n return input;\n}\n\n/** Object args print with their keys SORTED, so two spellings of one object are one canonical string\n * — one rewrite-rule row, one facet memo, one library connection memo (library.ts) — the way\n * `jsonEqual` already matches them. A `JSON.stringify` / `JSON5.stringify` replacer. */\nexport const keySortedForPrint = (_key: string, value: unknown): unknown =>\n // oxlint-disable-next-line iterate/simple-truthiness-check -- `value` is `unknown` (a JSON replacer arg); the typeof discriminates real objects from primitive values\n value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? Object.fromEntries(\n Object.keys(value as Record<string, unknown>)\n .sort()\n .map((k) => [k, (value as Record<string, unknown>)[k]]),\n )\n : value;\n\n/** Canonical stored form: dotted path + `.method(args)` calls (args `JSON5.stringify`d, object keys\n * sorted). `holes: true` — a rewrite rule's TARGET only — spells the marker literals back as `@` /\n * `...@`, and `parse(print(e, { holes: true }), { holes: true })` round-trips; without it the\n * reserved literals print as the plain JSON5 they are, so a CALL that happens to carry `{ \"@\": true }`\n * as data round-trips through `parse` (no holes) unchanged — the resolve/invoke law holds for it. */\nexport function print(expr: ItxExpression, options?: { holes?: boolean }): string {\n return expr\n .map((step, i) => {\n const dot = i ? \".\" : \"\";\n if (typeof step === \"string\") return dot + step;\n const json = JSON5.stringify(step.slice(1), keySortedForPrint).slice(1, -1);\n const args = options?.holes\n ? json.replace(MARKERS_IN_PRINT, (match) =>\n match === \"{'@':true}\" ? \"@\" : match === \"'...@':true\" ? \"...@\" : match,\n )\n : json;\n return step[0] === \"\" ? `(${args})` : `${dot}${step[0]}(${args})`;\n })\n .join(\"\");\n}\n\n/** Parse an itx-expression prefix (either codec half) — `normalizedItxExpression` (so every step is\n * an identifier that is not reserved, in either half) plus the two refusals only a PREFIX has: the\n * anonymous call step (`f(x)(y)` — a prefix cannot call a result), and a call step with NO args,\n * which pins nothing and is the same prefix as the plain name: spell `itx.ai.run`. */\nexport function parseItxExpressionPrefix(source: ItxExpressionInput): ItxExpressionPrefix {\n const expr = normalizedItxExpression(source);\n const spelled = typeof source === \"string\" ? source : print(expr);\n for (const step of expr) {\n if (!Array.isArray(step)) continue;\n if (step[0] === \"\")\n throw new Error(`an itx-expression prefix cannot call a result — ${JSON.stringify(spelled)}`);\n if (step.length === 1)\n throw new Error(\n `an itx-expression prefix pins literal args with a call step — ${JSON.stringify(spelled)} has \"${step[0]}()\" with none; spell \"${step[0]}\"`,\n );\n }\n return expr;\n}\n\n/** THE ONE canonical spelling of an itx-expression prefix — the rewrite-rule table's key, what a lent\n * stub is keyed by through `provide`'s sugar: parsed, then printed (dotted names; pinned args as JSON5\n * literals). */\nexport function canonicalItxExpressionPrefix(source: ItxExpressionInput): string {\n return print(parseItxExpressionPrefix(source));\n}\n\n// ── invoke handle ── THE DOTTED SURFACE: how a surface that declares only fixed methods is\n// spoken as deep dotted access (`itx.slack.chat.postMessage({...})`), every unknown segment\n// accumulating into ONE `invoke(expression)` dispatch, `[...root, ...prefix, [method, ...args]]`.\n// Declared members always win. The pieces: the reserved names, the function-backed PATH PROXY, the\n// PROTOTYPE HOP that installs the fallback on a class, `InvokeHandle` (the genuine RpcTarget a\n// mid-chain capability is handed back as), and `walkStepsOnRpcStub`.\n//\n// WHY A PROTOTYPE HOP AND NOT A PROXY AROUND THE INSTANCE (tried FIRST and reverted): workerd RPC\n// classifies a call's RESULT for promise pipelining with native brand checks a JS Proxy can never\n// pass (`serializeJsValueWithPipeline` in worker-rpc.c++ → `NonPipelinable`; cloudflare/workerd#6873).\n// So a surface returned FROM A METHOD must hand back a REAL, unproxied instance or every pipelined\n// call on it dies with \"The RPC receiver does not implement the method ...\". A mid-chain call\n// returns its handle ACROSS an RPC boundary (`itx.facets.get('b').hello()` is two dispatches), and\n// capnweb's RpcTarget IS the native `cloudflare:workers` RpcTarget on workerd, so a real RpcTarget\n// passes on both hops. The hop squares that with dynamic dispatch by inserting one proxied link\n// BETWEEN `Class.prototype` and its parent:\n//\n// instance ──proto──▶ Class.prototype ──proto──▶ Proxy(hop) ──proto──▶ parent\n//\n// - The instance is a genuine, natively-branded RpcTarget → the pipeline classifier accepts it.\n// - Declared members resolve BEFORE the hop — built-ins win, so a dynamic capability can never\n// shadow a declared name (the deliberate trade-off).\n// - Unknown string keys reach the hop's `get` trap and become path proxies, dispatched via the\n// receiver's own `invoke`; the receiver IS the invoker, so it wires with zero glue.\n// - Instances stay clean of own properties, so Workers RPC's instance-property protection needs\n// no `getOwnPropertyDescriptor` help.\n//\n// KNOWN QUIRKS: no `has` trap on the hop, so `\"x\" in instance`\n// reflects DECLARED members only while `instance.x` conjures a dispatcher — feature-detect with\n// access, not `in`; a typo'd built-in (`itx.strems`) is a syntactically valid dynamic dispatch that\n// fails at the capability table, not a crisp missing-method error.\n//\n// apps/os's library tier (src/library.ts) builds its connectors on this section and the codec, as a\n// userspace worker would (library.test.ts pins that).\n\n/** The dispatch method every dotted miss collapses onto. `IterateContextRpcTarget` implements it directly (root\n * `itx`); a mid-chain `InvokeHandle` implements it relative to itself (empty root). */\ntype InvokeTarget = {\n invoke(itxExpression: ItxExpression): unknown;\n};\n\n/** Names that must NEVER become dynamic capability segments — a dispatcher answering them would turn\n * a plain property probe into a live capability call. Enforced at the prototype-chain hop and at\n * every depth of the path proxies it hands out. Two kinds, one set: */\nconst RESERVED_SEGMENT_NAMES: ReadonlySet<string> = new Set([\n // JS/RPC protocol machinery a framework or capnweb probes on any object (`then` above all: an\n // instance must never look thenable, or every `await` of it would resolve a capability).\n \"__defineGetter__\",\n \"__defineSetter__\",\n \"__lookupGetter__\",\n \"__lookupSetter__\",\n \"__proto__\",\n \"apply\",\n \"bind\",\n \"call\",\n \"catch\",\n \"constructor\",\n \"dup\",\n \"finally\",\n \"hasOwnProperty\",\n \"isPrototypeOf\",\n \"map\",\n \"onRpcBroken\",\n \"propertyIsEnumerable\",\n \"prototype\",\n \"then\",\n \"toLocaleString\",\n \"toString\",\n \"valueOf\",\n // Names common protocols LOOK UP on arbitrary objects and CALL if callable: JSON.stringify\n // (toJSON) and vitest/jest equality (asymmetricMatch). The asymmetricMatch case is worse than\n // noise — vitest treats any object with a callable asymmetricMatch as an asymmetric matcher, and\n // a dispatcher returning a (truthy) Promise makes the equality SPURIOUSLY PASS. The bar for this\n // half is HIGH (these names become unreachable as dotted segments; explicit invoke still\n // reaches them): probed-and-called by ubiquitous protocols AND implausible as capability names.\n \"toJSON\",\n \"asymmetricMatch\",\n]);\n\n/** The path proxy: a function-backed Proxy (not an RpcTarget instance) — each missing property\n * extends `path`, and applying the function reduces the whole accumulated access into ONE\n * `invoke(expression)` call, `[...root, ...path.slice(0, -1), [path.at(-1), ...args]]`. */\nfunction createItxExpressionPathProxy(\n invoker: InvokeTarget,\n root: readonly string[],\n path: string[],\n): unknown {\n const valueFor = (key: string) => createItxExpressionPathProxy(invoker, root, [...path, key]);\n return new Proxy(function () {}, {\n apply(_target, _thisArg, args) {\n const method = path[path.length - 1];\n const expr: ItxExpression = [...root, ...path.slice(0, -1), [method, ...(args as unknown[])]];\n return invoker.invoke(expr);\n },\n get(target, key, receiver) {\n if (typeof key === \"symbol\") return Reflect.get(target, key, receiver);\n if (RESERVED_SEGMENT_NAMES.has(key)) return undefined;\n return valueFor(key);\n },\n getOwnPropertyDescriptor(target, key) {\n const descriptor = Reflect.getOwnPropertyDescriptor(target, key);\n if (descriptor) return descriptor;\n if (typeof key === \"symbol\" || RESERVED_SEGMENT_NAMES.has(key)) return undefined;\n // Cap'n Web's server-side path traversal probes own descriptors before reading a segment, so\n // dynamic roots must look discoverable here to reach the apply trap.\n return { configurable: true, enumerable: true, value: valueFor(key), writable: false };\n },\n has(target, key) {\n if (typeof key === \"symbol\") return key in target;\n return !RESERVED_SEGMENT_NAMES.has(key);\n },\n });\n}\n\n/** Install the hop drawn in the header on a class's PROTOTYPE CHAIN, with the scope `root` (`[\"itx\"]`\n * for the edge context, `[]` for a handle). Call ONCE per class. Constructor inheritance is\n * untouched — only `Class.prototype`'s parent link changes, and the hop forwards everything it does\n * not intercept. */\nexport function installPrototypeInvokeFallback<T extends abstract new (...args: never[]) => object>(\n cls: T,\n root: readonly string[],\n): void {\n const parentPrototype = Object.getPrototypeOf(cls.prototype) as object;\n const hop = new Proxy(Object.create(parentPrototype) as object, {\n get(hopTarget, key, receiver) {\n // Symbols and anything the parent chain already answers (dispose protocol, capnweb\n // internals, Object.prototype) pass through with the instance as receiver.\n if (typeof key === \"symbol\" || key in hopTarget) {\n return Reflect.get(hopTarget, key, receiver);\n }\n if (RESERVED_SEGMENT_NAMES.has(key)) return undefined;\n // The dynamic fallback exists for INSTANCES. A lookup whose receiver is not one — someone\n // probing `Class.prototype.foo` directly, a framework walking prototypes — must see plain\n // \"undefined\", not conjure a dispatcher over an uninitialized receiver.\n if (!(receiver instanceof (cls as unknown as abstract new (...args: never[]) => object))) {\n return undefined;\n }\n // The receiver IS the invoker (it implements invoke). Its method resolves at CALL\n // time, so a trap firing mid-construction (a property miss on `this` before field initializers\n // ran) can't bake a dispatcher over half-initialized state into the path proxy.\n return createItxExpressionPathProxy(receiver as unknown as InvokeTarget, root, [key]);\n },\n });\n Object.setPrototypeOf(cls.prototype, hop);\n}\n\n/** A branded, pipelinable handle for a MID-CHAIN capability (`facets.get(name)`, `cd(path)`,\n * `workers.get(spec)`, a lent stub) whose unknown dotted members reduce into ONE dispatch of the\n * itx-expression STEPS relative to it; the constructor's `dispatch` routes those steps into the\n * underlying object. Declared members (`invoke` / `applyRoot`) win over the fallback, so a\n * capability cannot be named either — the two reserved words this wrapper adds. */\nexport class InvokeHandle extends RpcTarget {\n readonly #dispatchItxExpressionSteps: (itxExpressionSteps: ItxExpression) => unknown;\n constructor(dispatchItxExpressionSteps: (itxExpressionSteps: ItxExpression) => unknown) {\n super();\n this.#dispatchItxExpressionSteps = dispatchItxExpressionSteps;\n }\n /** THE dispatch method the prototype hop reduces onto; the expression is RELATIVE to this handle. */\n invoke(itxExpressionSteps: ItxExpression): unknown {\n return this.#dispatchItxExpressionSteps(itxExpressionSteps);\n }\n /** Call the bare capability this handle fronts — the ANONYMOUS call step (how a rewritten call\n * whose target IS a handle calls it: `handle(events, range)`). */\n applyRoot(args: unknown[]): unknown {\n return this.#dispatchItxExpressionSteps([[\"\", ...args]]);\n }\n}\ninstallPrototypeInvokeFallback(InvokeHandle, []);\n\n/** Walk itx-expression steps off a capnweb stub; the ANONYMOUS call step (`\"\"`) calls the value\n * itself (a bare function lent as a capability). NO await inside the loop: on a capnweb stub every\n * step is a PIPELINED path, so an n-step chain costs ONE round trip, flushed by the caller's single\n * await. A DIRECT call on the stub, never `.apply`: reading `.apply` off a capnweb stub's method is\n * itself a pipelined remote path, and calling it sends the stub as an argument, which a facet stub\n * refuses with a DataCloneError. What a connector over a lent stub or a remote capnweb API walks. */\nexport function walkStepsOnRpcStub(stub: unknown, steps: ItxExpression): unknown {\n let value: unknown = stub;\n for (const step of steps) {\n if (typeof step === \"string\") value = (value as Record<string, unknown>)[step];\n else {\n const [method, ...args] = step;\n value =\n method === \"\"\n ? (value as (...a: unknown[]) => unknown)(...args)\n : (value as Record<string, (...a: unknown[]) => unknown>)[method](...args);\n }\n }\n return value;\n}\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ //#region src/lib.ts
2
+ /** A plain Error carrying `code` (+ optional `data`) as own enumerable properties. */
3
+ function codedError(code, message, data) {
4
+ return Object.assign(new Error(message), data === void 0 ? { code } : {
5
+ code,
6
+ data
7
+ });
8
+ }
9
+ const isRecord = (v) => typeof v === "object" && !!v && !Array.isArray(v);
10
+ /** Structural deep-equal over plain JSON values — order-insensitive, unbudgeted (JSON is acyclic).
11
+ * THE one deep-equal: the live-state diff's "don't emit" test AND the idempotency-body compare
12
+ * (re-exported through stream/processor.ts). The `Object.hasOwn(b, k)` guard is load-bearing — without
13
+ * it, two objects with the same key COUNT but different key SETS compare equal. */
14
+ function jsonEqual(a, b) {
15
+ if (Object.is(a, b)) return true;
16
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));
17
+ if (isRecord(a) && isRecord(b)) {
18
+ const ka = Object.keys(a);
19
+ return ka.length === Object.keys(b).length && ka.every((k) => Object.hasOwn(b, k) && jsonEqual(a[k], b[k]));
20
+ }
21
+ return false;
22
+ }
23
+ async function withTimeout(promise, ms, what) {
24
+ let timer;
25
+ try {
26
+ return await Promise.race([promise, new Promise((_, reject) => {
27
+ timer = setTimeout(() => reject(codedError("TIMEOUT", `${typeof what === "function" ? what() : what}: no answer in ${ms / 1e3}s`)), ms);
28
+ })]);
29
+ } finally {
30
+ if (timer) clearTimeout(timer);
31
+ }
32
+ }
33
+ //#endregion
34
+ export { jsonEqual as n, withTimeout as r, codedError as t };
35
+
36
+ //# sourceMappingURL=lib-BWr-5mFO.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib-BWr-5mFO.mjs","names":[],"sources":["../src/lib.ts"],"sourcesContent":["// lib.ts — the pure helpers every layer shares, one platform-neutral file (it rides the SDK bundle\n// and the Node unit tests, so no cloudflare:workers here):\n// errors — `codedError` / `errorCode` / `reportIssue` / `forwardIssues`: THE machine-readable\n// error channel\n// release — `releaseRpcSessions`: dispose the Workers-RPC values a round trip reached\n// patch — `diff` / `applyPatch` / `jsonEqual`: the live-state delta (an RFC 6902 subset)\n// timeout — `withTimeout`: a promise raced against a deadline (code TIMEOUT)\n// origin — `isSameOriginBrowserRequest`: may a request spend the cookies it carries;\n// `cookieValueOf`: one cookie out of a `Cookie` header;\n// `sameOriginPath`, `isLocalOrigin`, `resolveContextPath`: origins and paths\n\n// ── errors ── THE machine-readable error channel, after cloudflare-os\n// (workshop-shared/src/api.ts: plain Error + a `code` own-property via Object.assign, read with\n// `\"code\" in error`). Why this shape and no other: capnweb coerces custom error NAMES to a\n// builtin whitelist and drops subclass identity, but preserves ALL own enumerable properties\n// across the wire (verified at runtime against @iterate-com/capnweb 0.10.0) — and with\n// enhanced_error_serialization (our compat date) own props survive native Workers-RPC hops too.\n// So: never classify by `name`, `instanceof`, or message regex across a hop; check the code.\n// Human messages stay verbatim and greppable — the code rides beside them, never instead.\n// workerd's own stamped flags (`.retryable`, `.overloaded`, `.durableObjectReset`) ride the same\n// own-property channel; honor them rather than inventing a retry taxonomy.\n\n/** The stable machine-readable codes — SCREAMING_SNAKE, defined once, both ends import this. */\ntype ErrorCode =\n | \"SCHEDULE_LIMIT\" // bounded pending/failed scheduled batches per context\n | \"INVALID_INPUT\"\n | \"IDENTITY_CONFLICT\" // verified login cannot replace another linked identity\n | \"GRANT_NOT_FOUND\" // a caller may revoke only a grant in their own inventory\n | \"NO_ITX_EXPRESSION_MATCH\" // no rewrite rule matches the call (default-deny)\n | \"IDEMPOTENCY_CONFLICT\"\n | \"OFFSET_CONFLICT\" // an input's expected `offset` is not the offset it would land at\n | \"EVENT_TOO_LARGE\" // one event's serialized body is over the append ceiling (stream.ts EVENT_BODY_MAX_CHARS)\n | \"REDUCE_CHECKPOINT_TOO_LARGE\" // a reduce's state would not fit one storage cell (stream/processor.ts)\n | \"EVENT_UNREADABLE\" // a stored row's body is not JSON — `data.offset` names it (stream.ts read)\n | \"STREAM_PAUSED\"\n | \"INVALID_CONTEXT\" // a context name / project id the codec refuses (apps/os context/paths.ts `DurableObjectNameCodec`) — coded, so it survives the hop\n | \"EXPRESSION_TOO_LONG\" // a STRING itx expression over ITX_EXPRESSION_STRING_MAX_CHARS — pass the parsed form\n | \"FACET_SOURCE_TOO_LARGE\" // a facet's literal source over FACET_SOURCE_MAX_CHARS (worker-loader.ts) — refused on entry\n | \"INVALID_CREDENTIALS\" // authenticate(): the admin secret did not match, or the credentials named no known kind\n | \"UNAUTHENTICATED\" // authenticate({ type: \"from-server-cookie\" }): no session cookie on the request, or a cross-origin browser's request\n | \"FORBIDDEN\" // projects.get(project): outside the session's reach (a grant narrowed to other projects, or the user is no member of its org); create on a narrowed grant; grants/consent on a session that carries none; organizations.get outside the session's memberships; `cd` in the global namespace (a global context is reached by identity, never by path); a first-party facet off the context the platform hosts it on, or loaded code in the global namespace (apps/os first-party-facet-placement.ts)\n | \"PROJECT_NAME_TAKEN\" // projects.create({ project }): a project of that slug exists in another org\n | \"RPC_STUB_OFFLINE\" // the rpc stub a row names is neither borrowed nor pager-backed right now — or its lend ended mid-call (recalled, returned, broken; the relay re-codes)\n | \"NOT_A_METHOD\" // the dotted path's terminal segment is not callable on the target\n | \"NO_FACET\" // no facet of that name has been loaded into this context\n | \"FACET_ABORTED\" // the facet instance this call ran on was reset by `itx.facets.abort` (apps/os context/facet-host.ts) — its next call starts it fresh\n | \"FACET_NO_UPGRADE\" // a WebSocket upgrade aimed at a facet: a facet answers RPC and plain HTTP, never a socket — sockets terminate at the edge (apps/os context/facet-host.ts)\n | \"WAIT_TIMEOUT\" // waitForEvent expired with no matching event committed\n | \"TIMEOUT\"; // lib.ts withTimeout: the call did not answer within its deadline\n// (There is no separate boundary-validation library: the append method's own runtime guards\n// throw plain Errors; a client is JUST capnweb, so malformed args surface as ordinary errors.)\n\n/** A plain Error carrying `code` (+ optional `data`) as own enumerable properties. */\nexport function codedError(code: ErrorCode, message: string, data?: unknown): Error {\n return Object.assign(new Error(message), data === undefined ? { code } : { code, data });\n}\n\n/** The code of an error that crossed any number of hops — undefined for uncoded errors. */\nexport function errorCode(error: unknown): ErrorCode | undefined {\n return typeof error === \"object\" && error && \"code\" in error\n ? ((error as { code: unknown }).code as ErrorCode)\n : undefined;\n}\n\n// reportIssue — the ONE exit for unexpected failures (cloudflare-os error-reporting.ts, minus\n// its private Reporter Worker): one bounded console.error line; query event=\"issue\" in Workers\n// Logs. A host forwards issues elsewhere too by `forwardIssues(forward)` (apps/os posthog.ts:\n// PostHog Error Tracking) — capture sites never change. Deliberately NO cloudflare:workers\n// import: this file rides the platform-neutral SDK bundle. Reporting must never disturb the\n// caller — armored end to end; worst case it prints nothing.\n\n// Bounds verbatim from cloudflare-os: hostile strings get clipped, never explode a log line.\nconst MAX = { message: 1024, stack: 16_384, string: 256, attributeKeys: 32 } as const;\ntype Scalar = string | number | boolean | null; // attribute values stay queryable scalars\n\n/** One reported issue as `reportIssue` prints it, and the value that was caught. */\nexport type Issue = {\n failureSite: string;\n caught: unknown;\n attributes: Record<string, Scalar>;\n};\nlet forwardIssue: ((issue: Issue) => void) | undefined;\n\n/** Also hand every issue to `forward` (one per isolate; the last call wins). It runs inside\n * `reportIssue`'s armor: a throw is swallowed. */\nexport function forwardIssues(forward: (issue: Issue) => void): void {\n forwardIssue = forward;\n}\n\n/** Print ONE bounded console.error line for an unexpected failure; never throws. */\nexport function reportIssue(\n failureSite: string,\n caught: unknown,\n attributes?: Record<string, Scalar | undefined>,\n): void {\n try {\n const bounded: Record<string, Scalar> = {};\n for (const [key, value] of Object.entries(attributes || {}).slice(0, MAX.attributeKeys)) {\n if (value === undefined) continue;\n bounded[key.slice(0, MAX.string)] =\n typeof value === \"string\" ? value.slice(0, MAX.string) : value;\n }\n const code = errorCode(caught);\n // The thrown value, bounded — name/message/stack read off an Error; an arbitrary object is\n // never walked.\n const error =\n caught instanceof Error\n ? {\n type: (caught.name || \"Error\").slice(0, MAX.string),\n message: caught.message.slice(0, MAX.message),\n ...(caught.stack && { stack: caught.stack.slice(0, MAX.stack) }),\n }\n : typeof caught === \"object\" && caught\n ? { type: \"ObjectThrown\" }\n : { type: `${typeof caught}Thrown`, message: String(caught).slice(0, MAX.message) };\n console.error({\n ...bounded, // fixed keys spread last so an attribute can never shadow them\n event: \"issue\",\n failureSite: failureSite.slice(0, MAX.string),\n code,\n error,\n });\n forwardIssue?.({ failureSite, caught, attributes: bounded });\n } catch {\n // Reporting must never disturb the caller — swallow and move on.\n }\n}\n\n// ── release ── a Workers-RPC value (a stub, a call's promise) keeps its session, and the actor at\n// its far end, open until disposed.\n\n/** Release each of `rpcSessions`, the last first. The answer they served is already in, so a release\n * that throws is reported, never made the call's failure. */\nexport function releaseRpcSessions(rpcSessions: readonly unknown[]): void {\n for (const rpcSession of [...rpcSessions].reverse())\n try {\n // A session-brand value or an RPC result object carries a disposer; a void call's undefined\n // answer, or a plain value, has nothing to release.\n (rpcSession as Partial<Disposable> | undefined)?.[Symbol.dispose]?.();\n } catch (error) {\n reportIssue(\"itx-expression.release-rpc-session\", error);\n }\n}\n\n// ── patch ── the LiveView-style delta that rides every live-state change event. `diff` runs at\n// the PRODUCER (the processor's reduce, the mini-app helper's set) where old and new value are\n// both in hand; the stream forwards the ops verbatim; only CLIENTS apply them. The format is an\n// RFC 6902 subset (add / replace / remove, JSON-Pointer paths) so any off-the-shelf json-patch\n// library applies our patch payloads — we invent no wire format, we just never emit the exotic ops.\n\nexport type PatchOp =\n | { op: \"add\"; path: string; value: unknown }\n | { op: \"replace\"; path: string; value: unknown }\n | { op: \"remove\"; path: string };\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && !!v && !Array.isArray(v);\n\nconst escape = (seg: string | number): string =>\n String(seg).replaceAll(\"~\", \"~0\").replaceAll(\"/\", \"~1\");\n\n/** Structural deep-equal over plain JSON values — order-insensitive, unbudgeted (JSON is acyclic).\n * THE one deep-equal: the live-state diff's \"don't emit\" test AND the idempotency-body compare\n * (re-exported through stream/processor.ts). The `Object.hasOwn(b, k)` guard is load-bearing — without\n * it, two objects with the same key COUNT but different key SETS compare equal. */\nexport function jsonEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (Array.isArray(a) && Array.isArray(b))\n return a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));\n if (isRecord(a) && isRecord(b)) {\n const ka = Object.keys(a);\n return (\n ka.length === Object.keys(b).length &&\n ka.every((k) => Object.hasOwn(b, k) && jsonEqual(a[k], b[k]))\n );\n }\n return false;\n}\n\n/** Structural diff `a → b`, or undefined when deep-equal (the producer's \"don't emit\" signal).\n * Objects recurse per key; arrays get the chat-log fast paths (pure append → `add …/-` ops,\n * pure tail-truncate → `remove` ops) and are replaced wholesale on any middle divergence —\n * the LiveView trade: optimize the growing log, full-render the rewrite.\n * Both sides are JSON-normalized first — the wire is JSON, so the diff must see exactly what\n * the wire will carry: undefined-valued keys vanish, Dates become their ISO strings, array\n * holes become null. Diffing what you didn't normalize is how a Date change goes silent. */\nexport function diff(a: unknown, b: unknown): PatchOp[] | undefined {\n // stringify can itself return undefined (a bare function, toJSON() → undefined) — parse\n // would then throw SyntaxError('\"undefined\" is not valid JSON'); treat those as absent.\n const json = (v: unknown) => {\n const s = JSON.stringify(v);\n return s ? (JSON.parse(s) as unknown) : undefined;\n };\n return walk(json(a), json(b), \"\");\n}\n\nfunction walk(a: unknown, b: unknown, path: string): PatchOp[] | undefined {\n if (jsonEqual(a, b)) return undefined;\n if (Array.isArray(a) && Array.isArray(b)) {\n const shared = Math.min(a.length, b.length);\n let p = 0;\n while (p < shared && jsonEqual(a[p], b[p])) p++;\n if (p === a.length) return b.slice(p).map((value) => ({ op: \"add\", path: `${path}/-`, value }));\n if (p === b.length)\n return a\n .slice(p)\n .map((_, i) => ({ op: \"remove\", path: `${path}/${a.length - 1 - i}` }) as const);\n return [{ op: \"replace\", path, value: b }];\n }\n if (isRecord(a) && isRecord(b)) {\n const ops: PatchOp[] = [];\n for (const k of Object.keys(a))\n if (!Object.hasOwn(b, k)) ops.push({ op: \"remove\", path: `${path}/${escape(k)}` });\n for (const k of Object.keys(b)) {\n if (!Object.hasOwn(a, k)) ops.push({ op: \"add\", path: `${path}/${escape(k)}`, value: b[k] });\n else ops.push(...(walk(a[k], b[k], `${path}/${escape(k)}`) ?? []));\n }\n return ops.length ? ops : undefined;\n }\n return [{ op: \"replace\", path, value: b }];\n}\n\n/** Apply a patch non-mutatingly (clone-then-mutate). The client half of `diff` — exported\n * through the SDK so subscribers need no third-party json-patch dependency. */\nexport function applyPatch<T>(doc: T, ops: PatchOp[]): T {\n let root: unknown = structuredClone(doc);\n for (const op of ops) {\n if (op.path === \"\") {\n if (op.op === \"remove\") throw new Error(\"applyPatch: cannot remove the document root\");\n root = op.value;\n continue;\n }\n const segs = op.path\n .slice(1)\n .split(\"/\")\n .map((s) => s.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n // Patches arrive over the wire. \"__proto__\" is refused outright (assigning it invokes the\n // prototype setter, not a property write); other prototype members (\"constructor\", …) are\n // ordinary keys — traversal below is own-property-only, so the chain is never walked.\n for (const s of segs)\n if (s === \"__proto__\") throw new Error(`applyPatch: refusing __proto__ in path ${op.path}`);\n const last = segs.pop()!;\n let parent: unknown = root;\n for (const s of segs) {\n parent = Array.isArray(parent)\n ? parent[Number(s)]\n : isRecord(parent) && Object.hasOwn(parent, s)\n ? parent[s]\n : undefined;\n if (parent === undefined) throw new Error(`applyPatch: missing path ${op.path}`);\n }\n if (Array.isArray(parent)) {\n if (op.op === \"add\") {\n if (last === \"-\") parent.push(op.value);\n else parent.splice(Number(last), 0, op.value);\n } else if (op.op === \"replace\") parent[Number(last)] = op.value;\n else parent.splice(Number(last), 1);\n } else if (isRecord(parent)) {\n if (op.op === \"remove\") delete parent[last];\n else parent[last] = op.value;\n } else throw new Error(`applyPatch: path ${op.path} traverses a non-container`);\n }\n return root as T;\n}\n\n// ── timeout ── race a promise against a deadline. The timer is CLEARED on every exit: a leaked\n// timer per call would pin a Durable Object awake. A loss rejects with code TIMEOUT so a caller can\n// tell \"took too long\" from the call's own failure (the DO's facet watchdog aborts the facet on it).\n//\n// `what` may be a THUNK: a label whose construction is expensive (the facet watchdog prints the whole\n// pushed batch) is built ONLY when the deadline is actually lost, off the per-call hot path.\n\nexport async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n what: string | (() => string),\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n codedError(\n \"TIMEOUT\",\n `${typeof what === \"function\" ? what() : what}: no answer in ${ms / 1000}s`,\n ),\n ),\n ms,\n );\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n// ── origin ── the one check that makes an ambient cookie safe to honour (session.ts\n// `from-server-cookie`, the issuer's form posts in apps/os issuer-pages.ts).\n\n/** Whether `request` may spend the cookies it carries: its `Origin` header is this origin, or absent\n * (a non-browser client — curl, a script). A browser stamps the page's origin on every WebSocket\n * handshake, every cross-site fetch and every form POST, so a foreign origin means a foreign site\n * drove the request with the visitor's cookie riding along. A malformed `Origin` (the literal\n * `null` of a sandboxed document included) is foreign. */\nexport function isSameOriginBrowserRequest(request: Pick<Request, \"url\" | \"headers\">): boolean {\n const origin = request.headers.get(\"origin\");\n // oxlint-disable-next-line iterate/simple-truthiness-check -- security: only a truly absent Origin (non-browser client) is trusted; an empty \"\" Origin header must stay foreign, which a truthiness check would wrongly accept\n if (origin === null) return true;\n try {\n return new URL(origin).origin === new URL(request.url).origin;\n } catch {\n return false;\n }\n}\n\n/** The value of the cookie `name` in a `Cookie` header, or null. */\nexport function cookieValueOf(cookieHeader: string | null, name: string): string | null {\n for (const part of (cookieHeader || \"\").split(\";\")) {\n const separator = part.indexOf(\"=\");\n if (separator < 0) continue;\n if (part.slice(0, separator).trim() === name) return part.slice(separator + 1).trim();\n }\n return null;\n}\n\n/** `next` as a path on `origin`, else \"/\" — a redirect never leaves the host: `//evil.example`,\n * `/\\evil.example` and an absolute URL all resolve to a foreign origin and fall back to \"/\". The\n * issuer's login redirect uses it too (apps/os issuer-pages.ts). */\nexport function sameOriginPath(next: string, origin: string): string {\n try {\n const url = new URL(next, origin);\n return url.origin === origin ? url.pathname + url.search : \"/\";\n } catch {\n return \"/\";\n }\n}\n\n/** Only plain HTTP loopback origins use development login and client registration. */\nexport function isLocalOrigin(origin: string) {\n const url = new URL(origin);\n return (\n url.protocol === \"http:\" &&\n (url.hostname === \"localhost\" ||\n url.hostname.endsWith(\".localhost\") ||\n url.hostname === \"127.0.0.1\")\n );\n}\n\n/** Resolve a `cd` target against a context's own path — the one resolver every `cd` (the edge\n * method, the built-in root, the library's relative handles) shares. Absolute (\"/agents/x\") stands alone; relative\n * (\"agents/x\", \"../inbox\", \".\") joins onto `base`. `.` and `..` resolve; the root cannot be\n * escaped (\"/..\" is \"/\"). The result is canonical: leading slash, no trailing slash but for \"/\". */\nexport function resolveContextPath(basePath: string, contextPath: string): string {\n const segments: string[] = [];\n for (const seg of `${contextPath.startsWith(\"/\") ? \"\" : basePath}/${contextPath}`.split(\"/\")) {\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") segments.pop();\n else segments.push(seg);\n }\n return `/${segments.join(\"/\")}`;\n}\n"],"mappings":";;AAqDA,SAAgB,WAAW,MAAiB,SAAiB,MAAuB;CAClF,OAAO,OAAO,OAAO,IAAI,MAAM,OAAO,GAAG,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI;EAAE;EAAM;CAAK,CAAC;AACzF;AAoGA,MAAM,YAAY,MAChB,OAAO,MAAM,YAAY,CAAC,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC;;;;;AASlD,SAAgB,UAAU,GAAY,GAAqB;CACzD,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,OAAO;CAC5B,IAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GACrC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,UAAU,GAAG,EAAE,EAAE,CAAC;CACtE,IAAI,SAAS,CAAC,KAAK,SAAS,CAAC,GAAG;EAC9B,MAAM,KAAK,OAAO,KAAK,CAAC;EACxB,OACE,GAAG,WAAW,OAAO,KAAK,CAAC,CAAC,CAAC,UAC7B,GAAG,OAAO,MAAM,OAAO,OAAO,GAAG,CAAC,KAAK,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;CAEhE;CACA,OAAO;AACT;AA+FA,eAAsB,YACpB,SACA,IACA,MACY;CACZ,IAAI;CACJ,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,CACxB,SACA,IAAI,SAAgB,GAAG,WAAW;GAChC,QAAQ,iBAEJ,OACE,WACE,WACA,GAAG,OAAO,SAAS,aAAa,KAAK,IAAI,KAAK,iBAAiB,KAAK,IAAK,EAC3E,CACF,GACF,EACF;EACF,CAAC,CACH,CAAC;CACH,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF"}
package/dist/lib.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ /** The stable machine-readable codes — SCREAMING_SNAKE, defined once, both ends import this. */
2
+ type ErrorCode = "SCHEDULE_LIMIT" | "INVALID_INPUT" | "IDENTITY_CONFLICT" | "GRANT_NOT_FOUND" | "NO_ITX_EXPRESSION_MATCH" | "IDEMPOTENCY_CONFLICT" | "OFFSET_CONFLICT" | "EVENT_TOO_LARGE" | "REDUCE_CHECKPOINT_TOO_LARGE" | "EVENT_UNREADABLE" | "STREAM_PAUSED" | "INVALID_CONTEXT" | "EXPRESSION_TOO_LONG" | "FACET_SOURCE_TOO_LARGE" | "INVALID_CREDENTIALS" | "UNAUTHENTICATED" | "FORBIDDEN" | "PROJECT_NAME_TAKEN" | "RPC_STUB_OFFLINE" | "NOT_A_METHOD" | "NO_FACET" | "FACET_ABORTED" | "FACET_NO_UPGRADE" | "WAIT_TIMEOUT" | "TIMEOUT";
3
+ /** A plain Error carrying `code` (+ optional `data`) as own enumerable properties. */
4
+ export declare function codedError(code: ErrorCode, message: string, data?: unknown): Error;
5
+ /** The code of an error that crossed any number of hops — undefined for uncoded errors. */
6
+ export declare function errorCode(error: unknown): ErrorCode | undefined;
7
+ type Scalar = string | number | boolean | null;
8
+ /** One reported issue as `reportIssue` prints it, and the value that was caught. */
9
+ export type Issue = {
10
+ failureSite: string;
11
+ caught: unknown;
12
+ attributes: Record<string, Scalar>;
13
+ };
14
+ /** Also hand every issue to `forward` (one per isolate; the last call wins). It runs inside
15
+ * `reportIssue`'s armor: a throw is swallowed. */
16
+ export declare function forwardIssues(forward: (issue: Issue) => void): void;
17
+ /** Print ONE bounded console.error line for an unexpected failure; never throws. */
18
+ export declare function reportIssue(failureSite: string, caught: unknown, attributes?: Record<string, Scalar | undefined>): void;
19
+ /** Release each of `rpcSessions`, the last first. The answer they served is already in, so a release
20
+ * that throws is reported, never made the call's failure. */
21
+ export declare function releaseRpcSessions(rpcSessions: readonly unknown[]): void;
22
+ export type PatchOp = {
23
+ op: "add";
24
+ path: string;
25
+ value: unknown;
26
+ } | {
27
+ op: "replace";
28
+ path: string;
29
+ value: unknown;
30
+ } | {
31
+ op: "remove";
32
+ path: string;
33
+ };
34
+ /** Structural deep-equal over plain JSON values — order-insensitive, unbudgeted (JSON is acyclic).
35
+ * THE one deep-equal: the live-state diff's "don't emit" test AND the idempotency-body compare
36
+ * (re-exported through stream/processor.ts). The `Object.hasOwn(b, k)` guard is load-bearing — without
37
+ * it, two objects with the same key COUNT but different key SETS compare equal. */
38
+ export declare function jsonEqual(a: unknown, b: unknown): boolean;
39
+ /** Structural diff `a → b`, or undefined when deep-equal (the producer's "don't emit" signal).
40
+ * Objects recurse per key; arrays get the chat-log fast paths (pure append → `add …/-` ops,
41
+ * pure tail-truncate → `remove` ops) and are replaced wholesale on any middle divergence —
42
+ * the LiveView trade: optimize the growing log, full-render the rewrite.
43
+ * Both sides are JSON-normalized first — the wire is JSON, so the diff must see exactly what
44
+ * the wire will carry: undefined-valued keys vanish, Dates become their ISO strings, array
45
+ * holes become null. Diffing what you didn't normalize is how a Date change goes silent. */
46
+ export declare function diff(a: unknown, b: unknown): PatchOp[] | undefined;
47
+ /** Apply a patch non-mutatingly (clone-then-mutate). The client half of `diff` — exported
48
+ * through the SDK so subscribers need no third-party json-patch dependency. */
49
+ export declare function applyPatch<T>(doc: T, ops: PatchOp[]): T;
50
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number, what: string | (() => string)): Promise<T>;
51
+ /** Whether `request` may spend the cookies it carries: its `Origin` header is this origin, or absent
52
+ * (a non-browser client — curl, a script). A browser stamps the page's origin on every WebSocket
53
+ * handshake, every cross-site fetch and every form POST, so a foreign origin means a foreign site
54
+ * drove the request with the visitor's cookie riding along. A malformed `Origin` (the literal
55
+ * `null` of a sandboxed document included) is foreign. */
56
+ export declare function isSameOriginBrowserRequest(request: Pick<Request, "url" | "headers">): boolean;
57
+ /** The value of the cookie `name` in a `Cookie` header, or null. */
58
+ export declare function cookieValueOf(cookieHeader: string | null, name: string): string | null;
59
+ /** `next` as a path on `origin`, else "/" — a redirect never leaves the host: `//evil.example`,
60
+ * `/\evil.example` and an absolute URL all resolve to a foreign origin and fall back to "/". The
61
+ * issuer's login redirect uses it too (apps/os issuer-pages.ts). */
62
+ export declare function sameOriginPath(next: string, origin: string): string;
63
+ /** Only plain HTTP loopback origins use development login and client registration. */
64
+ export declare function isLocalOrigin(origin: string): boolean;
65
+ /** Resolve a `cd` target against a context's own path — the one resolver every `cd` (the edge
66
+ * method, the built-in root, the library's relative handles) shares. Absolute ("/agents/x") stands alone; relative
67
+ * ("agents/x", "../inbox", ".") joins onto `base`. `.` and `..` resolve; the root cannot be
68
+ * escaped ("/.." is "/"). The result is canonical: leading slash, no trailing slash but for "/". */
69
+ export declare function resolveContextPath(basePath: string, contextPath: string): string;
70
+ export {};
package/dist/lib.mjs ADDED
@@ -0,0 +1,228 @@
1
+ //#region src/lib.ts
2
+ /** A plain Error carrying `code` (+ optional `data`) as own enumerable properties. */
3
+ function codedError(code, message, data) {
4
+ return Object.assign(new Error(message), data === void 0 ? { code } : {
5
+ code,
6
+ data
7
+ });
8
+ }
9
+ /** The code of an error that crossed any number of hops — undefined for uncoded errors. */
10
+ function errorCode(error) {
11
+ return typeof error === "object" && error && "code" in error ? error.code : void 0;
12
+ }
13
+ const MAX = {
14
+ message: 1024,
15
+ stack: 16384,
16
+ string: 256,
17
+ attributeKeys: 32
18
+ };
19
+ let forwardIssue;
20
+ /** Also hand every issue to `forward` (one per isolate; the last call wins). It runs inside
21
+ * `reportIssue`'s armor: a throw is swallowed. */
22
+ function forwardIssues(forward) {
23
+ forwardIssue = forward;
24
+ }
25
+ /** Print ONE bounded console.error line for an unexpected failure; never throws. */
26
+ function reportIssue(failureSite, caught, attributes) {
27
+ try {
28
+ const bounded = {};
29
+ for (const [key, value] of Object.entries(attributes || {}).slice(0, MAX.attributeKeys)) {
30
+ if (value === void 0) continue;
31
+ bounded[key.slice(0, MAX.string)] = typeof value === "string" ? value.slice(0, MAX.string) : value;
32
+ }
33
+ const code = errorCode(caught);
34
+ const error = caught instanceof Error ? {
35
+ type: (caught.name || "Error").slice(0, MAX.string),
36
+ message: caught.message.slice(0, MAX.message),
37
+ ...caught.stack && { stack: caught.stack.slice(0, MAX.stack) }
38
+ } : typeof caught === "object" && caught ? { type: "ObjectThrown" } : {
39
+ type: `${typeof caught}Thrown`,
40
+ message: String(caught).slice(0, MAX.message)
41
+ };
42
+ console.error({
43
+ ...bounded,
44
+ event: "issue",
45
+ failureSite: failureSite.slice(0, MAX.string),
46
+ code,
47
+ error
48
+ });
49
+ forwardIssue?.({
50
+ failureSite,
51
+ caught,
52
+ attributes: bounded
53
+ });
54
+ } catch {}
55
+ }
56
+ /** Release each of `rpcSessions`, the last first. The answer they served is already in, so a release
57
+ * that throws is reported, never made the call's failure. */
58
+ function releaseRpcSessions(rpcSessions) {
59
+ for (const rpcSession of [...rpcSessions].reverse()) try {
60
+ rpcSession?.[Symbol.dispose]?.();
61
+ } catch (error) {
62
+ reportIssue("itx-expression.release-rpc-session", error);
63
+ }
64
+ }
65
+ const isRecord = (v) => typeof v === "object" && !!v && !Array.isArray(v);
66
+ const escape = (seg) => String(seg).replaceAll("~", "~0").replaceAll("/", "~1");
67
+ /** Structural deep-equal over plain JSON values — order-insensitive, unbudgeted (JSON is acyclic).
68
+ * THE one deep-equal: the live-state diff's "don't emit" test AND the idempotency-body compare
69
+ * (re-exported through stream/processor.ts). The `Object.hasOwn(b, k)` guard is load-bearing — without
70
+ * it, two objects with the same key COUNT but different key SETS compare equal. */
71
+ function jsonEqual(a, b) {
72
+ if (Object.is(a, b)) return true;
73
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));
74
+ if (isRecord(a) && isRecord(b)) {
75
+ const ka = Object.keys(a);
76
+ return ka.length === Object.keys(b).length && ka.every((k) => Object.hasOwn(b, k) && jsonEqual(a[k], b[k]));
77
+ }
78
+ return false;
79
+ }
80
+ /** Structural diff `a → b`, or undefined when deep-equal (the producer's "don't emit" signal).
81
+ * Objects recurse per key; arrays get the chat-log fast paths (pure append → `add …/-` ops,
82
+ * pure tail-truncate → `remove` ops) and are replaced wholesale on any middle divergence —
83
+ * the LiveView trade: optimize the growing log, full-render the rewrite.
84
+ * Both sides are JSON-normalized first — the wire is JSON, so the diff must see exactly what
85
+ * the wire will carry: undefined-valued keys vanish, Dates become their ISO strings, array
86
+ * holes become null. Diffing what you didn't normalize is how a Date change goes silent. */
87
+ function diff(a, b) {
88
+ const json = (v) => {
89
+ const s = JSON.stringify(v);
90
+ return s ? JSON.parse(s) : void 0;
91
+ };
92
+ return walk(json(a), json(b), "");
93
+ }
94
+ function walk(a, b, path) {
95
+ if (jsonEqual(a, b)) return void 0;
96
+ if (Array.isArray(a) && Array.isArray(b)) {
97
+ const shared = Math.min(a.length, b.length);
98
+ let p = 0;
99
+ while (p < shared && jsonEqual(a[p], b[p])) p++;
100
+ if (p === a.length) return b.slice(p).map((value) => ({
101
+ op: "add",
102
+ path: `${path}/-`,
103
+ value
104
+ }));
105
+ if (p === b.length) return a.slice(p).map((_, i) => ({
106
+ op: "remove",
107
+ path: `${path}/${a.length - 1 - i}`
108
+ }));
109
+ return [{
110
+ op: "replace",
111
+ path,
112
+ value: b
113
+ }];
114
+ }
115
+ if (isRecord(a) && isRecord(b)) {
116
+ const ops = [];
117
+ for (const k of Object.keys(a)) if (!Object.hasOwn(b, k)) ops.push({
118
+ op: "remove",
119
+ path: `${path}/${escape(k)}`
120
+ });
121
+ for (const k of Object.keys(b)) if (!Object.hasOwn(a, k)) ops.push({
122
+ op: "add",
123
+ path: `${path}/${escape(k)}`,
124
+ value: b[k]
125
+ });
126
+ else ops.push(...walk(a[k], b[k], `${path}/${escape(k)}`) ?? []);
127
+ return ops.length ? ops : void 0;
128
+ }
129
+ return [{
130
+ op: "replace",
131
+ path,
132
+ value: b
133
+ }];
134
+ }
135
+ /** Apply a patch non-mutatingly (clone-then-mutate). The client half of `diff` — exported
136
+ * through the SDK so subscribers need no third-party json-patch dependency. */
137
+ function applyPatch(doc, ops) {
138
+ let root = structuredClone(doc);
139
+ for (const op of ops) {
140
+ if (op.path === "") {
141
+ if (op.op === "remove") throw new Error("applyPatch: cannot remove the document root");
142
+ root = op.value;
143
+ continue;
144
+ }
145
+ const segs = op.path.slice(1).split("/").map((s) => s.replaceAll("~1", "/").replaceAll("~0", "~"));
146
+ for (const s of segs) if (s === "__proto__") throw new Error(`applyPatch: refusing __proto__ in path ${op.path}`);
147
+ const last = segs.pop();
148
+ let parent = root;
149
+ for (const s of segs) {
150
+ parent = Array.isArray(parent) ? parent[Number(s)] : isRecord(parent) && Object.hasOwn(parent, s) ? parent[s] : void 0;
151
+ if (parent === void 0) throw new Error(`applyPatch: missing path ${op.path}`);
152
+ }
153
+ if (Array.isArray(parent)) if (op.op === "add") if (last === "-") parent.push(op.value);
154
+ else parent.splice(Number(last), 0, op.value);
155
+ else if (op.op === "replace") parent[Number(last)] = op.value;
156
+ else parent.splice(Number(last), 1);
157
+ else if (isRecord(parent)) if (op.op === "remove") delete parent[last];
158
+ else parent[last] = op.value;
159
+ else throw new Error(`applyPatch: path ${op.path} traverses a non-container`);
160
+ }
161
+ return root;
162
+ }
163
+ async function withTimeout(promise, ms, what) {
164
+ let timer;
165
+ try {
166
+ return await Promise.race([promise, new Promise((_, reject) => {
167
+ timer = setTimeout(() => reject(codedError("TIMEOUT", `${typeof what === "function" ? what() : what}: no answer in ${ms / 1e3}s`)), ms);
168
+ })]);
169
+ } finally {
170
+ if (timer) clearTimeout(timer);
171
+ }
172
+ }
173
+ /** Whether `request` may spend the cookies it carries: its `Origin` header is this origin, or absent
174
+ * (a non-browser client — curl, a script). A browser stamps the page's origin on every WebSocket
175
+ * handshake, every cross-site fetch and every form POST, so a foreign origin means a foreign site
176
+ * drove the request with the visitor's cookie riding along. A malformed `Origin` (the literal
177
+ * `null` of a sandboxed document included) is foreign. */
178
+ function isSameOriginBrowserRequest(request) {
179
+ const origin = request.headers.get("origin");
180
+ if (origin === null) return true;
181
+ try {
182
+ return new URL(origin).origin === new URL(request.url).origin;
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+ /** The value of the cookie `name` in a `Cookie` header, or null. */
188
+ function cookieValueOf(cookieHeader, name) {
189
+ for (const part of (cookieHeader || "").split(";")) {
190
+ const separator = part.indexOf("=");
191
+ if (separator < 0) continue;
192
+ if (part.slice(0, separator).trim() === name) return part.slice(separator + 1).trim();
193
+ }
194
+ return null;
195
+ }
196
+ /** `next` as a path on `origin`, else "/" — a redirect never leaves the host: `//evil.example`,
197
+ * `/\evil.example` and an absolute URL all resolve to a foreign origin and fall back to "/". The
198
+ * issuer's login redirect uses it too (apps/os issuer-pages.ts). */
199
+ function sameOriginPath(next, origin) {
200
+ try {
201
+ const url = new URL(next, origin);
202
+ return url.origin === origin ? url.pathname + url.search : "/";
203
+ } catch {
204
+ return "/";
205
+ }
206
+ }
207
+ /** Only plain HTTP loopback origins use development login and client registration. */
208
+ function isLocalOrigin(origin) {
209
+ const url = new URL(origin);
210
+ return url.protocol === "http:" && (url.hostname === "localhost" || url.hostname.endsWith(".localhost") || url.hostname === "127.0.0.1");
211
+ }
212
+ /** Resolve a `cd` target against a context's own path — the one resolver every `cd` (the edge
213
+ * method, the built-in root, the library's relative handles) shares. Absolute ("/agents/x") stands alone; relative
214
+ * ("agents/x", "../inbox", ".") joins onto `base`. `.` and `..` resolve; the root cannot be
215
+ * escaped ("/.." is "/"). The result is canonical: leading slash, no trailing slash but for "/". */
216
+ function resolveContextPath(basePath, contextPath) {
217
+ const segments = [];
218
+ for (const seg of `${contextPath.startsWith("/") ? "" : basePath}/${contextPath}`.split("/")) {
219
+ if (seg === "" || seg === ".") continue;
220
+ if (seg === "..") segments.pop();
221
+ else segments.push(seg);
222
+ }
223
+ return `/${segments.join("/")}`;
224
+ }
225
+ //#endregion
226
+ export { applyPatch, codedError, cookieValueOf, diff, errorCode, forwardIssues, isLocalOrigin, isSameOriginBrowserRequest, jsonEqual, releaseRpcSessions, reportIssue, resolveContextPath, sameOriginPath, withTimeout };
227
+
228
+ //# sourceMappingURL=lib.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.mjs","names":[],"sources":["../src/lib.ts"],"sourcesContent":["// lib.ts — the pure helpers every layer shares, one platform-neutral file (it rides the SDK bundle\n// and the Node unit tests, so no cloudflare:workers here):\n// errors — `codedError` / `errorCode` / `reportIssue` / `forwardIssues`: THE machine-readable\n// error channel\n// release — `releaseRpcSessions`: dispose the Workers-RPC values a round trip reached\n// patch — `diff` / `applyPatch` / `jsonEqual`: the live-state delta (an RFC 6902 subset)\n// timeout — `withTimeout`: a promise raced against a deadline (code TIMEOUT)\n// origin — `isSameOriginBrowserRequest`: may a request spend the cookies it carries;\n// `cookieValueOf`: one cookie out of a `Cookie` header;\n// `sameOriginPath`, `isLocalOrigin`, `resolveContextPath`: origins and paths\n\n// ── errors ── THE machine-readable error channel, after cloudflare-os\n// (workshop-shared/src/api.ts: plain Error + a `code` own-property via Object.assign, read with\n// `\"code\" in error`). Why this shape and no other: capnweb coerces custom error NAMES to a\n// builtin whitelist and drops subclass identity, but preserves ALL own enumerable properties\n// across the wire (verified at runtime against @iterate-com/capnweb 0.10.0) — and with\n// enhanced_error_serialization (our compat date) own props survive native Workers-RPC hops too.\n// So: never classify by `name`, `instanceof`, or message regex across a hop; check the code.\n// Human messages stay verbatim and greppable — the code rides beside them, never instead.\n// workerd's own stamped flags (`.retryable`, `.overloaded`, `.durableObjectReset`) ride the same\n// own-property channel; honor them rather than inventing a retry taxonomy.\n\n/** The stable machine-readable codes — SCREAMING_SNAKE, defined once, both ends import this. */\ntype ErrorCode =\n | \"SCHEDULE_LIMIT\" // bounded pending/failed scheduled batches per context\n | \"INVALID_INPUT\"\n | \"IDENTITY_CONFLICT\" // verified login cannot replace another linked identity\n | \"GRANT_NOT_FOUND\" // a caller may revoke only a grant in their own inventory\n | \"NO_ITX_EXPRESSION_MATCH\" // no rewrite rule matches the call (default-deny)\n | \"IDEMPOTENCY_CONFLICT\"\n | \"OFFSET_CONFLICT\" // an input's expected `offset` is not the offset it would land at\n | \"EVENT_TOO_LARGE\" // one event's serialized body is over the append ceiling (stream.ts EVENT_BODY_MAX_CHARS)\n | \"REDUCE_CHECKPOINT_TOO_LARGE\" // a reduce's state would not fit one storage cell (stream/processor.ts)\n | \"EVENT_UNREADABLE\" // a stored row's body is not JSON — `data.offset` names it (stream.ts read)\n | \"STREAM_PAUSED\"\n | \"INVALID_CONTEXT\" // a context name / project id the codec refuses (apps/os context/paths.ts `DurableObjectNameCodec`) — coded, so it survives the hop\n | \"EXPRESSION_TOO_LONG\" // a STRING itx expression over ITX_EXPRESSION_STRING_MAX_CHARS — pass the parsed form\n | \"FACET_SOURCE_TOO_LARGE\" // a facet's literal source over FACET_SOURCE_MAX_CHARS (worker-loader.ts) — refused on entry\n | \"INVALID_CREDENTIALS\" // authenticate(): the admin secret did not match, or the credentials named no known kind\n | \"UNAUTHENTICATED\" // authenticate({ type: \"from-server-cookie\" }): no session cookie on the request, or a cross-origin browser's request\n | \"FORBIDDEN\" // projects.get(project): outside the session's reach (a grant narrowed to other projects, or the user is no member of its org); create on a narrowed grant; grants/consent on a session that carries none; organizations.get outside the session's memberships; `cd` in the global namespace (a global context is reached by identity, never by path); a first-party facet off the context the platform hosts it on, or loaded code in the global namespace (apps/os first-party-facet-placement.ts)\n | \"PROJECT_NAME_TAKEN\" // projects.create({ project }): a project of that slug exists in another org\n | \"RPC_STUB_OFFLINE\" // the rpc stub a row names is neither borrowed nor pager-backed right now — or its lend ended mid-call (recalled, returned, broken; the relay re-codes)\n | \"NOT_A_METHOD\" // the dotted path's terminal segment is not callable on the target\n | \"NO_FACET\" // no facet of that name has been loaded into this context\n | \"FACET_ABORTED\" // the facet instance this call ran on was reset by `itx.facets.abort` (apps/os context/facet-host.ts) — its next call starts it fresh\n | \"FACET_NO_UPGRADE\" // a WebSocket upgrade aimed at a facet: a facet answers RPC and plain HTTP, never a socket — sockets terminate at the edge (apps/os context/facet-host.ts)\n | \"WAIT_TIMEOUT\" // waitForEvent expired with no matching event committed\n | \"TIMEOUT\"; // lib.ts withTimeout: the call did not answer within its deadline\n// (There is no separate boundary-validation library: the append method's own runtime guards\n// throw plain Errors; a client is JUST capnweb, so malformed args surface as ordinary errors.)\n\n/** A plain Error carrying `code` (+ optional `data`) as own enumerable properties. */\nexport function codedError(code: ErrorCode, message: string, data?: unknown): Error {\n return Object.assign(new Error(message), data === undefined ? { code } : { code, data });\n}\n\n/** The code of an error that crossed any number of hops — undefined for uncoded errors. */\nexport function errorCode(error: unknown): ErrorCode | undefined {\n return typeof error === \"object\" && error && \"code\" in error\n ? ((error as { code: unknown }).code as ErrorCode)\n : undefined;\n}\n\n// reportIssue — the ONE exit for unexpected failures (cloudflare-os error-reporting.ts, minus\n// its private Reporter Worker): one bounded console.error line; query event=\"issue\" in Workers\n// Logs. A host forwards issues elsewhere too by `forwardIssues(forward)` (apps/os posthog.ts:\n// PostHog Error Tracking) — capture sites never change. Deliberately NO cloudflare:workers\n// import: this file rides the platform-neutral SDK bundle. Reporting must never disturb the\n// caller — armored end to end; worst case it prints nothing.\n\n// Bounds verbatim from cloudflare-os: hostile strings get clipped, never explode a log line.\nconst MAX = { message: 1024, stack: 16_384, string: 256, attributeKeys: 32 } as const;\ntype Scalar = string | number | boolean | null; // attribute values stay queryable scalars\n\n/** One reported issue as `reportIssue` prints it, and the value that was caught. */\nexport type Issue = {\n failureSite: string;\n caught: unknown;\n attributes: Record<string, Scalar>;\n};\nlet forwardIssue: ((issue: Issue) => void) | undefined;\n\n/** Also hand every issue to `forward` (one per isolate; the last call wins). It runs inside\n * `reportIssue`'s armor: a throw is swallowed. */\nexport function forwardIssues(forward: (issue: Issue) => void): void {\n forwardIssue = forward;\n}\n\n/** Print ONE bounded console.error line for an unexpected failure; never throws. */\nexport function reportIssue(\n failureSite: string,\n caught: unknown,\n attributes?: Record<string, Scalar | undefined>,\n): void {\n try {\n const bounded: Record<string, Scalar> = {};\n for (const [key, value] of Object.entries(attributes || {}).slice(0, MAX.attributeKeys)) {\n if (value === undefined) continue;\n bounded[key.slice(0, MAX.string)] =\n typeof value === \"string\" ? value.slice(0, MAX.string) : value;\n }\n const code = errorCode(caught);\n // The thrown value, bounded — name/message/stack read off an Error; an arbitrary object is\n // never walked.\n const error =\n caught instanceof Error\n ? {\n type: (caught.name || \"Error\").slice(0, MAX.string),\n message: caught.message.slice(0, MAX.message),\n ...(caught.stack && { stack: caught.stack.slice(0, MAX.stack) }),\n }\n : typeof caught === \"object\" && caught\n ? { type: \"ObjectThrown\" }\n : { type: `${typeof caught}Thrown`, message: String(caught).slice(0, MAX.message) };\n console.error({\n ...bounded, // fixed keys spread last so an attribute can never shadow them\n event: \"issue\",\n failureSite: failureSite.slice(0, MAX.string),\n code,\n error,\n });\n forwardIssue?.({ failureSite, caught, attributes: bounded });\n } catch {\n // Reporting must never disturb the caller — swallow and move on.\n }\n}\n\n// ── release ── a Workers-RPC value (a stub, a call's promise) keeps its session, and the actor at\n// its far end, open until disposed.\n\n/** Release each of `rpcSessions`, the last first. The answer they served is already in, so a release\n * that throws is reported, never made the call's failure. */\nexport function releaseRpcSessions(rpcSessions: readonly unknown[]): void {\n for (const rpcSession of [...rpcSessions].reverse())\n try {\n // A session-brand value or an RPC result object carries a disposer; a void call's undefined\n // answer, or a plain value, has nothing to release.\n (rpcSession as Partial<Disposable> | undefined)?.[Symbol.dispose]?.();\n } catch (error) {\n reportIssue(\"itx-expression.release-rpc-session\", error);\n }\n}\n\n// ── patch ── the LiveView-style delta that rides every live-state change event. `diff` runs at\n// the PRODUCER (the processor's reduce, the mini-app helper's set) where old and new value are\n// both in hand; the stream forwards the ops verbatim; only CLIENTS apply them. The format is an\n// RFC 6902 subset (add / replace / remove, JSON-Pointer paths) so any off-the-shelf json-patch\n// library applies our patch payloads — we invent no wire format, we just never emit the exotic ops.\n\nexport type PatchOp =\n | { op: \"add\"; path: string; value: unknown }\n | { op: \"replace\"; path: string; value: unknown }\n | { op: \"remove\"; path: string };\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && !!v && !Array.isArray(v);\n\nconst escape = (seg: string | number): string =>\n String(seg).replaceAll(\"~\", \"~0\").replaceAll(\"/\", \"~1\");\n\n/** Structural deep-equal over plain JSON values — order-insensitive, unbudgeted (JSON is acyclic).\n * THE one deep-equal: the live-state diff's \"don't emit\" test AND the idempotency-body compare\n * (re-exported through stream/processor.ts). The `Object.hasOwn(b, k)` guard is load-bearing — without\n * it, two objects with the same key COUNT but different key SETS compare equal. */\nexport function jsonEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (Array.isArray(a) && Array.isArray(b))\n return a.length === b.length && a.every((v, i) => jsonEqual(v, b[i]));\n if (isRecord(a) && isRecord(b)) {\n const ka = Object.keys(a);\n return (\n ka.length === Object.keys(b).length &&\n ka.every((k) => Object.hasOwn(b, k) && jsonEqual(a[k], b[k]))\n );\n }\n return false;\n}\n\n/** Structural diff `a → b`, or undefined when deep-equal (the producer's \"don't emit\" signal).\n * Objects recurse per key; arrays get the chat-log fast paths (pure append → `add …/-` ops,\n * pure tail-truncate → `remove` ops) and are replaced wholesale on any middle divergence —\n * the LiveView trade: optimize the growing log, full-render the rewrite.\n * Both sides are JSON-normalized first — the wire is JSON, so the diff must see exactly what\n * the wire will carry: undefined-valued keys vanish, Dates become their ISO strings, array\n * holes become null. Diffing what you didn't normalize is how a Date change goes silent. */\nexport function diff(a: unknown, b: unknown): PatchOp[] | undefined {\n // stringify can itself return undefined (a bare function, toJSON() → undefined) — parse\n // would then throw SyntaxError('\"undefined\" is not valid JSON'); treat those as absent.\n const json = (v: unknown) => {\n const s = JSON.stringify(v);\n return s ? (JSON.parse(s) as unknown) : undefined;\n };\n return walk(json(a), json(b), \"\");\n}\n\nfunction walk(a: unknown, b: unknown, path: string): PatchOp[] | undefined {\n if (jsonEqual(a, b)) return undefined;\n if (Array.isArray(a) && Array.isArray(b)) {\n const shared = Math.min(a.length, b.length);\n let p = 0;\n while (p < shared && jsonEqual(a[p], b[p])) p++;\n if (p === a.length) return b.slice(p).map((value) => ({ op: \"add\", path: `${path}/-`, value }));\n if (p === b.length)\n return a\n .slice(p)\n .map((_, i) => ({ op: \"remove\", path: `${path}/${a.length - 1 - i}` }) as const);\n return [{ op: \"replace\", path, value: b }];\n }\n if (isRecord(a) && isRecord(b)) {\n const ops: PatchOp[] = [];\n for (const k of Object.keys(a))\n if (!Object.hasOwn(b, k)) ops.push({ op: \"remove\", path: `${path}/${escape(k)}` });\n for (const k of Object.keys(b)) {\n if (!Object.hasOwn(a, k)) ops.push({ op: \"add\", path: `${path}/${escape(k)}`, value: b[k] });\n else ops.push(...(walk(a[k], b[k], `${path}/${escape(k)}`) ?? []));\n }\n return ops.length ? ops : undefined;\n }\n return [{ op: \"replace\", path, value: b }];\n}\n\n/** Apply a patch non-mutatingly (clone-then-mutate). The client half of `diff` — exported\n * through the SDK so subscribers need no third-party json-patch dependency. */\nexport function applyPatch<T>(doc: T, ops: PatchOp[]): T {\n let root: unknown = structuredClone(doc);\n for (const op of ops) {\n if (op.path === \"\") {\n if (op.op === \"remove\") throw new Error(\"applyPatch: cannot remove the document root\");\n root = op.value;\n continue;\n }\n const segs = op.path\n .slice(1)\n .split(\"/\")\n .map((s) => s.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n // Patches arrive over the wire. \"__proto__\" is refused outright (assigning it invokes the\n // prototype setter, not a property write); other prototype members (\"constructor\", …) are\n // ordinary keys — traversal below is own-property-only, so the chain is never walked.\n for (const s of segs)\n if (s === \"__proto__\") throw new Error(`applyPatch: refusing __proto__ in path ${op.path}`);\n const last = segs.pop()!;\n let parent: unknown = root;\n for (const s of segs) {\n parent = Array.isArray(parent)\n ? parent[Number(s)]\n : isRecord(parent) && Object.hasOwn(parent, s)\n ? parent[s]\n : undefined;\n if (parent === undefined) throw new Error(`applyPatch: missing path ${op.path}`);\n }\n if (Array.isArray(parent)) {\n if (op.op === \"add\") {\n if (last === \"-\") parent.push(op.value);\n else parent.splice(Number(last), 0, op.value);\n } else if (op.op === \"replace\") parent[Number(last)] = op.value;\n else parent.splice(Number(last), 1);\n } else if (isRecord(parent)) {\n if (op.op === \"remove\") delete parent[last];\n else parent[last] = op.value;\n } else throw new Error(`applyPatch: path ${op.path} traverses a non-container`);\n }\n return root as T;\n}\n\n// ── timeout ── race a promise against a deadline. The timer is CLEARED on every exit: a leaked\n// timer per call would pin a Durable Object awake. A loss rejects with code TIMEOUT so a caller can\n// tell \"took too long\" from the call's own failure (the DO's facet watchdog aborts the facet on it).\n//\n// `what` may be a THUNK: a label whose construction is expensive (the facet watchdog prints the whole\n// pushed batch) is built ONLY when the deadline is actually lost, off the per-call hot path.\n\nexport async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n what: string | (() => string),\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n codedError(\n \"TIMEOUT\",\n `${typeof what === \"function\" ? what() : what}: no answer in ${ms / 1000}s`,\n ),\n ),\n ms,\n );\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n// ── origin ── the one check that makes an ambient cookie safe to honour (session.ts\n// `from-server-cookie`, the issuer's form posts in apps/os issuer-pages.ts).\n\n/** Whether `request` may spend the cookies it carries: its `Origin` header is this origin, or absent\n * (a non-browser client — curl, a script). A browser stamps the page's origin on every WebSocket\n * handshake, every cross-site fetch and every form POST, so a foreign origin means a foreign site\n * drove the request with the visitor's cookie riding along. A malformed `Origin` (the literal\n * `null` of a sandboxed document included) is foreign. */\nexport function isSameOriginBrowserRequest(request: Pick<Request, \"url\" | \"headers\">): boolean {\n const origin = request.headers.get(\"origin\");\n // oxlint-disable-next-line iterate/simple-truthiness-check -- security: only a truly absent Origin (non-browser client) is trusted; an empty \"\" Origin header must stay foreign, which a truthiness check would wrongly accept\n if (origin === null) return true;\n try {\n return new URL(origin).origin === new URL(request.url).origin;\n } catch {\n return false;\n }\n}\n\n/** The value of the cookie `name` in a `Cookie` header, or null. */\nexport function cookieValueOf(cookieHeader: string | null, name: string): string | null {\n for (const part of (cookieHeader || \"\").split(\";\")) {\n const separator = part.indexOf(\"=\");\n if (separator < 0) continue;\n if (part.slice(0, separator).trim() === name) return part.slice(separator + 1).trim();\n }\n return null;\n}\n\n/** `next` as a path on `origin`, else \"/\" — a redirect never leaves the host: `//evil.example`,\n * `/\\evil.example` and an absolute URL all resolve to a foreign origin and fall back to \"/\". The\n * issuer's login redirect uses it too (apps/os issuer-pages.ts). */\nexport function sameOriginPath(next: string, origin: string): string {\n try {\n const url = new URL(next, origin);\n return url.origin === origin ? url.pathname + url.search : \"/\";\n } catch {\n return \"/\";\n }\n}\n\n/** Only plain HTTP loopback origins use development login and client registration. */\nexport function isLocalOrigin(origin: string) {\n const url = new URL(origin);\n return (\n url.protocol === \"http:\" &&\n (url.hostname === \"localhost\" ||\n url.hostname.endsWith(\".localhost\") ||\n url.hostname === \"127.0.0.1\")\n );\n}\n\n/** Resolve a `cd` target against a context's own path — the one resolver every `cd` (the edge\n * method, the built-in root, the library's relative handles) shares. Absolute (\"/agents/x\") stands alone; relative\n * (\"agents/x\", \"../inbox\", \".\") joins onto `base`. `.` and `..` resolve; the root cannot be\n * escaped (\"/..\" is \"/\"). The result is canonical: leading slash, no trailing slash but for \"/\". */\nexport function resolveContextPath(basePath: string, contextPath: string): string {\n const segments: string[] = [];\n for (const seg of `${contextPath.startsWith(\"/\") ? \"\" : basePath}/${contextPath}`.split(\"/\")) {\n if (seg === \"\" || seg === \".\") continue;\n if (seg === \"..\") segments.pop();\n else segments.push(seg);\n }\n return `/${segments.join(\"/\")}`;\n}\n"],"mappings":"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"}
package/dist/node.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { type RpcStub } from "capnweb";
2
+ import type { IterateApi, SessionCredentials } from "./api.ts";
3
+ /** One connection to an Iterate deployment. Dispose the owner to close every child capability and its socket.
4
+ * Operations are never retried: replaying a script could duplicate its effects. */
5
+ export type IterateConnection = Disposable & {
6
+ session: Awaited<ReturnType<RpcStub<IterateApi>["authenticate"]>>;
7
+ closed: Promise<{
8
+ code: number;
9
+ reason: string;
10
+ }>;
11
+ };
12
+ export declare function connectIterate(input: {
13
+ baseUrl: string;
14
+ auth: SessionCredentials;
15
+ }): Promise<IterateConnection>;
package/dist/node.mjs ADDED
@@ -0,0 +1,47 @@
1
+ import { r as withTimeout } from "./lib-BWr-5mFO.mjs";
2
+ import { newWebSocketRpcSession } from "capnweb";
3
+ import WebSocket from "ws";
4
+ //#region src/node.ts
5
+ async function connectIterate(input) {
6
+ const url = new URL("/api", input.baseUrl);
7
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error("Iterate URL must use http or https.");
8
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
9
+ const socket = new WebSocket(url.href, { handshakeTimeout: 15e3 });
10
+ const root = newWebSocketRpcSession(socket);
11
+ const closed = new Promise((resolve) => {
12
+ socket.once("close", (code, reason) => resolve({
13
+ code,
14
+ reason: reason.toString()
15
+ }));
16
+ });
17
+ socket.on("error", () => {});
18
+ try {
19
+ const session = await withTimeout(root.authenticate(input.auth), 2e4, "Iterate authentication");
20
+ return {
21
+ session,
22
+ closed,
23
+ [Symbol.dispose]() {
24
+ try {
25
+ session[Symbol.dispose]();
26
+ } finally {
27
+ try {
28
+ root[Symbol.dispose]();
29
+ } finally {
30
+ socket.close();
31
+ }
32
+ }
33
+ }
34
+ };
35
+ } catch (error) {
36
+ try {
37
+ root[Symbol.dispose]();
38
+ } finally {
39
+ socket.terminate();
40
+ }
41
+ throw error;
42
+ }
43
+ }
44
+ //#endregion
45
+ export { connectIterate };
46
+
47
+ //# sourceMappingURL=node.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.mjs","names":[],"sources":["../src/node.ts"],"sourcesContent":["import { newWebSocketRpcSession, type RpcStub } from \"capnweb\";\nimport WebSocket from \"ws\";\nimport type { IterateApi, SessionCredentials } from \"./api.ts\";\nimport { withTimeout } from \"./lib.ts\";\n\n/** One connection to an Iterate deployment. Dispose the owner to close every child capability and its socket.\n * Operations are never retried: replaying a script could duplicate its effects. */\nexport type IterateConnection = Disposable & {\n session: Awaited<ReturnType<RpcStub<IterateApi>[\"authenticate\"]>>;\n closed: Promise<{ code: number; reason: string }>;\n};\n\n// Explicit return type keeps declaration emit from expanding capnweb's recursive mapped types.\nexport async function connectIterate(input: {\n baseUrl: string;\n auth: SessionCredentials;\n}): Promise<IterateConnection> {\n const url = new URL(\"/api\", input.baseUrl);\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new Error(\"Iterate URL must use http or https.\");\n }\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const socket = new WebSocket(url.href, { handshakeTimeout: 15_000 });\n // ws implements the DOM event/send/close interface consumed by capnweb.\n const root = newWebSocketRpcSession<IterateApi>(socket as unknown as globalThis.WebSocket);\n const closed = new Promise<{ code: number; reason: string }>((resolve) => {\n socket.once(\"close\", (code, reason) => resolve({ code, reason: reason.toString() }));\n });\n // The transport surfaces failures through RPC and `closed`; ws also emits an EventEmitter error.\n socket.on(\"error\", () => {});\n try {\n const session = await withTimeout(\n root.authenticate(input.auth),\n 20_000,\n \"Iterate authentication\",\n );\n return {\n session,\n closed,\n [Symbol.dispose]() {\n try {\n session[Symbol.dispose]();\n } finally {\n try {\n root[Symbol.dispose]();\n } finally {\n socket.close();\n }\n }\n },\n };\n } catch (error) {\n try {\n root[Symbol.dispose]();\n } finally {\n socket.terminate();\n }\n throw error;\n }\n}\n"],"mappings":";;;;AAaA,eAAsB,eAAe,OAGN;CAC7B,MAAM,MAAM,IAAI,IAAI,QAAQ,MAAM,OAAO;CACzC,IAAI,IAAI,aAAa,YAAY,IAAI,aAAa,SAChD,MAAM,IAAI,MAAM,qCAAqC;CAEvD,IAAI,WAAW,IAAI,aAAa,WAAW,SAAS;CACpD,MAAM,SAAS,IAAI,UAAU,IAAI,MAAM,EAAE,kBAAkB,KAAO,CAAC;CAEnE,MAAM,OAAO,uBAAmC,MAAyC;CACzF,MAAM,SAAS,IAAI,SAA2C,YAAY;EACxE,OAAO,KAAK,UAAU,MAAM,WAAW,QAAQ;GAAE;GAAM,QAAQ,OAAO,SAAS;EAAE,CAAC,CAAC;CACrF,CAAC;CAED,OAAO,GAAG,eAAe,CAAC,CAAC;CAC3B,IAAI;EACF,MAAM,UAAU,MAAM,YACpB,KAAK,aAAa,MAAM,IAAI,GAC5B,KACA,wBACF;EACA,OAAO;GACL;GACA;GACA,CAAC,OAAO,WAAW;IACjB,IAAI;KACF,QAAQ,OAAO,QAAQ,CAAC;IAC1B,UAAU;KACR,IAAI;MACF,KAAK,OAAO,QAAQ,CAAC;KACvB,UAAU;MACR,OAAO,MAAM;KACf;IACF;GACF;EACF;CACF,SAAS,OAAO;EACd,IAAI;GACF,KAAK,OAAO,QAAQ,CAAC;EACvB,UAAU;GACR,OAAO,UAAU;EACnB;EACA,MAAM;CACR;AACF"}