@turing-machine-js/machine 6.1.0 → 6.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/README.md CHANGED
@@ -364,7 +364,7 @@ Both APIs are first-class — `run()` is built on top of `runStepByStep()` (see
364
364
 
365
365
  **Rule of thumb.** If your consumer reads `state.debug` and expects the engine to act on it (pause, fire callbacks), use `run()`. If you want pull-based iteration with full control over timing, use `runStepByStep()` — the `debugBreak` field is still on every yield, so you can inspect breakpoint metadata yourself.
366
366
 
367
- **Don't split one logical flow across both APIs.** A consumer that wants stepwise UI *and* hook-driven breakpoints should use `run({ onStep, onPause, debug })` exclusively, implementing "wait between steps" by awaiting a resolvable Promise inside `onStep`. Routing some operations through `runStepByStep()` and others through `run()` means `state.debug` only flows through one of the two paths — a subtle footgun where breakpoints silently disappear on whichever code path uses the generator directly.
367
+ **Don't split one logical flow across both APIs.** A consumer that wants stepwise UI *and* hook-driven breakpoints should use `run({ onStep, onPause, debug })` exclusively. Routing some operations through `runStepByStep()` and others through `run()` means `state.debug` only flows through one of the two paths — a subtle footgun where breakpoints silently disappear on whichever code path uses the generator directly. For per-iter throttle / "wait between steps" UIs, see [Throttle pattern](#throttle-pattern).
368
368
 
369
369
  ## Subroutine composition with `withOverrodeHaltState`
370
370
 
@@ -516,6 +516,37 @@ If `onPause` is not provided, breaks fire-and-resume invisibly — the trajector
516
516
 
517
517
  **Caveat:** `haltState` is a module-level singleton. Setting `haltState.debug` affects every machine in the process; clear in `afterEach` / `finally` for test isolation.
518
518
 
519
+ ### Throttle pattern
520
+
521
+ For per-iter throttle / animation / "wait between steps" UIs, do the await inside `onPause`, not `onStep` — `onPause` is the engine's awaited callback. To make it fire on every iter (no user-set breakpoints needed), arm `.after = true` on the initial state and rearm `m.nextState.debug.after = true` inside the callback. The chain is self-propagating:
522
+
523
+ ```ts
524
+ initialState.debug.after = true; // first pause-point
525
+
526
+ await machine.run({
527
+ initialState,
528
+ debug: true,
529
+ onPause: async (m) => {
530
+ // m.debugBreak.after is true here on every iter.
531
+ await new Promise((r) => setTimeout(r, intervalMs)); // throttle
532
+
533
+ // Rearm for the next iter (engine processes m.nextState next).
534
+ // `state.debug` is shared across `withOverrodeHaltState` wrappers — a
535
+ // single arm reaches all of them.
536
+ if (!m.nextState.isHalt) m.nextState.debug.after = true;
537
+ },
538
+ });
539
+ ```
540
+
541
+ A few details:
542
+
543
+ - **Halting iter**: `m.nextState === haltState` on the last user iter. The example skips the rearm there. The halting iter's own `.after` already fired this callback (engine v5+ fixed [#108](https://github.com/mellonis/turing-machine-js/issues/108) part 1).
544
+ - **`haltState.debug.after`**: throws on assignment (#108 part 2). Don't try to rearm onto haltState itself — pause for halt by checking `m.nextState.isHalt` in the callback before the rearm step.
545
+ - **Coexists with user-authored breaks**: if a user state also has `.before = true` set by the consumer's own code, `onPause` fires twice on that iter (before + after). Tell them apart with `m.debugBreak`.
546
+ - **Click-pause** during throttling: keep a flag set from the outside; check it inside `onPause` before the `setTimeout` and surface a "real" pause (await a resolvable Promise the UI controls) instead of the throttle one. The engine doesn't need to know — it just sees a longer awaited `onPause`.
547
+
548
+ (v6.2.0 briefly widened `onStep` to async to enable a throttle-in-onStep pattern. That was a mistake — restored to sync in v6.3.0. The rearm-via-`onPause` pattern above is the engine-native shape.)
549
+
519
550
  ## Special objects
520
551
 
521
552
  ### haltState
@@ -590,6 +621,8 @@ API surface changes since v3, in past tense so the timing of each piece is expli
590
621
  - **v5** — `onDebugBreak` renamed to `onPause`. New `run({ debug: boolean })` master switch suppresses all `onPause` dispatches without unsetting `state.debug` assignments. Assigning a truthy `.after` to `haltState.debug` now throws at write time (halt is terminal — no iteration-after-halt to anchor on).
591
622
  - **v6** — Per-iter lifecycle reordered to `before → step → after`, all firing on the same yield. Previously `after` fired on iter K+1's tick with a `prevYield` substitution dance; that substitution is gone. The `MachineState.debugBreak` field shape is unchanged across all three versions.
592
623
  - **v6.1** — `state.debug` ergonomics: the field is now always a non-null `DebugConfig` instance (lazy-initialized on first read), so chained field writes like `state.debug.before = true` work on a fresh state without a prior whole-object assignment. The `DebugConfig` instance is `Object.seal`-ed, so typos like `state.debug.bofore = true` throw `TypeError` at write time instead of silently creating a useless property. `state.debug = null` continues to work but semantically means "reset filters" — the next read returns a fresh empty `DebugConfig` (#150).
624
+ - **v6.2** *(superseded by v6.3.0)* — widened `onStep`'s signature to `(m) => void | Promise<void>` and added an inline `await onStep(...)` in the run loop, enabling throttle-in-`onStep` patterns. This overturned the docstring-stated contract that `onStep` is sync (microtask-free); the right place for per-iter throttling is `onPause` with self-rearm (see [Throttle pattern](#throttle-pattern)). Restored in v6.3.0.
625
+ - **v6.3** — `onStep` reverted to its v6.0–v6.1 sync contract — `(m) => void`, called synchronously inside the run loop. The Throttle pattern section documents the engine-native shape for per-iter throttle / "wait between iters" UIs. No other API changes.
593
626
 
594
627
  For the full release history, see the [GitHub releases page](https://github.com/mellonis/turing-machine-js/releases).
595
628
 
@@ -38,6 +38,17 @@ export default class TuringMachine {
38
38
  * Sync, ~free hook fired on every iteration. Use for logging/tracing —
39
39
  * the hot loop runs this without a microtask boundary, so it must not
40
40
  * be async.
41
+ *
42
+ * For per-iter throttle / coordination ("wait between iters" UIs):
43
+ * arm `state.debug.after = true` on each visited state and do the
44
+ * throttle inside `onPause` (which IS awaited). See the README's
45
+ * Throttle pattern section for a worked example.
46
+ *
47
+ * (v6.2.0 widened this to `void | Promise<void>` and added an inline
48
+ * `await`. That was a mistake — it drove the awaited contract into a
49
+ * hook that was deliberately documented as sync. Restored in v6.3.0;
50
+ * consumers needing per-iter await belong on the `onPause`-rearm
51
+ * pattern, not this hook.)
41
52
  */
42
53
  onStep?: (machineState: MachineState) => void;
43
54
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turing-machine-js/machine",
3
- "version": "6.1.0",
3
+ "version": "6.3.0",
4
4
  "description": "A convenient Turing machine",
5
5
  "engines": {
6
6
  "npm": ">=7.0.0"
@@ -23,6 +23,10 @@
23
23
  "turing",
24
24
  "machine"
25
25
  ],
26
+ "scripts": {
27
+ "build": "tsc --build --verbose tsconfig.build.json && node ../../scripts/build-node-entries.mjs --package=@turing-machine-js/machine",
28
+ "prepublishOnly": "npm run build"
29
+ },
26
30
  "main": "dist/index.cjs",
27
31
  "module": "dist/index.mjs",
28
32
  "types": "dist/index.d.ts",
@@ -34,5 +38,5 @@
34
38
  "default": "./dist/index.mjs"
35
39
  }
36
40
  },
37
- "gitHead": "5a3e3ced5bacd541fefdf087f5b4301267be01d2"
41
+ "gitHead": "6be43b1e88c4c562c9c11b495b285ff114f86b37"
38
42
  }