iterate 0.3.0 → 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.
- package/README.md +164 -82
- package/dist/{next/api.d.ts → api.d.ts} +197 -33
- package/dist/{next/app-server.d.ts → app-server.d.ts} +7 -0
- package/dist/{next/app-server.mjs → app-server.mjs} +17 -15
- package/dist/app-server.mjs.map +1 -0
- package/dist/{next/app-session.mjs → app-session.mjs} +12 -15
- package/dist/app-session.mjs.map +1 -0
- package/dist/{next/app.mjs → app.mjs} +53 -14
- package/dist/app.mjs.map +1 -0
- package/dist/{next/client → client}/live-state.d.ts +11 -11
- package/dist/client/oauth.d.ts +17 -0
- package/dist/{next/client → client}/react.d.ts +10 -42
- package/dist/{next/client → client}/socket.d.ts +1 -0
- package/dist/client.mjs +156 -4
- package/dist/client.mjs.map +1 -0
- package/dist/{next/expression.d.ts → expression.d.ts} +11 -69
- package/dist/{next/expression.mjs → expression.mjs} +11 -109
- package/dist/expression.mjs.map +1 -0
- package/dist/lib-BWr-5mFO.mjs +36 -0
- package/dist/lib-BWr-5mFO.mjs.map +1 -0
- package/dist/{next/lib.d.ts → lib.d.ts} +16 -2
- package/dist/{next/lib.mjs → lib.mjs} +32 -3
- package/dist/lib.mjs.map +1 -0
- package/dist/node.d.ts +15 -3
- package/dist/node.mjs +36 -174
- package/dist/node.mjs.map +1 -1
- package/dist/{next/oauth-scopes.mjs → oauth-scopes.mjs} +1 -1
- package/dist/oauth-scopes.mjs.map +1 -0
- package/dist/{next/oauth.mjs → oauth.mjs} +14 -2
- package/dist/oauth.mjs.map +1 -0
- package/dist/principal.d.ts +8 -0
- package/dist/principal.mjs +8 -0
- package/dist/principal.mjs.map +1 -0
- package/dist/project-ingress.d.ts +58 -0
- package/dist/project-ingress.mjs +104 -0
- package/dist/project-ingress.mjs.map +1 -0
- package/dist/{next/react.mjs → react.mjs} +12 -12
- package/dist/react.mjs.map +1 -0
- package/dist/sdk/auth.d.ts +25 -0
- package/dist/sdk/index.d.ts +155 -0
- package/dist/sdk/record-pipelined-steps.d.ts +19 -0
- package/dist/sdk.mjs +245 -2
- package/dist/sdk.mjs.map +1 -0
- package/dist/{next/stream → stream}/processor.d.ts +28 -23
- package/dist/{next/stream → stream}/processor.mjs +48 -25
- package/dist/stream/processor.mjs.map +1 -0
- package/dist/{next/stream → stream}/run.d.ts +9 -6
- package/dist/{next/stream → stream}/run.mjs +13 -8
- package/dist/stream/run.mjs.map +1 -0
- package/dist/stream/test-support.d.ts +45 -0
- package/dist/stream/test-support.mjs +196 -0
- package/dist/stream/test-support.mjs.map +1 -0
- package/package.json +65 -219
- package/THIRD_PARTY_NOTICES.md +0 -55
- package/bin/iterate.js +0 -94
- package/dist/api-url-B6404M82.mjs +0 -17
- package/dist/api-url-B6404M82.mjs.map +0 -1
- package/dist/app-ref-BipL0feU.mjs +0 -35
- package/dist/app-ref-BipL0feU.mjs.map +0 -1
- package/dist/app-ref-C1CrgXqX.mjs +0 -7
- package/dist/app-ref-C1CrgXqX.mjs.map +0 -1
- package/dist/app-ref-DYai_om1.mjs +0 -7
- package/dist/app-ref-DYai_om1.mjs.map +0 -1
- package/dist/cli-D0c-pDL_.mjs +0 -1010
- package/dist/cli-D0c-pDL_.mjs.map +0 -1
- package/dist/client.d.ts +0 -3
- package/dist/cloudflare-BTm90gQ4.mjs +0 -951
- package/dist/cloudflare-BTm90gQ4.mjs.map +0 -1
- package/dist/contract-s4FW4eES.mjs +0 -309
- package/dist/contract-s4FW4eES.mjs.map +0 -1
- package/dist/document-review/index.d.ts +0 -5
- package/dist/document-review/types.d.ts +0 -107
- package/dist/document-review.mjs +0 -7015
- package/dist/document-review.mjs.map +0 -1
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs +0 -205
- package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +0 -1
- package/dist/idempotency-DleloJNt.mjs +0 -28
- package/dist/idempotency-DleloJNt.mjs.map +0 -1
- package/dist/index.d.mts +0 -5
- package/dist/index.mjs +0 -8
- package/dist/index.mjs.map +0 -1
- package/dist/itx/api-url.d.ts +0 -6
- package/dist/itx/itx-node-client.d.ts +0 -65
- package/dist/itx/itx-session.d.ts +0 -215
- package/dist/itx/owned-rpc-session.d.ts +0 -14
- package/dist/itx/query-client.d.ts +0 -10
- package/dist/itx-api.generated.d.ts +0 -6195
- package/dist/itx-session-sjud8GiT.mjs +0 -534
- package/dist/itx-session-sjud8GiT.mjs.map +0 -1
- package/dist/live-state-BJNqOwFw.mjs +0 -299
- package/dist/live-state-BJNqOwFw.mjs.map +0 -1
- package/dist/next/app-server.mjs.map +0 -1
- package/dist/next/app-session.mjs.map +0 -1
- package/dist/next/app.mjs.map +0 -1
- package/dist/next/client/oauth.d.ts +0 -12
- package/dist/next/client.mjs +0 -156
- package/dist/next/client.mjs.map +0 -1
- package/dist/next/expression.mjs.map +0 -1
- package/dist/next/lib.mjs.map +0 -1
- package/dist/next/oauth-scopes.mjs.map +0 -1
- package/dist/next/oauth.mjs.map +0 -1
- package/dist/next/principal.d.ts +0 -64
- package/dist/next/principal.mjs +0 -98
- package/dist/next/principal.mjs.map +0 -1
- package/dist/next/project-ingress.d.ts +0 -37
- package/dist/next/project-ingress.mjs +0 -75
- package/dist/next/project-ingress.mjs.map +0 -1
- package/dist/next/react.mjs.map +0 -1
- package/dist/next/sdk/auth.d.ts +0 -5
- package/dist/next/sdk/index.d.ts +0 -112
- package/dist/next/sdk.mjs +0 -139
- package/dist/next/sdk.mjs.map +0 -1
- package/dist/next/stream/processor.mjs.map +0 -1
- package/dist/next/stream/run.mjs.map +0 -1
- package/dist/next-node.d.ts +0 -15
- package/dist/next-node.mjs +0 -51
- package/dist/next-node.mjs.map +0 -1
- package/dist/processor-host-capabilities-BMFH3KTM.mjs +0 -56
- package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +0 -1
- package/dist/processors/cloudflare.d.ts +0 -3
- package/dist/processors/durable-object-processor-durability.d.ts +0 -79
- package/dist/processors/event-consumption-metrics.d.ts +0 -82
- package/dist/processors/idempotency.d.ts +0 -13
- package/dist/processors/index.d.ts +0 -12
- package/dist/processors/processor-contracts.d.ts +0 -342
- package/dist/processors/processor-facet.d.ts +0 -186
- package/dist/processors/processor-host-capabilities.d.ts +0 -60
- package/dist/processors/prompt-sections.d.ts +0 -17
- package/dist/processors/rpc-types.d.ts +0 -515
- package/dist/processors/schemas.d.ts +0 -102
- package/dist/processors/stream-handle.d.ts +0 -45
- package/dist/processors/stream-processor-keepalive.d.ts +0 -95
- package/dist/processors/stream-processor-registry.d.ts +0 -233
- package/dist/processors/stream-processor-runner.d.ts +0 -289
- package/dist/processors/stream-processor.d.ts +0 -339
- package/dist/processors/stream-runtime-metrics.d.ts +0 -107
- package/dist/processors/testing.d.ts +0 -302
- package/dist/processors-BoNyeBfQ.mjs +0 -10
- package/dist/processors-BoNyeBfQ.mjs.map +0 -1
- package/dist/processors-cloudflare.mjs +0 -3
- package/dist/processors-testing.mjs +0 -435
- package/dist/processors-testing.mjs.map +0 -1
- package/dist/processors.mjs +0 -52
- package/dist/processors.mjs.map +0 -1
- package/dist/protocol-DnK_f2m6.mjs +0 -251
- package/dist/protocol-DnK_f2m6.mjs.map +0 -1
- package/dist/sdk/capnweb/index.d.ts +0 -2
- package/dist/sdk/capnweb/live-state/compact.d.ts +0 -5
- package/dist/sdk/capnweb/live-state/diff.d.ts +0 -41
- package/dist/sdk/capnweb/live-state/engine.d.ts +0 -44
- package/dist/sdk/capnweb/live-state/index.d.ts +0 -41
- package/dist/sdk/capnweb/live-state/protocol.d.ts +0 -87
- package/dist/sdk/capnweb/live-state/retain.d.ts +0 -23
- package/dist/sdk/capnweb/live-state/store.d.ts +0 -20
- package/dist/sdk/capnweb/live-state/types.d.ts +0 -11
- package/dist/sdk/capnweb/react.d.ts +0 -45
- package/dist/sdk/capnweb/react.mjs +0 -316
- package/dist/sdk/capnweb/react.mjs.map +0 -1
- package/dist/sdk/capnweb.mjs +0 -4
- package/dist/sdk/itx/react.d.ts +0 -191
- package/dist/sdk/itx/react.mjs +0 -383
- package/dist/sdk/itx/react.mjs.map +0 -1
- package/dist/sdk-DMB-IM11.mjs +0 -933
- package/dist/sdk-DMB-IM11.mjs.map +0 -1
- package/dist/sdk.d.ts +0 -339
- package/dist/serve-itx.d.ts +0 -46
- package/dist/starter-apps/flake-dashboard/app-ref.d.ts +0 -31
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs +0 -1055
- package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/flake-dashboard/contract.d.ts +0 -4839
- package/dist/starter-apps/flake-dashboard/contract.mjs +0 -2
- package/dist/starter-apps/flake-dashboard/index.d.ts +0 -17
- package/dist/starter-apps/flake-dashboard/index.mjs +0 -56
- package/dist/starter-apps/flake-dashboard/index.mjs.map +0 -1
- package/dist/starter-apps/flake-dashboard/worker.d.ts +0 -4607
- package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +0 -8914
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs +0 -17987
- package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/github-ai-linter/contract.d.ts +0 -9193
- package/dist/starter-apps/github-ai-linter/index.d.ts +0 -10
- package/dist/starter-apps/github-ai-linter/index.mjs +0 -36
- package/dist/starter-apps/github-ai-linter/index.mjs.map +0 -1
- package/dist/starter-apps/github-ai-linter/prompt.d.ts +0 -13
- package/dist/starter-apps/github-ai-linter/review-bot.d.ts +0 -808
- package/dist/starter-apps/github-ai-linter/rules.d.ts +0 -34
- package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +0 -19
- package/dist/starter-apps/github-ai-linter/worker.d.ts +0 -19
- package/dist/starter-apps/github-ai-linter/worker.mjs +0 -947
- package/dist/starter-apps/github-ai-linter/worker.mjs.map +0 -1
- package/dist/starter-apps/guestbook/app-ref.d.ts +0 -27
- package/dist/starter-apps/guestbook/client.d.ts +0 -7
- package/dist/starter-apps/guestbook/client.mjs +0 -59
- package/dist/starter-apps/guestbook/configured-worker.mjs +0 -205
- package/dist/starter-apps/guestbook/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/guestbook/index.d.ts +0 -9
- package/dist/starter-apps/guestbook/index.mjs +0 -31
- package/dist/starter-apps/guestbook/index.mjs.map +0 -1
- package/dist/starter-apps/guestbook/processor.d.ts +0 -2267
- package/dist/starter-apps/guestbook/worker.d.ts +0 -26
- package/dist/starter-apps/guestbook/worker.mjs +0 -191
- package/dist/starter-apps/guestbook/worker.mjs.map +0 -1
- package/dist/starter-apps/media/configured-worker.mjs +0 -577
- package/dist/starter-apps/media/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/media/index.mjs +0 -36
- package/dist/starter-apps/media/index.mjs.map +0 -1
- package/dist/starter-apps/media/ref.mjs +0 -20
- package/dist/starter-apps/media/ref.mjs.map +0 -1
- package/dist/starter-apps/media/worker.mjs +0 -579
- package/dist/starter-apps/media/worker.mjs.map +0 -1
- package/dist/starter-apps/notes/configured-worker.mjs +0 -6134
- package/dist/starter-apps/notes/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/notes/index.mjs +0 -23
- package/dist/starter-apps/notes/index.mjs.map +0 -1
- package/dist/starter-apps/notes/ref.mjs +0 -21
- package/dist/starter-apps/notes/ref.mjs.map +0 -1
- package/dist/starter-apps/notes/worker.mjs +0 -427
- package/dist/starter-apps/notes/worker.mjs.map +0 -1
- package/dist/starter-apps/todo/client.mjs +0 -59
- package/dist/starter-apps/todo/configured-worker.mjs +0 -2864
- package/dist/starter-apps/todo/configured-worker.mjs.map +0 -1
- package/dist/starter-apps/todo/index.d.ts +0 -8
- package/dist/starter-apps/todo/index.mjs +0 -29
- package/dist/starter-apps/todo/index.mjs.map +0 -1
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs +0 -2082
- package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +0 -1
- package/dist/usingCtx-mZx5nsAW.mjs +0 -11800
- package/dist/usingCtx-mZx5nsAW.mjs.map +0 -1
- package/dist/worker-ref-DZxPDmb_.mjs +0 -390
- package/dist/worker-ref-DZxPDmb_.mjs.map +0 -1
- package/dist/worker.d.mts +0 -33
- package/dist/worker.mjs +0 -18
- package/dist/worker.mjs.map +0 -1
- package/menubar/Iterate.entitlements +0 -12
- package/menubar/Iterate.swift +0 -914
- package/menubar/IterateIcon.swift +0 -145
- package/menubar/README.md +0 -28
- package/menubar/build-menubar-app.sh +0 -59
- /package/dist/{next/api.mjs → api.mjs} +0 -0
- /package/dist/{next/app-session.d.ts → app-session.d.ts} +0 -0
- /package/dist/{next/app.d.ts → app.d.ts} +0 -0
- /package/dist/{next/oauth-scopes.d.ts → oauth-scopes.d.ts} +0 -0
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import { o as StreamProcessorRunner } from "./stream-processor-keepalive-DAQTP6m3.mjs";
|
|
2
|
-
import { n as durableObjectRecovery, t as durableObjectProgressStore } from "./durable-object-processor-durability-CNsTjAJS.mjs";
|
|
3
|
-
import { i as sameIdempotentEvent, t as idempotencyConflictMessage } from "./idempotency-DleloJNt.mjs";
|
|
4
|
-
//#region src/processors/testing.ts
|
|
5
|
-
function emptyThroughputReport() {
|
|
6
|
-
return {
|
|
7
|
-
perSecond5s: 0,
|
|
8
|
-
bytesPerSecond5s: 0,
|
|
9
|
-
lastMinute: {
|
|
10
|
-
count: 0,
|
|
11
|
-
bytes: 0,
|
|
12
|
-
perSecond: 0
|
|
13
|
-
},
|
|
14
|
-
series: {
|
|
15
|
-
counts: new Array(60).fill(0),
|
|
16
|
-
bytes: new Array(60).fill(0)
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
/** The empty `runtimeState()` answer the test MemoryStream serves. */
|
|
21
|
-
function emptyStreamRuntimeState() {
|
|
22
|
-
return {
|
|
23
|
-
coreProcessorState: null,
|
|
24
|
-
runtime: {
|
|
25
|
-
connections: {},
|
|
26
|
-
sending: {},
|
|
27
|
-
metrics: {
|
|
28
|
-
measuredSince: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
29
|
-
reportedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
30
|
-
ingress: emptyThroughputReport(),
|
|
31
|
-
egress: emptyThroughputReport()
|
|
32
|
-
},
|
|
33
|
-
storageSizeBytes: 0
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
var MemoryStream = class {
|
|
38
|
-
events = [];
|
|
39
|
-
streamId = crypto.randomUUID();
|
|
40
|
-
/** Injectable clock for createdAt stamps; harnesses point it at virtual time. */
|
|
41
|
-
now = Date.now;
|
|
42
|
-
/** Simulate a transient Stream DO outage: appends of this type throw. */
|
|
43
|
-
failAppendsOfType;
|
|
44
|
-
/**
|
|
45
|
-
* OPT-IN ASYNCHRONOUS COMMIT. Undefined — the default — is today's behaviour
|
|
46
|
-
* and every existing suite's behaviour.
|
|
47
|
-
*
|
|
48
|
-
* `append` below validates and publishes in its own SYNCHRONOUS body: it is
|
|
49
|
-
* declared `async`, but nothing inside it awaits, so the events are in
|
|
50
|
-
* `this.events` and their offsets are taken before the returned promise is
|
|
51
|
-
* even handed back. Two call sites therefore commit in the order they
|
|
52
|
-
* CALLED, whoever awaited and whoever did not. That determinism is what
|
|
53
|
-
* makes scenario steps reproducible and it is why ~250 suites rest on it.
|
|
54
|
-
*
|
|
55
|
-
* It is also a lie about production. A real append is an RPC to the Stream
|
|
56
|
-
* Durable Object; two of them can be in flight at once, and the one issued
|
|
57
|
-
* first is not guaranteed to commit first. Every ordering bug of that shape
|
|
58
|
-
* — an un-awaited append racing an awaited one, and the un-awaited one
|
|
59
|
-
* losing — is structurally invisible here.
|
|
60
|
-
*
|
|
61
|
-
* Set this and an append waits for the returned promise before it validates
|
|
62
|
-
* anything or takes an offset, so offsets are assigned at COMMIT the way the
|
|
63
|
-
* Durable Object assigns them. Return `undefined` to let a batch commit at
|
|
64
|
-
* once, which is what keeps the cost of the mode to the batches a test
|
|
65
|
-
* actually cares about.
|
|
66
|
-
*
|
|
67
|
-
* A test is expected to DECIDE which of two in-flight appends wins rather
|
|
68
|
-
* than to wait and see: a race whose loser cannot be pinned is a flake, not
|
|
69
|
-
* a test. Whatever the hook waits on must be bounded, because the fix for
|
|
70
|
-
* such a bug is usually to make the overtake impossible — and then a hook
|
|
71
|
-
* that waits for it forever hangs the suite that proves it.
|
|
72
|
-
*/
|
|
73
|
-
holdAppend;
|
|
74
|
-
/** Set by MemoryStreamNetwork.get(); a detached stream answers `at()` with itself. */
|
|
75
|
-
network;
|
|
76
|
-
path;
|
|
77
|
-
constructor(path = "/agents/test") {
|
|
78
|
-
this.path = path;
|
|
79
|
-
}
|
|
80
|
-
async __describe() {
|
|
81
|
-
return {
|
|
82
|
-
instructions: "in-memory test stream",
|
|
83
|
-
types: "",
|
|
84
|
-
children: {}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
async kill() {}
|
|
88
|
-
async append(...inputs) {
|
|
89
|
-
const held = this.holdAppend?.(inputs);
|
|
90
|
-
if (held !== void 0) await held;
|
|
91
|
-
const staged = [];
|
|
92
|
-
const results = [];
|
|
93
|
-
let nextOffset = (this.events.at(-1)?.offset ?? 0) + 1;
|
|
94
|
-
for (const input of inputs) {
|
|
95
|
-
if (input.type === this.failAppendsOfType) throw new Error(`injected append failure for ${input.type}`);
|
|
96
|
-
const detachedInput = {
|
|
97
|
-
...input,
|
|
98
|
-
...input.payload === void 0 ? {} : { payload: JSON.parse(JSON.stringify(input.payload)) },
|
|
99
|
-
...input.metadata === void 0 ? {} : { metadata: JSON.parse(JSON.stringify(input.metadata)) }
|
|
100
|
-
};
|
|
101
|
-
const existing = detachedInput.idempotencyKey === void 0 ? void 0 : [...this.events, ...staged].find((event) => event.idempotencyKey === detachedInput.idempotencyKey);
|
|
102
|
-
if (existing !== void 0 && detachedInput.idempotencyKey !== void 0) {
|
|
103
|
-
if (!sameIdempotentEvent(existing, detachedInput)) throw new Error(idempotencyConflictMessage(detachedInput.idempotencyKey, existing.offset));
|
|
104
|
-
results.push(existing);
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const event = {
|
|
108
|
-
...detachedInput,
|
|
109
|
-
createdAt: new Date(this.now()).toISOString(),
|
|
110
|
-
offset: nextOffset++,
|
|
111
|
-
path: this.path
|
|
112
|
-
};
|
|
113
|
-
staged.push(event);
|
|
114
|
-
results.push(event);
|
|
115
|
-
}
|
|
116
|
-
this.events.push(...staged);
|
|
117
|
-
return results;
|
|
118
|
-
}
|
|
119
|
-
appendIfStreamId(args) {
|
|
120
|
-
if (args.streamId !== this.streamId) throw new Error(`stream ID changed (${args.streamId} -> ${this.streamId}); append rejected`);
|
|
121
|
-
return this.append(...args.events);
|
|
122
|
-
}
|
|
123
|
-
at(path) {
|
|
124
|
-
return path === void 0 || this.network === void 0 ? this : this.network.get(path);
|
|
125
|
-
}
|
|
126
|
-
async getEvent(input) {
|
|
127
|
-
if ("offset" in input) return this.events.find((event) => event.offset === input.offset);
|
|
128
|
-
return this.events.find((event) => event.idempotencyKey === input.idempotencyKey);
|
|
129
|
-
}
|
|
130
|
-
async getEvents(input = {}) {
|
|
131
|
-
const { afterOffset = 0, limit = 500 } = input;
|
|
132
|
-
const beforeOffset = input.beforeOffset ?? Number.MAX_SAFE_INTEGER;
|
|
133
|
-
const matching = this.events.filter((event) => event.offset > afterOffset).filter((event) => event.offset < beforeOffset).filter((event) => input.eventTypes === void 0 || input.eventTypes.includes("*") || input.eventTypes.includes(event.type)).slice(0, limit);
|
|
134
|
-
const byteLimit = input.byteLimit;
|
|
135
|
-
if (byteLimit === void 0) return matching;
|
|
136
|
-
let bytes = 0;
|
|
137
|
-
return matching.filter((event, index) => {
|
|
138
|
-
bytes += new TextEncoder().encode(JSON.stringify(event)).byteLength;
|
|
139
|
-
return bytes <= byteLimit || index === 0;
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
async getEventPage(input = {}) {
|
|
143
|
-
return {
|
|
144
|
-
streamId: this.streamId,
|
|
145
|
-
streamMaxOffset: this.events.at(-1)?.offset ?? 0,
|
|
146
|
-
events: await this.getEvents(input)
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
readEvents(input = {}) {
|
|
150
|
-
let afterOffset = input.afterOffset ?? 0;
|
|
151
|
-
return {
|
|
152
|
-
next: async () => {
|
|
153
|
-
const page = await this.getEvents({
|
|
154
|
-
...input,
|
|
155
|
-
afterOffset
|
|
156
|
-
});
|
|
157
|
-
afterOffset = page.at(-1)?.offset ?? afterOffset;
|
|
158
|
-
return page;
|
|
159
|
-
},
|
|
160
|
-
[Symbol.dispose]() {}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
async waitForEvent(input) {
|
|
164
|
-
const deadline = Date.now() + input.timeoutMs;
|
|
165
|
-
while (Date.now() < deadline) {
|
|
166
|
-
for (const event of this.events) {
|
|
167
|
-
if (input.afterOffset !== void 0 && event.offset <= input.afterOffset) continue;
|
|
168
|
-
if (input.eventTypes !== void 0 && !input.eventTypes.includes(event.type)) continue;
|
|
169
|
-
if (input.predicate !== void 0 && !await input.predicate(event)) continue;
|
|
170
|
-
return event;
|
|
171
|
-
}
|
|
172
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
173
|
-
}
|
|
174
|
-
throw new Error("Timed out waiting for event");
|
|
175
|
-
}
|
|
176
|
-
async getProcessorRuntimeState() {
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
async runtimeState() {
|
|
180
|
-
return emptyStreamRuntimeState();
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
/**
|
|
184
|
-
* In-memory network of streams keyed by path, so router tests can observe
|
|
185
|
-
* the cross-stream forwards (`stream.at(path).append(...)`) next to
|
|
186
|
-
* same-stream appends. `now` feeds every member stream's createdAt stamp, so
|
|
187
|
-
* freshness checks (such as webhook acknowledgement horizons) can be tested on both sides
|
|
188
|
-
* of the horizon. `eventsAt` never creates a stream — `network.streams.size`
|
|
189
|
-
* assertions ("nothing was forwarded anywhere") stay honest.
|
|
190
|
-
*/
|
|
191
|
-
var MemoryStreamNetwork = class {
|
|
192
|
-
streams = /* @__PURE__ */ new Map();
|
|
193
|
-
now;
|
|
194
|
-
constructor(now = Date.now) {
|
|
195
|
-
this.now = now;
|
|
196
|
-
}
|
|
197
|
-
get(path) {
|
|
198
|
-
let stream = this.streams.get(path);
|
|
199
|
-
if (stream === void 0) {
|
|
200
|
-
stream = new MemoryStream(path);
|
|
201
|
-
stream.network = this;
|
|
202
|
-
stream.now = this.now;
|
|
203
|
-
this.streams.set(path, stream);
|
|
204
|
-
}
|
|
205
|
-
return stream;
|
|
206
|
-
}
|
|
207
|
-
eventsAt(path) {
|
|
208
|
-
return this.streams.get(path)?.events ?? [];
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
const durabilitySubstrates = /* @__PURE__ */ new WeakMap();
|
|
212
|
-
const progressSubstrates = /* @__PURE__ */ new WeakMap();
|
|
213
|
-
function makeDurabilitySubstrate() {
|
|
214
|
-
const kv = /* @__PURE__ */ new Map();
|
|
215
|
-
const alarm = { at: null };
|
|
216
|
-
return {
|
|
217
|
-
storage: {
|
|
218
|
-
kv: {
|
|
219
|
-
get: (key) => kv.has(key) ? structuredClone(kv.get(key)) : void 0,
|
|
220
|
-
put: (key, value) => void kv.set(key, structuredClone(value)),
|
|
221
|
-
delete: (key) => kv.delete(key)
|
|
222
|
-
},
|
|
223
|
-
getAlarm: async () => alarm.at,
|
|
224
|
-
setAlarm: async (at) => {
|
|
225
|
-
alarm.at = at;
|
|
226
|
-
},
|
|
227
|
-
deleteAlarm: async () => {
|
|
228
|
-
alarm.at = null;
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
alarm
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
/** In-memory {@link ProcessorProgressStore} with the DO store's fencing check,
|
|
235
|
-
* so progress survives `crash()` into the successor incarnation. Exported so
|
|
236
|
-
* suites can hand a harness a FRESH store over an existing stream — a full
|
|
237
|
-
* replay from offset zero, which is both the re-reduce-from-scratch recipe and the harshest
|
|
238
|
-
* at-least-once redelivery test (every per-event append re-runs).
|
|
239
|
-
*
|
|
240
|
-
* The contract is required because progress and recovery share the same
|
|
241
|
-
* per-processor Durable Object keys. A generic placeholder slug would let the
|
|
242
|
-
* runner commit progress under one key while recovery looks under another.
|
|
243
|
-
*/
|
|
244
|
-
function makeMemoryProgressStore(contract) {
|
|
245
|
-
const durability = makeDurabilitySubstrate();
|
|
246
|
-
const progress = durableObjectProgressStore({
|
|
247
|
-
storage: durability.storage,
|
|
248
|
-
name: contract.slug
|
|
249
|
-
});
|
|
250
|
-
progressSubstrates.set(progress, durability);
|
|
251
|
-
return progress;
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Build a {@link ProcessorHarness}: the real runner, ProcessorKeepalive, and
|
|
255
|
-
* Durable Object durability adapters over an in-memory stream/KV/alarm
|
|
256
|
-
* substrate. `crash()` is eviction: delivery stays detached until a new
|
|
257
|
-
* append wakes it or a due keepalive alarm appends the revival fact. Pass
|
|
258
|
-
* another harness's `substrate` to run a second incarnation over the same
|
|
259
|
-
* durable state (the zombie-race setup).
|
|
260
|
-
*
|
|
261
|
-
* Appends commit synchronously unless a suite opts out through
|
|
262
|
-
* `harness.stream.holdAppend` — the one seam for reproducing a processor that
|
|
263
|
-
* appends the same lane from two places and orders only one of them.
|
|
264
|
-
*/
|
|
265
|
-
function makeProcessorHarness(args) {
|
|
266
|
-
const path = args.substrate?.stream.path ?? args.path ?? "/harness/processor";
|
|
267
|
-
const clock = args.substrate?.clock ?? { now: 1e6 };
|
|
268
|
-
const stream = args.substrate?.stream ?? new MemoryStream(path);
|
|
269
|
-
stream.now = () => clock.now;
|
|
270
|
-
const durability = (args.substrate === void 0 ? void 0 : durabilitySubstrates.get(args.substrate) ?? progressSubstrates.get(args.substrate.progress)) ?? makeDurabilitySubstrate();
|
|
271
|
-
let progress = args.substrate?.progress;
|
|
272
|
-
let pendingSleeps = [];
|
|
273
|
-
const releaseDueSleeps = () => {
|
|
274
|
-
const due = pendingSleeps.filter((sleep) => sleep.dueAt <= clock.now);
|
|
275
|
-
pendingSleeps = pendingSleeps.filter((sleep) => sleep.dueAt > clock.now);
|
|
276
|
-
for (const sleep of due) sleep.resolve();
|
|
277
|
-
return due.length;
|
|
278
|
-
};
|
|
279
|
-
let processor;
|
|
280
|
-
let runner;
|
|
281
|
-
let recovery;
|
|
282
|
-
let incarnation = 0;
|
|
283
|
-
let deliveryEnabled = true;
|
|
284
|
-
let deliveryAttempt;
|
|
285
|
-
let deliveryError;
|
|
286
|
-
const keepAlive = (work) => {
|
|
287
|
-
work.catch(() => void 0);
|
|
288
|
-
};
|
|
289
|
-
const incarnate = () => {
|
|
290
|
-
processor = args.createProcessor({
|
|
291
|
-
stream,
|
|
292
|
-
path,
|
|
293
|
-
projectId: "proj_harness",
|
|
294
|
-
now: () => clock.now,
|
|
295
|
-
sleep: (ms) => new Promise((resolve) => pendingSleeps.push({
|
|
296
|
-
dueAt: clock.now + ms,
|
|
297
|
-
resolve
|
|
298
|
-
})),
|
|
299
|
-
reads: {
|
|
300
|
-
snapshot: () => runner.snapshot(),
|
|
301
|
-
waitUntilEvent: (input) => "offset" in input ? runner.waitUntilEvent(input) : runner.waitUntilEvent(input)
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
progress ??= durableObjectProgressStore({
|
|
305
|
-
storage: durability.storage,
|
|
306
|
-
name: processor.contract.slug
|
|
307
|
-
});
|
|
308
|
-
recovery = durableObjectRecovery({
|
|
309
|
-
storage: durability.storage,
|
|
310
|
-
name: processor.contract.slug,
|
|
311
|
-
stream,
|
|
312
|
-
version: "test-harness",
|
|
313
|
-
armAlarm: (atMs) => {
|
|
314
|
-
if (atMs === null) durability.storage.deleteAlarm();
|
|
315
|
-
else durability.storage.setAlarm(atMs);
|
|
316
|
-
},
|
|
317
|
-
waitUntil: keepAlive,
|
|
318
|
-
now: () => clock.now
|
|
319
|
-
});
|
|
320
|
-
runner = new StreamProcessorRunner({
|
|
321
|
-
processor,
|
|
322
|
-
stream,
|
|
323
|
-
durability: {
|
|
324
|
-
progress,
|
|
325
|
-
recovery
|
|
326
|
-
},
|
|
327
|
-
keepAlive: (work) => keepAlive(work()),
|
|
328
|
-
now: () => clock.now,
|
|
329
|
-
readPageSize: 5
|
|
330
|
-
});
|
|
331
|
-
};
|
|
332
|
-
incarnate();
|
|
333
|
-
const substrate = {
|
|
334
|
-
clock,
|
|
335
|
-
stream,
|
|
336
|
-
progress
|
|
337
|
-
};
|
|
338
|
-
if (args.substrate !== void 0) durabilitySubstrates.set(args.substrate, durability);
|
|
339
|
-
durabilitySubstrates.set(substrate, durability);
|
|
340
|
-
progressSubstrates.set(substrate.progress, durability);
|
|
341
|
-
const startDelivery = () => {
|
|
342
|
-
if (deliveryAttempt !== void 0) return;
|
|
343
|
-
const deliveryIncarnation = incarnation;
|
|
344
|
-
const attempt = runner.catchUp();
|
|
345
|
-
deliveryAttempt = attempt;
|
|
346
|
-
attempt.then(() => {
|
|
347
|
-
if (deliveryIncarnation === incarnation) deliveryAttempt = void 0;
|
|
348
|
-
}, (error) => {
|
|
349
|
-
if (deliveryIncarnation !== incarnation) return;
|
|
350
|
-
deliveryAttempt = void 0;
|
|
351
|
-
deliveryError = error;
|
|
352
|
-
});
|
|
353
|
-
};
|
|
354
|
-
const settle = async () => {
|
|
355
|
-
let quietRounds = 0;
|
|
356
|
-
let lastAppendedTypes = [];
|
|
357
|
-
for (let round = 0; round < 50; round++) {
|
|
358
|
-
const headBefore = stream.events.at(-1)?.offset ?? 0;
|
|
359
|
-
const releasedSleeps = releaseDueSleeps();
|
|
360
|
-
if (deliveryEnabled) startDelivery();
|
|
361
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
362
|
-
if (deliveryError !== void 0) {
|
|
363
|
-
const error = deliveryError;
|
|
364
|
-
deliveryError = void 0;
|
|
365
|
-
throw error;
|
|
366
|
-
}
|
|
367
|
-
const headAfter = stream.events.at(-1)?.offset ?? 0;
|
|
368
|
-
lastAppendedTypes = stream.events.filter((event) => event.offset > headBefore).map((event) => event.type);
|
|
369
|
-
quietRounds = headAfter === headBefore && releasedSleeps === 0 ? quietRounds + 1 : 0;
|
|
370
|
-
if (quietRounds === 3 && (!deliveryEnabled || deliveryAttempt === void 0 || durability.alarm.at !== null)) return;
|
|
371
|
-
}
|
|
372
|
-
const headOffset = stream.events.at(-1)?.offset ?? 0;
|
|
373
|
-
throw new Error(`harness settle() did not reach a fixpoint in 50 rounds; last round appended event types: ${lastAppendedTypes.join(", ") || "none"}; head offset: ${headOffset}`);
|
|
374
|
-
};
|
|
375
|
-
const append = async (...events) => {
|
|
376
|
-
await stream.append(...events);
|
|
377
|
-
deliveryEnabled = true;
|
|
378
|
-
await settle();
|
|
379
|
-
};
|
|
380
|
-
const advanceTime = async (ms) => {
|
|
381
|
-
const target = clock.now + ms;
|
|
382
|
-
while (durability.alarm.at !== null && durability.alarm.at <= target) {
|
|
383
|
-
clock.now = Math.max(clock.now, durability.alarm.at);
|
|
384
|
-
durability.alarm.at = null;
|
|
385
|
-
await recovery.handleAlarm();
|
|
386
|
-
deliveryEnabled = true;
|
|
387
|
-
await settle();
|
|
388
|
-
}
|
|
389
|
-
clock.now = target;
|
|
390
|
-
await settle();
|
|
391
|
-
};
|
|
392
|
-
const crash = () => {
|
|
393
|
-
runner.dispose();
|
|
394
|
-
incarnation += 1;
|
|
395
|
-
deliveryEnabled = false;
|
|
396
|
-
deliveryAttempt = void 0;
|
|
397
|
-
deliveryError = void 0;
|
|
398
|
-
pendingSleeps = [];
|
|
399
|
-
incarnate();
|
|
400
|
-
};
|
|
401
|
-
return {
|
|
402
|
-
clock,
|
|
403
|
-
stream,
|
|
404
|
-
substrate,
|
|
405
|
-
runner: () => runner,
|
|
406
|
-
processor: () => processor,
|
|
407
|
-
append,
|
|
408
|
-
advanceTime,
|
|
409
|
-
crash,
|
|
410
|
-
settle,
|
|
411
|
-
state: () => runner.currentState,
|
|
412
|
-
events: ((type) => type === void 0 ? [...stream.events] : stream.events.filter((row) => row.type === type)),
|
|
413
|
-
async play(...steps) {
|
|
414
|
-
for (const [index, step] of steps.entries()) {
|
|
415
|
-
const kind = typeof step === "function" ? "function" : step[0];
|
|
416
|
-
try {
|
|
417
|
-
if (typeof step === "function") {
|
|
418
|
-
await step();
|
|
419
|
-
await settle();
|
|
420
|
-
} else if (step[0] === "append") {
|
|
421
|
-
const [, ...events] = step;
|
|
422
|
-
await append(...events);
|
|
423
|
-
} else if (step[0] === "advanceTime") await advanceTime(step[1]);
|
|
424
|
-
else crash();
|
|
425
|
-
} catch (error) {
|
|
426
|
-
throw new Error(`harness play() step ${index} (${kind}) failed`, { cause: error });
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
//#endregion
|
|
433
|
-
export { MemoryStream, MemoryStreamNetwork, makeMemoryProgressStore, makeProcessorHarness };
|
|
434
|
-
|
|
435
|
-
//# sourceMappingURL=processors-testing.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"processors-testing.mjs","names":[],"sources":["../src/processors/testing.ts"],"sourcesContent":["// In-memory test doubles for the stream substrate — the ONE MemoryStream\n// (plus MemoryStreamNetwork for router suites observing cross-stream\n// forwards). The generic step harness drives the real runner over that\n// in-memory stream.\n// Step tuples form scenario spines; `h.append`/`h.advanceTime` settle single\n// actions, while `h.stream.append` commits without driving delivery.\n// Every harness uses the production DO durability adapters over in-memory KV\n// plus an alarm cell, so eviction and revival have one meaning in every suite.\n\nimport {\n durableObjectProgressStore,\n durableObjectRecovery,\n} from \"./durable-object-processor-durability.ts\";\nimport { idempotencyConflictMessage, sameIdempotentEvent } from \"./idempotency.ts\";\nimport type { StreamEvent, StreamEventInput } from \"./schemas.ts\";\nimport type { StreamEventReadInput } from \"./rpc-types.ts\";\nimport type { ConsumedInput, ProcessorState, ResolvedEvent } from \"./processor-contracts.ts\";\nimport type { RegisteredProcessorReads } from \"./stream-processor-registry.ts\";\nimport type { ProcessorStream } from \"./stream-handle.ts\";\nimport type { StreamProcessor, StreamProcessorContract } from \"./stream-processor.ts\";\nimport type { ProcessorProgressStore } from \"./stream-processor-runner.ts\";\nimport { StreamProcessorRunner } from \"./stream-processor-runner.ts\";\n\nfunction emptyThroughputReport() {\n return {\n perSecond5s: 0,\n bytesPerSecond5s: 0,\n lastMinute: { count: 0, bytes: 0, perSecond: 0 },\n series: { counts: new Array(60).fill(0), bytes: new Array(60).fill(0) },\n };\n}\n\n/** The empty `runtimeState()` answer the test MemoryStream serves. */\nfunction emptyStreamRuntimeState() {\n return {\n coreProcessorState: null,\n runtime: {\n connections: {},\n sending: {},\n metrics: {\n measuredSince: new Date(0).toISOString(),\n reportedAt: new Date(0).toISOString(),\n ingress: emptyThroughputReport(),\n egress: emptyThroughputReport(),\n },\n storageSizeBytes: 0,\n },\n };\n}\n\n// Deliberately typed against the narrow ProcessorStream handle (what the\n// machinery depends on), not the full generated `Stream` — the extra methods\n// below (getEvent/getEvents/waitForEvent, runtime-state stubs) exist for test\n// assertions and for suites that treat the double as a fuller stream.\nexport class MemoryStream implements ProcessorStream {\n events: StreamEvent[] = [];\n streamId = crypto.randomUUID();\n /** Injectable clock for createdAt stamps; harnesses point it at virtual time. */\n now: () => number = Date.now;\n /** Simulate a transient Stream DO outage: appends of this type throw. */\n failAppendsOfType: string | undefined;\n /**\n * OPT-IN ASYNCHRONOUS COMMIT. Undefined — the default — is today's behaviour\n * and every existing suite's behaviour.\n *\n * `append` below validates and publishes in its own SYNCHRONOUS body: it is\n * declared `async`, but nothing inside it awaits, so the events are in\n * `this.events` and their offsets are taken before the returned promise is\n * even handed back. Two call sites therefore commit in the order they\n * CALLED, whoever awaited and whoever did not. That determinism is what\n * makes scenario steps reproducible and it is why ~250 suites rest on it.\n *\n * It is also a lie about production. A real append is an RPC to the Stream\n * Durable Object; two of them can be in flight at once, and the one issued\n * first is not guaranteed to commit first. Every ordering bug of that shape\n * — an un-awaited append racing an awaited one, and the un-awaited one\n * losing — is structurally invisible here.\n *\n * Set this and an append waits for the returned promise before it validates\n * anything or takes an offset, so offsets are assigned at COMMIT the way the\n * Durable Object assigns them. Return `undefined` to let a batch commit at\n * once, which is what keeps the cost of the mode to the batches a test\n * actually cares about.\n *\n * A test is expected to DECIDE which of two in-flight appends wins rather\n * than to wait and see: a race whose loser cannot be pinned is a flake, not\n * a test. Whatever the hook waits on must be bounded, because the fix for\n * such a bug is usually to make the overtake impossible — and then a hook\n * that waits for it forever hangs the suite that proves it.\n */\n holdAppend: ((events: readonly StreamEventInput[]) => Promise<void> | undefined) | undefined;\n /** Set by MemoryStreamNetwork.get(); a detached stream answers `at()` with itself. */\n network: MemoryStreamNetwork | undefined;\n\n readonly path: string;\n\n constructor(path = \"/agents/test\") {\n this.path = path;\n }\n\n async __describe() {\n return { instructions: \"in-memory test stream\", types: \"\", children: {} };\n }\n\n async kill(): Promise<void> {}\n\n async append(...inputs: StreamEventInput[]): Promise<StreamEvent[]> {\n // Before ANYTHING else, including taking offsets: see `holdAppend`. The\n // `undefined` check keeps the default path free of an await, so with no\n // hook installed this method still commits inside its synchronous body.\n const held = this.holdAppend?.(inputs);\n if (held !== undefined) await held;\n // TWO-PHASE like the Stream DO: validate the whole batch against existing\n // AND same-batch state first, then publish — a failing input must not\n // leave earlier inputs committed (production batches are atomic).\n const staged: StreamEvent[] = [];\n const results: StreamEvent[] = [];\n // Next offset comes from the last event, not the array length — seeded\n // histories (e.g. stream-repros fixtures with bulk event types dropped)\n // have offset gaps.\n let nextOffset = (this.events.at(-1)?.offset ?? 0) + 1;\n for (const input of inputs) {\n if (input.type === this.failAppendsOfType) {\n throw new Error(`injected append failure for ${input.type}`);\n }\n // The casts restate what the round-trip preserves: serializing a\n // `Record<string, unknown>` yields a JSON object, so parsing it back\n // yields one — `JSON.parse` just types as `any` and cannot say so.\n const detachedInput: StreamEventInput = {\n ...input,\n ...(input.payload === undefined\n ? {}\n : { payload: JSON.parse(JSON.stringify(input.payload)) as Record<string, unknown> }),\n ...(input.metadata === undefined\n ? {}\n : { metadata: JSON.parse(JSON.stringify(input.metadata)) as Record<string, unknown> }),\n };\n const existing =\n detachedInput.idempotencyKey === undefined\n ? undefined\n : [...this.events, ...staged].find(\n (event) => event.idempotencyKey === detachedInput.idempotencyKey,\n );\n if (existing !== undefined && detachedInput.idempotencyKey !== undefined) {\n // The Stream DO's predicate, SHARED: a same-key append with a\n // DIFFERENT body is REJECTED, not deduplicated. Key-only dedup here\n // once masked exactly that production rejection from a test.\n if (!sameIdempotentEvent(existing, detachedInput)) {\n throw new Error(\n idempotencyConflictMessage(detachedInput.idempotencyKey, existing.offset),\n );\n }\n results.push(existing);\n continue;\n }\n const event: StreamEvent = {\n ...detachedInput,\n // Wall-clock like production, not offset-derived: expiry policy reads\n // createdAt, and epoch-1970 stamps would expire everything on arrival.\n createdAt: new Date(this.now()).toISOString(),\n offset: nextOffset++,\n path: this.path,\n };\n staged.push(event);\n results.push(event);\n }\n this.events.push(...staged);\n return results;\n }\n\n appendIfStreamId(args: { streamId: string; events: StreamEventInput[] }): Promise<StreamEvent[]> {\n if (args.streamId !== this.streamId) {\n throw new Error(`stream ID changed (${args.streamId} -> ${this.streamId}); append rejected`);\n }\n return this.append(...args.events);\n }\n\n at(path?: string): MemoryStream {\n return path === undefined || this.network === undefined ? this : this.network.get(path);\n }\n\n async getEvent(\n input: { offset: number } | { idempotencyKey: string },\n ): Promise<StreamEvent | undefined> {\n if (\"offset\" in input) return this.events.find((event) => event.offset === input.offset);\n return this.events.find((event) => event.idempotencyKey === input.idempotencyKey);\n }\n\n async getEvents(input: StreamEventReadInput = {}): Promise<StreamEvent[]> {\n const { afterOffset = 0, limit = 500 } = input;\n const beforeOffset = input.beforeOffset ?? Number.MAX_SAFE_INTEGER;\n const matching = this.events\n .filter((event) => event.offset > afterOffset)\n .filter((event) => event.offset < beforeOffset)\n .filter(\n (event) =>\n input.eventTypes === undefined ||\n input.eventTypes.includes(\"*\") ||\n input.eventTypes.includes(event.type),\n )\n .slice(0, limit);\n const byteLimit = input.byteLimit;\n if (byteLimit === undefined) return matching;\n let bytes = 0;\n return matching.filter((event, index) => {\n bytes += new TextEncoder().encode(JSON.stringify(event)).byteLength;\n return bytes <= byteLimit || index === 0;\n });\n }\n\n async getEventPage(input: StreamEventReadInput = {}) {\n return {\n streamId: this.streamId,\n streamMaxOffset: this.events.at(-1)?.offset ?? 0,\n events: await this.getEvents(input),\n };\n }\n\n readEvents(input: StreamEventReadInput = {}) {\n let afterOffset = input.afterOffset ?? 0;\n return {\n next: async () => {\n const page = await this.getEvents({ ...input, afterOffset });\n afterOffset = page.at(-1)?.offset ?? afterOffset;\n return page;\n },\n [Symbol.dispose]() {},\n };\n }\n\n async waitForEvent(input: {\n afterOffset?: number;\n eventTypes?: readonly string[];\n predicate?: (event: StreamEvent) => boolean | Promise<boolean>;\n timeoutMs: number;\n }): Promise<StreamEvent> {\n const deadline = Date.now() + input.timeoutMs;\n while (Date.now() < deadline) {\n for (const event of this.events) {\n if (input.afterOffset !== undefined && event.offset <= input.afterOffset) continue;\n if (input.eventTypes !== undefined && !input.eventTypes.includes(event.type)) continue;\n if (input.predicate !== undefined && !(await input.predicate(event))) continue;\n return event;\n }\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n throw new Error(\"Timed out waiting for event\");\n }\n\n async getProcessorRuntimeState(): Promise<null> {\n return null;\n }\n\n async runtimeState() {\n return emptyStreamRuntimeState();\n }\n}\n\n/**\n * In-memory network of streams keyed by path, so router tests can observe\n * the cross-stream forwards (`stream.at(path).append(...)`) next to\n * same-stream appends. `now` feeds every member stream's createdAt stamp, so\n * freshness checks (such as webhook acknowledgement horizons) can be tested on both sides\n * of the horizon. `eventsAt` never creates a stream — `network.streams.size`\n * assertions (\"nothing was forwarded anywhere\") stay honest.\n */\nexport class MemoryStreamNetwork {\n readonly streams = new Map<string, MemoryStream>();\n readonly now: () => number;\n\n constructor(now: () => number = Date.now) {\n this.now = now;\n }\n\n get(path: string): MemoryStream {\n let stream = this.streams.get(path);\n if (stream === undefined) {\n stream = new MemoryStream(path);\n stream.network = this;\n stream.now = this.now;\n this.streams.set(path, stream);\n }\n return stream;\n }\n\n eventsAt(path: string): StreamEvent[] {\n return this.streams.get(path)?.events ?? [];\n }\n}\n\n// =============================================================================\n// Step harness: scenario tests as ordered steps over a durable substrate.\n// =============================================================================\n\n/**\n * One scenario step. Three built-in kinds plus a function escape hatch:\n *\n * - `[\"append\", ...events]` — typed appends (the contract's `consumes`\n * vocabulary: what the outside world can put on this processor's stream);\n * - `[\"advanceTime\", ms]` — move the virtual clock, releasing any harness\n * `sleep(...)` calls that come due;\n * - `[\"crash\"]` — abandon the incarnation like an eviction (runtime state and\n * pending closures die; stream, progress, and clock survive);\n * - `async () => ...` — anything processor-specific (resolve a fake transport,\n * assert mid-scenario).\n *\n * After every step the harness drives delivery to a fixpoint, so each step\n * observes the stream consequences of the previous one.\n */\nexport type HarnessStep<Contract extends StreamProcessorContract> =\n | readonly [\"append\", ...ConsumedInput<Contract>[]]\n | readonly [\"advanceTime\", number]\n | readonly [\"crash\"]\n | (() => unknown);\n\n/** The durable substrate shared across incarnations (and zombie twins). */\nexport type HarnessSubstrate = {\n clock: { now: number };\n stream: MemoryStream;\n progress: ProcessorProgressStore<unknown>;\n};\n\ntype HarnessDurabilitySubstrate = {\n storage: DurableObjectStorage;\n alarm: { at: number | null };\n};\n\nconst durabilitySubstrates = new WeakMap<HarnessSubstrate, HarnessDurabilitySubstrate>();\nconst progressSubstrates = new WeakMap<object, HarnessDurabilitySubstrate>();\n\nfunction makeDurabilitySubstrate(): HarnessDurabilitySubstrate {\n const kv = new Map<string, unknown>();\n // `null as number | null` widens the literal so later `alarm.at = 123`\n // assignments typecheck — an annotated `let` can't live in an object field.\n const alarm = { at: null as number | null };\n const storage = {\n kv: {\n // The map stores whatever the caller `put` under the key; `get<T>`\n // mirrors the platform API's caller-asserted typing (the real\n // DurableObjectStorage.kv.get is exactly this trust), so the cast\n // restates the platform contract, not a new claim.\n get: <T = unknown>(key: string): T | undefined =>\n kv.has(key) ? (structuredClone(kv.get(key)) as T) : undefined,\n put: (key: string, value: unknown) => void kv.set(key, structuredClone(value)),\n delete: (key: string) => kv.delete(key),\n },\n getAlarm: async () => alarm.at,\n setAlarm: async (at: number) => {\n alarm.at = at;\n },\n deleteAlarm: async () => {\n alarm.at = null;\n },\n // Double assertion because this implements only the members the keepalive\n // and progress adapters touch (kv get/put/delete, get/set/deleteAlarm) — the\n // platform type's dozens of other members (sql, transactions, bookmarks)\n // are structurally missing on purpose. Safe for every consumer HERE\n // because the adapters' member usage is pinned by these suites; a new\n // adapter dependency would throw undefined-is-not-a-function loudly.\n } as unknown as DurableObjectStorage;\n return { storage, alarm };\n}\n\n/** What {@link makeProcessorHarness}'s factory receives: base deps plus the\n * harness's virtual clock/sleep, for processors that take injectable time. */\nexport type HarnessProcessorDeps<Contract extends StreamProcessorContract> = {\n stream: ProcessorStream;\n path: string;\n projectId: string | null;\n now: () => number;\n sleep: (ms: number) => Promise<void>;\n reads: Pick<RegisteredProcessorReads<ProcessorState<Contract>>, \"snapshot\" | \"waitUntilEvent\">;\n};\n\nexport type ProcessorHarness<\n Contract extends StreamProcessorContract,\n Processor extends StreamProcessor<Contract, object> = StreamProcessor<Contract, object>,\n> = {\n clock: { now: number };\n stream: MemoryStream;\n substrate: HarnessSubstrate;\n /** The current incarnation's runner, for tests that need its full surface.\n * A method (not a property) so suites can spread the harness into a wider\n * object without freezing a pre-crash incarnation. */\n runner(): StreamProcessorRunner<Contract>;\n /** The current incarnation's processor instance. */\n processor(): Processor;\n /** Run steps in order, driving delivery to a fixpoint after each. */\n play(...steps: HarnessStep<Contract>[]): Promise<void>;\n /** Typed append (the contract's `consumes` vocabulary) + drive to fixpoint. */\n append(...events: ConsumedInput<Contract>[]): Promise<void>;\n /** Advance the virtual clock, release due sleeps, drive to fixpoint. */\n advanceTime(ms: number): Promise<void>;\n /** Abandon this incarnation like an eviction; a fresh one takes over the\n * same stream/progress/clock. Does NOT deliver — the successor first acts\n * on the next step, exactly like a real revival. */\n crash(): void;\n /** Drive delivery to a fixpoint: repeated catch-up passes (with microtask\n * drains between them, so settled background work can land its appends)\n * until the stream head stops moving. */\n settle(): Promise<void>;\n /** The runner's committed reduced state. */\n state(): ProcessorState<Contract>;\n /** All committed stream rows. */\n events(): StreamEvent[];\n /** Committed rows of one event type, typed when the contract resolves it. */\n events<Type extends string>(type: Type): ResolvedEvent<Contract, Type>[];\n};\n\n/** In-memory {@link ProcessorProgressStore} with the DO store's fencing check,\n * so progress survives `crash()` into the successor incarnation. Exported so\n * suites can hand a harness a FRESH store over an existing stream — a full\n * replay from offset zero, which is both the re-reduce-from-scratch recipe and the harshest\n * at-least-once redelivery test (every per-event append re-runs).\n *\n * The contract is required because progress and recovery share the same\n * per-processor Durable Object keys. A generic placeholder slug would let the\n * runner commit progress under one key while recovery looks under another.\n */\nexport function makeMemoryProgressStore(\n contract: Pick<StreamProcessorContract, \"slug\">,\n): ProcessorProgressStore<unknown> {\n const durability = makeDurabilitySubstrate();\n const progress = durableObjectProgressStore<unknown>({\n storage: durability.storage,\n name: contract.slug,\n });\n progressSubstrates.set(progress, durability);\n return progress;\n}\n\n/**\n * Build a {@link ProcessorHarness}: the real runner, ProcessorKeepalive, and\n * Durable Object durability adapters over an in-memory stream/KV/alarm\n * substrate. `crash()` is eviction: delivery stays detached until a new\n * append wakes it or a due keepalive alarm appends the revival fact. Pass\n * another harness's `substrate` to run a second incarnation over the same\n * durable state (the zombie-race setup).\n *\n * Appends commit synchronously unless a suite opts out through\n * `harness.stream.holdAppend` — the one seam for reproducing a processor that\n * appends the same lane from two places and orders only one of them.\n */\nexport function makeProcessorHarness<\n Contract extends StreamProcessorContract,\n Processor extends StreamProcessor<Contract, object> = StreamProcessor<Contract, object>,\n>(args: {\n createProcessor: (deps: HarnessProcessorDeps<Contract>) => Processor;\n path?: string;\n substrate?: HarnessSubstrate;\n}): ProcessorHarness<Contract, Processor> {\n const path = args.substrate?.stream.path ?? args.path ?? \"/harness/processor\";\n const clock = args.substrate?.clock ?? { now: 1_000_000 };\n const stream = args.substrate?.stream ?? new MemoryStream(path);\n stream.now = () => clock.now;\n const inheritedDurability =\n args.substrate === undefined\n ? undefined\n : (durabilitySubstrates.get(args.substrate) ??\n progressSubstrates.get(args.substrate.progress));\n const durability = inheritedDurability ?? makeDurabilitySubstrate();\n\n // The substrate stores progress as `<unknown>` so one substrate type serves\n // every contract; the narrowing is safe because a substrate is only ever\n // shared between incarnations of the SAME processor (the zombie-race\n // setup), so the state it holds is this contract's. TypeScript cannot carry\n // the contract through the untyped substrate hand-off.\n let progress = args.substrate?.progress as\n | ProcessorProgressStore<ProcessorState<Contract>>\n | undefined;\n let pendingSleeps: { dueAt: number; resolve: () => void }[] = [];\n const releaseDueSleeps = () => {\n const due = pendingSleeps.filter((sleep) => sleep.dueAt <= clock.now);\n pendingSleeps = pendingSleeps.filter((sleep) => sleep.dueAt > clock.now);\n for (const sleep of due) sleep.resolve();\n return due.length;\n };\n\n let processor!: Processor;\n let runner!: StreamProcessorRunner<Contract>;\n let recovery!: ReturnType<typeof durableObjectRecovery>;\n let incarnation = 0;\n let deliveryEnabled = true;\n let deliveryAttempt: Promise<void> | undefined;\n let deliveryError: unknown;\n\n const keepAlive = (work: Promise<unknown>) => {\n void work.catch(() => undefined);\n };\n const incarnate = () => {\n processor = args.createProcessor({\n stream,\n path,\n projectId: \"proj_harness\",\n now: () => clock.now,\n sleep: (ms) =>\n new Promise<void>((resolve) => pendingSleeps.push({ dueAt: clock.now + ms, resolve })),\n reads: {\n snapshot: () => runner.snapshot(),\n waitUntilEvent: (input) =>\n \"offset\" in input ? runner.waitUntilEvent(input) : runner.waitUntilEvent(input),\n },\n });\n progress ??= durableObjectProgressStore<ProcessorState<Contract>>({\n storage: durability.storage,\n name: processor.contract.slug,\n });\n recovery = durableObjectRecovery({\n storage: durability.storage,\n name: processor.contract.slug,\n stream,\n version: \"test-harness\",\n armAlarm: (atMs) => {\n if (atMs === null) void durability.storage.deleteAlarm();\n else void durability.storage.setAlarm(atMs);\n },\n waitUntil: keepAlive,\n now: () => clock.now,\n });\n runner = new StreamProcessorRunner({\n processor,\n stream,\n durability: { progress, recovery },\n keepAlive: (work) => keepAlive(work()),\n now: () => clock.now,\n readPageSize: 5,\n });\n };\n incarnate();\n\n const substrate: HarnessSubstrate = {\n clock,\n stream,\n // Inverse of the narrowing above: the substrate's public face erases the\n // contract (stores are invariant in their state parameter, so neither\n // direction is assignable without the assertion pair).\n progress: progress as ProcessorProgressStore<unknown>,\n };\n if (args.substrate !== undefined) durabilitySubstrates.set(args.substrate, durability);\n durabilitySubstrates.set(substrate, durability);\n progressSubstrates.set(substrate.progress, durability);\n\n const startDelivery = () => {\n if (deliveryAttempt !== undefined) return;\n const deliveryIncarnation = incarnation;\n const attempt = runner.catchUp();\n deliveryAttempt = attempt;\n void attempt.then(\n () => {\n if (deliveryIncarnation === incarnation) deliveryAttempt = undefined;\n },\n (error: unknown) => {\n if (deliveryIncarnation !== incarnation) return;\n deliveryAttempt = undefined;\n deliveryError = error;\n },\n );\n };\n\n const settle = async () => {\n // Fixpoint with a bounded round count: each round drains real microtasks\n // and macrotasks so background work can append, then catches the runner up.\n let quietRounds = 0;\n let lastAppendedTypes: string[] = [];\n for (let round = 0; round < 50; round++) {\n const headBefore = stream.events.at(-1)?.offset ?? 0;\n const releasedSleeps = releaseDueSleeps();\n if (deliveryEnabled) startDelivery();\n await new Promise((resolve) => setTimeout(resolve, 0));\n if (deliveryError !== undefined) {\n const error = deliveryError;\n deliveryError = undefined;\n throw error;\n }\n const headAfter = stream.events.at(-1)?.offset ?? 0;\n lastAppendedTypes = stream.events\n .filter((event) => event.offset > headBefore)\n .map((event) => event.type);\n quietRounds = headAfter === headBefore && releasedSleeps === 0 ? quietRounds + 1 : 0;\n // An alarm-backed attempt may be intentionally hung so the next crash\n // can evict it. Once the stream is quiet and its durable recovery alarm\n // is armed, that parked attempt is a stable scenario boundary.\n if (\n quietRounds === 3 &&\n (!deliveryEnabled || deliveryAttempt === undefined || durability.alarm.at !== null)\n ) {\n return;\n }\n }\n const headOffset = stream.events.at(-1)?.offset ?? 0;\n throw new Error(\n `harness settle() did not reach a fixpoint in 50 rounds; ` +\n `last round appended event types: ${lastAppendedTypes.join(\", \") || \"none\"}; ` +\n `head offset: ${headOffset}`,\n );\n };\n\n const append = async (...events: ConsumedInput<Contract>[]) => {\n // A consumed input IS a stream event input — the contract type only\n // narrows `type`/`payload` to the consumed vocabulary. TypeScript cannot\n // relate the distributive contract-derived union back to the plain input\n // shape, so the widening is asserted.\n await stream.append(...(events as StreamEventInput[]));\n deliveryEnabled = true;\n await settle();\n };\n\n const advanceTime = async (ms: number) => {\n const target = clock.now + ms;\n while (durability.alarm.at !== null && durability.alarm.at <= target) {\n clock.now = Math.max(clock.now, durability.alarm.at);\n durability.alarm.at = null;\n await recovery.handleAlarm();\n deliveryEnabled = true;\n await settle();\n }\n clock.now = target;\n await settle();\n };\n\n const crash = () => {\n runner.dispose();\n incarnation += 1;\n deliveryEnabled = false;\n deliveryAttempt = undefined;\n deliveryError = undefined;\n pendingSleeps = [];\n incarnate();\n };\n\n return {\n clock,\n stream,\n substrate,\n runner: () => runner,\n processor: () => processor,\n append,\n advanceTime,\n crash,\n settle,\n state: () => runner.currentState,\n // TypeScript cannot check an implementation arrow against an overload\n // pair, so the assertion is unavoidable. Soundness is the same claim the\n // typed read surface makes everywhere: rows filtered to `type` carry that\n // type's contract payload — committed rows are trusted, not re-parsed,\n // exactly like production reads.\n events: ((type?: string) =>\n type === undefined\n ? [...stream.events]\n : stream.events.filter((row) => row.type === type)) as ProcessorHarness<\n Contract,\n Processor\n >[\"events\"],\n async play(...steps: HarnessStep<Contract>[]) {\n for (const [index, step] of steps.entries()) {\n const kind = typeof step === \"function\" ? \"function\" : step[0];\n try {\n if (typeof step === \"function\") {\n await step();\n await settle();\n } else if (step[0] === \"append\") {\n const [, ...events] = step;\n await append(...events);\n } else if (step[0] === \"advanceTime\") {\n await advanceTime(step[1]);\n } else {\n crash();\n }\n } catch (error) {\n throw new Error(`harness play() step ${index} (${kind}) failed`, { cause: error });\n }\n }\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/processors.mjs
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { A as resolveStreamPath, C as StreamListItem, D as StreamRuntimeMetrics, E as MinuteBuckets, F as isStreamIdMismatchError, I as isStreamOffsetConflictError, L as isStreamReceiverUnavailableError, M as StreamIdMismatchError, N as StreamOffsetConflictError, O as ageStreamThroughputMetrics, P as StreamReceiverUnavailableError, R as streamIdMismatchMessage, S as StreamEventInput, T as LatencyRing, _ as getEventInputSchema, a as revivalBackoffMs, b as MAX_COPIED_FROM_HOPS, c as awaitKeepAliveBacked, d as STREAM_PROCESSOR_REVIVED_EVENT_TYPE, f as assertObjectProcessorState, g as getConsumedEventDefinition, h as defineProcessorContract, i as REVIVAL_BACKOFF_PLATEAU_MS, j as MAX_STREAM_EVENT_READ_BYTE_LIMIT, k as pingRoundTrip, l as PLATFORM_STREAM_EVENTS, m as cachedEventSchema, n as MAX_CONSECUTIVE_BUSY_REFIRES, o as StreamProcessorRunner, p as buildEvent, r as ProcessorKeepalive, s as StreamProcessor, t as KEEPALIVE_ALARM_LEAD_MS, u as ProcessorContractAnnouncement, v as getResolvedEventDefinition, w as EventConsumptionMetrics, x as StreamEvent, y as mergeProcessorConfig, z as streamOffsetConflictMessage } from "./stream-processor-keepalive-DAQTP6m3.mjs";
|
|
2
|
-
import { n as hostRuntimeCapabilities, t as announceContract } from "./processor-host-capabilities-BMFH3KTM.mjs";
|
|
3
|
-
import { i as sameIdempotentEvent, n as isIdempotencyConflict, r as jsonValuesEqual, t as idempotencyConflictMessage } from "./idempotency-DleloJNt.mjs";
|
|
4
|
-
//#region src/processors/prompt-sections.ts
|
|
5
|
-
const SECTION_OPEN_TAG = /<section key="([^"<>]+)">/g;
|
|
6
|
-
/**
|
|
7
|
-
* Parse a prompt file's authoring syntax into sections, in file order.
|
|
8
|
-
* Content inside `<section key="x">...</section>` becomes a section with
|
|
9
|
-
* that key; content outside any tag (including a whole untagged file)
|
|
10
|
-
* becomes a section with `fallbackKey` — so an untagged prompt file parses
|
|
11
|
-
* to one section and keeps working unchanged. Throws on malformed authoring
|
|
12
|
-
* (unclosed or nested tags): this runs at append time, where a loud failure
|
|
13
|
-
* beats silently shipping tag soup to a model.
|
|
14
|
-
*/
|
|
15
|
-
function parsePromptSections(input) {
|
|
16
|
-
const sections = [];
|
|
17
|
-
const pushFallback = (raw) => {
|
|
18
|
-
const content = raw.trim();
|
|
19
|
-
if (content !== "") sections.push({
|
|
20
|
-
key: input.fallbackKey,
|
|
21
|
-
content
|
|
22
|
-
});
|
|
23
|
-
};
|
|
24
|
-
let cursor = 0;
|
|
25
|
-
SECTION_OPEN_TAG.lastIndex = 0;
|
|
26
|
-
for (;;) {
|
|
27
|
-
const open = SECTION_OPEN_TAG.exec(input.content);
|
|
28
|
-
if (open === null) break;
|
|
29
|
-
pushFallback(input.content.slice(cursor, open.index));
|
|
30
|
-
const bodyStart = open.index + open[0].length;
|
|
31
|
-
const close = input.content.indexOf("</section>", bodyStart);
|
|
32
|
-
if (close < 0) throw new Error(`unclosed <section key="${open[1]}"> in prompt file`);
|
|
33
|
-
const body = input.content.slice(bodyStart, close);
|
|
34
|
-
if (body.includes("<section ")) throw new Error(`nested <section> inside <section key="${open[1]}"> — sections are flat`);
|
|
35
|
-
sections.push({
|
|
36
|
-
key: open[1],
|
|
37
|
-
content: body.trim()
|
|
38
|
-
});
|
|
39
|
-
cursor = close + 10;
|
|
40
|
-
SECTION_OPEN_TAG.lastIndex = cursor;
|
|
41
|
-
}
|
|
42
|
-
pushFallback(input.content.slice(cursor));
|
|
43
|
-
if (sections.length === 0) sections.push({
|
|
44
|
-
key: input.fallbackKey,
|
|
45
|
-
content: ""
|
|
46
|
-
});
|
|
47
|
-
return sections;
|
|
48
|
-
}
|
|
49
|
-
//#endregion
|
|
50
|
-
export { EventConsumptionMetrics, KEEPALIVE_ALARM_LEAD_MS, LatencyRing, MAX_CONSECUTIVE_BUSY_REFIRES, MAX_COPIED_FROM_HOPS, MAX_STREAM_EVENT_READ_BYTE_LIMIT, MinuteBuckets, PLATFORM_STREAM_EVENTS, ProcessorContractAnnouncement, ProcessorKeepalive, REVIVAL_BACKOFF_PLATEAU_MS, STREAM_PROCESSOR_REVIVED_EVENT_TYPE, StreamEvent, StreamEventInput, StreamIdMismatchError, StreamListItem, StreamOffsetConflictError, StreamProcessor, StreamProcessorRunner, StreamReceiverUnavailableError, StreamRuntimeMetrics, ageStreamThroughputMetrics, announceContract, assertObjectProcessorState, awaitKeepAliveBacked, buildEvent, cachedEventSchema, defineProcessorContract, getConsumedEventDefinition, getEventInputSchema, getResolvedEventDefinition, hostRuntimeCapabilities, idempotencyConflictMessage, isIdempotencyConflict, isStreamIdMismatchError, isStreamOffsetConflictError, isStreamReceiverUnavailableError, jsonValuesEqual, mergeProcessorConfig, parsePromptSections, pingRoundTrip, resolveStreamPath, revivalBackoffMs, sameIdempotentEvent, streamIdMismatchMessage, streamOffsetConflictMessage };
|
|
51
|
-
|
|
52
|
-
//# sourceMappingURL=processors.mjs.map
|
package/dist/processors.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"processors.mjs","names":[],"sources":["../src/processors/prompt-sections.ts"],"sourcesContent":["// The append-time authoring parser for sectionized prompt files (design:\n// tasks/prompt-sections-tree.md and docs/prompt-sections-demo.html): authors\n// write ONE file with `<section key=\"...\">` tags; the APPENDING code parses\n// it once into a list of {key, content} sections it maps to keyed\n// context-added events — one per section, committed in a single atomic\n// batch, so file order becomes offset order becomes document order, and\n// every later change addresses a section by re-adding its key. Nothing ever\n// parses model-visible strings again. The tags are the SAME syntax the fold\n// renders the standing document with, so an unforked file round-trips\n// byte-identically. Shared between the platform (agent birth defaults) and\n// config-repo template workers, which is why it lives in the iterate\n// package rather than apps/os.\n\nexport type PromptSection = { key: string; content: string };\n\nconst SECTION_OPEN_TAG = /<section key=\"([^\"<>]+)\">/g;\n\n/**\n * Parse a prompt file's authoring syntax into sections, in file order.\n * Content inside `<section key=\"x\">...</section>` becomes a section with\n * that key; content outside any tag (including a whole untagged file)\n * becomes a section with `fallbackKey` — so an untagged prompt file parses\n * to one section and keeps working unchanged. Throws on malformed authoring\n * (unclosed or nested tags): this runs at append time, where a loud failure\n * beats silently shipping tag soup to a model.\n */\nexport function parsePromptSections(input: {\n content: string;\n fallbackKey: string;\n}): PromptSection[] {\n const sections: PromptSection[] = [];\n const pushFallback = (raw: string) => {\n const content = raw.trim();\n if (content !== \"\") sections.push({ key: input.fallbackKey, content });\n };\n let cursor = 0;\n SECTION_OPEN_TAG.lastIndex = 0;\n for (;;) {\n const open = SECTION_OPEN_TAG.exec(input.content);\n if (open === null) break;\n pushFallback(input.content.slice(cursor, open.index));\n const bodyStart = open.index + open[0].length;\n const close = input.content.indexOf(\"</section>\", bodyStart);\n if (close < 0) {\n throw new Error(`unclosed <section key=\"${open[1]}\"> in prompt file`);\n }\n const body = input.content.slice(bodyStart, close);\n if (body.includes(\"<section \")) {\n throw new Error(`nested <section> inside <section key=\"${open[1]}\"> — sections are flat`);\n }\n sections.push({ key: open[1]!, content: body.trim() });\n cursor = close + \"</section>\".length;\n SECTION_OPEN_TAG.lastIndex = cursor;\n }\n pushFallback(input.content.slice(cursor));\n if (sections.length === 0) {\n // An empty (or whitespace-only) file still parses: one empty fallback\n // segment, so a keyed supersession with empty content stays expressible.\n sections.push({ key: input.fallbackKey, content: \"\" });\n }\n return sections;\n}\n"],"mappings":";;;;AAeA,MAAM,mBAAmB;;;;;;;;;;AAWzB,SAAgB,oBAAoB,OAGhB;CAClB,MAAM,WAA4B,CAAC;CACnC,MAAM,gBAAgB,QAAgB;EACpC,MAAM,UAAU,IAAI,KAAK;EACzB,IAAI,YAAY,IAAI,SAAS,KAAK;GAAE,KAAK,MAAM;GAAa;EAAQ,CAAC;CACvE;CACA,IAAI,SAAS;CACb,iBAAiB,YAAY;CAC7B,SAAS;EACP,MAAM,OAAO,iBAAiB,KAAK,MAAM,OAAO;EAChD,IAAI,SAAS,MAAM;EACnB,aAAa,MAAM,QAAQ,MAAM,QAAQ,KAAK,KAAK,CAAC;EACpD,MAAM,YAAY,KAAK,QAAQ,KAAK,EAAE,CAAC;EACvC,MAAM,QAAQ,MAAM,QAAQ,QAAQ,cAAc,SAAS;EAC3D,IAAI,QAAQ,GACV,MAAM,IAAI,MAAM,0BAA0B,KAAK,GAAG,kBAAkB;EAEtE,MAAM,OAAO,MAAM,QAAQ,MAAM,WAAW,KAAK;EACjD,IAAI,KAAK,SAAS,WAAW,GAC3B,MAAM,IAAI,MAAM,yCAAyC,KAAK,GAAG,uBAAuB;EAE1F,SAAS,KAAK;GAAE,KAAK,KAAK;GAAK,SAAS,KAAK,KAAK;EAAE,CAAC;EACrD,SAAS,QAAQ;EACjB,iBAAiB,YAAY;CAC/B;CACA,aAAa,MAAM,QAAQ,MAAM,MAAM,CAAC;CACxC,IAAI,SAAS,WAAW,GAGtB,SAAS,KAAK;EAAE,KAAK,MAAM;EAAa,SAAS;CAAG,CAAC;CAEvD,OAAO;AACT"}
|