jssm 5.143.23 → 5.143.24

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.23 at 6/12/2026, 1:50:43 PM
21
+ * Generated for version 5.143.24 at 6/12/2026, 2:12:45 PM
22
22
 
23
23
  -->
24
- # jssm 5.143.23
24
+ # jssm 5.143.24
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.** 7,009 tests at 100.0% line coverage
284
+ library.** 7,012 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
- ***7,009 tests***, run 81,853 times.
417
+ ***7,012 tests***, run 81,856 times.
418
418
 
419
- - 6,253 specs with 100.0% coverage
420
- - 756 fuzz tests with 83.6% coverage
421
- - 5,870 TypeScript lines - 1.2 tests per line, 13.9 generated tests per line
419
+ - 6,256 specs with 100.0% coverage
420
+ - 756 fuzz tests with 83.5% coverage
421
+ - 5,876 TypeScript lines - 1.2 tests per line, 13.9 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)
@@ -21804,7 +21804,9 @@ function compile(tree) {
21804
21804
  throw new JssmError(undefined, `A state may only bind a property once (${sd.state} re-binds ${decl.name})`, { source_location: nth_matching_loc(tree, (n) => n.key === 'state_declaration' && n.name === sd.state, 1) });
21805
21805
  }
21806
21806
  else {
21807
- result_cfg.state_property.push({ name: label, default_value: decl.value });
21807
+ // property/state carry the unserialized pair so the constructor can
21808
+ // validate bindings without JSON.parse-ing label back apart (#734)
21809
+ result_cfg.state_property.push({ name: label, default_value: decl.value, property: decl.name, state: sd.state });
21808
21810
  }
21809
21811
  }
21810
21812
  });
@@ -22400,7 +22402,7 @@ var constants = /*#__PURE__*/Object.freeze({
22400
22402
  * Useful for runtime diagnostics and for embedding in serialized machine
22401
22403
  * snapshots so that deserializers can detect version-skew.
22402
22404
  */
22403
- const version = "5.143.23";
22405
+ const version = "5.143.24";
22404
22406
 
22405
22407
  // whargarbl lots of these return arrays could/should be sets
22406
22408
  const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
@@ -22734,6 +22736,7 @@ class Machine {
22734
22736
  this._default_properties = new Map();
22735
22737
  this._state_properties = new Map();
22736
22738
  this._required_properties = new Set();
22739
+ this._state_property_first_state = new Map();
22737
22740
  this._state_style = state_style_condense(default_state_config, this);
22738
22741
  this._active_state_style = state_style_condense(default_active_state_config, this);
22739
22742
  this._hooked_state_style = state_style_condense(default_hooked_state_config, this);
@@ -22921,6 +22924,19 @@ class Machine {
22921
22924
  if (Array.isArray(state_property)) {
22922
22925
  state_property.forEach(sp => {
22923
22926
  this._state_properties.set(sp.name, sp.default_value);
22927
+ // Record the unserialized (property, state) pair for post-build
22928
+ // validation. The compiler writes both fields; a hand-built config
22929
+ // that carries only the serialized name pays one JSON.parse here,
22930
+ // which is what every binding used to pay at validation time (#734).
22931
+ let j_property = sp.property, j_state = sp.state;
22932
+ if ((j_property === undefined) || (j_state === undefined)) {
22933
+ const inside = JSON.parse(sp.name);
22934
+ j_property = inside[0];
22935
+ j_state = inside[1];
22936
+ }
22937
+ if (!(this._state_property_first_state.has(j_property))) {
22938
+ this._state_property_first_state.set(j_property, j_state);
22939
+ }
22924
22940
  });
22925
22941
  }
22926
22942
  // set initial state either from the specified or the start state list. validate admission behavior.
@@ -22940,32 +22956,25 @@ class Machine {
22940
22956
  }
22941
22957
  // done building, do checks
22942
22958
  // assert all props are valid
22943
- // _state_properties keys are always JSON.stringify([string, string]),
22944
- // built by name_bind_prop_and_state which throws on non-strings so the
22945
- // non-array / non-string else-arms below are unreachable, not untested.
22946
- this._state_properties.forEach((_value, key) => {
22947
- const inside = JSON.parse(key);
22948
- /* v8 ignore else */
22949
- if (Array.isArray(inside)) {
22950
- const j_property = inside[0];
22951
- /* v8 ignore else */
22952
- if (typeof j_property === 'string') {
22953
- const j_state = inside[1];
22954
- /* v8 ignore else */
22955
- if (typeof j_state === 'string') {
22956
- if (!(this.known_prop(j_property))) {
22957
- throw new JssmError(this, `State "${j_state}" has property "${j_property}" which is not globally declared`);
22958
- }
22959
- }
22960
- }
22959
+ // provenance pairs were recorded at insertion — first state per property,
22960
+ // in first-binding order replacing the old JSON.parse of every
22961
+ // serialized key; the error fires for the same binding it always did,
22962
+ // because the first property in first-binding order whose name is
22963
+ // undeclared owns the earliest undeclared binding.
22964
+ this._state_property_first_state.forEach((j_state, j_property) => {
22965
+ if (!(this.known_prop(j_property))) {
22966
+ throw new JssmError(this, `State "${j_state}" has property "${j_property}" which is not globally declared`);
22961
22967
  }
22962
22968
  });
22963
22969
  // assert all required properties are serviced
22970
+ // states() allocates a fresh array per call, so take it once rather than
22971
+ // once per required property
22972
+ const all_states_for_props = this.states();
22964
22973
  this._required_properties.forEach(dp_key => {
22965
22974
  if (this._default_properties.has(dp_key)) {
22966
22975
  throw new JssmError(this, `The property "${dp_key}" is required, but also has a default; these conflict`);
22967
22976
  }
22968
- this.states().forEach(s => {
22977
+ all_states_for_props.forEach(s => {
22969
22978
  const bound_name = name_bind_prop_and_state(dp_key, s);
22970
22979
  if (!(this._state_properties.has(bound_name))) {
22971
22980
  throw new JssmError(this, `State "${s}" is missing required property "${dp_key}"`);
package/dist/cdn/viz.js CHANGED
@@ -21829,7 +21829,9 @@ function compile(tree) {
21829
21829
  throw new JssmError(undefined, `A state may only bind a property once (${sd.state} re-binds ${decl.name})`, { source_location: nth_matching_loc(tree, (n) => n.key === 'state_declaration' && n.name === sd.state, 1) });
21830
21830
  }
21831
21831
  else {
21832
- result_cfg.state_property.push({ name: label, default_value: decl.value });
21832
+ // property/state carry the unserialized pair so the constructor can
21833
+ // validate bindings without JSON.parse-ing label back apart (#734)
21834
+ result_cfg.state_property.push({ name: label, default_value: decl.value, property: decl.name, state: sd.state });
21833
21835
  }
21834
21836
  }
21835
21837
  });
@@ -22425,7 +22427,7 @@ var constants = /*#__PURE__*/Object.freeze({
22425
22427
  * Useful for runtime diagnostics and for embedding in serialized machine
22426
22428
  * snapshots so that deserializers can detect version-skew.
22427
22429
  */
22428
- const version = "5.143.23";
22430
+ const version = "5.143.24";
22429
22431
 
22430
22432
  // whargarbl lots of these return arrays could/should be sets
22431
22433
  const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
@@ -22759,6 +22761,7 @@ class Machine {
22759
22761
  this._default_properties = new Map();
22760
22762
  this._state_properties = new Map();
22761
22763
  this._required_properties = new Set();
22764
+ this._state_property_first_state = new Map();
22762
22765
  this._state_style = state_style_condense(default_state_config, this);
22763
22766
  this._active_state_style = state_style_condense(default_active_state_config, this);
22764
22767
  this._hooked_state_style = state_style_condense(default_hooked_state_config, this);
@@ -22946,6 +22949,19 @@ class Machine {
22946
22949
  if (Array.isArray(state_property)) {
22947
22950
  state_property.forEach(sp => {
22948
22951
  this._state_properties.set(sp.name, sp.default_value);
22952
+ // Record the unserialized (property, state) pair for post-build
22953
+ // validation. The compiler writes both fields; a hand-built config
22954
+ // that carries only the serialized name pays one JSON.parse here,
22955
+ // which is what every binding used to pay at validation time (#734).
22956
+ let j_property = sp.property, j_state = sp.state;
22957
+ if ((j_property === undefined) || (j_state === undefined)) {
22958
+ const inside = JSON.parse(sp.name);
22959
+ j_property = inside[0];
22960
+ j_state = inside[1];
22961
+ }
22962
+ if (!(this._state_property_first_state.has(j_property))) {
22963
+ this._state_property_first_state.set(j_property, j_state);
22964
+ }
22949
22965
  });
22950
22966
  }
22951
22967
  // set initial state either from the specified or the start state list. validate admission behavior.
@@ -22965,32 +22981,25 @@ class Machine {
22965
22981
  }
22966
22982
  // done building, do checks
22967
22983
  // assert all props are valid
22968
- // _state_properties keys are always JSON.stringify([string, string]),
22969
- // built by name_bind_prop_and_state which throws on non-strings so the
22970
- // non-array / non-string else-arms below are unreachable, not untested.
22971
- this._state_properties.forEach((_value, key) => {
22972
- const inside = JSON.parse(key);
22973
- /* v8 ignore else */
22974
- if (Array.isArray(inside)) {
22975
- const j_property = inside[0];
22976
- /* v8 ignore else */
22977
- if (typeof j_property === 'string') {
22978
- const j_state = inside[1];
22979
- /* v8 ignore else */
22980
- if (typeof j_state === 'string') {
22981
- if (!(this.known_prop(j_property))) {
22982
- throw new JssmError(this, `State "${j_state}" has property "${j_property}" which is not globally declared`);
22983
- }
22984
- }
22985
- }
22984
+ // provenance pairs were recorded at insertion — first state per property,
22985
+ // in first-binding order replacing the old JSON.parse of every
22986
+ // serialized key; the error fires for the same binding it always did,
22987
+ // because the first property in first-binding order whose name is
22988
+ // undeclared owns the earliest undeclared binding.
22989
+ this._state_property_first_state.forEach((j_state, j_property) => {
22990
+ if (!(this.known_prop(j_property))) {
22991
+ throw new JssmError(this, `State "${j_state}" has property "${j_property}" which is not globally declared`);
22986
22992
  }
22987
22993
  });
22988
22994
  // assert all required properties are serviced
22995
+ // states() allocates a fresh array per call, so take it once rather than
22996
+ // once per required property
22997
+ const all_states_for_props = this.states();
22989
22998
  this._required_properties.forEach(dp_key => {
22990
22999
  if (this._default_properties.has(dp_key)) {
22991
23000
  throw new JssmError(this, `The property "${dp_key}" is required, but also has a default; these conflict`);
22992
23001
  }
22993
- this.states().forEach(s => {
23002
+ all_states_for_props.forEach(s => {
22994
23003
  const bound_name = name_bind_prop_and_state(dp_key, s);
22995
23004
  if (!(this._state_properties.has(bound_name))) {
22996
23005
  throw new JssmError(this, `State "${s}" is missing required property "${dp_key}"`);