aifsmjs 0.5.8 → 0.6.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 +14 -7
- package/README_ZHTW.md +14 -7
- package/dist/{chunk-ZLQ7HZCE.js → chunk-D6H64FSI.js} +66 -46
- package/dist/chunk-D6H64FSI.js.map +1 -0
- package/dist/{chunk-NEJYZAKR.js → chunk-Q45LGXHO.js} +3 -3
- package/dist/{chunk-NEJYZAKR.js.map → chunk-Q45LGXHO.js.map} +1 -1
- package/dist/{chunk-CDK25FTD.cjs → chunk-QCTA2X4J.cjs} +7 -3
- package/dist/chunk-QCTA2X4J.cjs.map +1 -0
- package/dist/{chunk-A7U7QQL5.js → chunk-SSNKGEVB.js} +7 -4
- package/dist/chunk-SSNKGEVB.js.map +1 -0
- package/dist/{chunk-I354FONA.cjs → chunk-VGLF5NQH.cjs} +69 -46
- package/dist/chunk-VGLF5NQH.cjs.map +1 -0
- package/dist/{chunk-FHTQ7LSQ.cjs → chunk-VV5TKFQO.cjs} +4 -4
- package/dist/{chunk-FHTQ7LSQ.cjs.map → chunk-VV5TKFQO.cjs.map} +1 -1
- package/dist/{chunk-VZCSHTOI.js → chunk-XA24A7VP.js} +156 -157
- package/dist/chunk-XA24A7VP.js.map +1 -0
- package/dist/{chunk-2MME5V4F.cjs → chunk-YK25NVFC.cjs} +160 -161
- package/dist/chunk-YK25NVFC.cjs.map +1 -0
- package/dist/effects/index.cjs +3 -4
- package/dist/effects/index.cjs.map +1 -1
- package/dist/effects/index.d.cts +1 -1
- package/dist/effects/index.d.ts +1 -1
- package/dist/effects/index.js +2 -3
- package/dist/effects/index.js.map +1 -1
- package/dist/guards/index.cjs +5 -6
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.d.cts +1 -1
- package/dist/guards/index.d.ts +1 -1
- package/dist/guards/index.js +2 -3
- package/dist/guards/index.js.map +1 -1
- package/dist/index.cjs +31 -28
- package/dist/index.d.cts +66 -18
- package/dist/index.d.ts +66 -18
- package/dist/index.js +3 -4
- package/dist/inspect/index.cjs +0 -2
- package/dist/inspect/index.cjs.map +1 -1
- package/dist/inspect/index.d.cts +1 -1
- package/dist/inspect/index.d.ts +1 -1
- package/dist/inspect/index.js +0 -2
- package/dist/inspect/index.js.map +1 -1
- package/dist/pbt/index.cjs +69 -48
- package/dist/pbt/index.cjs.map +1 -1
- package/dist/pbt/index.d.cts +29 -16
- package/dist/pbt/index.d.ts +29 -16
- package/dist/pbt/index.js +61 -40
- package/dist/pbt/index.js.map +1 -1
- package/dist/replay/index.cjs +4 -5
- package/dist/replay/index.d.cts +1 -1
- package/dist/replay/index.d.ts +1 -1
- package/dist/replay/index.js +3 -4
- package/dist/timer/index.cjs +18 -5
- package/dist/timer/index.cjs.map +1 -1
- package/dist/timer/index.d.cts +6 -0
- package/dist/timer/index.d.ts +6 -0
- package/dist/timer/index.js +18 -5
- package/dist/timer/index.js.map +1 -1
- package/dist/{types-DIM7QTtf.d.ts → types-CrDxFfBx.d.cts} +64 -16
- package/dist/{types-DIM7QTtf.d.cts → types-CrDxFfBx.d.ts} +64 -16
- package/llms-full.txt +89 -17
- package/package.json +57 -22
- package/dist/chunk-2MME5V4F.cjs.map +0 -1
- package/dist/chunk-A7U7QQL5.js.map +0 -1
- package/dist/chunk-CDK25FTD.cjs.map +0 -1
- package/dist/chunk-I354FONA.cjs.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -9
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-Q7SFCCGT.cjs +0 -11
- package/dist/chunk-Q7SFCCGT.cjs.map +0 -1
- package/dist/chunk-VZCSHTOI.js.map +0 -1
- package/dist/chunk-ZLQ7HZCE.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { f as MachineDef, I as Implementations, m as RuntimeOptions, j as Runtime, M as MachineConfig, S as Snapshot, o as StateDef, p as StepResult, A as Action, e as GuardRef, G as Guard, T as TransitionConfig, r as TransitionDef } from './types-
|
|
2
|
-
export { a as ActionRef, E as Effect, b as EffectHandler, c as Enqueuer, d as GuardArgs, g as Middleware, h as MiddlewareContext, R as RESET_EVENT_TYPE, i as ResetEvent, k as RuntimeErrorEvent, l as RuntimeEventMap, n as RuntimeTransitionEvent, q as SubMachineDef } from './types-
|
|
1
|
+
import { f as MachineDef, I as Implementations, m as RuntimeOptions, j as Runtime, M as MachineConfig, S as Snapshot, o as StateDef, p as StepResult, A as Action, e as GuardRef, G as Guard, T as TransitionConfig, r as TransitionDef } from './types-CrDxFfBx.js';
|
|
2
|
+
export { a as ActionRef, E as Effect, b as EffectHandler, c as Enqueuer, d as GuardArgs, g as Middleware, h as MiddlewareContext, R as RESET_EVENT_TYPE, i as ResetEvent, k as RuntimeErrorEvent, l as RuntimeEventMap, n as RuntimeTransitionEvent, q as SubMachineDef } from './types-CrDxFfBx.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Thrown for an invalid machine definition and, since 0.6.0, for argument
|
|
6
|
+
* misuse at the definition/runtime boundary (`defineMachine`,
|
|
7
|
+
* `setup().defineMachine`, `createMachine`, `createRuntime`, and the
|
|
8
|
+
* `send` / `reset` / `subscribe` / `on` / `onTransition` runtime methods).
|
|
9
|
+
* Messages read `aifsmjs: <subject> must be <constraint>`.
|
|
10
|
+
*/
|
|
4
11
|
declare class InvalidDefinitionError extends Error {
|
|
5
12
|
constructor(message: string);
|
|
6
13
|
}
|
|
@@ -26,8 +33,9 @@ declare function defineMachine<Ctx = Record<string, never>, Evt extends {
|
|
|
26
33
|
}, States extends string = string>(def: MachineConfig<Ctx, Evt, States>): MachineDef<Ctx, Evt, States>;
|
|
27
34
|
/**
|
|
28
35
|
* Curried builder so `States` can be inferred from `keyof states` without
|
|
29
|
-
* `initial` collapsing it to a single literal. Pass
|
|
30
|
-
* type arguments; pass the def to the returned
|
|
36
|
+
* `initial` or a transition `target` collapsing it to a single literal. Pass
|
|
37
|
+
* `Ctx` and `Evt` as the type arguments; pass the def to the returned
|
|
38
|
+
* `defineMachine`.
|
|
31
39
|
*
|
|
32
40
|
* const machine = setup<MyCtx, MyEvt>().defineMachine({
|
|
33
41
|
* id: "m",
|
|
@@ -44,7 +52,9 @@ declare function setup<Ctx = Record<string, never>, Evt extends {
|
|
|
44
52
|
defineMachine: <const States extends string>(def: Readonly<{
|
|
45
53
|
id: string;
|
|
46
54
|
initial: NoInfer<States>;
|
|
47
|
-
states: Readonly<
|
|
55
|
+
states: Readonly<{
|
|
56
|
+
[K in States]: StateDef<Ctx, Evt, NoInfer<States>>;
|
|
57
|
+
}>;
|
|
48
58
|
}> & (Record<string, never> extends Ctx ? {
|
|
49
59
|
readonly context?: Ctx;
|
|
50
60
|
} : {
|
|
@@ -75,6 +85,21 @@ declare class UnknownActionError extends Error {
|
|
|
75
85
|
readonly actionName: string;
|
|
76
86
|
constructor(actionName: string);
|
|
77
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Thrown (from `step()`, and so from `send()` before anything is committed)
|
|
90
|
+
* when an action returns a non-nullish primitive (`false`, `0`, `""`, ...) as
|
|
91
|
+
* the patch for an object context. An object context accepts only a
|
|
92
|
+
* plain-object patch or `undefined`/`null` (no change).
|
|
93
|
+
*
|
|
94
|
+
* `actionName` is the string ref, the inline function's name, or `"<inline>"`
|
|
95
|
+
* for an anonymous inline action (mirrors guard naming).
|
|
96
|
+
*
|
|
97
|
+
* @since 0.6.0
|
|
98
|
+
*/
|
|
99
|
+
declare class InvalidActionResultError extends Error {
|
|
100
|
+
readonly actionName: string;
|
|
101
|
+
constructor(actionName: string, patch: unknown);
|
|
102
|
+
}
|
|
78
103
|
/**
|
|
79
104
|
* Compute the next snapshot and collected effects from a single event.
|
|
80
105
|
*
|
|
@@ -87,6 +112,7 @@ declare class UnknownActionError extends Error {
|
|
|
87
112
|
* 6. return { snapshot, effects, changed }
|
|
88
113
|
*
|
|
89
114
|
* The function is pure: it never dispatches effects and never mutates inputs.
|
|
115
|
+
* Each guard on the path is evaluated at most once.
|
|
90
116
|
*/
|
|
91
117
|
declare function step<Ctx, Evt extends {
|
|
92
118
|
type: string;
|
|
@@ -98,11 +124,15 @@ declare class RuntimeDisposedError extends Error {
|
|
|
98
124
|
/**
|
|
99
125
|
* Thrown by `send()` / `reset()` when a sub-machine init or dispose throws.
|
|
100
126
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
127
|
+
* Prepare-then-commit: the new child is constructed before the previous one
|
|
128
|
+
* is disposed. Invariants:
|
|
129
|
+
* - `phase: "init"` — the new child's constructor threw. The parent snapshot
|
|
130
|
+
* is not committed and the previous child (if any) is untouched: still live
|
|
131
|
+
* and still returned by `subRuntime()`. No middleware ran, no `'transition'`
|
|
132
|
+
* was emitted, no effects were dispatched.
|
|
133
|
+
* - `phase: "dispose"` — the previous child's `dispose()` threw. The new
|
|
134
|
+
* child (if any) was discarded, the parent snapshot is not committed, and
|
|
135
|
+
* `subRuntime()` returns `undefined` until the sub state is re-entered.
|
|
106
136
|
* - Never thrown from `runtime.dispose()` cascade (never-throws contract).
|
|
107
137
|
*
|
|
108
138
|
* @since 0.3.0
|
|
@@ -115,9 +145,18 @@ declare class SubMachineError extends Error {
|
|
|
115
145
|
}
|
|
116
146
|
/**
|
|
117
147
|
* Build a thin stateful runtime around a machine. `send()` calls `step()`,
|
|
118
|
-
* runs the read-only middleware pipeline, dispatches effects, and
|
|
119
|
-
* subscribers
|
|
120
|
-
*
|
|
148
|
+
* commits, runs the read-only middleware pipeline, dispatches effects, and
|
|
149
|
+
* notifies subscribers then `'transition'` listeners. `send()`/`reset()` are
|
|
150
|
+
* run-to-completion: a call made while the runtime is already dispatching
|
|
151
|
+
* (from middleware, an effect handler, a listener, or a child runtime) is
|
|
152
|
+
* queued FIFO and processed after the current event's last notification.
|
|
153
|
+
* The runtime owns an `AbortController`; `dispose()` aborts it and clears all
|
|
154
|
+
* state.
|
|
155
|
+
*
|
|
156
|
+
* Arguments are validated before anything is created: a non-object `def`,
|
|
157
|
+
* `def.states`, `impl` or `opts`, or a `middleware` option that is not an
|
|
158
|
+
* array of functions, throws `InvalidDefinitionError`. The rest of `def` is
|
|
159
|
+
* trusted (build it with `defineMachine` / `setup().defineMachine`).
|
|
121
160
|
*/
|
|
122
161
|
declare function createRuntime<Ctx, Evt extends {
|
|
123
162
|
type: string;
|
|
@@ -132,12 +171,21 @@ declare function assign<Ctx, Evt>(updater: (args: {
|
|
|
132
171
|
event: Evt;
|
|
133
172
|
}) => Partial<Ctx>): Action<Ctx, Evt>;
|
|
134
173
|
/**
|
|
135
|
-
* Merge
|
|
136
|
-
*
|
|
174
|
+
* Merge an action's result into the current context. The function never
|
|
175
|
+
* mutates either argument.
|
|
137
176
|
*
|
|
138
|
-
*
|
|
177
|
+
* - `undefined` / `null` patch: `current` is returned unchanged.
|
|
178
|
+
* - Object context (not an array or `ArrayBuffer` view) + plain-object patch:
|
|
179
|
+
* shallow merge into a new object that keeps `current`'s prototype, so a
|
|
180
|
+
* class-instance context keeps its methods and untouched fields. Only own
|
|
181
|
+
* enumerable (string and symbol) properties are carried; `#private` and
|
|
182
|
+
* non-enumerable members are not, so prefer plain-object contexts.
|
|
183
|
+
* - Object context + non-nullish primitive patch (`false`, `0`, `""`, ...):
|
|
184
|
+
* throws {@link InvalidActionResultError} (`actionName` names the action).
|
|
185
|
+
* - Anything else (a primitive or array context, or a non-plain-object patch
|
|
186
|
+
* such as an array or class instance): the patch replaces `current`.
|
|
139
187
|
*/
|
|
140
|
-
declare function mergeContext<Ctx>(current: Ctx, patch: Partial<Ctx> | void): Ctx;
|
|
188
|
+
declare function mergeContext<Ctx>(current: Ctx, patch: Partial<Ctx> | void, actionName?: string): Ctx;
|
|
141
189
|
|
|
142
190
|
declare class UnknownGuardError extends Error {
|
|
143
191
|
readonly guardName: string;
|
|
@@ -220,4 +268,4 @@ declare function createSnapshot<C, S extends string>(args: {
|
|
|
220
268
|
status?: "active" | "final";
|
|
221
269
|
}): Snapshot<C, S>;
|
|
222
270
|
|
|
223
|
-
export { Action, AsyncGuardError, Guard, GuardRef, Implementations, InvalidDefinitionError, MachineDef, Runtime, RuntimeDisposedError, RuntimeOptions, Snapshot, StateDef, StepResult, SubMachineError, TransitionConfig, TransitionDef, UnknownActionError, UnknownGuardError, assign, createMachine, createRuntime, createSnapshot, deepFreeze, defineMachine, evalGuard, freezeSnapshot, initialSnapshot, isAsyncGuardFn, mergeContext, normalizeTransition, normalizeTransitions, resolveGuard, resolveTransitions, setup, step };
|
|
271
|
+
export { Action, AsyncGuardError, Guard, GuardRef, Implementations, InvalidActionResultError, InvalidDefinitionError, MachineConfig, MachineDef, Runtime, RuntimeDisposedError, RuntimeOptions, Snapshot, StateDef, StepResult, SubMachineError, TransitionConfig, TransitionDef, UnknownActionError, UnknownGuardError, assign, createMachine, createRuntime, createSnapshot, deepFreeze, defineMachine, evalGuard, freezeSnapshot, initialSnapshot, isAsyncGuardFn, mergeContext, normalizeTransition, normalizeTransitions, resolveGuard, resolveTransitions, setup, step };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
export { InvalidDefinitionError, RESET_EVENT_TYPE, RuntimeDisposedError, SubMachineError, createMachine, createRuntime, defineMachine, initialSnapshot, setup } from './chunk-
|
|
2
|
-
export { UnknownActionError, assign, createSnapshot, deepFreeze, freezeSnapshot, mergeContext, normalizeTransition, normalizeTransitions, resolveTransitions, step } from './chunk-
|
|
1
|
+
export { InvalidDefinitionError, RESET_EVENT_TYPE, RuntimeDisposedError, SubMachineError, createMachine, createRuntime, defineMachine, initialSnapshot, setup } from './chunk-XA24A7VP.js';
|
|
2
|
+
export { InvalidActionResultError, UnknownActionError, assign, createSnapshot, deepFreeze, freezeSnapshot, mergeContext, normalizeTransition, normalizeTransitions, resolveTransitions, step } from './chunk-D6H64FSI.js';
|
|
3
3
|
import './chunk-JKZAOPQC.js';
|
|
4
|
-
export { AsyncGuardError, UnknownGuardError, evalGuard, isAsyncGuardFn, resolveGuard } from './chunk-
|
|
5
|
-
import './chunk-PZ5AY32C.js';
|
|
4
|
+
export { AsyncGuardError, UnknownGuardError, evalGuard, isAsyncGuardFn, resolveGuard } from './chunk-SSNKGEVB.js';
|
|
6
5
|
//# sourceMappingURL=index.js.map
|
|
7
6
|
//# sourceMappingURL=index.js.map
|
package/dist/inspect/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/inspect/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/inspect/index.ts"],"names":[],"mappings":";;;AAUO,SAAS,OACd,GAAA,GAAwE,CAAC,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,EAC9D;AAC9B,EAAA,OAAO,CAAC,IAAI,IAAA,KAAS;AACnB,IAAA,IAAA,EAAK;AACL,IAAA,IAAI,GAAG,OAAA,EAAS;AACd,MAAA,GAAA,CAAI,CAAA,CAAA,EAAI,EAAA,CAAG,KAAA,CAAM,IAAI,CAAA,EAAA,EAAK,EAAA,CAAG,IAAA,CAAK,KAAK,CAAA,QAAA,EAAM,EAAA,CAAG,IAAA,CAAK,KAAK,IAAI,EAAE,CAAA;AAAA,IAClE;AAAA,EACF,CAAA;AACF;AAaO,SAAS,QAAkE,IAAA,EAGjD;AAC/B,EAAA,OAAO,CAAC,IAAI,IAAA,KAAS;AACnB,IAAA,IAAA,EAAK;AACL,IAAA,IAAI,GAAG,OAAA,EAAS;AACd,MAAA,IAAA,CAAK,OAAA,CAAQ,QAAQ,IAAA,CAAK,GAAA,EAAK,KAAK,SAAA,CAAU,EAAA,CAAG,IAAI,CAAC,CAAA;AAAA,IACxD;AAAA,EACF,CAAA;AACF;AAcO,SAAS,SACd,IAAA,EAC8B;AAC9B,EAAA,OAAO,CAAC,IAAI,IAAA,KAAS;AACnB,IAAA,IAAA,EAAK;AACL,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,OAAO,EAAA,CAAG,KAAA;AAAA,MACV,MAAM,EAAA,CAAG,IAAA;AAAA,MACT,MAAM,EAAA,CAAG,IAAA;AAAA,MACT,SAAS,EAAA,CAAG;AAAA,KACb,CAAA;AAAA,EACH,CAAA;AACF","file":"index.cjs","sourcesContent":["import type { Middleware, MiddlewareContext } from \"../fsm/types.js\";\n\nexport type { Middleware, MiddlewareContext } from \"../fsm/types.js\";\n\n/**\n * Log every transition that changed the snapshot. Default formatter:\n * \"[EVENT_TYPE] oldState → newState\"\n *\n * Pass a custom `out` callback to integrate with structured logging.\n */\nexport function logger<Ctx, Evt extends { type: string }, States extends string>(\n out: (line: string, ctx: MiddlewareContext<Ctx, Evt, States>) => void = (l) => console.log(l),\n): Middleware<Ctx, Evt, States> {\n return (mw, next) => {\n next();\n if (mw.changed) {\n out(`[${mw.event.type}] ${mw.prev.value} → ${mw.next.value}`, mw);\n }\n };\n}\n\nexport type StorageLike = {\n setItem(key: string, value: string): void;\n};\n\n/**\n * Persist the latest snapshot to a storage-like sink on every change. The\n * snapshot is JSON-serialised; non-serializable context fields will throw.\n *\n * For replay, pair this with `aifsmjs/replay` and an event log of your own\n * choosing — this middleware only persists the latest snapshot.\n */\nexport function persist<Ctx, Evt extends { type: string }, States extends string>(opts: {\n key: string;\n storage: StorageLike;\n}): Middleware<Ctx, Evt, States> {\n return (mw, next) => {\n next();\n if (mw.changed) {\n opts.storage.setItem(opts.key, JSON.stringify(mw.next));\n }\n };\n}\n\n/**\n * Collect every event-snapshot pair into the supplied array. Useful for\n * test assertions, time-travel debugging, or building event logs to feed\n * back into `replay()`.\n */\nexport type RecordedEntry<Ctx, Evt, States extends string> = Readonly<{\n event: MiddlewareContext<Ctx, Evt, States>[\"event\"];\n prev: MiddlewareContext<Ctx, Evt, States>[\"prev\"];\n next: MiddlewareContext<Ctx, Evt, States>[\"next\"];\n changed: boolean;\n}>;\n\nexport function recorder<Ctx, Evt extends { type: string }, States extends string>(\n sink: RecordedEntry<Ctx, Evt, States>[],\n): Middleware<Ctx, Evt, States> {\n return (mw, next) => {\n next();\n sink.push({\n event: mw.event,\n prev: mw.prev,\n next: mw.next,\n changed: mw.changed,\n });\n };\n}\n"]}
|
package/dist/inspect/index.d.cts
CHANGED
package/dist/inspect/index.d.ts
CHANGED
package/dist/inspect/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/inspect/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/inspect/index.ts"],"names":[],"mappings":";AAUO,SAAS,OACd,GAAA,GAAwE,CAAC,MAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,EAC9D;AAC9B,EAAA,OAAO,CAAC,IAAI,IAAA,KAAS;AACnB,IAAA,IAAA,EAAK;AACL,IAAA,IAAI,GAAG,OAAA,EAAS;AACd,MAAA,GAAA,CAAI,CAAA,CAAA,EAAI,EAAA,CAAG,KAAA,CAAM,IAAI,CAAA,EAAA,EAAK,EAAA,CAAG,IAAA,CAAK,KAAK,CAAA,QAAA,EAAM,EAAA,CAAG,IAAA,CAAK,KAAK,IAAI,EAAE,CAAA;AAAA,IAClE;AAAA,EACF,CAAA;AACF;AAaO,SAAS,QAAkE,IAAA,EAGjD;AAC/B,EAAA,OAAO,CAAC,IAAI,IAAA,KAAS;AACnB,IAAA,IAAA,EAAK;AACL,IAAA,IAAI,GAAG,OAAA,EAAS;AACd,MAAA,IAAA,CAAK,OAAA,CAAQ,QAAQ,IAAA,CAAK,GAAA,EAAK,KAAK,SAAA,CAAU,EAAA,CAAG,IAAI,CAAC,CAAA;AAAA,IACxD;AAAA,EACF,CAAA;AACF;AAcO,SAAS,SACd,IAAA,EAC8B;AAC9B,EAAA,OAAO,CAAC,IAAI,IAAA,KAAS;AACnB,IAAA,IAAA,EAAK;AACL,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,OAAO,EAAA,CAAG,KAAA;AAAA,MACV,MAAM,EAAA,CAAG,IAAA;AAAA,MACT,MAAM,EAAA,CAAG,IAAA;AAAA,MACT,SAAS,EAAA,CAAG;AAAA,KACb,CAAA;AAAA,EACH,CAAA;AACF","file":"index.js","sourcesContent":["import type { Middleware, MiddlewareContext } from \"../fsm/types.js\";\n\nexport type { Middleware, MiddlewareContext } from \"../fsm/types.js\";\n\n/**\n * Log every transition that changed the snapshot. Default formatter:\n * \"[EVENT_TYPE] oldState → newState\"\n *\n * Pass a custom `out` callback to integrate with structured logging.\n */\nexport function logger<Ctx, Evt extends { type: string }, States extends string>(\n out: (line: string, ctx: MiddlewareContext<Ctx, Evt, States>) => void = (l) => console.log(l),\n): Middleware<Ctx, Evt, States> {\n return (mw, next) => {\n next();\n if (mw.changed) {\n out(`[${mw.event.type}] ${mw.prev.value} → ${mw.next.value}`, mw);\n }\n };\n}\n\nexport type StorageLike = {\n setItem(key: string, value: string): void;\n};\n\n/**\n * Persist the latest snapshot to a storage-like sink on every change. The\n * snapshot is JSON-serialised; non-serializable context fields will throw.\n *\n * For replay, pair this with `aifsmjs/replay` and an event log of your own\n * choosing — this middleware only persists the latest snapshot.\n */\nexport function persist<Ctx, Evt extends { type: string }, States extends string>(opts: {\n key: string;\n storage: StorageLike;\n}): Middleware<Ctx, Evt, States> {\n return (mw, next) => {\n next();\n if (mw.changed) {\n opts.storage.setItem(opts.key, JSON.stringify(mw.next));\n }\n };\n}\n\n/**\n * Collect every event-snapshot pair into the supplied array. Useful for\n * test assertions, time-travel debugging, or building event logs to feed\n * back into `replay()`.\n */\nexport type RecordedEntry<Ctx, Evt, States extends string> = Readonly<{\n event: MiddlewareContext<Ctx, Evt, States>[\"event\"];\n prev: MiddlewareContext<Ctx, Evt, States>[\"prev\"];\n next: MiddlewareContext<Ctx, Evt, States>[\"next\"];\n changed: boolean;\n}>;\n\nexport function recorder<Ctx, Evt extends { type: string }, States extends string>(\n sink: RecordedEntry<Ctx, Evt, States>[],\n): Middleware<Ctx, Evt, States> {\n return (mw, next) => {\n next();\n sink.push({\n event: mw.event,\n prev: mw.prev,\n next: mw.next,\n changed: mw.changed,\n });\n };\n}\n"]}
|
package/dist/pbt/index.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
3
|
+
var chunkYK25NVFC_cjs = require('../chunk-YK25NVFC.cjs');
|
|
4
|
+
var chunkVV5TKFQO_cjs = require('../chunk-VV5TKFQO.cjs');
|
|
5
|
+
var chunkVGLF5NQH_cjs = require('../chunk-VGLF5NQH.cjs');
|
|
6
6
|
require('../chunk-3B2USJ3H.cjs');
|
|
7
|
-
require('../chunk-
|
|
8
|
-
var chunkQ7SFCCGT_cjs = require('../chunk-Q7SFCCGT.cjs');
|
|
7
|
+
var chunkQCTA2X4J_cjs = require('../chunk-QCTA2X4J.cjs');
|
|
9
8
|
var fc2 = require('fast-check');
|
|
9
|
+
var util = require('util');
|
|
10
10
|
|
|
11
11
|
function _interopNamespace(e) {
|
|
12
12
|
if (e && e.__esModule) return e;
|
|
@@ -52,7 +52,7 @@ var SendCommand = class {
|
|
|
52
52
|
const before = r.getSnapshot();
|
|
53
53
|
r.send(this.event);
|
|
54
54
|
const after = r.getSnapshot();
|
|
55
|
-
const predicted =
|
|
55
|
+
const predicted = chunkVGLF5NQH_cjs.step(this.def, before, this.event, this.impl);
|
|
56
56
|
if (predicted.snapshot.value !== after.value) {
|
|
57
57
|
throw new Error(
|
|
58
58
|
`aifsmjs/pbt: determinism violation \u2014 predicted "${String(predicted.snapshot.value)}" but runtime returned "${String(after.value)}" after ${this.toString()}`
|
|
@@ -74,18 +74,6 @@ function commandsFromMachine(def, impl, eventArbitraries) {
|
|
|
74
74
|
}
|
|
75
75
|
return fc2__namespace.commands(arbs, { size: "+1" });
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
// src/pbt/properties.ts
|
|
79
|
-
var properties_exports = {};
|
|
80
|
-
chunkQ7SFCCGT_cjs.__export(properties_exports, {
|
|
81
|
-
assertAll: () => assertAll,
|
|
82
|
-
assignDoesNotMutate: () => assignDoesNotMutate,
|
|
83
|
-
guardsFalseNoTransition: () => guardsFalseNoTransition,
|
|
84
|
-
reachableStatesSubsetDeclared: () => reachableStatesSubsetDeclared,
|
|
85
|
-
replayEqualsFold: () => replayEqualsFold,
|
|
86
|
-
snapshotAlwaysFrozen: () => snapshotAlwaysFrozen,
|
|
87
|
-
unknownEventNoOp: () => unknownEventNoOp
|
|
88
|
-
});
|
|
89
77
|
function buildAssertOpts(opts) {
|
|
90
78
|
const out = {};
|
|
91
79
|
if (opts?.numRuns !== void 0) out.numRuns = opts.numRuns;
|
|
@@ -93,13 +81,20 @@ function buildAssertOpts(opts) {
|
|
|
93
81
|
if (opts?.verbose) out.verbose = true;
|
|
94
82
|
return out;
|
|
95
83
|
}
|
|
84
|
+
function contextEquals(a, b) {
|
|
85
|
+
return util.isDeepStrictEqual(a, b);
|
|
86
|
+
}
|
|
96
87
|
function snapshotAlwaysFrozen(def, impl, eventArbitraries, opts) {
|
|
97
88
|
fc2__namespace.assert(
|
|
98
89
|
fc2__namespace.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {
|
|
99
|
-
const real =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
90
|
+
const real = chunkYK25NVFC_cjs.createRuntime(def, impl, { dispatchEffects: false });
|
|
91
|
+
try {
|
|
92
|
+
const model = initialModel(def);
|
|
93
|
+
fc2__namespace.modelRun(() => ({ model, real }), cmds);
|
|
94
|
+
return Object.isFrozen(real.getSnapshot());
|
|
95
|
+
} finally {
|
|
96
|
+
real.dispose();
|
|
97
|
+
}
|
|
103
98
|
}),
|
|
104
99
|
buildAssertOpts(opts)
|
|
105
100
|
);
|
|
@@ -107,8 +102,8 @@ function snapshotAlwaysFrozen(def, impl, eventArbitraries, opts) {
|
|
|
107
102
|
function unknownEventNoOp(def, impl, unknownType, opts) {
|
|
108
103
|
fc2__namespace.assert(
|
|
109
104
|
fc2__namespace.property(fc2__namespace.constant(unknownType), (t) => {
|
|
110
|
-
const initial =
|
|
111
|
-
const result =
|
|
105
|
+
const initial = chunkYK25NVFC_cjs.initialSnapshot(def);
|
|
106
|
+
const result = chunkVGLF5NQH_cjs.step(def, initial, { type: t }, impl);
|
|
112
107
|
return result.changed === false && result.snapshot === initial && result.effects.length === 0;
|
|
113
108
|
}),
|
|
114
109
|
buildAssertOpts(opts)
|
|
@@ -118,13 +113,17 @@ function reachableStatesSubsetDeclared(def, impl, eventArbitraries, opts) {
|
|
|
118
113
|
const declared = new Set(Object.keys(def.states));
|
|
119
114
|
fc2__namespace.assert(
|
|
120
115
|
fc2__namespace.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {
|
|
121
|
-
const real =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
116
|
+
const real = chunkYK25NVFC_cjs.createRuntime(def, impl, { dispatchEffects: false });
|
|
117
|
+
try {
|
|
118
|
+
const model = initialModel(def);
|
|
119
|
+
fc2__namespace.modelRun(() => ({ model, real }), cmds);
|
|
120
|
+
for (const s of model.reached) {
|
|
121
|
+
if (!declared.has(s)) return false;
|
|
122
|
+
}
|
|
123
|
+
return declared.has(real.getSnapshot().value);
|
|
124
|
+
} finally {
|
|
125
|
+
real.dispose();
|
|
126
126
|
}
|
|
127
|
-
return declared.has(real.getSnapshot().value);
|
|
128
127
|
}),
|
|
129
128
|
buildAssertOpts(opts)
|
|
130
129
|
);
|
|
@@ -133,11 +132,15 @@ function replayEqualsFold(def, impl, eventArbitraries, opts) {
|
|
|
133
132
|
const eventArb = fc2__namespace.oneof(...Object.values(eventArbitraries));
|
|
134
133
|
fc2__namespace.assert(
|
|
135
134
|
fc2__namespace.property(fc2__namespace.array(eventArb, { maxLength: 32 }), (events) => {
|
|
136
|
-
const real =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
const real = chunkYK25NVFC_cjs.createRuntime(def, impl, { dispatchEffects: false });
|
|
136
|
+
try {
|
|
137
|
+
for (const e of events) real.send(e);
|
|
138
|
+
const live = real.getSnapshot();
|
|
139
|
+
const replayed = chunkVV5TKFQO_cjs.replay(chunkYK25NVFC_cjs.initialSnapshot(def), events, def, impl).snapshot;
|
|
140
|
+
return live.value === replayed.value && contextEquals(live.context, replayed.context);
|
|
141
|
+
} finally {
|
|
142
|
+
real.dispose();
|
|
143
|
+
}
|
|
141
144
|
}),
|
|
142
145
|
buildAssertOpts(opts)
|
|
143
146
|
);
|
|
@@ -146,7 +149,8 @@ function guardsFalseNoTransition(def, impl, eventArbitraries, opts) {
|
|
|
146
149
|
const blockedGuards = new Proxy(
|
|
147
150
|
{},
|
|
148
151
|
{
|
|
149
|
-
get: () => () => false
|
|
152
|
+
get: () => () => false,
|
|
153
|
+
getOwnPropertyDescriptor: () => ({ configurable: true })
|
|
150
154
|
}
|
|
151
155
|
);
|
|
152
156
|
const blockedImpl = {
|
|
@@ -154,17 +158,17 @@ function guardsFalseNoTransition(def, impl, eventArbitraries, opts) {
|
|
|
154
158
|
guards: blockedGuards
|
|
155
159
|
};
|
|
156
160
|
const isFullyGuarded = (value, eventType) => {
|
|
157
|
-
const candidates =
|
|
161
|
+
const candidates = chunkVGLF5NQH_cjs.normalizeTransitions(chunkQCTA2X4J_cjs.ownValue(def.states[value]?.on, eventType));
|
|
158
162
|
return candidates.length > 0 && candidates.every((t) => t.guard !== void 0);
|
|
159
163
|
};
|
|
160
164
|
fc2__namespace.assert(
|
|
161
165
|
fc2__namespace.property(
|
|
162
166
|
fc2__namespace.array(fc2__namespace.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),
|
|
163
167
|
(events) => {
|
|
164
|
-
let snap =
|
|
168
|
+
let snap = chunkYK25NVFC_cjs.initialSnapshot(def);
|
|
165
169
|
for (const e of events) {
|
|
166
170
|
const fullyGuarded = isFullyGuarded(snap.value, e.type);
|
|
167
|
-
const r =
|
|
171
|
+
const r = chunkVGLF5NQH_cjs.step(def, snap, e, blockedImpl);
|
|
168
172
|
if (fullyGuarded && r.changed !== false) return false;
|
|
169
173
|
snap = r.snapshot;
|
|
170
174
|
}
|
|
@@ -174,20 +178,28 @@ function guardsFalseNoTransition(def, impl, eventArbitraries, opts) {
|
|
|
174
178
|
buildAssertOpts(opts)
|
|
175
179
|
);
|
|
176
180
|
}
|
|
181
|
+
var ownState = (v) => [
|
|
182
|
+
...Reflect.ownKeys(v).flatMap((k) => [k, v[k]]),
|
|
183
|
+
...v instanceof Map || v instanceof Set ? [...v.entries()].flat() : [],
|
|
184
|
+
v instanceof Date && v.getTime()
|
|
185
|
+
];
|
|
177
186
|
function assignDoesNotMutate(def, impl, eventArbitraries, opts) {
|
|
178
|
-
const dummy = { a: 1, b: 2 };
|
|
179
|
-
const merged = chunkI354FONA_cjs.mergeContext(dummy, { b: 3 });
|
|
180
|
-
if (merged === dummy) throw new Error("aifsmjs/pbt: mergeContext returned the same reference");
|
|
181
187
|
fc2__namespace.assert(
|
|
182
188
|
fc2__namespace.property(
|
|
183
189
|
fc2__namespace.array(fc2__namespace.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),
|
|
184
190
|
(events) => {
|
|
185
|
-
let snap =
|
|
191
|
+
let snap = chunkYK25NVFC_cjs.initialSnapshot(def);
|
|
186
192
|
for (const e of events) {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
193
|
+
const before = /* @__PURE__ */ new Map();
|
|
194
|
+
const walk = (v) => {
|
|
195
|
+
if (!v || typeof v !== "object" || before.has(v)) return;
|
|
196
|
+
const own = ownState(v);
|
|
197
|
+
before.set(v, own);
|
|
198
|
+
own.forEach(walk);
|
|
199
|
+
};
|
|
200
|
+
walk(snap.context);
|
|
201
|
+
snap = chunkVGLF5NQH_cjs.step(def, snap, e, impl).snapshot;
|
|
202
|
+
for (const [obj, own] of before) if (!contextEquals(ownState(obj), own)) return false;
|
|
191
203
|
}
|
|
192
204
|
return true;
|
|
193
205
|
}
|
|
@@ -205,7 +217,16 @@ function assertAll(def, impl, eventArbitraries, opts) {
|
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
// src/pbt/index.ts
|
|
208
|
-
var properties =
|
|
220
|
+
var properties = Object.freeze({
|
|
221
|
+
assertAll,
|
|
222
|
+
assignDoesNotMutate,
|
|
223
|
+
contextEquals,
|
|
224
|
+
guardsFalseNoTransition,
|
|
225
|
+
reachableStatesSubsetDeclared,
|
|
226
|
+
replayEqualsFold,
|
|
227
|
+
snapshotAlwaysFrozen,
|
|
228
|
+
unknownEventNoOp
|
|
229
|
+
});
|
|
209
230
|
|
|
210
231
|
exports.assertAll = assertAll;
|
|
211
232
|
exports.assignDoesNotMutate = assignDoesNotMutate;
|
package/dist/pbt/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pbt/commands.ts","../../src/pbt/properties.ts","../../src/pbt/index.ts"],"names":["step","fc","__export","fc2","createRuntime","initialSnapshot","replay","normalizeTransitions","mergeContext"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as fc from \"fast-check\";\nimport { step } from \"../fsm/lifecycle.js\";\nimport type { Implementations, MachineDef, Runtime, Snapshot } from \"../fsm/types.js\";\n\n/**\n * Pure-model representation of an FSM run, used by `fc.commands`.\n * `reached` tracks every state visited so generic properties can check\n * containment without re-running.\n */\nexport type FsmModel<Ctx, States extends string> = {\n value: States;\n context: Ctx;\n status: \"active\" | \"final\";\n reached: Set<States>;\n};\n\nexport type EventArbitraries<Evt extends { type: string }> = Readonly<\n Record<string, fc.Arbitrary<Evt>>\n>;\n\nexport type FsmCommand<Ctx, Evt extends { type: string }, States extends string> = fc.Command<\n FsmModel<Ctx, States>,\n Runtime<Ctx, Evt, States>\n>;\n\nexport function initialModel<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n): FsmModel<Ctx, States> {\n return {\n value: def.initial,\n context: def.context,\n status: def.states[def.initial]?.final ? \"final\" : \"active\",\n reached: new Set([def.initial]),\n };\n}\n\nclass SendCommand<Ctx, Evt extends { type: string }, States extends string>\n implements FsmCommand<Ctx, Evt, States>\n{\n constructor(\n private readonly event: Evt,\n private readonly def: MachineDef<Ctx, Evt, States>,\n private readonly impl: Implementations<Ctx, Evt>,\n ) {}\n\n check(_m: Readonly<FsmModel<Ctx, States>>): boolean {\n // Every event is always applicable; invariants are asserted in run().\n return true;\n }\n\n run(m: FsmModel<Ctx, States>, r: Runtime<Ctx, Evt, States>): void {\n const before: Snapshot<Ctx, States> = r.getSnapshot();\n r.send(this.event);\n const after = r.getSnapshot();\n\n const predicted = step(this.def, before, this.event, this.impl);\n\n /* v8 ignore start — invariant guard: fires only when the live runtime\n diverges from the pure step() prediction, i.e. an internal bug. */\n if (predicted.snapshot.value !== after.value) {\n throw new Error(\n `aifsmjs/pbt: determinism violation — predicted \"${String(predicted.snapshot.value)}\" but runtime returned \"${String(after.value)}\" after ${this.toString()}`,\n );\n }\n /* v8 ignore stop */\n\n m.value = after.value;\n m.context = after.context as Ctx;\n m.status = after.status;\n m.reached.add(after.value);\n }\n\n toString(): string {\n return `send(${JSON.stringify(this.event)})`;\n }\n}\n\n/**\n * Build an `fc.Arbitrary` of FSM command sequences. Each command pulls one\n * event from the user-supplied arbitrary map and, when run, asserts that the\n * pure `step()` prediction matches the runtime's observable outcome.\n *\n * Pair this arbitrary with `fc.property(...)` inside a `fc.assert(...)` call,\n * or use the helpers in `aifsmjs/pbt` properties to get the six generic\n * invariants for free.\n */\nexport function commandsFromMachine<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n): fc.Arbitrary<Iterable<FsmCommand<Ctx, Evt, States>>> {\n const arbs: fc.Arbitrary<FsmCommand<Ctx, Evt, States>>[] = [];\n for (const arb of Object.values(eventArbitraries)) {\n arbs.push(arb.map((event) => new SendCommand(event, def, impl)));\n }\n return fc.commands(arbs, { size: \"+1\" });\n}\n","import * as fc from \"fast-check\";\nimport { initialSnapshot } from \"../fsm/definition.js\";\nimport { step } from \"../fsm/lifecycle.js\";\nimport { normalizeTransitions } from \"../fsm/resolver.js\";\nimport { createRuntime } from \"../fsm/runtime.js\";\nimport type { Guard, Implementations, MachineDef } from \"../fsm/types.js\";\nimport { mergeContext } from \"../fsm/updater.js\";\nimport { replay } from \"../replay/index.js\";\nimport {\n type EventArbitraries,\n type FsmModel,\n commandsFromMachine,\n initialModel,\n} from \"./commands.js\";\n\nexport type AssertOpts = Readonly<{\n numRuns?: number;\n seed?: number;\n verbose?: boolean;\n}>;\n\nfunction buildAssertOpts(opts: AssertOpts | undefined): fc.Parameters<unknown> {\n const out: fc.Parameters<unknown> = {};\n if (opts?.numRuns !== undefined) out.numRuns = opts.numRuns;\n if (opts?.seed !== undefined) out.seed = opts.seed;\n if (opts?.verbose) out.verbose = true;\n return out;\n}\n\n/**\n * #1 snapshotAlwaysFrozen — after any event sequence the live snapshot remains\n * frozen at the top level.\n */\nexport function snapshotAlwaysFrozen<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {\n const real = createRuntime(def, impl);\n const model: FsmModel<Ctx, States> = initialModel(def);\n fc.modelRun(() => ({ model, real }), cmds);\n return Object.isFrozen(real.getSnapshot());\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #2 unknownEventNoOp — sending an event whose `type` is not declared in any\n * state's `on` map never changes the snapshot.\n */\nexport function unknownEventNoOp<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n unknownType: string,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(fc.constant(unknownType), (t) => {\n const initial = initialSnapshot(def);\n const result = step(def, initial, { type: t } as unknown as Evt, impl);\n return result.changed === false && result.snapshot === initial && result.effects.length === 0;\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #3 reachableStatesSubsetDeclared — every state visited during a run belongs\n * to `def.states`.\n */\nexport function reachableStatesSubsetDeclared<\n Ctx,\n Evt extends { type: string },\n States extends string,\n>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const declared = new Set<string>(Object.keys(def.states));\n fc.assert(\n fc.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {\n const real = createRuntime(def, impl);\n const model: FsmModel<Ctx, States> = initialModel(def);\n fc.modelRun(() => ({ model, real }), cmds);\n for (const s of model.reached as Set<string>) {\n /* v8 ignore next — property failure branch; an unreachable state would indicate a bug. */\n if (!declared.has(s)) return false;\n }\n return declared.has(real.getSnapshot().value);\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #4 replayEqualsFold — `replay(initial, log)` produces the same final state\n * as a live runtime fed the same events. Effects dispatched by the runtime are\n * ignored; the comparison is on `{ value, context }`.\n */\nexport function replayEqualsFold<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const eventArb = fc.oneof(...Object.values(eventArbitraries));\n fc.assert(\n fc.property(fc.array(eventArb, { maxLength: 32 }), (events) => {\n const real = createRuntime(def, impl, { dispatchEffects: false });\n for (const e of events) real.send(e);\n const live = real.getSnapshot();\n const replayed = replay(initialSnapshot(def), events, def, impl).snapshot;\n return (\n live.value === replayed.value &&\n JSON.stringify(live.context) === JSON.stringify(replayed.context)\n );\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #5 guardsFalseNoTransition — when every candidate transition for the current\n * (state, event) pair carries a guard and every guard returns `false`, the\n * snapshot is unchanged (`changed === false`).\n *\n * Implementation: synthesise an impl that forces every guard to `false`, then\n * for each step whose candidate list is fully guarded, assert the step did not\n * change state. Steps with an unguarded fallback candidate (which fires even\n * when all guards are false) are skipped — the README claim is specifically\n * about the all-guards-false case.\n */\nexport function guardsFalseNoTransition<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const blockedGuards = new Proxy(\n {},\n {\n get: () => () => false,\n },\n ) as Readonly<Record<string, Guard<Ctx, Evt>>>;\n const blockedImpl: Implementations<Ctx, Evt> = {\n ...impl,\n guards: blockedGuards,\n };\n // True when every candidate transition for (value, eventType) carries a\n // guard — i.e. blocking all guards leaves no unconditional fallback, so a\n // correct step() must report changed === false.\n const isFullyGuarded = (value: States, eventType: string): boolean => {\n const candidates = normalizeTransitions(def.states[value]?.on?.[eventType]);\n return candidates.length > 0 && candidates.every((t) => t.guard !== undefined);\n };\n fc.assert(\n fc.property(\n fc.array(fc.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),\n (events) => {\n let snap = initialSnapshot(def);\n for (const e of events) {\n const fullyGuarded = isFullyGuarded(snap.value, e.type);\n const r = step(def, snap, e, blockedImpl);\n // The named invariant: all guards false + no unconditional fallback\n // ⇒ no transition. Without this assertion the property was vacuous\n // (it only failed if step() threw).\n if (fullyGuarded && r.changed !== false) return false;\n snap = r.snapshot;\n }\n return true;\n },\n ),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #6 assignDoesNotMutate — running an `assign`-style action never mutates the\n * previous context object. Verified by deep-equality check on a snapshot taken\n * before each event.\n */\nexport function assignDoesNotMutate<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n // Quick sanity guard: mergeContext is the only context mutator used by step.\n const dummy = { a: 1, b: 2 };\n const merged = mergeContext(dummy, { b: 3 });\n /* v8 ignore next — invariant guard; mergeContext returning the same ref would mean unit tests have already broken. */\n if (merged === dummy) throw new Error(\"aifsmjs/pbt: mergeContext returned the same reference\");\n\n fc.assert(\n fc.property(\n fc.array(fc.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),\n (events) => {\n let snap = initialSnapshot(def);\n for (const e of events) {\n const beforeCtxSerialised = JSON.stringify(snap.context);\n step(def, snap, e, impl);\n /* v8 ignore next — property failure branch; step() mutating snap.context would indicate a bug. */\n if (JSON.stringify(snap.context) !== beforeCtxSerialised) return false;\n // Continue with the actual result for subsequent events\n snap = step(def, snap, e, impl).snapshot;\n }\n return true;\n },\n ),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * Assert every generic property in one call. Use this when you don't need\n * fine-grained control over per-property options.\n */\nexport function assertAll<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts & { unknownEventType?: string },\n): void {\n snapshotAlwaysFrozen(def, impl, eventArbitraries, opts);\n unknownEventNoOp(def, impl, opts?.unknownEventType ?? \"__AIFSMJS_UNKNOWN__\", opts);\n reachableStatesSubsetDeclared(def, impl, eventArbitraries, opts);\n replayEqualsFold(def, impl, eventArbitraries, opts);\n guardsFalseNoTransition(def, impl, eventArbitraries, opts);\n assignDoesNotMutate(def, impl, eventArbitraries, opts);\n}\n","export {\n commandsFromMachine,\n initialModel,\n type EventArbitraries,\n type FsmCommand,\n type FsmModel,\n} from \"./commands.js\";\n\nexport {\n assertAll,\n assignDoesNotMutate,\n guardsFalseNoTransition,\n reachableStatesSubsetDeclared,\n replayEqualsFold,\n snapshotAlwaysFrozen,\n unknownEventNoOp,\n type AssertOpts,\n} from \"./properties.js\";\n\n// Convenience namespace mirroring the README:\n// import { properties } from \"aifsmjs/pbt\"\n// properties.snapshotAlwaysFrozen(...)\nimport * as propertiesModule from \"./properties.js\";\nexport const properties = propertiesModule;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/pbt/commands.ts","../../src/pbt/properties.ts","../../src/pbt/index.ts"],"names":["step","fc","isDeepStrictEqual","fc2","createRuntime","initialSnapshot","replay","normalizeTransitions","ownValue"],"mappings":"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","file":"index.cjs","sourcesContent":["import * as fc from \"fast-check\";\nimport { step } from \"../fsm/lifecycle.js\";\nimport type { Implementations, MachineDef, Runtime, Snapshot } from \"../fsm/types.js\";\n\n/**\n * Pure-model representation of an FSM run, used by `fc.commands`.\n * `reached` tracks every state visited so generic properties can check\n * containment without re-running.\n */\nexport type FsmModel<Ctx, States extends string> = {\n value: States;\n context: Ctx;\n status: \"active\" | \"final\";\n reached: Set<States>;\n};\n\nexport type EventArbitraries<Evt extends { type: string }> = Readonly<\n Record<string, fc.Arbitrary<Evt>>\n>;\n\nexport type FsmCommand<Ctx, Evt extends { type: string }, States extends string> = fc.Command<\n FsmModel<Ctx, States>,\n Runtime<Ctx, Evt, States>\n>;\n\nexport function initialModel<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n): FsmModel<Ctx, States> {\n return {\n value: def.initial,\n context: def.context,\n status: def.states[def.initial]?.final ? \"final\" : \"active\",\n reached: new Set([def.initial]),\n };\n}\n\nclass SendCommand<Ctx, Evt extends { type: string }, States extends string>\n implements FsmCommand<Ctx, Evt, States>\n{\n constructor(\n private readonly event: Evt,\n private readonly def: MachineDef<Ctx, Evt, States>,\n private readonly impl: Implementations<Ctx, Evt>,\n ) {}\n\n check(_m: Readonly<FsmModel<Ctx, States>>): boolean {\n // Every event is always applicable; invariants are asserted in run().\n return true;\n }\n\n run(m: FsmModel<Ctx, States>, r: Runtime<Ctx, Evt, States>): void {\n const before: Snapshot<Ctx, States> = r.getSnapshot();\n r.send(this.event);\n const after = r.getSnapshot();\n\n const predicted = step(this.def, before, this.event, this.impl);\n\n /* v8 ignore start — invariant guard: fires only when the live runtime\n diverges from the pure step() prediction, i.e. an internal bug. */\n if (predicted.snapshot.value !== after.value) {\n throw new Error(\n `aifsmjs/pbt: determinism violation — predicted \"${String(predicted.snapshot.value)}\" but runtime returned \"${String(after.value)}\" after ${this.toString()}`,\n );\n }\n /* v8 ignore stop */\n\n m.value = after.value;\n m.context = after.context as Ctx;\n m.status = after.status;\n m.reached.add(after.value);\n }\n\n toString(): string {\n return `send(${JSON.stringify(this.event)})`;\n }\n}\n\n/**\n * Build an `fc.Arbitrary` of FSM command sequences. Each command pulls one\n * event from the user-supplied arbitrary map and, when run, asserts that the\n * pure `step()` prediction matches the runtime's observable outcome.\n *\n * Pair this arbitrary with `fc.property(...)` inside a `fc.assert(...)` call,\n * or use the helpers in `aifsmjs/pbt` properties to get the six generic\n * invariants for free.\n */\nexport function commandsFromMachine<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n): fc.Arbitrary<Iterable<FsmCommand<Ctx, Evt, States>>> {\n const arbs: fc.Arbitrary<FsmCommand<Ctx, Evt, States>>[] = [];\n for (const arb of Object.values(eventArbitraries)) {\n arbs.push(arb.map((event) => new SendCommand(event, def, impl)));\n }\n return fc.commands(arbs, { size: \"+1\" });\n}\n","import { isDeepStrictEqual } from \"node:util\";\nimport * as fc from \"fast-check\";\nimport { initialSnapshot } from \"../fsm/definition.js\";\nimport { ownValue } from \"../fsm/evaluator.js\";\nimport { step } from \"../fsm/lifecycle.js\";\nimport { normalizeTransitions } from \"../fsm/resolver.js\";\nimport { createRuntime } from \"../fsm/runtime.js\";\nimport type { Guard, Implementations, MachineDef } from \"../fsm/types.js\";\nimport { replay } from \"../replay/index.js\";\nimport {\n type EventArbitraries,\n type FsmModel,\n commandsFromMachine,\n initialModel,\n} from \"./commands.js\";\n\nexport type AssertOpts = Readonly<{\n numRuns?: number;\n seed?: number;\n verbose?: boolean;\n}>;\n\nfunction buildAssertOpts(opts: AssertOpts | undefined): fc.Parameters<unknown> {\n const out: fc.Parameters<unknown> = {};\n if (opts?.numRuns !== undefined) out.numRuns = opts.numRuns;\n if (opts?.seed !== undefined) out.seed = opts.seed;\n if (opts?.verbose) out.verbose = true;\n return out;\n}\n\n/**\n * Structural deep-equality for two context values (C3). Backed by `node:util`\n * `isDeepStrictEqual`, replacing the previous `JSON.stringify(a) === JSON.stringify(b)`\n * oracle which was unsound:\n *\n * - key-order-sensitive → false-FAIL on `{a:1,b:2}` vs `{b:2,a:1}`;\n * - drops undefined keys → false-PASS on `{v:undefined,w:1}` vs `{w:1}`;\n * - lossy for `Map`/`Set`/`Date` (all serialise to `{}` or an ISO string);\n * - throws on `BigInt`.\n *\n * `isDeepStrictEqual` distinguishes present-but-undefined from absent keys,\n * compares `Map`/`Set`/`Date` by contents, and tolerates `BigInt` — exactly\n * the verdicts a context-equality oracle for PBT requires. No new dependency\n * (Node built-in; the package already targets Node >=18).\n */\nexport function contextEquals(a: unknown, b: unknown): boolean {\n return isDeepStrictEqual(a, b);\n}\n\n/**\n * #1 snapshotAlwaysFrozen — after any event sequence the live snapshot remains\n * frozen at the top level.\n */\nexport function snapshotAlwaysFrozen<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {\n const real = createRuntime(def, impl, { dispatchEffects: false });\n // Dispose every generated run's runtime (its signal aborts, its child\n // runtimes are torn down); the asserted value is computed first.\n try {\n const model: FsmModel<Ctx, States> = initialModel(def);\n fc.modelRun(() => ({ model, real }), cmds);\n return Object.isFrozen(real.getSnapshot());\n } finally {\n real.dispose();\n }\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #2 unknownEventNoOp — sending an event whose `type` is not declared in any\n * state's `on` map never changes the snapshot.\n */\nexport function unknownEventNoOp<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n unknownType: string,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(fc.constant(unknownType), (t) => {\n const initial = initialSnapshot(def);\n const result = step(def, initial, { type: t } as unknown as Evt, impl);\n return result.changed === false && result.snapshot === initial && result.effects.length === 0;\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #3 reachableStatesSubsetDeclared — every state visited during a run belongs\n * to `def.states`.\n */\nexport function reachableStatesSubsetDeclared<\n Ctx,\n Evt extends { type: string },\n States extends string,\n>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const declared = new Set<string>(Object.keys(def.states));\n fc.assert(\n fc.property(commandsFromMachine(def, impl, eventArbitraries), (cmds) => {\n const real = createRuntime(def, impl, { dispatchEffects: false });\n try {\n const model: FsmModel<Ctx, States> = initialModel(def);\n fc.modelRun(() => ({ model, real }), cmds);\n for (const s of model.reached as Set<string>) {\n /* v8 ignore next — property failure branch; an unreachable state would indicate a bug. */\n if (!declared.has(s)) return false;\n }\n return declared.has(real.getSnapshot().value);\n } finally {\n real.dispose();\n }\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #4 replayEqualsFold — `replay(initial, log)` produces the same final state\n * as a live runtime fed the same events. Effects dispatched by the runtime are\n * ignored; the comparison is on `{ value, context }`.\n */\nexport function replayEqualsFold<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n const eventArb = fc.oneof(...Object.values(eventArbitraries));\n fc.assert(\n fc.property(fc.array(eventArb, { maxLength: 32 }), (events) => {\n const real = createRuntime(def, impl, { dispatchEffects: false });\n try {\n for (const e of events) real.send(e);\n const live = real.getSnapshot();\n const replayed = replay(initialSnapshot(def), events, def, impl).snapshot;\n return live.value === replayed.value && contextEquals(live.context, replayed.context);\n } finally {\n real.dispose();\n }\n }),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * #5 guardsFalseNoTransition — when every candidate transition for the current\n * (state, event) pair carries a guard and every guard returns `false`, the\n * snapshot is unchanged (`changed === false`).\n *\n * Implementation: synthesise an impl that forces every guard to `false`, then\n * for each step whose candidate list is fully guarded, assert the step did not\n * change state. Steps with an unguarded fallback candidate (which fires even\n * when all guards are false) are skipped — the README claim is specifically\n * about the all-guards-false case.\n */\nexport function guardsFalseNoTransition<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n // Every key reads as an own property: step() resolves guard refs with an\n // own-key lookup (the value itself still comes from `get`), so a `get` trap\n // alone would surface as UnknownGuardError.\n const blockedGuards = new Proxy(\n {},\n {\n get: () => () => false,\n getOwnPropertyDescriptor: () => ({ configurable: true }),\n },\n ) as Readonly<Record<string, Guard<Ctx, Evt>>>;\n const blockedImpl: Implementations<Ctx, Evt> = {\n ...impl,\n guards: blockedGuards,\n };\n // True when every candidate transition for (value, eventType) carries a\n // guard — i.e. blocking all guards leaves no unconditional fallback, so a\n // correct step() must report changed === false.\n const isFullyGuarded = (value: States, eventType: string): boolean => {\n const candidates = normalizeTransitions(ownValue(def.states[value]?.on, eventType));\n return candidates.length > 0 && candidates.every((t) => t.guard !== undefined);\n };\n fc.assert(\n fc.property(\n fc.array(fc.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),\n (events) => {\n let snap = initialSnapshot(def);\n for (const e of events) {\n const fullyGuarded = isFullyGuarded(snap.value, e.type);\n const r = step(def, snap, e, blockedImpl);\n // The named invariant: all guards false + no unconditional fallback\n // ⇒ no transition. Without this assertion the property was vacuous\n // (it only failed if step() threw).\n if (fullyGuarded && r.changed !== false) return false;\n snap = r.snapshot;\n }\n return true;\n },\n ),\n buildAssertOpts(opts),\n );\n}\n\n// Own keys/values of one object, plus Map/Set entries and Date time.\nconst ownState = (v: object): unknown[] => [\n ...Reflect.ownKeys(v).flatMap((k) => [k, (v as Record<PropertyKey, unknown>)[k]]),\n ...(v instanceof Map || v instanceof Set ? [...v.entries()].flat() : []),\n v instanceof Date && v.getTime(),\n];\n\n/**\n * #6 assignDoesNotMutate — running an `assign`-style action never mutates the\n * previous context object. Verified by re-checking the own state of every\n * object reachable from the pre-step context.\n */\nexport function assignDoesNotMutate<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts,\n): void {\n fc.assert(\n fc.property(\n fc.array(fc.oneof(...Object.values(eventArbitraries)), { maxLength: 16 }),\n (events) => {\n let snap = initialSnapshot(def);\n for (const e of events) {\n // Record, not clone (clones drop prototypes, throw on functions).\n const before = new Map<object, unknown[]>();\n const walk = (v: unknown): void => {\n if (!v || typeof v !== \"object\" || before.has(v)) return;\n const own = ownState(v);\n before.set(v, own);\n own.forEach(walk);\n };\n walk(snap.context);\n // `before` keeps the pre-step objects, so advancing `snap` first is safe.\n snap = step(def, snap, e, impl).snapshot;\n for (const [obj, own] of before) if (!contextEquals(ownState(obj), own)) return false;\n }\n return true;\n },\n ),\n buildAssertOpts(opts),\n );\n}\n\n/**\n * Assert every generic property in one call. Use this when you don't need\n * fine-grained control over per-property options.\n */\nexport function assertAll<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n eventArbitraries: EventArbitraries<Evt>,\n opts?: AssertOpts & { unknownEventType?: string },\n): void {\n snapshotAlwaysFrozen(def, impl, eventArbitraries, opts);\n unknownEventNoOp(def, impl, opts?.unknownEventType ?? \"__AIFSMJS_UNKNOWN__\", opts);\n reachableStatesSubsetDeclared(def, impl, eventArbitraries, opts);\n replayEqualsFold(def, impl, eventArbitraries, opts);\n guardsFalseNoTransition(def, impl, eventArbitraries, opts);\n assignDoesNotMutate(def, impl, eventArbitraries, opts);\n}\n","export {\n commandsFromMachine,\n initialModel,\n type EventArbitraries,\n type FsmCommand,\n type FsmModel,\n} from \"./commands.js\";\n\nexport {\n assertAll,\n assignDoesNotMutate,\n guardsFalseNoTransition,\n reachableStatesSubsetDeclared,\n replayEqualsFold,\n snapshotAlwaysFrozen,\n unknownEventNoOp,\n type AssertOpts,\n} from \"./properties.js\";\n\n// Convenience namespace mirroring the README:\n// import { properties } from \"aifsmjs/pbt\"\n// properties.snapshotAlwaysFrozen(...)\n// A frozen object literal rather than `import * as`: a namespace re-export\n// makes tsup emit a shared `__export` helper chunk that every subpath entry\n// (root, guards, timer, ...) then imports and pays for.\nimport {\n assertAll,\n assignDoesNotMutate,\n contextEquals,\n guardsFalseNoTransition,\n reachableStatesSubsetDeclared,\n replayEqualsFold,\n snapshotAlwaysFrozen,\n unknownEventNoOp,\n} from \"./properties.js\";\nexport const properties = Object.freeze({\n assertAll,\n assignDoesNotMutate,\n contextEquals,\n guardsFalseNoTransition,\n reachableStatesSubsetDeclared,\n replayEqualsFold,\n snapshotAlwaysFrozen,\n unknownEventNoOp,\n});\n"]}
|
package/dist/pbt/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fc from 'fast-check';
|
|
2
|
-
import { j as Runtime, f as MachineDef, I as Implementations } from '../types-
|
|
2
|
+
import { j as Runtime, f as MachineDef, I as Implementations } from '../types-CrDxFfBx.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Pure-model representation of an FSM run, used by `fc.commands`.
|
|
@@ -39,6 +39,22 @@ type AssertOpts = Readonly<{
|
|
|
39
39
|
seed?: number;
|
|
40
40
|
verbose?: boolean;
|
|
41
41
|
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Structural deep-equality for two context values (C3). Backed by `node:util`
|
|
44
|
+
* `isDeepStrictEqual`, replacing the previous `JSON.stringify(a) === JSON.stringify(b)`
|
|
45
|
+
* oracle which was unsound:
|
|
46
|
+
*
|
|
47
|
+
* - key-order-sensitive → false-FAIL on `{a:1,b:2}` vs `{b:2,a:1}`;
|
|
48
|
+
* - drops undefined keys → false-PASS on `{v:undefined,w:1}` vs `{w:1}`;
|
|
49
|
+
* - lossy for `Map`/`Set`/`Date` (all serialise to `{}` or an ISO string);
|
|
50
|
+
* - throws on `BigInt`.
|
|
51
|
+
*
|
|
52
|
+
* `isDeepStrictEqual` distinguishes present-but-undefined from absent keys,
|
|
53
|
+
* compares `Map`/`Set`/`Date` by contents, and tolerates `BigInt` — exactly
|
|
54
|
+
* the verdicts a context-equality oracle for PBT requires. No new dependency
|
|
55
|
+
* (Node built-in; the package already targets Node >=18).
|
|
56
|
+
*/
|
|
57
|
+
declare function contextEquals(a: unknown, b: unknown): boolean;
|
|
42
58
|
/**
|
|
43
59
|
* #1 snapshotAlwaysFrozen — after any event sequence the live snapshot remains
|
|
44
60
|
* frozen at the top level.
|
|
@@ -84,8 +100,8 @@ declare function guardsFalseNoTransition<Ctx, Evt extends {
|
|
|
84
100
|
}, States extends string>(def: MachineDef<Ctx, Evt, States>, impl: Implementations<Ctx, Evt>, eventArbitraries: EventArbitraries<Evt>, opts?: AssertOpts): void;
|
|
85
101
|
/**
|
|
86
102
|
* #6 assignDoesNotMutate — running an `assign`-style action never mutates the
|
|
87
|
-
* previous context object. Verified by
|
|
88
|
-
*
|
|
103
|
+
* previous context object. Verified by re-checking the own state of every
|
|
104
|
+
* object reachable from the pre-step context.
|
|
89
105
|
*/
|
|
90
106
|
declare function assignDoesNotMutate<Ctx, Evt extends {
|
|
91
107
|
type: string;
|
|
@@ -100,18 +116,15 @@ declare function assertAll<Ctx, Evt extends {
|
|
|
100
116
|
unknownEventType?: string;
|
|
101
117
|
}): void;
|
|
102
118
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
declare const properties: typeof propertiesModule;
|
|
119
|
+
declare const properties: Readonly<{
|
|
120
|
+
assertAll: typeof assertAll;
|
|
121
|
+
assignDoesNotMutate: typeof assignDoesNotMutate;
|
|
122
|
+
contextEquals: typeof contextEquals;
|
|
123
|
+
guardsFalseNoTransition: typeof guardsFalseNoTransition;
|
|
124
|
+
reachableStatesSubsetDeclared: typeof reachableStatesSubsetDeclared;
|
|
125
|
+
replayEqualsFold: typeof replayEqualsFold;
|
|
126
|
+
snapshotAlwaysFrozen: typeof snapshotAlwaysFrozen;
|
|
127
|
+
unknownEventNoOp: typeof unknownEventNoOp;
|
|
128
|
+
}>;
|
|
116
129
|
|
|
117
130
|
export { type AssertOpts, type EventArbitraries, type FsmCommand, type FsmModel, assertAll, assignDoesNotMutate, commandsFromMachine, guardsFalseNoTransition, initialModel, properties, reachableStatesSubsetDeclared, replayEqualsFold, snapshotAlwaysFrozen, unknownEventNoOp };
|