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.
Files changed (241) hide show
  1. package/README.md +164 -82
  2. package/dist/{next/api.d.ts → api.d.ts} +197 -33
  3. package/dist/{next/app-server.d.ts → app-server.d.ts} +7 -0
  4. package/dist/{next/app-server.mjs → app-server.mjs} +17 -15
  5. package/dist/app-server.mjs.map +1 -0
  6. package/dist/{next/app-session.mjs → app-session.mjs} +12 -15
  7. package/dist/app-session.mjs.map +1 -0
  8. package/dist/{next/app.mjs → app.mjs} +53 -14
  9. package/dist/app.mjs.map +1 -0
  10. package/dist/{next/client → client}/live-state.d.ts +11 -11
  11. package/dist/client/oauth.d.ts +17 -0
  12. package/dist/{next/client → client}/react.d.ts +10 -42
  13. package/dist/{next/client → client}/socket.d.ts +1 -0
  14. package/dist/client.mjs +156 -4
  15. package/dist/client.mjs.map +1 -0
  16. package/dist/{next/expression.d.ts → expression.d.ts} +11 -69
  17. package/dist/{next/expression.mjs → expression.mjs} +11 -109
  18. package/dist/expression.mjs.map +1 -0
  19. package/dist/lib-BWr-5mFO.mjs +36 -0
  20. package/dist/lib-BWr-5mFO.mjs.map +1 -0
  21. package/dist/{next/lib.d.ts → lib.d.ts} +16 -2
  22. package/dist/{next/lib.mjs → lib.mjs} +32 -3
  23. package/dist/lib.mjs.map +1 -0
  24. package/dist/node.d.ts +15 -3
  25. package/dist/node.mjs +36 -174
  26. package/dist/node.mjs.map +1 -1
  27. package/dist/{next/oauth-scopes.mjs → oauth-scopes.mjs} +1 -1
  28. package/dist/oauth-scopes.mjs.map +1 -0
  29. package/dist/{next/oauth.mjs → oauth.mjs} +14 -2
  30. package/dist/oauth.mjs.map +1 -0
  31. package/dist/principal.d.ts +8 -0
  32. package/dist/principal.mjs +8 -0
  33. package/dist/principal.mjs.map +1 -0
  34. package/dist/project-ingress.d.ts +58 -0
  35. package/dist/project-ingress.mjs +104 -0
  36. package/dist/project-ingress.mjs.map +1 -0
  37. package/dist/{next/react.mjs → react.mjs} +12 -12
  38. package/dist/react.mjs.map +1 -0
  39. package/dist/sdk/auth.d.ts +25 -0
  40. package/dist/sdk/index.d.ts +155 -0
  41. package/dist/sdk/record-pipelined-steps.d.ts +19 -0
  42. package/dist/sdk.mjs +245 -2
  43. package/dist/sdk.mjs.map +1 -0
  44. package/dist/{next/stream → stream}/processor.d.ts +28 -23
  45. package/dist/{next/stream → stream}/processor.mjs +48 -25
  46. package/dist/stream/processor.mjs.map +1 -0
  47. package/dist/{next/stream → stream}/run.d.ts +9 -6
  48. package/dist/{next/stream → stream}/run.mjs +13 -8
  49. package/dist/stream/run.mjs.map +1 -0
  50. package/dist/stream/test-support.d.ts +45 -0
  51. package/dist/stream/test-support.mjs +196 -0
  52. package/dist/stream/test-support.mjs.map +1 -0
  53. package/package.json +65 -219
  54. package/THIRD_PARTY_NOTICES.md +0 -55
  55. package/bin/iterate.js +0 -94
  56. package/dist/api-url-B6404M82.mjs +0 -17
  57. package/dist/api-url-B6404M82.mjs.map +0 -1
  58. package/dist/app-ref-BipL0feU.mjs +0 -35
  59. package/dist/app-ref-BipL0feU.mjs.map +0 -1
  60. package/dist/app-ref-C1CrgXqX.mjs +0 -7
  61. package/dist/app-ref-C1CrgXqX.mjs.map +0 -1
  62. package/dist/app-ref-DYai_om1.mjs +0 -7
  63. package/dist/app-ref-DYai_om1.mjs.map +0 -1
  64. package/dist/cli-D0c-pDL_.mjs +0 -1010
  65. package/dist/cli-D0c-pDL_.mjs.map +0 -1
  66. package/dist/client.d.ts +0 -3
  67. package/dist/cloudflare-BTm90gQ4.mjs +0 -951
  68. package/dist/cloudflare-BTm90gQ4.mjs.map +0 -1
  69. package/dist/contract-s4FW4eES.mjs +0 -309
  70. package/dist/contract-s4FW4eES.mjs.map +0 -1
  71. package/dist/document-review/index.d.ts +0 -5
  72. package/dist/document-review/types.d.ts +0 -107
  73. package/dist/document-review.mjs +0 -7015
  74. package/dist/document-review.mjs.map +0 -1
  75. package/dist/durable-object-processor-durability-CNsTjAJS.mjs +0 -205
  76. package/dist/durable-object-processor-durability-CNsTjAJS.mjs.map +0 -1
  77. package/dist/idempotency-DleloJNt.mjs +0 -28
  78. package/dist/idempotency-DleloJNt.mjs.map +0 -1
  79. package/dist/index.d.mts +0 -5
  80. package/dist/index.mjs +0 -8
  81. package/dist/index.mjs.map +0 -1
  82. package/dist/itx/api-url.d.ts +0 -6
  83. package/dist/itx/itx-node-client.d.ts +0 -65
  84. package/dist/itx/itx-session.d.ts +0 -215
  85. package/dist/itx/owned-rpc-session.d.ts +0 -14
  86. package/dist/itx/query-client.d.ts +0 -10
  87. package/dist/itx-api.generated.d.ts +0 -6195
  88. package/dist/itx-session-sjud8GiT.mjs +0 -534
  89. package/dist/itx-session-sjud8GiT.mjs.map +0 -1
  90. package/dist/live-state-BJNqOwFw.mjs +0 -299
  91. package/dist/live-state-BJNqOwFw.mjs.map +0 -1
  92. package/dist/next/app-server.mjs.map +0 -1
  93. package/dist/next/app-session.mjs.map +0 -1
  94. package/dist/next/app.mjs.map +0 -1
  95. package/dist/next/client/oauth.d.ts +0 -12
  96. package/dist/next/client.mjs +0 -156
  97. package/dist/next/client.mjs.map +0 -1
  98. package/dist/next/expression.mjs.map +0 -1
  99. package/dist/next/lib.mjs.map +0 -1
  100. package/dist/next/oauth-scopes.mjs.map +0 -1
  101. package/dist/next/oauth.mjs.map +0 -1
  102. package/dist/next/principal.d.ts +0 -64
  103. package/dist/next/principal.mjs +0 -98
  104. package/dist/next/principal.mjs.map +0 -1
  105. package/dist/next/project-ingress.d.ts +0 -37
  106. package/dist/next/project-ingress.mjs +0 -75
  107. package/dist/next/project-ingress.mjs.map +0 -1
  108. package/dist/next/react.mjs.map +0 -1
  109. package/dist/next/sdk/auth.d.ts +0 -5
  110. package/dist/next/sdk/index.d.ts +0 -112
  111. package/dist/next/sdk.mjs +0 -139
  112. package/dist/next/sdk.mjs.map +0 -1
  113. package/dist/next/stream/processor.mjs.map +0 -1
  114. package/dist/next/stream/run.mjs.map +0 -1
  115. package/dist/next-node.d.ts +0 -15
  116. package/dist/next-node.mjs +0 -51
  117. package/dist/next-node.mjs.map +0 -1
  118. package/dist/processor-host-capabilities-BMFH3KTM.mjs +0 -56
  119. package/dist/processor-host-capabilities-BMFH3KTM.mjs.map +0 -1
  120. package/dist/processors/cloudflare.d.ts +0 -3
  121. package/dist/processors/durable-object-processor-durability.d.ts +0 -79
  122. package/dist/processors/event-consumption-metrics.d.ts +0 -82
  123. package/dist/processors/idempotency.d.ts +0 -13
  124. package/dist/processors/index.d.ts +0 -12
  125. package/dist/processors/processor-contracts.d.ts +0 -342
  126. package/dist/processors/processor-facet.d.ts +0 -186
  127. package/dist/processors/processor-host-capabilities.d.ts +0 -60
  128. package/dist/processors/prompt-sections.d.ts +0 -17
  129. package/dist/processors/rpc-types.d.ts +0 -515
  130. package/dist/processors/schemas.d.ts +0 -102
  131. package/dist/processors/stream-handle.d.ts +0 -45
  132. package/dist/processors/stream-processor-keepalive.d.ts +0 -95
  133. package/dist/processors/stream-processor-registry.d.ts +0 -233
  134. package/dist/processors/stream-processor-runner.d.ts +0 -289
  135. package/dist/processors/stream-processor.d.ts +0 -339
  136. package/dist/processors/stream-runtime-metrics.d.ts +0 -107
  137. package/dist/processors/testing.d.ts +0 -302
  138. package/dist/processors-BoNyeBfQ.mjs +0 -10
  139. package/dist/processors-BoNyeBfQ.mjs.map +0 -1
  140. package/dist/processors-cloudflare.mjs +0 -3
  141. package/dist/processors-testing.mjs +0 -435
  142. package/dist/processors-testing.mjs.map +0 -1
  143. package/dist/processors.mjs +0 -52
  144. package/dist/processors.mjs.map +0 -1
  145. package/dist/protocol-DnK_f2m6.mjs +0 -251
  146. package/dist/protocol-DnK_f2m6.mjs.map +0 -1
  147. package/dist/sdk/capnweb/index.d.ts +0 -2
  148. package/dist/sdk/capnweb/live-state/compact.d.ts +0 -5
  149. package/dist/sdk/capnweb/live-state/diff.d.ts +0 -41
  150. package/dist/sdk/capnweb/live-state/engine.d.ts +0 -44
  151. package/dist/sdk/capnweb/live-state/index.d.ts +0 -41
  152. package/dist/sdk/capnweb/live-state/protocol.d.ts +0 -87
  153. package/dist/sdk/capnweb/live-state/retain.d.ts +0 -23
  154. package/dist/sdk/capnweb/live-state/store.d.ts +0 -20
  155. package/dist/sdk/capnweb/live-state/types.d.ts +0 -11
  156. package/dist/sdk/capnweb/react.d.ts +0 -45
  157. package/dist/sdk/capnweb/react.mjs +0 -316
  158. package/dist/sdk/capnweb/react.mjs.map +0 -1
  159. package/dist/sdk/capnweb.mjs +0 -4
  160. package/dist/sdk/itx/react.d.ts +0 -191
  161. package/dist/sdk/itx/react.mjs +0 -383
  162. package/dist/sdk/itx/react.mjs.map +0 -1
  163. package/dist/sdk-DMB-IM11.mjs +0 -933
  164. package/dist/sdk-DMB-IM11.mjs.map +0 -1
  165. package/dist/sdk.d.ts +0 -339
  166. package/dist/serve-itx.d.ts +0 -46
  167. package/dist/starter-apps/flake-dashboard/app-ref.d.ts +0 -31
  168. package/dist/starter-apps/flake-dashboard/configured-worker.mjs +0 -1055
  169. package/dist/starter-apps/flake-dashboard/configured-worker.mjs.map +0 -1
  170. package/dist/starter-apps/flake-dashboard/contract.d.ts +0 -4839
  171. package/dist/starter-apps/flake-dashboard/contract.mjs +0 -2
  172. package/dist/starter-apps/flake-dashboard/index.d.ts +0 -17
  173. package/dist/starter-apps/flake-dashboard/index.mjs +0 -56
  174. package/dist/starter-apps/flake-dashboard/index.mjs.map +0 -1
  175. package/dist/starter-apps/flake-dashboard/worker.d.ts +0 -4607
  176. package/dist/starter-apps/github-ai-linter/ai-linter.d.ts +0 -8914
  177. package/dist/starter-apps/github-ai-linter/configured-worker.mjs +0 -17987
  178. package/dist/starter-apps/github-ai-linter/configured-worker.mjs.map +0 -1
  179. package/dist/starter-apps/github-ai-linter/contract.d.ts +0 -9193
  180. package/dist/starter-apps/github-ai-linter/index.d.ts +0 -10
  181. package/dist/starter-apps/github-ai-linter/index.mjs +0 -36
  182. package/dist/starter-apps/github-ai-linter/index.mjs.map +0 -1
  183. package/dist/starter-apps/github-ai-linter/prompt.d.ts +0 -13
  184. package/dist/starter-apps/github-ai-linter/review-bot.d.ts +0 -808
  185. package/dist/starter-apps/github-ai-linter/rules.d.ts +0 -34
  186. package/dist/starter-apps/github-ai-linter/worker-ref.d.ts +0 -19
  187. package/dist/starter-apps/github-ai-linter/worker.d.ts +0 -19
  188. package/dist/starter-apps/github-ai-linter/worker.mjs +0 -947
  189. package/dist/starter-apps/github-ai-linter/worker.mjs.map +0 -1
  190. package/dist/starter-apps/guestbook/app-ref.d.ts +0 -27
  191. package/dist/starter-apps/guestbook/client.d.ts +0 -7
  192. package/dist/starter-apps/guestbook/client.mjs +0 -59
  193. package/dist/starter-apps/guestbook/configured-worker.mjs +0 -205
  194. package/dist/starter-apps/guestbook/configured-worker.mjs.map +0 -1
  195. package/dist/starter-apps/guestbook/index.d.ts +0 -9
  196. package/dist/starter-apps/guestbook/index.mjs +0 -31
  197. package/dist/starter-apps/guestbook/index.mjs.map +0 -1
  198. package/dist/starter-apps/guestbook/processor.d.ts +0 -2267
  199. package/dist/starter-apps/guestbook/worker.d.ts +0 -26
  200. package/dist/starter-apps/guestbook/worker.mjs +0 -191
  201. package/dist/starter-apps/guestbook/worker.mjs.map +0 -1
  202. package/dist/starter-apps/media/configured-worker.mjs +0 -577
  203. package/dist/starter-apps/media/configured-worker.mjs.map +0 -1
  204. package/dist/starter-apps/media/index.mjs +0 -36
  205. package/dist/starter-apps/media/index.mjs.map +0 -1
  206. package/dist/starter-apps/media/ref.mjs +0 -20
  207. package/dist/starter-apps/media/ref.mjs.map +0 -1
  208. package/dist/starter-apps/media/worker.mjs +0 -579
  209. package/dist/starter-apps/media/worker.mjs.map +0 -1
  210. package/dist/starter-apps/notes/configured-worker.mjs +0 -6134
  211. package/dist/starter-apps/notes/configured-worker.mjs.map +0 -1
  212. package/dist/starter-apps/notes/index.mjs +0 -23
  213. package/dist/starter-apps/notes/index.mjs.map +0 -1
  214. package/dist/starter-apps/notes/ref.mjs +0 -21
  215. package/dist/starter-apps/notes/ref.mjs.map +0 -1
  216. package/dist/starter-apps/notes/worker.mjs +0 -427
  217. package/dist/starter-apps/notes/worker.mjs.map +0 -1
  218. package/dist/starter-apps/todo/client.mjs +0 -59
  219. package/dist/starter-apps/todo/configured-worker.mjs +0 -2864
  220. package/dist/starter-apps/todo/configured-worker.mjs.map +0 -1
  221. package/dist/starter-apps/todo/index.d.ts +0 -8
  222. package/dist/starter-apps/todo/index.mjs +0 -29
  223. package/dist/starter-apps/todo/index.mjs.map +0 -1
  224. package/dist/stream-processor-keepalive-DAQTP6m3.mjs +0 -2082
  225. package/dist/stream-processor-keepalive-DAQTP6m3.mjs.map +0 -1
  226. package/dist/usingCtx-mZx5nsAW.mjs +0 -11800
  227. package/dist/usingCtx-mZx5nsAW.mjs.map +0 -1
  228. package/dist/worker-ref-DZxPDmb_.mjs +0 -390
  229. package/dist/worker-ref-DZxPDmb_.mjs.map +0 -1
  230. package/dist/worker.d.mts +0 -33
  231. package/dist/worker.mjs +0 -18
  232. package/dist/worker.mjs.map +0 -1
  233. package/menubar/Iterate.entitlements +0 -12
  234. package/menubar/Iterate.swift +0 -914
  235. package/menubar/IterateIcon.swift +0 -145
  236. package/menubar/README.md +0 -28
  237. package/menubar/build-menubar-app.sh +0 -59
  238. /package/dist/{next/api.mjs → api.mjs} +0 -0
  239. /package/dist/{next/app-session.d.ts → app-session.d.ts} +0 -0
  240. /package/dist/{next/app.d.ts → app.d.ts} +0 -0
  241. /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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"}
@@ -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
@@ -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"}