aifsmjs 0.2.0 → 0.3.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/llms-full.txt CHANGED
@@ -21,9 +21,9 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
21
21
 
22
22
  > A small, strict FSM library for any TypeScript/JS app that needs deterministic, replayable state transitions. Lifecycle is a pure `step()` function. Chain-of-Responsibility intuition is reserved for cross-cutting concerns (observe / persist / replay), never for the transition core.
23
23
 
24
- **Primary audience**: developers building stateful flows — multi-step forms, checkout funnels, auth flows, tutorial sequences, document-status workflows, scene flow in interactive apps, and the same patterns in browser-based games (PixiJS / Svelte 5 / plain Canvas / WebGL). The core is **environment-neutral** (pure function + adapter boundary): browser, Node, Bun, Deno, Flutter WebView, and Web Workers all work. The Roadmap section keeps gaming-specific niceties (tick hook, ECS bridge) as opt-in subpaths, not core surface.
24
+ Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) — see also [aibridgejs](https://github.com/yshengliao/aibridgejs) (cross-context RPC) and [aiecsjs](https://github.com/yshengliao/aiecsjs) (ECS).
25
25
 
26
- > Traditional Chinese version: [README_ZHTW.md](README_ZHTW.md).
26
+ **Primary audience**: developers building stateful flows — multi-step forms, checkout funnels, auth flows, tutorial sequences, document-status workflows, scene flow in interactive apps, and the same patterns in browser-based games (PixiJS / Svelte 5 / plain Canvas / WebGL). The core is **environment-neutral** (pure function + adapter boundary): browser, Node, Bun, Deno, Flutter WebView, and Web Workers all work. The Roadmap section keeps gaming-specific niceties (tick hook, ECS bridge) as opt-in subpaths, not core surface.
27
27
 
28
28
  ---
29
29
 
@@ -119,15 +119,16 @@ The three layers are fully decoupled: take `step()` alone for replay, take `Mach
119
119
 
120
120
  | Will do (v1) | Won't do |
121
121
  | --------------------------------------------------- | ------------------------------------------------- |
122
- | Flat states + transitions | Hierarchical / compound states |
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
124
  | Guards (sync only; inline async throws `InvalidDefinitionError` at `defineMachine`; runtime throws `AsyncGuardError` on thenable return) | Async guards |
124
125
  | Actions (assign + enqueue effects) | Async API inside an action (use an effect) |
125
126
  | Fire-and-forget effects | Actor invocation / spawn |
126
127
  | Read-only inspect middleware | Cancellable transition middleware |
127
128
  | `replay(initial, log, def, impl)` pure function | Time-travel debugger (v2 candidate) |
128
129
  | `fast-check` `fc.commands` adapter | Custom PBT framework |
129
- | String ref + runtime injection | Closures embedded in definition (breaks serialize) |
130
- | Tree-shake friendly subpath exports | Single root import for everything |
130
+ | String ref + runtime injection | Single root import for everything |
131
+ | Tree-shake friendly subpath exports | ECS / Pixi bridges (opt-in subpath, not core) |
131
132
 
132
133
  ---
133
134
 
@@ -369,6 +370,27 @@ Non-goals:
369
370
  - No async lifecycle hook
370
371
  - Inspect middleware cannot alter the transition outcome
371
372
 
373
+ ### Sub-machine lifecycle (since 0.3.0, experimental)
374
+
375
+ When a state declares `sub`, the per-transition ordering is:
376
+
377
+ 1. Parent `step()` runs: `exit actions → transition.actions → entry actions`.
378
+ 2. Old child (if any) `dispose()` — synchronous; exceptions become
379
+ `SubMachineError(phase: "dispose")`.
380
+ 3. New child (if next state has `sub`) instantiation — exceptions become
381
+ `SubMachineError(phase: "init")`.
382
+ 4. Parent snapshot commits.
383
+ 5. Middleware pipeline runs.
384
+ 6. Effects dispatch.
385
+ 7. `'transition'` event emits to `on()` / `onTransition()` subscribers.
386
+
387
+ If step 2 or 3 throws, the parent snapshot is **not** committed (rollback
388
+ to `prev`); no middleware / effects / `'transition'` runs.
389
+
390
+ `runtime.dispose()` cascades to the child via `controller.signal`'s abort
391
+ listener and an explicit `child.dispose()` call. Cascade swallows child
392
+ exceptions to honour the never-throws dispose contract.
393
+
372
394
  ---
373
395
 
374
396
  ## Lifecycle Protocol
@@ -451,7 +473,7 @@ Example-first, PBT-augmented. Lesson from jssm: "3000+ tests / 100% coverage" tu
451
473
  - **Example tests** (vitest): for every src module, write happy path + edge + error-message triplets.
452
474
  - **PBT smoke**: each generic property runs 50 iterations as an invariant guard, not as a coverage source.
453
475
  - **CI-enforced thresholds**: `@vitest/coverage-v8` is wired to **100% statements / 100% lines / 100% functions / ≥90% branches**. The few defensive invariant-guard branches (e.g. runtime determinism mismatch) carry `/* v8 ignore */` annotations with rationale.
454
- - **Size budget**: `scripts/check-size.mjs` enforces per-subpath gzip caps in CI — core ≤3 KB, replay ≤1.6 KB, pbt ≤4.5 KB, others ≤1 KB. Exceeding any cap fails the build.
476
+ - **Size budget**: `scripts/check-size.mjs` enforces per-subpath gzip caps in CI — core ≤4.7 KB (raised in 0.3.0 for sub-machine sugar), replay ≤1.8 KB, pbt ≤5.5 KB (raised in 0.3.0 because `pbt` transitively imports `createRuntime`), others ≤1 KB. Exceeding any cap fails the build.
455
477
 
456
478
  ### The 6 built-in generic properties
457
479
 
@@ -471,7 +493,7 @@ Example-first, PBT-augmented. Lesson from jssm: "3000+ tests / 100% coverage" tu
471
493
  | | aifsmjs | XState v5 | Robot3 | @xstate/store | Zag.js |
472
494
  | -------------------------- | -------------- | ----------------- | ----------------- | ----------------- | ----------------- |
473
495
  | Core size (gzip) | ~2.8KB | ~15KB | ~1KB | < 1KB | per-component |
474
- | Hierarchical states | No (v1) | Yes | No | N/A | Yes |
496
+ | Hierarchical states | Sugar (0.3.0) | Yes | No | N/A | Yes |
475
497
  | Async invoke / actor | No | Yes | No | N/A | No |
476
498
  | Guard combinators | and/or/not | and/or/not | No | N/A | No |
477
499
  | Effects dual-track | enqueue | enqueueActions | reduce/action | enq.effect() | array of names |
@@ -487,12 +509,23 @@ Example-first, PBT-augmented. Lesson from jssm: "3000+ tests / 100% coverage" tu
487
509
  | Version | Scope |
488
510
  | ------- | ------------------------------------------------------------------ |
489
511
  | v0.1 | core + guards + effects + inspect + replay + pbt (this release) |
490
- | v0.2 | Hierarchical / compound states, including entry/exit ordering |
491
- | v0.3 | Parallel state regions |
492
- | v0.4 | Actor invocation (async) and spawn |
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 |
493
515
  | v0.5 | `aifsmjs-bridge-bitecs` / `aifsmjs-bridge-pixi` (separate sub-packages) |
494
516
  | v1.0 | API freeze and stability guarantee |
495
517
 
518
+ **Out of scope (v1)**:
519
+
520
+ - **Parallel state regions** (out of scope for v1)
521
+ - **Actor invocation / spawn** (out of scope for v1)
522
+ - **Tick / game-loop hook** (out of scope for v1)
523
+ - **ECS / Pixi bridges** (out of scope for v1)
524
+
525
+ **v0.4 candidate**: `historyState` — remember last active sub-state on
526
+ re-entry. Workaround today: snapshot via `onTransition` and restore
527
+ manually.
528
+
496
529
  ---
497
530
 
498
531
  ## License
@@ -510,6 +543,73 @@ All notable changes to this project will be documented in this file.
510
543
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
511
544
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
512
545
 
546
+ ## [0.3.0] — 2026-05-29
547
+
548
+ ### Added
549
+
550
+ - **Hierarchical / sub-machine sugar** (experimental). `StateDef` accepts
551
+ two new optional fields: `sub` (a child `SubMachineDef`) and `subImpl`
552
+ (the child's `Implementations`). When the runtime enters a state with
553
+ `sub`, a child `Runtime` is lazily instantiated; when it exits, the child
554
+ is disposed. Access via `runtime.subRuntime()`. See `STABILITY.md` for
555
+ the experimental contract.
556
+ - New error: `SubMachineError` (`{ parentState, phase, cause }`) thrown
557
+ by `send()` / `reset()` on child init / dispose failure. `dispose()`
558
+ cascade swallows child dispose exceptions (idempotent + never-throws
559
+ contract).
560
+ - New type alias: `SubMachineDef<SubCtx, SubEvt, SubStates>`.
561
+ - **`runtime.onTransition(handler, opts?)`** — semantic sugar over
562
+ `runtime.on('transition', handler, opts)`. One-line delegation; shares
563
+ the same listener Set, so registration order across both APIs determines
564
+ invocation order. Returned unsubscribe identical to `on('transition', ...)`.
565
+
566
+ ### Stability
567
+
568
+ - New file: `STABILITY.md`. Documents the three tiers: **stable**
569
+ (everything shipped 0.1.0–0.2.1), **experimental** (`sub`, `subImpl`,
570
+ `subRuntime`, `SubMachineError`, `SubMachineDef`), **draft**
571
+ (`historyState`, v0.4 candidate).
572
+
573
+ ### Changed (positioning)
574
+
575
+ - **README "Capabilities / Limitations" table**: "Hierarchical / compound
576
+ states" moved out of the "Won't do" column. Added on the "Will do" side
577
+ as "Hierarchical sugar via `state.sub` (experimental since 0.3.0)".
578
+ - **README Lifecycle Invariants**: documented the sub-machine ordering —
579
+ parent `step()` lifecycle (exit / actions / entry) runs first, then
580
+ child dispose → child init → snapshot commit → middleware → effects →
581
+ `transition` emit.
582
+
583
+ ### Build & tooling
584
+
585
+ - **`scripts/check-size.mjs`**: core gzip budget raised 3,700 → 4,700 B
586
+ to absorb sub-machine lifecycle, `SubMachineError`, and `onTransition`
587
+ sugar (measured at 4,465 B). `pbt` budget raised 4,600 → 5,500 B because
588
+ `pbt/properties.ts` imports `createRuntime` from `runtime.ts`; the new
589
+ sub-machine code is pulled in transitively (measured at 5,228 B).
590
+
591
+ ### Compatibility
592
+
593
+ This release is **non-breaking** for v0.2.1 callers who do not opt into
594
+ the new sub-machine fields. All existing API signatures, error types, and
595
+ runtime behaviour are byte-identical.
596
+
597
+ ## [0.2.1] — 2026-05-28
598
+
599
+ ### Security
600
+
601
+ - **Resolve two Dependabot moderate advisories** on the transitive dev-only graph by upgrading `vitest` 2.1.0 → 4.1.7 and `@vitest/coverage-v8` 2.1.9 → 4.1.7. Adds `vite` 8.0.14 as a direct devDependency to satisfy vitest 4's peer range (`^6 || ^7 || ^8`). These are dev-only — runtime surface unchanged. Same fix as `aibridgejs` 0.1.2.
602
+ - [GHSA-67mh-4wv8-2f99](https://github.com/advisories/GHSA-67mh-4wv8-2f99) `esbuild <=0.24.2` CORS development server data leak (fixed in 0.25.0).
603
+ - [GHSA-4w7w-66w2-5vf9](https://github.com/advisories/GHSA-4w7w-66w2-5vf9) `vite <=6.4.1` path traversal in optimized deps `.map` handling (fixed in 6.4.2 / 7.3.2 / 8.0.5).
604
+
605
+ ### Changed
606
+
607
+ - **Coverage threshold relaxed**: statements 100 → 95 in [vitest.config.ts](vitest.config.ts). Vitest 4 with v8 coverage scores defensive race-recovery if-guards (e.g. `if (!current) return;` in timeout/abort handlers) as separate statements that are not deterministically reachable. Lines and functions stay at 100%; branches stays at 90%.
608
+ - **`prepublishOnly` now includes `verify:llms`** so llms-full.txt drift is caught at publish time as well as CI.
609
+ - **README opening unified across the ai*js family**: five-badge shields row, one-line tagline as blockquote, ecosystem footer.
610
+
611
+ Runtime surface unchanged. Production bundles are byte-identical to 0.2.0.
612
+
513
613
  ## [0.2.0] — 2026-05-28
514
614
 
515
615
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aifsmjs",
3
- "version": "0.2.0",
3
+ "version": "0.3.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",
@@ -74,6 +74,7 @@
74
74
  "dist",
75
75
  "README.md",
76
76
  "README_ZHTW.md",
77
+ "STABILITY.md",
77
78
  "LICENSE",
78
79
  "llms.txt",
79
80
  "llms-full.txt"
@@ -94,7 +95,7 @@
94
95
  "example:approval": "tsx examples/02-approval-workflow/index.ts",
95
96
  "example:checkout-funnel": "tsx examples/03-checkout-funnel/index.ts",
96
97
  "example:form-wizard": "tsx examples/04-form-wizard/index.ts",
97
- "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm coverage && pnpm build && pnpm verify:exports && pnpm check:size"
98
+ "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm coverage && pnpm build && pnpm verify:exports && pnpm verify:llms && pnpm check:size"
98
99
  },
99
100
  "peerDependencies": {
100
101
  "fast-check": "^3.20.0"
@@ -107,12 +108,13 @@
107
108
  "devDependencies": {
108
109
  "@biomejs/biome": "^1.9.0",
109
110
  "@types/node": "^22.0.0",
110
- "@vitest/coverage-v8": "^2.1.9",
111
+ "@vitest/coverage-v8": "^4.1.7",
111
112
  "fast-check": "^3.20.0",
112
113
  "tsup": "^8.3.0",
113
114
  "tsx": "^4.22.3",
114
115
  "typescript": "^5.6.0",
115
- "vitest": "^2.1.0"
116
+ "vite": "^8.0.14",
117
+ "vitest": "^4.1.7"
116
118
  },
117
119
  "engines": {
118
120
  "node": ">=18.0.0"