@demlik/tea 0.15.0 → 0.16.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 (115) hide show
  1. package/README.md +30 -7
  2. package/dist/agent/index.d.ts +49 -37
  3. package/dist/agent/index.js +12 -8
  4. package/dist/chunk-4LPY67E4.js +18 -0
  5. package/dist/chunk-4LPY67E4.js.map +1 -0
  6. package/dist/chunk-5XU6ZJO2.js +533 -0
  7. package/dist/chunk-5XU6ZJO2.js.map +1 -0
  8. package/dist/{chunk-AYS5N6WY.js → chunk-6NG4V5SB.js} +62 -172
  9. package/dist/chunk-6NG4V5SB.js.map +1 -0
  10. package/dist/{chunk-3B5X7SBB.js → chunk-7MGXQZPW.js} +11 -9
  11. package/dist/chunk-7MGXQZPW.js.map +1 -0
  12. package/dist/{chunk-V46HSVCK.js → chunk-BCTEOVOU.js} +3 -3
  13. package/dist/{chunk-V46HSVCK.js.map → chunk-BCTEOVOU.js.map} +1 -1
  14. package/dist/chunk-CH5KTOEZ.js +710 -0
  15. package/dist/chunk-CH5KTOEZ.js.map +1 -0
  16. package/dist/chunk-CIHSHLQX.js +64 -0
  17. package/dist/chunk-CIHSHLQX.js.map +1 -0
  18. package/dist/{chunk-TCLBCERH.js → chunk-EHUDXAKC.js} +238 -212
  19. package/dist/chunk-EHUDXAKC.js.map +1 -0
  20. package/dist/chunk-GOX2CYIM.js +380 -0
  21. package/dist/chunk-GOX2CYIM.js.map +1 -0
  22. package/dist/{chunk-22POWGFA.js → chunk-IVJP3GKJ.js} +46 -20
  23. package/dist/chunk-IVJP3GKJ.js.map +1 -0
  24. package/dist/{chunk-QPEVCJ5Q.js → chunk-MVZS5FDM.js} +13 -6
  25. package/dist/chunk-MVZS5FDM.js.map +1 -0
  26. package/dist/{chunk-VTQI2NHP.js → chunk-MYNLY3HP.js} +9 -8
  27. package/dist/chunk-MYNLY3HP.js.map +1 -0
  28. package/dist/{chunk-Q5CQTBFA.js → chunk-NSIAORRY.js} +20 -30
  29. package/dist/chunk-NSIAORRY.js.map +1 -0
  30. package/dist/{chunk-MYXFNWAJ.js → chunk-QNGYQMZB.js} +4 -4
  31. package/dist/chunk-QNGYQMZB.js.map +1 -0
  32. package/dist/chunk-WT5PBPLB.js +98 -0
  33. package/dist/chunk-WT5PBPLB.js.map +1 -0
  34. package/dist/{core-DnzqWy6u.d.ts → core-BbgtiXdg.d.ts} +231 -139
  35. package/dist/do/index.d.ts +37 -33
  36. package/dist/do/index.js +21 -14
  37. package/dist/do/index.js.map +1 -1
  38. package/dist/{durable-effects-CWzA-o2B.d.ts → durable-effects-7MTK_jnj.d.ts} +1 -1
  39. package/dist/effect/index.d.ts +151 -0
  40. package/dist/effect/index.js +98 -0
  41. package/dist/effect/index.js.map +1 -0
  42. package/dist/extension/index.d.ts +6 -7
  43. package/dist/extension/index.js +3 -2
  44. package/dist/extension/index.js.map +1 -1
  45. package/dist/flow/index.d.ts +68 -59
  46. package/dist/flow/index.js +32 -27
  47. package/dist/flow/index.js.map +1 -1
  48. package/dist/idempotency/index.d.ts +6 -7
  49. package/dist/idempotency/index.js +4 -2
  50. package/dist/{index-CSyMYDAB.d.ts → index-7vV8xmsD.d.ts} +1 -1
  51. package/dist/{index-KXqTRJyb.d.ts → index-Bz5Rh9Ga.d.ts} +19 -96
  52. package/dist/index-CjFDUBeq.d.ts +207 -0
  53. package/dist/{index-CuNO9m8i.d.ts → index-CkFYA9Y6.d.ts} +14 -16
  54. package/dist/{index-CA_7I5g9.d.ts → index-Cou86-GX.d.ts} +137 -246
  55. package/dist/{index-DrIRb_Ch.d.ts → index-DU6JpQ8G.d.ts} +1 -1
  56. package/dist/{index-HSYgG2Yt.d.ts → index-Xufl55W8.d.ts} +247 -211
  57. package/dist/{index-C2DwkjlP.d.ts → index-_dOR3xqA.d.ts} +57 -45
  58. package/dist/index.d.ts +121 -385
  59. package/dist/index.js +2 -1
  60. package/dist/jev/index.d.ts +133 -97
  61. package/dist/jev/index.js +205 -161
  62. package/dist/jev/index.js.map +1 -1
  63. package/dist/{machine-DGlPWCWI.d.ts → machine-BQzTV8On.d.ts} +171 -288
  64. package/dist/machine-viz/index.d.ts +5 -5
  65. package/dist/machine-viz/index.js +8 -4
  66. package/dist/machine-viz/index.js.map +1 -1
  67. package/dist/mem/index.d.ts +3 -4
  68. package/dist/mem/index.js +2 -1
  69. package/dist/mem/index.js.map +1 -1
  70. package/dist/node/index.d.ts +119 -78
  71. package/dist/node/index.js +52 -38
  72. package/dist/node/index.js.map +1 -1
  73. package/dist/paginate/index.d.ts +27 -22
  74. package/dist/paginate/index.js +6 -4
  75. package/dist/parity/index.d.ts +4 -5
  76. package/dist/parity/index.js +3 -2
  77. package/dist/parity/index.js.map +1 -1
  78. package/dist/pbt/index.d.ts +2 -2
  79. package/dist/pbt/index.js +2 -1
  80. package/dist/pbt/index.js.map +1 -1
  81. package/dist/persistence/index.d.ts +6 -7
  82. package/dist/persistence/index.js +5 -3
  83. package/dist/promise/index.d.ts +186 -0
  84. package/dist/promise/index.js +5 -0
  85. package/dist/promise/index.js.map +1 -0
  86. package/dist/{protocol-D8VbH-VH.d.ts → protocol-ClRZBhHQ.d.ts} +7 -8
  87. package/dist/react/index.d.ts +36 -17
  88. package/dist/react/index.js +27 -4
  89. package/dist/react/index.js.map +1 -1
  90. package/dist/resilience/index.d.ts +63 -456
  91. package/dist/resilience/index.js +37 -483
  92. package/dist/resilience/index.js.map +1 -1
  93. package/dist/{runtime-types-CMF13BBh.d.ts → runtime-types-CiVpC0HN.d.ts} +131 -388
  94. package/dist/testing/index.d.ts +22 -16
  95. package/dist/testing/index.js +15 -7
  96. package/dist/testing/index.js.map +1 -1
  97. package/dist/timing/index.d.ts +25 -22
  98. package/dist/timing/index.js +4 -3
  99. package/dist/timing/index.js.map +1 -1
  100. package/dist/{types-BseNPIhr.d.ts → types-LxZxGYda.d.ts} +2 -2
  101. package/dist/work-queue/index.d.ts +5 -6
  102. package/package.json +18 -5
  103. package/dist/chunk-22POWGFA.js.map +0 -1
  104. package/dist/chunk-3B5X7SBB.js.map +0 -1
  105. package/dist/chunk-66PY5WYF.js +0 -1816
  106. package/dist/chunk-66PY5WYF.js.map +0 -1
  107. package/dist/chunk-AYS5N6WY.js.map +0 -1
  108. package/dist/chunk-L3TND7LA.js +0 -97
  109. package/dist/chunk-L3TND7LA.js.map +0 -1
  110. package/dist/chunk-MYXFNWAJ.js.map +0 -1
  111. package/dist/chunk-Q5CQTBFA.js.map +0 -1
  112. package/dist/chunk-QPEVCJ5Q.js.map +0 -1
  113. package/dist/chunk-TCLBCERH.js.map +0 -1
  114. package/dist/chunk-VTQI2NHP.js.map +0 -1
  115. package/dist/index-ZCMhnVKN.d.ts +0 -272
@@ -0,0 +1,710 @@
1
+ // src/pure/core.ts
2
+ var __DEV__ = (() => {
3
+ try {
4
+ const meta = import.meta;
5
+ if (meta.env?.DEV !== void 0) {
6
+ return meta.env.DEV;
7
+ }
8
+ } catch {
9
+ }
10
+ try {
11
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
12
+ } catch {
13
+ return false;
14
+ }
15
+ })();
16
+ function deepFreeze(obj, seen) {
17
+ if (obj === null || typeof obj !== "object") return obj;
18
+ const proto = Object.getPrototypeOf(obj);
19
+ if (proto !== Object.prototype && proto !== Array.prototype && proto !== null)
20
+ return obj;
21
+ const visited = seen ?? /* @__PURE__ */ new WeakSet();
22
+ if (visited.has(obj)) return obj;
23
+ visited.add(obj);
24
+ Object.freeze(obj);
25
+ for (const v of Object.values(obj))
26
+ deepFreeze(v, visited);
27
+ return obj;
28
+ }
29
+ function hasFunctionValues(obj) {
30
+ if (obj === null || typeof obj !== "object") return null;
31
+ for (const [key, value] of Object.entries(obj)) {
32
+ if (typeof value === "function") return key;
33
+ }
34
+ return null;
35
+ }
36
+ function assertPureResult(result, msgType) {
37
+ if (result && typeof result.then === "function") {
38
+ throw new Error(
39
+ `@demlik/tea: update cell "${msgType}" returned a Promise. update must be synchronous. Move async work to interpret.`
40
+ );
41
+ }
42
+ if (!Array.isArray(result) || result.length < 2 || !Array.isArray(result[1])) {
43
+ throw new Error(
44
+ `@demlik/tea: update cell "${msgType}" returned a non-tuple. Expected [State, Cmd[]], got ${typeof result}.`
45
+ );
46
+ }
47
+ const cmds = result[1];
48
+ for (const cmd of cmds) {
49
+ const fnField = hasFunctionValues(cmd);
50
+ if (fnField !== null) {
51
+ throw new Error(
52
+ `@demlik/tea: Cmd "${cmd.type}" has function field "${fnField}". Cmds must be plain data, not closures.`
53
+ );
54
+ }
55
+ }
56
+ }
57
+ function without(rec, key) {
58
+ const { [key]: _dropped, ...rest } = rec;
59
+ return rest;
60
+ }
61
+ var Outcome = {
62
+ ok: (value) => ({ _tag: "Ok", value }),
63
+ err: (error) => ({ _tag: "Err", error })
64
+ };
65
+ function isOutcome(value) {
66
+ if (typeof value !== "object" || value === null) return false;
67
+ const tag = value._tag;
68
+ return tag === "Ok" && "value" in value || tag === "Err" && "error" in value;
69
+ }
70
+ function malformedResult(issues) {
71
+ return {
72
+ _tag: "malformed_result",
73
+ issues: issues.map((issue) => ({
74
+ path: (issue.path ?? []).map(
75
+ (segment) => String(typeof segment === "object" ? segment.key : segment)
76
+ ).join("."),
77
+ message: issue.message
78
+ }))
79
+ };
80
+ }
81
+ var AsyncSchemaError = class extends Error {
82
+ constructor(where) {
83
+ super(
84
+ `@demlik/tea: ${where} validated asynchronously. tea checks schemas synchronously, so a schema whose \`~standard.validate\` returns a Promise cannot be used here.`
85
+ );
86
+ this.where = where;
87
+ }
88
+ where;
89
+ name = "AsyncSchemaError";
90
+ _tag = "AsyncSchemaError";
91
+ };
92
+ function validateSync(schema, value, where) {
93
+ const result = schema["~standard"].validate(value);
94
+ if (result instanceof Promise) {
95
+ result.catch(() => {
96
+ });
97
+ throw new AsyncSchemaError(where);
98
+ }
99
+ return result;
100
+ }
101
+ var UndeclaredFailureError = class extends Error {
102
+ constructor(cmdType, failure, declared) {
103
+ super(
104
+ `@demlik/tea: the "${cmdType}" handler failed with ${describeTag(failure)}, which its Cmd.define does not declare (declared: ${declared.length === 0 ? "none" : declared.map((t) => `"${t}"`).join(", ")}). An undeclared failure goes to the error sink, never to "${cmdType}_err".`
105
+ );
106
+ this.cmdType = cmdType;
107
+ this.failure = failure;
108
+ this.declared = declared;
109
+ }
110
+ cmdType;
111
+ failure;
112
+ declared;
113
+ name = "UndeclaredFailureError";
114
+ _tag = "UndeclaredFailureError";
115
+ };
116
+ function describeTag(failure) {
117
+ const tag = typeof failure === "object" && failure !== null ? failure._tag : void 0;
118
+ return typeof tag === "string" ? `_tag "${tag}"` : "a value with no _tag";
119
+ }
120
+ var OutcomeContractError = class extends Error {
121
+ constructor(cmdType, detail) {
122
+ super(
123
+ `@demlik/tea: the "${cmdType}" handler ${detail}. A Cmd.define'd handler returns an outcome \u2014 \`ok(value)\` or \`err({ _tag })\` \u2014 and the engine mints "${cmdType}_ok" / "${cmdType}_err" from it.`
124
+ );
125
+ this.cmdType = cmdType;
126
+ }
127
+ cmdType;
128
+ name = "OutcomeContractError";
129
+ _tag = "OutcomeContractError";
130
+ };
131
+ var cmdEdge = /* @__PURE__ */ Symbol("tea.cmdEdge");
132
+ function cmdEdgeOver(defs, clock) {
133
+ const byType = /* @__PURE__ */ new Map();
134
+ for (const def of defs) byType.set(def.cmdType, def);
135
+ return (cmd, returned) => {
136
+ const def = byType.get(cmd.type);
137
+ if (def === void 0) return returned;
138
+ if (returned === void 0 || returned === null) return void 0;
139
+ if (isOutcome(returned)) return mint(def, cmd, returned, clock());
140
+ throw new OutcomeContractError(def.cmdType, describeReturn(def, returned));
141
+ };
142
+ }
143
+ function describeReturn(def, returned) {
144
+ const type = typeof returned === "object" ? returned.type : void 0;
145
+ if (typeof type !== "string") {
146
+ return "returned a value that is not an outcome";
147
+ }
148
+ if (type === def.okType || type === def.errType) {
149
+ return `returned its own "${type}" Msg`;
150
+ }
151
+ return `returned the "${type}" Msg`;
152
+ }
153
+ function mint(def, cmd, outcome, at) {
154
+ if (outcome._tag === "Err") {
155
+ const tag = typeof outcome.error === "object" && outcome.error !== null ? outcome.error._tag : void 0;
156
+ if (typeof tag !== "string" || !def.errTags.includes(tag)) {
157
+ throw new UndeclaredFailureError(def.cmdType, outcome.error, def.errTags);
158
+ }
159
+ return { type: def.errType, cmd, error: outcome.error, at };
160
+ }
161
+ const parsed = validateSync(
162
+ def.schema.ok,
163
+ outcome.value,
164
+ `the "${def.cmdType}" Cmd's ok schema`
165
+ );
166
+ if (parsed.issues !== void 0) {
167
+ return {
168
+ type: def.errType,
169
+ cmd,
170
+ error: malformedResult(parsed.issues),
171
+ at
172
+ };
173
+ }
174
+ return { type: def.okType, cmd, value: parsed.value, at };
175
+ }
176
+ function cmdEdgeOf(ctx) {
177
+ const edge = typeof ctx === "object" && ctx !== null ? ctx[cmdEdge] : void 0;
178
+ return edge ?? ((_, returned) => returned);
179
+ }
180
+ var detachWork = /* @__PURE__ */ Symbol("tea.detachWork");
181
+ function detachWorkOf(ctx) {
182
+ const detach = typeof ctx === "object" && ctx !== null ? ctx[detachWork] : void 0;
183
+ return detach ?? (() => void 0);
184
+ }
185
+ function defineCmd(name, spec) {
186
+ const okType = `${name}_ok`;
187
+ const errType = `${name}_err`;
188
+ const build = (input) => ({ ...input, type: name });
189
+ return Object.assign(build, {
190
+ cmdType: name,
191
+ okType,
192
+ errType,
193
+ ok: (cmd, value, at) => stamp({ type: okType, cmd, value }, at),
194
+ err: (cmd, error, at) => stamp({ type: errType, cmd, error }, at),
195
+ schema: { input: spec.input, ok: spec.ok },
196
+ errTags: spec.err
197
+ });
198
+ }
199
+ function stamp(msg, at) {
200
+ return at === void 0 ? msg : { ...msg, at };
201
+ }
202
+ function detectUpdateForm(update) {
203
+ const firstKey = Object.keys(update)[0];
204
+ if (firstKey === void 0) return "reducer";
205
+ const firstValue = update[firstKey];
206
+ return typeof firstValue === "function" ? "reducer" : "transitions";
207
+ }
208
+ function formOf(machine) {
209
+ return machine.__form ?? detectUpdateForm(machine.update);
210
+ }
211
+ var NoCellError = class extends Error {
212
+ constructor(msgType, stateName, acceptedTypes2) {
213
+ super(
214
+ // The pre-#14 text is kept VERBATIM as a prefix: a caller matching on it
215
+ // keeps matching, and the new clause only ever appends.
216
+ `@demlik/tea: no update cell for msg.type "${msgType}" in state "${stateName}" \u2014 the machine's update does not handle this Msg (an unknown wire msg.type, or a missing cell reached by bypassing the mapped types).` + (acceptedTypes2.length === 0 ? ` This state accepts no Msg at all.` : ` This state accepts: ${acceptedTypes2.map((t) => `"${t}"`).join(", ")}.`)
217
+ );
218
+ this.msgType = msgType;
219
+ this.stateName = stateName;
220
+ this.acceptedTypes = acceptedTypes2;
221
+ }
222
+ msgType;
223
+ stateName;
224
+ acceptedTypes;
225
+ name = "NoCellError";
226
+ _tag = "NoCellError";
227
+ };
228
+ function stateNameOf(state) {
229
+ if (state === void 0 || state === null) return "(no state)";
230
+ if (typeof state === "object" && "type" in state) {
231
+ const t = state.type;
232
+ if (typeof t === "string") return t;
233
+ }
234
+ return "(untagged state)";
235
+ }
236
+ function acceptedTypes(machine, state) {
237
+ if (state === void 0 || state === null) return [];
238
+ if (formOf(machine) === "reducer") {
239
+ return cellKeysOf(machine.update);
240
+ }
241
+ const table = machine.update;
242
+ const row = table[state.type];
243
+ return row === void 0 || row === null ? [] : cellKeysOf(row);
244
+ }
245
+ function cellKeysOf(row) {
246
+ return Object.keys(row).filter(
247
+ (key) => typeof row[key] === "function"
248
+ );
249
+ }
250
+ function lookupCell(machine, state, msg) {
251
+ if (state === void 0 || state === null) {
252
+ return {
253
+ cell: void 0,
254
+ stateName: stateNameOf(state),
255
+ acceptedTypes: acceptedTypes(machine, state)
256
+ };
257
+ }
258
+ if (formOf(machine) === "reducer") {
259
+ const record = machine.update;
260
+ const cell2 = record[msg.type];
261
+ const stateName2 = stateNameOf(state);
262
+ return typeof cell2 === "function" ? { cell: cell2, stateName: stateName2 } : {
263
+ cell: void 0,
264
+ stateName: stateName2,
265
+ acceptedTypes: acceptedTypes(machine, state)
266
+ };
267
+ }
268
+ const table = machine.update;
269
+ const stateKey = state.type;
270
+ const cell = table[stateKey]?.[msg.type];
271
+ const stateName = String(stateKey);
272
+ return typeof cell === "function" ? { cell, stateName } : {
273
+ cell: void 0,
274
+ stateName,
275
+ acceptedTypes: acceptedTypes(machine, state)
276
+ };
277
+ }
278
+ function applyCell(machine, state, msg) {
279
+ const found = lookupCell(machine, state, msg);
280
+ if (found.cell === void 0) {
281
+ throw new NoCellError(msg.type, found.stateName, found.acceptedTypes);
282
+ }
283
+ return found.cell(state, msg);
284
+ }
285
+ function checkedStep(state, msg, step) {
286
+ if (__DEV__) deepFreeze(state);
287
+ const result = step(state, msg);
288
+ if (__DEV__ && result !== null) assertPureResult(result, msg.type);
289
+ return result;
290
+ }
291
+ function applyCellChecked(machine, state, msg) {
292
+ return checkedStep(
293
+ state,
294
+ msg,
295
+ (s, m) => applyCell(machine, s, m)
296
+ );
297
+ }
298
+ function msgKeysOf(machine) {
299
+ const keys = Object.keys(machine.update);
300
+ if (keys.length === 0) return [];
301
+ if (formOf(machine) === "reducer") return keys;
302
+ const table = machine.update;
303
+ const seen = /* @__PURE__ */ new Set();
304
+ const union = [];
305
+ for (const stateKey of keys) {
306
+ const row = table[stateKey];
307
+ if (row === null || row === void 0) continue;
308
+ for (const msgKey of Object.keys(row)) {
309
+ if (seen.has(msgKey)) continue;
310
+ seen.add(msgKey);
311
+ union.push(msgKey);
312
+ }
313
+ }
314
+ return union;
315
+ }
316
+ function describeMachine(machine) {
317
+ const msgs = msgKeysOf(machine);
318
+ if (formOf(machine) === "reducer") return { form: "reducer", msgs };
319
+ const table = machine.update;
320
+ const states = Object.keys(table);
321
+ const accepts = {};
322
+ for (const stateKey of states) {
323
+ const row = table[stateKey];
324
+ accepts[stateKey] = row === null || row === void 0 ? [] : Object.keys(row);
325
+ }
326
+ return { form: "transitions", msgs, states, accepts };
327
+ }
328
+ function acceptsOf(machine, stateType) {
329
+ const shape = describeMachine(machine);
330
+ if (shape.form === "reducer") return shape.msgs;
331
+ return shape.accepts[stateType] ?? [];
332
+ }
333
+ var Cmd = {
334
+ /**
335
+ * Declare a typed Cmd constructor (ADR 0014, 0021). Returns the builder —
336
+ * `fetch({ url })` yields `{ type: "fetch", url }` — carrying the minted Msg
337
+ * builders `fetch.ok(cmd, value)` / `fetch.err(cmd, error)` (for replay
338
+ * logs and tests) and the declaration the runtime edge parses against.
339
+ * `Settled<typeof fetch>` is the two-arm Msg union it settles with;
340
+ * `defineMachine({ cmds: [fetch] })` folds that union into the machine's `M`.
341
+ *
342
+ * const fetch = Cmd.define("fetch", {
343
+ * input: z.object({ url: z.string() }),
344
+ * ok: z.object({ status: z.number(), body: z.string() }),
345
+ * err: ["not_found", "timeout"],
346
+ * });
347
+ *
348
+ * `input` and `ok` take any Standard Schema whose `validate` is synchronous:
349
+ * zod directly, Effect Schema through `Schema.toStandardSchemaV1(...)`. `err`
350
+ * is the `_tag` list the handler may fail with; the runtime adds
351
+ * `malformed_result` for an `Ok` value the `ok` schema rejects.
352
+ *
353
+ * The handler returns an outcome and the engine mints the Msg:
354
+ *
355
+ * fetch: async (cmd, { ok, err }) =>
356
+ * res.status === 404 ? err({ _tag: "not_found" }) : ok(await res.json()),
357
+ *
358
+ * A throw or an undeclared tag goes to the error sink, never to `fetch_err`.
359
+ * The handler reads its services off the plain `ctx` handed to `run` (ADR
360
+ * 0020).
361
+ *
362
+ * A Cmd must not wait; see `DepKeyedSub` for anything that watches.
363
+ */
364
+ define: defineCmd,
365
+ /**
366
+ * The empty Cmd array. Typed `readonly never[]` so it's assignable to any
367
+ * `readonly C[]` for any `C extends Cmd`. Use in `init` returns and
368
+ * Transitions cells that emit zero effects:
369
+ *
370
+ * init: (loaded) => [loaded ?? initial, Cmd.none],
371
+ * tick: (state) => [state, Cmd.none],
372
+ *
373
+ * Elm's `Cmd.none` analogue. Cultural signal alongside the runtime help:
374
+ * `[state, Cmd.none]` reads as intent ("this transition emits nothing"),
375
+ * `[state, []]` reads as "empty array of what".
376
+ *
377
+ * Frozen at runtime so a downstream consumer can't `.push()` into the
378
+ * shared reference.
379
+ */
380
+ none: Object.freeze([]),
381
+ /**
382
+ * Flat concat of cmd arrays. Use when a cell composes effects from
383
+ * multiple conditional sources:
384
+ *
385
+ * return [next, Cmd.batch(
386
+ * Cmd.whenDefined(state.queueItemId, (id) => ({ type: "complete", id })),
387
+ * Cmd.when(state.windowId !== undefined, { type: "close_window", windowId }),
388
+ * [{ type: "detach_debugger", tabId: state.tabId }],
389
+ * )];
390
+ *
391
+ * `<const C>` keeps inline cmd literals' discriminants narrow (same
392
+ * rationale as `when` / `whenDefined` above).
393
+ */
394
+ batch: (...arrs) => {
395
+ const out = [];
396
+ for (const arr of arrs) out.push(...arr);
397
+ return out;
398
+ },
399
+ /**
400
+ * Emit `cmd` wrapped in a single-element array iff `cond` is true.
401
+ * Otherwise return the empty array. Spreads cleanly into the cmds array
402
+ * returned by a Transitions cell.
403
+ *
404
+ * `const C` (TypeScript 5.0+) keeps the inferred `type:` discriminator
405
+ * literal narrow when called with an object literal — so `Cmd.when(b,
406
+ * { type: "x", ... })` infers as `{ type: "x", ... }`, not `{ type:
407
+ * string, ... }`, and stays assignable to a discriminated-union arm
408
+ * without `as const` at the call site.
409
+ */
410
+ when: (cond, cmd) => cond ? [cmd] : [],
411
+ /**
412
+ * If `value` is defined, call `build(value)` and emit the resulting cmd
413
+ * wrapped in a single-element array. If `value` is `undefined`, return
414
+ * the empty array. `value` is narrowed to `T` inside `build`.
415
+ *
416
+ * `const C` on the return type keeps the callback's object-literal
417
+ * `type:` field narrow (see `when` above for the full rationale).
418
+ */
419
+ whenDefined: (value, build) => value !== void 0 ? [build(value)] : []
420
+ };
421
+ function subId(s) {
422
+ return s;
423
+ }
424
+ function subIdOf(type, deps) {
425
+ return subId(structuralHash({ type, deps }));
426
+ }
427
+ function depsInactive(deps) {
428
+ return deps === null || deps === void 0;
429
+ }
430
+ function structuralHash(deps) {
431
+ return stableStringify(deps);
432
+ }
433
+ function stableStringify(value) {
434
+ if (value === null) return "null";
435
+ const t = typeof value;
436
+ if (t === "string") return JSON.stringify(value);
437
+ if (t === "number" || t === "boolean") return String(value);
438
+ if (t === "undefined") return "undefined";
439
+ if (t === "function") {
440
+ throw new Error(
441
+ "@demlik/tea: structuralHash received a function in `deps`. A dep-keyed Sub's `deps` must be a plain state slice (string / number / boolean / null / array / object), not a closure \u2014 see invariant 1."
442
+ );
443
+ }
444
+ if (Array.isArray(value)) {
445
+ return `[${value.map(stableStringify).join(",")}]`;
446
+ }
447
+ if (t === "object") {
448
+ const proto = Object.getPrototypeOf(value);
449
+ if (proto !== Object.prototype && proto !== null) {
450
+ const ctor = value.constructor;
451
+ const label = ctor?.name ?? "object";
452
+ throw new Error(
453
+ `@demlik/tea: structuralHash received a non-plain object (${label}) in \`deps\`. A \`Date\`, \`Map\`, \`Set\`, \`Error\` or class instance carries no own enumerable properties, so every one of them would hash to the same id as \`{}\` \u2014 a silent collision, not a wrong-looking key. Project it to plain data first (e.g. \`startedAt.toISOString()\`, \`[...set].sort()\`) \u2014 see invariant 1.`
454
+ );
455
+ }
456
+ const obj = value;
457
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort();
458
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
459
+ }
460
+ throw new Error(
461
+ `@demlik/tea: structuralHash received an unsupported \`deps\` value of type "${t}". Use plain JSON-compatible data for a dep-keyed Sub's \`deps\`.`
462
+ );
463
+ }
464
+ function subEntriesOf(machine) {
465
+ return machine.subs ?? [];
466
+ }
467
+ function desiredSub(entry, state) {
468
+ const deps = entry.deps(state);
469
+ if (depsInactive(deps)) return null;
470
+ return { id: subIdOf(entry.type, deps), type: entry.type, deps };
471
+ }
472
+ function foldUpdates(machine, initialState, msgs) {
473
+ let state = initialState;
474
+ const cmds = [];
475
+ for (const msg of msgs) {
476
+ const [next, emitted] = applyCellChecked(machine, state, msg);
477
+ state = next;
478
+ cmds.push(...emitted);
479
+ }
480
+ return { state, cmds };
481
+ }
482
+ function foldMsgs(machine, base, msgs) {
483
+ return foldUpdates(machine, base, msgs).state;
484
+ }
485
+
486
+ // src/runtime-types.ts
487
+ function absurd(x) {
488
+ throw new Error(`unreachable: ${JSON.stringify(x)}`);
489
+ }
490
+ var noop = (state, _msg) => [state, []];
491
+ var definedPortNames = /* @__PURE__ */ new Set();
492
+ var PortNameCollisionError = class extends Error {
493
+ name = "PortNameCollisionError";
494
+ _tag = "PortNameCollisionError";
495
+ constructor(portName) {
496
+ super(
497
+ `definePort: a port named "${portName}" was already defined. Each definePort call must use a unique name. If two modules need the same port, export it from one module and import it.`
498
+ );
499
+ }
500
+ };
501
+ function __resetPortRegistry() {
502
+ definedPortNames.clear();
503
+ }
504
+ var QuiescenceTimeoutError = class extends Error {
505
+ constructor(iterations) {
506
+ super(
507
+ `@demlik/tea: idle() did not reach quiescence after ${iterations} iterations \u2014 the dispatch tail kept advancing. The machine is likely livelocking (an interpret handler enqueues a follow-up Msg that enqueues another, without end). Bound the follow-up chain in the reducer, or stop the runtime.`
508
+ );
509
+ this.iterations = iterations;
510
+ }
511
+ iterations;
512
+ name = "QuiescenceTimeoutError";
513
+ _tag = "QuiescenceTimeoutError";
514
+ };
515
+ var DriveFailedError = class extends Error {
516
+ constructor(state) {
517
+ super(
518
+ `@demlik/tea: driveToDone ended on a failed State \u2014 the run reached a terminal State the \`failed\` predicate marks as a failure. Read \`error.state\` for the final Model.`
519
+ );
520
+ this.state = state;
521
+ }
522
+ state;
523
+ name = "DriveFailedError";
524
+ _tag = "DriveFailedError";
525
+ };
526
+ var DriveStalledError = class extends Error {
527
+ constructor(state) {
528
+ super(
529
+ `@demlik/tea: driveToDone stalled \u2014 the start chain quiesced on a State that is neither terminal nor failed, with no live Sub and no Cmd in flight to move it further. Read \`error.state\` for the State it parked on; if a later transition was expected, the machine needs a Sub (or a Cmd) that delivers it.`
530
+ );
531
+ this.state = state;
532
+ }
533
+ state;
534
+ name = "DriveStalledError";
535
+ _tag = "DriveStalledError";
536
+ };
537
+ var RuntimeDiscardNotice = class extends Error {
538
+ };
539
+ var RuntimeDiscardedError = class extends RuntimeDiscardNotice {
540
+ constructor(pendingCmds) {
541
+ super(
542
+ `@demlik/tea: runtime stopped with ${pendingCmds} Cmd(s) in flight \u2014 their results will reach no listener. In React this is usually a ctx-identity change rebuilding useMachine's runtime mid-flight: stabilize the ctx (useMemo / useState) so the runtime outlives the Cmds it started. An intentional teardown (unmount, navigation) can silence this with a run({ onError }) sink that ignores phase: "discard".`
543
+ );
544
+ this.pendingCmds = pendingCmds;
545
+ }
546
+ pendingCmds;
547
+ name = "RuntimeDiscardedError";
548
+ _tag = "RuntimeDiscardedError";
549
+ };
550
+ var IdentityDropNotice = class extends RuntimeDiscardNotice {
551
+ constructor(msgType) {
552
+ super(
553
+ `@demlik/tea: Msg "${msgType}" was addressed to a different instance than this state owns and was dropped before \`update\` ran \u2014 the machine's \`identity\` filter working as declared. If this Msg was meant for THIS instance, its \`ofMsg\` projection disagrees with \`ofState\`; if the instance is being reused across runs, route the message to the run that owns it.`
554
+ );
555
+ this.msgType = msgType;
556
+ }
557
+ msgType;
558
+ name = "IdentityDropNotice";
559
+ _tag = "IdentityDropNotice";
560
+ };
561
+ var DisposeTimeoutNotice = class extends RuntimeDiscardNotice {
562
+ constructor(pendingDisposals, timeoutMs) {
563
+ super(
564
+ `@demlik/tea: stop() gave up waiting for ${pendingDisposals} async teardown(s) after ${timeoutMs}ms \u2014 the resources they release may still be open. Make \`release\` / the Sub cleanup settle (bound its own IO), or raise run({ disposeTimeoutMs }).`
565
+ );
566
+ this.pendingDisposals = pendingDisposals;
567
+ this.timeoutMs = timeoutMs;
568
+ }
569
+ pendingDisposals;
570
+ timeoutMs;
571
+ name = "DisposeTimeoutNotice";
572
+ _tag = "DisposeTimeoutNotice";
573
+ };
574
+ var DispatchDiscardedError = class extends RuntimeDiscardNotice {
575
+ constructor(msgType) {
576
+ super(
577
+ `@demlik/tea: Msg "${msgType}" arrived while the runtime was stopping and was discarded \u2014 stop() refuses new work so the drain terminates. It is the result of a Cmd that outlived its runtime; keep the runtime alive until the Cmd settles (in React: stabilize useMachine's ctx) if the transition matters.`
578
+ );
579
+ this.msgType = msgType;
580
+ }
581
+ msgType;
582
+ name = "DispatchDiscardedError";
583
+ _tag = "DispatchDiscardedError";
584
+ };
585
+ function definePort(name) {
586
+ let isHmr = false;
587
+ try {
588
+ const meta = import.meta;
589
+ isHmr = meta?.hot !== void 0 && meta?.hot !== null;
590
+ } catch {
591
+ isHmr = false;
592
+ }
593
+ if (!isHmr && definedPortNames.has(name)) {
594
+ throw new PortNameCollisionError(name);
595
+ }
596
+ definedPortNames.add(name);
597
+ return { __brand: "port", name };
598
+ }
599
+ function isFencedStore(store) {
600
+ return "fenced" in store && store.fenced === true;
601
+ }
602
+ var StoreConflictError = class extends Error {
603
+ constructor(expectedVersion, actualVersion) {
604
+ super(
605
+ `@demlik/tea: store conflict \u2014 expected version ${expectedVersion}, found ${actualVersion}. Another process has written this state; this run is no longer the single writer and has stopped.`
606
+ );
607
+ this.expectedVersion = expectedVersion;
608
+ this.actualVersion = actualVersion;
609
+ }
610
+ expectedVersion;
611
+ actualVersion;
612
+ name = "StoreConflictError";
613
+ _tag = "store_conflict";
614
+ };
615
+ function schemaMigrate(schema, upcast = (raw) => raw) {
616
+ return (raw) => {
617
+ let migrated;
618
+ try {
619
+ migrated = upcast(raw);
620
+ } catch {
621
+ return null;
622
+ }
623
+ const parsed = schema.safeParse(migrated);
624
+ return parsed.success ? parsed.data : null;
625
+ };
626
+ }
627
+ function defineMachine(m) {
628
+ if (m.__form === void 0) {
629
+ Object.defineProperty(m, "__form", {
630
+ value: detectUpdateForm(m.update),
631
+ enumerable: false,
632
+ writable: false,
633
+ configurable: true
634
+ });
635
+ }
636
+ return m;
637
+ }
638
+ function asReducer(reducer) {
639
+ return reducer;
640
+ }
641
+ function replay(machine, opts) {
642
+ const loaded = opts.loaded ?? null;
643
+ const [initialState, initCmds] = machine.init(loaded, opts.ctx);
644
+ if (loaded !== null && initCmds.length > 0) {
645
+ throw new Error(
646
+ "TEA contract violation: machine.init must return [state, []] (no Cmds) when `loaded` is non-null. Init's rehydrate branch is a pure state passthrough \u2014 the migration/parse boundary, not the boot-effect hook.\n\nRoutes for boot effects:\n - Stateless infrastructure (e.g. chrome.alarms ensure) \u2192 host module top level, outside TEA.\n - State-conditional resume (e.g. re-attach a session) \u2192 a `boot` Msg dispatched once from the host after `run(...)` returns, handled in update.\n\nSee .patterns/tea/tea-invariants.md (Invariant 2)."
647
+ );
648
+ }
649
+ const { state, cmds: foldedCmds } = foldUpdates(
650
+ machine,
651
+ initialState,
652
+ opts.msgs
653
+ );
654
+ const cmds = [...initCmds, ...foldedCmds];
655
+ const byId = /* @__PURE__ */ new Map();
656
+ for (const entry of subEntriesOf(machine)) {
657
+ const sub = desiredSub(entry, state);
658
+ if (sub !== null && !byId.has(sub.id)) {
659
+ byId.set(sub.id, sub);
660
+ }
661
+ }
662
+ return { state, cmds, subs: [...byId.values()] };
663
+ }
664
+ function wrapDetached(handler) {
665
+ return (cmd, ctx, dispatch) => {
666
+ if (dispatch === void 0) {
667
+ throw new Error(
668
+ "@demlik/tea: a wrapDetached handler was invoked without the injected dispatch. The kernel always supplies it; call the handler through the runtime, not directly."
669
+ );
670
+ }
671
+ return handler(cmd, ctx, dispatch);
672
+ };
673
+ }
674
+ function tryApplyCell(machine, state, msg) {
675
+ const found = lookupCell(machine, state, msg);
676
+ if (found.cell === void 0) {
677
+ return Outcome.err(
678
+ new NoCellError(msg.type, found.stateName, found.acceptedTypes)
679
+ );
680
+ }
681
+ return Outcome.ok(found.cell(state, msg));
682
+ }
683
+ function tryFoldMsgs(machine, base, msgs) {
684
+ let state = base;
685
+ for (const [index, msg] of msgs.entries()) {
686
+ if (__DEV__) deepFreeze(state);
687
+ const stepped = tryApplyCell(machine, state, msg);
688
+ if (stepped._tag === "Err") {
689
+ return Outcome.err({ index, msg, error: stepped.error });
690
+ }
691
+ if (__DEV__) assertPureResult(stepped.value, msg.type);
692
+ state = stepped.value[0];
693
+ }
694
+ return Outcome.ok(state);
695
+ }
696
+ function tryInterpret(work, onOk, onErr) {
697
+ return async (cmd, ctx) => {
698
+ let value;
699
+ try {
700
+ value = await work(cmd, ctx);
701
+ } catch (error) {
702
+ return onErr(error, cmd);
703
+ }
704
+ return onOk(value, cmd);
705
+ };
706
+ }
707
+
708
+ export { AsyncSchemaError, Cmd, DispatchDiscardedError, DisposeTimeoutNotice, DriveFailedError, DriveStalledError, IdentityDropNotice, NoCellError, Outcome, OutcomeContractError, PortNameCollisionError, QuiescenceTimeoutError, RuntimeDiscardNotice, RuntimeDiscardedError, StoreConflictError, UndeclaredFailureError, __resetPortRegistry, absurd, acceptedTypes, acceptsOf, applyCell, applyCellChecked, asReducer, checkedStep, cmdEdge, cmdEdgeOf, cmdEdgeOver, defineMachine, definePort, describeMachine, desiredSub, detachWork, detachWorkOf, detectUpdateForm, foldMsgs, foldUpdates, formOf, isFencedStore, malformedResult, msgKeysOf, noop, replay, schemaMigrate, structuralHash, subEntriesOf, subId, subIdOf, tryApplyCell, tryFoldMsgs, tryInterpret, validateSync, without, wrapDetached };
709
+ //# sourceMappingURL=chunk-CH5KTOEZ.js.map
710
+ //# sourceMappingURL=chunk-CH5KTOEZ.js.map