ihsm 0.0.23 → 0.1.1

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.
Files changed (85) hide show
  1. package/README.md +105 -113
  2. package/lib/cjs/index.d.ts +5 -1394
  3. package/lib/cjs/index.js +53 -764
  4. package/lib/cjs/index.js.map +1 -1
  5. package/lib/cjs/internal/runtime.d.ts +293 -0
  6. package/lib/cjs/internal/runtime.js +1906 -0
  7. package/lib/cjs/internal/runtime.js.map +1 -0
  8. package/lib/cjs/internal/types.d.ts +348 -0
  9. package/lib/cjs/internal/types.js +9 -0
  10. package/lib/cjs/internal/types.js.map +1 -0
  11. package/lib/cjs/test-only.d.ts +5 -0
  12. package/lib/cjs/test-only.js +21 -0
  13. package/lib/cjs/test-only.js.map +1 -0
  14. package/lib/cjs/testing.d.ts +38 -91
  15. package/lib/cjs/testing.js +72 -38
  16. package/lib/cjs/testing.js.map +1 -1
  17. package/lib/cjs/transition-routines.d.ts +3 -0
  18. package/lib/cjs/transition-routines.js +11 -0
  19. package/lib/cjs/transition-routines.js.map +1 -0
  20. package/lib/cjs/types.d.ts +5 -0
  21. package/lib/cjs/{internal/defs.private.js → types.js} +1 -1
  22. package/lib/cjs/types.js.map +1 -0
  23. package/lib/esm/index.d.ts +5 -1394
  24. package/lib/esm/index.js +3 -742
  25. package/lib/esm/index.js.map +1 -1
  26. package/lib/esm/internal/runtime.d.ts +293 -0
  27. package/lib/esm/internal/runtime.js +1847 -0
  28. package/lib/esm/internal/runtime.js.map +1 -0
  29. package/lib/esm/internal/types.d.ts +348 -0
  30. package/lib/esm/internal/types.js +6 -0
  31. package/lib/esm/internal/types.js.map +1 -0
  32. package/lib/esm/test-only.d.ts +5 -0
  33. package/lib/esm/test-only.js +15 -0
  34. package/lib/esm/test-only.js.map +1 -0
  35. package/lib/esm/testing.d.ts +38 -91
  36. package/lib/esm/testing.js +72 -38
  37. package/lib/esm/testing.js.map +1 -1
  38. package/lib/esm/transition-routines.d.ts +3 -0
  39. package/lib/esm/transition-routines.js +3 -0
  40. package/lib/esm/transition-routines.js.map +1 -0
  41. package/lib/esm/types.d.ts +5 -0
  42. package/lib/esm/types.js +2 -0
  43. package/lib/esm/types.js.map +1 -0
  44. package/package.json +22 -2
  45. package/lib/cjs/internal/defs.private.d.ts +0 -41
  46. package/lib/cjs/internal/defs.private.js.map +0 -1
  47. package/lib/cjs/internal/dispatch.debug.d.ts +0 -4
  48. package/lib/cjs/internal/dispatch.debug.js +0 -332
  49. package/lib/cjs/internal/dispatch.debug.js.map +0 -1
  50. package/lib/cjs/internal/dispatch.production.d.ts +0 -6
  51. package/lib/cjs/internal/dispatch.production.js +0 -241
  52. package/lib/cjs/internal/dispatch.production.js.map +0 -1
  53. package/lib/cjs/internal/dispatch.trace.d.ts +0 -4
  54. package/lib/cjs/internal/dispatch.trace.js +0 -418
  55. package/lib/cjs/internal/dispatch.trace.js.map +0 -1
  56. package/lib/cjs/internal/hsm.d.ts +0 -60
  57. package/lib/cjs/internal/hsm.js +0 -215
  58. package/lib/cjs/internal/hsm.js.map +0 -1
  59. package/lib/cjs/internal/lookup.d.ts +0 -15
  60. package/lib/cjs/internal/lookup.js +0 -32
  61. package/lib/cjs/internal/lookup.js.map +0 -1
  62. package/lib/cjs/internal/utils.d.ts +0 -26
  63. package/lib/cjs/internal/utils.js +0 -63
  64. package/lib/cjs/internal/utils.js.map +0 -1
  65. package/lib/esm/internal/defs.private.d.ts +0 -41
  66. package/lib/esm/internal/defs.private.js +0 -2
  67. package/lib/esm/internal/defs.private.js.map +0 -1
  68. package/lib/esm/internal/dispatch.debug.d.ts +0 -4
  69. package/lib/esm/internal/dispatch.debug.js +0 -328
  70. package/lib/esm/internal/dispatch.debug.js.map +0 -1
  71. package/lib/esm/internal/dispatch.production.d.ts +0 -6
  72. package/lib/esm/internal/dispatch.production.js +0 -237
  73. package/lib/esm/internal/dispatch.production.js.map +0 -1
  74. package/lib/esm/internal/dispatch.trace.d.ts +0 -4
  75. package/lib/esm/internal/dispatch.trace.js +0 -414
  76. package/lib/esm/internal/dispatch.trace.js.map +0 -1
  77. package/lib/esm/internal/hsm.d.ts +0 -60
  78. package/lib/esm/internal/hsm.js +0 -211
  79. package/lib/esm/internal/hsm.js.map +0 -1
  80. package/lib/esm/internal/lookup.d.ts +0 -15
  81. package/lib/esm/internal/lookup.js +0 -29
  82. package/lib/esm/internal/lookup.js.map +0 -1
  83. package/lib/esm/internal/utils.d.ts +0 -26
  84. package/lib/esm/internal/utils.js +0 -52
  85. package/lib/esm/internal/utils.js.map +0 -1
package/README.md CHANGED
@@ -6,11 +6,11 @@
6
6
 
7
7
  # ihsm
8
8
 
9
- **Class-based hierarchical state machines and run-to-completion actors for TypeScript, explicitly designed for [Deterministic Simulation Testing](https://filasieno.github.io/ihsm/testing) (DST)** — typed `post`/`call`, **zero** production dependencies, **~4.6 KB gzip** in the browser. → [Documentation](https://filasieno.github.io/ihsm/)
9
+ **Class-based hierarchical state machines and run-to-completion actors for TypeScript, explicitly designed for [Deterministic Simulation Testing](https://filasieno.github.io/ihsm/testing) (DST)** — nominal **`Config`**, generated handles, promise **services**, **zero** production dependencies, **~4.6 KB gzip** in the browser. → [Documentation](https://filasieno.github.io/ihsm/) · [Tutorial 00 — Config](examples/00-config/README.md)
10
10
 
11
11
  ihsm is state management and orchestration for backends, session actors, protocol handlers, and embedded tooling: states are **classes**, events are **methods**, hierarchy is **inheritance**, and each machine is an **actor** with serialized, run-to-completion dispatch.
12
12
 
13
- > **Built for Deterministic Simulation Testing.** Determinism is not an add-on here — it is the design center. Every source of nondeterminism is pushed behind one seam: **serialized run-to-completion dispatch** (each handler runs to completion and never interleaves; `await sync()` drains to a barrier), a single **`Port`** boundary for *all* I/O (sockets, clocks, the filesystem), and a compiler-enforced **public/internal protocol split**. Swap the port for a mock, replace the clock with one you advance by hand, and the same inputs always produce the same outputs — so a failure **replays exactly**. The dedicated [`ihsm/testing`](#entry-points) entry point ships `makeTestActor`, `@mock`/`makeTestPort`, and a `TestPort` virtual clock for this, and never bloats your production bundle. See the [Deterministic Testing chapter](https://filasieno.github.io/ihsm/testing).
13
+ > **Built for Deterministic Simulation Testing.** Determinism is not an add-on here — it is the design center. Every source of nondeterminism is pushed behind one seam: **serialized run-to-completion dispatch** (each handler runs to completion and never interleaves; `await actor.hsm.sync()` drains to a barrier), a single **`Port`** boundary for *all* I/O (sockets, clocks, the filesystem), and a compiler-enforced **public/internal protocol split**. Swap the port for a mock, replace the clock with one you advance by hand, and the same inputs always produce the same outputs — so a failure **replays exactly**. The dedicated [`ihsm/testing`](#entry-points) entry point ships `makeTestActor`, `@mock`/`makeTestPort`, and a `TestPort` virtual clock for this, and never bloats your production bundle. See the [Deterministic Testing chapter](https://filasieno.github.io/ihsm/testing).
14
14
 
15
15
  Requires **Node.js 22+** (or a modern browser). Class names in traces and errors come from `Class.name` — no extra registration step in a typical npm/Node project.
16
16
 
@@ -20,11 +20,9 @@ It uses event-driven programming, class-based hierarchical statecharts, and the
20
20
 
21
21
  📖 [Read the documentation](https://filasieno.github.io/ihsm/)
22
22
 
23
- 📑 [API reference](https://filasieno.github.io/ihsm/api)
24
-
25
23
  📖 [Reference](https://filasieno.github.io/ihsm/reference)
26
24
 
27
- 🧪 [Deterministic Testing chapter](https://filasieno.github.io/ihsm/testing)
25
+ 🧪 [Deterministic Testing](https://filasieno.github.io/ihsm/testing)
28
26
 
29
27
  💬 [Open an issue](https://github.com/filasieno/ihsm/issues)
30
28
 
@@ -53,116 +51,114 @@ npm install ihsm
53
51
  ```
54
52
 
55
53
  ```ts
56
- import { InitialState, makeHsm, TopState } from 'ihsm';
54
+ import { InitialState, makeActor, Port, TopState } from 'ihsm';
57
55
 
58
56
  interface DoorCtx {
59
57
  openCount: number;
60
58
  }
61
59
 
62
- // All possible signals are enumerated in a formal protocol
63
- interface DoorProtocol {
64
- open(): void;
65
- close(): void;
60
+ interface DoorConfig {
61
+ context: DoorCtx;
62
+ notifications: {
63
+ open(): void;
64
+ close(): void;
65
+ };
66
66
  }
67
67
 
68
- class DoorTop extends TopState<DoorCtx, DoorProtocol> {}
68
+
69
+ class DoorTop extends TopState<DoorConfig> {
70
+ }
69
71
 
70
72
  @InitialState
71
73
  class Closed extends DoorTop {
72
74
  open(): void {
73
75
  this.ctx.openCount += 1;
74
- this.transition(Open);
76
+ this.hsm.transition(Open);
75
77
  }
76
78
  }
77
79
 
78
80
  class Open extends DoorTop {
79
81
  close(): void {
80
- this.transition(Closed);
82
+ this.hsm.transition(Closed);
81
83
  }
82
84
  }
83
85
 
84
- const door = makeHsm(DoorTop, { openCount: 0 });
85
- await door.sync(); // wait for initialization
86
+ const door = makeActor(DoorTop, { openCount: 0 }, new Port());
87
+ await door.hsm.sync();
86
88
 
87
- door.post('open');
88
- await door.sync();
89
+ door.notify.open();
90
+ await door.hsm.sync();
89
91
 
90
- console.log(door.currentStateName); // 'Open'
91
- console.log(door.ctx.openCount); // 1
92
+ console.log(door.hsm.currentStateName); // 'Open'
93
+ console.log(door.ctx.openCount); // 1
92
94
  ```
93
95
 
94
- ---
96
+ See **[examples/00-config/](examples/00-config/README.md)** for the full protocol tour.
95
97
 
96
- ## Typed services with `call()`
98
+ ---
97
99
 
98
- Most state-machine libraries make you reach for snapshots, child actors, or ad hoc callbacks to ask the machine a question. ihsm treats **services** as ordinary protocol methods — the runtime injects `resolve` / `reject`, and the client gets a typed `Promise`.
100
+ ## Typed services (promise-returning)
99
101
 
100
- Define the service once on your `Protocol`. Implement it on a state class. Call it from anywhere that holds the `Hsm` handle.
102
+ Services are declared on the protocol's `services` bucket. The generated client method **always** returns `Promise<Reply>` callers must `await`, so RTC ordering is explicit.
101
103
 
102
104
  ```ts
103
- import {
104
- InitialState,
105
- makeHsm,
106
- RejectCallback,
107
- ResolveCallback,
108
- TopState,
109
- } from 'ihsm';
105
+ import { InitialState, makeActor, Port, TopState } from 'ihsm';
110
106
 
111
107
  interface WalletCtx {
112
108
  balance: number;
113
109
  }
114
110
 
115
- // note the `getBalance` and `withdraw`.
116
- // since they have a *resolve* and *reject* the are services allowing State Machines to serve requests **AND** transition at the same time if required.
117
- interface WalletProtocol {
118
- deposit(amount: number): void;
119
- getBalance(resolve: ResolveCallback<number>, reject: RejectCallback): void;
120
- withdraw(resolve: ResolveCallback<number>, reject: RejectCallback, amount: number): void;
111
+ interface WalletConfig {
112
+ notifications: { deposit(amount: number): void };
113
+ services: {
114
+ getBalance(): Promise<number>;
115
+ withdraw(amount: number): Promise<number>;
116
+ };
117
+ context: WalletCtx;
121
118
  }
122
119
 
123
- class WalletTop extends TopState<WalletCtx, WalletProtocol> {
120
+
121
+ class WalletTop extends TopState<WalletConfig> {
122
+
124
123
  deposit(amount: number): void {
125
124
  this.ctx.balance += amount;
126
125
  }
127
126
 
128
- getBalance(resolve: ResolveCallback<number>): void {
129
- resolve(this.ctx.balance);
127
+ getBalance(): number {
128
+ return this.ctx.balance;
130
129
  }
131
130
 
132
- withdraw(resolve: ResolveCallback<number>, reject: RejectCallback, amount: number): void {
131
+ withdraw(amount: number): number {
133
132
  if (amount > this.ctx.balance) {
134
- reject(new Error('insufficient funds'));
135
- return;
133
+ throw new Error('insufficient funds');
136
134
  }
137
135
  this.ctx.balance -= amount;
138
- resolve(this.ctx.balance);
136
+ return this.ctx.balance;
139
137
  }
140
138
  }
141
139
 
142
140
  @InitialState
143
141
  class Open extends WalletTop {}
144
142
 
145
- const wallet = makeHsm(WalletTop, { balance: 100 });
146
- await wallet.sync();
143
+ const wallet = makeActor(WalletTop, { balance: 100 }, new Port());
144
+ await wallet.hsm.sync();
147
145
 
148
- wallet.post('deposit', 50);
146
+ wallet.notify.deposit(50);
149
147
 
150
- const balance = await wallet.call('getBalance'); // Promise<number> — no extra sync()
148
+ const balance = await wallet.call.getBalance();
151
149
 
152
150
  try {
153
- await wallet.call('withdraw', 200);
154
- } catch (err) {
155
- // reject() from the handler becomes a thrown Error here
151
+ await wallet.call.withdraw(200);
152
+ } catch {
153
+ // handler throw rejected Promise
156
154
  }
157
155
 
158
- const left = await wallet.call('getBalance'); // 150
156
+ const left = await wallet.call.getBalance(); // 150
159
157
  ```
160
158
 
161
- **Events** (`void` handlers) → `post('deposit', 50)`. **Services** (`resolve` / `reject` handlers) → `await call('getBalance')`. Same run-to-completion dispatch, same serialization guarantees, full TypeScript inference on names, payloads, and return types.
159
+ **Notifications** → `wallet.notify.deposit(50)` (void). **Services** → `await wallet.call.getBalance()` (Promise). The split is **nominal** via `Config.notifications` vs `Config.services`.
162
160
 
163
- The split is enforced **at compile time**: the protocol is partitioned into event keys and service keys, so `post('getBalance')` (a service) and `call('deposit', 50)` (an event) are both type errors — you can only `post` events and `call` services.
164
-
165
- See [Call services](https://filasieno.github.io/ihsm/reference#_4-messaging-post-call-sync) in the reference.
161
+ See [Tutorial 00 Config](examples/00-config/README.md) and the [reference](https://filasieno.github.io/ihsm/reference).
166
162
 
167
163
  ---
168
164
 
@@ -174,36 +170,37 @@ Also not that all states are stateless classes.
174
170
  All state is stored in the actor context available at `this.ctx`.
175
171
 
176
172
  ```ts
177
- import { InitialState, makeHsm, TopState } from 'ihsm';
173
+ import { InitialState, makeActor, Port, TopState } from 'ihsm';
178
174
 
179
175
  interface PlayerCtx {
180
176
  track: string;
181
177
  }
182
178
 
183
- interface PlayerProtocol {
184
- play(): void;
185
- pause(): void;
186
- stop(): void;
179
+ interface PlayerConfig {
180
+ context: PlayerCtx;
181
+ notifications: { play(): void; pause(): void; stop(): void };
187
182
  }
188
183
 
189
- class PlayerTop extends TopState<PlayerCtx, PlayerProtocol> {}
184
+
185
+ class PlayerTop extends TopState<PlayerConfig> {
186
+ }
190
187
 
191
188
  class Active extends PlayerTop {
192
189
  stop(): void {
193
- this.transition(Stopped);
190
+ this.hsm.transition(Stopped);
194
191
  }
195
192
  }
196
193
 
197
194
  @InitialState
198
195
  class Playing extends Active {
199
196
  pause(): void {
200
- this.transition(Paused);
197
+ this.hsm.transition(Paused);
201
198
  }
202
199
  }
203
200
 
204
201
  class Paused extends Active {
205
202
  play(): void {
206
- this.transition(Playing);
203
+ this.hsm.transition(Playing);
207
204
  }
208
205
  }
209
206
 
@@ -211,15 +208,15 @@ class Paused extends Active {
211
208
  class Stopped extends PlayerTop {
212
209
  play(): void {
213
210
  this.ctx.track = 'demo.mp3';
214
- this.transition(Playing);
211
+ this.hsm.transition(Playing);
215
212
  }
216
213
  }
217
214
 
218
- const player = makeHsm(PlayerTop, { track: '' });
219
- await player.sync();
215
+ const player = makeActor(PlayerTop, { track: '' }, new Port());
216
+ await player.hsm.sync();
220
217
 
221
- player.post('play');
222
- await player.sync();
218
+ player.notify.play();
219
+ await player.hsm.sync();
223
220
  // active leaf: Playing — inherits stop() from Active
224
221
  ```
225
222
 
@@ -227,27 +224,28 @@ See [Hierarchy & transitions](https://filasieno.github.io/ihsm/reference#_5-tran
227
224
 
228
225
  ---
229
226
 
230
- ## Messaging: `post`, `sync`, and `call`
227
+ ## Messaging: notifications, services, and sync
231
228
 
232
229
  Every machine is an actor with **single-threaded, run-to-completion dispatch**. While a handler runs to completion, new messages queue — no re-entrancy.
233
230
 
234
231
  | API | Role | Returns |
235
232
  | --- | ---- | ------- |
236
- | `post(event, args)` | Fire-and-forget event | `void` (use `sync()` to wait) |
237
- | `call(service, args)` | Typed request/response | `Promise<T>` |
238
- | `deferredPost(ms, event, args)` | Timer then `post` | `void` |
239
- | `sync()` | Drain queue up to marker | `Promise<void>` |
233
+ | `actor.notify.event(…)` | Fire-and-forget notification | `void` (use `hsm.sync()` to wait) |
234
+ | `actor.notifyNow.event(…)` | Hi-priority notification | `void` |
235
+ | `await actor.call.service(…)` | Typed request/response | `Promise<T>` |
236
+ | `this.hsm.port.defer(ms).event(…)` | Timer then self-notification | handler-only |
237
+ | `await actor.hsm.sync()` | Drain queue up to marker | `Promise<void>` |
240
238
 
241
239
  ```ts
242
- door.post('open');
243
- await door.sync(); // handler + transition finished
240
+ door.notify.open();
241
+ await door.hsm.sync();
244
242
 
245
- const id = await account.call('lookup', 'user-42'); // await the service directly
243
+ const id = await account.call.lookup('user-42');
246
244
  ```
247
245
 
248
- Inside handlers you also get `transition()`, `sleep()`, and `postNow()` for hi-priority follow-up steps within the same dispatch turn.
246
+ Inside handlers use `this.hsm.transition()`, `this.notify`, and `this.notifyNow`. For delays, `await new Promise(r => this.hsm.port.setTimeout(r, ms))`; for timer-driven self-notifications, `this.hsm.port.defer(ms).event(…)`.
249
247
 
250
- See [Post & sync](https://filasieno.github.io/ihsm/reference#_4-messaging-post-call-sync) in the reference.
248
+ See [Messaging](https://filasieno.github.io/ihsm/reference#_4-messaging-notifications-services-sync) in the reference.
251
249
 
252
250
  ---
253
251
 
@@ -262,7 +260,7 @@ class Idle extends FileTop {
262
260
  async transfer(from: string, to: string): Promise<void> {
263
261
  const data = await readFile(from);
264
262
  await writeFile(to, data);
265
- this.transition(Done);
263
+ this.hsm.transition(Done);
266
264
  }
267
265
  }
268
266
  ```
@@ -275,11 +273,11 @@ See [Async handlers](https://filasieno.github.io/ihsm/reference#_9-async-handler
275
273
 
276
274
  Production code imports `ihsm`; tests import `ihsm/testing`. Every source of nondeterminism lives behind a **`Port`** — sockets, clocks, randomness, the filesystem. Tests swap in a **`TestPort`** (virtual clock, scripted random, recorded message log) or an **`@mock`** port stub, then drive the machine with **`makeTestActor`** (merged public + internal protocol, `subscribe()` for golden traces).
277
275
 
278
- Two rules: **never perform I/O outside a port**, and **never `sleep()` on wall-clock time in a test** — advance virtual time and `await sync()` instead.
276
+ Two rules: **never perform I/O outside a port**, and **never `sleep()` on wall-clock time in a test** — advance virtual time and `await actor.hsm.sync()` instead.
279
277
 
280
278
  ### Virtual clock — simulate days of timers in microseconds
281
279
 
282
- `deferredPost` arms timers through the port. Replace the real clock with `TestPort` and call `advance(ms)` by hand:
280
+ `this.hsm.port.defer(ms).onTick()` arms timers through the port. Replace the real clock with `TestPort` and call `advance(ms)` by hand:
283
281
 
284
282
  ```ts
285
283
  import { InitialState, TopState } from 'ihsm';
@@ -287,41 +285,36 @@ import { makeTestActor, TestPort } from 'ihsm/testing';
287
285
 
288
286
  const HOUR_MS = 60 * 60 * 1000;
289
287
 
290
- class HeartbeatCtx {
291
- ticks = 0;
288
+ interface HeartbeatConfig {
289
+ context: { ticks: number };
290
+ notifications: { start(): void };
291
+ internalNotifications: { onTick(): void };
292
292
  }
293
293
 
294
- interface HeartbeatPublic {
295
- start(): void;
294
+ class HeartbeatTop extends TopState<HeartbeatConfig> {
296
295
  }
297
296
 
298
- interface HeartbeatInternal {
299
- onTick(): void;
300
- }
301
-
302
- class HeartbeatTop extends TopState<HeartbeatCtx, HeartbeatPublic, HeartbeatInternal> {}
303
-
304
297
  @InitialState
305
298
  class Running extends HeartbeatTop {
306
299
  start(): void {
307
- this.deferredPost(HOUR_MS, 'onTick');
300
+ this.hsm.port.defer(HOUR_MS).onTick();
308
301
  }
309
302
  onTick(): void {
310
303
  this.ctx.ticks += 1;
311
- this.deferredPost(HOUR_MS, 'onTick');
304
+ this.hsm.port.defer(HOUR_MS).onTick();
312
305
  }
313
306
  }
314
307
 
315
- const clock = new TestPort<HeartbeatTop>();
308
+ const clock = new TestPort<typeof HeartbeatTop>();
316
309
  const test = makeTestActor(HeartbeatTop, new HeartbeatCtx(), clock);
317
- await test.sync();
310
+ await test.hsm.sync();
318
311
 
319
- test.post('start');
320
- await test.sync();
312
+ test.start();
313
+ await test.hsm.sync();
321
314
 
322
315
  for (let hour = 0; hour < 48; hour++) {
323
316
  clock.advance(HOUR_MS); // fire the due tick — no real waiting
324
- await test.sync();
317
+ await test.hsm.sync();
325
318
  }
326
319
 
327
320
  // test.ctx.ticks === 48
@@ -337,7 +330,7 @@ Put `fetch()` behind a port. The mock records outbound calls but does **not** au
337
330
  import { mock, makeTestActor, makeTestPort, TestPort } from 'ihsm/testing';
338
331
 
339
332
  @mock
340
- abstract class MockFetchPort extends TestPort<FetchTop> {
333
+ abstract class MockFetchPort extends TestPort<typeof FetchTop> {
341
334
  abstract request(url: string): { value: number; subscription: { dispose(): void } };
342
335
  }
343
336
 
@@ -348,14 +341,14 @@ port.request.default(() => ({
348
341
  }));
349
342
 
350
343
  const fetcher = makeTestActor(FetchTop, freshCtx(), port);
351
- await fetcher.sync();
344
+ await fetcher.hsm.sync();
352
345
 
353
- fetcher.post('fetch', 'https://example.com');
354
- await fetcher.sync();
346
+ fetcher.fetch('https://example.com');
347
+ await fetcher.hsm.sync();
355
348
  // fetcher.currentState === Fetching — in-flight, still timer-free
356
349
 
357
350
  port.send('onResponse', 200, 'ok'); // you decide when the "network" replies
358
- await fetcher.sync();
351
+ await fetcher.hsm.sync();
359
352
  // fetcher.currentState === Done
360
353
  // port.trace === ['request:https://example.com', 'onResponse:200,ok']
361
354
  ```
@@ -365,12 +358,12 @@ await fetcher.sync();
365
358
  Wire `subscribe` to the port message log for a byte-identical transcript across runs:
366
359
 
367
360
  ```ts
368
- const port = new TestPort<HeartbeatTop>();
361
+ const port = new TestPort<typeof HeartbeatTop>();
369
362
  const test = makeTestActor(HeartbeatTop, new HeartbeatCtx(), port);
370
363
  const sub = test.subscribe(m => port.record(m.event, ...m.payload));
371
364
 
372
- test.post('start');
373
- await test.sync();
365
+ test.start();
366
+ await test.hsm.sync();
374
367
  // port.events === ['start']
375
368
 
376
369
  sub.dispose();
@@ -395,11 +388,11 @@ version:
395
388
 
396
389
  | Import | Contents | Ships in production? |
397
390
  | ------ | -------- | -------------------- |
398
- | `ihsm` | The runtime: `makeHsm` / `makeActor`, `TopState`, ports, tracing | **yes** |
391
+ | `ihsm` | The runtime: `makeActor` / `makeActor`, `TopState`, ports, tracing | **yes** |
399
392
  | `ihsm/testing` or `@ihsm/core/testing` | Deterministic-testing utilities: `makeTestActor`, `@mock` / `makeTestPort`, `TestPort` (re-exports the core API too) | **no** — test-only |
400
393
 
401
394
  ```ts
402
- import { makeHsm, TopState } from 'ihsm'; // production code
395
+ import { makeActor, TopState } from 'ihsm'; // production code
403
396
  import { makeTestActor, mock, TestPort } from 'ihsm/testing'; // tests only
404
397
  ```
405
398
 
@@ -421,7 +414,7 @@ ihsm ships modern **ES2022** ESM and CommonJS. Supported runtimes:
421
414
 
422
415
  ### Size and dependencies
423
416
 
424
- Measured with `esbuild` bundling `lib/esm/index.js` for the browser (full runtime — run-to-completion dispatch, transitions, tracing, typed `call`):
417
+ Measured with `esbuild` bundling `lib/esm/index.js` for the browser (full runtime — run-to-completion dispatch, transitions, tracing, promise services):
425
418
 
426
419
  | | |
427
420
  | --- | --- |
@@ -429,7 +422,7 @@ Measured with `esbuild` bundling `lib/esm/index.js` for the browser (full runtim
429
422
  | **Published package** | `lib/` only (~46 KB npm tarball) |
430
423
  | **Minified bundle** | **~22 KB** (21.7 KiB; single-file ESM/IIFE) |
431
424
  | **Gzip** | **~4.6 KB** (typical CDN / HTTP transfer size) |
432
- | **Tree-shaking** | `"sideEffects": false` — runtime is one cohesive module (~22 KB even when importing only `makeHsm`) |
425
+ | **Tree-shaking** | `"sideEffects": false` — runtime is one cohesive module (~22 KB even when importing only `makeActor`) |
433
426
 
434
427
  Node loads the unminified `lib/` files directly (~18 KB entry, ~62 KB total); minify numbers apply to browser bundles.
435
428
 
@@ -461,12 +454,11 @@ Inspired by Harel statecharts and the SCXML family of notations.
461
454
  | **Documentation site** | [filasieno.github.io/ihsm](https://filasieno.github.io/ihsm/) |
462
455
  | Deterministic Simulation Testing | [/testing](https://filasieno.github.io/ihsm/testing) |
463
456
  | Reference (concepts + interactive examples) | [/reference](https://filasieno.github.io/ihsm/reference) |
464
- | API reference (TSDoc) | [/api](https://filasieno.github.io/ihsm/api) |
465
457
  | Source: DST chapter | [reference/TESTING.md](./reference/TESTING.md) |
466
458
  | Source: reference | [reference/REFERENCE.md](./reference/REFERENCE.md) |
467
459
  | Source: example machines | [examples/](./examples/) |
468
460
 
469
- The reference page combines the full manual with embedded playgrounds; the API is generated from TSDoc.
461
+ The reference page combines the manual with embedded playgrounds. The testing chapter runs five DST examples first, then the full technique.
470
462
 
471
463
  ---
472
464