jssm 5.143.23 → 5.143.25
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 +7 -7
- package/dist/cdn/instance.js +55 -24
- package/dist/cdn/viz.js +55 -24
- package/dist/cli/fsl-render.cjs +1 -1
- package/dist/cli/fsl.cjs +1 -1
- package/dist/deno/README.md +7 -7
- package/dist/deno/jssm.d.ts +1 -0
- package/dist/deno/jssm.js +1 -1
- package/dist/deno/jssm_types.d.ts +9 -0
- package/dist/jssm.es5.cjs +1 -1
- package/dist/jssm.es5.iife.js +1 -1
- package/dist/jssm.es6.mjs +1 -1
- package/dist/jssm_viz.cjs +1 -1
- package/dist/jssm_viz.iife.cjs +1 -1
- package/dist/jssm_viz.mjs +1 -1
- package/jssm.es5.d.cts +10 -0
- package/jssm.es6.d.ts +10 -0
- package/jssm_viz.es5.d.cts +10 -0
- package/jssm_viz.es6.d.ts +10 -0
- package/package.json +1 -1
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.
|
|
21
|
+
* Generated for version 5.143.25 at 6/12/2026, 2:28:30 PM
|
|
22
22
|
|
|
23
23
|
-->
|
|
24
|
-
# jssm 5.143.
|
|
24
|
+
# jssm 5.143.25
|
|
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,
|
|
284
|
+
library.** 7,013 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,
|
|
417
|
+
***7,013 tests***, run 81,857 times.
|
|
418
418
|
|
|
419
|
-
- 6,
|
|
420
|
-
- 756 fuzz tests with 83.
|
|
421
|
-
- 5,
|
|
419
|
+
- 6,257 specs with 100.0% coverage
|
|
420
|
+
- 756 fuzz tests with 83.4% coverage
|
|
421
|
+
- 5,880 TypeScript lines - 1.2 tests per line, 13.9 generated tests per line
|
|
422
422
|
|
|
423
423
|
[](https://github.com/StoneCypher/jssm/actions)
|
|
424
424
|
[](https://www.npmjs.com/package/jssm)
|
package/dist/cdn/instance.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
22405
|
+
const version = "5.143.25";
|
|
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
|
-
//
|
|
22944
|
-
//
|
|
22945
|
-
//
|
|
22946
|
-
|
|
22947
|
-
|
|
22948
|
-
|
|
22949
|
-
if (
|
|
22950
|
-
|
|
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
|
-
|
|
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}"`);
|
|
@@ -25212,7 +25221,19 @@ class Machine {
|
|
|
25212
25221
|
const edgeId = (to_id === undefined) ? undefined : this._edge_id_by_pair.get(pair_key(this._state_id, to_id));
|
|
25213
25222
|
if ((edgeId !== undefined) && (!(this._edges[edgeId].forced_only))) {
|
|
25214
25223
|
if (this._has_transition_hooks || this._has_post_transition_hooks) {
|
|
25215
|
-
|
|
25224
|
+
// first matching outbound edge's kind, without building the result
|
|
25225
|
+
// array edges_between allocated here on every hooked transition.
|
|
25226
|
+
// First-match semantics are kept deliberately: _edge_map is
|
|
25227
|
+
// last-wins for multi-edge (from, to) pairs, so lookup_transition_for
|
|
25228
|
+
// could disagree with the old edges_between(...)[0]. #735
|
|
25229
|
+
// TODO this won't do the right thing if various edges have different types
|
|
25230
|
+
for (const ob_eid of this._outbound_edge_ids.get(this._state)) {
|
|
25231
|
+
const ob_edge = this._edges[ob_eid];
|
|
25232
|
+
if (ob_edge.to === newStateOrAction) {
|
|
25233
|
+
trans_type = ob_edge.kind;
|
|
25234
|
+
break;
|
|
25235
|
+
}
|
|
25236
|
+
}
|
|
25216
25237
|
}
|
|
25217
25238
|
valid = true;
|
|
25218
25239
|
newState = newStateOrAction;
|
|
@@ -25226,6 +25247,11 @@ class Machine {
|
|
|
25226
25247
|
// unchanged for all downstream uses without introducing an impossible
|
|
25227
25248
|
// (uncoverable) branch; the value is only dereferenced under the guards
|
|
25228
25249
|
// that imply it was built. #670
|
|
25250
|
+
// NOTE (#735): the { ...hook_args, hook_name } spreads at the four
|
|
25251
|
+
// everything-hook sites are contractual, not waste — handlers may capture
|
|
25252
|
+
// their context, and each captured context must durably carry its own
|
|
25253
|
+
// hook_name (pinned by the simultaneous-everything-hook specs). A shared
|
|
25254
|
+
// mutated object cannot satisfy that; do not "optimize" the spreads away.
|
|
25229
25255
|
const hook_args_obj = (this._has_hooks || this._has_post_hooks)
|
|
25230
25256
|
? {
|
|
25231
25257
|
data: this._data,
|
|
@@ -25585,10 +25611,15 @@ class Machine {
|
|
|
25585
25611
|
cause: 'transition'
|
|
25586
25612
|
});
|
|
25587
25613
|
}
|
|
25588
|
-
|
|
25614
|
+
// one state-record fetch answers both checks; newState is known-valid
|
|
25615
|
+
// here, and the public state_is_terminal / state_is_complete pair would
|
|
25616
|
+
// each redo has_state plus its own map walk. Same predicates:
|
|
25617
|
+
// terminal = no exits, complete = the constructor-set flag. #735
|
|
25618
|
+
const new_state_rec = this._states.get(newState);
|
|
25619
|
+
if (new_state_rec.to.length === 0) {
|
|
25589
25620
|
this._fire('terminal', { state: newState, data: newData_after });
|
|
25590
25621
|
}
|
|
25591
|
-
if (
|
|
25622
|
+
if (new_state_rec.complete) {
|
|
25592
25623
|
this._fire('complete', { state: newState, data: newData_after });
|
|
25593
25624
|
}
|
|
25594
25625
|
}
|
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
|
-
|
|
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.
|
|
22430
|
+
const version = "5.143.25";
|
|
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
|
-
//
|
|
22969
|
-
//
|
|
22970
|
-
//
|
|
22971
|
-
|
|
22972
|
-
|
|
22973
|
-
|
|
22974
|
-
if (
|
|
22975
|
-
|
|
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
|
-
|
|
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}"`);
|
|
@@ -25237,7 +25246,19 @@ class Machine {
|
|
|
25237
25246
|
const edgeId = (to_id === undefined) ? undefined : this._edge_id_by_pair.get(pair_key(this._state_id, to_id));
|
|
25238
25247
|
if ((edgeId !== undefined) && (!(this._edges[edgeId].forced_only))) {
|
|
25239
25248
|
if (this._has_transition_hooks || this._has_post_transition_hooks) {
|
|
25240
|
-
|
|
25249
|
+
// first matching outbound edge's kind, without building the result
|
|
25250
|
+
// array edges_between allocated here on every hooked transition.
|
|
25251
|
+
// First-match semantics are kept deliberately: _edge_map is
|
|
25252
|
+
// last-wins for multi-edge (from, to) pairs, so lookup_transition_for
|
|
25253
|
+
// could disagree with the old edges_between(...)[0]. #735
|
|
25254
|
+
// TODO this won't do the right thing if various edges have different types
|
|
25255
|
+
for (const ob_eid of this._outbound_edge_ids.get(this._state)) {
|
|
25256
|
+
const ob_edge = this._edges[ob_eid];
|
|
25257
|
+
if (ob_edge.to === newStateOrAction) {
|
|
25258
|
+
trans_type = ob_edge.kind;
|
|
25259
|
+
break;
|
|
25260
|
+
}
|
|
25261
|
+
}
|
|
25241
25262
|
}
|
|
25242
25263
|
valid = true;
|
|
25243
25264
|
newState = newStateOrAction;
|
|
@@ -25251,6 +25272,11 @@ class Machine {
|
|
|
25251
25272
|
// unchanged for all downstream uses without introducing an impossible
|
|
25252
25273
|
// (uncoverable) branch; the value is only dereferenced under the guards
|
|
25253
25274
|
// that imply it was built. #670
|
|
25275
|
+
// NOTE (#735): the { ...hook_args, hook_name } spreads at the four
|
|
25276
|
+
// everything-hook sites are contractual, not waste — handlers may capture
|
|
25277
|
+
// their context, and each captured context must durably carry its own
|
|
25278
|
+
// hook_name (pinned by the simultaneous-everything-hook specs). A shared
|
|
25279
|
+
// mutated object cannot satisfy that; do not "optimize" the spreads away.
|
|
25254
25280
|
const hook_args_obj = (this._has_hooks || this._has_post_hooks)
|
|
25255
25281
|
? {
|
|
25256
25282
|
data: this._data,
|
|
@@ -25610,10 +25636,15 @@ class Machine {
|
|
|
25610
25636
|
cause: 'transition'
|
|
25611
25637
|
});
|
|
25612
25638
|
}
|
|
25613
|
-
|
|
25639
|
+
// one state-record fetch answers both checks; newState is known-valid
|
|
25640
|
+
// here, and the public state_is_terminal / state_is_complete pair would
|
|
25641
|
+
// each redo has_state plus its own map walk. Same predicates:
|
|
25642
|
+
// terminal = no exits, complete = the constructor-set flag. #735
|
|
25643
|
+
const new_state_rec = this._states.get(newState);
|
|
25644
|
+
if (new_state_rec.to.length === 0) {
|
|
25614
25645
|
this._fire('terminal', { state: newState, data: newData_after });
|
|
25615
25646
|
}
|
|
25616
|
-
if (
|
|
25647
|
+
if (new_state_rec.complete) {
|
|
25617
25648
|
this._fire('complete', { state: newState, data: newData_after });
|
|
25618
25649
|
}
|
|
25619
25650
|
}
|