jssm 5.143.12 → 5.143.15

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
@@ -18,10 +18,10 @@ Please edit the file it's derived from, instead: `./src/md/readme_base.md`
18
18
 
19
19
 
20
20
 
21
- * Generated for version 5.143.12 at 6/12/2026, 8:38:46 AM
21
+ * Generated for version 5.143.15 at 6/12/2026, 9:37:20 AM
22
22
 
23
23
  -->
24
- # jssm 5.143.12
24
+ # jssm 5.143.15
25
25
 
26
26
  [**Try the live editor**](https://stonecypher.github.io/jssm-viz-demo/graph_explorer.html) ·
27
27
  [Documentation](https://stonecypher.github.io/jssm/docs/) ·
@@ -281,7 +281,7 @@ That decision shows up everywhere downstream:
281
281
  or run `npm run benny` against your own machine.
282
282
 
283
283
  - **More thoroughly tested than any other JavaScript state-machine
284
- library.** 6,952 tests at 100.0% line coverage
284
+ library.** 6,958 tests at 100.0% line coverage
285
285
  ([report](https://coveralls.io/github/StoneCypher/jssm)), plus
286
286
  fuzz testing via `fast-check`, with parser test data across ten natural
287
287
  languages and Emoji.
@@ -414,11 +414,11 @@ If your contribution is missing here, please open an issue.
414
414
 
415
415
  <br/>
416
416
 
417
- ***6,952 tests***, run 78,925 times.
417
+ ***6,958 tests***, run 78,931 times.
418
418
 
419
- - 6,225 specs with 100.0% coverage
420
- - 727 fuzz tests with 73.1% coverage
421
- - 5,871 TypeScript lines - 1.2 tests per line, 13.4 generated tests per line
419
+ - 6,231 specs with 100.0% coverage
420
+ - 727 fuzz tests with 72.8% coverage
421
+ - 5,865 TypeScript lines - 1.2 tests per line, 13.5 generated tests per line
422
422
 
423
423
  [![Actions Status](https://github.com/StoneCypher/jssm/workflows/Node%20CI/badge.svg)](https://github.com/StoneCypher/jssm/actions)
424
424
  [![NPM version](https://img.shields.io/npm/v/jssm.svg)](https://www.npmjs.com/package/jssm)
@@ -22788,7 +22788,7 @@ var constants = /*#__PURE__*/Object.freeze({
22788
22788
  * Useful for runtime diagnostics and for embedding in serialized machine
22789
22789
  * snapshots so that deserializers can detect version-skew.
22790
22790
  */
22791
- const version = "5.143.12";
22791
+ const version = "5.143.15";
22792
22792
 
22793
22793
  // whargarbl lots of these return arrays could/should be sets
22794
22794
  const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
@@ -24779,36 +24779,30 @@ class Machine {
24779
24779
  set_hook(HookDesc) {
24780
24780
  switch (HookDesc.kind) {
24781
24781
  case 'hook': {
24782
- // Nested by `from` then `to`; avoids JSON.stringify on every transition (#642).
24783
- let inner = this._hooks.get(HookDesc.from);
24784
- if (inner === undefined) {
24785
- inner = new Map();
24786
- this._hooks.set(HookDesc.from, inner);
24787
- }
24788
- inner.set(HookDesc.to, HookDesc.handler);
24782
+ // Numeric pair key (#729). intern() rather than id_of(): a hook may
24783
+ // name a state the machine doesn't have — it gets an id no live state
24784
+ // can match, so it registers silently and never fires, as before.
24785
+ this._hooks.set(pair_key(this._state_interner.intern(HookDesc.from), this._state_interner.intern(HookDesc.to)), HookDesc.handler);
24789
24786
  this._has_hooks = true;
24790
24787
  this._has_basic_hooks = true;
24791
24788
  break;
24792
24789
  }
24793
24790
  case 'named': {
24794
- // Nested by `from` then `to` then `action`; same rationale as 'hook' (#642).
24795
- let inner = this._named_hooks.get(HookDesc.from);
24791
+ // Numeric pair key, then action id; the per-pair action map stays a
24792
+ // map because the action interner may keep growing (#729).
24793
+ const pk = pair_key(this._state_interner.intern(HookDesc.from), this._state_interner.intern(HookDesc.to));
24794
+ let inner = this._named_hooks.get(pk);
24796
24795
  if (inner === undefined) {
24797
24796
  inner = new Map();
24798
- this._named_hooks.set(HookDesc.from, inner);
24799
- }
24800
- let inner2 = inner.get(HookDesc.to);
24801
- if (inner2 === undefined) {
24802
- inner2 = new Map();
24803
- inner.set(HookDesc.to, inner2);
24797
+ this._named_hooks.set(pk, inner);
24804
24798
  }
24805
- inner2.set(HookDesc.action, HookDesc.handler);
24799
+ inner.set(this._action_interner.intern(HookDesc.action), HookDesc.handler);
24806
24800
  this._has_hooks = true;
24807
24801
  this._has_named_hooks = true;
24808
24802
  break;
24809
24803
  }
24810
24804
  case 'global action':
24811
- this._global_action_hooks.set(HookDesc.action, HookDesc.handler);
24805
+ this._global_action_hooks.set(this._action_interner.intern(HookDesc.action), HookDesc.handler);
24812
24806
  this._has_hooks = true;
24813
24807
  this._has_global_action_hooks = true;
24814
24808
  break;
@@ -24836,12 +24830,12 @@ class Machine {
24836
24830
  this._has_hooks = true;
24837
24831
  break;
24838
24832
  case 'entry':
24839
- this._entry_hooks.set(HookDesc.to, HookDesc.handler);
24833
+ this._entry_hooks.set(this._state_interner.intern(HookDesc.to), HookDesc.handler);
24840
24834
  this._has_hooks = true;
24841
24835
  this._has_entry_hooks = true;
24842
24836
  break;
24843
24837
  case 'exit':
24844
- this._exit_hooks.set(HookDesc.from, HookDesc.handler);
24838
+ this._exit_hooks.set(this._state_interner.intern(HookDesc.from), HookDesc.handler);
24845
24839
  this._has_hooks = true;
24846
24840
  this._has_exit_hooks = true;
24847
24841
  break;
@@ -24851,36 +24845,27 @@ class Machine {
24851
24845
  this._has_after_hooks = true;
24852
24846
  break;
24853
24847
  case 'post hook': {
24854
- // Nested by `from` then `to`; same rationale as 'hook' (#642).
24855
- let inner = this._post_hooks.get(HookDesc.from);
24856
- if (inner === undefined) {
24857
- inner = new Map();
24858
- this._post_hooks.set(HookDesc.from, inner);
24859
- }
24860
- inner.set(HookDesc.to, HookDesc.handler);
24848
+ // Numeric pair key; same rationale as 'hook' (#729).
24849
+ this._post_hooks.set(pair_key(this._state_interner.intern(HookDesc.from), this._state_interner.intern(HookDesc.to)), HookDesc.handler);
24861
24850
  this._has_post_hooks = true;
24862
24851
  this._has_post_basic_hooks = true;
24863
24852
  break;
24864
24853
  }
24865
24854
  case 'post named': {
24866
- // Nested by `from` then `to` then `action`; same rationale as 'hook' (#642).
24867
- let inner = this._post_named_hooks.get(HookDesc.from);
24855
+ // Numeric pair key, then action id; same rationale as 'named' (#729).
24856
+ const pk = pair_key(this._state_interner.intern(HookDesc.from), this._state_interner.intern(HookDesc.to));
24857
+ let inner = this._post_named_hooks.get(pk);
24868
24858
  if (inner === undefined) {
24869
24859
  inner = new Map();
24870
- this._post_named_hooks.set(HookDesc.from, inner);
24871
- }
24872
- let inner2 = inner.get(HookDesc.to);
24873
- if (inner2 === undefined) {
24874
- inner2 = new Map();
24875
- inner.set(HookDesc.to, inner2);
24860
+ this._post_named_hooks.set(pk, inner);
24876
24861
  }
24877
- inner2.set(HookDesc.action, HookDesc.handler);
24862
+ inner.set(this._action_interner.intern(HookDesc.action), HookDesc.handler);
24878
24863
  this._has_post_hooks = true;
24879
24864
  this._has_post_named_hooks = true;
24880
24865
  break;
24881
24866
  }
24882
24867
  case 'post global action':
24883
- this._post_global_action_hooks.set(HookDesc.action, HookDesc.handler);
24868
+ this._post_global_action_hooks.set(this._action_interner.intern(HookDesc.action), HookDesc.handler);
24884
24869
  this._has_post_hooks = true;
24885
24870
  this._has_post_global_action_hooks = true;
24886
24871
  break;
@@ -24908,12 +24893,12 @@ class Machine {
24908
24893
  this._has_post_hooks = true;
24909
24894
  break;
24910
24895
  case 'post entry':
24911
- this._post_entry_hooks.set(HookDesc.to, HookDesc.handler);
24896
+ this._post_entry_hooks.set(this._state_interner.intern(HookDesc.to), HookDesc.handler);
24912
24897
  this._has_post_entry_hooks = true;
24913
24898
  this._has_post_hooks = true;
24914
24899
  break;
24915
24900
  case 'post exit':
24916
- this._post_exit_hooks.set(HookDesc.from, HookDesc.handler);
24901
+ this._post_exit_hooks.set(this._state_interner.intern(HookDesc.from), HookDesc.handler);
24917
24902
  this._has_post_exit_hooks = true;
24918
24903
  this._has_post_hooks = true;
24919
24904
  break;
@@ -24964,25 +24949,23 @@ class Machine {
24964
24949
  let removed = false;
24965
24950
  switch (HookDesc.kind) {
24966
24951
  case 'hook': {
24967
- const inner = this._hooks.get(HookDesc.from);
24968
- if (inner !== undefined && inner.has(HookDesc.to)) {
24969
- inner.delete(HookDesc.to);
24970
- removed = true;
24971
- }
24952
+ // id_of, not intern: removal of an unknown name reports false and
24953
+ // must not grow the interner tables (#729).
24954
+ const fid = this._state_interner.id_of(HookDesc.from), tid = this._state_interner.id_of(HookDesc.to);
24955
+ removed = (fid !== undefined) && (tid !== undefined) && this._hooks.delete(pair_key(fid, tid));
24972
24956
  break;
24973
24957
  }
24974
24958
  case 'named': {
24975
- const inner = this._named_hooks.get(HookDesc.from);
24976
- const inner2 = inner === undefined ? undefined : inner.get(HookDesc.to);
24977
- if (inner2 !== undefined && inner2.has(HookDesc.action)) {
24978
- inner2.delete(HookDesc.action);
24979
- removed = true;
24980
- }
24959
+ const fid = this._state_interner.id_of(HookDesc.from), tid = this._state_interner.id_of(HookDesc.to), aid = this._action_interner.id_of(HookDesc.action);
24960
+ const inner = ((fid === undefined) || (tid === undefined)) ? undefined : this._named_hooks.get(pair_key(fid, tid));
24961
+ removed = (inner !== undefined) && (aid !== undefined) && inner.delete(aid);
24981
24962
  break;
24982
24963
  }
24983
- case 'global action':
24984
- removed = this._global_action_hooks.delete(HookDesc.action);
24964
+ case 'global action': {
24965
+ const aid = this._action_interner.id_of(HookDesc.action);
24966
+ removed = (aid !== undefined) && this._global_action_hooks.delete(aid);
24985
24967
  break;
24968
+ }
24986
24969
  case 'any action':
24987
24970
  if (this._any_action_hook !== undefined) {
24988
24971
  this._any_action_hook = undefined;
@@ -25013,35 +24996,35 @@ class Machine {
25013
24996
  removed = true;
25014
24997
  }
25015
24998
  break;
25016
- case 'entry':
25017
- removed = this._entry_hooks.delete(HookDesc.to);
24999
+ case 'entry': {
25000
+ const tid = this._state_interner.id_of(HookDesc.to);
25001
+ removed = (tid !== undefined) && this._entry_hooks.delete(tid);
25018
25002
  break;
25019
- case 'exit':
25020
- removed = this._exit_hooks.delete(HookDesc.from);
25003
+ }
25004
+ case 'exit': {
25005
+ const fid = this._state_interner.id_of(HookDesc.from);
25006
+ removed = (fid !== undefined) && this._exit_hooks.delete(fid);
25021
25007
  break;
25008
+ }
25022
25009
  case 'after':
25023
25010
  removed = this._after_hooks.delete(HookDesc.from);
25024
25011
  break;
25025
25012
  case 'post hook': {
25026
- const inner = this._post_hooks.get(HookDesc.from);
25027
- if (inner !== undefined && inner.has(HookDesc.to)) {
25028
- inner.delete(HookDesc.to);
25029
- removed = true;
25030
- }
25013
+ const fid = this._state_interner.id_of(HookDesc.from), tid = this._state_interner.id_of(HookDesc.to);
25014
+ removed = (fid !== undefined) && (tid !== undefined) && this._post_hooks.delete(pair_key(fid, tid));
25031
25015
  break;
25032
25016
  }
25033
25017
  case 'post named': {
25034
- const inner = this._post_named_hooks.get(HookDesc.from);
25035
- const inner2 = inner === undefined ? undefined : inner.get(HookDesc.to);
25036
- if (inner2 !== undefined && inner2.has(HookDesc.action)) {
25037
- inner2.delete(HookDesc.action);
25038
- removed = true;
25039
- }
25018
+ const fid = this._state_interner.id_of(HookDesc.from), tid = this._state_interner.id_of(HookDesc.to), aid = this._action_interner.id_of(HookDesc.action);
25019
+ const inner = ((fid === undefined) || (tid === undefined)) ? undefined : this._post_named_hooks.get(pair_key(fid, tid));
25020
+ removed = (inner !== undefined) && (aid !== undefined) && inner.delete(aid);
25040
25021
  break;
25041
25022
  }
25042
- case 'post global action':
25043
- removed = this._post_global_action_hooks.delete(HookDesc.action);
25023
+ case 'post global action': {
25024
+ const aid = this._action_interner.id_of(HookDesc.action);
25025
+ removed = (aid !== undefined) && this._post_global_action_hooks.delete(aid);
25044
25026
  break;
25027
+ }
25045
25028
  case 'post any action':
25046
25029
  if (this._post_any_action_hook !== undefined) {
25047
25030
  this._post_any_action_hook = undefined;
@@ -25072,12 +25055,16 @@ class Machine {
25072
25055
  removed = true;
25073
25056
  }
25074
25057
  break;
25075
- case 'post entry':
25076
- removed = this._post_entry_hooks.delete(HookDesc.to);
25058
+ case 'post entry': {
25059
+ const tid = this._state_interner.id_of(HookDesc.to);
25060
+ removed = (tid !== undefined) && this._post_entry_hooks.delete(tid);
25077
25061
  break;
25078
- case 'post exit':
25079
- removed = this._post_exit_hooks.delete(HookDesc.from);
25062
+ }
25063
+ case 'post exit': {
25064
+ const fid = this._state_interner.id_of(HookDesc.from);
25065
+ removed = (fid !== undefined) && this._post_exit_hooks.delete(fid);
25080
25066
  break;
25067
+ }
25081
25068
  case 'pre everything':
25082
25069
  if (this._pre_everything_hook !== undefined) {
25083
25070
  this._pre_everything_hook = undefined;
@@ -25561,7 +25548,7 @@ class Machine {
25561
25548
  *
25562
25549
  */
25563
25550
  transition_impl(newStateOrAction, newData, wasForced, wasAction) {
25564
- let valid = false, trans_type, newState, newStateId = NaN, fromAction = undefined;
25551
+ let valid = false, trans_type, newState, newStateId = NaN, actionId = NaN, fromAction = undefined;
25565
25552
  if (wasForced) {
25566
25553
  // numeric inline of valid_force_transition: any existing edge
25567
25554
  // qualifies, forced or not. one string probe (the user's target name)
@@ -25577,15 +25564,18 @@ class Machine {
25577
25564
  }
25578
25565
  else if (wasAction) {
25579
25566
  // single numeric resolution: the old path looked the action up twice,
25580
- // once inside valid_action and again inside current_action_edge_for
25581
- const edgeId = this.current_action_for(newStateOrAction);
25582
- if ((edgeId !== undefined) && (edgeId !== null)) {
25567
+ // once inside valid_action and again inside current_action_edge_for.
25568
+ // aid is captured for the numeric hook probes below (#729).
25569
+ const aid = this._action_interner.id_of(newStateOrAction);
25570
+ const edgeId = (aid === undefined) ? undefined : this._edge_id_by_action_pair.get(pair_key(aid, this._state_id));
25571
+ if (edgeId !== undefined) {
25583
25572
  const edge = this._edges[edgeId];
25584
25573
  valid = true;
25585
25574
  trans_type = edge.kind;
25586
25575
  newState = edge.to;
25587
25576
  newStateId = this._edge_to_ids[edgeId];
25588
25577
  fromAction = newStateOrAction;
25578
+ actionId = aid;
25589
25579
  }
25590
25580
  }
25591
25581
  else {
@@ -25638,8 +25628,11 @@ class Machine {
25638
25628
  }
25639
25629
  }
25640
25630
  // Captured pre-transition source state so 'data-change' detail and similar
25641
- // events can name where we came from.
25631
+ // events can name where we came from. fromStateId mirrors it for the
25632
+ // numeric post-hook probes: by the time they run, _state_id is already
25633
+ // the destination (#729).
25642
25634
  const fromState = this._state;
25635
+ const fromStateId = this._state_id;
25643
25636
  const oldData = this._data;
25644
25637
  if (valid) {
25645
25638
  // once validity is known, clear old 'after' timeout clause. This must
@@ -25672,7 +25665,7 @@ class Machine {
25672
25665
  data_changed = true;
25673
25666
  }
25674
25667
  // 1b. global specific action hook
25675
- const outcome2 = abstract_hook_step(this._global_action_hooks.get(newStateOrAction), hook_args);
25668
+ const outcome2 = abstract_hook_step(this._global_action_hooks.get(actionId), hook_args);
25676
25669
  if (outcome2.pass === false) {
25677
25670
  this._fire_hook_rejection('global action', fromState, newState, fromAction, oldData, newData, wasForced);
25678
25671
  return false;
@@ -25703,7 +25696,7 @@ class Machine {
25703
25696
  }
25704
25697
  // 4. exit hook
25705
25698
  if (this._has_exit_hooks) {
25706
- const outcome = abstract_hook_step(this._exit_hooks.get(this._state), hook_args);
25699
+ const outcome = abstract_hook_step(this._exit_hooks.get(this._state_id), hook_args);
25707
25700
  if (outcome.pass === false) {
25708
25701
  this._fire_hook_rejection('exit', fromState, newState, fromAction, oldData, newData, wasForced);
25709
25702
  return false;
@@ -25715,11 +25708,9 @@ class Machine {
25715
25708
  // 5. named transition / action hook
25716
25709
  if (this._has_named_hooks) {
25717
25710
  if (wasAction) {
25718
- // Nested lookup: from -> to -> action. Each step is a small Map keyed by
25719
- // an already-interned state/action name; no per-call string allocation.
25720
- const byTo = this._named_hooks.get(this._state);
25721
- const byAct = byTo === undefined ? undefined : byTo.get(newState);
25722
- const nh = byAct === undefined ? undefined : byAct.get(newStateOrAction);
25711
+ // Numeric pair probe, then the action id captured at dispatch (#729).
25712
+ const byPair = this._named_hooks.get(pair_key(this._state_id, newStateId));
25713
+ const nh = byPair === undefined ? undefined : byPair.get(actionId);
25723
25714
  const outcome = abstract_hook_step(nh, hook_args);
25724
25715
  if (outcome.pass === false) {
25725
25716
  this._fire_hook_rejection('named', fromState, newState, fromAction, oldData, newData, wasForced);
@@ -25732,9 +25723,8 @@ class Machine {
25732
25723
  }
25733
25724
  // 6. regular hook
25734
25725
  if (this._has_basic_hooks) {
25735
- // Nested lookup: from -> to. See note on _hooks declaration (#642).
25736
- const byTo = this._hooks.get(this._state);
25737
- const h = byTo === undefined ? undefined : byTo.get(newState);
25726
+ // Numeric pair probe (#729); one integer hash replaces two string maps.
25727
+ const h = this._hooks.get(pair_key(this._state_id, newStateId));
25738
25728
  const outcome = abstract_hook_step(h, hook_args);
25739
25729
  if (outcome.pass === false) {
25740
25730
  this._fire_hook_rejection('hook', fromState, newState, fromAction, oldData, newData, wasForced);
@@ -25780,7 +25770,7 @@ class Machine {
25780
25770
  }
25781
25771
  // 8. entry hook
25782
25772
  if (this._has_entry_hooks) {
25783
- const outcome = abstract_hook_step(this._entry_hooks.get(newState), hook_args);
25773
+ const outcome = abstract_hook_step(this._entry_hooks.get(newStateId), hook_args);
25784
25774
  if (outcome.pass === false) {
25785
25775
  this._fire_hook_rejection('entry', fromState, newState, fromAction, oldData, newData, wasForced);
25786
25776
  return false;
@@ -25861,7 +25851,7 @@ class Machine {
25861
25851
  this._post_any_action_hook(hook_args);
25862
25852
  }
25863
25853
  // 2. global specific action hook
25864
- const pgah = this._post_global_action_hooks.get(hook_args.action);
25854
+ const pgah = this._post_global_action_hooks.get(actionId);
25865
25855
  if (pgah !== undefined) {
25866
25856
  pgah(hook_args);
25867
25857
  }
@@ -25872,7 +25862,7 @@ class Machine {
25872
25862
  }
25873
25863
  // 4. exit hook
25874
25864
  if (this._has_post_exit_hooks) {
25875
- const peh = this._post_exit_hooks.get(hook_args.from); // todo this is probably from instead
25865
+ const peh = this._post_exit_hooks.get(fromStateId);
25876
25866
  if (peh !== undefined) {
25877
25867
  peh(hook_args);
25878
25868
  }
@@ -25880,10 +25870,9 @@ class Machine {
25880
25870
  // 5. named transition / action hook
25881
25871
  if (this._has_post_named_hooks) {
25882
25872
  if (wasAction) {
25883
- // Nested lookup: from -> to -> action. See note on _post_named_hooks (#642).
25884
- const byTo = this._post_named_hooks.get(hook_args.from);
25885
- const byAct = byTo === undefined ? undefined : byTo.get(hook_args.to);
25886
- const pnh = byAct === undefined ? undefined : byAct.get(hook_args.action);
25873
+ // Numeric pair probe, then the action id captured at dispatch (#729).
25874
+ const byPair = this._post_named_hooks.get(pair_key(fromStateId, newStateId));
25875
+ const pnh = byPair === undefined ? undefined : byPair.get(actionId);
25887
25876
  if (pnh !== undefined) {
25888
25877
  pnh(hook_args);
25889
25878
  }
@@ -25891,9 +25880,8 @@ class Machine {
25891
25880
  }
25892
25881
  // 6. regular hook
25893
25882
  if (this._has_post_basic_hooks) {
25894
- // Nested lookup: from -> to. See note on _post_hooks (#642).
25895
- const byTo = this._post_hooks.get(hook_args.from);
25896
- const hook = byTo === undefined ? undefined : byTo.get(hook_args.to);
25883
+ // Numeric pair probe (#729).
25884
+ const hook = this._post_hooks.get(pair_key(fromStateId, newStateId));
25897
25885
  if (hook !== undefined) {
25898
25886
  hook(hook_args);
25899
25887
  }
@@ -25919,7 +25907,7 @@ class Machine {
25919
25907
  }
25920
25908
  // 8. entry hook
25921
25909
  if (this._has_post_entry_hooks) {
25922
- const hook = this._post_entry_hooks.get(hook_args.to);
25910
+ const hook = this._post_entry_hooks.get(newStateId);
25923
25911
  if (hook !== undefined) {
25924
25912
  hook(hook_args);
25925
25913
  }