aifsmjs 0.2.1 → 0.3.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.
@@ -1,5 +1,5 @@
1
1
  import * as fc from 'fast-check';
2
- import { i as Runtime, M as MachineDef, I as Implementations } from '../types-BLwKAHnW.cjs';
2
+ import { i as Runtime, M as MachineDef, I as Implementations } from '../types-CGKk6Rur.cjs';
3
3
 
4
4
  /**
5
5
  * Pure-model representation of an FSM run, used by `fc.commands`.
@@ -1,5 +1,5 @@
1
1
  import * as fc from 'fast-check';
2
- import { i as Runtime, M as MachineDef, I as Implementations } from '../types-BLwKAHnW.js';
2
+ import { i as Runtime, M as MachineDef, I as Implementations } from '../types-CGKk6Rur.js';
3
3
 
4
4
  /**
5
5
  * Pure-model representation of an FSM run, used by `fc.commands`.
package/dist/pbt/index.js CHANGED
@@ -248,6 +248,18 @@ var RuntimeDisposedError = class extends Error {
248
248
  this.name = "RuntimeDisposedError";
249
249
  }
250
250
  };
251
+ var SubMachineError = class extends Error {
252
+ parentState;
253
+ phase;
254
+ cause;
255
+ constructor(parentState, phase, cause) {
256
+ super(`aifsmjs: sub-machine ${phase} failed at parent state "${parentState}"`, { cause });
257
+ this.name = "SubMachineError";
258
+ this.parentState = parentState;
259
+ this.phase = phase;
260
+ this.cause = cause;
261
+ }
262
+ };
251
263
  var RESET_EVENT = Object.freeze({ type: RESET_EVENT_TYPE });
252
264
  function composeMiddleware(middleware) {
253
265
  return (ctx, finalNext) => {
@@ -272,6 +284,8 @@ function createRuntime(def, impl, opts = {}) {
272
284
  const shouldDispatch = opts.dispatchEffects !== false;
273
285
  const controller = new AbortController();
274
286
  let disposed = false;
287
+ let childRuntime;
288
+ let childAbortCleanup;
275
289
  const eventListeners = {
276
290
  transition: /* @__PURE__ */ new Set(),
277
291
  error: /* @__PURE__ */ new Set(),
@@ -287,14 +301,7 @@ function createRuntime(def, impl, opts = {}) {
287
301
  }
288
302
  function runMiddleware(prev, event, effects, changed) {
289
303
  if (!middlewareChain) return;
290
- const mwCtx = deepFreeze({
291
- prev,
292
- next: snapshot,
293
- event,
294
- effects,
295
- changed
296
- });
297
- middlewareChain(mwCtx, () => {
304
+ middlewareChain(deepFreeze({ prev, next: snapshot, event, effects, changed }), () => {
298
305
  });
299
306
  }
300
307
  function dispatchEffects(effects, context, event) {
@@ -305,52 +312,128 @@ function createRuntime(def, impl, opts = {}) {
305
312
  const r = handler(eff, { context, event, signal: controller.signal });
306
313
  if (r instanceof Promise) {
307
314
  r.catch((err) => {
308
- const payload = { error: err, event };
309
- emit("error", payload);
315
+ emit("error", { error: err, event });
310
316
  });
311
317
  }
312
318
  }
313
319
  }
320
+ function wireChildAbort(child) {
321
+ if (controller.signal.aborted) {
322
+ try {
323
+ child.dispose();
324
+ } catch {
325
+ }
326
+ return () => {
327
+ };
328
+ }
329
+ const onAbort = () => {
330
+ try {
331
+ child.dispose();
332
+ } catch {
333
+ }
334
+ };
335
+ controller.signal.addEventListener("abort", onAbort, { once: true });
336
+ return () => controller.signal.removeEventListener("abort", onAbort);
337
+ }
338
+ function findChosenIsExternal(value, event, context) {
339
+ const state = def.states[value];
340
+ if (!state?.on) return false;
341
+ const candidates = state.on[event.type];
342
+ if (!candidates) return false;
343
+ const list = Array.isArray(candidates) ? candidates : [candidates];
344
+ for (const t of list) {
345
+ if (!t.guard || evalGuard(t.guard, context, event, impl, value)) {
346
+ return t.target !== void 0;
347
+ }
348
+ }
349
+ return false;
350
+ }
351
+ function applySubLifecycle(prevValue, nextValue) {
352
+ const prevStateDef = def.states[prevValue];
353
+ const nextStateDef = def.states[nextValue];
354
+ if (prevStateDef?.sub !== void 0 && childRuntime !== void 0) {
355
+ const child = childRuntime;
356
+ childRuntime = void 0;
357
+ childAbortCleanup?.();
358
+ childAbortCleanup = void 0;
359
+ try {
360
+ child.dispose();
361
+ } catch (cause) {
362
+ throw new SubMachineError(prevValue, "dispose", cause);
363
+ }
364
+ }
365
+ if (nextStateDef?.sub !== void 0) {
366
+ let newChild;
367
+ try {
368
+ newChild = createRuntime(nextStateDef.sub, nextStateDef.subImpl ?? {});
369
+ } catch (cause) {
370
+ throw new SubMachineError(nextValue, "init", cause);
371
+ }
372
+ childRuntime = newChild;
373
+ childAbortCleanup = wireChildAbort(newChild);
374
+ }
375
+ }
314
376
  function send(event) {
315
377
  if (disposed) throw new RuntimeDisposedError();
316
378
  const prev = snapshot;
317
379
  const result = step(def, prev, event, impl);
380
+ const isExternal = result.changed && (prev.value !== result.snapshot.value || findChosenIsExternal(prev.value, event, prev.context));
381
+ if (result.changed && isExternal) applySubLifecycle(prev.value, result.snapshot.value);
318
382
  snapshot = result.snapshot;
319
383
  const committed = result.snapshot;
320
384
  runMiddleware(prev, event, result.effects, result.changed);
321
- if (shouldDispatch) {
322
- dispatchEffects(result.effects, committed.context, event);
323
- }
385
+ if (shouldDispatch) dispatchEffects(result.effects, committed.context, event);
324
386
  if (result.changed) {
325
387
  notify(committed);
326
- const payload = {
388
+ emit("transition", {
327
389
  prev,
328
390
  next: committed,
329
391
  event,
330
392
  effects: result.effects,
331
393
  changed: true
332
- };
333
- emit("transition", payload);
394
+ });
334
395
  }
335
396
  return snapshot;
336
397
  }
337
398
  function reset(event) {
338
399
  if (disposed) throw new RuntimeDisposedError();
339
400
  const prev = snapshot;
340
- snapshot = initialSnapshot(def);
341
- const changed = prev.value !== snapshot.value;
401
+ const nextSnap = initialSnapshot(def);
402
+ const changed = prev.value !== nextSnap.value;
403
+ if (childRuntime) {
404
+ const child = childRuntime;
405
+ childRuntime = void 0;
406
+ childAbortCleanup?.();
407
+ childAbortCleanup = void 0;
408
+ try {
409
+ child.dispose();
410
+ } catch (cause) {
411
+ throw new SubMachineError(prev.value, "dispose", cause);
412
+ }
413
+ }
414
+ const initStateDef = def.states[nextSnap.value];
415
+ if (initStateDef?.sub) {
416
+ let newChild;
417
+ try {
418
+ newChild = createRuntime(initStateDef.sub, initStateDef.subImpl ?? {});
419
+ } catch (cause) {
420
+ throw new SubMachineError(nextSnap.value, "init", cause);
421
+ }
422
+ childRuntime = newChild;
423
+ childAbortCleanup = wireChildAbort(newChild);
424
+ }
425
+ snapshot = nextSnap;
342
426
  const triggerEvent = event ?? RESET_EVENT;
343
427
  runMiddleware(prev, triggerEvent, [], changed);
344
428
  if (changed) {
345
429
  notify();
346
- const payload = {
430
+ emit("transition", {
347
431
  prev,
348
432
  next: snapshot,
349
433
  event: triggerEvent,
350
434
  effects: [],
351
435
  changed: true
352
- };
353
- emit("transition", payload);
436
+ });
354
437
  }
355
438
  return snapshot;
356
439
  }
@@ -371,36 +454,43 @@ function createRuntime(def, impl, opts = {}) {
371
454
  if (disposed || options?.signal?.aborted) return () => {
372
455
  };
373
456
  const target = eventListeners[type];
457
+ let detachAbort;
458
+ const cleanup = () => {
459
+ target.delete(wrapped);
460
+ if (detachAbort) {
461
+ detachAbort();
462
+ externalAbortCleanups.delete(detachAbort);
463
+ }
464
+ };
374
465
  let wrapped = listener;
375
466
  if (options?.once) {
376
467
  wrapped = (payload) => {
377
- target.delete(wrapped);
468
+ cleanup();
378
469
  listener(payload);
379
470
  };
380
471
  }
381
472
  target.add(wrapped);
382
- let detachAbort;
383
473
  const signal = options?.signal;
384
474
  if (signal) {
385
- const onAbort = () => {
386
- target.delete(wrapped);
387
- if (detachAbort) externalAbortCleanups.delete(detachAbort);
388
- };
475
+ const onAbort = () => cleanup();
389
476
  signal.addEventListener("abort", onAbort, { once: true });
390
477
  detachAbort = () => signal.removeEventListener("abort", onAbort);
391
478
  externalAbortCleanups.add(detachAbort);
392
479
  }
393
- return () => {
394
- target.delete(wrapped);
395
- if (detachAbort) {
396
- detachAbort();
397
- externalAbortCleanups.delete(detachAbort);
398
- }
399
- };
480
+ return cleanup;
400
481
  }
401
482
  function dispose() {
402
483
  if (disposed) return;
403
484
  disposed = true;
485
+ if (childRuntime) {
486
+ childAbortCleanup?.();
487
+ childAbortCleanup = void 0;
488
+ try {
489
+ childRuntime.dispose();
490
+ } catch {
491
+ }
492
+ childRuntime = void 0;
493
+ }
404
494
  controller.abort();
405
495
  listeners.clear();
406
496
  emit("dispose", void 0);
@@ -408,7 +498,7 @@ function createRuntime(def, impl, opts = {}) {
408
498
  for (const cleanup of externalAbortCleanups) cleanup();
409
499
  externalAbortCleanups.clear();
410
500
  }
411
- return {
501
+ const runtime = {
412
502
  getSnapshot: () => snapshot,
413
503
  snapshot: () => snapshot,
414
504
  send,
@@ -427,8 +517,22 @@ function createRuntime(def, impl, opts = {}) {
427
517
  };
428
518
  listeners.add(listener);
429
519
  return () => listeners.delete(listener);
430
- }
520
+ },
521
+ subRuntime: () => childRuntime,
522
+ onTransition: (handler, options) => on("transition", handler, options)
431
523
  };
524
+ const bootStateDef = def.states[snapshot.value];
525
+ if (bootStateDef?.sub) {
526
+ let newChild;
527
+ try {
528
+ newChild = createRuntime(bootStateDef.sub, bootStateDef.subImpl ?? {});
529
+ } catch (cause) {
530
+ throw new SubMachineError(snapshot.value, "init", cause);
531
+ }
532
+ childRuntime = newChild;
533
+ childAbortCleanup = wireChildAbort(newChild);
534
+ }
535
+ return runtime;
432
536
  }
433
537
 
434
538
  // src/fsm/definition.ts