@turing-machine-js/machine 5.0.0 → 6.0.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/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [6.0.0] - 2026-05-09
8
+
9
+ ### Changed (BREAKING)
10
+
11
+ - **`onPause` dispatch order rewritten** ([#119](https://github.com/mellonis/turing-machine-js/issues/119)). `onPause(after, K)` now fires on iter K's *own* yield, alongside `onPause(before, K)` and `onStep(K)` — instead of v5's behavior of firing on iter K+1's yield with a `prevYield` substitution. The set of dispatched calls per run and per-iter semantics are unchanged; only the cross-hook *sequence* differs. Per-iter lifecycle inside `run()` is now `before → step → after`. Consumers using both `onStep` and `onPause` and asserting cross-hook ordering must update; consumers treating the hooks as independent observers see no change.
12
+
13
+ Subordinate consequences:
14
+ - `onPause(after)` carries the iteration's own `MachineState` directly — no `prevYield` substitution dance.
15
+ - `runStepByStep` no longer tracks `pendingAfterFromPrev` across yields; `debugBreak.after` on a yield refers to *that* iter's after-arm.
16
+ - The v5 post-loop after-fire drain (#108 part 1) collapses — the halting iter's after rides along on its own yield like any other iter.
17
+ - The generator's return type narrows back from `Generator<MachineState, MachineState | null>` to `Generator<MachineState>`; `for..of` consumers (the canonical pattern) are unaffected.
18
+
19
+ ### Closes
20
+
21
+ - [#107](https://github.com/mellonis/turing-machine-js/issues/107) — "expose un-substituted `machineState` for after-break consumers" disappears as a problem: there is no substitution to expose around.
22
+
23
+ ### Migration
24
+
25
+ No call-site change to `run()`'s API:
26
+
27
+ ```ts
28
+ await machine.run({
29
+ initialState,
30
+ onStep: (m) => { /* unchanged */ },
31
+ onPause: (m) => {
32
+ if (m.debugBreak?.before) console.log('before:', m.state.name);
33
+ if (m.debugBreak?.after) console.log('after:', m.state.name);
34
+ },
35
+ });
36
+ ```
37
+
38
+ Tests asserting the v5 sequence (`pause(after K-1) → pause(before K) → step(K)`) need updating to the v6 sequence (`pause(before K) → step(K) → pause(after K)`).
39
+
7
40
  ## [5.0.0] - 2026-05-09
8
41
 
9
42
  ### Changed (BREAKING)
package/README.md CHANGED
@@ -270,7 +270,7 @@ await machine.run({
270
270
  });
271
271
  ```
272
272
 
273
- For `after` calls, `m` is the previous yield's snapshot `m.state` is the state whose `after` filter fired. For `before` calls, `m` is the current iteration. `onStep` always sees the original (un-substituted) yield.
273
+ Both `before` and `after` for the same iteration fire on the iteration's own yield, in the order **before → step → after**. `m.state` is always the iteration's own state; the `m.debugBreak` flag (`{before: true}` or `{after: true}`) tells the consumer which timing fired.
274
274
 
275
275
  If `onPause` is not provided, breaks fire-and-resume invisibly — the trajectory is identical to running without `debug` set.
276
276
 
@@ -12,13 +12,15 @@ export type MachineState = {
12
12
  movements: symbol[];
13
13
  nextState: State;
14
14
  /**
15
- * Set only when this iteration boundary is a debug break.
15
+ * Set only when this iteration is a debug break point.
16
16
  * Field is OMITTED entirely when no break fires (no `debugBreak: undefined`).
17
17
  * At least one of `before` / `after` is `true` when the field is present.
18
18
  *
19
- * For consumers of the `runStepByStep` generator the `state` field reflects
20
- * the current iteration regardless of timing; `run()` substitutes the prior
21
- * yield's snapshot for `after` calls so consumers see the source state.
19
+ * Both flags refer to THIS iter — `before` means the iter's `state.debug.before`
20
+ * matched, `after` means the iter's `state.debug.after` matched. `run()`
21
+ * dispatches the two timings as separate `onPause` calls (before-call has
22
+ * `debugBreak: {before: true}` only; after-call has `debugBreak: {after: true}`
23
+ * only) so consumers can distinguish without ambiguity.
22
24
  */
23
25
  debugBreak?: {
24
26
  before?: true;
@@ -31,7 +33,7 @@ export default class TuringMachine {
31
33
  tapeBlock?: TapeBlock;
32
34
  });
33
35
  get tapeBlock(): TapeBlock;
34
- run({ initialState, stepsLimit, onStep, onPause, }: RunParameter & {
36
+ run({ initialState, stepsLimit, onStep, onPause, debug, }: RunParameter & {
35
37
  /**
36
38
  * Sync, ~free hook fired on every iteration. Use for logging/tracing —
37
39
  * the hot loop runs this without a microtask boundary, so it must not
@@ -39,16 +41,31 @@ export default class TuringMachine {
39
41
  */
40
42
  onStep?: (machineState: MachineState) => void;
41
43
  /**
42
- * Async hook fired only when `state.debug[when]` matches at the current
44
+ * Async hook fired when `state.debug[when]` matches at the current
43
45
  * iteration. The promise is awaited inline, so the consumer can suspend
44
46
  * execution by deferring its resolution. Use for pause-capable inspection
45
47
  * (debugger UIs, conditional breakpoints in tests).
46
48
  *
47
- * Renamed from `onDebugBreak` in v5.0.0. The `m.debugBreak` payload field
48
- * keeps its name (it describes the engine's reason for pausing).
49
+ * Renamed from `onDebugBreak` in v5.0.0. In v6.0.0 the dispatch order
50
+ * was changed so that `before` and `after` for the SAME iter fire on the
51
+ * same yield (per-iter lifecycle: before → step → after); previously the
52
+ * `after` of iter K fired on iter K+1's tick with a substituted source
53
+ * view. The `m.debugBreak` payload field keeps its name (it describes the
54
+ * engine's reason for pausing).
49
55
  */
50
56
  onPause?: (machineState: MachineState) => void | Promise<void>;
57
+ /**
58
+ * Master switch for `onPause` dispatch. When `false`, suppresses all
59
+ * pause-fires (before and after) regardless of `state.debug` assignments.
60
+ * `onStep` is unaffected. Defaults to `true`.
61
+ *
62
+ * The `m.debugBreak` field is still populated on yields by the underlying
63
+ * generator (it's a property of the iteration, not of the consumer); only
64
+ * `run()`'s hook dispatch is gated. Direct `runStepByStep` consumers see
65
+ * the metadata regardless.
66
+ */
67
+ debug?: boolean;
51
68
  }): Promise<void>;
52
- runStepByStep({ initialState, stepsLimit }: RunParameter): Generator<MachineState, MachineState | null>;
69
+ runStepByStep({ initialState, stepsLimit }: RunParameter): Generator<MachineState>;
53
70
  }
54
71
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turing-machine-js/machine",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "A convenient Turing machine",
5
5
  "engines": {
6
6
  "npm": ">=7.0.0"
@@ -34,5 +34,5 @@
34
34
  "default": "./dist/index.mjs"
35
35
  }
36
36
  },
37
- "gitHead": "ccf9a4743965c2a4ea6340ce1de8f6596e094251"
37
+ "gitHead": "6c7b2ac3055470b56c6882471767c746f5a9d7fb"
38
38
  }