@demlik/tea 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/agent/index.d.ts +182 -35
- package/dist/agent/index.js +11 -11
- package/dist/{chunk-KWNCM6XM.js → chunk-AHW2CLPT.js} +65 -18
- package/dist/chunk-AHW2CLPT.js.map +1 -0
- package/dist/{chunk-K4ETBNKS.js → chunk-AIIXV4XB.js} +38 -9
- package/dist/chunk-AIIXV4XB.js.map +1 -0
- package/dist/{chunk-LYVOGOBC.js → chunk-BRX3243I.js} +3 -3
- package/dist/{chunk-LYVOGOBC.js.map → chunk-BRX3243I.js.map} +1 -1
- package/dist/{chunk-GWWCCSDH.js → chunk-C3G2QFEZ.js} +4 -4
- package/dist/{chunk-GWWCCSDH.js.map → chunk-C3G2QFEZ.js.map} +1 -1
- package/dist/{chunk-C4U5D5HP.js → chunk-FNGZMVIT.js} +3 -3
- package/dist/chunk-FNGZMVIT.js.map +1 -0
- package/dist/{chunk-QWIPH4YH.js → chunk-H2ELKHI6.js} +16 -6
- package/dist/chunk-H2ELKHI6.js.map +1 -0
- package/dist/chunk-H3MWNH7A.js +119 -0
- package/dist/chunk-H3MWNH7A.js.map +1 -0
- package/dist/chunk-I7E7AD2S.js +213 -0
- package/dist/chunk-I7E7AD2S.js.map +1 -0
- package/dist/{chunk-OYR7ENKU.js → chunk-IHUNKHDX.js} +3 -3
- package/dist/{chunk-OYR7ENKU.js.map → chunk-IHUNKHDX.js.map} +1 -1
- package/dist/chunk-JVE2LBFJ.js +21 -0
- package/dist/chunk-JVE2LBFJ.js.map +1 -0
- package/dist/chunk-JVKU2UQC.js +34 -0
- package/dist/chunk-JVKU2UQC.js.map +1 -0
- package/dist/{chunk-OPH6Y4UT.js → chunk-JYNYG5SF.js} +4 -4
- package/dist/{chunk-OPH6Y4UT.js.map → chunk-JYNYG5SF.js.map} +1 -1
- package/dist/{chunk-XBZQT2TD.js → chunk-PCJA2HTZ.js} +437 -162
- package/dist/chunk-PCJA2HTZ.js.map +1 -0
- package/dist/{chunk-DFUEAA6L.js → chunk-PQSDUX6B.js} +3 -3
- package/dist/{chunk-DFUEAA6L.js.map → chunk-PQSDUX6B.js.map} +1 -1
- package/dist/{chunk-6XBEYZME.js → chunk-WTSOW2MD.js} +5 -5
- package/dist/{chunk-6XBEYZME.js.map → chunk-WTSOW2MD.js.map} +1 -1
- package/dist/{chunk-4PXNZ5UY.js → chunk-XVIKSRCK.js} +4 -4
- package/dist/{chunk-4PXNZ5UY.js.map → chunk-XVIKSRCK.js.map} +1 -1
- package/dist/{chunk-4NTCPTNQ.js → chunk-YIMOFXST.js} +3 -3
- package/dist/{chunk-4NTCPTNQ.js.map → chunk-YIMOFXST.js.map} +1 -1
- package/dist/{core-rDbOaJTN.d.ts → core-Ce9ncpuv.d.ts} +5 -4
- package/dist/do/index.d.ts +140 -64
- package/dist/do/index.js +17 -18
- package/dist/do/index.js.map +1 -1
- package/dist/drive-loop-CsUsW3Wo.d.ts +127 -0
- package/dist/{durable-effects-CNaw97Be.d.ts → durable-effects-BmUrRA-h.d.ts} +1 -1
- package/dist/effect/index.d.ts +6 -150
- package/dist/effect/index.js +4 -96
- package/dist/effect/index.js.map +1 -1
- package/dist/extension/index.d.ts +7 -5
- package/dist/extension/index.js +2 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/flow/index.d.ts +13 -13
- package/dist/flow/index.js +13 -12
- package/dist/flow/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +3 -3
- package/dist/idempotency/index.js +4 -4
- package/dist/{index-q1PoukOZ.d.ts → index-2kNxy6az.d.ts} +2 -2
- package/dist/{index-B_BxplWi.d.ts → index-B1Wzarkl.d.ts} +1 -1
- package/dist/{index-Cberb5Rr.d.ts → index-Bf9_bNql.d.ts} +2 -2
- package/dist/index-BnsAqn0-.d.ts +286 -0
- package/dist/{index-Cx0F2M34.d.ts → index-Ce5GYAuI.d.ts} +2 -2
- package/dist/{index-CWmsXNdX.d.ts → index-D8sYVzCI.d.ts} +1 -1
- package/dist/{index-CcrKLtse.d.ts → index-DWdg7R1l.d.ts} +1 -1
- package/dist/{index-BOrsbdAf.d.ts → index-Dy3379ve.d.ts} +5 -5
- package/dist/{index-DaX_P35Q.d.ts → index-e36uEyl4.d.ts} +3 -3
- package/dist/index.d.ts +109 -36
- package/dist/index.js +2 -2
- package/dist/jev/index.d.ts +8 -8
- package/dist/jev/index.js +6 -7
- package/dist/jev/index.js.map +1 -1
- package/dist/{machine-BcfCFMYF.d.ts → machine-CuojfZup.d.ts} +353 -57
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +6 -6
- package/dist/mem/index.js +6 -2
- package/dist/mem/index.js.map +1 -1
- package/dist/node/index.d.ts +16 -10
- package/dist/node/index.js +22 -3
- package/dist/node/index.js.map +1 -1
- package/dist/otel/index.d.ts +161 -0
- package/dist/otel/index.js +403 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/paginate/index.d.ts +4 -4
- package/dist/paginate/index.js +5 -5
- package/dist/parity/index.d.ts +3 -3
- package/dist/parity/index.js +3 -3
- package/dist/pbt/index.d.ts +166 -53
- package/dist/pbt/index.js +2 -2
- package/dist/pbt/index.js.map +1 -1
- package/dist/persistence/index.d.ts +5 -5
- package/dist/persistence/index.js +5 -5
- package/dist/promise/index.d.ts +2 -2
- package/dist/promise/index.js +4 -3
- package/dist/react/index.d.ts +2 -2
- package/dist/resilience/index.d.ts +13 -9
- package/dist/resilience/index.js +5 -5
- package/dist/resilience/index.js.map +1 -1
- package/dist/{runtime-types-B62Ucnxr.d.ts → runtime-types-BKg2CGMx.d.ts} +88 -14
- package/dist/testing/effect/index.d.ts +65 -0
- package/dist/testing/effect/index.js +148 -0
- package/dist/testing/effect/index.js.map +1 -0
- package/dist/testing/index.d.ts +12 -158
- package/dist/testing/index.js +4 -130
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/promise/index.d.ts +41 -0
- package/dist/testing/promise/index.js +37 -0
- package/dist/testing/promise/index.js.map +1 -0
- package/dist/timing/index.d.ts +4 -4
- package/dist/timing/index.js +4 -4
- package/dist/{types-DPm5YCDQ.d.ts → types-SzNSHKlV.d.ts} +1 -1
- package/dist/work-queue/index.d.ts +4 -4
- package/dist/work-queue/index.js +4 -3
- package/dist/work-queue/index.js.map +1 -1
- package/package.json +26 -14
- package/dist/chunk-C4U5D5HP.js.map +0 -1
- package/dist/chunk-G5E74IJB.js +0 -8
- package/dist/chunk-G5E74IJB.js.map +0 -1
- package/dist/chunk-K4ETBNKS.js.map +0 -1
- package/dist/chunk-KWNCM6XM.js.map +0 -1
- package/dist/chunk-QWIPH4YH.js.map +0 -1
- package/dist/chunk-XBZQT2TD.js.map +0 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { CellFailure, builtinRunners, cellResult, settleCellExit } from '../../chunk-I7E7AD2S.js';
|
|
2
|
+
import { driveLoop, DEFAULT_MAX_ROUNDS, driveContractOf, DriveRoundsExceededError, DriveNoHandlerError, annotateTrace, driveTraceOf, desiredSubs } from '../../chunk-H3MWNH7A.js';
|
|
3
|
+
export { DEFAULT_MAX_ROUNDS, DriveNoHandlerError, DriveRoundsExceededError, driveTraceOf } from '../../chunk-H3MWNH7A.js';
|
|
4
|
+
import '../../chunk-JVKU2UQC.js';
|
|
5
|
+
import '../../chunk-AHW2CLPT.js';
|
|
6
|
+
import '../../chunk-JVE2LBFJ.js';
|
|
7
|
+
import '../../chunk-FNGZMVIT.js';
|
|
8
|
+
import '../../chunk-AIIXV4XB.js';
|
|
9
|
+
import { Effect, Stream, Exit, Cause, Fiber } from 'effect';
|
|
10
|
+
|
|
11
|
+
var DriveInterrupted = class {
|
|
12
|
+
};
|
|
13
|
+
function drive(machine, initial, msg, interpret, ...[opts]) {
|
|
14
|
+
const options = opts ?? {};
|
|
15
|
+
return Effect.gen(function* () {
|
|
16
|
+
const services = yield* Effect.context();
|
|
17
|
+
const runEffect = Effect.runPromiseExitWith(services);
|
|
18
|
+
const fork = Effect.runForkWith(services);
|
|
19
|
+
return yield* Effect.callback((resume, signal) => {
|
|
20
|
+
const cells = interpret;
|
|
21
|
+
const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));
|
|
22
|
+
const cellFor = (type) => {
|
|
23
|
+
const cell = cells[type];
|
|
24
|
+
if (cell === void 0) return void 0;
|
|
25
|
+
return async (cmd) => {
|
|
26
|
+
const exit = await runEffect(cellResult(cell(cmd)), { signal });
|
|
27
|
+
if (signal.aborted) throw new DriveInterrupted();
|
|
28
|
+
return settleCellExit(exit, defined.has(type));
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
driveLoop(
|
|
32
|
+
machine,
|
|
33
|
+
initial,
|
|
34
|
+
msg,
|
|
35
|
+
options.ctx,
|
|
36
|
+
options.maxRounds ?? DEFAULT_MAX_ROUNDS,
|
|
37
|
+
{
|
|
38
|
+
contract: driveContractOf(machine, options.clock ?? Date.now),
|
|
39
|
+
cellFor,
|
|
40
|
+
subs: effectSubs(
|
|
41
|
+
machine,
|
|
42
|
+
options.subscribe ?? {},
|
|
43
|
+
fork,
|
|
44
|
+
signal
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
).then(
|
|
48
|
+
(result) => resume(Effect.succeed(result)),
|
|
49
|
+
(error) => resume(failureOf(error))
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function failureOf(error) {
|
|
55
|
+
if (error instanceof DriveRoundsExceededError || error instanceof DriveNoHandlerError) {
|
|
56
|
+
return Effect.fail(error);
|
|
57
|
+
}
|
|
58
|
+
if (error instanceof CellFailure) {
|
|
59
|
+
annotateTrace(error.failure, driveTraceOf(error) ?? []);
|
|
60
|
+
return Effect.fail(error.failure);
|
|
61
|
+
}
|
|
62
|
+
if (error instanceof DriveInterrupted) return Effect.interrupt;
|
|
63
|
+
return Effect.die(error);
|
|
64
|
+
}
|
|
65
|
+
function effectSubs(machine, subscribe, fork, signal) {
|
|
66
|
+
const builtins = builtinRunners;
|
|
67
|
+
const started = /* @__PURE__ */ new Map();
|
|
68
|
+
let buffered = [];
|
|
69
|
+
let failed;
|
|
70
|
+
let wake;
|
|
71
|
+
const notify = () => {
|
|
72
|
+
const woken = wake;
|
|
73
|
+
wake = void 0;
|
|
74
|
+
woken?.();
|
|
75
|
+
};
|
|
76
|
+
signal.addEventListener("abort", notify, { once: true });
|
|
77
|
+
const stop = (sub) => {
|
|
78
|
+
sub.stopped = true;
|
|
79
|
+
return sub.ended || sub.fiber === void 0 ? Promise.resolve() : Effect.runPromise(Fiber.interrupt(sub.fiber));
|
|
80
|
+
};
|
|
81
|
+
const anyRunning = () => [...started.values()].some((sub) => !sub.ended);
|
|
82
|
+
return {
|
|
83
|
+
reconcile(state) {
|
|
84
|
+
const desired = desiredSubs(machine, state);
|
|
85
|
+
for (const [id, sub] of started) {
|
|
86
|
+
if (desired.has(id)) continue;
|
|
87
|
+
started.delete(id);
|
|
88
|
+
void stop(sub);
|
|
89
|
+
}
|
|
90
|
+
for (const [id, sub] of desired) {
|
|
91
|
+
if (started.has(id)) continue;
|
|
92
|
+
const make = subscribe[sub.type] ?? builtins[sub.type];
|
|
93
|
+
if (make === void 0) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`@demlik/tea: no subscribe runner for Sub type "${sub.type}". Pass one to drive in \`subscribe\`.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const entry = { stopped: false, ended: false };
|
|
99
|
+
started.set(id, entry);
|
|
100
|
+
entry.fiber = fork(
|
|
101
|
+
Stream.runForEach(
|
|
102
|
+
make(sub),
|
|
103
|
+
(next) => Effect.sync(() => {
|
|
104
|
+
if (entry.stopped) return;
|
|
105
|
+
buffered.push(next);
|
|
106
|
+
notify();
|
|
107
|
+
})
|
|
108
|
+
).pipe(
|
|
109
|
+
Effect.onExit(
|
|
110
|
+
(exit) => Effect.sync(() => {
|
|
111
|
+
entry.ended = true;
|
|
112
|
+
if (!entry.stopped && Exit.isFailure(exit) && !Cause.hasInterruptsOnly(exit.cause)) {
|
|
113
|
+
failed ??= exit.cause;
|
|
114
|
+
}
|
|
115
|
+
notify();
|
|
116
|
+
})
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
async next() {
|
|
123
|
+
for (; ; ) {
|
|
124
|
+
if (signal.aborted) throw new DriveInterrupted();
|
|
125
|
+
if (buffered.length > 0) {
|
|
126
|
+
const out = buffered;
|
|
127
|
+
buffered = [];
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
if (failed !== void 0) throw Cause.squash(failed);
|
|
131
|
+
if (!anyRunning()) return void 0;
|
|
132
|
+
await new Promise((resolve) => {
|
|
133
|
+
wake = resolve;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
async close() {
|
|
138
|
+
signal.removeEventListener("abort", notify);
|
|
139
|
+
const stopping = [...started.values()].map(stop);
|
|
140
|
+
started.clear();
|
|
141
|
+
await Promise.all(stopping);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { drive };
|
|
147
|
+
//# sourceMappingURL=index.js.map
|
|
148
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/testing/effect/drive.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `drive(machine, initial, msg, interpret, opts)` for the Effect engine: the\n// Promise `drive`'s rounds, trace and bound, over the Effect engine's handlers\n// and Subs (#321).\n//\n// There is no second loop. Each Effect cell is adapted into the shared loop's\n// cell with the settle rule the engine itself uses (`settleCellExit` in\n// `src/effect/run.ts`), run with the services the caller provided, so a cell\n// settles here exactly as it does under `run`. The Subs the machine wants are\n// started and stopped after every fold, as the engine reconciles them, and\n// their Msgs are folded once the Cmds in flight have settled.\n// ---------------------------------------------------------------------------\n\nimport { Cause, Effect, Exit, Fiber, Stream } from \"effect\";\nimport { CellFailure } from \"../../effect/failures\";\nimport {\n builtinRunners,\n type CellErrors,\n cellResult,\n type EffectInterpret,\n type EffectSubscribe,\n type InterpretServices,\n type SubscribeOption,\n type SubscribeServices,\n settleCellExit,\n} from \"../../effect/run\";\nimport type { Cmd, Machine, Sub } from \"../../pure/core\";\nimport {\n annotateTrace,\n DEFAULT_MAX_ROUNDS,\n type DriveCell,\n DriveNoHandlerError,\n type DriveOptions,\n type DriveResult,\n DriveRoundsExceededError,\n type DriveSubs,\n desiredSubs,\n driveContractOf,\n driveLoop,\n driveTraceOf,\n} from \"../drive-loop\";\n\n/**\n * The Effect `drive`'s options: the Promise `drive`'s, plus `subscribe` — the\n * runners the engine's `run` takes, required exactly when `run` requires them.\n */\nexport type EffectDriveOptions<U extends Sub, Ctx, B> = DriveOptions<Ctx> &\n SubscribeOption<U, B>;\n\n/** The failures a drive ends with: the loop's own, and a hand-written cell's. */\nexport type EffectDriveError<M, C extends Cmd, I> =\n | DriveRoundsExceededError<M, C>\n | DriveNoHandlerError<M, C>\n | CellErrors<C, I>;\n\n/** The drive was interrupted; nothing is left to run. */\nclass DriveInterrupted {}\n\ntype AnyCell = (cmd: unknown) => Effect.Effect<unknown, unknown>;\ntype AnyRunner = (sub: Sub) => Stream.Stream<unknown, unknown>;\ntype RunEffect = (\n effect: Effect.Effect<unknown, unknown>,\n options: { readonly signal: AbortSignal },\n) => Promise<Exit.Exit<unknown, unknown>>;\ntype ForkEffect = (\n effect: Effect.Effect<unknown, unknown>,\n) => Fiber.Fiber<unknown, unknown>;\n\n/**\n * Drive `machine` from `initial` through `msg` against the REAL Effect\n * `interpret` map — the one a host hands the Effect engine's `run` — and the\n * Subs the machine wants, feeding every Msg back until the machine goes quiet.\n * Yields the settled state together with the whole history, `{ state, trace }`.\n *\n * The rounds, the trace, `maxRounds` and `clock` are the Promise `drive`'s\n * (`@demlik/tea/testing/promise`). What is the Effect engine's:\n *\n * - a cell returns an Effect. A `Cmd.define`d Cmd's success mints `_ok` and\n * its declared failure `_err`; a hand-written Cmd's success is its\n * follow-up — a Msg, a list of them folded in order, or nothing (#324) —\n * and its failure fails the drive, typed. A defect dies. A cell takes no\n * `dispatch`, so a defined Cmd's handler cannot send its own `_ok` /\n * `_err`: the engine's rule holds by construction;\n * - the services the cells and runners read are this Effect's requirements,\n * so a test provides them the way a host does: `Effect.provide(layer)`;\n * - the Subs the machine wants run, through `opts.subscribe` or the engine's\n * built-in runner, started and stopped after every fold. Their Msgs are\n * folded once no Cmd's follow-up is pending. The drive settles once no Msg\n * is pending and no Sub is still running, so hand it a runner whose Stream\n * ends — a Sub that never ends keeps it waiting. A Stream that fails with\n * an error it did not map to a Msg dies, as it stops the engine's run.\n *\n * Interrupting the drive interrupts every cell and Sub still running.\n *\n * ```ts\n * const { state, trace } = await Effect.runPromise(\n * drive(machine, initial, { type: \"go\" }, {\n * fetch: (cmd) =>\n * Effect.gen(function* () {\n * const api = yield* Api;\n * return yield* api.get(cmd.id);\n * }),\n * }).pipe(Effect.provide(ApiTest)),\n * );\n * ```\n *\n * Fails with {@link DriveRoundsExceededError} when the machine is still\n * emitting work after `opts.maxRounds` rounds, {@link DriveNoHandlerError}\n * for a Cmd no cell answers, or a hand-written cell's own failure — each with\n * the partial trace, readable via `driveTraceOf`.\n */\nexport function drive<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n // The defaults read no services, so an omitted map adds none to `R`.\n I extends EffectInterpret<M, C> = EffectInterpret<M, C, never>,\n B extends EffectSubscribe<M, U> = EffectSubscribe<M, U, never>,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n initial: S,\n msg: M,\n interpret: I,\n ...[opts]: Record<never, never> extends EffectDriveOptions<U, Ctx, B>\n ? [opts?: EffectDriveOptions<U, Ctx, B>]\n : [opts: EffectDriveOptions<U, Ctx, B>]\n): Effect.Effect<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>,\n InterpretServices<I> | SubscribeServices<B>\n> {\n const options = (opts ?? {}) as {\n ctx?: Ctx;\n maxRounds?: number;\n clock?: () => number;\n subscribe?: Readonly<Record<string, AnyRunner | undefined>>;\n };\n return Effect.gen(function* () {\n const services = yield* Effect.context<\n InterpretServices<I> | SubscribeServices<B>\n >();\n const runEffect = Effect.runPromiseExitWith(services) as RunEffect;\n const fork = Effect.runForkWith(services) as ForkEffect;\n\n return yield* Effect.callback<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>\n >((resume, signal) => {\n const cells = interpret as unknown as Readonly<\n Record<string, AnyCell | undefined>\n >;\n const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));\n const cellFor = (type: string): DriveCell<M, C> | undefined => {\n const cell = cells[type];\n if (cell === undefined) return undefined;\n return async (cmd) => {\n const exit = await runEffect(cellResult(cell(cmd)), { signal });\n if (signal.aborted) throw new DriveInterrupted();\n return settleCellExit(exit, defined.has(type));\n };\n };\n\n driveLoop(\n machine,\n initial,\n msg,\n options.ctx as Ctx,\n options.maxRounds ?? DEFAULT_MAX_ROUNDS,\n {\n contract: driveContractOf(machine, options.clock ?? Date.now),\n cellFor,\n subs: effectSubs<S, M>(\n machine,\n options.subscribe ?? {},\n fork,\n signal,\n ),\n },\n ).then(\n (result) => resume(Effect.succeed(result)),\n (error: unknown) => resume(failureOf<M, C, I>(error)),\n );\n });\n });\n}\n\n/**\n * Sort what left the loop: its own failures and a hand-written cell's failure\n * are typed, everything else — a defect, a contract breach, a failed Sub — dies.\n */\nfunction failureOf<M, C extends Cmd, I>(\n error: unknown,\n): Effect.Effect<never, EffectDriveError<M, C, I>> {\n if (\n error instanceof DriveRoundsExceededError ||\n error instanceof DriveNoHandlerError\n ) {\n return Effect.fail(error as EffectDriveError<M, C, I>);\n }\n if (error instanceof CellFailure) {\n annotateTrace(error.failure, driveTraceOf(error) ?? []);\n return Effect.fail(error.failure as EffectDriveError<M, C, I>);\n }\n if (error instanceof DriveInterrupted) return Effect.interrupt;\n return Effect.die(error);\n}\n\n/**\n * One started Sub. It stays registered while the machine wants it, even after\n * its Stream ends, so a Sub that finished is not started again — the engine\n * keeps it the same way. `stopped` drops any Msg that arrives after a fold\n * turned it off; `ended` says its Stream no longer runs.\n */\ninterface StartedSub {\n stopped: boolean;\n ended: boolean;\n fiber?: Fiber.Fiber<unknown, unknown>;\n}\n\n/**\n * The Effect engine's Subs as the shared loop runs them: each wanted Sub's\n * Stream is drained on its own fiber into a buffer the loop reads between\n * rounds.\n */\nfunction effectSubs<S, M>(\n machine: { readonly subs?: ReadonlyArray<unknown> },\n subscribe: Readonly<Record<string, AnyRunner | undefined>>,\n fork: ForkEffect,\n signal: AbortSignal,\n): DriveSubs<S, M> {\n const builtins = builtinRunners as unknown as Readonly<\n Record<string, AnyRunner | undefined>\n >;\n const started = new Map<string, StartedSub>();\n let buffered: M[] = [];\n let failed: Cause.Cause<unknown> | undefined;\n let wake: (() => void) | undefined;\n const notify = (): void => {\n const woken = wake;\n wake = undefined;\n woken?.();\n };\n signal.addEventListener(\"abort\", notify, { once: true });\n\n const stop = (sub: StartedSub): Promise<void> => {\n sub.stopped = true;\n return sub.ended || sub.fiber === undefined\n ? Promise.resolve()\n : Effect.runPromise(Fiber.interrupt(sub.fiber));\n };\n const anyRunning = (): boolean =>\n [...started.values()].some((sub) => !sub.ended);\n\n return {\n reconcile(state) {\n const desired = desiredSubs(machine, state);\n for (const [id, sub] of started) {\n if (desired.has(id)) continue;\n started.delete(id);\n void stop(sub);\n }\n for (const [id, sub] of desired) {\n if (started.has(id)) continue;\n const make = subscribe[sub.type] ?? builtins[sub.type];\n if (make === undefined) {\n throw new Error(\n `@demlik/tea: no subscribe runner for Sub type \"${sub.type}\". ` +\n \"Pass one to drive in `subscribe`.\",\n );\n }\n const entry: StartedSub = { stopped: false, ended: false };\n started.set(id, entry);\n entry.fiber = fork(\n Stream.runForEach(make(sub), (next) =>\n Effect.sync(() => {\n if (entry.stopped) return;\n buffered.push(next as M);\n notify();\n }),\n ).pipe(\n Effect.onExit((exit) =>\n Effect.sync(() => {\n entry.ended = true;\n if (\n !entry.stopped &&\n Exit.isFailure(exit) &&\n !Cause.hasInterruptsOnly(exit.cause)\n ) {\n failed ??= exit.cause;\n }\n notify();\n }),\n ),\n ),\n );\n }\n },\n async next() {\n for (;;) {\n if (signal.aborted) throw new DriveInterrupted();\n if (buffered.length > 0) {\n const out = buffered;\n buffered = [];\n return out;\n }\n if (failed !== undefined) throw Cause.squash(failed);\n if (!anyRunning()) return undefined;\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n },\n async close() {\n signal.removeEventListener(\"abort\", notify);\n const stopping = [...started.values()].map(stop);\n started.clear();\n await Promise.all(stopping);\n },\n };\n}\n"]}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Cmd,
|
|
2
|
-
import {
|
|
1
|
+
import { C as Cmd, c as Sub, M as Machine, B as BuiltinSub } from '../core-Ce9ncpuv.js';
|
|
2
|
+
import { R as Runtime } from '../runtime-types-BKg2CGMx.js';
|
|
3
3
|
import '@standard-schema/spec';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -160,156 +160,6 @@ declare function bindMachine<S, M extends {
|
|
|
160
160
|
type: string;
|
|
161
161
|
}, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: Ctx): BoundMachine<S, M, C, U, Ctx>;
|
|
162
162
|
|
|
163
|
-
/**
|
|
164
|
-
* The default round bound when `opts.maxRounds` is omitted: **100**.
|
|
165
|
-
*
|
|
166
|
-
* A round is one drain of the pending Msgs plus one drain of the Cmds those
|
|
167
|
-
* folds emitted, so a settling machine costs a handful; 100 is loose enough
|
|
168
|
-
* that a legitimate retry ladder finishes inside it and tight enough that a
|
|
169
|
-
* genuine cycle stops rather than hangs.
|
|
170
|
-
*/
|
|
171
|
-
declare const DEFAULT_MAX_ROUNDS = 100;
|
|
172
|
-
/**
|
|
173
|
-
* One entry of a driven run's history, in dispatch order.
|
|
174
|
-
*
|
|
175
|
-
* - `msg` — a Msg folded through the machine's reducer. The seed Msg is the
|
|
176
|
-
* first entry of every trace; every later one is a settle Msg a handler
|
|
177
|
-
* returned (or fired through its injected `dispatch`).
|
|
178
|
-
* - `cmd` — a Cmd handed to an interpret handler. It is recorded BEFORE the
|
|
179
|
-
* handler is awaited, so a trace recovered from a rejected handler names the
|
|
180
|
-
* Cmd that rejected as its last entry.
|
|
181
|
-
*/
|
|
182
|
-
type DriveTraceEntry<M, C> = {
|
|
183
|
-
readonly kind: "msg";
|
|
184
|
-
readonly msg: M;
|
|
185
|
-
} | {
|
|
186
|
-
readonly kind: "cmd";
|
|
187
|
-
readonly cmd: C;
|
|
188
|
-
};
|
|
189
|
-
/** What a settled {@link drive} hands back. */
|
|
190
|
-
interface DriveResult<S, M, C> {
|
|
191
|
-
/** The state after the machine went quiet — no Msg pending, no Cmd unperformed. */
|
|
192
|
-
readonly state: S;
|
|
193
|
-
/**
|
|
194
|
-
* Every Msg folded and every Cmd dispatched, in order. Filtering it to its
|
|
195
|
-
* `msg` entries and replaying those through `replay` from the same initial
|
|
196
|
-
* state reproduces {@link DriveResult.state}.
|
|
197
|
-
*/
|
|
198
|
-
readonly trace: readonly DriveTraceEntry<M, C>[];
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* {@link drive}'s `ctx` field. `CtxArg` widened by exactly one case: a machine
|
|
202
|
-
* declaring `ctx: undefined` in its `types` block — the pure shape every
|
|
203
|
-
* how-to on this package writes — may omit it too. `CtxArg` alone keeps `ctx`
|
|
204
|
-
* REQUIRED there, because `{}` is not assignable to `undefined`, which would
|
|
205
|
-
* make the common pure call site spell `{ ctx: undefined }` to say nothing.
|
|
206
|
-
* A machine whose `Ctx` carries a field a handler reads still requires it.
|
|
207
|
-
*/
|
|
208
|
-
type DriveCtxArg<Ctx> = [undefined] extends [Ctx] ? {
|
|
209
|
-
readonly ctx?: Ctx;
|
|
210
|
-
} : CtxArg<Ctx>;
|
|
211
|
-
/**
|
|
212
|
-
* {@link drive}'s options. `ctx` is conditionally optional (see
|
|
213
|
-
* {@link DriveCtxArg}).
|
|
214
|
-
*/
|
|
215
|
-
type DriveOptions<Ctx> = DriveCtxArg<Ctx> & {
|
|
216
|
-
/**
|
|
217
|
-
* The round bound. Exceeding it throws {@link DriveRoundsExceededError};
|
|
218
|
-
* `drive` never returns a half-driven state. Defaults to
|
|
219
|
-
* {@link DEFAULT_MAX_ROUNDS} (100).
|
|
220
|
-
*/
|
|
221
|
-
readonly maxRounds?: number;
|
|
222
|
-
/**
|
|
223
|
-
* The clock that stamps `at` on a `Cmd.define`d Cmd's minted `_ok` / `_err`
|
|
224
|
-
* Msg — the one `run` takes. Defaults to `Date.now`; pin it for a test that
|
|
225
|
-
* asserts on `at`.
|
|
226
|
-
*/
|
|
227
|
-
readonly clock?: () => number;
|
|
228
|
-
};
|
|
229
|
-
/**
|
|
230
|
-
* Raised when a driven machine is still emitting work after `maxRounds` rounds.
|
|
231
|
-
* Follows the tea error dialect (`override readonly name`) and carries a `_tag`
|
|
232
|
-
* discriminant, so a test branches on it by `instanceof` or on the tag.
|
|
233
|
-
*
|
|
234
|
-
* It carries the round count it stopped at and the PARTIAL trace up to that
|
|
235
|
-
* point, which is what a test needs to see the cycle — the bound alone says
|
|
236
|
-
* only that one exists.
|
|
237
|
-
*/
|
|
238
|
-
declare class DriveRoundsExceededError<M, C> extends Error {
|
|
239
|
-
/** The bound that was exceeded. */
|
|
240
|
-
readonly maxRounds: number;
|
|
241
|
-
/** The round the driver stopped at — always `maxRounds + 1`. */
|
|
242
|
-
readonly rounds: number;
|
|
243
|
-
/** Every Msg folded and Cmd dispatched before the driver gave up. */
|
|
244
|
-
readonly trace: readonly DriveTraceEntry<M, C>[];
|
|
245
|
-
readonly name = "DriveRoundsExceededError";
|
|
246
|
-
readonly _tag: "DriveRoundsExceededError";
|
|
247
|
-
constructor(
|
|
248
|
-
/** The bound that was exceeded. */
|
|
249
|
-
maxRounds: number,
|
|
250
|
-
/** The round the driver stopped at — always `maxRounds + 1`. */
|
|
251
|
-
rounds: number,
|
|
252
|
-
/** Every Msg folded and Cmd dispatched before the driver gave up. */
|
|
253
|
-
trace: readonly DriveTraceEntry<M, C>[]);
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Raised when a Cmd reaches {@link drive} with no handler for its `type` in the
|
|
257
|
-
* `handlers` record. `Interpret`'s mapped type makes that a compile error at a
|
|
258
|
-
* well-typed call site; this is the runtime arm for a handler record assembled
|
|
259
|
-
* dynamically or cast.
|
|
260
|
-
*/
|
|
261
|
-
declare class DriveNoHandlerError<M, C> extends Error {
|
|
262
|
-
/** The Cmd `type` nothing in the handler record answered. */
|
|
263
|
-
readonly cmdType: string;
|
|
264
|
-
/** Every Msg folded and Cmd dispatched before the miss. */
|
|
265
|
-
readonly trace: readonly DriveTraceEntry<M, C>[];
|
|
266
|
-
readonly name = "DriveNoHandlerError";
|
|
267
|
-
readonly _tag: "DriveNoHandlerError";
|
|
268
|
-
constructor(
|
|
269
|
-
/** The Cmd `type` nothing in the handler record answered. */
|
|
270
|
-
cmdType: string,
|
|
271
|
-
/** Every Msg folded and Cmd dispatched before the miss. */
|
|
272
|
-
trace: readonly DriveTraceEntry<M, C>[]);
|
|
273
|
-
}
|
|
274
|
-
/**
|
|
275
|
-
* Read back the partial trace `drive` attached to an error a handler rejected
|
|
276
|
-
* with. Returns `undefined` for anything `drive` did not annotate.
|
|
277
|
-
*
|
|
278
|
-
* A rejecting handler's error propagates out of `drive` UNSWALLOWED — same
|
|
279
|
-
* value, same `instanceof`, same `_tag` — so a test that already branches on
|
|
280
|
-
* the handler's own error type keeps doing so. The trace rides along on a
|
|
281
|
-
* non-enumerable property so it is recoverable without changing how the error
|
|
282
|
-
* prints or serializes.
|
|
283
|
-
*/
|
|
284
|
-
declare function driveTraceOf<M, C>(err: unknown): readonly DriveTraceEntry<M, C>[] | undefined;
|
|
285
|
-
/**
|
|
286
|
-
* Drive `machine` from `initial` through `msg` against the REAL interpret
|
|
287
|
-
* `handlers`, feeding every settle Msg back until the machine goes quiet, and
|
|
288
|
-
* hand back the settled state together with the whole history.
|
|
289
|
-
*
|
|
290
|
-
* `handlers` is the same table a host hands `run` as its `interpret` (e.g.
|
|
291
|
-
* a jev knob's `resilient_run` handler), so the test drives the real interpreter and
|
|
292
|
-
* mocks only the port beneath it.
|
|
293
|
-
*
|
|
294
|
-
* One round is: fold every pending Msg, then await a handler for every Cmd
|
|
295
|
-
* those folds emitted. A Msg a handler returns — or fires through its injected
|
|
296
|
-
* `dispatch` — is pending for the next round. A machine that emits no Cmd for
|
|
297
|
-
* its seed Msg therefore settles in ONE round and never awaits a handler at
|
|
298
|
-
* all.
|
|
299
|
-
*
|
|
300
|
-
* The `ctx` handed to each handler is `opts.ctx` augmented with a NO-OP `emit`,
|
|
301
|
-
* so a handler that emits to a `Port` runs without a live runtime; nothing here
|
|
302
|
-
* observes ports. Supply your own `emit` on `ctx` to capture them.
|
|
303
|
-
*
|
|
304
|
-
* @throws {DriveRoundsExceededError} when the machine is still emitting work
|
|
305
|
-
* after `opts.maxRounds` rounds. It never returns a half-driven state.
|
|
306
|
-
* @throws whatever a handler rejects with, unchanged — annotated with the
|
|
307
|
-
* partial trace, readable via {@link driveTraceOf}.
|
|
308
|
-
*/
|
|
309
|
-
declare function drive<S, M extends {
|
|
310
|
-
type: string;
|
|
311
|
-
}, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, initial: S, msg: M, handlers: Interpret<M, C, Ctx>, ...[opts]: Record<never, never> extends DriveOptions<Ctx> ? [opts?: DriveOptions<Ctx>] : [opts: DriveOptions<Ctx>]): Promise<DriveResult<S, M, C>>;
|
|
312
|
-
|
|
313
163
|
/**
|
|
314
164
|
* Assert that replaying `opts.msgs` is a pure function of the Msg log: the
|
|
315
165
|
* final state and every emitted Cmd come out the same under two different
|
|
@@ -378,14 +228,18 @@ type StateFactoryAPI<S extends {
|
|
|
378
228
|
* overrides (undefineds stripped), then stamps the `type` discriminator.
|
|
379
229
|
*
|
|
380
230
|
* @example
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
231
|
+
* ```ts
|
|
232
|
+
* import { stateFactory } from "@demlik/tea/testing";
|
|
233
|
+
*
|
|
234
|
+
* type S = { type: "a"; n: number } | { type: "b"; s: string };
|
|
235
|
+
* const f = stateFactory<S>({ a: { n: 0 }, b: { s: "" } });
|
|
236
|
+
* f.a(); // { type: "a", n: 0 }
|
|
237
|
+
* f.a({ n: 42 }); // { type: "a", n: 42 }
|
|
238
|
+
* f.b({ s: "hi" }); // { type: "b", s: "hi" }
|
|
239
|
+
* ```
|
|
386
240
|
*/
|
|
387
241
|
declare function stateFactory<S extends {
|
|
388
242
|
type: string;
|
|
389
243
|
}>(defaults: StateFactoryDefaults<S>): StateFactoryAPI<S>;
|
|
390
244
|
|
|
391
|
-
export { type BoundMachine,
|
|
245
|
+
export { type BoundMachine, type ReplayOpts, type StateFactoryAPI, type StateFactoryDefaults, bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, expectReplayDeterministic, noopRuntime, stateFactory, step };
|
package/dist/testing/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
export { bindMachine } from '../chunk-JVKU2UQC.js';
|
|
2
|
+
import '../chunk-FNGZMVIT.js';
|
|
3
|
+
import { replay } from '../chunk-AIIXV4XB.js';
|
|
3
4
|
import { expect, vi } from 'vitest';
|
|
4
5
|
|
|
5
6
|
function expectFinalState(machine, opts, expected) {
|
|
@@ -22,133 +23,6 @@ function step(machine, loaded, msg, ctx) {
|
|
|
22
23
|
const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });
|
|
23
24
|
return [state, cmds];
|
|
24
25
|
}
|
|
25
|
-
function bindMachine(machine, ctx) {
|
|
26
|
-
return {
|
|
27
|
-
step(loaded, msg) {
|
|
28
|
-
const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });
|
|
29
|
-
return [state, cmds];
|
|
30
|
-
},
|
|
31
|
-
expectFinalState(opts, expected) {
|
|
32
|
-
const { state } = replay(machine, { ...opts, ctx });
|
|
33
|
-
expect(state).toEqual(expected);
|
|
34
|
-
},
|
|
35
|
-
expectCmdEmitted(opts, cmd) {
|
|
36
|
-
const { cmds } = replay(machine, { ...opts, ctx });
|
|
37
|
-
expect(cmds).toContainEqual(cmd);
|
|
38
|
-
},
|
|
39
|
-
expectCmdSequence(opts, expected) {
|
|
40
|
-
const { cmds } = replay(machine, { ...opts, ctx });
|
|
41
|
-
expect(cmds).toEqual(expected);
|
|
42
|
-
},
|
|
43
|
-
expectActiveSubs(opts, expected) {
|
|
44
|
-
const { subs } = replay(machine, { ...opts, ctx });
|
|
45
|
-
expect(subs).toEqual(expected);
|
|
46
|
-
},
|
|
47
|
-
replay(opts) {
|
|
48
|
-
return replay(machine, { ...opts, ctx });
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/testing/drive.ts
|
|
54
|
-
var DEFAULT_MAX_ROUNDS = 100;
|
|
55
|
-
var DriveRoundsExceededError = class extends Error {
|
|
56
|
-
constructor(maxRounds, rounds, trace) {
|
|
57
|
-
super(
|
|
58
|
-
`@demlik/tea: drive did not settle within ${maxRounds} round(s) \u2014 the machine was still emitting work at round ${rounds}. The partial trace (${trace.length} entr${trace.length === 1 ? "y" : "ies"}) is on the error's \`trace\` field.`
|
|
59
|
-
);
|
|
60
|
-
this.maxRounds = maxRounds;
|
|
61
|
-
this.rounds = rounds;
|
|
62
|
-
this.trace = trace;
|
|
63
|
-
}
|
|
64
|
-
maxRounds;
|
|
65
|
-
rounds;
|
|
66
|
-
trace;
|
|
67
|
-
name = "DriveRoundsExceededError";
|
|
68
|
-
_tag = "DriveRoundsExceededError";
|
|
69
|
-
};
|
|
70
|
-
var DriveNoHandlerError = class extends Error {
|
|
71
|
-
constructor(cmdType, trace) {
|
|
72
|
-
super(
|
|
73
|
-
`@demlik/tea: drive found no interpret handler for Cmd type "${cmdType}" \u2014 the handlers record must answer every Cmd the machine emits.`
|
|
74
|
-
);
|
|
75
|
-
this.cmdType = cmdType;
|
|
76
|
-
this.trace = trace;
|
|
77
|
-
}
|
|
78
|
-
cmdType;
|
|
79
|
-
trace;
|
|
80
|
-
name = "DriveNoHandlerError";
|
|
81
|
-
_tag = "DriveNoHandlerError";
|
|
82
|
-
};
|
|
83
|
-
var TRACE_KEY = "driveTrace";
|
|
84
|
-
function driveTraceOf(err) {
|
|
85
|
-
if (typeof err !== "object" || err === null) return void 0;
|
|
86
|
-
const carried = err[TRACE_KEY];
|
|
87
|
-
return Array.isArray(carried) ? carried : void 0;
|
|
88
|
-
}
|
|
89
|
-
async function drive(machine, initial, msg, handlers, ...[opts]) {
|
|
90
|
-
const options = opts ?? {};
|
|
91
|
-
const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;
|
|
92
|
-
const bound = bindMachine(machine, options.ctx);
|
|
93
|
-
const settle = cmdEdgeOver(machine.cmds ?? [], options.clock ?? Date.now);
|
|
94
|
-
const noopEmit = { emit: () => {
|
|
95
|
-
} };
|
|
96
|
-
const handlerCtx = {
|
|
97
|
-
...noopEmit,
|
|
98
|
-
ok: Outcome.ok,
|
|
99
|
-
err: Outcome.err,
|
|
100
|
-
[cmdEdge]: settle,
|
|
101
|
-
...options.ctx
|
|
102
|
-
};
|
|
103
|
-
const cells = handlers;
|
|
104
|
-
const trace = [];
|
|
105
|
-
let state = initial;
|
|
106
|
-
let pending = [msg];
|
|
107
|
-
let round = 0;
|
|
108
|
-
while (pending.length > 0) {
|
|
109
|
-
round += 1;
|
|
110
|
-
if (round > maxRounds) {
|
|
111
|
-
throw new DriveRoundsExceededError(maxRounds, round, trace);
|
|
112
|
-
}
|
|
113
|
-
const folding = pending;
|
|
114
|
-
pending = [];
|
|
115
|
-
const emitted = [];
|
|
116
|
-
for (const next of folding) {
|
|
117
|
-
trace.push({ kind: "msg", msg: next });
|
|
118
|
-
const [advanced, cmds] = bound.step(state, next);
|
|
119
|
-
state = advanced;
|
|
120
|
-
emitted.push(...cmds);
|
|
121
|
-
}
|
|
122
|
-
for (const cmd of emitted) {
|
|
123
|
-
trace.push({ kind: "cmd", cmd });
|
|
124
|
-
const cell = cells[cmd.type];
|
|
125
|
-
if (cell === void 0) {
|
|
126
|
-
throw new DriveNoHandlerError(cmd.type, trace);
|
|
127
|
-
}
|
|
128
|
-
let settled;
|
|
129
|
-
try {
|
|
130
|
-
settled = settle(
|
|
131
|
-
cmd,
|
|
132
|
-
await cell(cmd, handlerCtx, (fired) => {
|
|
133
|
-
pending.push(fired);
|
|
134
|
-
})
|
|
135
|
-
);
|
|
136
|
-
} catch (err) {
|
|
137
|
-
if (typeof err === "object" && err !== null) {
|
|
138
|
-
Object.defineProperty(err, TRACE_KEY, {
|
|
139
|
-
value: [...trace],
|
|
140
|
-
enumerable: false,
|
|
141
|
-
configurable: true,
|
|
142
|
-
writable: true
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
throw err;
|
|
146
|
-
}
|
|
147
|
-
if (settled !== void 0) pending.push(settled);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return { state, trace };
|
|
151
|
-
}
|
|
152
26
|
var AMBIENT_A = { now: 1e6, rng: 0.123 };
|
|
153
27
|
var AMBIENT_B = { now: 9876543, rng: 0.987 };
|
|
154
28
|
function expectReplayDeterministic(machine, opts) {
|
|
@@ -248,6 +122,6 @@ function stateFactory(defaults) {
|
|
|
248
122
|
return api;
|
|
249
123
|
}
|
|
250
124
|
|
|
251
|
-
export {
|
|
125
|
+
export { expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, expectReplayDeterministic, noopRuntime, stateFactory, step };
|
|
252
126
|
//# sourceMappingURL=index.js.map
|
|
253
127
|
//# sourceMappingURL=index.js.map
|