@demlik/tea 0.16.0 → 0.18.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 +3 -2
- package/dist/agent/index.d.ts +182 -35
- package/dist/agent/index.js +11 -11
- package/dist/{chunk-5XU6ZJO2.js → chunk-AHW2CLPT.js} +80 -27
- package/dist/chunk-AHW2CLPT.js.map +1 -0
- package/dist/{chunk-CH5KTOEZ.js → chunk-AIIXV4XB.js} +65 -13
- package/dist/chunk-AIIXV4XB.js.map +1 -0
- package/dist/{chunk-BCTEOVOU.js → chunk-BRX3243I.js} +3 -3
- package/dist/{chunk-BCTEOVOU.js.map → chunk-BRX3243I.js.map} +1 -1
- package/dist/{chunk-IVJP3GKJ.js → chunk-C3G2QFEZ.js} +4 -4
- package/dist/{chunk-IVJP3GKJ.js.map → chunk-C3G2QFEZ.js.map} +1 -1
- package/dist/{chunk-GOX2CYIM.js → chunk-FNGZMVIT.js} +3 -3
- package/dist/chunk-FNGZMVIT.js.map +1 -0
- package/dist/{chunk-WT5PBPLB.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-NSIAORRY.js → chunk-IHUNKHDX.js} +3 -3
- package/dist/{chunk-NSIAORRY.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-MYNLY3HP.js → chunk-JYNYG5SF.js} +4 -4
- package/dist/{chunk-MYNLY3HP.js.map → chunk-JYNYG5SF.js.map} +1 -1
- package/dist/{chunk-EHUDXAKC.js → chunk-PCJA2HTZ.js} +437 -162
- package/dist/chunk-PCJA2HTZ.js.map +1 -0
- package/dist/{chunk-MVZS5FDM.js → chunk-PQSDUX6B.js} +3 -3
- package/dist/{chunk-MVZS5FDM.js.map → chunk-PQSDUX6B.js.map} +1 -1
- package/dist/{chunk-6NG4V5SB.js → chunk-WTSOW2MD.js} +5 -5
- package/dist/{chunk-6NG4V5SB.js.map → chunk-WTSOW2MD.js.map} +1 -1
- package/dist/{chunk-7MGXQZPW.js → chunk-XVIKSRCK.js} +4 -4
- package/dist/{chunk-7MGXQZPW.js.map → chunk-XVIKSRCK.js.map} +1 -1
- package/dist/{chunk-CIHSHLQX.js → chunk-YIMOFXST.js} +3 -3
- package/dist/{chunk-CIHSHLQX.js.map → chunk-YIMOFXST.js.map} +1 -1
- package/dist/{core-BbgtiXdg.d.ts → core-Ce9ncpuv.d.ts} +10 -5
- 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-7MTK_jnj.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-Cou86-GX.d.ts → index-2kNxy6az.d.ts} +2 -2
- package/dist/{index-DU6JpQ8G.d.ts → index-B1Wzarkl.d.ts} +1 -1
- package/dist/{index-_dOR3xqA.d.ts → index-Bf9_bNql.d.ts} +2 -2
- package/dist/index-BnsAqn0-.d.ts +286 -0
- package/dist/{index-CkFYA9Y6.d.ts → index-Ce5GYAuI.d.ts} +2 -2
- package/dist/{index-CjFDUBeq.d.ts → index-D8sYVzCI.d.ts} +1 -1
- package/dist/{index-7vV8xmsD.d.ts → index-DWdg7R1l.d.ts} +1 -1
- package/dist/{index-Bz5Rh9Ga.d.ts → index-Dy3379ve.d.ts} +5 -5
- package/dist/{index-Xufl55W8.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-BQzTV8On.d.ts → machine-CuojfZup.d.ts} +355 -59
- 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 +9 -10
- 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-CiVpC0HN.d.ts → runtime-types-BKg2CGMx.d.ts} +89 -15
- 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 +25 -257
- package/dist/testing/index.js +25 -301
- 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-LxZxGYda.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 +27 -15
- package/dist/chunk-5XU6ZJO2.js.map +0 -1
- package/dist/chunk-CH5KTOEZ.js.map +0 -1
- package/dist/chunk-EHUDXAKC.js.map +0 -1
- package/dist/chunk-G5E74IJB.js +0 -8
- package/dist/chunk-G5E74IJB.js.map +0 -1
- package/dist/chunk-GOX2CYIM.js.map +0 -1
- package/dist/chunk-WT5PBPLB.js.map +0 -1
package/dist/pbt/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fc from 'fast-check';
|
|
2
|
-
import { C as Cmd,
|
|
2
|
+
import { C as Cmd, c as Sub, M as Machine, g as Reducer, i as Transitions, U as UpdateForm } from '../core-Ce9ncpuv.js';
|
|
3
3
|
import '@standard-schema/spec';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -16,12 +16,27 @@ import '@standard-schema/spec';
|
|
|
16
16
|
* property accessor.
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import type { Machine } from "@demlik/tea";
|
|
21
|
+
* import {
|
|
22
|
+
* arbMsgSequence,
|
|
23
|
+
* propertyTerminates,
|
|
24
|
+
* stubCtxThrowingProxy,
|
|
25
|
+
* } from "@demlik/tea/pbt";
|
|
26
|
+
* import type * as fc from "fast-check";
|
|
27
|
+
*
|
|
28
|
+
* type State = { type: "idle" } | { type: "done" };
|
|
29
|
+
* type Msg = { type: "go" };
|
|
30
|
+
* interface MyCtx { readonly someField: string }
|
|
31
|
+
* declare const machine: Machine<State, Msg, never, never, MyCtx>;
|
|
32
|
+
* declare const msgArb: fc.Arbitrary<Msg>;
|
|
33
|
+
*
|
|
34
|
+
* const ctx = stubCtxThrowingProxy<MyCtx>();
|
|
35
|
+
* propertyTerminates(machine, ctx, arbMsgSequence(msgArb), (s) => s.type === "done");
|
|
36
|
+
* // If anything reads ctx.someField during the run, an Error throws:
|
|
37
|
+
* // "@demlik/tea/pbt: ctx access denied (PBT operates on the pure
|
|
38
|
+
* // reducer; ctx field 'someField' was read)"
|
|
39
|
+
* ```
|
|
25
40
|
*/
|
|
26
41
|
declare function stubCtxThrowingProxy<Ctx>(): Ctx;
|
|
27
42
|
|
|
@@ -32,11 +47,20 @@ declare function stubCtxThrowingProxy<Ctx>(): Ctx;
|
|
|
32
47
|
* (same load-bearing discipline as `Reducer<S, M, C>` in the substrate).
|
|
33
48
|
*
|
|
34
49
|
* @example
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import {
|
|
52
|
+
* arbConstantMsg,
|
|
53
|
+
* arbRecordMsg,
|
|
54
|
+
* type MsgArbitraryTable,
|
|
55
|
+
* } from "@demlik/tea/pbt";
|
|
56
|
+
* import * as fc from "fast-check";
|
|
57
|
+
*
|
|
58
|
+
* type Msg = { type: "start" } | { type: "tick"; now: number };
|
|
59
|
+
* const table: MsgArbitraryTable<Msg> = {
|
|
60
|
+
* start: arbConstantMsg("start"),
|
|
61
|
+
* tick: arbRecordMsg("tick", { now: fc.integer({ min: 0 }) }),
|
|
62
|
+
* };
|
|
63
|
+
* ```
|
|
40
64
|
*/
|
|
41
65
|
type MsgArbitraryTable<M extends {
|
|
42
66
|
type: string;
|
|
@@ -55,11 +79,17 @@ type MsgArbitraryTable<M extends {
|
|
|
55
79
|
* Variants without an explicit weight default to 1.
|
|
56
80
|
*
|
|
57
81
|
* @example
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
82
|
+
* ```ts
|
|
83
|
+
* import { arbConstantMsg, arbMsg, arbRecordMsg } from "@demlik/tea/pbt";
|
|
84
|
+
* import * as fc from "fast-check";
|
|
85
|
+
*
|
|
86
|
+
* type Msg = { type: "start" } | { type: "tick"; now: number } | { type: "stop" };
|
|
87
|
+
* const msgArb = arbMsg<Msg>({
|
|
88
|
+
* start: arbConstantMsg("start"),
|
|
89
|
+
* tick: arbRecordMsg("tick", { now: fc.integer() }),
|
|
90
|
+
* stop: arbConstantMsg("stop"),
|
|
91
|
+
* }, { weights: { tick: 5, start: 1, stop: 1 } });
|
|
92
|
+
* ```
|
|
63
93
|
*/
|
|
64
94
|
declare function arbMsg<M extends {
|
|
65
95
|
type: string;
|
|
@@ -71,7 +101,12 @@ declare function arbMsg<M extends {
|
|
|
71
101
|
* `fc.constant({ type })` that narrows the result to `{ type: T }` literal.
|
|
72
102
|
*
|
|
73
103
|
* @example
|
|
74
|
-
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { arbConstantMsg } from "@demlik/tea/pbt";
|
|
106
|
+
* import type * as fc from "fast-check";
|
|
107
|
+
*
|
|
108
|
+
* const stop: fc.Arbitrary<{ type: "stop" }> = arbConstantMsg("stop");
|
|
109
|
+
* ```
|
|
75
110
|
*/
|
|
76
111
|
declare function arbConstantMsg<T extends string>(type: T): fc.Arbitrary<{
|
|
77
112
|
type: T;
|
|
@@ -82,8 +117,13 @@ declare function arbConstantMsg<T extends string>(type: T): fc.Arbitrary<{
|
|
|
82
117
|
* discriminator literal and combines with the per-field arbitraries.
|
|
83
118
|
*
|
|
84
119
|
* @example
|
|
85
|
-
*
|
|
86
|
-
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { arbRecordMsg } from "@demlik/tea/pbt";
|
|
122
|
+
* import * as fc from "fast-check";
|
|
123
|
+
*
|
|
124
|
+
* const tick: fc.Arbitrary<{ type: "tick"; now: number }> =
|
|
125
|
+
* arbRecordMsg("tick", { now: fc.integer({ min: 0 }) });
|
|
126
|
+
* ```
|
|
87
127
|
*/
|
|
88
128
|
declare function arbRecordMsg<T extends string, R>(type: T, fields: {
|
|
89
129
|
[K in keyof R]: fc.Arbitrary<R[K]>;
|
|
@@ -104,8 +144,16 @@ declare function arbRecordMsg<T extends string, R>(type: T, fields: {
|
|
|
104
144
|
* `auditing.ws:client:hello` cell on the first transition).
|
|
105
145
|
*
|
|
106
146
|
* @example
|
|
107
|
-
*
|
|
108
|
-
*
|
|
147
|
+
* ```ts
|
|
148
|
+
* import { arbMsgSequence } from "@demlik/tea/pbt";
|
|
149
|
+
* import type * as fc from "fast-check";
|
|
150
|
+
*
|
|
151
|
+
* type Msg = { type: "ws:client:hello" } | { type: "tick" };
|
|
152
|
+
* declare const msgArb: fc.Arbitrary<Msg>;
|
|
153
|
+
*
|
|
154
|
+
* arbMsgSequence(msgArb, { minLength: 1, maxLength: 30 });
|
|
155
|
+
* arbMsgSequence(msgArb, { prefix: [{ type: "ws:client:hello" }], maxLength: 20 });
|
|
156
|
+
* ```
|
|
109
157
|
*/
|
|
110
158
|
declare function arbMsgSequence<M extends {
|
|
111
159
|
type: string;
|
|
@@ -160,10 +208,21 @@ interface Step<S, M, C> {
|
|
|
160
208
|
* @param msgs The Msg sequence to fold.
|
|
161
209
|
*
|
|
162
210
|
* @example
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
211
|
+
* ```ts
|
|
212
|
+
* import type { Machine } from "@demlik/tea";
|
|
213
|
+
* import { foldEvents } from "@demlik/tea/pbt";
|
|
214
|
+
*
|
|
215
|
+
* type State = { type: "idle" } | { type: "counting"; n: number } | { type: "stopped" };
|
|
216
|
+
* type Msg = { type: "start" } | { type: "tick" } | { type: "stop" };
|
|
217
|
+
* interface Ctx { readonly tabId: number }
|
|
218
|
+
* declare const machine: Machine<State, Msg, never, never, Ctx>;
|
|
219
|
+
* declare const ctx: Ctx;
|
|
220
|
+
*
|
|
221
|
+
* const { states, steps, finalState } = foldEvents(machine, ctx, null, [
|
|
222
|
+
* { type: "start" }, { type: "tick" }, { type: "stop" },
|
|
223
|
+
* ]);
|
|
224
|
+
* // steps[1] = { prev: { type: "counting", n: 0 }, msg: { type: "tick" }, ... }
|
|
225
|
+
* ```
|
|
167
226
|
*/
|
|
168
227
|
declare function foldEvents<S, M extends {
|
|
169
228
|
type: string;
|
|
@@ -192,13 +251,30 @@ interface PropertyOpts<S> {
|
|
|
192
251
|
* (every msg variant is a noop from a given state).
|
|
193
252
|
*
|
|
194
253
|
* @example
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
254
|
+
* ```ts
|
|
255
|
+
* import type { Machine } from "@demlik/tea";
|
|
256
|
+
* import {
|
|
257
|
+
* arbMsgSequence,
|
|
258
|
+
* propertyTerminates,
|
|
259
|
+
* stubCtxThrowingProxy,
|
|
260
|
+
* } from "@demlik/tea/pbt";
|
|
261
|
+
* import type * as fc from "fast-check";
|
|
262
|
+
*
|
|
263
|
+
* type State = { type: "idle" } | { type: "auditing" } | { type: "done" };
|
|
264
|
+
* type Msg = { type: "start_audit" } | { type: "ws:audit:done" };
|
|
265
|
+
* interface BackgroundCtx { readonly tabId: number }
|
|
266
|
+
* declare const auditBackgroundMachine: Machine<State, Msg, never, never, BackgroundCtx>;
|
|
267
|
+
* declare const msgArb: fc.Arbitrary<Msg>;
|
|
268
|
+
* const auditingState: State = { type: "auditing" };
|
|
269
|
+
*
|
|
270
|
+
* propertyTerminates(
|
|
271
|
+
* auditBackgroundMachine,
|
|
272
|
+
* stubCtxThrowingProxy<BackgroundCtx>(),
|
|
273
|
+
* arbMsgSequence(msgArb, { minLength: 1, maxLength: 30 }),
|
|
274
|
+
* (s) => s.type === "done" || s.type === "idle",
|
|
275
|
+
* { loaded: auditingState, numRuns: 500 },
|
|
276
|
+
* );
|
|
277
|
+
* ```
|
|
202
278
|
*/
|
|
203
279
|
declare function propertyTerminates<S, M extends {
|
|
204
280
|
type: string;
|
|
@@ -213,16 +289,31 @@ declare function propertyTerminates<S, M extends {
|
|
|
213
289
|
* that still violates the predicate.
|
|
214
290
|
*
|
|
215
291
|
* @example
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
292
|
+
* ```ts
|
|
293
|
+
* import type { Machine } from "@demlik/tea";
|
|
294
|
+
* import { arbMsgSequence, propertyInvariant } from "@demlik/tea/pbt";
|
|
295
|
+
* import type * as fc from "fast-check";
|
|
296
|
+
*
|
|
297
|
+
* type State = { type: "idle" } | { type: "auditing" };
|
|
298
|
+
* type Msg = { type: "start_audit" } | { type: "ws:client:hello" };
|
|
299
|
+
* type Cmd = { type: "send_ws"; msg: { type: "client:ready" } };
|
|
300
|
+
* interface BackgroundCtx { readonly tabId: number }
|
|
301
|
+
* declare const auditBackgroundMachine: Machine<State, Msg, Cmd, never, BackgroundCtx>;
|
|
302
|
+
* declare const ctx: BackgroundCtx;
|
|
303
|
+
* declare const msgArb: fc.Arbitrary<Msg>;
|
|
304
|
+
* const auditingState: State = { type: "auditing" };
|
|
305
|
+
*
|
|
306
|
+
* // Pin the resume-handshake fix: `ws:client:hello` in auditing must
|
|
307
|
+
* // emit `send_ws { client:ready }`.
|
|
308
|
+
* propertyInvariant(
|
|
309
|
+
* auditBackgroundMachine, ctx,
|
|
310
|
+
* arbMsgSequence(msgArb, { prefix: [{ type: "ws:client:hello" }] }),
|
|
311
|
+
* ({ prev, msg, cmds }) =>
|
|
312
|
+
* !(prev.type === "auditing" && msg.type === "ws:client:hello") ||
|
|
313
|
+
* cmds.some((c) => c.type === "send_ws" && c.msg.type === "client:ready"),
|
|
314
|
+
* { loaded: auditingState, numRuns: 200 },
|
|
315
|
+
* );
|
|
316
|
+
* ```
|
|
226
317
|
*/
|
|
227
318
|
declare function propertyInvariant<S, M extends {
|
|
228
319
|
type: string;
|
|
@@ -234,13 +325,27 @@ declare function propertyInvariant<S, M extends {
|
|
|
234
325
|
* predicates that need the full sequence, not a single step.
|
|
235
326
|
*
|
|
236
327
|
* @example
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
328
|
+
* ```ts
|
|
329
|
+
* import type { Machine } from "@demlik/tea";
|
|
330
|
+
* import { propertyTrace } from "@demlik/tea/pbt";
|
|
331
|
+
* import type * as fc from "fast-check";
|
|
332
|
+
*
|
|
333
|
+
* type State = { type: "auditing" } | { type: "done" };
|
|
334
|
+
* type Msg = { type: "ws:audit:progress" } | { type: "ws:audit:done" };
|
|
335
|
+
* interface Ctx { readonly tabId: number }
|
|
336
|
+
* declare const machine: Machine<State, Msg, never, never, Ctx>;
|
|
337
|
+
* declare const ctx: Ctx;
|
|
338
|
+
* declare const seqArb: fc.Arbitrary<readonly Msg[]>;
|
|
339
|
+
* const auditingState: State = { type: "auditing" };
|
|
340
|
+
*
|
|
341
|
+
* propertyTrace(
|
|
342
|
+
* machine, ctx, seqArb,
|
|
343
|
+
* (steps, final) =>
|
|
344
|
+
* final.type !== "done" ||
|
|
345
|
+
* steps.some((step) => step.msg.type === "ws:audit:done"),
|
|
346
|
+
* { loaded: auditingState, numRuns: 500 },
|
|
347
|
+
* );
|
|
348
|
+
* ```
|
|
244
349
|
*/
|
|
245
350
|
declare function propertyTrace<S, M extends {
|
|
246
351
|
type: string;
|
|
@@ -262,9 +367,17 @@ declare function propertyTrace<S, M extends {
|
|
|
262
367
|
* the union.
|
|
263
368
|
*
|
|
264
369
|
* @example
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
370
|
+
* ```ts
|
|
371
|
+
* import type { Machine } from "@demlik/tea";
|
|
372
|
+
* import { msgTypeKeys } from "@demlik/tea/pbt";
|
|
373
|
+
*
|
|
374
|
+
* type State = { type: "idle" } | { type: "auditing" };
|
|
375
|
+
* type Msg = { type: "start_audit" } | { type: "stop_audit" } | { type: "window_created" };
|
|
376
|
+
* declare const auditBackgroundMachine: Machine<State, Msg, never, never, unknown>;
|
|
377
|
+
*
|
|
378
|
+
* msgTypeKeys(auditBackgroundMachine);
|
|
379
|
+
* // → ["start_audit", "stop_audit", "window_created"]
|
|
380
|
+
* ```
|
|
268
381
|
*/
|
|
269
382
|
declare function msgTypeKeys<S, M extends {
|
|
270
383
|
type: string;
|
package/dist/pbt/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import '../chunk-
|
|
2
|
-
import { replay, applyCell, msgKeysOf } from '../chunk-
|
|
1
|
+
import '../chunk-FNGZMVIT.js';
|
|
2
|
+
import { replay, applyCell, msgKeysOf } from '../chunk-AIIXV4XB.js';
|
|
3
3
|
import * as fc from 'fast-check';
|
|
4
4
|
|
|
5
5
|
// src/pbt/arbitraries/ctx.ts
|
package/dist/pbt/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pbt/arbitraries/ctx.ts","../../src/pbt/arbitraries/msg.ts","../../src/pbt/arbitraries/sequence.ts","../../src/pbt/runners/replay-fold.ts","../../src/pbt/runners/property.ts","../../src/pbt/util/msg-keys.ts"],"names":["fc2","fc3"],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `stubCtxThrowingProxy<Ctx>()` — a Proxy that satisfies the Ctx type-shape\n// at compile time but throws on every property access at runtime.\n//\n// Why this exists: PBT runs the pure reducer surface ONLY — `replay` never\n// invokes `interpret` and never starts a Sub, so Ctx should never be\n// dereferenced during property generation. But the substrate's `replay`\n// signature requires a `ctx: Ctx` value at compile time (it forwards ctx\n// into `init`, which on some machines IS pure but on others reads ctx). A\n// hand-rolled `{} as Ctx` would silently pass through any accidental ctx\n// access — exactly the silent footgun this proxy catches.\n//\n// Open question 2 (design doc): we ship this as the default. Tests that\n// genuinely need a working ctx (because their `init` reads from it) can\n// pass a real ctx value instead — the proxy is opt-in, not load-bearing.\n//\n// Strengthens invariant 6 (no silent failures — accidental ctx reads\n// produce a useful error, not a `undefined.foo` confusion later).\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Ctx value whose every property access throws. Use as the `ctx`\n * argument to PBT runners when the reducer under test is pure (does not\n * read from ctx). Accidental ctx access during property generation\n * surfaces as a labelled error instead of a silent `undefined` or\n * downstream type-error.\n *\n * Returns a typed `Ctx` (via `as unknown as Ctx`) — the proxy satisfies the\n * type-shape requirement at the call site; the runtime contract is\n * \"throw on first read.\" The `as unknown` cast is the one boundary cast\n * this helper requires; the proxy is structurally a `Ctx` for every\n * property accessor.\n *\n * @example\n * import { stubCtxThrowingProxy } from \"../index\";\n * const ctx = stubCtxThrowingProxy<MyCtx>();\n * propertyTerminates(machine, ctx, ...);\n * // If anything reads ctx.someField during the run, an Error throws:\n * // \"@demlik/tea/pbt: ctx access denied (PBT operates on the pure\n * // reducer; ctx field 'someField' was read)\"\n */\nexport function stubCtxThrowingProxy<Ctx>(): Ctx {\n const handler: ProxyHandler<object> = {\n get(_target, prop: string | symbol): unknown {\n // Allow vitest / fast-check internals to introspect via Symbol-keyed\n // checks (e.g. Symbol.toStringTag, Symbol.iterator). Returning\n // undefined on symbol keys keeps property-tree walks cheap; the\n // throw fires on real domain field reads.\n if (typeof prop === \"symbol\") return undefined;\n // Some shrinkers / pretty-printers in fast-check probe a small set\n // of conventional field names (`toJSON`, `toString`, `valueOf`,\n // `then` — the last for thenable-check). Return undefined for those\n // so the proxy doesn't fail a probe before the property body runs.\n if (\n prop === \"toJSON\" ||\n prop === \"toString\" ||\n prop === \"valueOf\" ||\n prop === \"then\"\n ) {\n return undefined;\n }\n throw new Error(\n `@demlik/tea/pbt: ctx access denied (PBT operates on the pure reducer; ` +\n `ctx field '${prop}' was read). Pass a real ctx value if the reducer's ` +\n `init reads from ctx.`,\n );\n },\n has(): boolean {\n return false;\n },\n ownKeys(): ArrayLike<string | symbol> {\n return [];\n },\n };\n return new Proxy({}, handler) as unknown as Ctx;\n}\n","// ---------------------------------------------------------------------------\n// Msg arbitrary builders.\n//\n// `MsgArbitraryTable<M>` is a strict mapped type — every Msg variant must\n// have an arbitrary entry, keyed by `M[\"type\"]`, narrowing the arbitrary's\n// `T` to `Extract<M, { type: K }>`. This mirrors the load-bearing discipline\n// of `Reducer<S, M, C>` and `Transitions<S, M, C>` in the substrate: adding\n// a Msg variant without an arbitrary fails to compile, not at runtime.\n//\n// `arbMsg(table, opts?)` collapses the table into a single `fc.Arbitrary<M>`\n// via `fc.oneof`. Optional per-variant weighting for sequence-skew tests.\n//\n// `arbConstantMsg(type)` / `arbRecordMsg(type, fields)` are sugar over\n// `fc.constant({ type })` / `fc.record({ type: fc.constant(type), ...fields })`.\n// They exist because every Msg variant arbitrary either (a) carries no\n// payload — `arbConstantMsg(\"stop\")` — or (b) carries a record of fields —\n// `arbRecordMsg(\"tick\", { now: fc.integer() })`. Naming the two shapes\n// prevents callers from hand-rolling `fc.record({ type: fc.constant(\"...\") })`\n// inconsistently across test files.\n//\n// Open question 1 from the design doc (strict vs Partial table): strict by\n// default. The mapped type catches drift at compile time; `arbMsgPartial`\n// sugar is a future addition iff friction becomes real.\n// ---------------------------------------------------------------------------\n\nimport * as fc from \"fast-check\";\n\n/**\n * Strict per-variant Msg arbitrary table. Mapped type: every key in\n * `M[\"type\"]` MUST have an arbitrary entry whose `T` is the narrowed\n * variant. Adding a new Msg variant without an entry is a compile error\n * (same load-bearing discipline as `Reducer<S, M, C>` in the substrate).\n *\n * @example\n * type Msg = { type: \"start\" } | { type: \"tick\"; now: number };\n * const table: MsgArbitraryTable<Msg> = {\n * start: arbConstantMsg(\"start\"),\n * tick: arbRecordMsg(\"tick\", { now: fc.integer({ min: 0 }) }),\n * };\n */\nexport type MsgArbitraryTable<M extends { type: string }> = {\n [K in M[\"type\"]]: fc.Arbitrary<Extract<M, { type: K }>>;\n};\n\n/**\n * Build a single `fc.Arbitrary<M>` from a `MsgArbitraryTable<M>`. Picks one\n * variant per generation via `fc.oneof`; optional per-variant `weights`\n * bias the distribution (default uniform).\n *\n * @param table Per-variant arbitrary table (strict — every variant required).\n * @param opts Optional bias: `weights` is a `Partial<Record<M[\"type\"], number>>`.\n * Variants without an explicit weight default to 1.\n *\n * @example\n * const msgArb = arbMsg<Msg>({\n * start: arbConstantMsg(\"start\"),\n * tick: arbRecordMsg(\"tick\", { now: fc.integer() }),\n * stop: arbConstantMsg(\"stop\"),\n * }, { weights: { tick: 5, start: 1, stop: 1 } });\n */\nexport function arbMsg<M extends { type: string }>(\n table: MsgArbitraryTable<M>,\n opts?: { weights?: Partial<Record<M[\"type\"], number>> },\n): fc.Arbitrary<M> {\n const weights = opts?.weights;\n const entries = Object.entries(table) as [M[\"type\"], fc.Arbitrary<M>][];\n if (entries.length === 0) {\n throw new Error(\"@demlik/tea/pbt: arbMsg table is empty\");\n }\n const weighted = entries.map(([key, arb]) => ({\n arbitrary: arb,\n weight: weights?.[key] ?? 1,\n }));\n return fc.oneof(...weighted);\n}\n\n/**\n * Build a payload-free Msg variant arbitrary. Sugar for\n * `fc.constant({ type })` that narrows the result to `{ type: T }` literal.\n *\n * @example\n * arbConstantMsg(\"stop\") // fc.Arbitrary<{ type: \"stop\" }>\n */\nexport function arbConstantMsg<T extends string>(\n type: T,\n): fc.Arbitrary<{ type: T }> {\n return fc.constant({ type });\n}\n\n/**\n * Build a record-shaped Msg variant arbitrary. Sugar for\n * `fc.record({ type: fc.constant(type), ...fields })` that stamps the\n * discriminator literal and combines with the per-field arbitraries.\n *\n * @example\n * arbRecordMsg(\"tick\", { now: fc.integer({ min: 0 }) })\n * // fc.Arbitrary<{ type: \"tick\"; now: number }>\n */\nexport function arbRecordMsg<T extends string, R>(\n type: T,\n fields: { [K in keyof R]: fc.Arbitrary<R[K]> },\n): fc.Arbitrary<{ type: T } & R> {\n // `fc.record` builds an arbitrary of the merged-shape record. The\n // `type: fc.constant(type)` field stamps the literal discriminator;\n // the spread of `fields` provides every payload field's arbitrary.\n //\n // The intermediate `as Record<string, fc.Arbitrary<unknown>>` is the\n // single load-bearing cast — fc.record's parameter type is invariant in\n // its inferred field shape, and the merged literal-and-generic spread\n // doesn't unify with the strict `{ [K in keyof R]: Arbitrary<R[K]> }`\n // shape TS computes for us. The outer cast to `fc.Arbitrary<{ type: T }\n // & R>` restates the result shape we know to be correct at this\n // boundary. Mirrors the same discipline `fc.record` callers use\n // throughout the prior-art branch.\n const recordArbitraries: Record<string, fc.Arbitrary<unknown>> = {\n type: fc.constant(type),\n ...(fields as Record<string, fc.Arbitrary<unknown>>),\n };\n return fc.record(recordArbitraries) as unknown as fc.Arbitrary<\n { type: T } & R\n >;\n}\n","// ---------------------------------------------------------------------------\n// Msg-sequence arbitraries.\n//\n// `arbMsgSequence(msgArb, opts)` produces `fc.Arbitrary<readonly M[]>` —\n// fast-check's default-shrinker handles the array; the per-element shrinker\n// inside `msgArb` shrinks each variant. Optional `prefix` is prepended\n// verbatim (used by Test A: \"the bug-triggering Msg leads the sequence\").\n//\n// `arbGuidedSequence` is the state-aware sequence builder. In v1 it throws\n// — Phase 6 of the design doc lands the `fc.commands`-backed implementation.\n// The export exists today so the surface is forward-compatible: consumers\n// that need state-aware generation get a useful error pointing at the\n// design doc instead of \"is not a function.\"\n// ---------------------------------------------------------------------------\n\nimport * as fc from \"fast-check\";\nimport type { Cmd, Machine, Sub } from \"../../index\";\n\n/**\n * Build an arbitrary of Msg sequences over a per-Msg arbitrary. Optionally\n * bounded in length and / or prefixed with a fixed sequence.\n *\n * - `minLength` (default 0) and `maxLength` (default 30) bound the random\n * array length. `prefix` is prepended AFTER the random tail is built; the\n * final length is `prefix.length + tail.length`.\n * - `prefix` is the load-bearing escape hatch for \"the first msg is the\n * one that exercises the cell I care about\" (e.g. Test A's\n * `[{ type: \"ws:client:hello\" }]` prefix to land in the\n * `auditing.ws:client:hello` cell on the first transition).\n *\n * @example\n * arbMsgSequence(msgArb, { minLength: 1, maxLength: 30 });\n * arbMsgSequence(msgArb, { prefix: [{ type: \"ws:client:hello\" }], maxLength: 20 });\n */\nexport function arbMsgSequence<M extends { type: string }>(\n msgArb: fc.Arbitrary<M>,\n opts?: {\n readonly minLength?: number;\n readonly maxLength?: number;\n readonly prefix?: readonly NoInfer<M>[];\n },\n): fc.Arbitrary<readonly M[]> {\n const minLength = opts?.minLength ?? 0;\n const maxLength = opts?.maxLength ?? 30;\n const prefix = opts?.prefix ?? [];\n const tail = fc.array(msgArb, { minLength, maxLength });\n if (prefix.length === 0) return tail;\n return tail.map((arr) => [...prefix, ...arr]);\n}\n\n/**\n * State-aware Msg sequence arbitrary — generates only msgs whose\n * precondition holds in the current state. Backed by `fc.commands`\n * internally. Planned for a later phase.\n *\n * **NOT IMPLEMENTED IN v1.** The export exists today so the surface is\n * forward-compatible; this ships in a later phase alongside\n * shrinkers. Calling it throws.\n */\nexport function arbGuidedSequence<\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: Ctx,\n _cells: {\n [K in M[\"type\"]]?: (\n state: S,\n ) => fc.Arbitrary<Extract<M, { type: K }>> | null;\n },\n _opts?: { maxLength?: number },\n): fc.Arbitrary<readonly M[]> {\n throw new Error(\n \"@demlik/tea/pbt: arbGuidedSequence is not implemented in v1. \" +\n \"Use arbMsgSequence with \" +\n \"an unguided arbMsg table for now; states that don't accept a given \" +\n \"msg should noop in the reducer (mapped-type discipline).\",\n );\n}\n","// ---------------------------------------------------------------------------\n// `foldEvents` — pure deterministic fold over a Msg sequence.\n//\n// PBT needs the per-step (prev/msg/next/cmds) structure to assert\n// step-shaped invariants. The substrate's `replay(machine, opts)` returns\n// only final state + cumulative cmds — insufficient. We use `replay` for\n// the boot step (calls `machine.init(loaded, ctx)` faithfully) then walk\n// msgs ourselves through `applyCell`, the substrate's own cell dispatch.\n//\n// Why walk cells directly instead of looping `replay` once per msg with\n// `loaded: prevState`? Because `init(loaded)` runs every call, and on\n// machines with non-trivial init transforms (e.g. the audit machine's\n// `auditing → attach_debugger` resume cell) that would either (a) re-fire\n// the transform on every step, or (b) require fragile cmd-subtraction to\n// isolate this step's cmds. Calling the cells directly keeps the fold pure\n// and the cmds per-step exactly.\n//\n// Dispatches through `applyCell` — the single primitive `run`/`replay` use —\n// so form classification agrees with production by construction (#275).\n//\n// `foldEvents` does NOT call `interpret`, does NOT touch a Store, does NOT\n// start Subs. The reducer surface is the contract.\n// ---------------------------------------------------------------------------\n\nimport {\n applyCell,\n type Cmd,\n type Machine,\n replay,\n type Sub,\n} from \"../../index\";\n\n/**\n * One unit in a fold trace — the four pieces of data a property\n * invariant typically asserts on. `prev` is the state before the msg\n * applied; `next` is the state after; `cmds` is the cmds emitted by this\n * single step (NOT the running total).\n */\nexport interface Step<S, M, C> {\n readonly prev: S;\n readonly msg: M;\n readonly next: S;\n readonly cmds: readonly C[];\n}\n\n/**\n * Fold a Msg sequence through a machine's pure reducer, returning the\n * per-step trace, every state in order, and the final state. Initial\n * state comes from `replay(machine, { msgs: [], ctx, loaded })` — i.e.\n * `machine.init(loaded ?? null, ctx)` faithfully. Init's emitted cmds\n * are NOT included in any `Step` (they're the boot moment, not a Msg).\n *\n * @param machine The machine under test.\n * @param ctx The Ctx — typically `stubCtxThrowingProxy<Ctx>()` when\n * init is pure. Pass a real ctx if init reads from it.\n * @param loaded Optional pre-init snapshot (the `loaded` arg to\n * `machine.init`). Defaults to `null` (fresh boot).\n * @param msgs The Msg sequence to fold.\n *\n * @example\n * const { states, steps, finalState } = foldEvents(machine, ctx, null, [\n * { type: \"start\" }, { type: \"tick\" }, { type: \"stop\" },\n * ]);\n * // steps[1] = { prev: { type: \"counting\", n: 0 }, msg: { type: \"tick\" }, ... }\n */\nexport function foldEvents<\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 loaded: NoInfer<S> | null,\n msgs: readonly NoInfer<M>[],\n): {\n readonly states: readonly S[];\n readonly steps: readonly Step<S, M, C>[];\n readonly finalState: S;\n} {\n // Boot through `replay` so `init` runs faithfully and we honor the\n // substrate's loaded-snapshot contract (init may transform the loaded\n // value before the first Msg lands — e.g. the audit machine flips\n // `debuggerAttached: false` on resume).\n const { state: initialState } = replay(machine, {\n msgs: [],\n ctx,\n loaded: loaded ?? null,\n });\n\n // Per-msg dispatch goes through `applyCell` — the same primitive `run` and\n // `replay` use — so this runner classifies the update form identically to\n // production (`formOf` honors the `__form` tag; #275).\n const states: S[] = [initialState];\n const steps: Step<S, M, C>[] = [];\n let current = initialState;\n\n for (const msg of msgs) {\n const [next, emitted] = applyCell<S, M, C>(machine, current, msg);\n steps.push({ prev: current, msg, next, cmds: [...emitted] });\n states.push(next);\n current = next;\n }\n\n return { states, steps, finalState: current };\n}\n","// ---------------------------------------------------------------------------\n// Property runners — three thin wrappers over `fc.assert(fc.property(...))`\n// that fold a generated Msg sequence through the machine via `foldEvents`\n// and pipe the result into a TEA-shaped predicate.\n//\n// - `propertyTerminates` — every sequence ends in a terminal state.\n// - `propertyInvariant` — predicate holds on every (prev, msg, next, cmds) step.\n// - `propertyTrace` — predicate holds on the full (trace, final) shape.\n//\n// Each runner accepts `numRuns` (passed through to fast-check), an optional\n// `loaded` snapshot (forwarded to `foldEvents`), and the Msg-sequence\n// arbitrary. They return `void`; failures throw via fast-check's standard\n// shrinking output.\n//\n// Default shrinker is fast-check's built-in (open question 4): drop-msg /\n// prefix shrinkers can be layered later but are opt-in, not default. The\n// built-in shrinker handles array-shape shrinking + per-element variant\n// shrinking automatically.\n// ---------------------------------------------------------------------------\n\nimport * as fc from \"fast-check\";\nimport type { Cmd, Machine, Sub } from \"../../index\";\nimport { foldEvents, type Step } from \"./replay-fold\";\n\n/** Common options shared by every property runner. */\ninterface PropertyOpts<S> {\n /** Pre-init snapshot — passed to `machine.init(loaded, ctx)`. Default `null`. */\n readonly loaded?: S | null;\n /** Number of generated sequences to test. Forwarded to `fc.assert`. */\n readonly numRuns?: number;\n /** Optional fast-check seed for reproducibility. */\n readonly seed?: number;\n}\n\n/** The `foldEvents` result a runner's `check` inspects. */\ntype FoldResult<S, M, C> = {\n readonly states: readonly S[];\n readonly steps: readonly Step<S, M, C>[];\n readonly finalState: S;\n};\n\n/**\n * Build the fast-check parameters shared by every runner: the 200-default\n * `numRuns` plus an optional `seed`. Single source of truth so a change to\n * the default lands in one place.\n */\nfunction resolveFcParams<M>(\n opts: PropertyOpts<unknown> | undefined,\n): fc.Parameters<[readonly M[]]> {\n return {\n numRuns: opts?.numRuns ?? 200,\n ...(opts?.seed !== undefined ? { seed: opts.seed } : {}),\n };\n}\n\n/**\n * The fold+assert core every runner shares: assert `check` over each\n * generated Msg sequence, folded once through the machine via `foldEvents`.\n * `check` throws on failure so the error message carries the offending\n * shape into fast-check's shrunk counter-example.\n */\nfunction runProperty<\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: Ctx,\n seqArb: fc.Arbitrary<readonly M[]>,\n opts: PropertyOpts<S> | undefined,\n check: (result: FoldResult<S, M, C>) => void,\n): void {\n fc.assert(\n fc.property(seqArb, (msgs) => {\n check(foldEvents(machine, ctx, opts?.loaded ?? null, msgs));\n }),\n resolveFcParams<M>(opts),\n );\n}\n\n/**\n * Assert every generated Msg sequence ends in a terminal state. `terminal`\n * is the predicate that names the acceptable final states (often\n * `(s) => s.type === \"done\" || s.type === \"idle\"`).\n *\n * The \"every sequence\" property catches stuck states (machine gets to a\n * non-terminal state and no further msg can escape) AND silent loops\n * (every msg variant is a noop from a given state).\n *\n * @example\n * propertyTerminates(\n * auditBackgroundMachine,\n * stubCtxThrowingProxy<BackgroundCtx>(),\n * arbMsgSequence(msgArb, { minLength: 1, maxLength: 30 }),\n * (s) => s.type === \"done\" || s.type === \"idle\",\n * { loaded: auditingState, numRuns: 500 },\n * );\n */\nexport function propertyTerminates<\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 seqArb: fc.Arbitrary<readonly NoInfer<M>[]>,\n terminal: (state: NoInfer<S>) => boolean,\n opts?: NoInfer<PropertyOpts<S>>,\n): void {\n runProperty(machine, ctx, seqArb, opts, ({ finalState }) => {\n if (!terminal(finalState)) {\n // Throwing here (vs returning false) lets the error message carry\n // the offending final state — fast-check pretty-prints it inside\n // the shrunk counter-example.\n throw new Error(\n `propertyTerminates: final state did not match terminal predicate. ` +\n `Final: ${JSON.stringify(finalState)}`,\n );\n }\n });\n}\n\n/**\n * Assert a predicate holds on EVERY transition step in a generated\n * sequence. The predicate sees `{ prev, msg, next, cmds }` per step;\n * return true to accept, false (or throw) to fail.\n *\n * Use to pin a \"when X happens in state Y, cmd Z must be emitted\" rule.\n * The shrinker minimizes the offending sequence to the smallest Msg list\n * that still violates the predicate.\n *\n * @example\n * // Pin the resume-handshake fix: `ws:client:hello` in auditing must\n * // emit `send_ws { client:ready }`.\n * propertyInvariant(\n * auditBackgroundMachine, ctx,\n * arbMsgSequence(msgArb, { prefix: [{ type: \"ws:client:hello\" }] }),\n * ({ prev, msg, cmds }) =>\n * !(prev.type === \"auditing\" && msg.type === \"ws:client:hello\") ||\n * cmds.some((c) => c.type === \"send_ws\" && c.msg.type === \"client:ready\"),\n * { loaded: auditingState, numRuns: 200 },\n * );\n */\nexport function propertyInvariant<\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 seqArb: fc.Arbitrary<readonly NoInfer<M>[]>,\n invariant: (step: Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>) => boolean,\n opts?: NoInfer<PropertyOpts<S>>,\n): void {\n runProperty(machine, ctx, seqArb, opts, ({ steps }) => {\n for (const step of steps) {\n if (!invariant(step)) {\n throw new Error(\n `propertyInvariant: failed at step msg=${JSON.stringify(step.msg)} ` +\n `prev=${JSON.stringify(step.prev)} next=${JSON.stringify(step.next)} ` +\n `cmds=${JSON.stringify(step.cmds)}`,\n );\n }\n }\n });\n}\n\n/**\n * Assert a predicate over the full trace plus the final state. Use when\n * the property is \"some Msg appeared in the trace before reaching the\n * final state\" or \"no two consecutive steps had the same state.type\" —\n * predicates that need the full sequence, not a single step.\n *\n * @example\n * propertyTrace(\n * machine, ctx, seqArb,\n * (steps, final) =>\n * final.type !== \"done\" ||\n * steps.some((step) => step.msg.type === \"ws:audit:done\"),\n * { loaded: auditingState, numRuns: 500 },\n * );\n */\nexport function propertyTrace<\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 seqArb: fc.Arbitrary<readonly NoInfer<M>[]>,\n predicate: (\n steps: readonly Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>[],\n finalState: NoInfer<S>,\n ) => boolean,\n opts?: NoInfer<PropertyOpts<S>>,\n): void {\n runProperty(machine, ctx, seqArb, opts, ({ steps, finalState }) => {\n if (!predicate(steps, finalState)) {\n throw new Error(\n `propertyTrace: predicate failed on trace length=${steps.length}, ` +\n `final=${JSON.stringify(finalState)}`,\n );\n }\n });\n}\n","// ---------------------------------------------------------------------------\n// msgTypeKeys — read the Msg discriminant set at runtime from a machine's\n// Reducer record or Transitions table.\n//\n// TS Msg unions only exist at the type level; the discriminant strings are\n// erased at runtime. The reducer record / transitions table is the load-\n// bearing artifact that DOES carry the discriminants at runtime — its keys\n// (for Reducer) or any one cell's inner keys (for Transitions) are exactly\n// the set we need to build an `arbMsg` table without hand-listing them.\n//\n// Standalone helper: hand it a machine and it returns the Msg variants the\n// reducer/transitions record carries at runtime. `arbMsg` does NOT call it —\n// `MsgArbitraryTable<M>` enforces table coverage at compile time (adding a\n// variant without an arbitrary fails to compile, not at runtime). This helper\n// is for callers that want the discriminant set explicitly at runtime — e.g.\n// a test asserting a table covers every variant.\n// ---------------------------------------------------------------------------\n\nimport {\n type Cmd,\n msgKeysOf,\n type Reducer,\n type Transitions,\n type UpdateForm,\n} from \"../../index\";\n\n/**\n * Extract the Msg discriminant set at runtime from a machine. Useful for\n * asserting that a user-supplied `MsgArbitraryTable<M>` covers every variant\n * the reducer can handle.\n *\n * Takes the MACHINE (not the bare `update` record) so the reducer-vs-\n * transitions branch honors the authoritative `__form` tag via `msgKeysOf` —\n * the same classification production `run` uses (#275).\n *\n * - **Reducer form** — keys of the top-level record ARE the Msg variants.\n * - **Transitions form** — keys of any single state's inner record. Every\n * state's inner record must cover every Msg variant (mapped-type\n * discipline), so reading the first state's keys is equivalent to reading\n * the union.\n *\n * @example\n * import { auditBackgroundMachine } from \"./reducer\";\n * msgTypeKeys(auditBackgroundMachine);\n * // → [\"start_audit\", \"stop_audit\", \"window_created\", ...]\n */\nexport function msgTypeKeys<\n S,\n M extends { type: string },\n C extends Cmd,\n>(machine: {\n update:\n | Reducer<S, M, C>\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n __form?: UpdateForm;\n}): readonly string[] {\n return msgKeysOf(machine);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/pbt/arbitraries/ctx.ts","../../src/pbt/arbitraries/msg.ts","../../src/pbt/arbitraries/sequence.ts","../../src/pbt/runners/replay-fold.ts","../../src/pbt/runners/property.ts","../../src/pbt/util/msg-keys.ts"],"names":["fc2","fc3"],"mappings":";;;;;AAwDO,SAAS,oBAAA,GAAiC;AAC/C,EAAA,MAAM,OAAA,GAAgC;AAAA,IACpC,GAAA,CAAI,SAAS,IAAA,EAAgC;AAK3C,MAAA,IAAI,OAAO,IAAA,KAAS,QAAA,EAAU,OAAO,MAAA;AAKrC,MAAA,IACE,SAAS,QAAA,IACT,IAAA,KAAS,cACT,IAAA,KAAS,SAAA,IACT,SAAS,MAAA,EACT;AACA,QAAA,OAAO,MAAA;AAAA,MACT;AACA,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,oFACgB,IAAI,CAAA,wEAAA;AAAA,OAEtB;AAAA,IACF,CAAA;AAAA,IACA,GAAA,GAAe;AACb,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IACA,OAAA,GAAsC;AACpC,MAAA,OAAO,EAAC;AAAA,IACV;AAAA,GACF;AACA,EAAA,OAAO,IAAI,KAAA,CAAM,EAAC,EAAG,OAAO,CAAA;AAC9B;ACfO,SAAS,MAAA,CACd,OACA,IAAA,EACiB;AACjB,EAAA,MAAM,UAAU,IAAA,EAAM,OAAA;AACtB,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA;AACpC,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AACxB,IAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,EAC1D;AACA,EAAA,MAAM,WAAW,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAC,GAAA,EAAK,GAAG,CAAA,MAAO;AAAA,IAC5C,SAAA,EAAW,GAAA;AAAA,IACX,MAAA,EAAQ,OAAA,GAAU,GAAG,CAAA,IAAK;AAAA,GAC5B,CAAE,CAAA;AACF,EAAA,OAAU,EAAA,CAAA,KAAA,CAAM,GAAG,QAAQ,CAAA;AAC7B;AAcO,SAAS,eACd,IAAA,EAC2B;AAC3B,EAAA,OAAU,EAAA,CAAA,QAAA,CAAS,EAAE,IAAA,EAAM,CAAA;AAC7B;AAgBO,SAAS,YAAA,CACd,MACA,MAAA,EAC+B;AAa/B,EAAA,MAAM,iBAAA,GAA2D;AAAA,IAC/D,IAAA,EAAS,YAAS,IAAI,CAAA;AAAA,IACtB,GAAI;AAAA,GACN;AACA,EAAA,OAAU,UAAO,iBAAiB,CAAA;AAGpC;ACxGO,SAAS,cAAA,CACd,QACA,IAAA,EAK4B;AAC5B,EAAA,MAAM,SAAA,GAAY,MAAM,SAAA,IAAa,CAAA;AACrC,EAAA,MAAM,SAAA,GAAY,MAAM,SAAA,IAAa,EAAA;AACrC,EAAA,MAAM,MAAA,GAAS,IAAA,EAAM,MAAA,IAAU,EAAC;AAChC,EAAA,MAAM,OAAUA,EAAA,CAAA,KAAA,CAAM,MAAA,EAAQ,EAAE,SAAA,EAAW,WAAW,CAAA;AACtD,EAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAChC,EAAA,OAAO,IAAA,CAAK,IAAI,CAAC,GAAA,KAAQ,CAAC,GAAG,MAAA,EAAQ,GAAG,GAAG,CAAC,CAAA;AAC9C;AAWO,SAAS,iBAAA,CAOd,QAAA,EACA,IAAA,EACA,MAAA,EAKA,KAAA,EAC4B;AAC5B,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GAIF;AACF;;;ACbO,SAAS,UAAA,CAOd,OAAA,EACA,GAAA,EACA,MAAA,EACA,IAAA,EAKA;AAKA,EAAA,MAAM,EAAE,KAAA,EAAO,YAAA,EAAa,GAAI,OAAO,OAAA,EAAS;AAAA,IAC9C,MAAM,EAAC;AAAA,IACP,GAAA;AAAA,IACA,QAAQ,MAAA,IAAU;AAAA,GACnB,CAAA;AAKD,EAAA,MAAM,MAAA,GAAc,CAAC,YAAY,CAAA;AACjC,EAAA,MAAM,QAAyB,EAAC;AAChC,EAAA,IAAI,OAAA,GAAU,YAAA;AAEd,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,CAAC,IAAA,EAAM,OAAO,IAAI,SAAA,CAAmB,OAAA,EAAS,SAAS,GAAG,CAAA;AAChE,IAAA,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,OAAA,EAAS,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,CAAC,GAAG,OAAO,CAAA,EAAG,CAAA;AAC3D,IAAA,MAAA,CAAO,KAAK,IAAI,CAAA;AAChB,IAAA,OAAA,GAAU,IAAA;AAAA,EACZ;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,UAAA,EAAY,OAAA,EAAQ;AAC9C;;;ACvEA,SAAS,gBACP,IAAA,EAC+B;AAC/B,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,MAAM,OAAA,IAAW,GAAA;AAAA,IAC1B,GAAI,MAAM,IAAA,KAAS,MAAA,GAAY,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,EAAK,GAAI;AAAC,GACxD;AACF;AAQA,SAAS,WAAA,CAOP,OAAA,EACA,GAAA,EACA,MAAA,EACA,MACA,KAAA,EACM;AACN,EAAGC,EAAA,CAAA,MAAA;AAAA,IACEA,EAAA,CAAA,QAAA,CAAS,MAAA,EAAQ,CAAC,IAAA,KAAS;AAC5B,MAAA,KAAA,CAAM,WAAW,OAAA,EAAS,GAAA,EAAK,MAAM,MAAA,IAAU,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,IAC5D,CAAC,CAAA;AAAA,IACD,gBAAmB,IAAI;AAAA,GACzB;AACF;AAqCO,SAAS,kBAAA,CAOd,OAAA,EACA,GAAA,EACA,MAAA,EACA,UACA,IAAA,EACM;AACN,EAAA,WAAA,CAAY,SAAS,GAAA,EAAK,MAAA,EAAQ,MAAM,CAAC,EAAE,YAAW,KAAM;AAC1D,IAAA,IAAI,CAAC,QAAA,CAAS,UAAU,CAAA,EAAG;AAIzB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,yEAAA,EACY,IAAA,CAAK,SAAA,CAAU,UAAU,CAAC,CAAA;AAAA,OACxC;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AACH;AAsCO,SAAS,iBAAA,CAOd,OAAA,EACA,GAAA,EACA,MAAA,EACA,WACA,IAAA,EACM;AACN,EAAA,WAAA,CAAY,SAAS,GAAA,EAAK,MAAA,EAAQ,MAAM,CAAC,EAAE,OAAM,KAAM;AACrD,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,IAAI,CAAC,SAAA,CAAU,IAAI,CAAA,EAAG;AACpB,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,sCAAA,EAAyC,KAAK,SAAA,CAAU,IAAA,CAAK,GAAG,CAAC,CAAA,MAAA,EACvD,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAC,CAAA,MAAA,EAAS,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAC,SAC3D,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,SACrC;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AACH;AA+BO,SAAS,aAAA,CAOd,OAAA,EACA,GAAA,EACA,MAAA,EACA,WAIA,IAAA,EACM;AACN,EAAA,WAAA,CAAY,OAAA,EAAS,KAAK,MAAA,EAAQ,IAAA,EAAM,CAAC,EAAE,KAAA,EAAO,YAAW,KAAM;AACjE,IAAA,IAAI,CAAC,SAAA,CAAU,KAAA,EAAO,UAAU,CAAA,EAAG;AACjC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,mDAAmD,KAAA,CAAM,MAAM,WACpD,IAAA,CAAK,SAAA,CAAU,UAAU,CAAC,CAAA;AAAA,OACvC;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AACH;;;AC5MO,SAAS,YAId,OAAA,EAKoB;AACpB,EAAA,OAAO,UAAU,OAAO,CAAA;AAC1B","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// `stubCtxThrowingProxy<Ctx>()` — a Proxy that satisfies the Ctx type-shape\n// at compile time but throws on every property access at runtime.\n//\n// Why this exists: PBT runs the pure reducer surface ONLY — `replay` never\n// invokes `interpret` and never starts a Sub, so Ctx should never be\n// dereferenced during property generation. But the substrate's `replay`\n// signature requires a `ctx: Ctx` value at compile time (it forwards ctx\n// into `init`, which on some machines IS pure but on others reads ctx). A\n// hand-rolled `{} as Ctx` would silently pass through any accidental ctx\n// access — exactly the silent footgun this proxy catches.\n//\n// Open question 2 (design doc): we ship this as the default. Tests that\n// genuinely need a working ctx (because their `init` reads from it) can\n// pass a real ctx value instead — the proxy is opt-in, not load-bearing.\n//\n// Strengthens invariant 6 (no silent failures — accidental ctx reads\n// produce a useful error, not a `undefined.foo` confusion later).\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Ctx value whose every property access throws. Use as the `ctx`\n * argument to PBT runners when the reducer under test is pure (does not\n * read from ctx). Accidental ctx access during property generation\n * surfaces as a labelled error instead of a silent `undefined` or\n * downstream type-error.\n *\n * Returns a typed `Ctx` (via `as unknown as Ctx`) — the proxy satisfies the\n * type-shape requirement at the call site; the runtime contract is\n * \"throw on first read.\" The `as unknown` cast is the one boundary cast\n * this helper requires; the proxy is structurally a `Ctx` for every\n * property accessor.\n *\n * @example\n * ```ts\n * import type { Machine } from \"@demlik/tea\";\n * import {\n * arbMsgSequence,\n * propertyTerminates,\n * stubCtxThrowingProxy,\n * } from \"@demlik/tea/pbt\";\n * import type * as fc from \"fast-check\";\n *\n * type State = { type: \"idle\" } | { type: \"done\" };\n * type Msg = { type: \"go\" };\n * interface MyCtx { readonly someField: string }\n * declare const machine: Machine<State, Msg, never, never, MyCtx>;\n * declare const msgArb: fc.Arbitrary<Msg>;\n *\n * const ctx = stubCtxThrowingProxy<MyCtx>();\n * propertyTerminates(machine, ctx, arbMsgSequence(msgArb), (s) => s.type === \"done\");\n * // If anything reads ctx.someField during the run, an Error throws:\n * // \"@demlik/tea/pbt: ctx access denied (PBT operates on the pure\n * // reducer; ctx field 'someField' was read)\"\n * ```\n */\nexport function stubCtxThrowingProxy<Ctx>(): Ctx {\n const handler: ProxyHandler<object> = {\n get(_target, prop: string | symbol): unknown {\n // Allow vitest / fast-check internals to introspect via Symbol-keyed\n // checks (e.g. Symbol.toStringTag, Symbol.iterator). Returning\n // undefined on symbol keys keeps property-tree walks cheap; the\n // throw fires on real domain field reads.\n if (typeof prop === \"symbol\") return undefined;\n // Some shrinkers / pretty-printers in fast-check probe a small set\n // of conventional field names (`toJSON`, `toString`, `valueOf`,\n // `then` — the last for thenable-check). Return undefined for those\n // so the proxy doesn't fail a probe before the property body runs.\n if (\n prop === \"toJSON\" ||\n prop === \"toString\" ||\n prop === \"valueOf\" ||\n prop === \"then\"\n ) {\n return undefined;\n }\n throw new Error(\n `@demlik/tea/pbt: ctx access denied (PBT operates on the pure reducer; ` +\n `ctx field '${prop}' was read). Pass a real ctx value if the reducer's ` +\n `init reads from ctx.`,\n );\n },\n has(): boolean {\n return false;\n },\n ownKeys(): ArrayLike<string | symbol> {\n return [];\n },\n };\n return new Proxy({}, handler) as unknown as Ctx;\n}\n","// ---------------------------------------------------------------------------\n// Msg arbitrary builders.\n//\n// `MsgArbitraryTable<M>` is a strict mapped type — every Msg variant must\n// have an arbitrary entry, keyed by `M[\"type\"]`, narrowing the arbitrary's\n// `T` to `Extract<M, { type: K }>`. This mirrors the load-bearing discipline\n// of `Reducer<S, M, C>` and `Transitions<S, M, C>` in the substrate: adding\n// a Msg variant without an arbitrary fails to compile, not at runtime.\n//\n// `arbMsg(table, opts?)` collapses the table into a single `fc.Arbitrary<M>`\n// via `fc.oneof`. Optional per-variant weighting for sequence-skew tests.\n//\n// `arbConstantMsg(type)` / `arbRecordMsg(type, fields)` are sugar over\n// `fc.constant({ type })` / `fc.record({ type: fc.constant(type), ...fields })`.\n// They exist because every Msg variant arbitrary either (a) carries no\n// payload — `arbConstantMsg(\"stop\")` — or (b) carries a record of fields —\n// `arbRecordMsg(\"tick\", { now: fc.integer() })`. Naming the two shapes\n// prevents callers from hand-rolling `fc.record({ type: fc.constant(\"...\") })`\n// inconsistently across test files.\n//\n// Open question 1 from the design doc (strict vs Partial table): strict by\n// default. The mapped type catches drift at compile time; `arbMsgPartial`\n// sugar is a future addition iff friction becomes real.\n// ---------------------------------------------------------------------------\n\nimport * as fc from \"fast-check\";\n\n/**\n * Strict per-variant Msg arbitrary table. Mapped type: every key in\n * `M[\"type\"]` MUST have an arbitrary entry whose `T` is the narrowed\n * variant. Adding a new Msg variant without an entry is a compile error\n * (same load-bearing discipline as `Reducer<S, M, C>` in the substrate).\n *\n * @example\n * ```ts\n * import {\n * arbConstantMsg,\n * arbRecordMsg,\n * type MsgArbitraryTable,\n * } from \"@demlik/tea/pbt\";\n * import * as fc from \"fast-check\";\n *\n * type Msg = { type: \"start\" } | { type: \"tick\"; now: number };\n * const table: MsgArbitraryTable<Msg> = {\n * start: arbConstantMsg(\"start\"),\n * tick: arbRecordMsg(\"tick\", { now: fc.integer({ min: 0 }) }),\n * };\n * ```\n */\nexport type MsgArbitraryTable<M extends { type: string }> = {\n [K in M[\"type\"]]: fc.Arbitrary<Extract<M, { type: K }>>;\n};\n\n/**\n * Build a single `fc.Arbitrary<M>` from a `MsgArbitraryTable<M>`. Picks one\n * variant per generation via `fc.oneof`; optional per-variant `weights`\n * bias the distribution (default uniform).\n *\n * @param table Per-variant arbitrary table (strict — every variant required).\n * @param opts Optional bias: `weights` is a `Partial<Record<M[\"type\"], number>>`.\n * Variants without an explicit weight default to 1.\n *\n * @example\n * ```ts\n * import { arbConstantMsg, arbMsg, arbRecordMsg } from \"@demlik/tea/pbt\";\n * import * as fc from \"fast-check\";\n *\n * type Msg = { type: \"start\" } | { type: \"tick\"; now: number } | { type: \"stop\" };\n * const msgArb = arbMsg<Msg>({\n * start: arbConstantMsg(\"start\"),\n * tick: arbRecordMsg(\"tick\", { now: fc.integer() }),\n * stop: arbConstantMsg(\"stop\"),\n * }, { weights: { tick: 5, start: 1, stop: 1 } });\n * ```\n */\nexport function arbMsg<M extends { type: string }>(\n table: MsgArbitraryTable<M>,\n opts?: { weights?: Partial<Record<M[\"type\"], number>> },\n): fc.Arbitrary<M> {\n const weights = opts?.weights;\n const entries = Object.entries(table) as [M[\"type\"], fc.Arbitrary<M>][];\n if (entries.length === 0) {\n throw new Error(\"@demlik/tea/pbt: arbMsg table is empty\");\n }\n const weighted = entries.map(([key, arb]) => ({\n arbitrary: arb,\n weight: weights?.[key] ?? 1,\n }));\n return fc.oneof(...weighted);\n}\n\n/**\n * Build a payload-free Msg variant arbitrary. Sugar for\n * `fc.constant({ type })` that narrows the result to `{ type: T }` literal.\n *\n * @example\n * ```ts\n * import { arbConstantMsg } from \"@demlik/tea/pbt\";\n * import type * as fc from \"fast-check\";\n *\n * const stop: fc.Arbitrary<{ type: \"stop\" }> = arbConstantMsg(\"stop\");\n * ```\n */\nexport function arbConstantMsg<T extends string>(\n type: T,\n): fc.Arbitrary<{ type: T }> {\n return fc.constant({ type });\n}\n\n/**\n * Build a record-shaped Msg variant arbitrary. Sugar for\n * `fc.record({ type: fc.constant(type), ...fields })` that stamps the\n * discriminator literal and combines with the per-field arbitraries.\n *\n * @example\n * ```ts\n * import { arbRecordMsg } from \"@demlik/tea/pbt\";\n * import * as fc from \"fast-check\";\n *\n * const tick: fc.Arbitrary<{ type: \"tick\"; now: number }> =\n * arbRecordMsg(\"tick\", { now: fc.integer({ min: 0 }) });\n * ```\n */\nexport function arbRecordMsg<T extends string, R>(\n type: T,\n fields: { [K in keyof R]: fc.Arbitrary<R[K]> },\n): fc.Arbitrary<{ type: T } & R> {\n // `fc.record` builds an arbitrary of the merged-shape record. The\n // `type: fc.constant(type)` field stamps the literal discriminator;\n // the spread of `fields` provides every payload field's arbitrary.\n //\n // The intermediate `as Record<string, fc.Arbitrary<unknown>>` is the\n // single load-bearing cast — fc.record's parameter type is invariant in\n // its inferred field shape, and the merged literal-and-generic spread\n // doesn't unify with the strict `{ [K in keyof R]: Arbitrary<R[K]> }`\n // shape TS computes for us. The outer cast to `fc.Arbitrary<{ type: T }\n // & R>` restates the result shape we know to be correct at this\n // boundary. Mirrors the same discipline `fc.record` callers use\n // throughout the prior-art branch.\n const recordArbitraries: Record<string, fc.Arbitrary<unknown>> = {\n type: fc.constant(type),\n ...(fields as Record<string, fc.Arbitrary<unknown>>),\n };\n return fc.record(recordArbitraries) as unknown as fc.Arbitrary<\n { type: T } & R\n >;\n}\n","// ---------------------------------------------------------------------------\n// Msg-sequence arbitraries.\n//\n// `arbMsgSequence(msgArb, opts)` produces `fc.Arbitrary<readonly M[]>` —\n// fast-check's default-shrinker handles the array; the per-element shrinker\n// inside `msgArb` shrinks each variant. Optional `prefix` is prepended\n// verbatim (used by Test A: \"the bug-triggering Msg leads the sequence\").\n//\n// `arbGuidedSequence` is the state-aware sequence builder. In v1 it throws\n// — Phase 6 of the design doc lands the `fc.commands`-backed implementation.\n// The export exists today so the surface is forward-compatible: consumers\n// that need state-aware generation get a useful error pointing at the\n// design doc instead of \"is not a function.\"\n// ---------------------------------------------------------------------------\n\nimport * as fc from \"fast-check\";\nimport type { Cmd, Machine, Sub } from \"../../index\";\n\n/**\n * Build an arbitrary of Msg sequences over a per-Msg arbitrary. Optionally\n * bounded in length and / or prefixed with a fixed sequence.\n *\n * - `minLength` (default 0) and `maxLength` (default 30) bound the random\n * array length. `prefix` is prepended AFTER the random tail is built; the\n * final length is `prefix.length + tail.length`.\n * - `prefix` is the load-bearing escape hatch for \"the first msg is the\n * one that exercises the cell I care about\" (e.g. Test A's\n * `[{ type: \"ws:client:hello\" }]` prefix to land in the\n * `auditing.ws:client:hello` cell on the first transition).\n *\n * @example\n * ```ts\n * import { arbMsgSequence } from \"@demlik/tea/pbt\";\n * import type * as fc from \"fast-check\";\n *\n * type Msg = { type: \"ws:client:hello\" } | { type: \"tick\" };\n * declare const msgArb: fc.Arbitrary<Msg>;\n *\n * arbMsgSequence(msgArb, { minLength: 1, maxLength: 30 });\n * arbMsgSequence(msgArb, { prefix: [{ type: \"ws:client:hello\" }], maxLength: 20 });\n * ```\n */\nexport function arbMsgSequence<M extends { type: string }>(\n msgArb: fc.Arbitrary<M>,\n opts?: {\n readonly minLength?: number;\n readonly maxLength?: number;\n readonly prefix?: readonly NoInfer<M>[];\n },\n): fc.Arbitrary<readonly M[]> {\n const minLength = opts?.minLength ?? 0;\n const maxLength = opts?.maxLength ?? 30;\n const prefix = opts?.prefix ?? [];\n const tail = fc.array(msgArb, { minLength, maxLength });\n if (prefix.length === 0) return tail;\n return tail.map((arr) => [...prefix, ...arr]);\n}\n\n/**\n * State-aware Msg sequence arbitrary — generates only msgs whose\n * precondition holds in the current state. Backed by `fc.commands`\n * internally. Planned for a later phase.\n *\n * **NOT IMPLEMENTED IN v1.** The export exists today so the surface is\n * forward-compatible; this ships in a later phase alongside\n * shrinkers. Calling it throws.\n */\nexport function arbGuidedSequence<\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: Ctx,\n _cells: {\n [K in M[\"type\"]]?: (\n state: S,\n ) => fc.Arbitrary<Extract<M, { type: K }>> | null;\n },\n _opts?: { maxLength?: number },\n): fc.Arbitrary<readonly M[]> {\n throw new Error(\n \"@demlik/tea/pbt: arbGuidedSequence is not implemented in v1. \" +\n \"Use arbMsgSequence with \" +\n \"an unguided arbMsg table for now; states that don't accept a given \" +\n \"msg should noop in the reducer (mapped-type discipline).\",\n );\n}\n","// ---------------------------------------------------------------------------\n// `foldEvents` — pure deterministic fold over a Msg sequence.\n//\n// PBT needs the per-step (prev/msg/next/cmds) structure to assert\n// step-shaped invariants. The substrate's `replay(machine, opts)` returns\n// only final state + cumulative cmds — insufficient. We use `replay` for\n// the boot step (calls `machine.init(loaded, ctx)` faithfully) then walk\n// msgs ourselves through `applyCell`, the substrate's own cell dispatch.\n//\n// Why walk cells directly instead of looping `replay` once per msg with\n// `loaded: prevState`? Because `init(loaded)` runs every call, and on\n// machines with non-trivial init transforms (e.g. the audit machine's\n// `auditing → attach_debugger` resume cell) that would either (a) re-fire\n// the transform on every step, or (b) require fragile cmd-subtraction to\n// isolate this step's cmds. Calling the cells directly keeps the fold pure\n// and the cmds per-step exactly.\n//\n// Dispatches through `applyCell` — the single primitive `run`/`replay` use —\n// so form classification agrees with production by construction (#275).\n//\n// `foldEvents` does NOT call `interpret`, does NOT touch a Store, does NOT\n// start Subs. The reducer surface is the contract.\n// ---------------------------------------------------------------------------\n\nimport {\n applyCell,\n type Cmd,\n type Machine,\n replay,\n type Sub,\n} from \"../../index\";\n\n/**\n * One unit in a fold trace — the four pieces of data a property\n * invariant typically asserts on. `prev` is the state before the msg\n * applied; `next` is the state after; `cmds` is the cmds emitted by this\n * single step (NOT the running total).\n */\nexport interface Step<S, M, C> {\n readonly prev: S;\n readonly msg: M;\n readonly next: S;\n readonly cmds: readonly C[];\n}\n\n/**\n * Fold a Msg sequence through a machine's pure reducer, returning the\n * per-step trace, every state in order, and the final state. Initial\n * state comes from `replay(machine, { msgs: [], ctx, loaded })` — i.e.\n * `machine.init(loaded ?? null, ctx)` faithfully. Init's emitted cmds\n * are NOT included in any `Step` (they're the boot moment, not a Msg).\n *\n * @param machine The machine under test.\n * @param ctx The Ctx — typically `stubCtxThrowingProxy<Ctx>()` when\n * init is pure. Pass a real ctx if init reads from it.\n * @param loaded Optional pre-init snapshot (the `loaded` arg to\n * `machine.init`). Defaults to `null` (fresh boot).\n * @param msgs The Msg sequence to fold.\n *\n * @example\n * ```ts\n * import type { Machine } from \"@demlik/tea\";\n * import { foldEvents } from \"@demlik/tea/pbt\";\n *\n * type State = { type: \"idle\" } | { type: \"counting\"; n: number } | { type: \"stopped\" };\n * type Msg = { type: \"start\" } | { type: \"tick\" } | { type: \"stop\" };\n * interface Ctx { readonly tabId: number }\n * declare const machine: Machine<State, Msg, never, never, Ctx>;\n * declare const ctx: Ctx;\n *\n * const { states, steps, finalState } = foldEvents(machine, ctx, null, [\n * { type: \"start\" }, { type: \"tick\" }, { type: \"stop\" },\n * ]);\n * // steps[1] = { prev: { type: \"counting\", n: 0 }, msg: { type: \"tick\" }, ... }\n * ```\n */\nexport function foldEvents<\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 loaded: NoInfer<S> | null,\n msgs: readonly NoInfer<M>[],\n): {\n readonly states: readonly S[];\n readonly steps: readonly Step<S, M, C>[];\n readonly finalState: S;\n} {\n // Boot through `replay` so `init` runs faithfully and we honor the\n // substrate's loaded-snapshot contract (init may transform the loaded\n // value before the first Msg lands — e.g. the audit machine flips\n // `debuggerAttached: false` on resume).\n const { state: initialState } = replay(machine, {\n msgs: [],\n ctx,\n loaded: loaded ?? null,\n });\n\n // Per-msg dispatch goes through `applyCell` — the same primitive `run` and\n // `replay` use — so this runner classifies the update form identically to\n // production (`formOf` honors the `__form` tag; #275).\n const states: S[] = [initialState];\n const steps: Step<S, M, C>[] = [];\n let current = initialState;\n\n for (const msg of msgs) {\n const [next, emitted] = applyCell<S, M, C>(machine, current, msg);\n steps.push({ prev: current, msg, next, cmds: [...emitted] });\n states.push(next);\n current = next;\n }\n\n return { states, steps, finalState: current };\n}\n","// ---------------------------------------------------------------------------\n// Property runners — three thin wrappers over `fc.assert(fc.property(...))`\n// that fold a generated Msg sequence through the machine via `foldEvents`\n// and pipe the result into a TEA-shaped predicate.\n//\n// - `propertyTerminates` — every sequence ends in a terminal state.\n// - `propertyInvariant` — predicate holds on every (prev, msg, next, cmds) step.\n// - `propertyTrace` — predicate holds on the full (trace, final) shape.\n//\n// Each runner accepts `numRuns` (passed through to fast-check), an optional\n// `loaded` snapshot (forwarded to `foldEvents`), and the Msg-sequence\n// arbitrary. They return `void`; failures throw via fast-check's standard\n// shrinking output.\n//\n// Default shrinker is fast-check's built-in (open question 4): drop-msg /\n// prefix shrinkers can be layered later but are opt-in, not default. The\n// built-in shrinker handles array-shape shrinking + per-element variant\n// shrinking automatically.\n// ---------------------------------------------------------------------------\n\nimport * as fc from \"fast-check\";\nimport type { Cmd, Machine, Sub } from \"../../index\";\nimport { foldEvents, type Step } from \"./replay-fold\";\n\n/** Common options shared by every property runner. */\ninterface PropertyOpts<S> {\n /** Pre-init snapshot — passed to `machine.init(loaded, ctx)`. Default `null`. */\n readonly loaded?: S | null;\n /** Number of generated sequences to test. Forwarded to `fc.assert`. */\n readonly numRuns?: number;\n /** Optional fast-check seed for reproducibility. */\n readonly seed?: number;\n}\n\n/** The `foldEvents` result a runner's `check` inspects. */\ntype FoldResult<S, M, C> = {\n readonly states: readonly S[];\n readonly steps: readonly Step<S, M, C>[];\n readonly finalState: S;\n};\n\n/**\n * Build the fast-check parameters shared by every runner: the 200-default\n * `numRuns` plus an optional `seed`. Single source of truth so a change to\n * the default lands in one place.\n */\nfunction resolveFcParams<M>(\n opts: PropertyOpts<unknown> | undefined,\n): fc.Parameters<[readonly M[]]> {\n return {\n numRuns: opts?.numRuns ?? 200,\n ...(opts?.seed !== undefined ? { seed: opts.seed } : {}),\n };\n}\n\n/**\n * The fold+assert core every runner shares: assert `check` over each\n * generated Msg sequence, folded once through the machine via `foldEvents`.\n * `check` throws on failure so the error message carries the offending\n * shape into fast-check's shrunk counter-example.\n */\nfunction runProperty<\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: Ctx,\n seqArb: fc.Arbitrary<readonly M[]>,\n opts: PropertyOpts<S> | undefined,\n check: (result: FoldResult<S, M, C>) => void,\n): void {\n fc.assert(\n fc.property(seqArb, (msgs) => {\n check(foldEvents(machine, ctx, opts?.loaded ?? null, msgs));\n }),\n resolveFcParams<M>(opts),\n );\n}\n\n/**\n * Assert every generated Msg sequence ends in a terminal state. `terminal`\n * is the predicate that names the acceptable final states (often\n * `(s) => s.type === \"done\" || s.type === \"idle\"`).\n *\n * The \"every sequence\" property catches stuck states (machine gets to a\n * non-terminal state and no further msg can escape) AND silent loops\n * (every msg variant is a noop from a given state).\n *\n * @example\n * ```ts\n * import type { Machine } from \"@demlik/tea\";\n * import {\n * arbMsgSequence,\n * propertyTerminates,\n * stubCtxThrowingProxy,\n * } from \"@demlik/tea/pbt\";\n * import type * as fc from \"fast-check\";\n *\n * type State = { type: \"idle\" } | { type: \"auditing\" } | { type: \"done\" };\n * type Msg = { type: \"start_audit\" } | { type: \"ws:audit:done\" };\n * interface BackgroundCtx { readonly tabId: number }\n * declare const auditBackgroundMachine: Machine<State, Msg, never, never, BackgroundCtx>;\n * declare const msgArb: fc.Arbitrary<Msg>;\n * const auditingState: State = { type: \"auditing\" };\n *\n * propertyTerminates(\n * auditBackgroundMachine,\n * stubCtxThrowingProxy<BackgroundCtx>(),\n * arbMsgSequence(msgArb, { minLength: 1, maxLength: 30 }),\n * (s) => s.type === \"done\" || s.type === \"idle\",\n * { loaded: auditingState, numRuns: 500 },\n * );\n * ```\n */\nexport function propertyTerminates<\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 seqArb: fc.Arbitrary<readonly NoInfer<M>[]>,\n terminal: (state: NoInfer<S>) => boolean,\n opts?: NoInfer<PropertyOpts<S>>,\n): void {\n runProperty(machine, ctx, seqArb, opts, ({ finalState }) => {\n if (!terminal(finalState)) {\n // Throwing here (vs returning false) lets the error message carry\n // the offending final state — fast-check pretty-prints it inside\n // the shrunk counter-example.\n throw new Error(\n `propertyTerminates: final state did not match terminal predicate. ` +\n `Final: ${JSON.stringify(finalState)}`,\n );\n }\n });\n}\n\n/**\n * Assert a predicate holds on EVERY transition step in a generated\n * sequence. The predicate sees `{ prev, msg, next, cmds }` per step;\n * return true to accept, false (or throw) to fail.\n *\n * Use to pin a \"when X happens in state Y, cmd Z must be emitted\" rule.\n * The shrinker minimizes the offending sequence to the smallest Msg list\n * that still violates the predicate.\n *\n * @example\n * ```ts\n * import type { Machine } from \"@demlik/tea\";\n * import { arbMsgSequence, propertyInvariant } from \"@demlik/tea/pbt\";\n * import type * as fc from \"fast-check\";\n *\n * type State = { type: \"idle\" } | { type: \"auditing\" };\n * type Msg = { type: \"start_audit\" } | { type: \"ws:client:hello\" };\n * type Cmd = { type: \"send_ws\"; msg: { type: \"client:ready\" } };\n * interface BackgroundCtx { readonly tabId: number }\n * declare const auditBackgroundMachine: Machine<State, Msg, Cmd, never, BackgroundCtx>;\n * declare const ctx: BackgroundCtx;\n * declare const msgArb: fc.Arbitrary<Msg>;\n * const auditingState: State = { type: \"auditing\" };\n *\n * // Pin the resume-handshake fix: `ws:client:hello` in auditing must\n * // emit `send_ws { client:ready }`.\n * propertyInvariant(\n * auditBackgroundMachine, ctx,\n * arbMsgSequence(msgArb, { prefix: [{ type: \"ws:client:hello\" }] }),\n * ({ prev, msg, cmds }) =>\n * !(prev.type === \"auditing\" && msg.type === \"ws:client:hello\") ||\n * cmds.some((c) => c.type === \"send_ws\" && c.msg.type === \"client:ready\"),\n * { loaded: auditingState, numRuns: 200 },\n * );\n * ```\n */\nexport function propertyInvariant<\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 seqArb: fc.Arbitrary<readonly NoInfer<M>[]>,\n invariant: (step: Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>) => boolean,\n opts?: NoInfer<PropertyOpts<S>>,\n): void {\n runProperty(machine, ctx, seqArb, opts, ({ steps }) => {\n for (const step of steps) {\n if (!invariant(step)) {\n throw new Error(\n `propertyInvariant: failed at step msg=${JSON.stringify(step.msg)} ` +\n `prev=${JSON.stringify(step.prev)} next=${JSON.stringify(step.next)} ` +\n `cmds=${JSON.stringify(step.cmds)}`,\n );\n }\n }\n });\n}\n\n/**\n * Assert a predicate over the full trace plus the final state. Use when\n * the property is \"some Msg appeared in the trace before reaching the\n * final state\" or \"no two consecutive steps had the same state.type\" —\n * predicates that need the full sequence, not a single step.\n *\n * @example\n * ```ts\n * import type { Machine } from \"@demlik/tea\";\n * import { propertyTrace } from \"@demlik/tea/pbt\";\n * import type * as fc from \"fast-check\";\n *\n * type State = { type: \"auditing\" } | { type: \"done\" };\n * type Msg = { type: \"ws:audit:progress\" } | { type: \"ws:audit:done\" };\n * interface Ctx { readonly tabId: number }\n * declare const machine: Machine<State, Msg, never, never, Ctx>;\n * declare const ctx: Ctx;\n * declare const seqArb: fc.Arbitrary<readonly Msg[]>;\n * const auditingState: State = { type: \"auditing\" };\n *\n * propertyTrace(\n * machine, ctx, seqArb,\n * (steps, final) =>\n * final.type !== \"done\" ||\n * steps.some((step) => step.msg.type === \"ws:audit:done\"),\n * { loaded: auditingState, numRuns: 500 },\n * );\n * ```\n */\nexport function propertyTrace<\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 seqArb: fc.Arbitrary<readonly NoInfer<M>[]>,\n predicate: (\n steps: readonly Step<NoInfer<S>, NoInfer<M>, NoInfer<C>>[],\n finalState: NoInfer<S>,\n ) => boolean,\n opts?: NoInfer<PropertyOpts<S>>,\n): void {\n runProperty(machine, ctx, seqArb, opts, ({ steps, finalState }) => {\n if (!predicate(steps, finalState)) {\n throw new Error(\n `propertyTrace: predicate failed on trace length=${steps.length}, ` +\n `final=${JSON.stringify(finalState)}`,\n );\n }\n });\n}\n","// ---------------------------------------------------------------------------\n// msgTypeKeys — read the Msg discriminant set at runtime from a machine's\n// Reducer record or Transitions table.\n//\n// TS Msg unions only exist at the type level; the discriminant strings are\n// erased at runtime. The reducer record / transitions table is the load-\n// bearing artifact that DOES carry the discriminants at runtime — its keys\n// (for Reducer) or any one cell's inner keys (for Transitions) are exactly\n// the set we need to build an `arbMsg` table without hand-listing them.\n//\n// Standalone helper: hand it a machine and it returns the Msg variants the\n// reducer/transitions record carries at runtime. `arbMsg` does NOT call it —\n// `MsgArbitraryTable<M>` enforces table coverage at compile time (adding a\n// variant without an arbitrary fails to compile, not at runtime). This helper\n// is for callers that want the discriminant set explicitly at runtime — e.g.\n// a test asserting a table covers every variant.\n// ---------------------------------------------------------------------------\n\nimport {\n type Cmd,\n msgKeysOf,\n type Reducer,\n type Transitions,\n type UpdateForm,\n} from \"../../index\";\n\n/**\n * Extract the Msg discriminant set at runtime from a machine. Useful for\n * asserting that a user-supplied `MsgArbitraryTable<M>` covers every variant\n * the reducer can handle.\n *\n * Takes the MACHINE (not the bare `update` record) so the reducer-vs-\n * transitions branch honors the authoritative `__form` tag via `msgKeysOf` —\n * the same classification production `run` uses (#275).\n *\n * - **Reducer form** — keys of the top-level record ARE the Msg variants.\n * - **Transitions form** — keys of any single state's inner record. Every\n * state's inner record must cover every Msg variant (mapped-type\n * discipline), so reading the first state's keys is equivalent to reading\n * the union.\n *\n * @example\n * ```ts\n * import type { Machine } from \"@demlik/tea\";\n * import { msgTypeKeys } from \"@demlik/tea/pbt\";\n *\n * type State = { type: \"idle\" } | { type: \"auditing\" };\n * type Msg = { type: \"start_audit\" } | { type: \"stop_audit\" } | { type: \"window_created\" };\n * declare const auditBackgroundMachine: Machine<State, Msg, never, never, unknown>;\n *\n * msgTypeKeys(auditBackgroundMachine);\n * // → [\"start_audit\", \"stop_audit\", \"window_created\"]\n * ```\n */\nexport function msgTypeKeys<\n S,\n M extends { type: string },\n C extends Cmd,\n>(machine: {\n update:\n | Reducer<S, M, C>\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n __form?: UpdateForm;\n}): readonly string[] {\n return msgKeysOf(machine);\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { T as Trace } from '../index-
|
|
2
|
-
export { B as BreadcrumbsOptions, a as Recorder, R as RecorderOptions, b as TraceAttachment, c as TraceBreadcrumb, d as breadcrumbsFromTrace, p as parseJSONL, r as recorder, t as traceAttachment } from '../index-
|
|
3
|
-
export { c as SnapshotConfig, d as SnapshotFailedMsg, e as SnapshotKnob, f as SnapshotLoadCmd, g as SnapshotLoadFailedMsg, h as SnapshotLoadedMsg, b as SnapshotSavedMsg, a as SnapshotState, S as SnapshotWriteCmd, i as createSnapshot, s as snapshotLoadDef, j as snapshotWriteDef } from '../index-
|
|
4
|
-
import { C as Cmd,
|
|
5
|
-
import '../runtime-types-
|
|
1
|
+
import { T as Trace } from '../index-B1Wzarkl.js';
|
|
2
|
+
export { B as BreadcrumbsOptions, a as Recorder, R as RecorderOptions, b as TraceAttachment, c as TraceBreadcrumb, d as breadcrumbsFromTrace, p as parseJSONL, r as recorder, t as traceAttachment } from '../index-B1Wzarkl.js';
|
|
3
|
+
export { c as SnapshotConfig, d as SnapshotFailedMsg, e as SnapshotKnob, f as SnapshotLoadCmd, g as SnapshotLoadFailedMsg, h as SnapshotLoadedMsg, b as SnapshotSavedMsg, a as SnapshotState, S as SnapshotWriteCmd, i as createSnapshot, s as snapshotLoadDef, j as snapshotWriteDef } from '../index-D8sYVzCI.js';
|
|
4
|
+
import { C as Cmd, c as Sub, M as Machine } from '../core-Ce9ncpuv.js';
|
|
5
|
+
import '../runtime-types-BKg2CGMx.js';
|
|
6
6
|
import '@standard-schema/spec';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -55,12 +55,11 @@ declare function replayTrace<S, M extends {
|
|
|
55
55
|
/**
|
|
56
56
|
* Order-insensitive structural deep-equality — the comparator the
|
|
57
57
|
* record/replay lane owns, exposed so consumers (e.g. `@demlik/tea/parity`'s
|
|
58
|
-
* `parityEqual`) reuse it rather than adding a
|
|
59
|
-
* of
|
|
58
|
+
* `parityEqual`) reuse it rather than adding a third deep-compare. It is one
|
|
59
|
+
* of two structural walkers in the package, divergent on purpose:
|
|
60
60
|
* this `deepEqual` returns a boolean verdict (order-insensitive, for
|
|
61
|
-
* replay/parity equality), `devtools`'s `diffState` returns a change list
|
|
62
|
-
* a human-readable diff view),
|
|
63
|
-
* helpers' internal compare — same shape, three jobs, not one to collapse.
|
|
61
|
+
* replay/parity equality), and `devtools`'s `diffState` returns a change list
|
|
62
|
+
* (for a human-readable diff view) — same shape, two jobs, not one to collapse.
|
|
64
63
|
* `true` iff `a` and `b` are structurally equal under {@link firstDivergence}'s
|
|
65
64
|
* walk (object keys compared order-insensitively, arrays index-by-index).
|
|
66
65
|
*/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { breadcrumbsFromTrace, deepEqual, parseJSONL, recorder, replayTrace, traceAttachment } from '../chunk-
|
|
2
|
-
export { createSnapshot, snapshotLoadDef, snapshotWriteDef } from '../chunk-CIHSHLQX.js';
|
|
3
|
-
import '../chunk-4LPY67E4.js';
|
|
4
|
-
import '../chunk-GOX2CYIM.js';
|
|
5
|
-
import '../chunk-CH5KTOEZ.js';
|
|
1
|
+
export { breadcrumbsFromTrace, deepEqual, parseJSONL, recorder, replayTrace, traceAttachment } from '../chunk-BRX3243I.js';
|
|
6
2
|
import '../chunk-HWVW4AXC.js';
|
|
3
|
+
export { createSnapshot, snapshotLoadDef, snapshotWriteDef } from '../chunk-YIMOFXST.js';
|
|
4
|
+
import '../chunk-4LPY67E4.js';
|
|
5
|
+
import '../chunk-FNGZMVIT.js';
|
|
6
|
+
import '../chunk-AIIXV4XB.js';
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
|
8
8
|
//# sourceMappingURL=index.js.map
|
package/dist/promise/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Cmd,
|
|
2
|
-
import { B as BootingRuntime, C as CtxArg, S as Store, O as OnError, a as Supervision, T as TelemetrySink } from '../runtime-types-
|
|
1
|
+
import { C as Cmd, c as Sub, M as Machine, R as RunHandlers } from '../core-Ce9ncpuv.js';
|
|
2
|
+
import { B as BootingRuntime, C as CtxArg, S as Store, O as OnError, a as Supervision, T as TelemetrySink } from '../runtime-types-BKg2CGMx.js';
|
|
3
3
|
import '@standard-schema/spec';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/promise/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { driveToDone, run } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import '../chunk-
|
|
1
|
+
export { driveToDone, run } from '../chunk-H2ELKHI6.js';
|
|
2
|
+
import '../chunk-AHW2CLPT.js';
|
|
3
|
+
import '../chunk-JVE2LBFJ.js';
|
|
4
|
+
import '../chunk-AIIXV4XB.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as EngineRun, S as Store, b as BootedRunHandle } from '../runtime-types-
|
|
2
|
-
import { C as Cmd,
|
|
1
|
+
import { E as EngineRun, S as Store, b as BootedRunHandle } from '../runtime-types-BKg2CGMx.js';
|
|
2
|
+
import { C as Cmd, c as Sub, R as RunHandlers, M as Machine } from '../core-Ce9ncpuv.js';
|
|
3
3
|
import '@standard-schema/spec';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as CmdOf, b as CmdDef, e as SettledErr, f as TaggedError, d as SettledOk, T as TimerDeps, C as Cmd } from '../core-Ce9ncpuv.js';
|
|
2
2
|
import { M as MsgType } from '../protocol-ClRZBhHQ.js';
|
|
3
|
-
import { a as DeadlineSub } from '../index-
|
|
4
|
-
export { A as ArmTimer,
|
|
5
|
-
import { c as ResilientConfig, a as ResilientState, R as RunCmd, S as SucceedMsg, F as FailMsg, b as ResilientTimerMsg } from '../index-
|
|
6
|
-
export { C as CacheConfig, d as CallBudget, e as CallPhase, f as CircuitConfig, g as CircuitPolicy, h as CircuitState, D as DEFAULT_RESILIENT_NAME, i as DeadlineExceededError, j as DeadlineNameOf, k as DefaultResilientName, l as RateLimitConfig, m as ResilientCallDeadlineConfig, n as ResilientDeadlineType, o as ResilientErrType, p as ResilientOkType, q as ResilientRunType, r as RunCmdDef, s as RunErr, t as SettleMsg, u as SettleOutcome, v as SettleResult, w as SlidingWindow, T as TokenBucket, x as canPass, y as createResilientCall, z as defaultCircuitPolicy, A as initBucket, B as initCircuit, E as initWindow, G as liftResilience, H as onFailure, I as onSuccess, J as record, K as refill, L as remaining, M as runCmdDef, N as tryConsume } from '../index-
|
|
3
|
+
import { a as DeadlineSub } from '../index-e36uEyl4.js';
|
|
4
|
+
export { A as ArmTimer, e as CacheEntry, c as CacheEvictMsg, f as CacheEvictionDeps, C as CacheEvictionSub, D as DeadlineExceeded, g as DeadlineMsgType, h as DeadlineOpts, b as DeadlinesSub, T as TtlCache, i as cacheEvictMsg, j as cacheEvictionSub, k as cacheEvictionSubscribe, d as deadlineExceeded, l as deadlineMsgType, m as deadlineSub, n as deadlines, o as deadlinesSub, p as evictExpired, q as get, r as has, t as initCache, u as nextTimer, v as remove, w as set, x as setTimeoutArmTimer, s as subscribeDeadline, y as subscribeWith } from '../index-e36uEyl4.js';
|
|
5
|
+
import { c as ResilientConfig, a as ResilientState, R as RunCmd, S as SucceedMsg, F as FailMsg, b as ResilientTimerMsg } from '../index-2kNxy6az.js';
|
|
6
|
+
export { C as CacheConfig, d as CallBudget, e as CallPhase, f as CircuitConfig, g as CircuitPolicy, h as CircuitState, D as DEFAULT_RESILIENT_NAME, i as DeadlineExceededError, j as DeadlineNameOf, k as DefaultResilientName, l as RateLimitConfig, m as ResilientCallDeadlineConfig, n as ResilientDeadlineType, o as ResilientErrType, p as ResilientOkType, q as ResilientRunType, r as RunCmdDef, s as RunErr, t as SettleMsg, u as SettleOutcome, v as SettleResult, w as SlidingWindow, T as TokenBucket, x as canPass, y as createResilientCall, z as defaultCircuitPolicy, A as initBucket, B as initCircuit, E as initWindow, G as liftResilience, H as onFailure, I as onSuccess, J as record, K as refill, L as remaining, M as runCmdDef, N as tryConsume } from '../index-2kNxy6az.js';
|
|
7
7
|
import { Rng, RetryPolicy } from '../retry-backoff/index.js';
|
|
8
8
|
import '@standard-schema/spec';
|
|
9
|
-
import '../types-
|
|
9
|
+
import '../types-SzNSHKlV.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* A minted credential: the opaque `value` to send on the wire, and the absolute
|
|
@@ -109,9 +109,13 @@ type TokenRefreshMsg = TokenRefreshedMsg | TokenRefreshFailedMsg;
|
|
|
109
109
|
* verbs, plus the `refresh_token` Cmd def (`run`) to list in `cmds`.
|
|
110
110
|
*
|
|
111
111
|
* @example
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { createTokenRefresh } from "@demlik/tea/resilience";
|
|
114
|
+
*
|
|
115
|
+
* const tr = createTokenRefresh({ skewMs: 30_000 });
|
|
116
|
+
* const s0 = tr.init();
|
|
117
|
+
* const [s1, cmds] = tr.ensureFresh(s0, Date.now()); // cmds = [refreshTokenCmd()]
|
|
118
|
+
* ```
|
|
115
119
|
*/
|
|
116
120
|
declare function createTokenRefresh(config?: TokenRefreshConfig): {
|
|
117
121
|
/** The `refresh_token` Cmd def — list it in the machine's `cmds`. */
|
package/dist/resilience/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { createResilientCall } from '../chunk-
|
|
2
|
-
export { DEFAULT_RESILIENT_NAME, canPass, createResilientCall, defaultCircuitPolicy, initBucket, initCircuit, initWindow, liftResilience, onFailure, onSuccess, record, refill, remaining, runCmdDef, tryConsume } from '../chunk-
|
|
1
|
+
import { createResilientCall } from '../chunk-WTSOW2MD.js';
|
|
2
|
+
export { DEFAULT_RESILIENT_NAME, canPass, createResilientCall, defaultCircuitPolicy, initBucket, initCircuit, initWindow, liftResilience, onFailure, onSuccess, record, refill, remaining, runCmdDef, tryConsume } from '../chunk-WTSOW2MD.js';
|
|
3
|
+
export { cacheEvictMsg, cacheEvictionSub, cacheEvictionSubscribe, deadlineExceeded, deadlineMsgType, deadlineSub, deadlines, deadlinesSub, evictExpired, get, has, initCache, nextTimer, remove, set, setTimeoutArmTimer, subscribeDeadline, subscribeWith } from '../chunk-C3G2QFEZ.js';
|
|
3
4
|
import { defaultRng, initRetry, recordFailure, shouldRetry, nextDelayMs } from '../chunk-VBDU4JL6.js';
|
|
4
5
|
import { unchecked } from '../chunk-4LPY67E4.js';
|
|
5
|
-
|
|
6
|
-
import '../chunk-
|
|
7
|
-
import { Cmd, without } from '../chunk-CH5KTOEZ.js';
|
|
6
|
+
import '../chunk-FNGZMVIT.js';
|
|
7
|
+
import { Cmd, without } from '../chunk-AIIXV4XB.js';
|
|
8
8
|
|
|
9
9
|
// src/internal/resilience/token-refresh/index.ts
|
|
10
10
|
function initTokenRefresh() {
|