@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/assertions.ts","../../src/testing/bind-machine.ts","../../src/testing/drive.ts","../../src/testing/expect-replay-deterministic.ts","../../src/testing/noop-runtime.ts","../../src/testing/state-factory.ts"],"names":["expect"],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Test-side assertions over `replay`. Each helper composes the replay call\n// with a single `expect(...)` so consumers stop hand-rolling the 5-line\n// \"destructure { state, cmds } → expect.toBe / toEqual / toContainEqual\"\n// boilerplate at every test site.\n//\n// Each helper is a thin wrapper that:\n// 1. Calls `replay(machine, opts)` once.\n// 2. Pipes the result into a single vitest `expect(...)` matcher.\n//\n// All helpers return `void` — assertions throw on failure (vitest contract).\n// No mutation of the machine or opts. Pure test ergonomics.\n//\n// Type-inference note: each helper anchors its generic parameters to the\n// `machine` argument. The `expected` (and `opts.msgs`) arguments are wrapped\n// in `NoInfer<...>` so they do NOT participate in inference — otherwise a\n// `[]`-shaped `expected` would force `C = never` and reject a machine whose\n// real Cmd union is non-empty. Anchoring inference to the machine is the\n// same discipline `replay` uses (it has nothing but the machine and opts).\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n// `NoInfer<T>` is the built-in TypeScript 5.4+ intrinsic that blocks\n// inference from a generic parameter at a position. We use it on `expected`\n// and `opts` so inference flows purely FROM the machine — `[]`-shaped\n// `expected` no longer drags `C` down to `never` and rejects the machine.\n\n/**\n * Shared options shape for every test assertion below. Mirrors the\n * second argument of `replay(machine, opts)` — `msgs` is the sequence to\n * fold over (may be empty for init-only assertions), `ctx` is the\n * substrate-required ctx value, `loaded` is the optional pre-`init`\n * snapshot (omit / pass `null` for fresh boot).\n */\nexport interface ReplayOpts<S, M, Ctx> {\n readonly msgs: readonly M[];\n readonly ctx: Ctx;\n readonly loaded?: S | null;\n}\n\n/**\n * Assert the final state after replaying `opts.msgs` equals `expected`.\n * Replaces the boilerplate:\n *\n * const { state } = replay(machine, opts);\n * expect(state).toEqual(expected);\n *\n * Uses `toEqual` (deep structural). For type-narrowing assertions that\n * branch on `state.type` afterwards, fall back to plain `replay` —\n * `expectFinalState` is the \"exact match\" tool.\n */\nexport function expectFinalState<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<S>,\n): void {\n const { state } = replay(machine, opts);\n expect(state).toEqual(expected);\n}\n\n/**\n * Assert that `cmd` appears at least once in the cmds array produced by\n * replaying `opts.msgs`. Uses `toContainEqual` (deep structural). Order\n * and surrounding cmds are not asserted — use `expectCmdSequence` for\n * exact ordered match.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toContainEqual(cmd);\n */\nexport function expectCmdEmitted<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n cmd: NoInfer<C>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toContainEqual(cmd);\n}\n\n/**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Uses `toEqual` against the full cmds array — extra cmds\n * fail the assertion, missing cmds fail the assertion, wrong order\n * fails the assertion.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toEqual(expected);\n */\nexport function expectCmdSequence<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly C[]>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toEqual(expected);\n}\n\n/**\n * Assert the exact set of subs desired at the final state after\n * replaying `opts.msgs`. Uses `toEqual` against the full subs array.\n *\n * Replaces:\n *\n * const { subs } = replay(machine, opts);\n * expect(subs).toEqual(expected);\n */\nexport function expectActiveSubs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly U[]>,\n): void {\n const { subs } = replay(machine, opts);\n expect(subs).toEqual(expected);\n}\n\n/**\n * Single-msg step helper — feeds `loaded → msg → next state + cmds emitted by\n * that msg`. Returns a readonly tuple in the shape consumers used before the\n * substrate (`[state, cmds]`), so existing tests that destructure with\n * `[next, cmds]` can swap `reduce(state, msg)` for `step(machine, state, msg,\n * ctx)` without changing the assertion code.\n *\n * Internally a `replay` call with `loaded: prevState` and a single-element\n * `msgs` array. The cmds returned are exactly the cmds emitted by THIS msg\n * (init emits no cmds when `loaded` is provided, so the slice is the entire\n * array).\n *\n * Use when consumers want to walk a machine one msg at a time and assert per\n * step. For one-shot \"final state after N msgs\" assertions, prefer\n * `expectFinalState`.\n */\nexport function step<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n loaded: NoInfer<S>,\n msg: NoInfer<M>,\n ctx: NoInfer<Ctx>,\n): readonly [S, readonly C[]] {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n}\n","// ---------------------------------------------------------------------------\n// `bindMachine(machine, ctx)` — bound test harness over the free-function\n// assertions in `./assertions.ts`. Closes over the `machine` and `ctx` once\n// so per-test callsites stop repeating both at every invocation.\n//\n// Why this exists: every consumer test file ends up hand-rolling a local\n// two-arg `step(state, msg)` wrapper around `step(machine, state, msg, ctx)`,\n// and the four `expect*` assertions repeat `machine` + `ctx` at every\n// callsite. The bound form pulls both into one place and gives every\n// per-test call its tightest possible shape (2-arg max).\n//\n// `bindMachine` is built ON TOP of the free helpers — it does not replace\n// them. Both forms coexist; pick the one that fits the call site:\n//\n// - Free form → one-off call inside a helper that isn't bound to one\n// machine (e.g. cross-machine integration test).\n// - Bound form → a `describe` block runs many assertions against one\n// machine + ctx. The bound form is the common case.\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport {\n type BuiltinSub,\n type Cmd,\n type Machine,\n replay,\n type Sub,\n} from \"../index\";\n\n/**\n * Replay options shape used by every bound method. `ctx` is captured by\n * `bindMachine`; the per-call opts only need `msgs` and an optional\n * `loaded` snapshot. (Compare `ReplayOpts` in `./assertions.ts`, which\n * still takes `ctx` because the free helpers cannot capture it.)\n */\ninterface BoundOpts<S, M> {\n readonly msgs: readonly M[];\n readonly loaded?: S | null;\n}\n\n/**\n * The bound testing surface. Every method is 2-arg max — `ctx` and\n * `machine` are captured by `bindMachine`.\n *\n * `NoInfer<...>` on `expected` / `cmd` arguments mirrors the discipline\n * the free helpers use (see `./assertions.ts`): inference flows purely\n * FROM the machine, so a `[]`-shaped expected does not drag `C` down to\n * `never` and reject the machine.\n */\nexport interface BoundMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n // `Ctx` is a phantom-style parameter — present for consumers that want to\n // spell `BoundMachine<S, M, C, U, Ctx>` explicitly, mirroring `Machine`.\n // biome-ignore lint/correctness/noUnusedVariables: phantom type param (see above)\n Ctx,\n> {\n /**\n * Single-msg step — feeds `loaded → msg → [next state, cmds emitted by\n * that msg]`. Mirrors the free `step(machine, loaded, msg, ctx)`.\n */\n step(loaded: S, msg: M): readonly [S, readonly C[]];\n /**\n * Assert the final state after replaying `opts.msgs` deep-equals\n * `expected`. Mirrors the free `expectFinalState(machine, opts, expected)`.\n */\n expectFinalState(opts: BoundOpts<S, M>, expected: NoInfer<S>): void;\n /**\n * Assert `cmd` appears at least once in the emitted cmd list. Order is\n * not asserted. Mirrors the free `expectCmdEmitted(machine, opts, cmd)`.\n */\n expectCmdEmitted(opts: BoundOpts<S, M>, cmd: NoInfer<C>): void;\n /**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Mirrors the free `expectCmdSequence(machine, opts, expected)`.\n */\n expectCmdSequence(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<C>[],\n ): void;\n /**\n * Assert the exact set of subs desired at the final state. Mirrors the\n * free `expectActiveSubs(machine, opts, expected)`.\n */\n expectActiveSubs(\n opts: BoundOpts<S, M>,\n expected: readonly NoInfer<U | BuiltinSub<M>>[],\n ): void;\n /**\n * Bound `replay` — returns `{ state, cmds, subs }` for the given opts.\n * Use for the \"narrow-then-assert\" pattern where the test inspects a\n * specific field after `state.type === \"...\"` discrimination.\n */\n replay(opts: BoundOpts<S, M>): {\n state: S;\n cmds: readonly C[];\n subs: readonly (U | BuiltinSub<M>)[];\n };\n}\n\n/**\n * Bind a machine + ctx into a small bag of 2-arg helpers. Each method\n * delegates to `replay(machine, { ...opts, ctx })` and pipes the result\n * through a single `expect(...)` matcher (or returns the raw tuple, for\n * `step` and `replay`).\n *\n * Use one bind per `describe` block (or per file) when many assertions\n * share the same machine + ctx. For one-off cross-machine calls, stick\n * with the free helpers from `./assertions.ts`.\n */\nexport function bindMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(machine: Machine<S, M, C, U, Ctx>, ctx: Ctx): BoundMachine<S, M, C, U, Ctx> {\n return {\n step(loaded, msg) {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n },\n expectFinalState(opts, expected) {\n const { state } = replay(machine, { ...opts, ctx });\n expect(state).toEqual(expected);\n },\n expectCmdEmitted(opts, cmd) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toContainEqual(cmd);\n },\n expectCmdSequence(opts, expected) {\n const { cmds } = replay(machine, { ...opts, ctx });\n expect(cmds).toEqual(expected);\n },\n expectActiveSubs(opts, expected) {\n const { subs } = replay(machine, { ...opts, ctx });\n expect(subs).toEqual(expected);\n },\n replay(opts) {\n return replay(machine, { ...opts, ctx });\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// `drive(machine, initial, msg, handlers, opts)` — the runtime's own loop,\n// said once, for a test.\n//\n// `bindMachine` hands back the Cmds a fold emitted and stops there; performing\n// them is the caller's. So every consumer test that exercises a Cmd-emitting\n// machine hand-writes the same fixture: step the Msg, await the real interpret\n// handler over each emitted Cmd, feed the settle Msg back, repeat until quiet,\n// with a hand-picked guard bound in the loop header. The guard count, the\n// accumulate-then-swap order and the \"did I drain this round before starting\n// the next\" question are re-decided at every copy site, and one of those copies\n// ships in `docs/how-to/ask-jev-a-typed-question.md`.\n//\n// `drive` is that fixture as one call. It composes `bindMachine`'s synchronous\n// 2-arg `step` and adds NO second reducer path — there is no `Runtime` here, no\n// observation, no clock, and nothing borrowed from the internal flow helpers.\n// The only impure thing it does is `await` the handler the caller handed it.\n//\n// **It returns the history, not just the endpoint.** `{ state, trace }`, where\n// `trace` is every Msg folded and every Cmd dispatched in order, is what makes\n// this worth a stable export rather than a snippet: a test asserts on the\n// SEQUENCE as well as the settled state — which handler ran, in which round,\n// and what came back — and the same trace's Msgs replayed through `replay` from\n// the same initial state reproduce the returned state exactly.\n//\n// Prior art converged on the same answer: Temporal's `TestWorkflowEnvironment`\n// hands a test the event history rather than the final result, and Effect's\n// `TestClock` runs make the effect log inspectable rather than opaque.\n//\n// **Exhausting `maxRounds` THROWS.** A driver that quietly returns a\n// half-driven state lets a test assert green on a machine that never settled —\n// the exact failure a hand-rolled `guard < 10` already risks.\n//\n// Strengthens invariant 9 (the testing surface is named and small).\n// ---------------------------------------------------------------------------\n\nimport {\n type Cmd,\n type CtxArg,\n type Interpret,\n type Machine,\n Outcome,\n type PortEmitter,\n type Sub,\n} from \"../index\";\nimport { cmdEdge, cmdEdgeOver } from \"../pure/core\";\nimport { bindMachine } from \"./bind-machine\";\n\n/**\n * The default round bound when `opts.maxRounds` is omitted: **100**.\n *\n * A round is one drain of the pending Msgs plus one drain of the Cmds those\n * folds emitted, so a settling machine costs a handful; 100 is loose enough\n * that a legitimate retry ladder finishes inside it and tight enough that a\n * genuine cycle stops rather than hangs.\n */\nexport const DEFAULT_MAX_ROUNDS = 100;\n\n/**\n * One entry of a driven run's history, in dispatch order.\n *\n * - `msg` — a Msg folded through the machine's reducer. The seed Msg is the\n * first entry of every trace; every later one is a settle Msg a handler\n * returned (or fired through its injected `dispatch`).\n * - `cmd` — a Cmd handed to an interpret handler. It is recorded BEFORE the\n * handler is awaited, so a trace recovered from a rejected handler names the\n * Cmd that rejected as its last entry.\n */\nexport type DriveTraceEntry<M, C> =\n | { readonly kind: \"msg\"; readonly msg: M }\n | { readonly kind: \"cmd\"; readonly cmd: C };\n\n/** What a settled {@link drive} hands back. */\nexport interface DriveResult<S, M, C> {\n /** The state after the machine went quiet — no Msg pending, no Cmd unperformed. */\n readonly state: S;\n /**\n * Every Msg folded and every Cmd dispatched, in order. Filtering it to its\n * `msg` entries and replaying those through `replay` from the same initial\n * state reproduces {@link DriveResult.state}.\n */\n readonly trace: readonly DriveTraceEntry<M, C>[];\n}\n\n/**\n * {@link drive}'s `ctx` field. `CtxArg` widened by exactly one case: a machine\n * declaring `ctx: undefined` in its `types` block — the pure shape every\n * how-to on this package writes — may omit it too. `CtxArg` alone keeps `ctx`\n * REQUIRED there, because `{}` is not assignable to `undefined`, which would\n * make the common pure call site spell `{ ctx: undefined }` to say nothing.\n * A machine whose `Ctx` carries a field a handler reads still requires it.\n */\nexport type DriveCtxArg<Ctx> = [undefined] extends [Ctx]\n ? { readonly ctx?: Ctx }\n : CtxArg<Ctx>;\n\n/**\n * {@link drive}'s options. `ctx` is conditionally optional (see\n * {@link DriveCtxArg}).\n */\nexport type DriveOptions<Ctx> = DriveCtxArg<Ctx> & {\n /**\n * The round bound. Exceeding it throws {@link DriveRoundsExceededError};\n * `drive` never returns a half-driven state. Defaults to\n * {@link DEFAULT_MAX_ROUNDS} (100).\n */\n readonly maxRounds?: number;\n /**\n * The clock that stamps `at` on a `Cmd.define`d Cmd's minted `_ok` / `_err`\n * Msg — the one `run` takes. Defaults to `Date.now`; pin it for a test that\n * asserts on `at`.\n */\n readonly clock?: () => number;\n};\n\n/**\n * Raised when a driven machine is still emitting work after `maxRounds` rounds.\n * Follows the tea error dialect (`override readonly name`) and carries a `_tag`\n * discriminant, so a test branches on it by `instanceof` or on the tag.\n *\n * It carries the round count it stopped at and the PARTIAL trace up to that\n * point, which is what a test needs to see the cycle — the bound alone says\n * only that one exists.\n */\nexport class DriveRoundsExceededError<M, C> extends Error {\n override readonly name = \"DriveRoundsExceededError\";\n readonly _tag = \"DriveRoundsExceededError\" as const;\n constructor(\n /** The bound that was exceeded. */\n public readonly maxRounds: number,\n /** The round the driver stopped at — always `maxRounds + 1`. */\n public readonly rounds: number,\n /** Every Msg folded and Cmd dispatched before the driver gave up. */\n public readonly trace: readonly DriveTraceEntry<M, C>[],\n ) {\n super(\n `@demlik/tea: drive did not settle within ${maxRounds} round(s) — the ` +\n `machine was still emitting work at round ${rounds}. The partial ` +\n `trace (${trace.length} entr${trace.length === 1 ? \"y\" : \"ies\"}) is ` +\n `on the error's \\`trace\\` field.`,\n );\n }\n}\n\n/**\n * Raised when a Cmd reaches {@link drive} with no handler for its `type` in the\n * `handlers` record. `Interpret`'s mapped type makes that a compile error at a\n * well-typed call site; this is the runtime arm for a handler record assembled\n * dynamically or cast.\n */\nexport class DriveNoHandlerError<M, C> extends Error {\n override readonly name = \"DriveNoHandlerError\";\n readonly _tag = \"DriveNoHandlerError\" as const;\n constructor(\n /** The Cmd `type` nothing in the handler record answered. */\n public readonly cmdType: string,\n /** Every Msg folded and Cmd dispatched before the miss. */\n public readonly trace: readonly DriveTraceEntry<M, C>[],\n ) {\n super(\n `@demlik/tea: drive found no interpret handler for Cmd type ` +\n `\"${cmdType}\" — the handlers record must answer every Cmd the ` +\n `machine emits.`,\n );\n }\n}\n\n/** The key `drive` hangs a partial trace off a rejected handler's error under. */\nconst TRACE_KEY = \"driveTrace\";\n\n/**\n * Read back the partial trace `drive` attached to an error a handler rejected\n * with. Returns `undefined` for anything `drive` did not annotate.\n *\n * A rejecting handler's error propagates out of `drive` UNSWALLOWED — same\n * value, same `instanceof`, same `_tag` — so a test that already branches on\n * the handler's own error type keeps doing so. The trace rides along on a\n * non-enumerable property so it is recoverable without changing how the error\n * prints or serializes.\n */\nexport function driveTraceOf<M, C>(\n err: unknown,\n): readonly DriveTraceEntry<M, C>[] | undefined {\n if (typeof err !== \"object\" || err === null) return undefined;\n const carried = (err as Record<string, unknown>)[TRACE_KEY];\n return Array.isArray(carried)\n ? (carried as readonly DriveTraceEntry<M, C>[])\n : undefined;\n}\n\n/**\n * Drive `machine` from `initial` through `msg` against the REAL interpret\n * `handlers`, feeding every settle Msg back until the machine goes quiet, and\n * hand back the settled state together with the whole history.\n *\n * `handlers` is the same table a host hands `run` as its `interpret` (e.g.\n * a jev knob's `resilient_run` handler), so the test drives the real interpreter and\n * mocks only the port beneath it.\n *\n * One round is: fold every pending Msg, then await a handler for every Cmd\n * those folds emitted. A Msg a handler returns — or fires through its injected\n * `dispatch` — is pending for the next round. A machine that emits no Cmd for\n * its seed Msg therefore settles in ONE round and never awaits a handler at\n * all.\n *\n * The `ctx` handed to each handler is `opts.ctx` augmented with a NO-OP `emit`,\n * so a handler that emits to a `Port` runs without a live runtime; nothing here\n * observes ports. Supply your own `emit` on `ctx` to capture them.\n *\n * @throws {DriveRoundsExceededError} when the machine is still emitting work\n * after `opts.maxRounds` rounds. It never returns a half-driven state.\n * @throws whatever a handler rejects with, unchanged — annotated with the\n * partial trace, readable via {@link driveTraceOf}.\n */\nexport async function drive<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n initial: S,\n msg: M,\n handlers: Interpret<M, C, Ctx>,\n ...[opts]: Record<never, never> extends DriveOptions<Ctx>\n ? [opts?: DriveOptions<Ctx>]\n : [opts: DriveOptions<Ctx>]\n): Promise<DriveResult<S, M, C>> {\n const options = (opts ?? {}) as {\n ctx?: Ctx;\n maxRounds?: number;\n clock?: () => number;\n };\n const maxRounds = options.maxRounds ?? DEFAULT_MAX_ROUNDS;\n const bound = bindMachine(machine, options.ctx as Ctx);\n\n // The `Cmd.define` edge `run` applies (ADR 0021): a defined Cmd's handler\n // returns an outcome, and the edge mints it into the def's `_ok` / `_err`\n // Msg. A hand-written Cmd's return passes through untouched.\n const settle = cmdEdgeOver(machine.cmds ?? [], options.clock ?? Date.now);\n\n // A handler's ctx is `Ctx & PortEmitter`, plus the `ok` / `err` builders and\n // the edge a defined Cmd's handler is handed. The Ctx half is the caller's;\n // the rest is the kernel's, and a driven test has no kernel — so a no-op\n // `emit` stands in, placed FIRST so a caller's own `emit` wins.\n const noopEmit: PortEmitter = { emit: () => {} };\n const handlerCtx = {\n ...noopEmit,\n ok: Outcome.ok,\n err: Outcome.err,\n [cmdEdge]: settle,\n ...(options.ctx as unknown as Record<string, unknown> | undefined),\n };\n\n // Indexed dynamically: `Interpret`'s mapped type already proved every Cmd\n // variant has a cell, but `cmd.type` is a union at the value level and TS\n // cannot correlate the two sides of that index.\n const cells = handlers as unknown as Record<\n string,\n | ((\n cmd: C,\n ctx: unknown,\n dispatch?: (msg: M) => void,\n // biome-ignore lint/suspicious/noConfusingVoidType: mirrors `Interpret`'s own cell return — a follow-up Msg, or nothing\n ) => Promise<M | void>)\n | undefined\n >;\n\n const trace: DriveTraceEntry<M, C>[] = [];\n let state = initial;\n let pending: M[] = [msg];\n let round = 0;\n\n while (pending.length > 0) {\n round += 1;\n if (round > maxRounds) {\n throw new DriveRoundsExceededError<M, C>(maxRounds, round, trace);\n }\n\n // Drain the round's Msgs first, THEN its Cmds. Swapping the buffer before\n // the fold is what keeps a settle Msg out of the round that produced it —\n // the ordering question every hand-rolled copy of this loop re-answers.\n const folding = pending;\n pending = [];\n const emitted: C[] = [];\n for (const next of folding) {\n trace.push({ kind: \"msg\", msg: next });\n const [advanced, cmds] = bound.step(state, next);\n state = advanced;\n emitted.push(...cmds);\n }\n\n for (const cmd of emitted) {\n trace.push({ kind: \"cmd\", cmd });\n const cell = cells[cmd.type];\n if (cell === undefined) {\n throw new DriveNoHandlerError<M, C>(cmd.type, trace);\n }\n // biome-ignore lint/suspicious/noConfusingVoidType: mirrors `Interpret`'s own cell return — a follow-up Msg, or nothing\n let settled: M | void;\n try {\n settled = settle(\n cmd,\n await cell(cmd, handlerCtx, (fired) => {\n pending.push(fired);\n }),\n ) as M | undefined;\n } catch (err) {\n // Unswallowed: the handler's own error is what leaves `drive`, so a\n // test's `instanceof` / `_tag` branch still lands. The trace rides on\n // a non-enumerable property (see `driveTraceOf`).\n if (typeof err === \"object\" && err !== null) {\n Object.defineProperty(err, TRACE_KEY, {\n value: [...trace],\n enumerable: false,\n configurable: true,\n writable: true,\n });\n }\n throw err;\n }\n if (settled !== undefined) pending.push(settled);\n }\n }\n\n return { state, trace };\n}\n","import { expect, vi } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\nimport type { ReplayOpts } from \"./assertions\";\n\ninterface Ambient {\n readonly now: number;\n readonly rng: number;\n}\n\nconst AMBIENT_A: Ambient = { now: 1_000_000, rng: 0.123 };\nconst AMBIENT_B: Ambient = { now: 9_876_543, rng: 0.987 };\n\n/**\n * Assert that replaying `opts.msgs` is a pure function of the Msg log: the\n * final state and every emitted Cmd come out the same under two different\n * global wall-clocks and RNG seeds.\n *\n * `init` and `update` never receive a clock, so a cell that reads\n * `Date.now()` or `Math.random()` reaches for the global one, and a replay of\n * the same log on another day or another host lands somewhere else. Two\n * back-to-back replays under one clock would hide that; this helper stubs\n * two distinct ambients and compares. Time belongs in the Msg (an `at`\n * field), where it is identical across replays.\n *\n * Subs are not compared: they are derived from the final state, so a state\n * that matches yields the same Subs.\n */\nexport function expectReplayDeterministic<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n): void {\n const a = underAmbient(AMBIENT_A, () => replay(machine, opts));\n const b = underAmbient(AMBIENT_B, () => replay(machine, opts));\n expect(\n b.state,\n \"final state changed under a different clock/RNG: init or update reads \" +\n \"Date.now()/Math.random(); time must enter as Msg data\",\n ).toEqual(a.state);\n expect(\n b.cmds,\n \"emitted Cmds changed under a different clock/RNG: a Cmd payload reads \" +\n \"Date.now()/Math.random(); time must enter as Msg data\",\n ).toEqual(a.cmds);\n}\n\n/** Run `fn` with the global `Date.now` and `Math.random` pinned, then restore them. */\nfunction underAmbient<T>(ambient: Ambient, fn: () => T): T {\n const nowSpy = vi.spyOn(Date, \"now\").mockReturnValue(ambient.now);\n const rngSpy = vi.spyOn(Math, \"random\").mockReturnValue(ambient.rng);\n try {\n return fn();\n } finally {\n nowSpy.mockRestore();\n rngSpy.mockRestore();\n }\n}\n","// ---------------------------------------------------------------------------\n// noopRuntime — a Runtime<S, M>-shaped value whose every method is inert.\n//\n// The motivating callsite: a machine's `ctx` requires a sibling Runtime<S,\n// M> reference (e.g. the queue runtime in extension/lib/queue-machine\n// reaches into an `auditRuntime: Runtime<...>` slot on its ctx so its\n// interpret handlers can dispatch into the audit machine). When the test\n// replays such a machine, `replay` never actually invokes `interpret`, so\n// the runtime reference is never dereferenced — but the type system\n// still requires a value of `Runtime<S, M>` shape at the ctx slot. Tests\n// today work around this with a hand-rolled 12-line NOOP_AUDIT_RUNTIME\n// const cast through `as unknown as Runtime<...>`.\n//\n// `noopRuntime<S, M>()` is that hand-roll, named once. It returns a real\n// Runtime<S, M> value whose every method is a structurally-correct no-op:\n//\n// - `dispatch`: resolves immediately, never advances state.\n// - `getState`: returns the optional `initialState` argument (or a\n// deliberate `undefined` cast as S — the function lies about its\n// return type when no initial state is provided; this is the trade-off\n// we accept for an ergonomic test substrate).\n// - `subscribe` / `observe` / `subscribePort`: register the listener,\n// return a no-op unsubscribe. The listener is never fired because the\n// state never changes.\n// - `emitPort`: no-op.\n// - `ready`: a Promise that resolves to the no-op runtime itself (the #45\n// `ready: Promise<Runtime<S, M>>` shape — already booted, nothing to wait\n// for).\n// - `stop`: resolved Promise<void>.\n//\n// Strengthens invariant 9 (the testing surface is named and small).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime } from \"../index\";\n\n/**\n * Construct an inert `Runtime<S, M>` value. Use at ctx sites that require\n * a Runtime reference but where the test never invokes `interpret` (so\n * the runtime is never dereferenced in practice).\n *\n * @param opts.initialState Optional value returned by `getState()`. Omit\n * when the test never calls `getState()` on the no-op runtime — most\n * tests only need the type-level slot filled.\n */\nexport function noopRuntime<S, M extends { type: string }>(opts?: {\n initialState?: S;\n}): Runtime<S, M> {\n const initialState = opts?.initialState;\n // Single source of truth for the \"do-nothing\" unsubscribe so multiple\n // subscribe call sites share the same function instance — purely cosmetic\n // (the function would be a no-op either way).\n const noopUnsubscribe = (): void => {};\n const runtime: Runtime<S, M> = {\n async dispatch(_msg: M): Promise<void> {\n // Resolves immediately. State never advances; listeners never fire.\n // Quiescent by default (#50), but the no-op has no follow-up chain to\n // drain, so single-step and run-to-quiescence collapse to the same no-op.\n },\n async dispatchOnce(_msg: M): Promise<void> {\n // Single-step sibling of `dispatch` (#50). Same no-op for the no-op\n // runtime — there is no tail to leave in flight.\n },\n async idle(): Promise<void> {\n // No tail to drain — the no-op runtime never enqueues a follow-up.\n },\n result(): S | undefined {\n // The no-op runtime never advances, so it is never terminal (#46).\n return undefined;\n },\n done(): Promise<S> {\n // Never terminal → the awaitable never settles, matching `run()` with no\n // `terminal` predicate. Returned as a never-resolving promise.\n return new Promise<S>(() => {});\n },\n getState(): S {\n // Lie if no initial state was provided. The caller's contract is\n // \"you accept the lie because you know replay never invokes\n // interpret and therefore never reads this.\" When the caller DOES\n // pass an initialState the lie collapses to truth.\n return initialState as S;\n },\n subscribe(_listener: () => void): () => void {\n return noopUnsubscribe;\n },\n observe(_observer: (msg: M, state: S) => void): () => void {\n return noopUnsubscribe;\n },\n onBoot(_handler: (state: S) => void): () => void {\n // Boot never fires on the inert runtime — it is \"already booted\" with no\n // initial fanout, so the handler is registered and never called (#47).\n return noopUnsubscribe;\n },\n on<K extends never>(\n _type: K,\n _handler: (event: never) => void,\n ): () => void {\n // The no-op runtime's event surface is `never` (no `events` projector),\n // so `on` is structurally present but uncallable — there is no `K` to\n // pass. The body is unreachable; it returns the shared no-op unsubscribe.\n return noopUnsubscribe;\n },\n subscribePort<T>(\n _port: Port<T>,\n _listener: (value: T) => void,\n ): () => void {\n return noopUnsubscribe;\n },\n emitPort<T>(_port: Port<T>, _value: T): void {\n // No-op. No subscribers to fan out to; nothing to do.\n },\n // Assigned just below — `ready` must resolve to `runtime` itself, which\n // can't be referenced inside its own object literal.\n ready: undefined as unknown as Promise<Runtime<S, M>>,\n async stop(): Promise<void> {\n // Resolves immediately. No in-flight tail to drain, no subs to clean.\n },\n };\n // Already \"booted\" — `ready` hands back the same runtime synchronously.\n runtime.ready = Promise.resolve(runtime);\n return runtime;\n}\n","// ---------------------------------------------------------------------------\n// stateFactory — typed phase-constructor API for a discriminated-union State.\n//\n// The motivating callsite: every reducer test today opens with ~60–80 lines\n// of hand-rolled factory functions, one per phase, each one's defaults\n// duplicated across files:\n//\n// function idleState() { return { type: \"idle\" }; }\n// function creatingWindowState(overrides?: Partial<...>) {\n// return { type: \"creating_window\", auditId: ..., wsUrl: ..., ...overrides };\n// }\n// ...\n//\n// `stateFactory<BackgroundState>({...})` collapses the whole block to one\n// declaration: pass each phase's REQUIRED defaults once (the type system\n// enforces every phase has an entry), and the helper builds an API of\n// `factory.idle()`, `factory.creating_window({...})`, etc.\n//\n// Two type-level guarantees:\n//\n// 1. Every phase has a defaults entry — `StateFactoryDefaults<S>` is a\n// mapped type keyed by every `S[\"type\"]`. Missing an entry is a\n// compile error (the @ts-expect-error in testing.spec.ts pins this).\n//\n// 2. Each phase's defaults match that phase's required shape minus the\n// `type` discriminator. Passing wrong fields fails to compile.\n//\n// The runtime is pure data:\n//\n// api[k] = (overrides) => ({\n// type: k,\n// ...defaults[k],\n// ...stripUndefined(overrides ?? {}),\n// })\n//\n// The `stripUndefined` pass is what preserves `exactOptionalPropertyTypes`\n// assignability — without it, calling `factory.creating_window({\n// queueItemId: undefined })` would land an explicit `queueItemId: undefined`\n// on the object, which is NOT assignable to `queueItemId?: string` under\n// `exactOptionalPropertyTypes`. Stripping undefineds before spread keeps\n// the boundary clean.\n//\n// `stripUndefined` is inlined (not imported from a shared stdlib) so this\n// package can stay dep-free of stdlib.\n// ---------------------------------------------------------------------------\n\n/** A factory-constructed phase: `S` narrowed to the arm whose discriminator equals `K`. */\ntype StateOf<S, K> = Extract<S, { type: K }>;\n\n/** A factory-constructed phase with the `type` discriminator removed — the \"fields the caller cares about\" half. */\ntype RequiredFields<T> = Omit<T, \"type\">;\n\n/**\n * Defaults shape passed to `stateFactory`. Mapped type over the discriminant\n * set: every phase MUST have an entry, and that entry MUST match the phase's\n * required fields minus the `type` discriminator.\n */\nexport type StateFactoryDefaults<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: RequiredFields<StateOf<S, K>>;\n};\n\n/**\n * The API returned by `stateFactory`. One method per phase; calling it with\n * no args returns the default snapshot, calling it with a partial overrides\n * object merges those overrides in (with undefineds stripped — see\n * stripUndefined below).\n */\nexport type StateFactoryAPI<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: (\n overrides?: Partial<RequiredFields<StateOf<S, K>>>,\n ) => StateOf<S, K>;\n};\n\n/**\n * Strip own-enumerable properties whose value is `undefined`. Used inside\n * the factory's spread chain so callers can pass `{ queueItemId: undefined }`\n * without the explicit-undefined landing on the result object (which would\n * fail to assign to a `queueItemId?: string` field under\n * `exactOptionalPropertyTypes`).\n *\n * Inlined (not imported from `a shared stdlib`) so `@demlik/tea/testing` can stay\n * dependency-free of stdlib. The 8-line cost of duplication is cheaper than\n * a package-graph edge.\n */\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const key of Object.keys(obj) as (keyof T)[]) {\n const value = obj[key];\n if (value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Build a typed phase-constructor API from per-phase defaults. The defaults\n * object MUST have one entry per `S[\"type\"]` discriminant; the returned API\n * has one method per phase. Each method merges the defaults with optional\n * overrides (undefineds stripped), then stamps the `type` discriminator.\n *\n * @example\n * type S = { type: \"a\"; n: number } | { type: \"b\"; s: string };\n * const f = stateFactory<S>({ a: { n: 0 }, b: { s: \"\" } });\n * f.a(); // { type: \"a\", n: 0 }\n * f.a({ n: 42 }); // { type: \"a\", n: 42 }\n * f.b({ s: \"hi\" }); // { type: \"b\", s: \"hi\" }\n */\nexport function stateFactory<S extends { type: string }>(\n defaults: StateFactoryDefaults<S>,\n): StateFactoryAPI<S> {\n // Build the API by walking the defaults' own keys. Each key corresponds\n // to one phase discriminator; the constructor closes over both the key\n // (for the `type` stamp) and that phase's defaults snapshot.\n const api = {} as { [k: string]: (overrides?: object) => unknown };\n for (const key of Object.keys(\n defaults,\n ) as (keyof StateFactoryDefaults<S>)[]) {\n const phaseDefaults = defaults[key];\n api[key as string] = (overrides?: object) => {\n // The strip-undefined pass lives here, not at the caller. See the\n // module header for the `exactOptionalPropertyTypes` rationale.\n const cleanedOverrides = overrides ? stripUndefined(overrides) : {};\n return { type: key, ...phaseDefaults, ...cleanedOverrides };\n };\n }\n return api as StateFactoryAPI<S>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/assertions.ts","../../src/testing/expect-replay-deterministic.ts","../../src/testing/noop-runtime.ts","../../src/testing/state-factory.ts"],"names":["expect"],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Test-side assertions over `replay`. Each helper composes the replay call\n// with a single `expect(...)` so consumers stop hand-rolling the 5-line\n// \"destructure { state, cmds } → expect.toBe / toEqual / toContainEqual\"\n// boilerplate at every test site.\n//\n// Each helper is a thin wrapper that:\n// 1. Calls `replay(machine, opts)` once.\n// 2. Pipes the result into a single vitest `expect(...)` matcher.\n//\n// All helpers return `void` — assertions throw on failure (vitest contract).\n// No mutation of the machine or opts. Pure test ergonomics.\n//\n// Type-inference note: each helper anchors its generic parameters to the\n// `machine` argument. The `expected` (and `opts.msgs`) arguments are wrapped\n// in `NoInfer<...>` so they do NOT participate in inference — otherwise a\n// `[]`-shaped `expected` would force `C = never` and reject a machine whose\n// real Cmd union is non-empty. Anchoring inference to the machine is the\n// same discipline `replay` uses (it has nothing but the machine and opts).\n//\n// Strengthens invariant 9 (the testing surface is named, small, and forks\n// nothing of the substrate's purity guarantees).\n// ---------------------------------------------------------------------------\n\nimport { expect } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\n\n// `NoInfer<T>` is the built-in TypeScript 5.4+ intrinsic that blocks\n// inference from a generic parameter at a position. We use it on `expected`\n// and `opts` so inference flows purely FROM the machine — `[]`-shaped\n// `expected` no longer drags `C` down to `never` and rejects the machine.\n\n/**\n * Shared options shape for every test assertion below. Mirrors the\n * second argument of `replay(machine, opts)` — `msgs` is the sequence to\n * fold over (may be empty for init-only assertions), `ctx` is the\n * substrate-required ctx value, `loaded` is the optional pre-`init`\n * snapshot (omit / pass `null` for fresh boot).\n */\nexport interface ReplayOpts<S, M, Ctx> {\n readonly msgs: readonly M[];\n readonly ctx: Ctx;\n readonly loaded?: S | null;\n}\n\n/**\n * Assert the final state after replaying `opts.msgs` equals `expected`.\n * Replaces the boilerplate:\n *\n * const { state } = replay(machine, opts);\n * expect(state).toEqual(expected);\n *\n * Uses `toEqual` (deep structural). For type-narrowing assertions that\n * branch on `state.type` afterwards, fall back to plain `replay` —\n * `expectFinalState` is the \"exact match\" tool.\n */\nexport function expectFinalState<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<S>,\n): void {\n const { state } = replay(machine, opts);\n expect(state).toEqual(expected);\n}\n\n/**\n * Assert that `cmd` appears at least once in the cmds array produced by\n * replaying `opts.msgs`. Uses `toContainEqual` (deep structural). Order\n * and surrounding cmds are not asserted — use `expectCmdSequence` for\n * exact ordered match.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toContainEqual(cmd);\n */\nexport function expectCmdEmitted<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n cmd: NoInfer<C>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toContainEqual(cmd);\n}\n\n/**\n * Assert the exact ordered sequence of cmds emitted by replaying\n * `opts.msgs`. Uses `toEqual` against the full cmds array — extra cmds\n * fail the assertion, missing cmds fail the assertion, wrong order\n * fails the assertion.\n *\n * Replaces:\n *\n * const { cmds } = replay(machine, opts);\n * expect(cmds).toEqual(expected);\n */\nexport function expectCmdSequence<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly C[]>,\n): void {\n const { cmds } = replay(machine, opts);\n expect(cmds).toEqual(expected);\n}\n\n/**\n * Assert the exact set of subs desired at the final state after\n * replaying `opts.msgs`. Uses `toEqual` against the full subs array.\n *\n * Replaces:\n *\n * const { subs } = replay(machine, opts);\n * expect(subs).toEqual(expected);\n */\nexport function expectActiveSubs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n expected: NoInfer<readonly U[]>,\n): void {\n const { subs } = replay(machine, opts);\n expect(subs).toEqual(expected);\n}\n\n/**\n * Single-msg step helper — feeds `loaded → msg → next state + cmds emitted by\n * that msg`. Returns a readonly tuple in the shape consumers used before the\n * substrate (`[state, cmds]`), so existing tests that destructure with\n * `[next, cmds]` can swap `reduce(state, msg)` for `step(machine, state, msg,\n * ctx)` without changing the assertion code.\n *\n * Internally a `replay` call with `loaded: prevState` and a single-element\n * `msgs` array. The cmds returned are exactly the cmds emitted by THIS msg\n * (init emits no cmds when `loaded` is provided, so the slice is the entire\n * array).\n *\n * Use when consumers want to walk a machine one msg at a time and assert per\n * step. For one-shot \"final state after N msgs\" assertions, prefer\n * `expectFinalState`.\n */\nexport function step<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n loaded: NoInfer<S>,\n msg: NoInfer<M>,\n ctx: NoInfer<Ctx>,\n): readonly [S, readonly C[]] {\n const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });\n return [state, cmds] as const;\n}\n","import { expect, vi } from \"vitest\";\nimport { type Cmd, type Machine, replay, type Sub } from \"../index\";\nimport type { ReplayOpts } from \"./assertions\";\n\ninterface Ambient {\n readonly now: number;\n readonly rng: number;\n}\n\nconst AMBIENT_A: Ambient = { now: 1_000_000, rng: 0.123 };\nconst AMBIENT_B: Ambient = { now: 9_876_543, rng: 0.987 };\n\n/**\n * Assert that replaying `opts.msgs` is a pure function of the Msg log: the\n * final state and every emitted Cmd come out the same under two different\n * global wall-clocks and RNG seeds.\n *\n * `init` and `update` never receive a clock, so a cell that reads\n * `Date.now()` or `Math.random()` reaches for the global one, and a replay of\n * the same log on another day or another host lands somewhere else. Two\n * back-to-back replays under one clock would hide that; this helper stubs\n * two distinct ambients and compares. Time belongs in the Msg (an `at`\n * field), where it is identical across replays.\n *\n * Subs are not compared: they are derived from the final state, so a state\n * that matches yields the same Subs.\n */\nexport function expectReplayDeterministic<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: NoInfer<ReplayOpts<S, M, Ctx>>,\n): void {\n const a = underAmbient(AMBIENT_A, () => replay(machine, opts));\n const b = underAmbient(AMBIENT_B, () => replay(machine, opts));\n expect(\n b.state,\n \"final state changed under a different clock/RNG: init or update reads \" +\n \"Date.now()/Math.random(); time must enter as Msg data\",\n ).toEqual(a.state);\n expect(\n b.cmds,\n \"emitted Cmds changed under a different clock/RNG: a Cmd payload reads \" +\n \"Date.now()/Math.random(); time must enter as Msg data\",\n ).toEqual(a.cmds);\n}\n\n/** Run `fn` with the global `Date.now` and `Math.random` pinned, then restore them. */\nfunction underAmbient<T>(ambient: Ambient, fn: () => T): T {\n const nowSpy = vi.spyOn(Date, \"now\").mockReturnValue(ambient.now);\n const rngSpy = vi.spyOn(Math, \"random\").mockReturnValue(ambient.rng);\n try {\n return fn();\n } finally {\n nowSpy.mockRestore();\n rngSpy.mockRestore();\n }\n}\n","// ---------------------------------------------------------------------------\n// noopRuntime — a Runtime<S, M>-shaped value whose every method is inert.\n//\n// The motivating callsite: a machine's `ctx` requires a sibling Runtime<S,\n// M> reference (e.g. the queue runtime in extension/lib/queue-machine\n// reaches into an `auditRuntime: Runtime<...>` slot on its ctx so its\n// interpret handlers can dispatch into the audit machine). When the test\n// replays such a machine, `replay` never actually invokes `interpret`, so\n// the runtime reference is never dereferenced — but the type system\n// still requires a value of `Runtime<S, M>` shape at the ctx slot. Tests\n// today work around this with a hand-rolled 12-line NOOP_AUDIT_RUNTIME\n// const cast through `as unknown as Runtime<...>`.\n//\n// `noopRuntime<S, M>()` is that hand-roll, named once. It returns a real\n// Runtime<S, M> value whose every method is a structurally-correct no-op:\n//\n// - `dispatch`: resolves immediately, never advances state.\n// - `getState`: returns the optional `initialState` argument (or a\n// deliberate `undefined` cast as S — the function lies about its\n// return type when no initial state is provided; this is the trade-off\n// we accept for an ergonomic test substrate).\n// - `subscribe` / `observe` / `subscribePort`: register the listener,\n// return a no-op unsubscribe. The listener is never fired because the\n// state never changes.\n// - `emitPort`: no-op.\n// - `ready`: a Promise that resolves to the no-op runtime itself (the #45\n// `ready: Promise<Runtime<S, M>>` shape — already booted, nothing to wait\n// for).\n// - `stop`: resolved Promise<void>.\n//\n// Strengthens invariant 9 (the testing surface is named and small).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime } from \"../index\";\n\n/**\n * Construct an inert `Runtime<S, M>` value. Use at ctx sites that require\n * a Runtime reference but where the test never invokes `interpret` (so\n * the runtime is never dereferenced in practice).\n *\n * @param opts.initialState Optional value returned by `getState()`. Omit\n * when the test never calls `getState()` on the no-op runtime — most\n * tests only need the type-level slot filled.\n */\nexport function noopRuntime<S, M extends { type: string }>(opts?: {\n initialState?: S;\n}): Runtime<S, M> {\n const initialState = opts?.initialState;\n // Single source of truth for the \"do-nothing\" unsubscribe so multiple\n // subscribe call sites share the same function instance — purely cosmetic\n // (the function would be a no-op either way).\n const noopUnsubscribe = (): void => {};\n const runtime: Runtime<S, M> = {\n async dispatch(_msg: M): Promise<void> {\n // Resolves immediately. State never advances; listeners never fire.\n // Quiescent by default (#50), but the no-op has no follow-up chain to\n // drain, so single-step and run-to-quiescence collapse to the same no-op.\n },\n async dispatchOnce(_msg: M): Promise<void> {\n // Single-step sibling of `dispatch` (#50). Same no-op for the no-op\n // runtime — there is no tail to leave in flight.\n },\n async idle(): Promise<void> {\n // No tail to drain — the no-op runtime never enqueues a follow-up.\n },\n result(): S | undefined {\n // The no-op runtime never advances, so it is never terminal (#46).\n return undefined;\n },\n done(): Promise<S> {\n // Never terminal → the awaitable never settles, matching `run()` with no\n // `terminal` predicate. Returned as a never-resolving promise.\n return new Promise<S>(() => {});\n },\n getState(): S {\n // Lie if no initial state was provided. The caller's contract is\n // \"you accept the lie because you know replay never invokes\n // interpret and therefore never reads this.\" When the caller DOES\n // pass an initialState the lie collapses to truth.\n return initialState as S;\n },\n subscribe(_listener: () => void): () => void {\n return noopUnsubscribe;\n },\n observe(_observer: (msg: M, state: S) => void): () => void {\n return noopUnsubscribe;\n },\n onBoot(_handler: (state: S) => void): () => void {\n // Boot never fires on the inert runtime — it is \"already booted\" with no\n // initial fanout, so the handler is registered and never called (#47).\n return noopUnsubscribe;\n },\n on<K extends never>(\n _type: K,\n _handler: (event: never) => void,\n ): () => void {\n // The no-op runtime's event surface is `never` (no `events` projector),\n // so `on` is structurally present but uncallable — there is no `K` to\n // pass. The body is unreachable; it returns the shared no-op unsubscribe.\n return noopUnsubscribe;\n },\n subscribePort<T>(\n _port: Port<T>,\n _listener: (value: T) => void,\n ): () => void {\n return noopUnsubscribe;\n },\n emitPort<T>(_port: Port<T>, _value: T): void {\n // No-op. No subscribers to fan out to; nothing to do.\n },\n // Assigned just below — `ready` must resolve to `runtime` itself, which\n // can't be referenced inside its own object literal.\n ready: undefined as unknown as Promise<Runtime<S, M>>,\n async stop(): Promise<void> {\n // Resolves immediately. No in-flight tail to drain, no subs to clean.\n },\n };\n // Already \"booted\" — `ready` hands back the same runtime synchronously.\n runtime.ready = Promise.resolve(runtime);\n return runtime;\n}\n","// ---------------------------------------------------------------------------\n// stateFactory — typed phase-constructor API for a discriminated-union State.\n//\n// The motivating callsite: every reducer test today opens with ~60–80 lines\n// of hand-rolled factory functions, one per phase, each one's defaults\n// duplicated across files:\n//\n// function idleState() { return { type: \"idle\" }; }\n// function creatingWindowState(overrides?: Partial<...>) {\n// return { type: \"creating_window\", auditId: ..., wsUrl: ..., ...overrides };\n// }\n// ...\n//\n// `stateFactory<BackgroundState>({...})` collapses the whole block to one\n// declaration: pass each phase's REQUIRED defaults once (the type system\n// enforces every phase has an entry), and the helper builds an API of\n// `factory.idle()`, `factory.creating_window({...})`, etc.\n//\n// Two type-level guarantees:\n//\n// 1. Every phase has a defaults entry — `StateFactoryDefaults<S>` is a\n// mapped type keyed by every `S[\"type\"]`. Missing an entry is a\n// compile error (the @ts-expect-error in testing.spec.ts pins this).\n//\n// 2. Each phase's defaults match that phase's required shape minus the\n// `type` discriminator. Passing wrong fields fails to compile.\n//\n// The runtime is pure data:\n//\n// api[k] = (overrides) => ({\n// type: k,\n// ...defaults[k],\n// ...stripUndefined(overrides ?? {}),\n// })\n//\n// The `stripUndefined` pass is what preserves `exactOptionalPropertyTypes`\n// assignability — without it, calling `factory.creating_window({\n// queueItemId: undefined })` would land an explicit `queueItemId: undefined`\n// on the object, which is NOT assignable to `queueItemId?: string` under\n// `exactOptionalPropertyTypes`. Stripping undefineds before spread keeps\n// the boundary clean.\n//\n// `stripUndefined` is inlined (not imported from a shared stdlib) so this\n// package can stay dep-free of stdlib.\n// ---------------------------------------------------------------------------\n\n/** A factory-constructed phase: `S` narrowed to the arm whose discriminator equals `K`. */\ntype StateOf<S, K> = Extract<S, { type: K }>;\n\n/** A factory-constructed phase with the `type` discriminator removed — the \"fields the caller cares about\" half. */\ntype RequiredFields<T> = Omit<T, \"type\">;\n\n/**\n * Defaults shape passed to `stateFactory`. Mapped type over the discriminant\n * set: every phase MUST have an entry, and that entry MUST match the phase's\n * required fields minus the `type` discriminator.\n */\nexport type StateFactoryDefaults<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: RequiredFields<StateOf<S, K>>;\n};\n\n/**\n * The API returned by `stateFactory`. One method per phase; calling it with\n * no args returns the default snapshot, calling it with a partial overrides\n * object merges those overrides in (with undefineds stripped — see\n * stripUndefined below).\n */\nexport type StateFactoryAPI<S extends { type: string }> = {\n readonly [K in S[\"type\"]]: (\n overrides?: Partial<RequiredFields<StateOf<S, K>>>,\n ) => StateOf<S, K>;\n};\n\n/**\n * Strip own-enumerable properties whose value is `undefined`. Used inside\n * the factory's spread chain so callers can pass `{ queueItemId: undefined }`\n * without the explicit-undefined landing on the result object (which would\n * fail to assign to a `queueItemId?: string` field under\n * `exactOptionalPropertyTypes`).\n *\n * Inlined (not imported from `a shared stdlib`) so `@demlik/tea/testing` can stay\n * dependency-free of stdlib. The 8-line cost of duplication is cheaper than\n * a package-graph edge.\n */\nfunction stripUndefined<T extends object>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const key of Object.keys(obj) as (keyof T)[]) {\n const value = obj[key];\n if (value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Build a typed phase-constructor API from per-phase defaults. The defaults\n * object MUST have one entry per `S[\"type\"]` discriminant; the returned API\n * has one method per phase. Each method merges the defaults with optional\n * overrides (undefineds stripped), then stamps the `type` discriminator.\n *\n * @example\n * ```ts\n * import { stateFactory } from \"@demlik/tea/testing\";\n *\n * type S = { type: \"a\"; n: number } | { type: \"b\"; s: string };\n * const f = stateFactory<S>({ a: { n: 0 }, b: { s: \"\" } });\n * f.a(); // { type: \"a\", n: 0 }\n * f.a({ n: 42 }); // { type: \"a\", n: 42 }\n * f.b({ s: \"hi\" }); // { type: \"b\", s: \"hi\" }\n * ```\n */\nexport function stateFactory<S extends { type: string }>(\n defaults: StateFactoryDefaults<S>,\n): StateFactoryAPI<S> {\n // Build the API by walking the defaults' own keys. Each key corresponds\n // to one phase discriminator; the constructor closes over both the key\n // (for the `type` stamp) and that phase's defaults snapshot.\n const api = {} as { [k: string]: (overrides?: object) => unknown };\n for (const key of Object.keys(\n defaults,\n ) as (keyof StateFactoryDefaults<S>)[]) {\n const phaseDefaults = defaults[key];\n api[key as string] = (overrides?: object) => {\n // The strip-undefined pass lives here, not at the caller. See the\n // module header for the `exactOptionalPropertyTypes` rationale.\n const cleanedOverrides = overrides ? stripUndefined(overrides) : {};\n return { type: key, ...phaseDefaults, ...cleanedOverrides };\n };\n }\n return api as StateFactoryAPI<S>;\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { b as DriveOptions, c as DriveResult } from '../../drive-loop-CsUsW3Wo.js';
|
|
2
|
+
export { d as DEFAULT_MAX_ROUNDS, e as DriveCtxArg, a as DriveNoHandlerError, D as DriveRoundsExceededError, f as DriveTraceEntry, g as driveTraceOf } from '../../drive-loop-CsUsW3Wo.js';
|
|
3
|
+
import { C as Cmd, c as Sub, M as Machine, I as Interpret } from '../../core-Ce9ncpuv.js';
|
|
4
|
+
import '../../runtime-types-BKg2CGMx.js';
|
|
5
|
+
import '@standard-schema/spec';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Drive `machine` from `initial` through `msg` against the REAL interpret
|
|
9
|
+
* `handlers`, feeding every settle Msg back until the machine goes quiet, and
|
|
10
|
+
* hand back the settled state together with the whole history.
|
|
11
|
+
*
|
|
12
|
+
* `handlers` is the same table a host hands `run` as its `interpret` (e.g.
|
|
13
|
+
* a jev knob's `resilient_run` handler), so the test drives the real interpreter and
|
|
14
|
+
* mocks only the port beneath it.
|
|
15
|
+
*
|
|
16
|
+
* One round is: fold every pending Msg, then await a handler for every Cmd
|
|
17
|
+
* those folds emitted. A Msg a handler returns — or fires through its injected
|
|
18
|
+
* `dispatch` — is pending for the next round. A machine that emits no Cmd for
|
|
19
|
+
* its seed Msg therefore settles in ONE round and never awaits a handler at
|
|
20
|
+
* all. Subs are not run.
|
|
21
|
+
*
|
|
22
|
+
* A `Cmd.define`d Cmd's handler is held to the engine's contract (ADR 0021):
|
|
23
|
+
* its outcome is minted into `_ok` / `_err`, and a `_ok` / `_err` it builds and
|
|
24
|
+
* dispatches itself is refused, failing the drive with `OutcomeContractError`
|
|
25
|
+
* exactly where `run` would report it (#304). A settle it minted through
|
|
26
|
+
* `cmdEdgeOf(ctx)` passes.
|
|
27
|
+
*
|
|
28
|
+
* The `ctx` handed to each handler is `opts.ctx` augmented with a NO-OP `emit`,
|
|
29
|
+
* so a handler that emits to a `Port` runs without a live runtime; nothing here
|
|
30
|
+
* observes ports. Supply your own `emit` on `ctx` to capture them.
|
|
31
|
+
*
|
|
32
|
+
* @throws {DriveRoundsExceededError} when the machine is still emitting work
|
|
33
|
+
* after `opts.maxRounds` rounds. It never returns a half-driven state.
|
|
34
|
+
* @throws whatever a handler rejects with, unchanged — annotated with the
|
|
35
|
+
* partial trace, readable via `driveTraceOf`.
|
|
36
|
+
*/
|
|
37
|
+
declare function drive<S, M extends {
|
|
38
|
+
type: string;
|
|
39
|
+
}, 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>>;
|
|
40
|
+
|
|
41
|
+
export { DriveOptions, DriveResult, drive };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { driveContractOf, driveLoop, DEFAULT_MAX_ROUNDS } from '../../chunk-H3MWNH7A.js';
|
|
2
|
+
export { DEFAULT_MAX_ROUNDS, DriveNoHandlerError, DriveRoundsExceededError, driveTraceOf } from '../../chunk-H3MWNH7A.js';
|
|
3
|
+
import '../../chunk-JVKU2UQC.js';
|
|
4
|
+
import '../../chunk-FNGZMVIT.js';
|
|
5
|
+
import { cmdEdge, Outcome } from '../../chunk-AIIXV4XB.js';
|
|
6
|
+
|
|
7
|
+
// src/testing/promise/drive.ts
|
|
8
|
+
async function drive(machine, initial, msg, handlers, ...[opts]) {
|
|
9
|
+
const options = opts ?? {};
|
|
10
|
+
const contract = driveContractOf(machine, options.clock ?? Date.now);
|
|
11
|
+
const noopEmit = { emit: () => {
|
|
12
|
+
} };
|
|
13
|
+
const handlerCtx = {
|
|
14
|
+
...noopEmit,
|
|
15
|
+
ok: Outcome.ok,
|
|
16
|
+
err: Outcome.err,
|
|
17
|
+
[cmdEdge]: contract.settle,
|
|
18
|
+
...options.ctx
|
|
19
|
+
};
|
|
20
|
+
const cells = handlers;
|
|
21
|
+
const cellFor = (type) => {
|
|
22
|
+
const cell = cells[type];
|
|
23
|
+
return cell && ((cmd, dispatch) => cell(cmd, handlerCtx, dispatch));
|
|
24
|
+
};
|
|
25
|
+
return driveLoop(
|
|
26
|
+
machine,
|
|
27
|
+
initial,
|
|
28
|
+
msg,
|
|
29
|
+
options.ctx,
|
|
30
|
+
options.maxRounds ?? DEFAULT_MAX_ROUNDS,
|
|
31
|
+
{ contract, cellFor }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { drive };
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
37
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/testing/promise/drive.ts"],"names":[],"mappings":";;;;;;;AA+DA,eAAsB,MAOpB,OAAA,EACA,OAAA,EACA,KACA,QAAA,EAAA,GACG,CAAC,IAAI,CAAA,EAGuB;AAC/B,EAAA,MAAM,OAAA,GAAW,QAAQ,EAAC;AAK1B,EAAA,MAAM,WAAW,eAAA,CAAgB,OAAA,EAAS,OAAA,CAAQ,KAAA,IAAS,KAAK,GAAG,CAAA;AAMnE,EAAA,MAAM,QAAA,GAAwB,EAAE,IAAA,EAAM,MAAM;AAAA,EAAC,CAAA,EAAE;AAC/C,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,GAAG,QAAA;AAAA,IACH,IAAI,OAAA,CAAQ,EAAA;AAAA,IACZ,KAAK,OAAA,CAAQ,GAAA;AAAA,IACb,CAAC,OAAO,GAAG,QAAA,CAAS,MAAA;AAAA,IACpB,GAAI,OAAA,CAAQ;AAAA,GACd;AAKA,EAAA,MAAM,KAAA,GAAQ,QAAA;AAKd,EAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KAA8C;AAC7D,IAAA,MAAM,IAAA,GAAO,MAAM,IAAI,CAAA;AACvB,IAAA,OAAO,SAAS,CAAC,GAAA,EAAK,aAAa,IAAA,CAAK,GAAA,EAAK,YAAY,QAAQ,CAAA,CAAA;AAAA,EACnE,CAAA;AAEA,EAAA,OAAO,SAAA;AAAA,IACL,OAAA;AAAA,IACA,OAAA;AAAA,IACA,GAAA;AAAA,IACA,OAAA,CAAQ,GAAA;AAAA,IACR,QAAQ,SAAA,IAAa,kBAAA;AAAA,IACrB,EAAE,UAAU,OAAA;AAAQ,GACtB;AACF","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `drive(machine, initial, msg, handlers, opts)` for the Promise engine: the\n// runtime's own Cmd→handler→settle-Msg loop, said once, for a test.\n//\n// The loop is `../drive-loop.ts`, shared with the Effect `drive`. What this\n// file adds is the Promise edge: each handler is awaited with the ctx `run`\n// would hand it, and its `dispatch`. There is no `Runtime` here and no Sub is\n// run; the only impure thing it does is `await` the handler the caller handed\n// it.\n//\n// Prior art converged on the same answer: Temporal's `TestWorkflowEnvironment`\n// hands a test the event history rather than the final result, and Effect's\n// `TestClock` runs make the effect log inspectable rather than opaque.\n// ---------------------------------------------------------------------------\n\nimport {\n type Cmd,\n type Interpret,\n type Machine,\n Outcome,\n type PortEmitter,\n type Sub,\n} from \"../../index\";\nimport { cmdEdge } from \"../../pure/core\";\nimport {\n DEFAULT_MAX_ROUNDS,\n type DriveCell,\n type DriveOptions,\n type DriveResult,\n driveContractOf,\n driveLoop,\n} from \"../drive-loop\";\n\n/**\n * Drive `machine` from `initial` through `msg` against the REAL interpret\n * `handlers`, feeding every settle Msg back until the machine goes quiet, and\n * hand back the settled state together with the whole history.\n *\n * `handlers` is the same table a host hands `run` as its `interpret` (e.g.\n * a jev knob's `resilient_run` handler), so the test drives the real interpreter and\n * mocks only the port beneath it.\n *\n * One round is: fold every pending Msg, then await a handler for every Cmd\n * those folds emitted. A Msg a handler returns — or fires through its injected\n * `dispatch` — is pending for the next round. A machine that emits no Cmd for\n * its seed Msg therefore settles in ONE round and never awaits a handler at\n * all. Subs are not run.\n *\n * A `Cmd.define`d Cmd's handler is held to the engine's contract (ADR 0021):\n * its outcome is minted into `_ok` / `_err`, and a `_ok` / `_err` it builds and\n * dispatches itself is refused, failing the drive with `OutcomeContractError`\n * exactly where `run` would report it (#304). A settle it minted through\n * `cmdEdgeOf(ctx)` passes.\n *\n * The `ctx` handed to each handler is `opts.ctx` augmented with a NO-OP `emit`,\n * so a handler that emits to a `Port` runs without a live runtime; nothing here\n * observes ports. Supply your own `emit` on `ctx` to capture them.\n *\n * @throws {DriveRoundsExceededError} when the machine is still emitting work\n * after `opts.maxRounds` rounds. It never returns a half-driven state.\n * @throws whatever a handler rejects with, unchanged — annotated with the\n * partial trace, readable via `driveTraceOf`.\n */\nexport async function drive<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n initial: S,\n msg: M,\n handlers: Interpret<M, C, Ctx>,\n ...[opts]: Record<never, never> extends DriveOptions<Ctx>\n ? [opts?: DriveOptions<Ctx>]\n : [opts: DriveOptions<Ctx>]\n): Promise<DriveResult<S, M, C>> {\n const options = (opts ?? {}) as {\n ctx?: Ctx;\n maxRounds?: number;\n clock?: () => number;\n };\n const contract = driveContractOf(machine, options.clock ?? Date.now);\n\n // A handler's ctx is `Ctx & PortEmitter`, plus the `ok` / `err` builders and\n // the edge a defined Cmd's handler is handed. The Ctx half is the caller's;\n // the rest is the kernel's, and a driven test has no kernel — so a no-op\n // `emit` stands in, placed FIRST so a caller's own `emit` wins.\n const noopEmit: PortEmitter = { emit: () => {} };\n const handlerCtx = {\n ...noopEmit,\n ok: Outcome.ok,\n err: Outcome.err,\n [cmdEdge]: contract.settle,\n ...(options.ctx as unknown as Record<string, unknown> | undefined),\n };\n\n // Indexed dynamically: `Interpret`'s mapped type already proved every Cmd\n // variant has a cell, but `cmd.type` is a union at the value level and TS\n // cannot correlate the two sides of that index.\n const cells = handlers as unknown as Record<\n string,\n | ((cmd: C, ctx: unknown, dispatch: (msg: M) => void) => Promise<unknown>)\n | undefined\n >;\n const cellFor = (type: string): DriveCell<M, C> | undefined => {\n const cell = cells[type];\n return cell && ((cmd, dispatch) => cell(cmd, handlerCtx, dispatch));\n };\n\n return driveLoop(\n machine,\n initial,\n msg,\n options.ctx as Ctx,\n options.maxRounds ?? DEFAULT_MAX_ROUNDS,\n { contract, cellFor },\n );\n}\n"]}
|
package/dist/timing/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { D as Debounced, d as debounce } from '../index-DHSUmQTR.js';
|
|
2
|
-
import { C as Cmd } from '../core-
|
|
3
|
-
import { T as TtlCache, a as DeadlineSub } from '../index-
|
|
4
|
-
export { D as ThrottledInputSettled, s as subscribeThrottledInput, d as throttledInputSettled } from '../index-
|
|
2
|
+
import { C as Cmd } from '../core-Ce9ncpuv.js';
|
|
3
|
+
import { T as TtlCache, a as DeadlineSub } from '../index-e36uEyl4.js';
|
|
4
|
+
export { D as ThrottledInputSettled, s as subscribeThrottledInput, d as throttledInputSettled } from '../index-e36uEyl4.js';
|
|
5
5
|
import '@standard-schema/spec';
|
|
6
|
-
import '../types-
|
|
6
|
+
import '../types-SzNSHKlV.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* internal/timing/throttle — a timer-based call transformer that caps invocation
|
package/dist/timing/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { debounce } from '../chunk-LFZUK6J2.js';
|
|
2
|
-
import { deadlineSub, initCache, get, set } from '../chunk-
|
|
3
|
-
export { subscribeDeadline as subscribeThrottledInput, deadlineExceeded as throttledInputSettled } from '../chunk-
|
|
4
|
-
import '../chunk-
|
|
5
|
-
import '../chunk-
|
|
2
|
+
import { deadlineSub, initCache, get, set } from '../chunk-C3G2QFEZ.js';
|
|
3
|
+
export { subscribeDeadline as subscribeThrottledInput, deadlineExceeded as throttledInputSettled } from '../chunk-C3G2QFEZ.js';
|
|
4
|
+
import '../chunk-FNGZMVIT.js';
|
|
5
|
+
import '../chunk-AIIXV4XB.js';
|
|
6
6
|
|
|
7
7
|
// src/internal/timing/throttle/index.ts
|
|
8
8
|
function throttle(fn, ms, opts) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Q as QueueItem } from '../index-
|
|
2
|
-
export { E as EnqueueInput, a as QueueItemStatus, c as createQueue } from '../index-
|
|
3
|
-
import '../runtime-types-
|
|
4
|
-
import '../core-
|
|
1
|
+
import { Q as QueueItem } from '../index-DWdg7R1l.js';
|
|
2
|
+
export { E as EnqueueInput, a as QueueItemStatus, c as createQueue } from '../index-DWdg7R1l.js';
|
|
3
|
+
import '../runtime-types-BKg2CGMx.js';
|
|
4
|
+
import '../core-Ce9ncpuv.js';
|
|
5
5
|
import '@standard-schema/spec';
|
|
6
6
|
|
|
7
7
|
/**
|
package/dist/work-queue/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { enqueueOp, claimNextOp, markDoneOp, patchItemOp, resetRunningOp, removeOp } from '../chunk-TXN2KFHF.js';
|
|
2
2
|
export { claimNextOp, enqueueOp, markDoneOp, patchItemOp, queueAdapter, removeOp, resetRunningOp } from '../chunk-TXN2KFHF.js';
|
|
3
|
+
import { restore } from '../chunk-JVE2LBFJ.js';
|
|
4
|
+
import '../chunk-AIIXV4XB.js';
|
|
3
5
|
|
|
4
6
|
// src/internal/work-queue/index.ts
|
|
5
7
|
function genId() {
|
|
6
8
|
return globalThis.crypto?.randomUUID?.() ?? `q_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
7
9
|
}
|
|
8
10
|
function createQueue(store) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return store.migrate(raw);
|
|
11
|
+
function loadParsed() {
|
|
12
|
+
return restore(store);
|
|
12
13
|
}
|
|
13
14
|
async function list() {
|
|
14
15
|
return await loadParsed() ?? [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/internal/work-queue/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * internal/work-queue — substrate-agnostic work-queue lifecycle on `Store<S>`.\n *\n * Two public surfaces, one shape:\n * - `createQueue(store)` (this file) is the in-process adapter — it binds the\n * pure ops to an injected `Store` and runs `load → mutate → save`.\n * - `internal/work-queue/ops` (`./ops`) is the BLESSED, importable surface\n * of those same pure ops. L2 compositions that own their own Model slice\n * (`monitored-run`, `idempotent-intake`, …) import the ops directly and\n * delegate status flips to them instead of re-rolling the transitions.\n * Both routes call the identical ops, so the lifecycle rules live in exactly\n * one place. This mirrors `@demlik/tea/do`'s one-shape-per-package rule.\n *\n * The package is generic in the input payload `I` (audit-specific fields go\n * here in the extension host). `output` is a free-form side-channel correlation\n * token (a backend id, URL, trace id) — typed as `string` because that's its\n * only honest type. Consumers that need structured per-item data persist it in\n * their own store and put the foreign-key string in `output`.\n */\n\nimport type { Store } from \"../../index\";\nimport {\n claimNextOp,\n enqueueOp,\n markDoneOp,\n patchItemOp,\n removeOp,\n resetRunningOp,\n} from \"./ops\";\n\n/**\n * Generate a queue-item id at the adapter boundary. Reads `Date.now()` and\n * `Math.random()` — impure helpers belong in the adapter (`index.ts`),\n * never in `ops.ts` (which is purely-functional by contract). Uses native\n * UUID when available, falls back to timestamp + random for environments\n * without `crypto.randomUUID`.\n */\nfunction genId(): string {\n return (\n globalThis.crypto?.randomUUID?.() ??\n `q_${Date.now()}_${Math.random().toString(36).slice(2)}`\n );\n}\n\nexport type QueueItemStatus =\n | \"pending\"\n | \"running\"\n | \"done\"\n | \"failed\"\n | \"cancelled\";\n\n/**\n * A single queue entry. Every field is `readonly`: a persisted slice is\n * immutable, so a transition returns a NEW item (the ops always spread —\n * `{ ...item, status: … }`) rather than mutating in place. This type-enforces\n * the durability invariant the consumers already honor by convention, and\n * keeps a slice round-trippable through JSON unchanged across a reload.\n */\nexport interface QueueItem<I> {\n readonly id: string;\n readonly status: QueueItemStatus;\n readonly input: I;\n readonly enqueuedAt: number;\n readonly startedAt?: number;\n readonly finishedAt?: number;\n readonly output?: string;\n readonly error?: string;\n}\n\n/**\n * Caller-facing enqueue payload. Just `I` today — kept named so future\n * variants (priority, deferUntil, …) can grow without breaking callers.\n */\nexport type EnqueueInput<I> = I;\n\n/**\n * Adapter that binds the pure ops to an injected `Store<QueueItem<I>[]>`.\n *\n * `load → mutate → save` is the only durability discipline this package\n * imposes. The store decides what \"save\" means (chrome.storage, KV, DO\n * storage, in-memory) — that decision belongs at the caller.\n *\n * Ops that don't change the queue (e.g. `markDone` on an unknown id) skip the\n * save call so the underlying store doesn't observe a write churn.\n */\nexport function createQueue<I>(store: Store<QueueItem<I>[]>) {\n // `Store<S>.load()` returns `unknown` (invariant 8 boundary). Route\n // through `store.migrate(raw)` to get the typed value before any\n // queue operation reads it — same wiring the substrate's `run()` uses at\n // boot. The work queue is a substrate-agnostic primitive on top of\n // `Store<S>`, so it honors the same contract.\n async function loadParsed(): Promise<QueueItem<I>[] | null> {\n const raw = await store.load();\n return store.migrate(raw);\n }\n\n async function list(): Promise<QueueItem<I>[]> {\n return (await loadParsed()) ?? [];\n }\n\n async function enqueue(input: EnqueueInput<I>): Promise<QueueItem<I>> {\n const queue = await list();\n const { next, item } = enqueueOp<I>(queue, input, Date.now(), genId());\n await store.save(next);\n return item;\n }\n\n async function claimNext(): Promise<QueueItem<I> | null> {\n const queue = await list();\n const result = claimNextOp<I>(queue, Date.now());\n if (!result) return null;\n await store.save(result.next);\n return result.claimed;\n }\n\n // `markDone` ignores the optional second arg by design: completed runs leave\n // the queue, and the consumer is expected to persist any output (auditId,\n // etc.) wherever those completed records belong. Kept in the signature for\n // call-site readability and forward-compat with `bindOutput`.\n async function markDone(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = markDoneOp<I>(queue, id);\n if (changed) await store.save(next);\n }\n\n async function markFailed(id: string, error: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"failed\",\n finishedAt: Date.now(),\n error,\n }));\n if (changed) await store.save(next);\n }\n\n async function markCancelled(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"cancelled\",\n finishedAt: Date.now(),\n }));\n if (changed) await store.save(next);\n }\n\n async function resetRunningToPending(): Promise<void> {\n const queue = await list();\n const { next, changed } = resetRunningOp<I>(queue);\n if (changed) await store.save(next);\n }\n\n async function removeItem(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = removeOp<I>(queue, id);\n if (changed) await store.save(next);\n }\n\n async function retryItem(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"pending\",\n startedAt: undefined,\n finishedAt: undefined,\n error: undefined,\n output: undefined,\n }));\n if (changed) await store.save(next);\n }\n\n // `bindOutput` attaches a side-channel correlation token (string) to a\n // running item without changing its status. The queue can't reach into\n // `I` to mutate it (the substrate is domain-agnostic), so callers that\n // need to thread a backend id / URL / trace id through the lifecycle\n // route it here. Consumers needing structured payloads should persist\n // them in their own store and put the foreign-key string in `output`.\n async function bindOutput(id: string, value: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n output: value,\n }));\n if (changed) await store.save(next);\n }\n\n return {\n enqueue,\n claimNext,\n markDone,\n markFailed,\n markCancelled,\n resetRunningToPending,\n removeItem,\n retryItem,\n bindOutput,\n list,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/internal/work-queue/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * internal/work-queue — substrate-agnostic work-queue lifecycle on `Store<S>`.\n *\n * Two public surfaces, one shape:\n * - `createQueue(store)` (this file) is the in-process adapter — it binds the\n * pure ops to an injected `Store` and runs `load → mutate → save`.\n * - `internal/work-queue/ops` (`./ops`) is the BLESSED, importable surface\n * of those same pure ops. L2 compositions that own their own Model slice\n * (`monitored-run`, `idempotent-intake`, …) import the ops directly and\n * delegate status flips to them instead of re-rolling the transitions.\n * Both routes call the identical ops, so the lifecycle rules live in exactly\n * one place. This mirrors `@demlik/tea/do`'s one-shape-per-package rule.\n *\n * The package is generic in the input payload `I` (audit-specific fields go\n * here in the extension host). `output` is a free-form side-channel correlation\n * token (a backend id, URL, trace id) — typed as `string` because that's its\n * only honest type. Consumers that need structured per-item data persist it in\n * their own store and put the foreign-key string in `output`.\n */\n\nimport type { Store } from \"../../index\";\nimport { restore } from \"../engine/restore\";\nimport {\n claimNextOp,\n enqueueOp,\n markDoneOp,\n patchItemOp,\n removeOp,\n resetRunningOp,\n} from \"./ops\";\n\n/**\n * Generate a queue-item id at the adapter boundary. Reads `Date.now()` and\n * `Math.random()` — impure helpers belong in the adapter (`index.ts`),\n * never in `ops.ts` (which is purely-functional by contract). Uses native\n * UUID when available, falls back to timestamp + random for environments\n * without `crypto.randomUUID`.\n */\nfunction genId(): string {\n return (\n globalThis.crypto?.randomUUID?.() ??\n `q_${Date.now()}_${Math.random().toString(36).slice(2)}`\n );\n}\n\nexport type QueueItemStatus =\n | \"pending\"\n | \"running\"\n | \"done\"\n | \"failed\"\n | \"cancelled\";\n\n/**\n * A single queue entry. Every field is `readonly`: a persisted slice is\n * immutable, so a transition returns a NEW item (the ops always spread —\n * `{ ...item, status: … }`) rather than mutating in place. This type-enforces\n * the durability invariant the consumers already honor by convention, and\n * keeps a slice round-trippable through JSON unchanged across a reload.\n */\nexport interface QueueItem<I> {\n readonly id: string;\n readonly status: QueueItemStatus;\n readonly input: I;\n readonly enqueuedAt: number;\n readonly startedAt?: number;\n readonly finishedAt?: number;\n readonly output?: string;\n readonly error?: string;\n}\n\n/**\n * Caller-facing enqueue payload. Just `I` today — kept named so future\n * variants (priority, deferUntil, …) can grow without breaking callers.\n */\nexport type EnqueueInput<I> = I;\n\n/**\n * Adapter that binds the pure ops to an injected `Store<QueueItem<I>[]>`.\n *\n * `load → mutate → save` is the only durability discipline this package\n * imposes. The store decides what \"save\" means (chrome.storage, KV, DO\n * storage, in-memory) — that decision belongs at the caller.\n *\n * Ops that don't change the queue (e.g. `markDone` on an unknown id) skip the\n * save call so the underlying store doesn't observe a write churn.\n */\nexport function createQueue<I>(store: Store<QueueItem<I>[]>) {\n // `Store<S>.load()` returns `unknown` (invariant 8 boundary). Route\n // through `store.migrate(raw)` to get the typed value before any\n // queue operation reads it — the same `restore` the substrate's `run()` uses\n // at boot, so a queue it cannot read throws `StoreRefusedError` and no op\n // saves over it. The work queue is a substrate-agnostic primitive on top of\n // `Store<S>`, so it honors the same contract.\n function loadParsed(): Promise<QueueItem<I>[] | null> {\n return restore(store);\n }\n\n async function list(): Promise<QueueItem<I>[]> {\n return (await loadParsed()) ?? [];\n }\n\n async function enqueue(input: EnqueueInput<I>): Promise<QueueItem<I>> {\n const queue = await list();\n const { next, item } = enqueueOp<I>(queue, input, Date.now(), genId());\n await store.save(next);\n return item;\n }\n\n async function claimNext(): Promise<QueueItem<I> | null> {\n const queue = await list();\n const result = claimNextOp<I>(queue, Date.now());\n if (!result) return null;\n await store.save(result.next);\n return result.claimed;\n }\n\n // `markDone` ignores the optional second arg by design: completed runs leave\n // the queue, and the consumer is expected to persist any output (auditId,\n // etc.) wherever those completed records belong. Kept in the signature for\n // call-site readability and forward-compat with `bindOutput`.\n async function markDone(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = markDoneOp<I>(queue, id);\n if (changed) await store.save(next);\n }\n\n async function markFailed(id: string, error: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"failed\",\n finishedAt: Date.now(),\n error,\n }));\n if (changed) await store.save(next);\n }\n\n async function markCancelled(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"cancelled\",\n finishedAt: Date.now(),\n }));\n if (changed) await store.save(next);\n }\n\n async function resetRunningToPending(): Promise<void> {\n const queue = await list();\n const { next, changed } = resetRunningOp<I>(queue);\n if (changed) await store.save(next);\n }\n\n async function removeItem(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = removeOp<I>(queue, id);\n if (changed) await store.save(next);\n }\n\n async function retryItem(id: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n status: \"pending\",\n startedAt: undefined,\n finishedAt: undefined,\n error: undefined,\n output: undefined,\n }));\n if (changed) await store.save(next);\n }\n\n // `bindOutput` attaches a side-channel correlation token (string) to a\n // running item without changing its status. The queue can't reach into\n // `I` to mutate it (the substrate is domain-agnostic), so callers that\n // need to thread a backend id / URL / trace id through the lifecycle\n // route it here. Consumers needing structured payloads should persist\n // them in their own store and put the foreign-key string in `output`.\n async function bindOutput(id: string, value: string): Promise<void> {\n const queue = await list();\n const { next, changed } = patchItemOp<I>(queue, id, (item) => ({\n ...item,\n output: value,\n }));\n if (changed) await store.save(next);\n }\n\n return {\n enqueue,\n claimNext,\n markDone,\n markFailed,\n markCancelled,\n resetRunningToPending,\n removeItem,\n retryItem,\n bindOutput,\n list,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demlik/tea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "TEA-faithful state machine substrate — one package, every host adapter. Core + subs + testing, plus React, Durable Object, Chrome extension, Node, in-memory, devtools, and property-testing modules as subpaths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tea",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"homepage": "https://demlik.run",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/kamp-us/demlik.git"
|
|
18
|
+
"url": "git+https://github.com/kamp-us/demlik.git",
|
|
19
|
+
"directory": "packages/tea"
|
|
19
20
|
},
|
|
20
21
|
"license": "MIT",
|
|
21
22
|
"author": "Can Sirin",
|
|
@@ -63,6 +64,14 @@
|
|
|
63
64
|
"types": "./dist/testing/index.d.ts",
|
|
64
65
|
"import": "./dist/testing/index.js"
|
|
65
66
|
},
|
|
67
|
+
"./testing/promise": {
|
|
68
|
+
"types": "./dist/testing/promise/index.d.ts",
|
|
69
|
+
"import": "./dist/testing/promise/index.js"
|
|
70
|
+
},
|
|
71
|
+
"./testing/effect": {
|
|
72
|
+
"types": "./dist/testing/effect/index.d.ts",
|
|
73
|
+
"import": "./dist/testing/effect/index.js"
|
|
74
|
+
},
|
|
66
75
|
"./pbt": {
|
|
67
76
|
"types": "./dist/pbt/index.d.ts",
|
|
68
77
|
"import": "./dist/pbt/index.js"
|
|
@@ -119,6 +128,10 @@
|
|
|
119
128
|
"types": "./dist/parity/index.d.ts",
|
|
120
129
|
"import": "./dist/parity/index.js"
|
|
121
130
|
},
|
|
131
|
+
"./otel": {
|
|
132
|
+
"types": "./dist/otel/index.d.ts",
|
|
133
|
+
"import": "./dist/otel/index.js"
|
|
134
|
+
},
|
|
122
135
|
"./devtools/styles.css": "./dist/devtools/styles.css",
|
|
123
136
|
"./package.json": "./package.json"
|
|
124
137
|
},
|
|
@@ -126,6 +139,7 @@
|
|
|
126
139
|
"@standard-schema/spec": "^1.1.0"
|
|
127
140
|
},
|
|
128
141
|
"peerDependencies": {
|
|
142
|
+
"@opentelemetry/api": "^1.9.0",
|
|
129
143
|
"effect": "^4.0.0-rc.0",
|
|
130
144
|
"fast-check": "^3 || ^4",
|
|
131
145
|
"react": "^18 || ^19",
|
|
@@ -134,6 +148,9 @@
|
|
|
134
148
|
"ws": "^8"
|
|
135
149
|
},
|
|
136
150
|
"peerDependenciesMeta": {
|
|
151
|
+
"@opentelemetry/api": {
|
|
152
|
+
"optional": true
|
|
153
|
+
},
|
|
137
154
|
"effect": {
|
|
138
155
|
"optional": true
|
|
139
156
|
},
|
|
@@ -155,19 +172,19 @@
|
|
|
155
172
|
},
|
|
156
173
|
"devDependencies": {
|
|
157
174
|
"@anthropic-ai/sdk": "0.124.0",
|
|
158
|
-
"@biomejs/biome": "2.3.14",
|
|
159
|
-
"@changesets/cli": "^2.27.11",
|
|
160
175
|
"@cloudflare/workers-types": "4.20260312.1",
|
|
161
|
-
"@
|
|
176
|
+
"@langfuse/otel": "5.11.1",
|
|
177
|
+
"@opentelemetry/api": "1.9.1",
|
|
178
|
+
"@opentelemetry/sdk-trace-base": "2.11.0",
|
|
162
179
|
"@types/chrome": "^0.1.42",
|
|
163
180
|
"@types/node": "^22",
|
|
164
181
|
"@types/react": "19.0.8",
|
|
165
182
|
"@types/react-dom": "19.0.3",
|
|
166
183
|
"@types/ws": "8.5.14",
|
|
167
|
-
"ai": "^7.0.93",
|
|
168
184
|
"effect": "4.0.0-rc.117",
|
|
169
185
|
"fast-check": "^3.23.0",
|
|
170
186
|
"happy-dom": "^20.9.0",
|
|
187
|
+
"openai": "7.23.0",
|
|
171
188
|
"react": "19.0.0",
|
|
172
189
|
"react-dom": "19.0.0",
|
|
173
190
|
"tsup": "^8.3.5",
|
|
@@ -175,7 +192,7 @@
|
|
|
175
192
|
"typescript": "5.7.3",
|
|
176
193
|
"vite": "^8.2.1",
|
|
177
194
|
"vitest": "~5.0.1",
|
|
178
|
-
"ws": "8.
|
|
195
|
+
"ws": "^8.21.0",
|
|
179
196
|
"zod": "^4.5.4"
|
|
180
197
|
},
|
|
181
198
|
"publishConfig": {
|
|
@@ -184,9 +201,6 @@
|
|
|
184
201
|
"bugs": {
|
|
185
202
|
"url": "https://github.com/kamp-us/demlik/issues"
|
|
186
203
|
},
|
|
187
|
-
"volta": {
|
|
188
|
-
"node": "23.7.0"
|
|
189
|
-
},
|
|
190
204
|
"scripts": {
|
|
191
205
|
"build": "NODE_OPTIONS=--max-old-space-size=8192 tsup",
|
|
192
206
|
"lint": "biome check src package.json",
|
|
@@ -198,9 +212,7 @@
|
|
|
198
212
|
"typecheck:consumers": "tsc -p tsconfig.consumers.json",
|
|
199
213
|
"docs:reference": "TEA_DOCS_WRITE=1 vitest run src/docs/reference/generate-reference.test.ts",
|
|
200
214
|
"docs:reference:check": "pnpm run docs:reference && git diff --exit-code -- docs/reference",
|
|
201
|
-
"verify
|
|
202
|
-
"verify-optional-peer-install": "node scripts/verify-optional-peer-install.mjs"
|
|
203
|
-
"check:merge-markers": "node scripts/check-merge-markers.mjs",
|
|
204
|
-
"release": "changeset"
|
|
215
|
+
"verify:exports": "node scripts/verify-exports.mjs",
|
|
216
|
+
"verify-optional-peer-install": "node scripts/verify-optional-peer-install.mjs"
|
|
205
217
|
}
|
|
206
218
|
}
|