@demlik/tea 0.20.0 → 0.22.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 +11 -2
- package/dist/agent/index.d.ts +36 -23
- package/dist/agent/index.js +12 -10
- package/dist/{chunk-UHAQ4PI6.js → chunk-2A5XTKQQ.js} +205 -59
- package/dist/chunk-2A5XTKQQ.js.map +1 -0
- package/dist/{chunk-JAKM53JF.js → chunk-7K7SEYCH.js} +16 -15
- package/dist/chunk-7K7SEYCH.js.map +1 -0
- package/dist/{chunk-RKVUVALV.js → chunk-A3QPMX5M.js} +31 -9
- package/dist/chunk-A3QPMX5M.js.map +1 -0
- package/dist/{chunk-DVNXEB7V.js → chunk-BVFS3ICC.js} +3 -3
- package/dist/chunk-BVFS3ICC.js.map +1 -0
- package/dist/{chunk-FTCYY2DO.js → chunk-CPE5ZQXQ.js} +3 -3
- package/dist/{chunk-FTCYY2DO.js.map → chunk-CPE5ZQXQ.js.map} +1 -1
- package/dist/chunk-DEAPNMKO.js +11 -0
- package/dist/chunk-DEAPNMKO.js.map +1 -0
- package/dist/{chunk-XWIE5V7R.js → chunk-ETRNSJCR.js} +4 -4
- package/dist/{chunk-XWIE5V7R.js.map → chunk-ETRNSJCR.js.map} +1 -1
- package/dist/chunk-FXWDDNGD.js +98 -0
- package/dist/chunk-FXWDDNGD.js.map +1 -0
- package/dist/chunk-GFGOQIEC.js +10 -0
- package/dist/chunk-GFGOQIEC.js.map +1 -0
- package/dist/{chunk-2R62Q5UK.js → chunk-IAHEYPYJ.js} +3 -3
- package/dist/{chunk-2R62Q5UK.js.map → chunk-IAHEYPYJ.js.map} +1 -1
- package/dist/{chunk-NGYFBD5G.js → chunk-J5JNLXO7.js} +3 -3
- package/dist/{chunk-NGYFBD5G.js.map → chunk-J5JNLXO7.js.map} +1 -1
- package/dist/{chunk-72CN3VPY.js → chunk-NFOGYDRS.js} +5 -5
- package/dist/{chunk-72CN3VPY.js.map → chunk-NFOGYDRS.js.map} +1 -1
- package/dist/{chunk-A73IJ27X.js → chunk-R2YNDRGL.js} +3 -3
- package/dist/{chunk-A73IJ27X.js.map → chunk-R2YNDRGL.js.map} +1 -1
- package/dist/{chunk-5HVKDHY6.js → chunk-R5NXOPL2.js} +3 -3
- package/dist/{chunk-5HVKDHY6.js.map → chunk-R5NXOPL2.js.map} +1 -1
- package/dist/chunk-RMBZ7YH7.js +52 -0
- package/dist/chunk-RMBZ7YH7.js.map +1 -0
- package/dist/{chunk-AIXC33SE.js → chunk-SHXUSTHM.js} +62 -15
- package/dist/chunk-SHXUSTHM.js.map +1 -0
- package/dist/{chunk-TAKPC6SD.js → chunk-UGTC33VH.js} +4 -4
- package/dist/{chunk-TAKPC6SD.js.map → chunk-UGTC33VH.js.map} +1 -1
- package/dist/{chunk-EA47MI5Q.js → chunk-VH5MYJ2K.js} +4 -4
- package/dist/{chunk-EA47MI5Q.js.map → chunk-VH5MYJ2K.js.map} +1 -1
- package/dist/{chunk-2774I242.js → chunk-WZEDIUWR.js} +21 -20
- package/dist/chunk-WZEDIUWR.js.map +1 -0
- package/dist/{chunk-TGJLXZAU.js → chunk-ZRRET3RP.js} +11 -11
- package/dist/chunk-ZRRET3RP.js.map +1 -0
- package/dist/{core-CtvAyPpH.d.ts → core-DNDAEhO2.d.ts} +9 -12
- package/dist/devtools/index.d.ts +4 -4
- package/dist/do/index.d.ts +103 -38
- package/dist/do/index.js +40 -21
- package/dist/do/index.js.map +1 -1
- package/dist/{drive-loop-Dw1sGSsW.d.ts → drive-loop-DlOEv49c.d.ts} +2 -2
- package/dist/{durable-effects-Ump5lSdO.d.ts → durable-effects-wLWEdy9H.d.ts} +1 -1
- package/dist/effect/index.d.ts +121 -28
- package/dist/effect/index.js +45 -31
- package/dist/effect/index.js.map +1 -1
- package/dist/extension/index.d.ts +3 -3
- package/dist/extension/index.js +2 -2
- package/dist/extension/index.js.map +1 -1
- package/dist/flow/index.d.ts +17 -13
- package/dist/flow/index.js +15 -12
- package/dist/flow/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +3 -3
- package/dist/idempotency/index.js +3 -3
- package/dist/{index-CrcoVfd_.d.ts → index-9P1PaXIV.d.ts} +2 -2
- package/dist/{index-D3NY3VCN.d.ts → index-BBLI2WWN.d.ts} +1 -1
- package/dist/{index-Dl71V7kt.d.ts → index-C1WhBZ6a.d.ts} +4 -5
- package/dist/{index-CKtEKGyp.d.ts → index-C92SgNWz.d.ts} +3 -3
- package/dist/{index-CZZ3HI-Z.d.ts → index-CAsUE_d1.d.ts} +4 -4
- package/dist/{index-EIgfl83J.d.ts → index-CO34esWT.d.ts} +2 -2
- package/dist/{index-Bk2hBUdK.d.ts → index-COeX9Bmz.d.ts} +11 -9
- package/dist/{index-CVU7SK3a.d.ts → index-Cg0QXnzf.d.ts} +2 -2
- package/dist/index.d.ts +9 -9
- package/dist/index.js +2 -2
- package/dist/jev/index.d.ts +15 -15
- package/dist/jev/index.js +5 -5
- package/dist/jev/index.js.map +1 -1
- package/dist/laws-DTtCDDlU.d.ts +135 -0
- package/dist/loop-DReKGV1X.d.ts +70 -0
- package/dist/{machine-Dtre2TmN.d.ts → machine-C0WhOAyB.d.ts} +7 -7
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +4 -3
- package/dist/mem/index.js +2 -2
- package/dist/mem/index.js.map +1 -1
- package/dist/node/index.d.ts +57 -9
- package/dist/node/index.js +175 -68
- package/dist/node/index.js.map +1 -1
- package/dist/otel/index.d.ts +28 -12
- package/dist/otel/index.js +25 -21
- package/dist/otel/index.js.map +1 -1
- package/dist/paginate/index.d.ts +4 -4
- package/dist/paginate/index.js +5 -5
- package/dist/parity/index.d.ts +11 -15
- package/dist/parity/index.js +4 -9
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/index.d.ts +279 -67
- package/dist/pbt/index.js +299 -58
- package/dist/pbt/index.js.map +1 -1
- package/dist/persistence/index.d.ts +10 -12
- package/dist/persistence/index.js +4 -4
- package/dist/promise/index.d.ts +118 -21
- package/dist/promise/index.js +6 -4
- package/dist/react/index.d.ts +34 -21
- package/dist/react/index.js +271 -42
- package/dist/react/index.js.map +1 -1
- package/dist/resilience/index.d.ts +6 -6
- package/dist/resilience/index.js +5 -5
- package/dist/{run-fiBe7pOG.d.ts → run-Cnw4qXj2.d.ts} +105 -23
- package/dist/{runtime-types-DEluQgGO.d.ts → runtime-types-CP-ykwgf.d.ts} +157 -29
- package/dist/testing/effect/index.d.ts +162 -9
- package/dist/testing/effect/index.js +110 -10
- package/dist/testing/effect/index.js.map +1 -1
- package/dist/testing/index.d.ts +6 -8
- package/dist/testing/index.js +4 -24
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/promise/index.d.ts +4 -4
- package/dist/testing/promise/index.js +5 -5
- package/dist/timing/index.d.ts +4 -4
- package/dist/timing/index.js +4 -4
- package/dist/{types-CXNzlbAg.d.ts → types-S53WDy_h.d.ts} +1 -1
- package/dist/work-queue/index.d.ts +4 -4
- package/dist/work-queue/index.js +2 -2
- package/package.json +19 -17
- package/dist/chunk-2774I242.js.map +0 -1
- package/dist/chunk-AIXC33SE.js.map +0 -1
- package/dist/chunk-DVNXEB7V.js.map +0 -1
- package/dist/chunk-HX3C6LDK.js +0 -34
- package/dist/chunk-HX3C6LDK.js.map +0 -1
- package/dist/chunk-JAKM53JF.js.map +0 -1
- package/dist/chunk-RKVUVALV.js.map +0 -1
- package/dist/chunk-TGJLXZAU.js.map +0 -1
- package/dist/chunk-UHAQ4PI6.js.map +0 -1
|
@@ -1,12 +1,165 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import '
|
|
7
|
-
import '
|
|
8
|
-
import '
|
|
1
|
+
export { F as Fold, L as LawBreak, S as Step, f as foldEvents, l as lawInvariant, a as lawTerminates, b as lawTrace } from '../../laws-DTtCDDlU.js';
|
|
2
|
+
import { e as DriveRoundsExceededError, d as DriveNoHandlerError, D as DriveOptions, a as DriveResult } from '../../drive-loop-DlOEv49c.js';
|
|
3
|
+
export { b as DEFAULT_MAX_ROUNDS, c as DriveCtxArg, f as DriveTraceEntry, g as driveTraceOf } from '../../drive-loop-DlOEv49c.js';
|
|
4
|
+
import * as effect_Cause from 'effect/Cause';
|
|
5
|
+
import * as effect_Types from 'effect/Types';
|
|
6
|
+
import { Arbitrary, Result, Effect } from 'effect';
|
|
7
|
+
import { C as Cmd, c as Sub, M as Machine } from '../../core-DNDAEhO2.js';
|
|
8
|
+
import { C as CellErrors, l as SubscribeOption, c as EffectInterpret, h as EffectSubscribe, I as InterpretServices, k as SubscribeServices } from '../../run-Cnw4qXj2.js';
|
|
9
|
+
import '../../runtime-types-CP-ykwgf.js';
|
|
9
10
|
import '@standard-schema/spec';
|
|
11
|
+
import '../../loop-DReKGV1X.js';
|
|
12
|
+
|
|
13
|
+
declare const NoCmdsError_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => effect_Cause.YieldableError & {
|
|
14
|
+
readonly _tag: "NoCmdsError";
|
|
15
|
+
} & Readonly<A>;
|
|
16
|
+
/**
|
|
17
|
+
* The machine has no `cmds` list, so there is no schema to draw an answer from.
|
|
18
|
+
*
|
|
19
|
+
* @experimental
|
|
20
|
+
*/
|
|
21
|
+
declare class NoCmdsError extends NoCmdsError_base<{
|
|
22
|
+
readonly message: string;
|
|
23
|
+
}> {
|
|
24
|
+
}
|
|
25
|
+
declare const NotEffectSchemaError_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => effect_Cause.YieldableError & {
|
|
26
|
+
readonly _tag: "NotEffectSchemaError";
|
|
27
|
+
} & Readonly<A>;
|
|
28
|
+
/**
|
|
29
|
+
* A Cmd's `ok` schema is not an Effect schema, so no answer can be drawn from
|
|
30
|
+
* it. A Cmd made with `Cmd.define` from `@demlik/tea` over zod, or any other
|
|
31
|
+
* Standard Schema, lands here. `cmdType` names that Cmd.
|
|
32
|
+
*
|
|
33
|
+
* @experimental
|
|
34
|
+
*/
|
|
35
|
+
declare class NotEffectSchemaError extends NotEffectSchemaError_base<{
|
|
36
|
+
readonly cmdType: string;
|
|
37
|
+
readonly message: string;
|
|
38
|
+
}> {
|
|
39
|
+
}
|
|
40
|
+
declare const NothingToDrawError_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => effect_Cause.YieldableError & {
|
|
41
|
+
readonly _tag: "NothingToDrawError";
|
|
42
|
+
} & Readonly<A>;
|
|
43
|
+
/**
|
|
44
|
+
* `machine.cmds` is empty and no `outside` Msg was given, so every list drawn
|
|
45
|
+
* would be empty.
|
|
46
|
+
*
|
|
47
|
+
* @experimental
|
|
48
|
+
*/
|
|
49
|
+
declare class NothingToDrawError extends NothingToDrawError_base<{
|
|
50
|
+
readonly message: string;
|
|
51
|
+
}> {
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Why {@link arbSchemaGuidedSequence} drew no lists.
|
|
55
|
+
*
|
|
56
|
+
* @experimental
|
|
57
|
+
*/
|
|
58
|
+
type SchemaAnswersError = NoCmdsError | NotEffectSchemaError | NothingToDrawError;
|
|
59
|
+
/**
|
|
60
|
+
* What {@link arbSchemaGuidedSequence} takes beside the machine and its ctx.
|
|
61
|
+
*
|
|
62
|
+
* @experimental
|
|
63
|
+
*/
|
|
64
|
+
interface SchemaGuidedOptions<S, M extends {
|
|
65
|
+
type: string;
|
|
66
|
+
}> {
|
|
67
|
+
/**
|
|
68
|
+
* The Msgs nothing sends a Cmd for, by Msg type: a user's click, a timer. A
|
|
69
|
+
* Msg type here and in no Cmd's answers is drawn only from here.
|
|
70
|
+
*/
|
|
71
|
+
readonly outside?: {
|
|
72
|
+
readonly [K in M["type"]]?: Arbitrary.Arbitrary<Extract<M, {
|
|
73
|
+
type: K;
|
|
74
|
+
}>>;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Caps the events drawn. Default 60. `checkEffect` and `sampleEffect` cap a
|
|
78
|
+
* list at their `size` too, which defaults to 10, so pass a `size` of at least
|
|
79
|
+
* this to draw lists this long.
|
|
80
|
+
*/
|
|
81
|
+
readonly maxLength?: number;
|
|
82
|
+
/**
|
|
83
|
+
* The saved state the fold starts from. Give `foldEvents` the same one, or it
|
|
84
|
+
* reads the list from a different start. Default `null`.
|
|
85
|
+
*/
|
|
86
|
+
readonly loaded?: S | null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Draw Msg lists for `Arbitrary.checkEffect` in which every answer answers a Cmd
|
|
90
|
+
* that was sent and not yet answered, and comes from that Cmd's own schema: an
|
|
91
|
+
* `ok` answer is drawn from its `schema.ok`, an `err` answer carries one of its
|
|
92
|
+
* `errTags`. Edit a schema and what is drawn changes with it, with no change to
|
|
93
|
+
* the test.
|
|
94
|
+
*
|
|
95
|
+
* Each list is drawn as blind events folded through the machine. An answer event
|
|
96
|
+
* names one waiting Cmd and is minted with that Cmd's def, `def.ok(cmd, value)`
|
|
97
|
+
* or `def.err(cmd, { _tag })`. With nothing waiting the answer is dropped, and an
|
|
98
|
+
* `outside` Msg the event drew goes in its place, or nothing when there is no
|
|
99
|
+
* `outside`. An event whose Msg the state has no cell for is dropped too, and a
|
|
100
|
+
* refused answer leaves its Cmd waiting. A Cmd no def in `machine.cmds` builds is
|
|
101
|
+
* never answered.
|
|
102
|
+
*
|
|
103
|
+
* It runs no check. Fold each list with `foldEvents` and judge it with a law,
|
|
104
|
+
* `lawInvariant`, `lawTerminates` or `lawTrace`, inside `Arbitrary.checkEffect`.
|
|
105
|
+
* A list it finds there shrinks by deleting events and shrinking them, and what
|
|
106
|
+
* is left still folds by the same rule. With `outside` given and every Msg
|
|
107
|
+
* accepted in every state, each event means one Msg, so a shrunk list is never
|
|
108
|
+
* longer than the one first found. A dropped event breaks that: deleting an
|
|
109
|
+
* answer before it can let it back in.
|
|
110
|
+
*
|
|
111
|
+
* It returns a failed `Result`, never an empty arbitrary, on a machine with no
|
|
112
|
+
* `machine.cmds` ({@link NoCmdsError}), on a Cmd whose `ok` schema is not an
|
|
113
|
+
* Effect schema ({@link NotEffectSchemaError}, naming it), and when there is
|
|
114
|
+
* nothing at all to draw ({@link NothingToDrawError}).
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* import type { CmdOf, Machine, Settled } from "@demlik/tea";
|
|
119
|
+
* import { Cmd } from "@demlik/tea/effect";
|
|
120
|
+
* import {
|
|
121
|
+
* arbSchemaGuidedSequence,
|
|
122
|
+
* foldEvents,
|
|
123
|
+
* lawInvariant,
|
|
124
|
+
* } from "@demlik/tea/testing/effect";
|
|
125
|
+
* import { Arbitrary, Effect, Result, Schema } from "effect";
|
|
126
|
+
*
|
|
127
|
+
* const fetchCount = Cmd.define("fetch_count", {
|
|
128
|
+
* input: Schema.Struct({}),
|
|
129
|
+
* ok: Schema.Struct({ n: Schema.Int }),
|
|
130
|
+
* err: ["offline"],
|
|
131
|
+
* });
|
|
132
|
+
* type Msg = { type: "start" } | Settled<typeof fetchCount>;
|
|
133
|
+
* declare const machine: Machine<
|
|
134
|
+
* { readonly total: number },
|
|
135
|
+
* Msg,
|
|
136
|
+
* CmdOf<typeof fetchCount>,
|
|
137
|
+
* never,
|
|
138
|
+
* object
|
|
139
|
+
* >;
|
|
140
|
+
*
|
|
141
|
+
* const lists = Result.getOrThrow(
|
|
142
|
+
* arbSchemaGuidedSequence(machine, {}, {
|
|
143
|
+
* outside: { start: Arbitrary.Constant({ type: "start" as const }) },
|
|
144
|
+
* }),
|
|
145
|
+
* );
|
|
146
|
+
* const result = await Effect.runPromise(
|
|
147
|
+
* Arbitrary.checkEffect(
|
|
148
|
+
* lists,
|
|
149
|
+
* (msgs) =>
|
|
150
|
+
* lawInvariant(foldEvents(machine, {}, null, msgs), ({ next }) =>
|
|
151
|
+
* Number.isFinite(next.total),
|
|
152
|
+
* ) === null,
|
|
153
|
+
* { runs: 500, size: 60 },
|
|
154
|
+
* ),
|
|
155
|
+
* );
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @experimental
|
|
159
|
+
*/
|
|
160
|
+
declare function arbSchemaGuidedSequence<S, M extends {
|
|
161
|
+
type: string;
|
|
162
|
+
}, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, ctx: NoInfer<Ctx>, opts?: SchemaGuidedOptions<NoInfer<S>, M>): Result.Result<Arbitrary.Arbitrary<readonly M[]>, SchemaAnswersError>;
|
|
10
163
|
|
|
11
164
|
/**
|
|
12
165
|
* The Effect `drive`'s options: the Promise `drive`'s, plus `subscribe` — the
|
|
@@ -62,4 +215,4 @@ declare function drive<S, M extends {
|
|
|
62
215
|
type: string;
|
|
63
216
|
}, C extends Cmd, U extends Sub, Ctx, I extends EffectInterpret<M, C> = EffectInterpret<M, C, never>, B extends EffectSubscribe<M, U> = EffectSubscribe<M, U, never>>(machine: Machine<S, M, C, U, Ctx>, initial: S, msg: M, interpret: I, ...[opts]: Record<never, never> extends EffectDriveOptions<U, Ctx, B> ? [opts?: EffectDriveOptions<U, Ctx, B>] : [opts: EffectDriveOptions<U, Ctx, B>]): Effect.Effect<DriveResult<S, M, C>, EffectDriveError<M, C, I>, InterpretServices<I> | SubscribeServices<B>>;
|
|
64
217
|
|
|
65
|
-
export { DriveNoHandlerError, DriveOptions, DriveResult, DriveRoundsExceededError, type EffectDriveError, type EffectDriveOptions, drive };
|
|
218
|
+
export { DriveNoHandlerError, DriveOptions, DriveResult, DriveRoundsExceededError, type EffectDriveError, type EffectDriveOptions, NoCmdsError, NotEffectSchemaError, NothingToDrawError, type SchemaAnswersError, type SchemaGuidedOptions, arbSchemaGuidedSequence, drive };
|
|
@@ -1,13 +1,113 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import '../../chunk-
|
|
5
|
-
|
|
6
|
-
import '../../chunk-
|
|
7
|
-
import '../../chunk-
|
|
8
|
-
import '../../chunk-
|
|
9
|
-
import
|
|
1
|
+
import { resolveGuided, MAX_PICK } from '../../chunk-FXWDDNGD.js';
|
|
2
|
+
export { foldEvents, lawInvariant, lawTerminates, lawTrace } from '../../chunk-FXWDDNGD.js';
|
|
3
|
+
import { CellFailure, builtinRunners, cellResult, settleCellExit } from '../../chunk-SHXUSTHM.js';
|
|
4
|
+
import { driveLoop, DEFAULT_MAX_ROUNDS, driveContractOf, DriveRoundsExceededError, DriveNoHandlerError, annotateTrace, driveTraceOf, desiredSubs } from '../../chunk-ETRNSJCR.js';
|
|
5
|
+
export { DEFAULT_MAX_ROUNDS, DriveNoHandlerError, DriveRoundsExceededError, driveTraceOf } from '../../chunk-ETRNSJCR.js';
|
|
6
|
+
import '../../chunk-RMBZ7YH7.js';
|
|
7
|
+
import '../../chunk-2A5XTKQQ.js';
|
|
8
|
+
import '../../chunk-R2YNDRGL.js';
|
|
9
|
+
import '../../chunk-DEAPNMKO.js';
|
|
10
|
+
import '../../chunk-BVFS3ICC.js';
|
|
11
|
+
import '../../chunk-A3QPMX5M.js';
|
|
12
|
+
import { Data, Result, Arbitrary, Effect, Stream, Exit, Cause, Schema, Fiber } from 'effect';
|
|
10
13
|
|
|
14
|
+
var NoCmdsError = class extends Data.TaggedError("NoCmdsError") {
|
|
15
|
+
};
|
|
16
|
+
var NotEffectSchemaError = class extends Data.TaggedError(
|
|
17
|
+
"NotEffectSchemaError"
|
|
18
|
+
) {
|
|
19
|
+
};
|
|
20
|
+
var NothingToDrawError = class extends Data.TaggedError("NothingToDrawError") {
|
|
21
|
+
};
|
|
22
|
+
var PREFIX = "@demlik/tea/testing/effect: arbSchemaGuidedSequence";
|
|
23
|
+
var upTo = (max) => Arbitrary.schema(
|
|
24
|
+
Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: max }))
|
|
25
|
+
);
|
|
26
|
+
var oneOf = (arms) => arms.length === 1 ? arms[0] : Arbitrary.flatMap(
|
|
27
|
+
upTo(arms.length - 1),
|
|
28
|
+
(at) => arms[at]
|
|
29
|
+
);
|
|
30
|
+
function outcomesOf(def) {
|
|
31
|
+
const ok = def.schema.ok;
|
|
32
|
+
if (!Schema.isSchema(ok)) return null;
|
|
33
|
+
const okArm = Schema.Struct({ outcome: Schema.Literal("ok"), value: ok });
|
|
34
|
+
const outcome = def.errTags.length === 0 ? okArm : Schema.Union([
|
|
35
|
+
okArm,
|
|
36
|
+
Schema.Struct({
|
|
37
|
+
outcome: Schema.Literal("err"),
|
|
38
|
+
tag: Schema.Literals(def.errTags)
|
|
39
|
+
})
|
|
40
|
+
]);
|
|
41
|
+
return Arbitrary.schema(outcome);
|
|
42
|
+
}
|
|
43
|
+
function arbSchemaGuidedSequence(machine, ctx, opts) {
|
|
44
|
+
const defs = machine.cmds;
|
|
45
|
+
if (defs === void 0) {
|
|
46
|
+
return Result.fail(
|
|
47
|
+
new NoCmdsError({
|
|
48
|
+
message: `${PREFIX}: the machine has no \`cmds\`, so no Cmd has a schema to draw its answer from. List its \`Cmd.define\`d Cmds in \`defineMachine({ cmds })\`.`
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const answerers = [];
|
|
53
|
+
const outcomes = [];
|
|
54
|
+
for (const def of defs) {
|
|
55
|
+
const drawn = outcomesOf(def);
|
|
56
|
+
if (drawn === null) {
|
|
57
|
+
return Result.fail(
|
|
58
|
+
new NotEffectSchemaError({
|
|
59
|
+
cmdType: def.cmdType,
|
|
60
|
+
message: `${PREFIX}: the "${def.cmdType}" Cmd's ok schema is not an Effect schema, so no answer can be drawn from it. Define it with \`Cmd.define\` from \`@demlik/tea/effect\`.`
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const mint = def;
|
|
65
|
+
answerers.push({
|
|
66
|
+
cmdType: def.cmdType,
|
|
67
|
+
make: (cmd, value) => {
|
|
68
|
+
const drew = value;
|
|
69
|
+
return drew.outcome === "ok" ? mint.ok(cmd, drew.value) : mint.err(cmd, { _tag: drew.tag });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
outcomes.push(drawn);
|
|
73
|
+
}
|
|
74
|
+
const outsides = Object.values(
|
|
75
|
+
opts?.outside ?? {}
|
|
76
|
+
);
|
|
77
|
+
const outside = outsides.length === 0 ? null : oneOf(outsides);
|
|
78
|
+
const answer = outcomes.length === 0 ? null : Arbitrary.map(
|
|
79
|
+
Arbitrary.all([
|
|
80
|
+
upTo(MAX_PICK),
|
|
81
|
+
Arbitrary.all(outcomes),
|
|
82
|
+
outside ?? Arbitrary.Constant(void 0)
|
|
83
|
+
]),
|
|
84
|
+
([pick, values, otherwise]) => otherwise === void 0 ? { kind: "answer", pick, values } : { kind: "answer", pick, values, otherwise }
|
|
85
|
+
);
|
|
86
|
+
const arms = [];
|
|
87
|
+
if (answer !== null) arms.push(answer, answer);
|
|
88
|
+
if (outside !== null) {
|
|
89
|
+
arms.push(
|
|
90
|
+
Arbitrary.map(
|
|
91
|
+
outside,
|
|
92
|
+
(msg) => ({ kind: "outside", msg })
|
|
93
|
+
)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (arms.length === 0) {
|
|
97
|
+
return Result.fail(
|
|
98
|
+
new NothingToDrawError({
|
|
99
|
+
message: `${PREFIX}: \`machine.cmds\` is empty and no \`outside\` Msg was given, so every list would be empty.`
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const loaded = opts?.loaded ?? null;
|
|
104
|
+
return Result.succeed(
|
|
105
|
+
Arbitrary.map(
|
|
106
|
+
Arbitrary.array(oneOf(arms), { maxLength: opts?.maxLength ?? 60 }),
|
|
107
|
+
(events) => resolveGuided(machine, ctx, loaded, answerers, events)
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
11
111
|
var DriveInterrupted = class {
|
|
12
112
|
};
|
|
13
113
|
function drive(machine, initial, msg, interpret, ...[opts]) {
|
|
@@ -143,6 +243,6 @@ function effectSubs(machine, subscribe, fork, signal) {
|
|
|
143
243
|
};
|
|
144
244
|
}
|
|
145
245
|
|
|
146
|
-
export { drive };
|
|
246
|
+
export { NoCmdsError, NotEffectSchemaError, NothingToDrawError, arbSchemaGuidedSequence, drive };
|
|
147
247
|
//# sourceMappingURL=index.js.map
|
|
148
248
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/testing/effect/drive.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `drive(machine, initial, msg, interpret, opts)` for the Effect engine: the\n// Promise `drive`'s rounds, trace and bound, over the Effect engine's handlers\n// and Subs (#321).\n//\n// There is no second loop. Each Effect cell is adapted into the shared loop's\n// cell with the settle rule the engine itself uses (`settleCellExit` in\n// `src/effect/run.ts`), run with the services the caller provided, so a cell\n// settles here exactly as it does under `run`. The Subs the machine wants are\n// started and stopped after every fold, as the engine reconciles them, and\n// their Msgs are folded once the Cmds in flight have settled.\n// ---------------------------------------------------------------------------\n\nimport { Cause, Effect, Exit, Fiber, Stream } from \"effect\";\nimport { CellFailure } from \"../../effect/failures\";\nimport {\n builtinRunners,\n type CellErrors,\n cellResult,\n type EffectInterpret,\n type EffectSubscribe,\n type InterpretServices,\n type SubscribeOption,\n type SubscribeServices,\n settleCellExit,\n} from \"../../effect/run\";\nimport type { Cmd, Machine, Sub } from \"../../pure/core\";\nimport {\n annotateTrace,\n DEFAULT_MAX_ROUNDS,\n type DriveCell,\n DriveNoHandlerError,\n type DriveOptions,\n type DriveResult,\n DriveRoundsExceededError,\n type DriveSubs,\n desiredSubs,\n driveContractOf,\n driveLoop,\n driveTraceOf,\n} from \"../drive-loop\";\n\n/**\n * The Effect `drive`'s options: the Promise `drive`'s, plus `subscribe` — the\n * runners the engine's `run` takes, required exactly when `run` requires them.\n */\nexport type EffectDriveOptions<U extends Sub, Ctx, B> = DriveOptions<Ctx> &\n SubscribeOption<U, B>;\n\n/** The failures a drive ends with: the loop's own, and a hand-written cell's. */\nexport type EffectDriveError<M, C extends Cmd, I> =\n | DriveRoundsExceededError<M, C>\n | DriveNoHandlerError<M, C>\n | CellErrors<C, I>;\n\n/** The drive was interrupted; nothing is left to run. */\nclass DriveInterrupted {}\n\ntype AnyCell = (cmd: unknown) => Effect.Effect<unknown, unknown>;\ntype AnyRunner = (sub: Sub) => Stream.Stream<unknown, unknown>;\ntype RunEffect = (\n effect: Effect.Effect<unknown, unknown>,\n options: { readonly signal: AbortSignal },\n) => Promise<Exit.Exit<unknown, unknown>>;\ntype ForkEffect = (\n effect: Effect.Effect<unknown, unknown>,\n) => Fiber.Fiber<unknown, unknown>;\n\n/**\n * Drive `machine` from `initial` through `msg` against the REAL Effect\n * `interpret` map — the one a host hands the Effect engine's `run` — and the\n * Subs the machine wants, feeding every Msg back until the machine goes quiet.\n * Yields the settled state together with the whole history, `{ state, trace }`.\n *\n * The rounds, the trace, `maxRounds` and `clock` are the Promise `drive`'s\n * (`@demlik/tea/testing/promise`). What is the Effect engine's:\n *\n * - a cell returns an Effect. A `Cmd.define`d Cmd's success mints `_ok` and\n * its declared failure `_err`; a hand-written Cmd's success is its\n * follow-up — a Msg, a list of them folded in order, or nothing (#324) —\n * and its failure fails the drive, typed. A defect dies. A cell takes no\n * `dispatch`, so a defined Cmd's handler cannot send its own `_ok` /\n * `_err`: the engine's rule holds by construction;\n * - the services the cells and runners read are this Effect's requirements,\n * so a test provides them the way a host does: `Effect.provide(layer)`;\n * - the Subs the machine wants run, through `opts.subscribe` or the engine's\n * built-in runner, started and stopped after every fold. Their Msgs are\n * folded once no Cmd's follow-up is pending. The drive settles once no Msg\n * is pending and no Sub is still running, so hand it a runner whose Stream\n * ends — a Sub that never ends keeps it waiting. A Stream that fails with\n * an error it did not map to a Msg dies, as it stops the engine's run.\n *\n * Interrupting the drive interrupts every cell and Sub still running.\n *\n * ```ts\n * const { state, trace } = await Effect.runPromise(\n * drive(machine, initial, { type: \"go\" }, {\n * fetch: (cmd) =>\n * Effect.gen(function* () {\n * const api = yield* Api;\n * return yield* api.get(cmd.id);\n * }),\n * }).pipe(Effect.provide(ApiTest)),\n * );\n * ```\n *\n * Fails with {@link DriveRoundsExceededError} when the machine is still\n * emitting work after `opts.maxRounds` rounds, {@link DriveNoHandlerError}\n * for a Cmd no cell answers, or a hand-written cell's own failure — each with\n * the partial trace, readable via `driveTraceOf`.\n */\nexport function drive<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n // The defaults read no services, so an omitted map adds none to `R`.\n I extends EffectInterpret<M, C> = EffectInterpret<M, C, never>,\n B extends EffectSubscribe<M, U> = EffectSubscribe<M, U, never>,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n initial: S,\n msg: M,\n interpret: I,\n ...[opts]: Record<never, never> extends EffectDriveOptions<U, Ctx, B>\n ? [opts?: EffectDriveOptions<U, Ctx, B>]\n : [opts: EffectDriveOptions<U, Ctx, B>]\n): Effect.Effect<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>,\n InterpretServices<I> | SubscribeServices<B>\n> {\n const options = (opts ?? {}) as {\n ctx?: Ctx;\n maxRounds?: number;\n clock?: () => number;\n subscribe?: Readonly<Record<string, AnyRunner | undefined>>;\n };\n return Effect.gen(function* () {\n const services = yield* Effect.context<\n InterpretServices<I> | SubscribeServices<B>\n >();\n const runEffect = Effect.runPromiseExitWith(services) as RunEffect;\n const fork = Effect.runForkWith(services) as ForkEffect;\n\n return yield* Effect.callback<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>\n >((resume, signal) => {\n const cells = interpret as unknown as Readonly<\n Record<string, AnyCell | undefined>\n >;\n const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));\n const cellFor = (type: string): DriveCell<M, C> | undefined => {\n const cell = cells[type];\n if (cell === undefined) return undefined;\n return async (cmd) => {\n const exit = await runEffect(cellResult(cell(cmd)), { signal });\n if (signal.aborted) throw new DriveInterrupted();\n return settleCellExit(exit, defined.has(type));\n };\n };\n\n driveLoop(\n machine,\n initial,\n msg,\n options.ctx as Ctx,\n options.maxRounds ?? DEFAULT_MAX_ROUNDS,\n {\n contract: driveContractOf(machine, options.clock ?? Date.now),\n cellFor,\n subs: effectSubs<S, M>(\n machine,\n options.subscribe ?? {},\n fork,\n signal,\n ),\n },\n ).then(\n (result) => resume(Effect.succeed(result)),\n (error: unknown) => resume(failureOf<M, C, I>(error)),\n );\n });\n });\n}\n\n/**\n * Sort what left the loop: its own failures and a hand-written cell's failure\n * are typed, everything else — a defect, a contract breach, a failed Sub — dies.\n */\nfunction failureOf<M, C extends Cmd, I>(\n error: unknown,\n): Effect.Effect<never, EffectDriveError<M, C, I>> {\n if (\n error instanceof DriveRoundsExceededError ||\n error instanceof DriveNoHandlerError\n ) {\n return Effect.fail(error as EffectDriveError<M, C, I>);\n }\n if (error instanceof CellFailure) {\n annotateTrace(error.failure, driveTraceOf(error) ?? []);\n return Effect.fail(error.failure as EffectDriveError<M, C, I>);\n }\n if (error instanceof DriveInterrupted) return Effect.interrupt;\n return Effect.die(error);\n}\n\n/**\n * One started Sub. It stays registered while the machine wants it, even after\n * its Stream ends, so a Sub that finished is not started again — the engine\n * keeps it the same way. `stopped` drops any Msg that arrives after a fold\n * turned it off; `ended` says its Stream no longer runs.\n */\ninterface StartedSub {\n stopped: boolean;\n ended: boolean;\n fiber?: Fiber.Fiber<unknown, unknown>;\n}\n\n/**\n * The Effect engine's Subs as the shared loop runs them: each wanted Sub's\n * Stream is drained on its own fiber into a buffer the loop reads between\n * rounds.\n */\nfunction effectSubs<S, M>(\n machine: { readonly subs?: ReadonlyArray<unknown> },\n subscribe: Readonly<Record<string, AnyRunner | undefined>>,\n fork: ForkEffect,\n signal: AbortSignal,\n): DriveSubs<S, M> {\n const builtins = builtinRunners as unknown as Readonly<\n Record<string, AnyRunner | undefined>\n >;\n const started = new Map<string, StartedSub>();\n let buffered: M[] = [];\n let failed: Cause.Cause<unknown> | undefined;\n let wake: (() => void) | undefined;\n const notify = (): void => {\n const woken = wake;\n wake = undefined;\n woken?.();\n };\n signal.addEventListener(\"abort\", notify, { once: true });\n\n const stop = (sub: StartedSub): Promise<void> => {\n sub.stopped = true;\n return sub.ended || sub.fiber === undefined\n ? Promise.resolve()\n : Effect.runPromise(Fiber.interrupt(sub.fiber));\n };\n const anyRunning = (): boolean =>\n [...started.values()].some((sub) => !sub.ended);\n\n return {\n reconcile(state) {\n const desired = desiredSubs(machine, state);\n for (const [id, sub] of started) {\n if (desired.has(id)) continue;\n started.delete(id);\n void stop(sub);\n }\n for (const [id, sub] of desired) {\n if (started.has(id)) continue;\n const make = subscribe[sub.type] ?? builtins[sub.type];\n if (make === undefined) {\n throw new Error(\n `@demlik/tea: no subscribe runner for Sub type \"${sub.type}\". ` +\n \"Pass one to drive in `subscribe`.\",\n );\n }\n const entry: StartedSub = { stopped: false, ended: false };\n started.set(id, entry);\n entry.fiber = fork(\n Stream.runForEach(make(sub), (next) =>\n Effect.sync(() => {\n if (entry.stopped) return;\n buffered.push(next as M);\n notify();\n }),\n ).pipe(\n Effect.onExit((exit) =>\n Effect.sync(() => {\n entry.ended = true;\n if (\n !entry.stopped &&\n Exit.isFailure(exit) &&\n !Cause.hasInterruptsOnly(exit.cause)\n ) {\n failed ??= exit.cause;\n }\n notify();\n }),\n ),\n ),\n );\n }\n },\n async next() {\n for (;;) {\n if (signal.aborted) throw new DriveInterrupted();\n if (buffered.length > 0) {\n const out = buffered;\n buffered = [];\n return out;\n }\n if (failed !== undefined) throw Cause.squash(failed);\n if (!anyRunning()) return undefined;\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n },\n async close() {\n signal.removeEventListener(\"abort\", notify);\n const stopping = [...started.values()].map(stop);\n started.clear();\n await Promise.all(stopping);\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/testing/effect/answers.ts","../../../src/testing/effect/drive.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `arbSchemaGuidedSequence(machine, ctx, opts)`: Msg lists for Effect's\n// `Arbitrary.checkEffect`, whose answers come from each Cmd's own Effect schema\n// (#749). A Cmd made with `Cmd.define` from `@demlik/tea/effect` already holds\n// its `ok` schema and its `err` tags, so nobody writes its answers a second time.\n//\n// The lists are drawn as blind events and folded through the machine by the\n// same rule `arbGuidedSequence` uses (`src/pbt/arbitraries/guided-fold.ts`), so\n// every answer answers a Cmd that was sent and not yet answered. Only the\n// generator differs: Effect's `Arbitrary` here, fast-check there. Nothing here\n// imports fast-check, and nothing here runs a check: the caller runs\n// `Arbitrary.checkEffect`, folds with `foldEvents` and judges with a law.\n// The ruling: https://github.com/kamp-us/demlik/issues/749#issuecomment-6564369809\n// ---------------------------------------------------------------------------\n\nimport { Arbitrary, Data, Result, Schema } from \"effect\";\nimport {\n type Answerer,\n type BlindEvent,\n MAX_PICK,\n resolveGuided,\n} from \"../../pbt/arbitraries/guided-fold\";\nimport type { AnyCmdDef, Cmd, Machine, Sub } from \"../../pure/core\";\n\n/**\n * The machine has no `cmds` list, so there is no schema to draw an answer from.\n *\n * @experimental\n */\nexport class NoCmdsError extends Data.TaggedError(\"NoCmdsError\")<{\n readonly message: string;\n}> {}\n\n/**\n * A Cmd's `ok` schema is not an Effect schema, so no answer can be drawn from\n * it. A Cmd made with `Cmd.define` from `@demlik/tea` over zod, or any other\n * Standard Schema, lands here. `cmdType` names that Cmd.\n *\n * @experimental\n */\nexport class NotEffectSchemaError extends Data.TaggedError(\n \"NotEffectSchemaError\",\n)<{\n readonly cmdType: string;\n readonly message: string;\n}> {}\n\n/**\n * `machine.cmds` is empty and no `outside` Msg was given, so every list drawn\n * would be empty.\n *\n * @experimental\n */\nexport class NothingToDrawError extends Data.TaggedError(\"NothingToDrawError\")<{\n readonly message: string;\n}> {}\n\n/**\n * Why {@link arbSchemaGuidedSequence} drew no lists.\n *\n * @experimental\n */\nexport type SchemaAnswersError =\n | NoCmdsError\n | NotEffectSchemaError\n | NothingToDrawError;\n\n/**\n * What {@link arbSchemaGuidedSequence} takes beside the machine and its ctx.\n *\n * @experimental\n */\nexport interface SchemaGuidedOptions<S, M extends { type: string }> {\n /**\n * The Msgs nothing sends a Cmd for, by Msg type: a user's click, a timer. A\n * Msg type here and in no Cmd's answers is drawn only from here.\n */\n readonly outside?: {\n readonly [K in M[\"type\"]]?: Arbitrary.Arbitrary<Extract<M, { type: K }>>;\n };\n /**\n * Caps the events drawn. Default 60. `checkEffect` and `sampleEffect` cap a\n * list at their `size` too, which defaults to 10, so pass a `size` of at least\n * this to draw lists this long.\n */\n readonly maxLength?: number;\n /**\n * The saved state the fold starts from. Give `foldEvents` the same one, or it\n * reads the list from a different start. Default `null`.\n */\n readonly loaded?: S | null;\n}\n\n/** What one answer event drew for one Cmd: its `ok` value, or an `err` tag. */\ntype Outcome =\n | { readonly outcome: \"ok\"; readonly value: unknown }\n | { readonly outcome: \"err\"; readonly tag: string };\n\n/** The minting half of a `CmdDef`, which `AnyCmdDef` leaves out. */\ninterface Minting {\n readonly ok: (cmd: Cmd, value: unknown) => unknown;\n readonly err: (cmd: Cmd, error: { readonly _tag: string }) => unknown;\n}\n\nconst PREFIX = \"@demlik/tea/testing/effect: arbSchemaGuidedSequence\";\n\n/** An arbitrary of the integers from 0 to `max`, both included. */\nconst upTo = (max: number): Arbitrary.Arbitrary<number> =>\n Arbitrary.schema(\n Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: max })),\n );\n\n/** Draw one of `arms`, each as likely as the next. */\nconst oneOf = <A>(\n arms: readonly Arbitrary.Arbitrary<A>[],\n): Arbitrary.Arbitrary<A> =>\n arms.length === 1\n ? (arms[0] as Arbitrary.Arbitrary<A>)\n : Arbitrary.flatMap(\n upTo(arms.length - 1),\n (at) => arms[at] as Arbitrary.Arbitrary<A>,\n );\n\n/**\n * The outcomes one Cmd's answer is drawn from: its `ok` schema, and each of its\n * `errTags`. `null` when that `ok` schema is not an Effect schema.\n */\nfunction outcomesOf(def: AnyCmdDef): Arbitrary.Arbitrary<Outcome> | null {\n const ok = def.schema.ok;\n if (!Schema.isSchema(ok)) return null;\n const okArm = Schema.Struct({ outcome: Schema.Literal(\"ok\"), value: ok });\n const outcome =\n def.errTags.length === 0\n ? okArm\n : Schema.Union([\n okArm,\n Schema.Struct({\n outcome: Schema.Literal(\"err\"),\n tag: Schema.Literals(def.errTags),\n }),\n ]);\n return Arbitrary.schema(outcome) as Arbitrary.Arbitrary<Outcome>;\n}\n\n/**\n * Draw Msg lists for `Arbitrary.checkEffect` in which every answer answers a Cmd\n * that was sent and not yet answered, and comes from that Cmd's own schema: an\n * `ok` answer is drawn from its `schema.ok`, an `err` answer carries one of its\n * `errTags`. Edit a schema and what is drawn changes with it, with no change to\n * the test.\n *\n * Each list is drawn as blind events folded through the machine. An answer event\n * names one waiting Cmd and is minted with that Cmd's def, `def.ok(cmd, value)`\n * or `def.err(cmd, { _tag })`. With nothing waiting the answer is dropped, and an\n * `outside` Msg the event drew goes in its place, or nothing when there is no\n * `outside`. An event whose Msg the state has no cell for is dropped too, and a\n * refused answer leaves its Cmd waiting. A Cmd no def in `machine.cmds` builds is\n * never answered.\n *\n * It runs no check. Fold each list with `foldEvents` and judge it with a law,\n * `lawInvariant`, `lawTerminates` or `lawTrace`, inside `Arbitrary.checkEffect`.\n * A list it finds there shrinks by deleting events and shrinking them, and what\n * is left still folds by the same rule. With `outside` given and every Msg\n * accepted in every state, each event means one Msg, so a shrunk list is never\n * longer than the one first found. A dropped event breaks that: deleting an\n * answer before it can let it back in.\n *\n * It returns a failed `Result`, never an empty arbitrary, on a machine with no\n * `machine.cmds` ({@link NoCmdsError}), on a Cmd whose `ok` schema is not an\n * Effect schema ({@link NotEffectSchemaError}, naming it), and when there is\n * nothing at all to draw ({@link NothingToDrawError}).\n *\n * @example\n * ```ts\n * import type { CmdOf, Machine, Settled } from \"@demlik/tea\";\n * import { Cmd } from \"@demlik/tea/effect\";\n * import {\n * arbSchemaGuidedSequence,\n * foldEvents,\n * lawInvariant,\n * } from \"@demlik/tea/testing/effect\";\n * import { Arbitrary, Effect, Result, Schema } from \"effect\";\n *\n * const fetchCount = Cmd.define(\"fetch_count\", {\n * input: Schema.Struct({}),\n * ok: Schema.Struct({ n: Schema.Int }),\n * err: [\"offline\"],\n * });\n * type Msg = { type: \"start\" } | Settled<typeof fetchCount>;\n * declare const machine: Machine<\n * { readonly total: number },\n * Msg,\n * CmdOf<typeof fetchCount>,\n * never,\n * object\n * >;\n *\n * const lists = Result.getOrThrow(\n * arbSchemaGuidedSequence(machine, {}, {\n * outside: { start: Arbitrary.Constant({ type: \"start\" as const }) },\n * }),\n * );\n * const result = await Effect.runPromise(\n * Arbitrary.checkEffect(\n * lists,\n * (msgs) =>\n * lawInvariant(foldEvents(machine, {}, null, msgs), ({ next }) =>\n * Number.isFinite(next.total),\n * ) === null,\n * { runs: 500, size: 60 },\n * ),\n * );\n * ```\n *\n * @experimental\n */\nexport function arbSchemaGuidedSequence<\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 ctx: NoInfer<Ctx>,\n opts?: SchemaGuidedOptions<NoInfer<S>, M>,\n): Result.Result<Arbitrary.Arbitrary<readonly M[]>, SchemaAnswersError> {\n const defs = machine.cmds;\n if (defs === undefined) {\n return Result.fail(\n new NoCmdsError({\n message: `${PREFIX}: the machine has no \\`cmds\\`, so no Cmd has a schema to draw its answer from. List its \\`Cmd.define\\`d Cmds in \\`defineMachine({ cmds })\\`.`,\n }),\n );\n }\n\n const answerers: Answerer<C, M>[] = [];\n const outcomes: Arbitrary.Arbitrary<Outcome>[] = [];\n for (const def of defs) {\n const drawn = outcomesOf(def);\n if (drawn === null) {\n return Result.fail(\n new NotEffectSchemaError({\n cmdType: def.cmdType,\n message: `${PREFIX}: the \"${def.cmdType}\" Cmd's ok schema is not an Effect schema, so no answer can be drawn from it. Define it with \\`Cmd.define\\` from \\`@demlik/tea/effect\\`.`,\n }),\n );\n }\n const mint = def as unknown as Minting;\n answerers.push({\n cmdType: def.cmdType,\n make: (cmd, value) => {\n const drew = value as Outcome;\n return (\n drew.outcome === \"ok\"\n ? mint.ok(cmd, drew.value)\n : mint.err(cmd, { _tag: drew.tag })\n ) as M;\n },\n });\n outcomes.push(drawn);\n }\n\n const outsides = Object.values(\n opts?.outside ?? {},\n ) as Arbitrary.Arbitrary<M>[];\n const outside = outsides.length === 0 ? null : oneOf(outsides);\n // An answer event also draws the outside Msg that stands in for it when nothing\n // is waiting. Then every event means one Msg, so a shrink, which only deletes\n // events and shrinks them, can never grow a list past the one first found.\n const answer =\n outcomes.length === 0\n ? null\n : Arbitrary.map(\n Arbitrary.all([\n upTo(MAX_PICK),\n Arbitrary.all(outcomes),\n outside ?? Arbitrary.Constant(undefined),\n ]),\n ([pick, values, otherwise]): BlindEvent<M> =>\n otherwise === undefined\n ? { kind: \"answer\", pick, values }\n : { kind: \"answer\", pick, values, otherwise },\n );\n\n // An answer is twice as likely as an outside Msg, as in `arbGuidedSequence`.\n // The answer arms come first, so a shrink turns an outside Msg into an answer,\n // which sends less, and never the other way.\n const arms: Arbitrary.Arbitrary<BlindEvent<M>>[] = [];\n if (answer !== null) arms.push(answer, answer);\n if (outside !== null) {\n arms.push(\n Arbitrary.map(\n outside,\n (msg): BlindEvent<M> => ({ kind: \"outside\", msg }),\n ),\n );\n }\n if (arms.length === 0) {\n return Result.fail(\n new NothingToDrawError({\n message: `${PREFIX}: \\`machine.cmds\\` is empty and no \\`outside\\` Msg was given, so every list would be empty.`,\n }),\n );\n }\n\n const loaded = opts?.loaded ?? null;\n return Result.succeed(\n Arbitrary.map(\n Arbitrary.array(oneOf(arms), { maxLength: opts?.maxLength ?? 60 }),\n (events) => resolveGuided(machine, ctx, loaded, answerers, events),\n ),\n );\n}\n","// ---------------------------------------------------------------------------\n// `drive(machine, initial, msg, interpret, opts)` for the Effect engine: the\n// Promise `drive`'s rounds, trace and bound, over the Effect engine's handlers\n// and Subs (#321).\n//\n// There is no second loop. Each Effect cell is adapted into the shared loop's\n// cell with the settle rule the engine itself uses (`settleCellExit` in\n// `src/effect/run.ts`), run with the services the caller provided, so a cell\n// settles here exactly as it does under `run`. The Subs the machine wants are\n// started and stopped after every fold, as the engine reconciles them, and\n// their Msgs are folded once the Cmds in flight have settled.\n// ---------------------------------------------------------------------------\n\nimport { Cause, Effect, Exit, Fiber, Stream } from \"effect\";\nimport { CellFailure } from \"../../effect/failures\";\nimport {\n builtinRunners,\n type CellErrors,\n cellResult,\n type EffectInterpret,\n type EffectSubscribe,\n type InterpretServices,\n type SubscribeOption,\n type SubscribeServices,\n settleCellExit,\n} from \"../../effect/run\";\nimport type { Cmd, Machine, Sub } from \"../../pure/core\";\nimport {\n annotateTrace,\n DEFAULT_MAX_ROUNDS,\n type DriveCell,\n DriveNoHandlerError,\n type DriveOptions,\n type DriveResult,\n DriveRoundsExceededError,\n type DriveSubs,\n desiredSubs,\n driveContractOf,\n driveLoop,\n driveTraceOf,\n} from \"../drive-loop\";\n\n/**\n * The Effect `drive`'s options: the Promise `drive`'s, plus `subscribe` — the\n * runners the engine's `run` takes, required exactly when `run` requires them.\n */\nexport type EffectDriveOptions<U extends Sub, Ctx, B> = DriveOptions<Ctx> &\n SubscribeOption<U, B>;\n\n/** The failures a drive ends with: the loop's own, and a hand-written cell's. */\nexport type EffectDriveError<M, C extends Cmd, I> =\n | DriveRoundsExceededError<M, C>\n | DriveNoHandlerError<M, C>\n | CellErrors<C, I>;\n\n/** The drive was interrupted; nothing is left to run. */\nclass DriveInterrupted {}\n\ntype AnyCell = (cmd: unknown) => Effect.Effect<unknown, unknown>;\ntype AnyRunner = (sub: Sub) => Stream.Stream<unknown, unknown>;\ntype RunEffect = (\n effect: Effect.Effect<unknown, unknown>,\n options: { readonly signal: AbortSignal },\n) => Promise<Exit.Exit<unknown, unknown>>;\ntype ForkEffect = (\n effect: Effect.Effect<unknown, unknown>,\n) => Fiber.Fiber<unknown, unknown>;\n\n/**\n * Drive `machine` from `initial` through `msg` against the REAL Effect\n * `interpret` map — the one a host hands the Effect engine's `run` — and the\n * Subs the machine wants, feeding every Msg back until the machine goes quiet.\n * Yields the settled state together with the whole history, `{ state, trace }`.\n *\n * The rounds, the trace, `maxRounds` and `clock` are the Promise `drive`'s\n * (`@demlik/tea/testing/promise`). What is the Effect engine's:\n *\n * - a cell returns an Effect. A `Cmd.define`d Cmd's success mints `_ok` and\n * its declared failure `_err`; a hand-written Cmd's success is its\n * follow-up — a Msg, a list of them folded in order, or nothing (#324) —\n * and its failure fails the drive, typed. A defect dies. A cell takes no\n * `dispatch`, so a defined Cmd's handler cannot send its own `_ok` /\n * `_err`: the engine's rule holds by construction;\n * - the services the cells and runners read are this Effect's requirements,\n * so a test provides them the way a host does: `Effect.provide(layer)`;\n * - the Subs the machine wants run, through `opts.subscribe` or the engine's\n * built-in runner, started and stopped after every fold. Their Msgs are\n * folded once no Cmd's follow-up is pending. The drive settles once no Msg\n * is pending and no Sub is still running, so hand it a runner whose Stream\n * ends — a Sub that never ends keeps it waiting. A Stream that fails with\n * an error it did not map to a Msg dies, as it stops the engine's run.\n *\n * Interrupting the drive interrupts every cell and Sub still running.\n *\n * ```ts\n * const { state, trace } = await Effect.runPromise(\n * drive(machine, initial, { type: \"go\" }, {\n * fetch: (cmd) =>\n * Effect.gen(function* () {\n * const api = yield* Api;\n * return yield* api.get(cmd.id);\n * }),\n * }).pipe(Effect.provide(ApiTest)),\n * );\n * ```\n *\n * Fails with {@link DriveRoundsExceededError} when the machine is still\n * emitting work after `opts.maxRounds` rounds, {@link DriveNoHandlerError}\n * for a Cmd no cell answers, or a hand-written cell's own failure — each with\n * the partial trace, readable via `driveTraceOf`.\n */\nexport function drive<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n // The defaults read no services, so an omitted map adds none to `R`.\n I extends EffectInterpret<M, C> = EffectInterpret<M, C, never>,\n B extends EffectSubscribe<M, U> = EffectSubscribe<M, U, never>,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n initial: S,\n msg: M,\n interpret: I,\n ...[opts]: Record<never, never> extends EffectDriveOptions<U, Ctx, B>\n ? [opts?: EffectDriveOptions<U, Ctx, B>]\n : [opts: EffectDriveOptions<U, Ctx, B>]\n): Effect.Effect<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>,\n InterpretServices<I> | SubscribeServices<B>\n> {\n const options = (opts ?? {}) as {\n ctx?: Ctx;\n maxRounds?: number;\n clock?: () => number;\n subscribe?: Readonly<Record<string, AnyRunner | undefined>>;\n };\n return Effect.gen(function* () {\n const services = yield* Effect.context<\n InterpretServices<I> | SubscribeServices<B>\n >();\n const runEffect = Effect.runPromiseExitWith(services) as RunEffect;\n const fork = Effect.runForkWith(services) as ForkEffect;\n\n return yield* Effect.callback<\n DriveResult<S, M, C>,\n EffectDriveError<M, C, I>\n >((resume, signal) => {\n const cells = interpret as unknown as Readonly<\n Record<string, AnyCell | undefined>\n >;\n const defined = new Set((machine.cmds ?? []).map((def) => def.cmdType));\n const cellFor = (type: string): DriveCell<M, C> | undefined => {\n const cell = cells[type];\n if (cell === undefined) return undefined;\n return async (cmd) => {\n const exit = await runEffect(cellResult(cell(cmd)), { signal });\n if (signal.aborted) throw new DriveInterrupted();\n return settleCellExit(exit, defined.has(type));\n };\n };\n\n driveLoop(\n machine,\n initial,\n msg,\n options.ctx as Ctx,\n options.maxRounds ?? DEFAULT_MAX_ROUNDS,\n {\n contract: driveContractOf(machine, options.clock ?? Date.now),\n cellFor,\n subs: effectSubs<S, M>(\n machine,\n options.subscribe ?? {},\n fork,\n signal,\n ),\n },\n ).then(\n (result) => resume(Effect.succeed(result)),\n (error: unknown) => resume(failureOf<M, C, I>(error)),\n );\n });\n });\n}\n\n/**\n * Sort what left the loop: its own failures and a hand-written cell's failure\n * are typed, everything else — a defect, a contract breach, a failed Sub — dies.\n */\nfunction failureOf<M, C extends Cmd, I>(\n error: unknown,\n): Effect.Effect<never, EffectDriveError<M, C, I>> {\n if (\n error instanceof DriveRoundsExceededError ||\n error instanceof DriveNoHandlerError\n ) {\n return Effect.fail(error as EffectDriveError<M, C, I>);\n }\n if (error instanceof CellFailure) {\n annotateTrace(error.failure, driveTraceOf(error) ?? []);\n return Effect.fail(error.failure as EffectDriveError<M, C, I>);\n }\n if (error instanceof DriveInterrupted) return Effect.interrupt;\n return Effect.die(error);\n}\n\n/**\n * One started Sub. It stays registered while the machine wants it, even after\n * its Stream ends, so a Sub that finished is not started again — the engine\n * keeps it the same way. `stopped` drops any Msg that arrives after a fold\n * turned it off; `ended` says its Stream no longer runs.\n */\ninterface StartedSub {\n stopped: boolean;\n ended: boolean;\n fiber?: Fiber.Fiber<unknown, unknown>;\n}\n\n/**\n * The Effect engine's Subs as the shared loop runs them: each wanted Sub's\n * Stream is drained on its own fiber into a buffer the loop reads between\n * rounds.\n */\nfunction effectSubs<S, M>(\n machine: { readonly subs?: ReadonlyArray<unknown> },\n subscribe: Readonly<Record<string, AnyRunner | undefined>>,\n fork: ForkEffect,\n signal: AbortSignal,\n): DriveSubs<S, M> {\n const builtins = builtinRunners as unknown as Readonly<\n Record<string, AnyRunner | undefined>\n >;\n const started = new Map<string, StartedSub>();\n let buffered: M[] = [];\n let failed: Cause.Cause<unknown> | undefined;\n let wake: (() => void) | undefined;\n const notify = (): void => {\n const woken = wake;\n wake = undefined;\n woken?.();\n };\n signal.addEventListener(\"abort\", notify, { once: true });\n\n const stop = (sub: StartedSub): Promise<void> => {\n sub.stopped = true;\n return sub.ended || sub.fiber === undefined\n ? Promise.resolve()\n : Effect.runPromise(Fiber.interrupt(sub.fiber));\n };\n const anyRunning = (): boolean =>\n [...started.values()].some((sub) => !sub.ended);\n\n return {\n reconcile(state) {\n const desired = desiredSubs(machine, state);\n for (const [id, sub] of started) {\n if (desired.has(id)) continue;\n started.delete(id);\n void stop(sub);\n }\n for (const [id, sub] of desired) {\n if (started.has(id)) continue;\n const make = subscribe[sub.type] ?? builtins[sub.type];\n if (make === undefined) {\n throw new Error(\n `@demlik/tea: no subscribe runner for Sub type \"${sub.type}\". ` +\n \"Pass one to drive in `subscribe`.\",\n );\n }\n const entry: StartedSub = { stopped: false, ended: false };\n started.set(id, entry);\n entry.fiber = fork(\n Stream.runForEach(make(sub), (next) =>\n Effect.sync(() => {\n if (entry.stopped) return;\n buffered.push(next as M);\n notify();\n }),\n ).pipe(\n Effect.onExit((exit) =>\n Effect.sync(() => {\n entry.ended = true;\n if (\n !entry.stopped &&\n Exit.isFailure(exit) &&\n !Cause.hasInterruptsOnly(exit.cause)\n ) {\n failed ??= exit.cause;\n }\n notify();\n }),\n ),\n ),\n );\n }\n },\n async next() {\n for (;;) {\n if (signal.aborted) throw new DriveInterrupted();\n if (buffered.length > 0) {\n const out = buffered;\n buffered = [];\n return out;\n }\n if (failed !== undefined) throw Cause.squash(failed);\n if (!anyRunning()) return undefined;\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n }\n },\n async close() {\n signal.removeEventListener(\"abort\", notify);\n const stopping = [...started.values()].map(stop);\n started.clear();\n await Promise.all(stopping);\n },\n };\n}\n"]}
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Cmd,
|
|
2
|
-
import { R as Runtime } from '../runtime-types-
|
|
1
|
+
import { C as Cmd, c as Sub, M as Machine, B as BuiltinSub } from '../core-DNDAEhO2.js';
|
|
2
|
+
import { R as Runtime } from '../runtime-types-CP-ykwgf.js';
|
|
3
3
|
import '@standard-schema/spec';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -75,10 +75,8 @@ declare function expectActiveSubs<S, M extends {
|
|
|
75
75
|
* `[next, cmds]` can swap `reduce(state, msg)` for `step(machine, state, msg,
|
|
76
76
|
* ctx)` without changing the assertion code.
|
|
77
77
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* (init emits no cmds when `loaded` is provided, so the slice is the entire
|
|
81
|
-
* array).
|
|
78
|
+
* Calls the update cell directly, without init or subscription derivation.
|
|
79
|
+
* The returned Cmds belong only to this message.
|
|
82
80
|
*
|
|
83
81
|
* Use when consumers want to walk a machine one msg at a time and assert per
|
|
84
82
|
* step. For one-shot "final state after N msgs" assertions, prefer
|
|
@@ -86,7 +84,7 @@ declare function expectActiveSubs<S, M extends {
|
|
|
86
84
|
*/
|
|
87
85
|
declare function step<S, M extends {
|
|
88
86
|
type: string;
|
|
89
|
-
}, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, loaded: NoInfer<S>, msg: NoInfer<M>,
|
|
87
|
+
}, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, loaded: NoInfer<S>, msg: NoInfer<M>, _ctx: NoInfer<Ctx>): readonly [S, readonly C[]];
|
|
90
88
|
|
|
91
89
|
/**
|
|
92
90
|
* Replay options shape used by every bound method. `ctx` is captured by
|
|
@@ -148,7 +146,7 @@ interface BoundMachine<S, M extends {
|
|
|
148
146
|
}
|
|
149
147
|
/**
|
|
150
148
|
* Bind a machine + ctx into a small bag of 2-arg helpers. Each method
|
|
151
|
-
* delegates to `replay(machine, { ...opts, ctx })` and pipes the result
|
|
149
|
+
* delegates to `step` or `replay(machine, { ...opts, ctx })` and pipes the result
|
|
152
150
|
* through a single `expect(...)` matcher (or returns the raw tuple, for
|
|
153
151
|
* `step` and `replay`).
|
|
154
152
|
*
|
package/dist/testing/index.js
CHANGED
|
@@ -1,28 +1,8 @@
|
|
|
1
|
-
export { bindMachine } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import { replay } from '../chunk-
|
|
1
|
+
export { bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, step } from '../chunk-RMBZ7YH7.js';
|
|
2
|
+
import '../chunk-BVFS3ICC.js';
|
|
3
|
+
import { replay } from '../chunk-A3QPMX5M.js';
|
|
4
4
|
import { expect, vi } from 'vitest';
|
|
5
5
|
|
|
6
|
-
function expectFinalState(machine, opts, expected) {
|
|
7
|
-
const { state } = replay(machine, opts);
|
|
8
|
-
expect(state).toEqual(expected);
|
|
9
|
-
}
|
|
10
|
-
function expectCmdEmitted(machine, opts, cmd) {
|
|
11
|
-
const { cmds } = replay(machine, opts);
|
|
12
|
-
expect(cmds).toContainEqual(cmd);
|
|
13
|
-
}
|
|
14
|
-
function expectCmdSequence(machine, opts, expected) {
|
|
15
|
-
const { cmds } = replay(machine, opts);
|
|
16
|
-
expect(cmds).toEqual(expected);
|
|
17
|
-
}
|
|
18
|
-
function expectActiveSubs(machine, opts, expected) {
|
|
19
|
-
const { subs } = replay(machine, opts);
|
|
20
|
-
expect(subs).toEqual(expected);
|
|
21
|
-
}
|
|
22
|
-
function step(machine, loaded, msg, ctx) {
|
|
23
|
-
const { state, cmds } = replay(machine, { msgs: [msg], ctx, loaded });
|
|
24
|
-
return [state, cmds];
|
|
25
|
-
}
|
|
26
6
|
var AMBIENT_A = { now: 1e6, rng: 0.123 };
|
|
27
7
|
var AMBIENT_B = { now: 9876543, rng: 0.987 };
|
|
28
8
|
function expectReplayDeterministic(machine, opts) {
|
|
@@ -122,6 +102,6 @@ function stateFactory(defaults) {
|
|
|
122
102
|
return api;
|
|
123
103
|
}
|
|
124
104
|
|
|
125
|
-
export {
|
|
105
|
+
export { expectReplayDeterministic, noopRuntime, stateFactory };
|
|
126
106
|
//# sourceMappingURL=index.js.map
|
|
127
107
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/testing/expect-replay-deterministic.ts","../../src/testing/noop-runtime.ts","../../src/testing/state-factory.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["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"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
import { C as Cmd,
|
|
4
|
-
import '../../runtime-types-
|
|
1
|
+
import { D as DriveOptions, a as DriveResult } from '../../drive-loop-DlOEv49c.js';
|
|
2
|
+
export { b as DEFAULT_MAX_ROUNDS, c as DriveCtxArg, d as DriveNoHandlerError, e as DriveRoundsExceededError, f as DriveTraceEntry, g as driveTraceOf } from '../../drive-loop-DlOEv49c.js';
|
|
3
|
+
import { C as Cmd, c as Sub, M as Machine, I as Interpret } from '../../core-DNDAEhO2.js';
|
|
4
|
+
import '../../runtime-types-CP-ykwgf.js';
|
|
5
5
|
import '@standard-schema/spec';
|
|
6
6
|
|
|
7
7
|
/**
|