aifsmjs 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/README_ZHTW.md +3 -3
- package/dist/guards/index.cjs.map +1 -1
- package/dist/guards/index.js.map +1 -1
- package/dist/index.cjs +60 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +59 -10
- package/dist/index.js.map +1 -1
- package/dist/pbt/index.cjs +53 -9
- package/dist/pbt/index.cjs.map +1 -1
- package/dist/pbt/index.js +53 -9
- package/dist/pbt/index.js.map +1 -1
- package/dist/replay/index.cjs +26 -1
- package/dist/replay/index.cjs.map +1 -1
- package/dist/replay/index.js +26 -1
- package/dist/replay/index.js.map +1 -1
- package/llms-full.txt +72 -8
- package/package.json +5 -3
package/llms-full.txt
CHANGED
|
@@ -19,9 +19,9 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
|
|
|
19
19
|
[](https://www.anthropic.com/claude-code)
|
|
20
20
|
[](README_ZHTW.md)
|
|
21
21
|
|
|
22
|
-
> A small, strict FSM library
|
|
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
|
|
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.
|
|
25
25
|
|
|
26
26
|
> Traditional Chinese version: [README_ZHTW.md](README_ZHTW.md).
|
|
27
27
|
|
|
@@ -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 | Hierarchical / compound states |
|
|
123
|
-
| Guards (sync only)
|
|
123
|
+
| Guards (sync only; inline async throws `InvalidDefinitionError` at `defineMachine`; runtime throws `AsyncGuardError` on thenable return) | Async guards |
|
|
124
124
|
| Actions (assign + enqueue effects) | Async API inside an action (use an effect) |
|
|
125
125
|
| Fire-and-forget effects | Actor invocation / spawn |
|
|
126
126
|
| Read-only inspect middleware | Cancellable transition middleware |
|
|
@@ -510,6 +510,66 @@ All notable changes to this project will be documented in this file.
|
|
|
510
510
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
511
511
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
512
512
|
|
|
513
|
+
## [0.2.0] — 2026-05-28
|
|
514
|
+
|
|
515
|
+
### Added
|
|
516
|
+
|
|
517
|
+
- **Async-guard detection**: `evalGuard` and `defineMachine`'s validation pass now throw on async guards. TypeScript already prevents the typed case, but a JS caller or a cast could slip an async guard through and silently pass every check (a thenable is truthy). The new check fails loudly:
|
|
518
|
+
- **Definition time** (inline `async` guard) → `InvalidDefinitionError` from `defineMachine`'s `validateDefinition`.
|
|
519
|
+
- **Runtime** (string-ref or cast guard whose return value is thenable) → `AsyncGuardError` from `evalGuard`. The thenable check uses `typeof x?.then === "function"`, so cross-realm Promises (iframe / worker / vm) and user-defined PromiseLike values are also caught — not just same-realm `instanceof Promise`.
|
|
520
|
+
- New exports from `aifsmjs`: `AsyncGuardError`, `isAsyncGuardFn`.
|
|
521
|
+
- README's "Capabilities / Limitations" table updated to reflect the new runtime guarantee.
|
|
522
|
+
|
|
523
|
+
### Fixed (correctness)
|
|
524
|
+
|
|
525
|
+
- **`send()` transition payload AND `notify()` listeners are captured pre-reentry** ([src/fsm/runtime.ts](src/fsm/runtime.ts)): the `next` field of the emitted `'transition'` event, the effect dispatch context, and the snapshot delivered to `subscribe()` listeners are all now read from a captured local `committed` snapshot rather than the outer mutable `snapshot` variable. Closes a race where a reentrant `send()` inside an effect handler or subscriber would race ahead and the outer payload / later subscribers in the same notify pass would end up pointing at the reentry's snapshot.
|
|
526
|
+
- **`evalGuard` falls back to `<inline>` for anonymous-arrow guards** ([src/fsm/evaluator.ts](src/fsm/evaluator.ts)): switched `??` to `||` so an empty `Function.prototype.name` falls back instead of producing `guard "" must be sync;`.
|
|
527
|
+
- **README + README_ZHTW + llms-full.txt** now describe the two error paths separately (`InvalidDefinitionError` at definition time vs `AsyncGuardError` at runtime) instead of conflating them.
|
|
528
|
+
|
|
529
|
+
### Changed (positioning + meta)
|
|
530
|
+
|
|
531
|
+
- **`package.json#description`** rewritten from «for web game development» to lead with the broader use case set (multi-step forms, checkout funnels, auth flows, tutorials, scene flow). The README's "Primary audience" paragraph already moved away from game-only framing in v0.2.0; the package metadata now matches.
|
|
532
|
+
|
|
533
|
+
### Build & tooling
|
|
534
|
+
|
|
535
|
+
- **`verify:llms` is now build-agnostic** ([scripts/build-llms-full.mjs](scripts/build-llms-full.mjs)): the script accepts `--check` which builds the file in memory and compares against disk, exit 1 on diff. The previous form used `git diff --exit-code -- llms-full.txt` after running the build, which failed any time the working tree had uncommitted changes (not just llms-full.txt drift). The new form works identically pre-commit and in CI.
|
|
536
|
+
- **Per-subpath gzip budgets raised** ([scripts/check-size.mjs](scripts/check-size.mjs)): core 3500 → 3700 B and replay 1600 → 1800 B to absorb the AsyncGuardError + thenable detection cost; pbt 4500 → 4600 B for a small symbol additions. All entries still tracked at ≥95% headroom.
|
|
537
|
+
|
|
538
|
+
### Added (examples)
|
|
539
|
+
|
|
540
|
+
- `examples/03-checkout-funnel` — e-commerce checkout funnel with guarded staging, payment / analytics effects, and a `replay()` round-trip. Demonstrates that aifsmjs models classic web UX flows with no canvas / game loop involvement.
|
|
541
|
+
- `examples/04-form-wizard` — multi-step form wizard with back / next / jump-to-step navigation, per-step validation, and draft persistence via the `persist` middleware.
|
|
542
|
+
|
|
543
|
+
### Changed (positioning)
|
|
544
|
+
|
|
545
|
+
- `README.md` and `README_ZHTW.md`'s "Primary audience" paragraph now leads with stateful web flows (multi-step forms, checkout funnels, auth flows, tutorials, document workflows) and frames games as one application of the same pattern. The core remains environment-neutral; the only opt-in dependency is `fast-check` for the `aifsmjs/pbt` PBT adapter.
|
|
546
|
+
|
|
547
|
+
### Compatibility
|
|
548
|
+
|
|
549
|
+
This release is **non-breaking at runtime** for users who already wrote sync guards. Async guards previously slipped through and silently passed; they now throw. If you relied on this accidental behaviour, move the async work into an effect (`enq.effect(...)`) and dispatch a follow-up event when the work completes — the pattern is documented in the README's "Common pitfalls" table.
|
|
550
|
+
|
|
551
|
+
## [0.1.2] — 2026-05-28
|
|
552
|
+
|
|
553
|
+
### Fixed
|
|
554
|
+
|
|
555
|
+
- **Memory**: `runtime.on(type, fn, { signal })` previously left an
|
|
556
|
+
abort listener attached to the external `AbortSignal` after
|
|
557
|
+
`runtime.dispose()`. The listener (and its closure over the user's
|
|
558
|
+
callback) was retained until the signal eventually fired or was
|
|
559
|
+
garbage-collected. `dispose()` now removes each abort listener from
|
|
560
|
+
the signal it was attached to, and the unsubscribe function returned
|
|
561
|
+
by `on()` does the same on manual unsubscribe.
|
|
562
|
+
|
|
563
|
+
### Internal
|
|
564
|
+
|
|
565
|
+
- Narrowed `dispatchEffects` event parameter from `Evt | ResetEvent`
|
|
566
|
+
to `Evt`; the function is only reached via `send()`. Removed the
|
|
567
|
+
corresponding `as Evt` cast.
|
|
568
|
+
- Removed a redundant `snapshot.context as Ctx` cast in `step()`.
|
|
569
|
+
|
|
570
|
+
No public API changes; existing 0.1.1 callers run unchanged. Core gzip
|
|
571
|
+
3296 B → 3401 B (97% of 3500 B budget).
|
|
572
|
+
|
|
513
573
|
## [0.1.1] — 2026-05-28
|
|
514
574
|
|
|
515
575
|
### Changed
|
|
@@ -738,16 +798,20 @@ package).
|
|
|
738
798
|
|
|
739
799
|
```bash
|
|
740
800
|
pnpm install
|
|
741
|
-
pnpm example:traffic-light
|
|
742
|
-
pnpm example:approval
|
|
801
|
+
pnpm example:traffic-light # 01-traffic-light
|
|
802
|
+
pnpm example:approval # 02-approval-workflow
|
|
803
|
+
pnpm example:checkout-funnel # 03-checkout-funnel
|
|
804
|
+
pnpm example:form-wizard # 04-form-wizard
|
|
743
805
|
```
|
|
744
806
|
|
|
745
807
|
## Index
|
|
746
808
|
|
|
747
|
-
| # | Example | What it shows |
|
|
809
|
+
| # | Example | What it shows | Real-world pattern |
|
|
748
810
|
|---|---|---|---|
|
|
749
|
-
| 01 | [traffic-light](01-traffic-light/index.ts) | Minimal `setup → defineMachine → createRuntime → send` loop with `assign` and a snapshot subscriber. | Cyclic scene flow (loading → menu → playing → result → loading) |
|
|
750
|
-
| 02 | [approval-workflow](02-approval-workflow/index.ts) | Multi-candidate guarded transitions (`and([...])`), effects + handlers, `persist` + `recorder` middleware, and `replay()` to reproduce the final snapshot. |
|
|
811
|
+
| 01 | [traffic-light](01-traffic-light/index.ts) | Minimal `setup → defineMachine → createRuntime → send` loop with `assign` and a snapshot subscriber. | Cyclic scene flow (loading → menu → playing → result → loading) — or any 3-state cycle. |
|
|
812
|
+
| 02 | [approval-workflow](02-approval-workflow/index.ts) | Multi-candidate guarded transitions (`and([...])`), effects + handlers, `persist` + `recorder` middleware, and `replay()` to reproduce the final snapshot. | Document approval, ticket triage, turn-based games with branching outcomes + post-mortem replay. |
|
|
813
|
+
| 03 | [checkout-funnel](03-checkout-funnel/index.ts) | E-commerce checkout: cart → shipping → payment → review → confirmed. Per-stage validation as guards, payment + analytics as effects, full replay of the funnel. | Plain web app — no canvas, no game loop. Demonstrates that aifsmjs models classic UX funnels. |
|
|
814
|
+
| 04 | [form-wizard](04-form-wizard/index.ts) | Multi-step form wizard with back / next / jump-to-step navigation. Per-step validation, draft persistence via the `persist` middleware. | Account onboarding, settings editor, multi-page survey. |
|
|
751
815
|
|
|
752
816
|
## Notes
|
|
753
817
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aifsmjs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Small, strict FSM library for
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
7
|
"state-machine",
|
|
@@ -88,10 +88,12 @@
|
|
|
88
88
|
"verify:exports": "node scripts/verify-exports.mjs",
|
|
89
89
|
"check:size": "node scripts/check-size.mjs",
|
|
90
90
|
"build:llms": "node scripts/build-llms-full.mjs",
|
|
91
|
-
"verify:llms": "node scripts/build-llms-full.mjs
|
|
91
|
+
"verify:llms": "node scripts/build-llms-full.mjs --check",
|
|
92
92
|
"coverage": "vitest run --coverage",
|
|
93
93
|
"example:traffic-light": "tsx examples/01-traffic-light/index.ts",
|
|
94
94
|
"example:approval": "tsx examples/02-approval-workflow/index.ts",
|
|
95
|
+
"example:checkout-funnel": "tsx examples/03-checkout-funnel/index.ts",
|
|
96
|
+
"example:form-wizard": "tsx examples/04-form-wizard/index.ts",
|
|
95
97
|
"prepublishOnly": "pnpm typecheck && pnpm lint && pnpm coverage && pnpm build && pnpm verify:exports && pnpm check:size"
|
|
96
98
|
},
|
|
97
99
|
"peerDependencies": {
|