aifsmjs 0.3.1 → 0.4.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 CHANGED
@@ -107,7 +107,7 @@ The three layers are fully decoupled: take `step()` alone for replay, take `Mach
107
107
  | Will do (v1) | Won't do |
108
108
  | --------------------------------------------------- | ------------------------------------------------- |
109
109
  | Flat states + transitions | Parallel state regions |
110
- | Hierarchical sugar via `state.sub` (experimental, since 0.3.0) | Closures embedded in definition (breaks serialize) |
110
+ | Hierarchical sugar via `state.sub` (stable since 0.4.0) | Closures embedded in definition (breaks serialize) |
111
111
  | Guards (sync only; inline async throws `InvalidDefinitionError` at `defineMachine`; runtime throws `AsyncGuardError` on thenable return) | Async guards |
112
112
  | Actions (assign + enqueue effects) | Async API inside an action (use an effect) |
113
113
  | Fire-and-forget effects | Actor invocation / spawn |
@@ -357,7 +357,7 @@ Non-goals:
357
357
  - No async lifecycle hook
358
358
  - Inspect middleware cannot alter the transition outcome
359
359
 
360
- ### Sub-machine lifecycle (since 0.3.0, experimental)
360
+ ### Sub-machine lifecycle (stable since 0.4.0)
361
361
 
362
362
  When a state declares `sub`, the per-transition ordering is:
363
363
 
@@ -497,8 +497,8 @@ Example-first, PBT-augmented. Lesson from jssm: "3000+ tests / 100% coverage" tu
497
497
  | ------- | ------------------------------------------------------------------ |
498
498
  | v0.1 | core + guards + effects + inspect + replay + pbt (this release) |
499
499
  | v0.2 | Async-guard detection, coverage tuning, llms-full.txt verify gate |
500
- | v0.3 | Hierarchical sugar via `state.sub` (experimental, this release) |
501
- | v0.4 | `historyState` — remember last active sub-state on re-entry |
500
+ | v0.3 | Hierarchical sugar via `state.sub` (experimental) |
501
+ | v0.4 | Sub-machine API promoted to stable; dependency-reduction cycle |
502
502
  | v0.5 | `aifsmjs-bridge-bitecs` / `aifsmjs-bridge-pixi` (separate sub-packages) |
503
503
  | v1.0 | API freeze and stability guarantee |
504
504
 
@@ -509,7 +509,7 @@ Example-first, PBT-augmented. Lesson from jssm: "3000+ tests / 100% coverage" tu
509
509
  - **Tick / game-loop hook** (out of scope for v1)
510
510
  - **ECS / Pixi bridges** (out of scope for v1)
511
511
 
512
- **v0.4 candidate**: `historyState` — remember last active sub-state on
512
+ **Future candidate**: `historyState` — remember last active sub-state on
513
513
  re-entry. Workaround today: snapshot via `onTransition` and restore
514
514
  manually.
515
515
 
package/README_ZHTW.md CHANGED
@@ -106,7 +106,7 @@ console.log(runtime.getSnapshot().context); // { ticks: 1 }
106
106
  | 會做(v1) | 不會做 |
107
107
  | --------------------------------------------------- | ------------------------------------------------- |
108
108
  | Flat states + transitions | Parallel state regions |
109
- | 透過 `state.sub` 的階層式 sugar(experimental,since 0.3.0) | Definition 內直接綁 closure(會無法序列化) |
109
+ | 透過 `state.sub` 的階層式 sugar(stable since 0.4.0) | Definition 內直接綁 closure(會無法序列化) |
110
110
  | Guards(sync only;inline async 在 `defineMachine` 時丟 `InvalidDefinitionError`;runtime 偵測到 thenable 回傳則丟 `AsyncGuardError`) | Async guards |
111
111
  | Actions(assign + enqueue effects) | 在 action 內呼叫 async API(請放到 effect) |
112
112
  | Fire-and-forget effects | Actor invocation / spawn |
@@ -356,7 +356,7 @@ sched.cancelAll();
356
356
  - async lifecycle hook
357
357
  - Inspect middleware 影響 transition 結果
358
358
 
359
- ### Sub-machine lifecycle(since 0.3.0,experimental)
359
+ ### Sub-machine lifecycle(stable since 0.4.0)
360
360
 
361
361
  當一個 state 宣告 `sub` 時,每次 transition 的執行順序為:
362
362
 
@@ -491,8 +491,8 @@ aifsmjs 在幾個常見議題上做了刻意取捨,跟主流 FSM library 寫
491
491
  | ---- | ----------------------------------------------------------------- |
492
492
  | v0.1 | core + guards + effects + inspect + replay + pbt(本次發佈) |
493
493
  | v0.2 | Async-guard 偵測、coverage 調整、llms-full.txt verify gate |
494
- | v0.3 | 透過 `state.sub` 的階層式 sugar(experimental,本次發佈) |
495
- | v0.4 | `historyState` — re-entry 時恢復上一次的 sub-state |
494
+ | v0.3 | 透過 `state.sub` 的階層式 sugar(experimental) |
495
+ | v0.4 | sub-machine API 升 stable;降依賴 cycle |
496
496
  | v0.5 | `aifsmjs-bridge-bitecs` / `aifsmjs-bridge-pixi`(獨立 sub-package) |
497
497
  | v1.0 | API freeze 與 stability guarantee |
498
498
 
@@ -503,7 +503,7 @@ aifsmjs 在幾個常見議題上做了刻意取捨,跟主流 FSM library 寫
503
503
  - **Tick / game-loop hook**(v1 不做)
504
504
  - **ECS / Pixi bridges**(v1 不做)
505
505
 
506
- **v0.4 候選**:`historyState` — re-entry 時自動恢復上一次的 sub-state。0.3.0 的暫代方案:透過 `onTransition` snapshot sub-runtime 的 value,之後手動還原。
506
+ **未來候選**:`historyState` — re-entry 時自動恢復上一次的 sub-state。0.3.0 的暫代方案:透過 `onTransition` snapshot sub-runtime 的 value,之後手動還原。
507
507
 
508
508
  ---
509
509
 
package/STABILITY.md CHANGED
@@ -27,10 +27,13 @@ Fully stable. Breaking changes only at a major version bump (1.0+).
27
27
  `on('transition', ...)` API; listed under Stable because the underlying
28
28
  contract is unchanged.
29
29
 
30
- ## Experimental (since 0.3.0)
30
+ ### Sub-machines (stable since 0.4.0)
31
31
 
32
- Shape and behaviour may change in any minor bump until 1.0. Production use
33
- is OK; expect a one-line patch on minor upgrades.
32
+ The hierarchical sub-machine surface shipped experimentally in 0.3.0 is
33
+ stable as of 0.4.0. Signatures and runtime semantics — including the
34
+ init-failure quarantine behaviour described below — are frozen for the 1.x
35
+ line. The boundaries listed are intentional design trade-offs, not bugs or
36
+ pending instability.
34
37
 
35
38
  - `StateDef.sub` (optional `SubMachineDef`) — when present, a child runtime
36
39
  is lazily initialised on entry and disposed on exit. Per-transition
@@ -45,7 +48,7 @@ is OK; expect a one-line patch on minor upgrades.
45
48
  failure. Fields: `parentState`, `phase ("init" | "dispose")`, `cause`.
46
49
  - `SubMachineDef` type alias.
47
50
 
48
- ### Known boundaries (0.3.0)
51
+ #### Design boundaries
49
52
 
50
53
  - **Replay / PBT do not see child state.** `replay()` and
51
54
  `commandsFromMachine` only inspect parent snapshots. If your business
@@ -54,24 +57,31 @@ is OK; expect a one-line patch on minor upgrades.
54
57
  disposed it. The handle is not reinitialised until the parent leaves and
55
58
  re-enters the sub-bearing state. Detect with `child.disposed`.
56
59
  - **Self-targeting external (`A → A`) is treated as full exit/entry**:
57
- child is disposed and reinitialised. The 0.3.0 dispatcher re-resolves
58
- guards to identify the chosen transition before deciding external vs
59
- internal, so guarded internal transitions on the same event no longer
60
- trigger a reinit.
60
+ child is disposed and reinitialised. The dispatcher re-resolves guards to
61
+ identify the chosen transition before deciding external vs internal, so
62
+ guarded internal transitions on the same event do not trigger a reinit.
61
63
  - **Init-failure mid-transition leaves the parent without a live child.**
62
64
  If `applySubLifecycle` successfully disposes the old child and then the
63
65
  new child's `createRuntime` throws, the parent's snapshot is rolled back
64
66
  to `prev` but `subRuntime()` returns `undefined`. Callers catching
65
67
  `SubMachineError(phase: "init")` should treat the runtime as quarantined
66
68
  — call `runtime.dispose()` (idempotent) or `runtime.reset()` (which
67
- attempts re-init) before sending further events. A future major may
68
- switch to a two-phase "init before dispose" commit strategy; for 0.3.x
69
- this remains opt-in only when child failures are expected.
69
+ attempts re-init) before sending further events. This quarantine
70
+ behaviour is part of the stable contract; a future **major** version may
71
+ switch to a two-phase "init before dispose" commit strategy (a breaking
72
+ change reserved for 1.0+), but the current semantics are frozen for the
73
+ 1.x line.
70
74
 
71
- ## Draft (planned, not implemented in 0.3.0)
75
+ ## Experimental
76
+
77
+ No experimental APIs as of 0.4.0. The 0.3.0 sub-machine surface graduated to
78
+ Stable in 0.4.0 — see "Sub-machines" above.
79
+
80
+ ## Draft (planned, not implemented)
72
81
 
73
82
  API sketched, not shipped. May change before release.
74
83
 
75
- - `historyState` (v0.4 candidate) — opt-in pseudo-state that remembers
76
- the last active sub-state on re-entry. Workaround in 0.3.0: snapshot
77
- the sub-runtime's value on exit via `onTransition`, restore manually.
84
+ - `historyState` (candidate for a future minor) — opt-in pseudo-state that
85
+ remembers the last active sub-state on re-entry. Workaround in 0.3.0:
86
+ snapshot the sub-runtime's value on exit via `onTransition`, restore
87
+ manually.
package/llms-full.txt CHANGED
@@ -120,7 +120,7 @@ The three layers are fully decoupled: take `step()` alone for replay, take `Mach
120
120
  | Will do (v1) | Won't do |
121
121
  | --------------------------------------------------- | ------------------------------------------------- |
122
122
  | Flat states + transitions | Parallel state regions |
123
- | Hierarchical sugar via `state.sub` (experimental, since 0.3.0) | Closures embedded in definition (breaks serialize) |
123
+ | Hierarchical sugar via `state.sub` (stable since 0.4.0) | Closures embedded in definition (breaks serialize) |
124
124
  | Guards (sync only; inline async throws `InvalidDefinitionError` at `defineMachine`; runtime throws `AsyncGuardError` on thenable return) | Async guards |
125
125
  | Actions (assign + enqueue effects) | Async API inside an action (use an effect) |
126
126
  | Fire-and-forget effects | Actor invocation / spawn |
@@ -370,7 +370,7 @@ Non-goals:
370
370
  - No async lifecycle hook
371
371
  - Inspect middleware cannot alter the transition outcome
372
372
 
373
- ### Sub-machine lifecycle (since 0.3.0, experimental)
373
+ ### Sub-machine lifecycle (stable since 0.4.0)
374
374
 
375
375
  When a state declares `sub`, the per-transition ordering is:
376
376
 
@@ -510,8 +510,8 @@ Example-first, PBT-augmented. Lesson from jssm: "3000+ tests / 100% coverage" tu
510
510
  | ------- | ------------------------------------------------------------------ |
511
511
  | v0.1 | core + guards + effects + inspect + replay + pbt (this release) |
512
512
  | v0.2 | Async-guard detection, coverage tuning, llms-full.txt verify gate |
513
- | v0.3 | Hierarchical sugar via `state.sub` (experimental, this release) |
514
- | v0.4 | `historyState` — remember last active sub-state on re-entry |
513
+ | v0.3 | Hierarchical sugar via `state.sub` (experimental) |
514
+ | v0.4 | Sub-machine API promoted to stable; dependency-reduction cycle |
515
515
  | v0.5 | `aifsmjs-bridge-bitecs` / `aifsmjs-bridge-pixi` (separate sub-packages) |
516
516
  | v1.0 | API freeze and stability guarantee |
517
517
 
@@ -522,7 +522,7 @@ Example-first, PBT-augmented. Lesson from jssm: "3000+ tests / 100% coverage" tu
522
522
  - **Tick / game-loop hook** (out of scope for v1)
523
523
  - **ECS / Pixi bridges** (out of scope for v1)
524
524
 
525
- **v0.4 candidate**: `historyState` — remember last active sub-state on
525
+ **Future candidate**: `historyState` — remember last active sub-state on
526
526
  re-entry. Workaround today: snapshot via `onTransition` and restore
527
527
  manually.
528
528
 
@@ -543,6 +543,34 @@ All notable changes to this project will be documented in this file.
543
543
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
544
544
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
545
545
 
546
+ ## [0.4.0] — 2026-05-29
547
+
548
+ ### Changed
549
+
550
+ - **Sub-machine API promoted experimental → stable.** The hierarchical
551
+ sub-machine surface shipped in 0.3.0 — `StateDef.sub`, `StateDef.subImpl`,
552
+ `Runtime.subRuntime()`, `SubMachineError`, and the `SubMachineDef` type
553
+ alias — is now stable. Signatures and runtime semantics, including the
554
+ init-failure quarantine behaviour, are frozen for the 1.x line; the
555
+ boundaries documented in `STABILITY.md` are intentional design trade-offs,
556
+ not instability. **No signature changed from 0.3.x.**
557
+
558
+ ### Dependency reduction
559
+
560
+ - Part of the ai*js v0.4.0 dependency-reduction cycle. `fast-check` remains
561
+ an **optional** peer dependency isolated to the `aifsmjs/pbt` subpath. A
562
+ fresh build confirms the core entry and the five non-pbt subpaths
563
+ (`guards` / `effects` / `inspect` / `replay` / `timer`) are tree-shake-free
564
+ of `fast-check` (only `dist/pbt/index.js` references it). `pnpm audit`
565
+ reports zero advisories. No `devDependency` changes.
566
+
567
+ ### Compatibility
568
+
569
+ This release adds **no runtime API** and changes **no signature**. The core
570
+ bundle is byte-identical to 0.3.1 (gzip 4,387 B); existing 0.3.x consumer
571
+ code is unaffected. The only substantive change is the documented stability
572
+ tier of the sub-machine API.
573
+
546
574
  ## [0.3.1] — 2026-05-29
547
575
 
548
576
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aifsmjs",
3
- "version": "0.3.1",
3
+ "version": "0.4.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",