aifsmjs 0.5.1 → 0.5.2
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 +15 -4
- package/README_ZHTW.md +16 -5
- package/llms-full.txt +21 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -207,7 +207,7 @@ function step<C, E, S>(
|
|
|
207
207
|
): { snapshot: Snapshot<C, S>; effects: readonly Effect[] };
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
**Pure function**. The invariant keeper for the whole library. It never dispatches effects
|
|
210
|
+
**Pure function**. The invariant keeper for the whole library. It never dispatches effects and never mutates the snapshot. A failing guard or unmapped event simply returns the original snapshot unchanged. It does throw on misuse (`UnknownGuardError`, `UnknownActionError`, `AsyncGuardError`) so guard/action wiring errors surface at development time rather than silently passing.
|
|
211
211
|
|
|
212
212
|
### `assign(updater)`
|
|
213
213
|
|
|
@@ -266,7 +266,7 @@ const runtime = createRuntime(def, impl, {
|
|
|
266
266
|
});
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
Koa-style `(ctx, next) => void` pipeline. `ctx` is `{ prev, next, event, effects }`, all
|
|
269
|
+
Koa-style `(ctx, next) => void` pipeline. `ctx` is `{ prev, next, event, effects, changed }`, all deep-frozen. **Cannot cancel a transition** — `next()` must be called; the return value carries no meaning.
|
|
270
270
|
|
|
271
271
|
### `aifsmjs/replay` — Pure event log replay
|
|
272
272
|
|
|
@@ -285,14 +285,25 @@ Never dispatches effects. For PBT, time-travel debugging, and incident reproduct
|
|
|
285
285
|
|
|
286
286
|
```typescript
|
|
287
287
|
import fc from "fast-check";
|
|
288
|
-
import {
|
|
288
|
+
import { createRuntime } from "aifsmjs";
|
|
289
|
+
import { commandsFromMachine, initialModel, properties } from "aifsmjs/pbt";
|
|
290
|
+
|
|
291
|
+
// Use one of the six built-in generic properties, or assertAll for all at once:
|
|
292
|
+
properties.replayEqualsFold(def, impl, {
|
|
293
|
+
NEXT: fc.constant({ type: "NEXT" as const }),
|
|
294
|
+
});
|
|
289
295
|
|
|
296
|
+
// Or build a custom property using commandsFromMachine:
|
|
290
297
|
fc.assert(
|
|
291
298
|
fc.property(
|
|
292
299
|
commandsFromMachine(def, impl, {
|
|
293
300
|
NEXT: fc.constant({ type: "NEXT" as const }),
|
|
294
301
|
}),
|
|
295
|
-
(cmds) =>
|
|
302
|
+
(cmds) => {
|
|
303
|
+
const real = createRuntime(def, impl, { dispatchEffects: false });
|
|
304
|
+
fc.modelRun(() => ({ model: initialModel(def), real }), cmds);
|
|
305
|
+
return true;
|
|
306
|
+
},
|
|
296
307
|
),
|
|
297
308
|
);
|
|
298
309
|
```
|
package/README_ZHTW.md
CHANGED
|
@@ -206,7 +206,7 @@ function step<C, E, S>(
|
|
|
206
206
|
): { snapshot: Snapshot<C, S>; effects: readonly Effect[] };
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
-
**Pure function**。整個 library 的 invariant 守護者。不會 dispatch effects、不會 mutate snapshot
|
|
209
|
+
**Pure function**。整個 library 的 invariant 守護者。不會 dispatch effects、不會 mutate snapshot。Guard 沒過或 event 沒對應 transition 就回原 snapshot(不改變)。誤用(`UnknownGuardError`、`UnknownActionError`、`AsyncGuardError`)才會丟錯,讓配接錯誤在開發期間立即浮現、不被靜默略過。
|
|
210
210
|
|
|
211
211
|
### `assign(updater)`
|
|
212
212
|
|
|
@@ -265,7 +265,7 @@ const runtime = createRuntime(def, impl, {
|
|
|
265
265
|
});
|
|
266
266
|
```
|
|
267
267
|
|
|
268
|
-
Koa-style `(ctx, next) => void` pipeline。`ctx` 是 `{ prev, next, event, effects }`,全部
|
|
268
|
+
Koa-style `(ctx, next) => void` pipeline。`ctx` 是 `{ prev, next, event, effects, changed }`,全部 deep-frozen。**不能中止 transition**——`next()` 必呼叫,回傳值無語意。
|
|
269
269
|
|
|
270
270
|
### `aifsmjs/replay` — Pure event log replay
|
|
271
271
|
|
|
@@ -284,14 +284,25 @@ const finalSnap = replay(initialSnapshot, eventLog, def, impl);
|
|
|
284
284
|
|
|
285
285
|
```typescript
|
|
286
286
|
import fc from "fast-check";
|
|
287
|
-
import {
|
|
287
|
+
import { createRuntime } from "aifsmjs";
|
|
288
|
+
import { commandsFromMachine, initialModel, properties } from "aifsmjs/pbt";
|
|
289
|
+
|
|
290
|
+
// 用任一內建 generic property,或 assertAll 一次跑全部:
|
|
291
|
+
properties.replayEqualsFold(def, impl, {
|
|
292
|
+
NEXT: fc.constant({ type: "NEXT" as const }),
|
|
293
|
+
});
|
|
288
294
|
|
|
295
|
+
// 或用 commandsFromMachine 自訂 property:
|
|
289
296
|
fc.assert(
|
|
290
297
|
fc.property(
|
|
291
298
|
commandsFromMachine(def, impl, {
|
|
292
299
|
NEXT: fc.constant({ type: "NEXT" as const }),
|
|
293
300
|
}),
|
|
294
|
-
(cmds) =>
|
|
301
|
+
(cmds) => {
|
|
302
|
+
const real = createRuntime(def, impl, { dispatchEffects: false });
|
|
303
|
+
fc.modelRun(() => ({ model: initialModel(def), real }), cmds);
|
|
304
|
+
return true;
|
|
305
|
+
},
|
|
295
306
|
),
|
|
296
307
|
);
|
|
297
308
|
```
|
|
@@ -398,7 +409,7 @@ aifsmjs 在幾個常見議題上做了刻意取捨,跟主流 FSM library 寫
|
|
|
398
409
|
|
|
399
410
|
- **`send()` 是同步、回傳 `Snapshot` 而非 `Promise<Snapshot>`**。Pure `step()` 設計上就是 sync,`replay(initial, log)` 與 PBT shrinking 才能單純。Effect handler 仍可 async;runtime 觸發後忽略結果,async rejection 走 `'error'` event channel。要 await effect 完成的人可自己包一層 `Promise.all`。
|
|
400
411
|
- **Guards / reducers 只能 sync**。非確定性 guard 會破壞 PBT determinism property (#1)。把 async 改寫成 event:先送 `FETCH_REQUEST`,handler 完成後送 `FETCH_DONE`,payload 帶結果。
|
|
401
|
-
- **Effects 是描述子,不是 inline callback**。Action 透過 `enqueue.effect(
|
|
412
|
+
- **Effects 是描述子,不是 inline callback**。Action 透過 `enqueue.effect(type, payload?)` 排隊;runtime 收集後 dispatcher 才執行 user handler。好處:machine definition 可序列化(沒 inline fn 時 JSON round-trip)、`replay()` 可把 event log 摺成同樣 snapshot、`inspect/persist` middleware 抓得到 effects 做 audit log。
|
|
402
413
|
- **兩種 factory 並存**。`setup<Ctx, Evt>().defineMachine(...)` 是型別友善版(States 從 `keyof states` 推導)。`createMachine(def, impl, opts?)` 是來自 ai*js 生態 spec 的 single-factory 捷徑。顯式 `defineMachine<Ctx, Evt, States>(def)` 仍保留作完全顯式控制。看 call site 哪個讀起來順手就用哪個。
|
|
403
414
|
- **`subscribe(listener)` 與 `on(type, fn, { signal, once })` 並存**。Typed `on()` 對齊平台 `EventTarget` 語意(signal + once),emit `'transition'`、`'error'`、`'dispose'`。原本的 `subscribe()` 保留 React `useSyncExternalStore` shape,可直接傳。兩者不互斥。
|
|
404
415
|
|
package/llms-full.txt
CHANGED
|
@@ -220,7 +220,7 @@ function step<C, E, S>(
|
|
|
220
220
|
): { snapshot: Snapshot<C, S>; effects: readonly Effect[] };
|
|
221
221
|
```
|
|
222
222
|
|
|
223
|
-
**Pure function**. The invariant keeper for the whole library. It never dispatches effects
|
|
223
|
+
**Pure function**. The invariant keeper for the whole library. It never dispatches effects and never mutates the snapshot. A failing guard or unmapped event simply returns the original snapshot unchanged. It does throw on misuse (`UnknownGuardError`, `UnknownActionError`, `AsyncGuardError`) so guard/action wiring errors surface at development time rather than silently passing.
|
|
224
224
|
|
|
225
225
|
### `assign(updater)`
|
|
226
226
|
|
|
@@ -279,7 +279,7 @@ const runtime = createRuntime(def, impl, {
|
|
|
279
279
|
});
|
|
280
280
|
```
|
|
281
281
|
|
|
282
|
-
Koa-style `(ctx, next) => void` pipeline. `ctx` is `{ prev, next, event, effects }`, all
|
|
282
|
+
Koa-style `(ctx, next) => void` pipeline. `ctx` is `{ prev, next, event, effects, changed }`, all deep-frozen. **Cannot cancel a transition** — `next()` must be called; the return value carries no meaning.
|
|
283
283
|
|
|
284
284
|
### `aifsmjs/replay` — Pure event log replay
|
|
285
285
|
|
|
@@ -298,14 +298,25 @@ Never dispatches effects. For PBT, time-travel debugging, and incident reproduct
|
|
|
298
298
|
|
|
299
299
|
```typescript
|
|
300
300
|
import fc from "fast-check";
|
|
301
|
-
import {
|
|
301
|
+
import { createRuntime } from "aifsmjs";
|
|
302
|
+
import { commandsFromMachine, initialModel, properties } from "aifsmjs/pbt";
|
|
303
|
+
|
|
304
|
+
// Use one of the six built-in generic properties, or assertAll for all at once:
|
|
305
|
+
properties.replayEqualsFold(def, impl, {
|
|
306
|
+
NEXT: fc.constant({ type: "NEXT" as const }),
|
|
307
|
+
});
|
|
302
308
|
|
|
309
|
+
// Or build a custom property using commandsFromMachine:
|
|
303
310
|
fc.assert(
|
|
304
311
|
fc.property(
|
|
305
312
|
commandsFromMachine(def, impl, {
|
|
306
313
|
NEXT: fc.constant({ type: "NEXT" as const }),
|
|
307
314
|
}),
|
|
308
|
-
(cmds) =>
|
|
315
|
+
(cmds) => {
|
|
316
|
+
const real = createRuntime(def, impl, { dispatchEffects: false });
|
|
317
|
+
fc.modelRun(() => ({ model: initialModel(def), real }), cmds);
|
|
318
|
+
return true;
|
|
319
|
+
},
|
|
309
320
|
),
|
|
310
321
|
);
|
|
311
322
|
```
|
|
@@ -545,6 +556,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
545
556
|
|
|
546
557
|
## [Unreleased]
|
|
547
558
|
|
|
559
|
+
## [0.5.2] - 2026-06-05
|
|
560
|
+
|
|
561
|
+
### Docs
|
|
562
|
+
|
|
563
|
+
- Review-driven documentation fixes (`README.md`, `README_ZHTW.md`, `llms-full.txt`): clarity and accuracy from a cross-package code review. No runtime or API change; `dist` byte-identical to 0.5.1.
|
|
564
|
+
|
|
548
565
|
## [0.5.1] - 2026-06-02
|
|
549
566
|
|
|
550
567
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aifsmjs",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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",
|