@turing-machine-js/machine 4.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 +63 -0
- package/README.md +4 -4
- package/dist/classes/TuringMachine.d.ts +37 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,69 @@ 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
|
+
|
|
40
|
+
## [5.0.0] - 2026-05-09
|
|
41
|
+
|
|
42
|
+
### Changed (BREAKING)
|
|
43
|
+
|
|
44
|
+
- **Hook renamed: `onDebugBreak` → `onPause`** on `run()` ([#110](https://github.com/mellonis/turing-machine-js/issues/110)). Hard rename, no deprecation alias. The hook signature, payload shape, and dispatch semantics are unchanged — only the option key on `run()` differs. Resolution of the [#109 RFC](https://github.com/mellonis/turing-machine-js/issues/109): the hook now describes the consumer's relationship (this is where you can pause) rather than the engine's event (a debug break fired). The `m.debugBreak` payload field is unchanged.
|
|
45
|
+
- **`haltState.debug.after` now throws on assignment** ([#108](https://github.com/mellonis/turing-machine-js/issues/108) part 2). Halt is terminal — there is no iteration-after-halt for an after-fire to anchor on. Symmetric `{ before: true, after: true }` writes also throw; use `{ before: true }`. Symbol-list filters on `haltState.debug.before` remain silent no-ops as in v4.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **Halting iter's `state.debug.after` now fires** ([#108](https://github.com/mellonis/turing-machine-js/issues/108) part 1). Previously `pendingAfterFromPrev` set after the halting iter's yield was discarded when the `while (!state.isHalt)` loop exited — losing that after-fire. `run()` now drains a final after-fire after the loop when the prior yield armed one. Observable: a debugger UI sees a "we just halted because of state X's after-filter" event that was silent before.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- **`run({ debug: boolean })` flag** ([#106](https://github.com/mellonis/turing-machine-js/issues/106)). Master switch for `onPause` dispatch. When `false`, suppresses all pause-fires (before, after, and the post-loop after-drain) regardless of `state.debug` assignments. `onStep` is unaffected. Defaults to `true`. The `m.debugBreak` field is still populated on yields by the underlying generator (it's a property of the iteration); only `run()`'s hook dispatch is gated. Useful for toggling "debug mode" without editing `state.debug = ...` across the state graph.
|
|
54
|
+
|
|
55
|
+
### Migration
|
|
56
|
+
|
|
57
|
+
```diff
|
|
58
|
+
await machine.run({
|
|
59
|
+
initialState,
|
|
60
|
+
- onDebugBreak: (m) => { ... },
|
|
61
|
+
+ onPause: (m) => { ... },
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```diff
|
|
66
|
+
- haltState.debug = { before: true, after: true }; // throws in v5
|
|
67
|
+
+ haltState.debug = { before: true };
|
|
68
|
+
```
|
|
69
|
+
|
|
7
70
|
## [4.0.0] - 2026-05-07
|
|
8
71
|
|
|
9
72
|
### Added
|
package/README.md
CHANGED
|
@@ -256,13 +256,13 @@ myState.debug = null;
|
|
|
256
256
|
|
|
257
257
|
The `debug` field is mutable — toggle breakpoints at runtime without rebuilding the graph. The internal cell is shared with `state.withOverrodeHaltState(...)` wrappers, so an assignment on the original is visible from every wrapper.
|
|
258
258
|
|
|
259
|
-
`run()` is async and accepts an `
|
|
259
|
+
`run()` is async and accepts an `onPause` hook:
|
|
260
260
|
|
|
261
261
|
```ts
|
|
262
262
|
await machine.run({
|
|
263
263
|
initialState,
|
|
264
264
|
onStep: (m) => { /* logger sees every step */ },
|
|
265
|
-
|
|
265
|
+
onPause: async (m) => {
|
|
266
266
|
// Awaited at every break — hold execution until you resolve.
|
|
267
267
|
if (m.debugBreak?.before) console.log('before:', m.state.name);
|
|
268
268
|
if (m.debugBreak?.after) console.log('after:', m.state.name);
|
|
@@ -270,9 +270,9 @@ await machine.run({
|
|
|
270
270
|
});
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
-
|
|
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
|
-
If `
|
|
275
|
+
If `onPause` is not provided, breaks fire-and-resume invisibly — the trajectory is identical to running without `debug` set.
|
|
276
276
|
|
|
277
277
|
**Filter semantics:** `true` is a wildcard (match any symbol). `[ifOtherSymbol]` is NOT a wildcard — it matches only the catch-all resolution case (same meaning as in transition keys).
|
|
278
278
|
|
|
@@ -12,13 +12,15 @@ export type MachineState = {
|
|
|
12
12
|
movements: symbol[];
|
|
13
13
|
nextState: State;
|
|
14
14
|
/**
|
|
15
|
-
* Set only when this iteration
|
|
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
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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,9 +33,38 @@ export default class TuringMachine {
|
|
|
31
33
|
tapeBlock?: TapeBlock;
|
|
32
34
|
});
|
|
33
35
|
get tapeBlock(): TapeBlock;
|
|
34
|
-
run({ initialState, stepsLimit, onStep,
|
|
36
|
+
run({ initialState, stepsLimit, onStep, onPause, debug, }: RunParameter & {
|
|
37
|
+
/**
|
|
38
|
+
* Sync, ~free hook fired on every iteration. Use for logging/tracing —
|
|
39
|
+
* the hot loop runs this without a microtask boundary, so it must not
|
|
40
|
+
* be async.
|
|
41
|
+
*/
|
|
35
42
|
onStep?: (machineState: MachineState) => void;
|
|
36
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Async hook fired when `state.debug[when]` matches at the current
|
|
45
|
+
* iteration. The promise is awaited inline, so the consumer can suspend
|
|
46
|
+
* execution by deferring its resolution. Use for pause-capable inspection
|
|
47
|
+
* (debugger UIs, conditional breakpoints in tests).
|
|
48
|
+
*
|
|
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).
|
|
55
|
+
*/
|
|
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;
|
|
37
68
|
}): Promise<void>;
|
|
38
69
|
runStepByStep({ initialState, stepsLimit }: RunParameter): Generator<MachineState>;
|
|
39
70
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turing-machine-js/machine",
|
|
3
|
-
"version": "
|
|
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": "
|
|
37
|
+
"gitHead": "6c7b2ac3055470b56c6882471767c746f5a9d7fb"
|
|
38
38
|
}
|