aifsmjs 0.5.9 → 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-LG2AH5X6.js → chunk-XA24A7VP.js} +143 -152
- package/dist/chunk-XA24A7VP.js.map +1 -0
- package/dist/{chunk-TPCDOVU4.cjs → chunk-YK25NVFC.cjs} +147 -156
- 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 +65 -49
- package/dist/pbt/index.cjs.map +1 -1
- package/dist/pbt/index.d.cts +13 -17
- package/dist/pbt/index.d.ts +13 -17
- package/dist/pbt/index.js +57 -41
- 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 +70 -16
- package/package.json +57 -22
- 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-LG2AH5X6.js.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-TPCDOVU4.cjs.map +0 -1
- package/dist/chunk-ZLQ7HZCE.js.map +0 -1
|
@@ -150,16 +150,19 @@ type MiddlewareContext<Ctx, Evt, States extends string> = Readonly<{
|
|
|
150
150
|
/**
|
|
151
151
|
* The triggering event. May be the user's `Evt` (from `send()` or an
|
|
152
152
|
* explicit `reset(event)`) or the `ResetEvent` sentinel emitted by a
|
|
153
|
-
* `reset()` with no event argument.
|
|
153
|
+
* `reset()` with no event argument. This is the caller's event object,
|
|
154
|
+
* passed unfrozen; treat it as read-only.
|
|
154
155
|
*/
|
|
155
156
|
event: Evt | ResetEvent;
|
|
157
|
+
/** Deep-frozen effect descriptors (payloads included) about to be dispatched. */
|
|
156
158
|
effects: readonly Effect[];
|
|
157
159
|
changed: boolean;
|
|
158
160
|
}>;
|
|
159
161
|
type Middleware<Ctx, Evt, States extends string> = (ctx: MiddlewareContext<Ctx, Evt, States>, next: () => void) => void;
|
|
160
162
|
/**
|
|
161
|
-
* Payload of the `'transition'` runtime event — emitted
|
|
162
|
-
*
|
|
163
|
+
* Payload of the `'transition'` runtime event — emitted whenever a transition
|
|
164
|
+
* fired (`changed === true`), including an internal transition whose state
|
|
165
|
+
* `value` did not change (only its `context` did).
|
|
163
166
|
*/
|
|
164
167
|
type RuntimeTransitionEvent<Ctx, Evt, States extends string> = Readonly<{
|
|
165
168
|
prev: Snapshot<Ctx, States>;
|
|
@@ -170,9 +173,11 @@ type RuntimeTransitionEvent<Ctx, Evt, States extends string> = Readonly<{
|
|
|
170
173
|
}>;
|
|
171
174
|
/**
|
|
172
175
|
* Payload of the `'error'` runtime event — currently emitted for async effect
|
|
173
|
-
* handler rejections (which would otherwise become unhandled).
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
+
* handler rejections (which would otherwise become unhandled). With no
|
|
177
|
+
* `'error'` listener (none registered, or cleared by `dispose()`) a rejection
|
|
178
|
+
* is discarded; outside production (`NODE_ENV !== "production"`) it is also
|
|
179
|
+
* reported via `console.warn`. Synchronous throws from effect handlers and
|
|
180
|
+
* middleware still propagate to the caller of `send()` / `reset()`.
|
|
176
181
|
*/
|
|
177
182
|
type RuntimeErrorEvent<Evt> = Readonly<{
|
|
178
183
|
error: unknown;
|
|
@@ -189,6 +194,24 @@ interface Runtime<Ctx, Evt extends {
|
|
|
189
194
|
getSnapshot(): Snapshot<Ctx, States>;
|
|
190
195
|
/** Alias for `getSnapshot()`. */
|
|
191
196
|
snapshot(): Snapshot<Ctx, States>;
|
|
197
|
+
/**
|
|
198
|
+
* Process `event`: `step()` -> sub-machine lifecycle -> commit ->
|
|
199
|
+
* middleware -> effects -> `subscribe` listeners -> `'transition'`
|
|
200
|
+
* listeners, then return the committed snapshot.
|
|
201
|
+
*
|
|
202
|
+
* Run-to-completion: a `send()`/`reset()` made while this runtime is already
|
|
203
|
+
* processing an event (from middleware, an effect handler, a listener, or a
|
|
204
|
+
* child runtime's listener) is queued FIFO and processed after the current
|
|
205
|
+
* event's last notification, with the same full sequence. Such a nested
|
|
206
|
+
* call returns the snapshot committed at the time of the call, not the
|
|
207
|
+
* outcome of its own event — read `getSnapshot()` after the outermost call
|
|
208
|
+
* returns (or subscribe). A throw from any queued event discards the rest
|
|
209
|
+
* of the queue and propagates from the outermost call.
|
|
210
|
+
*
|
|
211
|
+
* Throws `RuntimeDisposedError` after `dispose()`, and
|
|
212
|
+
* `InvalidDefinitionError` when `event` is not an object with a string
|
|
213
|
+
* `type`.
|
|
214
|
+
*/
|
|
192
215
|
send(event: Evt): Snapshot<Ctx, States>;
|
|
193
216
|
/**
|
|
194
217
|
* Predict whether sending `event` would fire a transition. Reuses
|
|
@@ -196,11 +219,23 @@ interface Runtime<Ctx, Evt extends {
|
|
|
196
219
|
* are expected to be pure; `can` then matches `send` for the same input.
|
|
197
220
|
*/
|
|
198
221
|
can(event: Evt): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Call `listener` with the committed snapshot after every event that fired
|
|
224
|
+
* a transition (`changed === true`), after middleware and effects and before
|
|
225
|
+
* `'transition'` listeners. A listener removed during a notification round
|
|
226
|
+
* is skipped for the rest of it; one added waits for the next event.
|
|
227
|
+
* Throws `InvalidDefinitionError` if `listener` is not a function. Returns
|
|
228
|
+
* an unsubscribe function (a no-op after `dispose()`).
|
|
229
|
+
*/
|
|
199
230
|
subscribe(listener: (snap: Snapshot<Ctx, States>) => void): () => void;
|
|
200
231
|
/**
|
|
201
232
|
* EventTarget-like typed listener API. Returns an unsubscribe function.
|
|
202
233
|
* `options.signal` removes the listener when aborted; `options.once`
|
|
203
|
-
* removes the listener
|
|
234
|
+
* removes the listener before its first invocation. A listener removed
|
|
235
|
+
* while an event is being dispatched (by its unsubscribe, `once`, its
|
|
236
|
+
* signal, or `dispose()`) is skipped for the rest of that dispatch; one
|
|
237
|
+
* added waits for the next event. Throws `InvalidDefinitionError` for an
|
|
238
|
+
* unknown event `type` or a non-function `listener`. After `dispose()`,
|
|
204
239
|
* `on()` is a no-op and returns a no-op unsubscribe.
|
|
205
240
|
*/
|
|
206
241
|
on<K extends keyof RuntimeEventMap<Ctx, Evt, States>>(type: K, listener: (payload: RuntimeEventMap<Ctx, Evt, States>[K]) => void, options?: {
|
|
@@ -208,17 +243,25 @@ interface Runtime<Ctx, Evt extends {
|
|
|
208
243
|
once?: boolean;
|
|
209
244
|
}): () => void;
|
|
210
245
|
/**
|
|
211
|
-
* Re-initialise the runtime to the definition's initial snapshot.
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
246
|
+
* Re-initialise the runtime to the definition's initial snapshot. Does NOT
|
|
247
|
+
* run entry actions (reset = re-birth, not "transition into initial"); the
|
|
248
|
+
* current sub-machine child is always replaced. Notifies subscribers,
|
|
249
|
+
* middleware (`changed: true`) and `'transition'` listeners whenever the
|
|
250
|
+
* value, status, or context reference differs from the initial snapshot.
|
|
251
|
+
* Throws RuntimeDisposedError if disposed. If an `event` is supplied (an
|
|
252
|
+
* object with a string `type`, else `InvalidDefinitionError`), middleware
|
|
253
|
+
* sees it as the trigger; otherwise a sentinel
|
|
254
|
+
* `{ type: "@@aifsmjs/RESET" }` is synthesised. Run-to-completion like
|
|
255
|
+
* `send()`: a nested call is queued.
|
|
216
256
|
*/
|
|
217
257
|
reset(event?: Evt): Snapshot<Ctx, States>;
|
|
218
258
|
/**
|
|
219
259
|
* Tear down: abort the internal AbortController (effect handlers see signal
|
|
220
260
|
* fire), clear listeners, and mark this runtime as disposed. Subsequent
|
|
221
|
-
* send()/reset() calls throw RuntimeDisposedError. Idempotent
|
|
261
|
+
* send()/reset() calls throw RuntimeDisposedError. Idempotent and never
|
|
262
|
+
* throws. Never queued: called during a dispatch it runs at once, drops any
|
|
263
|
+
* queued send()/reset() calls, and the outer call returns the last
|
|
264
|
+
* committed snapshot.
|
|
222
265
|
*/
|
|
223
266
|
dispose(): void;
|
|
224
267
|
/**
|
|
@@ -237,11 +280,16 @@ interface Runtime<Ctx, Evt extends {
|
|
|
237
280
|
* Returns the currently active sub-Runtime for the current parent state,
|
|
238
281
|
* or undefined if:
|
|
239
282
|
* - the current state has no `sub` definition, OR
|
|
240
|
-
* - the
|
|
241
|
-
*
|
|
242
|
-
* OR
|
|
283
|
+
* - the previous child's `dispose()` threw during a transition
|
|
284
|
+
* (SubMachineError phase "dispose"; the parent stays in its state and
|
|
285
|
+
* the child is recreated when the state is re-entered), OR
|
|
243
286
|
* - the parent runtime has been disposed.
|
|
244
287
|
*
|
|
288
|
+
* When a transition's new child fails to initialise (SubMachineError phase
|
|
289
|
+
* "init"), the previous child is left untouched and is still returned. A
|
|
290
|
+
* child that fails to initialise at `createRuntime` bootstrap makes
|
|
291
|
+
* `createRuntime` itself throw, so there is no runtime to ask.
|
|
292
|
+
*
|
|
245
293
|
* The returned Runtime is typed at the loosest sub-machine signature.
|
|
246
294
|
* Caller casts to the concrete sub type.
|
|
247
295
|
*
|
package/llms-full.txt
CHANGED
|
@@ -15,7 +15,7 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
|
|
|
15
15
|
|
|
16
16
|
Small deterministic FSM library for replayable TypeScript/JavaScript state machines. Definitions are plain data; guards/actions/effects are injected at runtime.
|
|
17
17
|
|
|
18
|
-
> **Status: 0.
|
|
18
|
+
> **Status: 0.6.0 - stable 1.0-track core.** Core FSM, guards, effects, inspect, replay, PBT helpers, scheduler, and sub-machines are live.
|
|
19
19
|
|
|
20
20
|
## Install
|
|
21
21
|
|
|
@@ -62,7 +62,7 @@ Prefer `setup<Ctx, Evt>().defineMachine()` for state inference. Use bare `define
|
|
|
62
62
|
| --- | --- |
|
|
63
63
|
| `aifsmjs` | `setup`, `defineMachine`, `createRuntime`, `createMachine`, `step`, `assign`, snapshots, runtime/errors/types. |
|
|
64
64
|
| `aifsmjs/guards` | `and`, `or`, `not`, `stateIn`. Guards must be synchronous. |
|
|
65
|
-
| `aifsmjs/effects` | `
|
|
65
|
+
| `aifsmjs/effects` | `createEnqueuer()` and `runEffects()`. |
|
|
66
66
|
| `aifsmjs/inspect` | Read-only middleware helpers: `logger`, `persist`, `recorder`. |
|
|
67
67
|
| `aifsmjs/replay` | Pure event-log replay. |
|
|
68
68
|
| `aifsmjs/pbt` | fast-check property helpers. |
|
|
@@ -71,18 +71,25 @@ Prefer `setup<Ctx, Evt>().defineMachine()` for state inference. Use bare `define
|
|
|
71
71
|
## Lifecycle Rules
|
|
72
72
|
|
|
73
73
|
- `step(def, snapshot, event, impl)` is pure and returns `{ snapshot, effects, changed }`.
|
|
74
|
-
- `createRuntime()` owns mutable runtime state,
|
|
74
|
+
- `createRuntime()` owns mutable runtime state. Per event it commits, then runs middleware, effects, `subscribe` listeners and `'transition'` listeners, in that order.
|
|
75
|
+
- `send()`/`reset()` are run-to-completion: a call made from middleware, an effect handler or a listener is queued and runs after the current event's notifications, with the same full sequence.
|
|
75
76
|
- Guards and reducers are sync. Thenable guards throw `AsyncGuardError`.
|
|
76
|
-
- Effects are fire-and-forget descriptors. Async rejection is routed to the runtime `"error"` channel.
|
|
77
|
-
- `reset()` rewinds the snapshot and notifies listeners,
|
|
77
|
+
- Effects are fire-and-forget descriptors. Async rejection is routed to the runtime `"error"` channel; with no `"error"` listener it is dropped (with a `console.warn` outside production).
|
|
78
|
+
- `reset()` rewinds to the initial snapshot without running entry actions, and notifies listeners whenever the value, status or context reference changes.
|
|
78
79
|
- `dispose()` is idempotent; post-dispose `send()`/`reset()` throw `RuntimeDisposedError`.
|
|
80
|
+
- Misused arguments to `defineMachine`, `createRuntime` and the runtime methods throw `InvalidDefinitionError`.
|
|
79
81
|
|
|
80
82
|
## Sharp Edges
|
|
81
83
|
|
|
82
|
-
- Middleware
|
|
83
|
-
-
|
|
84
|
+
- Middleware, synchronous effect and subscriber throws happen after snapshot commit. A throw can leave the committed snapshot visible without later notification, and it drops any queued `send()`/`reset()` calls.
|
|
85
|
+
- A nested `send()` returns the snapshot committed at the time of the call, not the outcome of its own event. Read `getSnapshot()` after the outermost call returns, or subscribe.
|
|
86
|
+
- A listener removed while a notification is running (unsubscribe, `once`, `signal`, `dispose()`) is skipped for the rest of that round.
|
|
87
|
+
- Middleware never freezes your event, but it deep-freezes effect descriptors, including any object you passed as a payload.
|
|
88
|
+
- Actions on an object context must return a plain-object patch (or nothing); the merge keeps the context's prototype. Returning `false`, `0` or `""` throws `InvalidActionResultError`. Prefer plain-object contexts.
|
|
89
|
+
- Sub-machine replacement builds the new child first: on init failure the old child stays live; on dispose failure the old child is already torn down and the new one is discarded.
|
|
84
90
|
- `subRuntime()` can return a disposed child handle if external code disposed it; it is recreated only after the parent exits and re-enters the sub state.
|
|
85
91
|
- `setup().defineMachine()` uses `NoInfer` so states infer from `keyof states`; keep regression tests for exact optional property configurations.
|
|
92
|
+
- `after()` throws `RangeError` for `NaN`, `Infinity` or a negative delay, and clamps delays above 2^31-1 ms (about 24.8 days). To mean "never", do not schedule.
|
|
86
93
|
- Do not perform async I/O inside guards or actions. Send events from effects instead.
|
|
87
94
|
|
|
88
95
|
## AI Context
|
|
@@ -105,7 +112,47 @@ MIT
|
|
|
105
112
|
|
|
106
113
|
All notable changes to aifsmjs are summarized here.
|
|
107
114
|
|
|
108
|
-
## [
|
|
115
|
+
## [0.6.0] - 2026-09-29
|
|
116
|
+
|
|
117
|
+
### Breaking
|
|
118
|
+
|
|
119
|
+
- `Runtime.send()` / `Runtime.reset()`: a call made while the runtime is already processing an event (from middleware, an effect handler, a `subscribe` or `'transition'` listener, or a child runtime's listener) is now queued and processed after the current event's last notification (run-to-completion) instead of running inside it, because the README-recommended "send from an effect" pattern delivered notifications in reverse and left subscribers holding a stale snapshot; the nested call returns the snapshot committed at that moment, and an error from a queued event propagates from the outermost call. Migration: read `getSnapshot()` after the outer `send()`/`reset()` returns (or subscribe) instead of using a nested call's return value or reading the snapshot right after it, and catch errors around the outermost call rather than around a nested `send()`.
|
|
120
|
+
- `Runtime.subscribe()` / `Runtime.on()` / `Runtime.onTransition()`: a listener removed while a notification is running (by its unsubscribe function, `once`, its `signal`, or `dispose()`) is now skipped for the rest of that round instead of still receiving the in-flight event, per the ai*js fan-out re-entrancy rule. Migration: if a removed listener must still see the current event, remove it after the call returns (for example `queueMicrotask(off)`), and do not rely on the remaining listeners running after a mid-notification `dispose()`.
|
|
121
|
+
- `mergeContext()` / action results: for an object context (not an array or binary view) a plain-object patch is now merged into a copy that keeps the context's prototype, where a class-instance context used to be replaced by the bare patch and lose its other fields and methods, and a non-nullish primitive result such as `false` or `0` now throws `InvalidActionResultError` from `step()`/`send()` instead of replacing the context. Migration: return a plain-object partial (or `undefined`) from actions on object contexts; to replace the context wholesale, return a new instance or array (non-plain objects still replace it).
|
|
122
|
+
- `Runtime.send()` / `Runtime.reset()` sub-machine lifecycle: when a transition replaces a child, the new child is now constructed before the old child is disposed, so an init failure leaves the old child live and still returned by `subRuntime()` (it used to be disposed first, leaving `undefined`). Migration: code that runs while a new child is being created must not assume the previous sibling child is already disposed, and code that expected `subRuntime()` to be `undefined` after a `SubMachineError` with `phase: "init"` should expect the previous child.
|
|
123
|
+
- `after()` / `createScheduler().after()`: an `ms` that is `NaN`, `±Infinity`, negative or not a number now throws `RangeError`, and a non-function `fn` throws `TypeError`, synchronously and before any timer is set (they used to fire after about 1 ms, or throw later from inside the timer), and a finite delay above 2^31-1 ms is clamped to 2^31-1 instead of firing almost at once. Migration: pass a finite `ms >= 0` and a function; callers using `Infinity` to mean "never" should simply not schedule.
|
|
124
|
+
- `defineMachine()` / `setup().defineMachine()` / `createMachine()` / `createRuntime()` / `Runtime.send()` / `Runtime.reset()` / `Runtime.subscribe()` / `Runtime.on()` / `Runtime.onTransition()`: argument misuse now throws `InvalidDefinitionError` (`aifsmjs: <subject> must be <constraint>`) at the call — a non-object definition, `states`, state or transition entry; a non-object `impl` or options object, or a `middleware` option that is not an array of functions; an event that is not an object with a string `type`; a non-function listener or an unknown `on()` event type — instead of a bare `TypeError` (at the call or at a later `send()`), a listener that threw at every notification, or a silently accepted value. Migration: pass `{}` as `impl` when a machine uses no named implementations, pass object events with a string `type` and function listeners, and catch `InvalidDefinitionError` where you caught `TypeError`.
|
|
125
|
+
|
|
126
|
+
### Changes
|
|
127
|
+
|
|
128
|
+
- Added: `InvalidActionResultError` (root export, `name === "InvalidActionResultError"`, with the offending `actionName`) for an action that returns a non-nullish primitive for an object context.
|
|
129
|
+
- Added: `mergeContext(current, patch, actionName?)` takes an optional action name for that error (default `"<inline>"`).
|
|
130
|
+
- Changed: `InvalidDefinitionError` is also the argument-validation error of the definition/runtime boundary; its non-object `states` message now reads `aifsmjs: definition states must be an object`.
|
|
131
|
+
- Changed: an async effect rejection with no `'error'` listener (none registered, or cleared by `dispose()`) is still discarded, but is now reported via `console.warn` when `NODE_ENV !== "production"`; production behaviour is unchanged and it never becomes an unhandled rejection.
|
|
132
|
+
- Changed: `aifsmjs/pbt`'s `properties` is a frozen object carrying the same eight functions instead of a module namespace object, which drops tsup's shared `__export` helper chunk (about 210 B gzip) from every subpath entry.
|
|
133
|
+
- Changed: `step()` returns a shared frozen empty `effects` array when nothing fires.
|
|
134
|
+
- Changed: size budgets in `scripts/check-size.mjs` (maintainer-approved for 0.6.0): `dist/index.js` 6,500 -> 6,700 B and `dist/pbt/index.js` 8,500 -> 8,800 B, other budgets unchanged; measured gzip closures 0.5.9 -> 0.6.0: index 6,359 -> 6,654, guards 1,375 -> 1,161, effects 1,574 -> 1,365, inspect 552 -> 329, replay 3,115 -> 3,139, pbt 8,471 -> 8,718, timer 1,071 -> 1,018 B.
|
|
135
|
+
- Fixed: transition/implementation lookups (`state.on[event.type]`, guard/action/effect refs) now resolve by own key only, so an undeclared event type or ref named after an `Object.prototype` member (`toString`, `constructor`, `__proto__`, ...) is no longer treated as a declared transition.
|
|
136
|
+
- Fixed: `deepFreeze` no longer throws on binary data (`ArrayBuffer` views, e.g. `Uint8Array`) reached through context or event payloads, in dev snapshots or via middleware in production.
|
|
137
|
+
- Fixed: `deepFreeze` recurses through an object that is already shallow-frozen (e.g. an effect descriptor), instead of stopping there — middleware can no longer mutate an effect payload before dispatch, and an already shallow-frozen dev context is still deep-frozen.
|
|
138
|
+
- Fixed: runtime event listeners are isolated per-listener — a throwing `'dispose'` or `'error'` listener no longer prevents later listeners for the same event from running.
|
|
139
|
+
- Fixed: `assignDoesNotMutate` detects mutation by a structural fingerprint instead of `structuredClone`, so it no longer false-fails for a pure machine whose context holds a class instance or a callback.
|
|
140
|
+
- Fixed: `setup().defineMachine()` infers `States` from `keyof states` only, so a terminal state written as `{}` or `{ final: true }` no longer collapses the inferred state union.
|
|
141
|
+
- Fixed: an explicit `context: undefined` passed to `defineMachine` / `setup().defineMachine` now defaults to `{}`, the same as an absent `context` key.
|
|
142
|
+
- Fixed: dev-mode detection reads `process.env.NODE_ENV` directly, so Vite / webpack 5 define-replacement enables dev-only deep-freezing in browser builds that have no `process` global.
|
|
143
|
+
- Fixed: the PBT `snapshotAlwaysFrozen` and `reachableStatesSubsetDeclared` properties no longer dispatch real effects while driving generated commands through a runtime.
|
|
144
|
+
- Fixed: `createScheduler().after()` merges `signal`/`setTimeout`/`clearTimeout` field-by-field with `??` instead of an object spread, so an explicitly-undefined per-call option no longer silently overrides the scheduler's default.
|
|
145
|
+
- Fixed: `MachineConfig`, the parameter type of `defineMachine`, is re-exported from the package root.
|
|
146
|
+
- Fixed: `reset()` now notifies subscribers, middleware (`changed: true`) and `'transition'` listeners when the context reference (or status) differs from the initial snapshot; it used to compare the state value alone and stay silent.
|
|
147
|
+
- Fixed: middleware no longer deep-freezes the caller's event object (and its payload graph) in any `NODE_ENV`; `MiddlewareContext.event` is the caller's object, passed unfrozen.
|
|
148
|
+
- Fixed: a parent `send()`/`reset()` issued from a child's `'dispose'` listener during a transition is queued until the transition commits, so a live child can no longer be left in a state that has no `sub`.
|
|
149
|
+
- Fixed: a parent disposed by a child's `'dispose'` listener during a transition no longer adopts the replacement child; the replacement is disposed and `subRuntime()` returns `undefined`.
|
|
150
|
+
- Fixed: `send()` decides whether a same-value transition is external from the guard pass that produced the snapshot, so each guard runs once per event and a non-idempotent guard can no longer desync the sub-machine lifecycle from the committed state.
|
|
151
|
+
- Fixed: `defineMachine()` rejects a sub-machine cycle through initial states with `InvalidDefinitionError` instead of letting `createRuntime()` recurse until the stack overflows; self-references through non-initial states stay legal.
|
|
152
|
+
- Fixed: `snapshotAlwaysFrozen`, `reachableStatesSubsetDeclared` and `replayEqualsFold` dispose the runtime they create for each generated run, so its `AbortSignal` fires and no run leaks a live runtime.
|
|
153
|
+
- Fixed: `package.json` `exports` nests `types` under `import` and `require` (`require.types` points at the `.d.cts` files) for every subpath, so `node16` / `nodenext` CommonJS consumers no longer hit TS1479 / TS1471; `verify-exports` walks nested conditions.
|
|
154
|
+
- Docs: corrected the `aifsmjs/effects` Public Surface row (README/README_ZHTW) to name the real export, `createEnqueuer()`, instead of `enqueue.effect()`.
|
|
155
|
+
- Docs: STABILITY.md's Behavioral Contract states the run-to-completion and fan-out clauses, the reset, merge, argument-validation and timer rules, and the new sub-machine order; README and README_ZHTW Lifecycle Rules and Sharp Edges mirror them, and the `Runtime` / `MiddlewareContext` JSDoc says the same.
|
|
109
156
|
|
|
110
157
|
## [0.5.9] - 2026-06-29
|
|
111
158
|
|
|
@@ -142,21 +189,27 @@ All notable changes to aifsmjs are summarized here.
|
|
|
142
189
|
|
|
143
190
|
| Surface | Status | Notes |
|
|
144
191
|
| --- | --- | --- |
|
|
145
|
-
| `aifsmjs` root | Stable | Definition/runtime/step/snapshot APIs and core errors
|
|
192
|
+
| `aifsmjs` root | Stable | Definition/runtime/step/snapshot APIs and core errors: `InvalidDefinitionError`, `InvalidActionResultError`, `UnknownActionError`, `UnknownGuardError`, `AsyncGuardError`, `RuntimeDisposedError`, `SubMachineError`. |
|
|
146
193
|
| `aifsmjs/guards` | Stable | Sync guard combinators. |
|
|
147
194
|
| `aifsmjs/effects` | Stable | Effect descriptors and dispatcher helper. |
|
|
148
195
|
| `aifsmjs/inspect` | Stable | Read-only middleware helpers. |
|
|
149
196
|
| `aifsmjs/replay` | Stable | Pure log replay. |
|
|
150
197
|
| `aifsmjs/pbt` | Stable | fast-check helpers. |
|
|
151
|
-
| `aifsmjs/timer` | Stable | Timer/scheduler helpers. |
|
|
198
|
+
| `aifsmjs/timer` | Stable | Timer/scheduler helpers. Exports no error class: misuse throws a built-in `RangeError`/`TypeError` whose message starts with `aifsmjs: `. |
|
|
152
199
|
|
|
153
200
|
## Behavioral Contract
|
|
154
201
|
|
|
155
202
|
- Definition data is serializable when using string refs instead of inline functions.
|
|
156
|
-
- `step()` is pure and never dispatches effects.
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
- `
|
|
203
|
+
- `step()` is pure and never dispatches effects. Each guard on the path to the chosen transition runs at most once per event, and `send()` decides the sub-machine lifecycle from that same guard pass.
|
|
204
|
+
- `send()`/`reset()` are run-to-completion: for one event, commit -> middleware -> effects -> `subscribe` listeners -> `'transition'` listeners all complete before any event sent from inside them is processed; nested `send()`/`reset()` calls are queued FIFO and processed afterwards with the same full sequence; a nested call returns the snapshot committed at the time of the call, not the outcome of its own event — read `getSnapshot()` after the outermost call returns. A listener that sends on every notification of a machine that always transitions keeps the drain running.
|
|
205
|
+
- A throw from any event in that sequence (a `SubMachineError`, an unknown action or guard, an `InvalidActionResultError`, or a synchronous middleware, effect-handler or listener throw) drops the calls still queued and propagates from the outermost `send()`/`reset()`; the snapshot stays at the last successful commit. `dispose()` is never queued: it runs at once, drops queued calls, and the outer call returns the last committed snapshot without throwing (the event in progress finishes with cleared listeners and an aborted signal). Parent and child runtimes queue independently.
|
|
206
|
+
- Listener fan-out (`subscribe`, `on`, `onTransition`) is synchronous over a copy of the listener set taken when the notification starts: a listener added meanwhile first fires on the next event, and one removed meanwhile (its unsubscribe function, `once`, its `signal`, or `dispose()`) is skipped for the rest of that round. A `once` listener is removed before it is called. A throwing `on()` listener does not stop the others; the first error is rethrown once all have run.
|
|
207
|
+
- Middleware receives the caller's event object by reference and never freezes it (treat it as read-only). The middleware context object is frozen, `prev`/`next` are frozen to the depth below, and the effect descriptors are deep-frozen, payloads included.
|
|
208
|
+
- Async effects are fire-and-forget; rejections emit runtime `"error"`. A rejection with no `'error'` listener is discarded in production and reported via `console.warn` when `NODE_ENV !== "production"`; it never becomes an unhandled rejection.
|
|
209
|
+
- `reset()` does not run entry actions and always replaces the current sub-machine child. `reset()` notifies subscribers, middleware (`changed: true`) and `'transition'` listeners whenever the value, status, or context reference differs from the initial snapshot.
|
|
210
|
+
- An action result is merged into an object context (not an array or binary view) by a shallow copy that keeps the context's prototype; only own enumerable string and symbol properties are carried, not `#private` or non-enumerable members, so prefer plain-object contexts. A non-nullish primitive result (`false`, `0`, `""`, ...) for an object context throws `InvalidActionResultError`; any other non-plain-object result (an array, a class instance) replaces the context, as does any result for a primitive or array context.
|
|
211
|
+
- Argument misuse at the definition/runtime boundary (`defineMachine`, `setup().defineMachine`, `createMachine`, `createRuntime`, `send`, `reset`, `subscribe`, `on`, `onTransition`) throws `InvalidDefinitionError` (`aifsmjs: <subject> must be <constraint>`) before anything is created or registered. Pure helpers (`step`, `replay`, `mergeContext`, guard combinators, `runEffects`, inspect middleware, PBT properties) trust their typed arguments.
|
|
212
|
+
- `after()` / `createScheduler().after()`: `ms` must be a finite number >= 0 (`RangeError`) and `fn` a function (`TypeError`), checked before any timer or listener exists; a finite delay above 2^31-1 ms (about 24.8 days) is clamped to 2^31-1 when handed to `setTimeout`.
|
|
160
213
|
- `dispose()` aborts runtime signal, clears listeners, and is idempotent. A throwing `'dispose'` listener is swallowed and never aborts teardown.
|
|
161
214
|
|
|
162
215
|
## Replay caveat
|
|
@@ -175,8 +228,9 @@ Snapshot freezing is depth-dependent on `NODE_ENV`:
|
|
|
175
228
|
Sub-machines are stable but sharp:
|
|
176
229
|
|
|
177
230
|
- Entry lazily creates the child; exit disposes it.
|
|
178
|
-
-
|
|
179
|
-
-
|
|
231
|
+
- Entry constructs the new child before the old child is disposed; init failure leaves the old child live and the parent unchanged; dispose failure surfaces after the old child is torn down and the new child is discarded.
|
|
232
|
+
- A `send()`/`reset()` on the parent from a child's listener during the parent's transition is queued until that transition has committed.
|
|
233
|
+
- Sub definitions may reference themselves or each other only through non-initial states; `defineMachine` rejects a cycle through initial-state subs, which the runtime would otherwise boot without end.
|
|
180
234
|
- External child disposal leaves a stale handle until the parent leaves/re-enters the state.
|
|
181
235
|
|
|
182
236
|
## Drafts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aifsmjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Small, strict FSM library for deterministic, replayable state machines in any TypeScript/JS app — multi-step forms, checkout funnels, auth flows, tutorials, scene flow. Pure step() lifecycle, opt-in effects, inspect, replay, and a fast-check property-based testing adapter. Browser / Node / Bun / Deno / WebView / Worker friendly.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fsm",
|
|
@@ -35,39 +35,74 @@
|
|
|
35
35
|
"types": "./dist/index.d.ts",
|
|
36
36
|
"exports": {
|
|
37
37
|
".": {
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
"import": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"default": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"require": {
|
|
43
|
+
"types": "./dist/index.d.cts",
|
|
44
|
+
"default": "./dist/index.cjs"
|
|
45
|
+
}
|
|
41
46
|
},
|
|
42
47
|
"./guards": {
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
"import": {
|
|
49
|
+
"types": "./dist/guards/index.d.ts",
|
|
50
|
+
"default": "./dist/guards/index.js"
|
|
51
|
+
},
|
|
52
|
+
"require": {
|
|
53
|
+
"types": "./dist/guards/index.d.cts",
|
|
54
|
+
"default": "./dist/guards/index.cjs"
|
|
55
|
+
}
|
|
46
56
|
},
|
|
47
57
|
"./effects": {
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
"import": {
|
|
59
|
+
"types": "./dist/effects/index.d.ts",
|
|
60
|
+
"default": "./dist/effects/index.js"
|
|
61
|
+
},
|
|
62
|
+
"require": {
|
|
63
|
+
"types": "./dist/effects/index.d.cts",
|
|
64
|
+
"default": "./dist/effects/index.cjs"
|
|
65
|
+
}
|
|
51
66
|
},
|
|
52
67
|
"./inspect": {
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
"import": {
|
|
69
|
+
"types": "./dist/inspect/index.d.ts",
|
|
70
|
+
"default": "./dist/inspect/index.js"
|
|
71
|
+
},
|
|
72
|
+
"require": {
|
|
73
|
+
"types": "./dist/inspect/index.d.cts",
|
|
74
|
+
"default": "./dist/inspect/index.cjs"
|
|
75
|
+
}
|
|
56
76
|
},
|
|
57
77
|
"./replay": {
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
"import": {
|
|
79
|
+
"types": "./dist/replay/index.d.ts",
|
|
80
|
+
"default": "./dist/replay/index.js"
|
|
81
|
+
},
|
|
82
|
+
"require": {
|
|
83
|
+
"types": "./dist/replay/index.d.cts",
|
|
84
|
+
"default": "./dist/replay/index.cjs"
|
|
85
|
+
}
|
|
61
86
|
},
|
|
62
87
|
"./pbt": {
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
88
|
+
"import": {
|
|
89
|
+
"types": "./dist/pbt/index.d.ts",
|
|
90
|
+
"default": "./dist/pbt/index.js"
|
|
91
|
+
},
|
|
92
|
+
"require": {
|
|
93
|
+
"types": "./dist/pbt/index.d.cts",
|
|
94
|
+
"default": "./dist/pbt/index.cjs"
|
|
95
|
+
}
|
|
66
96
|
},
|
|
67
97
|
"./timer": {
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
"import": {
|
|
99
|
+
"types": "./dist/timer/index.d.ts",
|
|
100
|
+
"default": "./dist/timer/index.js"
|
|
101
|
+
},
|
|
102
|
+
"require": {
|
|
103
|
+
"types": "./dist/timer/index.d.cts",
|
|
104
|
+
"default": "./dist/timer/index.cjs"
|
|
105
|
+
}
|
|
71
106
|
}
|
|
72
107
|
},
|
|
73
108
|
"files": [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fsm/evaluator.ts"],"names":[],"mappings":";AAEO,IAAM,iBAAA,GAAN,cAAgC,KAAA,CAAM;AAAA,EAClC,SAAA;AAAA,EACT,YAAY,SAAA,EAAmB;AAC7B,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,qCAAA,CAAuC,CAAA;AACzE,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAEO,IAAM,eAAA,GAAN,cAA8B,KAAA,CAAM;AAAA,EAChC,SAAA;AAAA,EACT,YAAY,SAAA,EAAmB;AAC7B,IAAA,KAAA;AAAA,MACE,mBAAmB,SAAS,CAAA,uGAAA;AAAA,KAC9B;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAcO,SAAS,eAAe,EAAA,EAAsB;AACnD,EAAA,IAAI,OAAO,EAAA,KAAO,UAAA,EAAY,OAAO,KAAA;AACrC,EAAA,OAAO,EAAA,CAAG,aAAa,IAAA,KAAS,eAAA;AAClC;AAYO,SAAS,WAAW,CAAA,EAAuC;AAChE,EAAA,OACE,CAAA,KAAM,IAAA,KACL,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,CAAA,KAAM,UAAA,CAAA,IACvC,OAAQ,CAAA,CAAyB,IAAA,KAAS,UAAA;AAE9C;AAMO,SAAS,YAAA,CACd,KACA,IAAA,EACiB;AACjB,EAAA,IAAI,OAAO,GAAA,KAAQ,UAAA,EAAY,OAAO,GAAA;AACtC,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,MAAA,GAAS,GAAG,CAAA;AAC5B,EAAA,IAAI,CAAC,EAAA,EAAI,MAAM,IAAI,kBAAkB,GAAG,CAAA;AACxC,EAAA,OAAO,EAAA;AACT;AAcO,SAAS,SAAA,CACd,GAAA,EACA,OAAA,EACA,KAAA,EACA,MACA,KAAA,EACS;AACT,EAAA,MAAM,EAAA,GAAK,YAAA,CAAa,GAAA,EAAK,IAAI,CAAA;AAGjC,EAAA,MAAM,YAAY,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM,GAAG,IAAA,IAAQ,UAAA;AAC7D,EAAA,IAAI,cAAA,CAAe,EAAE,CAAA,EAAG;AACtB,IAAA,MAAM,IAAI,gBAAgB,SAAS,CAAA;AAAA,EACrC;AASA,EAAA,MAAM,IAAA,GAAa,EAAE,OAAA,EAAS,KAAA,EAAM;AACpC,EAAA,MAAM,YAAY,IAAA,CAAK,MAAA;AACvB,EAAA,IAAI,SAAA,OAAgB,MAAA,GAAS,SAAA;AAC7B,EAAA,IAAI,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,KAAA,GAAQ,KAAA;AACtC,EAAA,MAAM,MAAA,GAAS,GAAG,IAAI,CAAA;AAKtB,EAAA,IAAI,UAAA,CAAW,MAAM,CAAA,EAAG;AACtB,IAAA,MAAM,IAAI,gBAAgB,SAAS,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,MAAA;AACT","file":"chunk-A7U7QQL5.js","sourcesContent":["import type { Guard, GuardRef, Implementations } from \"./types.js\";\n\nexport class UnknownGuardError extends Error {\n readonly guardName: string;\n constructor(guardName: string) {\n super(`aifsmjs: guard \"${guardName}\" not found in implementations.guards`);\n this.name = \"UnknownGuardError\";\n this.guardName = guardName;\n }\n}\n\nexport class AsyncGuardError extends Error {\n readonly guardName: string;\n constructor(guardName: string) {\n super(\n `aifsmjs: guard \"${guardName}\" must be sync; received a Promise. Async guards break determinism and replay. Move I/O into an effect.`,\n );\n this.name = \"AsyncGuardError\";\n this.guardName = guardName;\n }\n}\n\n/**\n * Detect declared-async guards at definition time. Catches the common case of\n * `async (args) => ...` inline guards. Combinator builders or arrow returns of\n * a Promise still slip past — those are caught at `evalGuard` runtime via\n * the `isThenable` check.\n *\n * Caveat: this relies on `Function.prototype.constructor.name === \"AsyncFunction\"`,\n * which is reliable in ES2017+ runtimes. If your bundler transpiles `async`\n * to generator-based code (e.g. ES5 / very old TypeScript targets), this\n * check returns `false` for those forms — the runtime `evalGuard` thenable\n * check still catches them.\n */\nexport function isAsyncGuardFn(fn: unknown): boolean {\n if (typeof fn !== \"function\") return false;\n return fn.constructor?.name === \"AsyncFunction\";\n}\n\n/**\n * Detect a thenable (PromiseLike) — anything with a callable `then`. Used in\n * place of `instanceof Promise` so cross-realm Promises (iframe / worker /\n * vm context) and user-defined thenables are also rejected.\n *\n * Exported for the guard combinators (`and`/`or`/`not`), which must apply the\n * same async-guard rejection to the values their inner guards return —\n * otherwise a nested thenable is coerced truthy inside the combinator and the\n * top-level `evalGuard` check never sees it (FSM-S-01).\n */\nexport function isThenable(x: unknown): x is PromiseLike<unknown> {\n return (\n x !== null &&\n (typeof x === \"object\" || typeof x === \"function\") &&\n typeof (x as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * Resolve a guard ref to a Guard function. String refs are looked up in the\n * implementations map; inline functions are returned as-is.\n */\nexport function resolveGuard<Ctx, Evt>(\n ref: GuardRef<Ctx, Evt>,\n impl: Implementations<Ctx, Evt>,\n): Guard<Ctx, Evt> {\n if (typeof ref === \"function\") return ref;\n const fn = impl.guards?.[ref];\n if (!fn) throw new UnknownGuardError(ref);\n return fn;\n}\n\n/**\n * Evaluate a guard ref against (context, event). Guards must be sync and pure;\n * TypeScript blocks declared-async signatures at compile time, but JS callers\n * or casts can still slip through. This function checks two ways:\n * 1. Inline AsyncFunction (declared `async`) → throw AsyncGuardError.\n * 2. Return value is a Promise → throw AsyncGuardError.\n * Both throws are user errors; they would otherwise silently pass the guard\n * (Promise is truthy) and break determinism.\n *\n * The optional `value` argument is the current state value, threaded so the\n * `stateIn` combinator and similar predicates can introspect it.\n */\nexport function evalGuard<Ctx, Evt>(\n ref: GuardRef<Ctx, Evt>,\n context: Ctx,\n event: Evt,\n impl: Implementations<Ctx, Evt>,\n value?: string,\n): boolean {\n const fn = resolveGuard(ref, impl);\n // Function.prototype.name is \"\" for anonymous arrows — use `||` not `??`\n // so the empty string falls back to \"<inline>\" for readable error messages.\n const guardName = typeof ref === \"string\" ? ref : fn.name || \"<inline>\";\n if (isAsyncGuardFn(fn)) {\n throw new AsyncGuardError(guardName);\n }\n // Build args while honouring exactOptionalPropertyTypes: omit fields that\n // would otherwise be assigned `undefined`.\n type Args = {\n context: Ctx;\n event: Evt;\n guards?: Readonly<Record<string, Guard<Ctx, Evt>>>;\n value?: string;\n };\n const args: Args = { context, event };\n const guardsMap = impl.guards;\n if (guardsMap) args.guards = guardsMap;\n if (value !== undefined) args.value = value;\n const result = fn(args);\n // TS narrows `result` to boolean from Guard's return type, but a JS caller\n // or a cast can slip a Promise / PromiseLike through. We accept anything\n // thenable (native Promise, cross-realm Promise, user-defined thenable),\n // not just same-realm `instanceof Promise`.\n if (isThenable(result)) {\n throw new AsyncGuardError(guardName);\n }\n return result;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fsm/evaluator.ts"],"names":[],"mappings":";;;AAEO,IAAM,iBAAA,GAAN,cAAgC,KAAA,CAAM;AAAA,EAClC,SAAA;AAAA,EACT,YAAY,SAAA,EAAmB;AAC7B,IAAA,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,qCAAA,CAAuC,CAAA;AACzE,IAAA,IAAA,CAAK,IAAA,GAAO,mBAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAEO,IAAM,eAAA,GAAN,cAA8B,KAAA,CAAM;AAAA,EAChC,SAAA;AAAA,EACT,YAAY,SAAA,EAAmB;AAC7B,IAAA,KAAA;AAAA,MACE,mBAAmB,SAAS,CAAA,uGAAA;AAAA,KAC9B;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,iBAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAcO,SAAS,eAAe,EAAA,EAAsB;AACnD,EAAA,IAAI,OAAO,EAAA,KAAO,UAAA,EAAY,OAAO,KAAA;AACrC,EAAA,OAAO,EAAA,CAAG,aAAa,IAAA,KAAS,eAAA;AAClC;AAYO,SAAS,WAAW,CAAA,EAAuC;AAChE,EAAA,OACE,CAAA,KAAM,IAAA,KACL,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,CAAA,KAAM,UAAA,CAAA,IACvC,OAAQ,CAAA,CAAyB,IAAA,KAAS,UAAA;AAE9C;AAMO,SAAS,YAAA,CACd,KACA,IAAA,EACiB;AACjB,EAAA,IAAI,OAAO,GAAA,KAAQ,UAAA,EAAY,OAAO,GAAA;AACtC,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,MAAA,GAAS,GAAG,CAAA;AAC5B,EAAA,IAAI,CAAC,EAAA,EAAI,MAAM,IAAI,kBAAkB,GAAG,CAAA;AACxC,EAAA,OAAO,EAAA;AACT;AAcO,SAAS,SAAA,CACd,GAAA,EACA,OAAA,EACA,KAAA,EACA,MACA,KAAA,EACS;AACT,EAAA,MAAM,EAAA,GAAK,YAAA,CAAa,GAAA,EAAK,IAAI,CAAA;AAGjC,EAAA,MAAM,YAAY,OAAO,GAAA,KAAQ,QAAA,GAAW,GAAA,GAAM,GAAG,IAAA,IAAQ,UAAA;AAC7D,EAAA,IAAI,cAAA,CAAe,EAAE,CAAA,EAAG;AACtB,IAAA,MAAM,IAAI,gBAAgB,SAAS,CAAA;AAAA,EACrC;AASA,EAAA,MAAM,IAAA,GAAa,EAAE,OAAA,EAAS,KAAA,EAAM;AACpC,EAAA,MAAM,YAAY,IAAA,CAAK,MAAA;AACvB,EAAA,IAAI,SAAA,OAAgB,MAAA,GAAS,SAAA;AAC7B,EAAA,IAAI,KAAA,KAAU,MAAA,EAAW,IAAA,CAAK,KAAA,GAAQ,KAAA;AACtC,EAAA,MAAM,MAAA,GAAS,GAAG,IAAI,CAAA;AAKtB,EAAA,IAAI,UAAA,CAAW,MAAM,CAAA,EAAG;AACtB,IAAA,MAAM,IAAI,gBAAgB,SAAS,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,MAAA;AACT","file":"chunk-CDK25FTD.cjs","sourcesContent":["import type { Guard, GuardRef, Implementations } from \"./types.js\";\n\nexport class UnknownGuardError extends Error {\n readonly guardName: string;\n constructor(guardName: string) {\n super(`aifsmjs: guard \"${guardName}\" not found in implementations.guards`);\n this.name = \"UnknownGuardError\";\n this.guardName = guardName;\n }\n}\n\nexport class AsyncGuardError extends Error {\n readonly guardName: string;\n constructor(guardName: string) {\n super(\n `aifsmjs: guard \"${guardName}\" must be sync; received a Promise. Async guards break determinism and replay. Move I/O into an effect.`,\n );\n this.name = \"AsyncGuardError\";\n this.guardName = guardName;\n }\n}\n\n/**\n * Detect declared-async guards at definition time. Catches the common case of\n * `async (args) => ...` inline guards. Combinator builders or arrow returns of\n * a Promise still slip past — those are caught at `evalGuard` runtime via\n * the `isThenable` check.\n *\n * Caveat: this relies on `Function.prototype.constructor.name === \"AsyncFunction\"`,\n * which is reliable in ES2017+ runtimes. If your bundler transpiles `async`\n * to generator-based code (e.g. ES5 / very old TypeScript targets), this\n * check returns `false` for those forms — the runtime `evalGuard` thenable\n * check still catches them.\n */\nexport function isAsyncGuardFn(fn: unknown): boolean {\n if (typeof fn !== \"function\") return false;\n return fn.constructor?.name === \"AsyncFunction\";\n}\n\n/**\n * Detect a thenable (PromiseLike) — anything with a callable `then`. Used in\n * place of `instanceof Promise` so cross-realm Promises (iframe / worker /\n * vm context) and user-defined thenables are also rejected.\n *\n * Exported for the guard combinators (`and`/`or`/`not`), which must apply the\n * same async-guard rejection to the values their inner guards return —\n * otherwise a nested thenable is coerced truthy inside the combinator and the\n * top-level `evalGuard` check never sees it (FSM-S-01).\n */\nexport function isThenable(x: unknown): x is PromiseLike<unknown> {\n return (\n x !== null &&\n (typeof x === \"object\" || typeof x === \"function\") &&\n typeof (x as { then?: unknown }).then === \"function\"\n );\n}\n\n/**\n * Resolve a guard ref to a Guard function. String refs are looked up in the\n * implementations map; inline functions are returned as-is.\n */\nexport function resolveGuard<Ctx, Evt>(\n ref: GuardRef<Ctx, Evt>,\n impl: Implementations<Ctx, Evt>,\n): Guard<Ctx, Evt> {\n if (typeof ref === \"function\") return ref;\n const fn = impl.guards?.[ref];\n if (!fn) throw new UnknownGuardError(ref);\n return fn;\n}\n\n/**\n * Evaluate a guard ref against (context, event). Guards must be sync and pure;\n * TypeScript blocks declared-async signatures at compile time, but JS callers\n * or casts can still slip through. This function checks two ways:\n * 1. Inline AsyncFunction (declared `async`) → throw AsyncGuardError.\n * 2. Return value is a Promise → throw AsyncGuardError.\n * Both throws are user errors; they would otherwise silently pass the guard\n * (Promise is truthy) and break determinism.\n *\n * The optional `value` argument is the current state value, threaded so the\n * `stateIn` combinator and similar predicates can introspect it.\n */\nexport function evalGuard<Ctx, Evt>(\n ref: GuardRef<Ctx, Evt>,\n context: Ctx,\n event: Evt,\n impl: Implementations<Ctx, Evt>,\n value?: string,\n): boolean {\n const fn = resolveGuard(ref, impl);\n // Function.prototype.name is \"\" for anonymous arrows — use `||` not `??`\n // so the empty string falls back to \"<inline>\" for readable error messages.\n const guardName = typeof ref === \"string\" ? ref : fn.name || \"<inline>\";\n if (isAsyncGuardFn(fn)) {\n throw new AsyncGuardError(guardName);\n }\n // Build args while honouring exactOptionalPropertyTypes: omit fields that\n // would otherwise be assigned `undefined`.\n type Args = {\n context: Ctx;\n event: Evt;\n guards?: Readonly<Record<string, Guard<Ctx, Evt>>>;\n value?: string;\n };\n const args: Args = { context, event };\n const guardsMap = impl.guards;\n if (guardsMap) args.guards = guardsMap;\n if (value !== undefined) args.value = value;\n const result = fn(args);\n // TS narrows `result` to boolean from Guard's return type, but a JS caller\n // or a cast can slip a Promise / PromiseLike through. We accept anything\n // thenable (native Promise, cross-realm Promise, user-defined thenable),\n // not just same-realm `instanceof Promise`.\n if (isThenable(result)) {\n throw new AsyncGuardError(guardName);\n }\n return result;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fsm/resolver.ts","../src/fsm/snapshot.ts","../src/fsm/updater.ts","../src/fsm/lifecycle.ts"],"names":["createEnqueuer","evalGuard"],"mappings":"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","file":"chunk-I354FONA.cjs","sourcesContent":["import type { MachineDef, TransitionConfig, TransitionDef } from \"./types.js\";\n\n/**\n * Normalize a single transition config into its object form. The string\n * shorthand `\"targetState\"` (à la XState) becomes `{ target: \"targetState\" }`;\n * the object form is returned unchanged. Centralised here so every consumer\n * (`step`, `resolveTransitions`, `can`, validation) sees the same shape.\n *\n * @since 0.5.3\n */\nexport function normalizeTransition<Ctx, Evt, States extends string>(\n entry: TransitionConfig<Ctx, Evt, States>,\n): TransitionDef<Ctx, Evt, States> {\n return typeof entry === \"string\" ? ({ target: entry } as TransitionDef<Ctx, Evt, States>) : entry;\n}\n\n/**\n * Normalize the raw `state.on[eventType]` value (object, string shorthand, or\n * an array mixing both) into an ordered list of {@link TransitionDef} objects.\n * Declaration order is preserved.\n *\n * @since 0.5.3\n */\nexport function normalizeTransitions<Ctx, Evt, States extends string>(\n entry:\n | TransitionConfig<Ctx, Evt, States>\n | readonly TransitionConfig<Ctx, Evt, States>[]\n | undefined,\n): readonly TransitionDef<Ctx, Evt, States>[] {\n if (entry === undefined) return [];\n if (Array.isArray(entry)) {\n // Hot path (send/step/can run this per event): when no string shorthand is\n // present, return the original array instead of allocating a normalized copy.\n return entry.some((t) => typeof t === \"string\")\n ? entry.map((t) => normalizeTransition(t))\n : (entry as readonly TransitionDef<Ctx, Evt, States>[]);\n }\n return [normalizeTransition(entry as TransitionConfig<Ctx, Evt, States>)];\n}\n\n/**\n * Return all transition candidates for (state, eventType). Order is preserved\n * from the declaration so that guard fallthrough behaves predictably. String\n * shorthands are normalized to `{ target }` objects.\n *\n * If the event has no entry under the given state, an empty array is returned.\n */\nexport function resolveTransitions<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n stateValue: States,\n eventType: string,\n): readonly TransitionDef<Ctx, Evt, States>[] {\n const state = def.states[stateValue];\n if (!state || !state.on) return [];\n return normalizeTransitions(state.on[eventType]);\n}\n","import type { Snapshot } from \"./types.js\";\n\nconst IS_DEV =\n typeof process !== \"undefined\" &&\n typeof process.env !== \"undefined\" &&\n process.env.NODE_ENV !== \"production\";\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nexport function deepFreeze<T>(value: T): T {\n if (value === null || typeof value !== \"object\") return value;\n if (Object.isFrozen(value)) return value;\n Object.freeze(value);\n if (Array.isArray(value)) {\n for (const item of value) deepFreeze(item);\n } else if (isPlainObject(value)) {\n for (const key of Object.keys(value)) {\n deepFreeze((value as Record<string, unknown>)[key]);\n }\n }\n return value;\n}\n\n/**\n * Wrap a freshly built snapshot. In dev mode the whole tree is deep-frozen so\n * accidental mutation throws immediately. In production only the top object is\n * frozen, keeping the cost negligible.\n */\nexport function freezeSnapshot<C, S extends string>(snap: Snapshot<C, S>): Snapshot<C, S> {\n // IS_DEV is always true in vitest; the production branch (`Object.freeze`)\n // is exercised only when NODE_ENV === \"production\" and is intentionally\n // left out of the coverage threshold.\n return IS_DEV ? deepFreeze(snap) : Object.freeze(snap);\n}\n\nexport function createSnapshot<C, S extends string>(args: {\n value: S;\n context: C;\n status?: \"active\" | \"final\";\n}): Snapshot<C, S> {\n return freezeSnapshot({\n value: args.value,\n context: args.context,\n status: args.status ?? \"active\",\n });\n}\n","import type { Action } from \"./types.js\";\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Build an Action that returns a Partial<Ctx> from a pure updater.\n * The partial is merged into the current context by `step()`.\n */\nexport function assign<Ctx, Evt>(\n updater: (args: { context: Ctx; event: Evt }) => Partial<Ctx>,\n): Action<Ctx, Evt> {\n return ({ context, event }) => updater({ context, event });\n}\n\n/**\n * Merge a partial context update into the current context. Plain-object\n * contexts get a shallow merge; non-object contexts get replaced wholesale.\n *\n * The function never mutates either argument.\n */\nexport function mergeContext<Ctx>(current: Ctx, patch: Partial<Ctx> | void): Ctx {\n if (patch === undefined || patch === null) return current;\n if (isPlainRecord(current) && isPlainRecord(patch)) {\n return { ...current, ...patch } as Ctx;\n }\n return patch as Ctx;\n}\n","import { createEnqueuer } from \"../effects/enqueuer.js\";\nimport { evalGuard } from \"./evaluator.js\";\nimport { normalizeTransitions } from \"./resolver.js\";\nimport { freezeSnapshot } from \"./snapshot.js\";\nimport type {\n Action,\n ActionRef,\n Effect,\n Implementations,\n MachineDef,\n Snapshot,\n StepResult,\n TransitionDef,\n} from \"./types.js\";\nimport { mergeContext } from \"./updater.js\";\n\nexport class UnknownActionError extends Error {\n readonly actionName: string;\n constructor(actionName: string) {\n super(`aifsmjs: action \"${actionName}\" not found in implementations.actions`);\n this.name = \"UnknownActionError\";\n this.actionName = actionName;\n }\n}\n\nfunction resolveAction<Ctx, Evt>(\n ref: ActionRef<Ctx, Evt>,\n impl: Implementations<Ctx, Evt>,\n): Action<Ctx, Evt> {\n if (typeof ref === \"function\") return ref;\n const fn = impl.actions?.[ref];\n if (!fn) throw new UnknownActionError(ref);\n return fn;\n}\n\nfunction runActions<Ctx, Evt>(\n refs: readonly ActionRef<Ctx, Evt>[] | undefined,\n ctx: Ctx,\n event: Evt,\n impl: Implementations<Ctx, Evt>,\n effectSink: Effect[],\n): Ctx {\n if (!refs || refs.length === 0) return ctx;\n const enqueue = createEnqueuer(effectSink as { type: string; payload?: unknown }[]);\n let current = ctx;\n for (const ref of refs) {\n const fn = resolveAction(ref, impl);\n const patch = fn({ context: current, event, enqueue });\n current = mergeContext(current, patch);\n }\n return current;\n}\n\nfunction pickTransition<Ctx, Evt, States extends string>(\n candidates: readonly TransitionDef<Ctx, Evt, States>[],\n ctx: Ctx,\n event: Evt,\n impl: Implementations<Ctx, Evt>,\n value: States,\n): TransitionDef<Ctx, Evt, States> | undefined {\n for (const t of candidates) {\n if (!t.guard) return t;\n if (evalGuard(t.guard, ctx, event, impl, value)) return t;\n }\n return undefined;\n}\n\n/**\n * Compute the next snapshot and collected effects from a single event.\n *\n * Order is fixed and uninterruptible:\n * 1. resolve candidate transitions for (state, event.type)\n * 2. evaluate guards in declaration order; pick the first passing one\n * 3. if external (target defined), run exit actions of the old state\n * 4. run transition.actions in declaration order\n * 5. if external, run entry actions of the new state\n * 6. return { snapshot, effects, changed }\n *\n * The function is pure: it never dispatches effects and never mutates inputs.\n */\nexport function step<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n snapshot: Snapshot<Ctx, States>,\n event: Evt,\n impl: Implementations<Ctx, Evt>,\n): StepResult<Ctx, States> {\n // Final state is inert: it never reacts to events.\n if (snapshot.status === \"final\") {\n return Object.freeze({ snapshot, effects: [] as readonly Effect[], changed: false });\n }\n\n const state = def.states[snapshot.value];\n /* v8 ignore next 3 — defensive: snapshot.value is always validated against def.states by defineMachine + initialSnapshot. */\n if (!state) {\n return Object.freeze({ snapshot, effects: [] as readonly Effect[], changed: false });\n }\n\n const candidateList = normalizeTransitions(state.on?.[event.type]);\n\n const chosen = pickTransition(candidateList, snapshot.context, event, impl, snapshot.value);\n if (!chosen) {\n return Object.freeze({ snapshot, effects: [] as readonly Effect[], changed: false });\n }\n\n const isExternal = chosen.target !== undefined;\n const nextStateValue = (chosen.target ?? snapshot.value) as States;\n const nextState = def.states[nextStateValue];\n\n const effectSink: Effect[] = [];\n let ctx = snapshot.context;\n\n if (isExternal) {\n ctx = runActions(state.exit, ctx, event, impl, effectSink);\n }\n ctx = runActions(chosen.actions, ctx, event, impl, effectSink);\n if (isExternal && nextState) {\n ctx = runActions(nextState.entry, ctx, event, impl, effectSink);\n }\n\n const status: \"active\" | \"final\" = nextState?.final === true ? \"final\" : \"active\";\n const nextSnapshot = freezeSnapshot({\n value: nextStateValue,\n context: ctx,\n status,\n });\n\n return Object.freeze({\n snapshot: nextSnapshot,\n effects: Object.freeze(effectSink.slice()) as readonly Effect[],\n changed: true,\n });\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fsm/types.ts","../src/fsm/definition.ts","../src/fsm/runtime.ts"],"names":[],"mappings":"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","file":"chunk-LG2AH5X6.js","sourcesContent":["// All public types live here so AI agents and humans can read the entire\n// public surface in one file.\n\nexport type Effect = Readonly<{ type: string; payload?: unknown }>;\n\nexport type Enqueuer = Readonly<{\n effect: (type: string, payload?: unknown) => void;\n}>;\n\nexport type GuardArgs<Ctx, Evt> = Readonly<{\n context: Ctx;\n event: Evt;\n /**\n * Optional guard registry, threaded by `evalGuard` so combinators can resolve\n * string refs nested inside `and / or / not`. Inline user guards may safely\n * ignore this field — it is `undefined` when guards are evaluated outside of\n * `evalGuard` (e.g. in unit tests calling the function directly).\n */\n guards?: Readonly<Record<string, Guard<Ctx, Evt>>>;\n /**\n * Current state value, threaded by `evalGuard` from the live snapshot. Used\n * by the `stateIn` combinator. `undefined` when guards are called outside of\n * a lifecycle evaluation.\n */\n value?: string;\n}>;\n\nexport type Guard<Ctx, Evt> = (args: GuardArgs<Ctx, Evt>) => boolean;\n\nexport type Action<Ctx, Evt> = (args: {\n context: Ctx;\n event: Evt;\n enqueue: Enqueuer;\n}) => Partial<Ctx> | void;\n\nexport type EffectHandler<Ctx, Evt> = (\n effect: Effect,\n args: { context: Ctx; event: Evt; signal: AbortSignal },\n) => void | Promise<void>;\n\nexport type GuardRef<Ctx, Evt> = string | Guard<Ctx, Evt>;\nexport type ActionRef<Ctx, Evt> = string | Action<Ctx, Evt>;\n\nexport type TransitionDef<Ctx, Evt, States extends string> = Readonly<{\n target?: States;\n guard?: GuardRef<Ctx, Evt>;\n actions?: readonly ActionRef<Ctx, Evt>[];\n}>;\n\n/**\n * A single transition as written in a `StateDef.on` map. Either the full\n * {@link TransitionDef} object form, or the string shorthand `\"targetState\"`\n * (à la XState) which the resolver normalizes to `{ target: \"targetState\" }`\n * before processing. The shorthand carries no guard or actions.\n *\n * @since 0.5.3\n */\nexport type TransitionConfig<Ctx, Evt, States extends string> =\n | States\n | TransitionDef<Ctx, Evt, States>;\n\n/**\n * @experimental v0.3.0\n *\n * A nested machine definition attachable to StateDef.sub. The type parameters\n * are independent from the parent machine's <Ctx, Evt, States>; sub-machines\n * may have entirely unrelated context and event shapes.\n *\n * This is an alias for MachineDef — sub-machines have the same definition\n * shape as top-level machines. The relationship is purely lifecycle:\n * a sub-machine instance is created when its parent state becomes active\n * and disposed when the parent state exits.\n */\nexport type SubMachineDef<\n SubCtx,\n SubEvt extends { type: string },\n SubStates extends string,\n> = MachineDef<SubCtx, SubEvt, SubStates>;\n\nexport type StateDef<Ctx, Evt, States extends string> = Readonly<{\n on?: Readonly<\n Record<\n string,\n TransitionConfig<Ctx, Evt, States> | readonly TransitionConfig<Ctx, Evt, States>[]\n >\n >;\n entry?: readonly ActionRef<Ctx, Evt>[];\n exit?: readonly ActionRef<Ctx, Evt>[];\n final?: boolean;\n /**\n * Optional sub-machine. When the runtime enters a state with `sub`,\n * the sub-machine is lazily instantiated; when it exits, the sub-machine\n * is disposed. See STABILITY.md for the experimental contract.\n *\n * The generic parameters are erased to `any` because sub-machine type\n * parameters are intentionally independent from the parent's `Ctx` / `Evt`\n * / `States`. `MachineDef`'s generics are invariant (guards / actions\n * consume them), so the storage position must use `any` rather than\n * `unknown`. Caller narrows via `runtime.subRuntime() as Runtime<...>`.\n *\n * @experimental since 0.3.0\n */\n // biome-ignore lint/suspicious/noExplicitAny: see JSDoc — invariant generic escape hatch\n sub?: MachineDef<any, any, any>;\n /**\n * Implementations for `sub`. Ignored if `sub` is absent. Defaults to `{}`\n * (sub-machine must rely on inline guards / actions / effects only).\n *\n * @experimental since 0.3.0\n */\n // biome-ignore lint/suspicious/noExplicitAny: same reason as `sub` above\n subImpl?: Implementations<any, any>;\n}>;\n\nexport type MachineDef<Ctx, Evt extends { type: string }, States extends string> = Readonly<{\n id: string;\n initial: States;\n context: Ctx;\n states: Readonly<Record<States, StateDef<Ctx, Evt, States>>>;\n}>;\n\n/**\n * Input shape accepted by `defineMachine` / `setup().defineMachine`. Identical\n * to {@link MachineDef} except `context` is **optional** — when omitted it\n * defaults to `{}` (paired with the `Ctx = Record<string, never>` default type\n * parameter). The returned value is always a fully-normalized\n * {@link MachineDef} with `context` present, so downstream consumers are\n * unaffected.\n *\n * @since 0.5.3\n */\nexport type MachineConfig<Ctx, Evt extends { type: string }, States extends string> = Readonly<{\n id: string;\n initial: States;\n states: Readonly<Record<States, StateDef<Ctx, Evt, States>>>;\n}> &\n // `context` may be omitted only when `Ctx` has no required properties (e.g. the\n // default `Record<string, never>`). If `Ctx` has required fields it must be\n // supplied, so omitting it can't silently default to `{}` and crash at runtime.\n (Record<string, never> extends Ctx ? { readonly context?: Ctx } : { readonly context: Ctx });\n\nexport type Snapshot<Ctx, States extends string> = Readonly<{\n value: States;\n context: Ctx;\n status: \"active\" | \"final\";\n}>;\n\nexport type Implementations<Ctx, Evt> = Readonly<{\n guards?: Readonly<Record<string, Guard<Ctx, Evt>>>;\n actions?: Readonly<Record<string, Action<Ctx, Evt>>>;\n effects?: Readonly<Record<string, EffectHandler<Ctx, Evt>>>;\n}>;\n\nexport type StepResult<Ctx, States extends string> = Readonly<{\n snapshot: Snapshot<Ctx, States>;\n effects: readonly Effect[];\n changed: boolean;\n}>;\n\n/**\n * Sentinel event type that `Runtime.reset()` synthesises when the caller does\n * not pass an explicit event. Middleware receives it through\n * `MiddlewareContext.event`. Exposed so user code can discriminate.\n */\nexport const RESET_EVENT_TYPE = \"@@aifsmjs/RESET\" as const;\nexport type ResetEvent = Readonly<{ type: typeof RESET_EVENT_TYPE }>;\n\nexport type MiddlewareContext<Ctx, Evt, States extends string> = Readonly<{\n prev: Snapshot<Ctx, States>;\n next: Snapshot<Ctx, States>;\n /**\n * The triggering event. May be the user's `Evt` (from `send()` or an\n * explicit `reset(event)`) or the `ResetEvent` sentinel emitted by a\n * `reset()` with no event argument.\n */\n event: Evt | ResetEvent;\n effects: readonly Effect[];\n changed: boolean;\n}>;\n\nexport type Middleware<Ctx, Evt, States extends string> = (\n ctx: MiddlewareContext<Ctx, Evt, States>,\n next: () => void,\n) => void;\n\n/**\n * Payload of the `'transition'` runtime event — emitted after each `send()` or\n * `reset()` that actually changed the snapshot value.\n */\nexport type RuntimeTransitionEvent<Ctx, Evt, States extends string> = Readonly<{\n prev: Snapshot<Ctx, States>;\n next: Snapshot<Ctx, States>;\n event: Evt | ResetEvent;\n effects: readonly Effect[];\n changed: boolean;\n}>;\n\n/**\n * Payload of the `'error'` runtime event — currently emitted for async effect\n * handler rejections (which would otherwise become unhandled). Synchronous\n * throws from effect handlers and middleware still propagate to the caller of\n * `send()` / `reset()`.\n */\nexport type RuntimeErrorEvent<Evt> = Readonly<{\n error: unknown;\n event: Evt | ResetEvent | undefined;\n}>;\n\nexport type RuntimeEventMap<Ctx, Evt, States extends string> = {\n transition: RuntimeTransitionEvent<Ctx, Evt, States>;\n error: RuntimeErrorEvent<Evt>;\n dispose: void;\n};\n\nexport interface Runtime<Ctx, Evt extends { type: string }, States extends string> {\n getSnapshot(): Snapshot<Ctx, States>;\n /** Alias for `getSnapshot()`. */\n snapshot(): Snapshot<Ctx, States>;\n send(event: Evt): Snapshot<Ctx, States>;\n /**\n * Predict whether sending `event` would fire a transition. Reuses\n * `resolveTransitions` + `evalGuard` without applying any actions. Guards\n * are expected to be pure; `can` then matches `send` for the same input.\n */\n can(event: Evt): boolean;\n subscribe(listener: (snap: Snapshot<Ctx, States>) => void): () => void;\n /**\n * EventTarget-like typed listener API. Returns an unsubscribe function.\n * `options.signal` removes the listener when aborted; `options.once`\n * removes the listener after the first invocation. After `dispose()`,\n * `on()` is a no-op and returns a no-op unsubscribe.\n */\n on<K extends keyof RuntimeEventMap<Ctx, Evt, States>>(\n type: K,\n listener: (payload: RuntimeEventMap<Ctx, Evt, States>[K]) => void,\n options?: { signal?: AbortSignal; once?: boolean },\n ): () => void;\n /**\n * Re-initialise the runtime to the definition's initial snapshot. Triggers\n * subscribers but does NOT run entry actions (reset = re-birth, not\n * \"transition into initial\"). Throws RuntimeDisposedError if disposed.\n * If an `event` is supplied, middleware sees it as the trigger; otherwise\n * a sentinel `{ type: \"@@aifsmjs/RESET\" }` is synthesised.\n */\n reset(event?: Evt): Snapshot<Ctx, States>;\n /**\n * Tear down: abort the internal AbortController (effect handlers see signal\n * fire), clear listeners, and mark this runtime as disposed. Subsequent\n * send()/reset() calls throw RuntimeDisposedError. Idempotent.\n */\n dispose(): void;\n /**\n * True after `dispose()` has been called.\n */\n readonly disposed: boolean;\n /**\n * AbortSignal scoped to this runtime's lifetime. Fires once on dispose().\n * Threaded to every EffectHandler invocation; external integrations\n * (e.g. component teardown) can also attach `signal.addEventListener(\"abort\", ...)`.\n */\n readonly signal: AbortSignal;\n /**\n * @experimental v0.3.0\n *\n * Returns the currently active sub-Runtime for the current parent state,\n * or undefined if:\n * - the current state has no `sub` definition, OR\n * - the sub-Runtime failed to initialise (SubMachineError was thrown\n * from `send()` / `reset()` / `createRuntime` per the spec contract),\n * OR\n * - the parent runtime has been disposed.\n *\n * The returned Runtime is typed at the loosest sub-machine signature.\n * Caller casts to the concrete sub type.\n *\n * Re-entry: when the parent leaves and re-enters a state with `sub`, a\n * fresh sub-Runtime is constructed. Previous sub-Runtime references held\n * by the caller are stale and MUST NOT be used (disposed).\n */\n subRuntime(): Runtime<unknown, { type: string }, string> | undefined;\n /**\n * Semantic sugar for `runtime.on('transition', handler, opts)`. Returns\n * the same unsubscribe function. Sharing the same listener Set with\n * `on('transition', ...)` means registration order determines invocation\n * order across both APIs.\n *\n * @since 0.3.0\n */\n onTransition(\n handler: (payload: RuntimeTransitionEvent<Ctx, Evt, States>) => void,\n options?: { signal?: AbortSignal; once?: boolean },\n ): () => void;\n}\n\nexport type RuntimeOptions<Ctx, Evt, States extends string> = Readonly<{\n middleware?: readonly Middleware<Ctx, Evt, States>[];\n /**\n * If false, do not dispatch effects through the effect handler map.\n * Useful for replay / dry-run modes. Defaults to true.\n */\n dispatchEffects?: boolean;\n}>;\n","import { isAsyncGuardFn } from \"./evaluator.js\";\nimport { normalizeTransitions } from \"./resolver.js\";\nimport { createRuntime } from \"./runtime.js\";\nimport { freezeSnapshot } from \"./snapshot.js\";\nimport type {\n Implementations,\n MachineConfig,\n MachineDef,\n Runtime,\n RuntimeOptions,\n Snapshot,\n StateDef,\n} from \"./types.js\";\n\nexport class InvalidDefinitionError extends Error {\n constructor(message: string) {\n super(`aifsmjs: ${message}`);\n this.name = \"InvalidDefinitionError\";\n }\n}\n\nfunction validateDefinition<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n // Cycle guard: subs may reference each other (or themselves) by object\n // identity. We validate each distinct definition object at most once so a\n // self/mutually-referential sub graph terminates instead of recursing\n // forever (C2). Seeded by the public entry points below.\n seen: WeakSet<object> = new WeakSet(),\n): void {\n if (!def.id || typeof def.id !== \"string\") {\n throw new InvalidDefinitionError(\"definition must have a non-empty string `id`\");\n }\n /* v8 ignore next 3 — additional safety: TS prevents non-object `states`; this guards untyped JS callers. */\n if (!def.states || typeof def.states !== \"object\") {\n throw new InvalidDefinitionError(\"definition must have a `states` object\");\n }\n const stateKeys = Object.keys(def.states) as States[];\n if (stateKeys.length === 0) {\n throw new InvalidDefinitionError(\"`states` must declare at least one state\");\n }\n if (!def.initial || !stateKeys.includes(def.initial)) {\n throw new InvalidDefinitionError(\n `\\`initial\\` \"${String(def.initial)}\" is not declared in states (${stateKeys.join(\", \")})`,\n );\n }\n for (const [stateName, stateDef] of Object.entries(def.states) as [\n States,\n (typeof def.states)[States],\n ][]) {\n // §4 sub-shape check + deep recursion (C2). The shallow shape check rejects\n // a malformed sub; recursing validateDefinition into the sub then rejects an\n // unknown transition target or a declared-async guard at construction\n // instead of leaving it to blow up at child.send(). Closes the FSM-07 sub\n // async-guard discrepancy (same root). The cycle guard makes this safe for\n // self/mutually-referential subs.\n if (stateDef.sub !== undefined) {\n const sub = stateDef.sub;\n const subStates = (sub as { states?: unknown }).states;\n const subInitial = (sub as { initial?: unknown }).initial;\n if (\n typeof sub !== \"object\" ||\n sub === null ||\n typeof subStates !== \"object\" ||\n subStates === null ||\n typeof subInitial !== \"string\" ||\n // initial must name one of the sub's own states — otherwise the child\n // boots pointing at a non-existent state and no-ops forever (FSM-S-02).\n !Object.hasOwn(subStates as object, subInitial)\n ) {\n throw new InvalidDefinitionError(\n `state \"${stateName}\".sub is not a valid sub-machine definition (missing states or initial)`,\n );\n }\n // Recurse — but only once per distinct sub object (cycle guard).\n if (!seen.has(sub as object)) {\n seen.add(sub as object);\n validateDefinition(sub as MachineDef<unknown, { type: string }, string>, seen);\n }\n }\n if (!stateDef.on) continue;\n for (const [evtType, entry] of Object.entries(stateDef.on)) {\n const transitions = normalizeTransitions(entry);\n for (const t of transitions) {\n if (t.target !== undefined && !stateKeys.includes(t.target)) {\n throw new InvalidDefinitionError(\n `transition ${stateName} -[${evtType}]-> \"${String(t.target)}\" targets an unknown state`,\n );\n }\n if (t.guard !== undefined && isAsyncGuardFn(t.guard)) {\n throw new InvalidDefinitionError(\n `transition ${stateName} -[${evtType}]-> uses an async guard. Guards must be sync; move I/O into an effect.`,\n );\n }\n }\n }\n }\n}\n\n/**\n * Validate a machine definition shape and return it. When `context` is\n * provided the same reference is returned; when it is omitted a shallow copy\n * with `context: {}` is returned. Validation is intentionally shallow.\n *\n * Two call forms:\n *\n * defineMachine<Ctx, Evt, States>({ ... })\n * Explicit generics. Use when you need full control (e.g. union event\n * types). Required because TypeScript cannot otherwise infer `Evt`.\n *\n * setup<Ctx, Evt>().defineMachine({ ... })\n * Curried form. Lets `States` be inferred from `keyof states`, so you\n * can omit it. Recommended for typical usage.\n */\nexport function defineMachine<\n Ctx = Record<string, never>,\n Evt extends { type: string } = { type: string },\n States extends string = string,\n>(def: MachineConfig<Ctx, Evt, States>): MachineDef<Ctx, Evt, States> {\n const normalized = (!(\"context\" in def) ? { ...def, context: {} as Ctx } : def) as MachineDef<\n Ctx,\n Evt,\n States\n >;\n validateDefinition(normalized);\n return normalized;\n}\n\n/**\n * Curried builder so `States` can be inferred from `keyof states` without\n * `initial` collapsing it to a single literal. Pass `Ctx` and `Evt` as the\n * type arguments; pass the def to the returned `defineMachine`.\n *\n * const machine = setup<MyCtx, MyEvt>().defineMachine({\n * id: \"m\",\n * initial: \"a\",\n * context: { ... },\n * states: { a: {...}, b: {...} }, // States inferred as \"a\" | \"b\"\n * });\n */\nexport function setup<\n Ctx = Record<string, never>,\n Evt extends { type: string } = { type: string },\n>(): {\n defineMachine: <const States extends string>(\n def: Readonly<{\n id: string;\n initial: NoInfer<States>;\n states: Readonly<Record<States, StateDef<Ctx, Evt, States>>>;\n }> &\n (Record<string, never> extends Ctx ? { readonly context?: Ctx } : { readonly context: Ctx }),\n ) => MachineDef<Ctx, Evt, States>;\n} {\n return {\n defineMachine: <const States extends string>(\n def: Readonly<{\n id: string;\n initial: NoInfer<States>;\n states: Readonly<Record<States, StateDef<Ctx, Evt, States>>>;\n }> &\n (Record<string, never> extends Ctx\n ? { readonly context?: Ctx }\n : { readonly context: Ctx }),\n ) => {\n const cast = (!(\"context\" in def)\n ? { ...def, context: {} as Ctx }\n : def) as unknown as MachineDef<Ctx, Evt, States>;\n validateDefinition(cast);\n return cast;\n },\n };\n}\n\n/**\n * Build the initial snapshot for a machine.\n */\nexport function initialSnapshot<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n): Snapshot<Ctx, States> {\n const isFinal = def.states[def.initial]?.final === true;\n return freezeSnapshot({\n value: def.initial,\n context: def.context,\n status: isFinal ? (\"final\" as const) : (\"active\" as const),\n });\n}\n\n/**\n * Convenience factory that composes `defineMachine` and `createRuntime` in\n * one call for the common case where you do not need to keep the machine\n * definition around for serialization or sharing.\n *\n * For type inference over `States` from `keyof states`, prefer\n * `setup<Ctx, Evt>().defineMachine(...)` then pass the result to\n * `createRuntime` separately. `createMachine` is the spec-style entry point\n * documented in the ai*js ecosystem review.\n */\nexport function createMachine<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n opts?: RuntimeOptions<Ctx, Evt, States>,\n): Runtime<Ctx, Evt, States> {\n return createRuntime(defineMachine(def), impl, opts ?? {});\n}\n","import { initialSnapshot } from \"./definition.js\";\nimport { evalGuard, isThenable } from \"./evaluator.js\";\nimport { step } from \"./lifecycle.js\";\nimport { normalizeTransitions } from \"./resolver.js\";\nimport { deepFreeze } from \"./snapshot.js\";\nimport {\n type Effect,\n type Implementations,\n type MachineDef,\n type Middleware,\n RESET_EVENT_TYPE,\n type ResetEvent,\n type Runtime,\n type RuntimeEventMap,\n type RuntimeOptions,\n type RuntimeTransitionEvent,\n type Snapshot,\n} from \"./types.js\";\n\nexport class RuntimeDisposedError extends Error {\n constructor() {\n super(\"aifsmjs: runtime has been disposed; send()/reset() are not allowed\");\n this.name = \"RuntimeDisposedError\";\n }\n}\n\n/**\n * Thrown by `send()` / `reset()` when a sub-machine init or dispose throws.\n *\n * Invariants:\n * - `phase: \"init\"` — child constructor threw. Parent snapshot was rolled\n * back to `prev`; no middleware ran; no `'transition'` emitted; no effects.\n * - `phase: \"dispose\"` — previous child's `dispose()` threw during transition.\n * Parent snapshot was rolled back to `prev`; child reference is cleared.\n * - Never thrown from `runtime.dispose()` cascade (never-throws contract).\n *\n * @since 0.3.0\n */\nexport class SubMachineError extends Error {\n readonly parentState: string;\n readonly phase: \"init\" | \"dispose\";\n override readonly cause: unknown;\n\n constructor(parentState: string, phase: \"init\" | \"dispose\", cause: unknown) {\n super(`aifsmjs: sub-machine ${phase} failed at parent state \"${parentState}\"`, { cause });\n this.name = \"SubMachineError\";\n this.parentState = parentState;\n this.phase = phase;\n this.cause = cause; // belt-and-suspenders: legacy bundlers ignore ES2022 cause option\n }\n}\n\nconst RESET_EVENT: ResetEvent = Object.freeze({ type: RESET_EVENT_TYPE });\n\nfunction composeMiddleware<Ctx, Evt, States extends string>(\n middleware: readonly Middleware<Ctx, Evt, States>[],\n): Middleware<Ctx, Evt, States> {\n return (ctx, finalNext) => {\n let index = -1;\n const dispatch = (i: number): void => {\n if (i <= index) throw new Error(\"aifsmjs: next() called multiple times in middleware\");\n index = i;\n const fn = middleware[i];\n if (!fn) {\n finalNext();\n return;\n }\n fn(ctx, () => dispatch(i + 1));\n };\n dispatch(0);\n };\n}\n\n/**\n * Build a thin stateful runtime around a machine. `send()` calls `step()`,\n * runs the read-only middleware pipeline, dispatches effects, and notifies\n * subscribers. The runtime owns an `AbortController`; `dispose()` aborts it\n * and clears all state.\n */\nexport function createRuntime<Ctx, Evt extends { type: string }, States extends string>(\n def: MachineDef<Ctx, Evt, States>,\n impl: Implementations<Ctx, Evt>,\n opts: RuntimeOptions<Ctx, Evt, States> = {},\n): Runtime<Ctx, Evt, States> {\n let snapshot: Snapshot<Ctx, States> = initialSnapshot(def);\n const listeners = new Set<(snap: Snapshot<Ctx, States>) => void>();\n const middlewareChain =\n opts.middleware && opts.middleware.length > 0 ? composeMiddleware(opts.middleware) : undefined;\n const shouldDispatch = opts.dispatchEffects !== false;\n const controller = new AbortController();\n let disposed = false;\n // §3.1 sub-machine state. childRuntime is the live child; childAbortCleanup\n // detaches the parent-abort listener attached by wireChildAbort. Both are\n // cleared together whenever the child is replaced or disposed (P1-3 fix).\n let childRuntime: Runtime<unknown, { type: string }, string> | undefined;\n let childAbortCleanup: (() => void) | undefined;\n\n type EventListeners = {\n [K in keyof RuntimeEventMap<Ctx, Evt, States>]: Set<\n (payload: RuntimeEventMap<Ctx, Evt, States>[K]) => void\n >;\n };\n const eventListeners: EventListeners = {\n transition: new Set(),\n error: new Set(),\n dispose: new Set(),\n };\n const externalAbortCleanups = new Set<() => void>();\n\n function emit<K extends keyof RuntimeEventMap<Ctx, Evt, States>>(\n type: K,\n payload: RuntimeEventMap<Ctx, Evt, States>[K],\n ): void {\n // Snapshot-before-iterate (family canonical, aieventjs .slice()): a\n // listener that subscribes/unsubscribes another during dispatch must not\n // mutate the set being walked. One array alloc per emit, matching the\n // family's accepted cost (FAM-S-03).\n for (const fn of Array.from(eventListeners[type])) fn(payload);\n }\n\n function notify(committed?: Snapshot<Ctx, States>) {\n const captured = committed ?? snapshot;\n // Snapshot-before-iterate, as above (FAM-S-03).\n for (const l of Array.from(listeners)) l(captured);\n }\n\n function runMiddleware(\n prev: Snapshot<Ctx, States>,\n event: Evt | ResetEvent,\n effects: readonly Effect[],\n changed: boolean,\n ) {\n if (!middlewareChain) return;\n middlewareChain(deepFreeze({ prev, next: snapshot, event, effects, changed }), () => {});\n }\n\n function dispatchEffects(effects: readonly Effect[], context: Ctx, event: Evt): void {\n if (!impl.effects || effects.length === 0) return;\n for (const eff of effects) {\n const handler = impl.effects[eff.type];\n if (!handler) continue;\n const r = handler(eff, { context, event, signal: controller.signal });\n // isThenable (not instanceof Promise) so cross-realm Promises and\n // user-defined PromiseLike results also have their rejections routed to\n // the 'error' channel; Promise.resolve() normalises them (FSM-B-03).\n if (isThenable(r)) {\n Promise.resolve(r).catch((err: unknown) => {\n emit(\"error\", { error: err, event });\n });\n }\n }\n }\n\n // §3.1 Attach one-shot abort listener: parent dispose → child.dispose().\n // Returns a cleanup fn that detaches the listener; caller stores it in\n // `childAbortCleanup` and invokes when the child is replaced/disposed\n // (P1-3 fix: prevent stale listeners accumulating on the parent signal).\n function wireChildAbort(child: Runtime<unknown, { type: string }, string>): () => void {\n /* v8 ignore next 7 — parent may already be aborted in edge cases; dispose still runs */\n if (controller.signal.aborted) {\n try {\n child.dispose();\n } catch {\n /* swallow */\n }\n return () => {};\n }\n /* v8 ignore next 7 — defensive: dispose() pre-cleans this listener and\n disposes the child manually before calling controller.abort(), so\n onAbort fires only if external code aborts the controller bypassing\n dispose(). Internal-only controller has no such external path today. */\n const onAbort = () => {\n try {\n child.dispose();\n } catch {\n /* swallow */\n }\n };\n controller.signal.addEventListener(\"abort\", onAbort, { once: true });\n return () => controller.signal.removeEventListener(\"abort\", onAbort);\n }\n\n // §3.1 Instantiate the child for `stateValue` (which must have a `sub`) and\n // wire its parent-abort listener, committing both to childRuntime /\n // childAbortCleanup. Throws SubMachineError(phase: \"init\") on failure; the\n // caller must NOT commit the parent snapshot on throw. Single source of the\n // init+wire sequence shared by applySubLifecycle, reset(), and bootstrap\n // (FSM-C-01) — keeps the most failure-sensitive path in one place.\n function initChildFor(stateValue: States): void {\n const stateDef = def.states[stateValue];\n const sub = stateDef?.sub;\n /* v8 ignore next 2 — callers only invoke this after checking stateDef.sub\n is defined; the guard documents that precondition and is never taken. */\n if (sub === undefined) return;\n let newChild: Runtime<unknown, { type: string }, string>;\n try {\n newChild = createRuntime(sub, stateDef.subImpl ?? {});\n } catch (cause) {\n throw new SubMachineError(stateValue as string, \"init\", cause);\n }\n childRuntime = newChild;\n childAbortCleanup = wireChildAbort(newChild);\n }\n\n // §3.3 Re-resolve guards to find the chosen transition and determine\n // whether it is external (has a `target`). Replaces the v0.3.0 dev\n // hasSelfTargetMarker heuristic that over-reported when an event had both\n // internal (no-target) and self-target (target === value) candidates\n // (P1-2 fix). Cost: one extra guard evaluation pass per same-value event.\n function findChosenIsExternal(value: States, event: Evt, context: Ctx): boolean {\n const state = def.states[value];\n if (!state?.on) return false;\n const list = normalizeTransitions(state.on[event.type]);\n if (list.length === 0) return false;\n for (const t of list) {\n if (!t.guard || evalGuard(t.guard, context, event, impl, value)) {\n return t.target !== undefined;\n }\n }\n /* v8 ignore next — defensive: caller only invokes when step() returned\n changed=true, which guarantees a matching guard exists in the same\n candidate list. The for-loop above always returns before this line. */\n return false;\n }\n\n // §3.1 Dispose old child and/or init new child. Throws SubMachineError on failure.\n // Caller must NOT commit snapshot on throw.\n function applySubLifecycle(prevValue: States, nextValue: States): void {\n const prevStateDef = def.states[prevValue];\n const nextStateDef = def.states[nextValue];\n if (prevStateDef?.sub !== undefined && childRuntime !== undefined) {\n const child = childRuntime;\n childRuntime = undefined;\n childAbortCleanup?.();\n childAbortCleanup = undefined;\n try {\n child.dispose();\n } catch (cause) {\n throw new SubMachineError(prevValue as string, \"dispose\", cause);\n }\n }\n if (nextStateDef?.sub !== undefined) {\n initChildFor(nextValue);\n }\n }\n\n function send(event: Evt): Snapshot<Ctx, States> {\n if (disposed) throw new RuntimeDisposedError();\n const prev = snapshot;\n const result = step(def, prev, event, impl);\n const isExternal =\n result.changed &&\n (prev.value !== result.snapshot.value ||\n findChosenIsExternal(prev.value, event, prev.context));\n // Sub lifecycle BEFORE snapshot commit (§3.4); throws SubMachineError on failure → no commit\n if (result.changed && isExternal) applySubLifecycle(prev.value, result.snapshot.value);\n snapshot = result.snapshot;\n const committed = result.snapshot;\n runMiddleware(prev, event, result.effects, result.changed);\n if (shouldDispatch) dispatchEffects(result.effects, committed.context, event);\n if (result.changed) {\n notify(committed);\n emit(\"transition\", {\n prev,\n next: committed,\n event,\n effects: result.effects,\n changed: true,\n } as RuntimeTransitionEvent<Ctx, Evt, States>);\n }\n return snapshot;\n }\n\n function reset(event?: Evt): Snapshot<Ctx, States> {\n if (disposed) throw new RuntimeDisposedError();\n const prev = snapshot;\n const nextSnap = initialSnapshot(def);\n const changed = prev.value !== nextSnap.value;\n // Dispose current child (§3.5)\n if (childRuntime) {\n const child = childRuntime;\n childRuntime = undefined;\n childAbortCleanup?.();\n childAbortCleanup = undefined;\n try {\n child.dispose();\n } catch (cause) {\n throw new SubMachineError(prev.value as string, \"dispose\", cause);\n }\n }\n // Init child for new initial state if it has sub (§3.5)\n const initStateDef = def.states[nextSnap.value];\n if (initStateDef?.sub) {\n initChildFor(nextSnap.value);\n }\n snapshot = nextSnap;\n // Capture the committed snapshot before notify()/emit so a subscriber that\n // re-entrantly send()s (which advances the mutable `snapshot`) cannot\n // corrupt this reset's payload — mirrors send()'s 0.2.0 fix (FSM-B-01).\n const committed = nextSnap;\n const triggerEvent: Evt | ResetEvent = event ?? RESET_EVENT;\n runMiddleware(prev, triggerEvent, [], changed);\n if (changed) {\n notify(committed);\n emit(\"transition\", {\n prev,\n next: committed,\n event: triggerEvent,\n effects: [],\n changed: true,\n } as RuntimeTransitionEvent<Ctx, Evt, States>);\n }\n // Return the live snapshot (consistent with send()): under a re-entrant\n // send() from a subscriber, this reflects the latest committed state. Only\n // the emitted payload above is pinned to this reset's own outcome.\n return snapshot;\n }\n\n function can(event: Evt): boolean {\n if (disposed || snapshot.status === \"final\") return false;\n const state = def.states[snapshot.value];\n /* v8 ignore next — defensive: snapshot.value always corresponds to a declared state. */\n if (!state) return false;\n const list = normalizeTransitions(state.on?.[event.type]);\n if (list.length === 0) return false;\n for (const t of list) {\n if (!t.guard) return true;\n if (evalGuard(t.guard, snapshot.context, event, impl, snapshot.value)) return true;\n }\n return false;\n }\n\n function on<K extends keyof RuntimeEventMap<Ctx, Evt, States>>(\n type: K,\n listener: (payload: RuntimeEventMap<Ctx, Evt, States>[K]) => void,\n options?: { signal?: AbortSignal; once?: boolean },\n ): () => void {\n if (disposed || options?.signal?.aborted) return () => {};\n const target = eventListeners[type];\n let detachAbort: (() => void) | undefined;\n // Full teardown shared by the once-wrapper, the abort handler, and the\n // returned unsubscribe so every path detaches the abort listener too — a\n // once-handler that also passed a { signal } previously left the abort\n // listener attached until dispose()/abort (memory leak).\n const cleanup = (): void => {\n target.delete(wrapped);\n if (detachAbort) {\n detachAbort();\n externalAbortCleanups.delete(detachAbort);\n }\n };\n let wrapped: (payload: RuntimeEventMap<Ctx, Evt, States>[K]) => void = listener;\n if (options?.once) {\n wrapped = (payload) => {\n cleanup();\n listener(payload);\n };\n }\n target.add(wrapped);\n const signal = options?.signal;\n if (signal) {\n const onAbort = () => cleanup();\n signal.addEventListener(\"abort\", onAbort, { once: true });\n detachAbort = () => signal.removeEventListener(\"abort\", onAbort);\n externalAbortCleanups.add(detachAbort);\n }\n return cleanup;\n }\n\n function dispose(): void {\n if (disposed) return;\n disposed = true;\n // Cascade child dispose; swallow exceptions (dispose contract) (§3.6)\n if (childRuntime) {\n childAbortCleanup?.();\n childAbortCleanup = undefined;\n try {\n childRuntime.dispose();\n } catch {\n /* swallow */\n }\n childRuntime = undefined;\n }\n controller.abort();\n listeners.clear();\n // §3.6 dispose() is contractually never-throws + idempotent (README:65,\n // STABILITY.md:22). emit('dispose') runs user listeners; a throwing one\n // must neither escape dispose() nor abort the remaining teardown (which\n // would leak external-signal abort listeners, since a second dispose()\n // short-circuits on `if (disposed) return`). The try/finally guarantees\n // the listener-set clear + externalAbortCleanups loop ALWAYS run, mirroring\n // the defensiveness of the child-dispose cascade above.\n try {\n emit(\"dispose\", undefined as RuntimeEventMap<Ctx, Evt, States>[\"dispose\"]);\n } catch {\n /* swallow — never-throws contract */\n } finally {\n for (const set of Object.values(eventListeners)) set.clear();\n for (const cleanup of externalAbortCleanups) cleanup();\n externalAbortCleanups.clear();\n }\n }\n\n const runtime: Runtime<Ctx, Evt, States> = {\n getSnapshot: () => snapshot,\n snapshot: () => snapshot,\n send,\n can,\n reset,\n dispose,\n on,\n get disposed() {\n return disposed;\n },\n get signal() {\n return controller.signal;\n },\n subscribe(listener) {\n if (disposed) return () => {};\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n subRuntime: () => childRuntime,\n onTransition: (handler, options) => on(\"transition\", handler, options),\n };\n\n // §2 Bootstrap: if initial state has sub, instantiate child BEFORE returning.\n // Failure throws SubMachineError(initialState, \"init\", cause).\n const bootStateDef = def.states[snapshot.value];\n if (bootStateDef?.sub) {\n initChildFor(snapshot.value);\n }\n\n return runtime;\n}\n"]}
|