jssm 5.141.0 → 5.141.2
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 +6 -6
- package/dist/cdn/instance.js +84 -49
- package/dist/cdn/viz.js +84 -49
- package/dist/cli/fsl-render.cjs +1 -1
- package/dist/cli/fsl.cjs +1 -1
- package/dist/deno/README.md +6 -6
- package/dist/deno/jssm.d.ts +13 -0
- package/dist/deno/jssm.js +1 -1
- 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 +13 -0
- package/jssm.es6.d.ts +13 -0
- package/jssm_viz.es5.d.cts +13 -0
- package/jssm_viz.es6.d.ts +13 -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.141.
|
|
21
|
+
* Generated for version 5.141.2 at 6/2/2026, 7:18:20 AM
|
|
22
22
|
|
|
23
23
|
-->
|
|
24
|
-
# jssm 5.141.
|
|
24
|
+
# jssm 5.141.2
|
|
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,
|
|
284
|
+
library.** 6,473 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,
|
|
417
|
+
***6,473 tests***, run 57,260 times.
|
|
418
418
|
|
|
419
|
-
- 5,
|
|
419
|
+
- 5,960 specs with 100.0% coverage
|
|
420
420
|
- 513 fuzz tests with 3.4% coverage
|
|
421
|
-
- 5,
|
|
421
|
+
- 5,566 TypeScript lines - 1.2 tests per line, 10.3 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
|
@@ -21327,7 +21327,7 @@ var constants = /*#__PURE__*/Object.freeze({
|
|
|
21327
21327
|
* Useful for runtime diagnostics and for embedding in serialized machine
|
|
21328
21328
|
* snapshots so that deserializers can detect version-skew.
|
|
21329
21329
|
*/
|
|
21330
|
-
const version = "5.141.
|
|
21330
|
+
const version = "5.141.2";
|
|
21331
21331
|
|
|
21332
21332
|
// whargarbl lots of these return arrays could/should be sets
|
|
21333
21333
|
const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
|
|
@@ -21622,6 +21622,7 @@ class Machine {
|
|
|
21622
21622
|
this._timeout_target_time = undefined;
|
|
21623
21623
|
this._after_mapping = new Map();
|
|
21624
21624
|
this._event_handlers = new Map();
|
|
21625
|
+
this._event_listener_count = 0;
|
|
21625
21626
|
this._firing_error = false;
|
|
21626
21627
|
// consolidate the state declarations
|
|
21627
21628
|
if (state_declaration) {
|
|
@@ -22978,12 +22979,28 @@ class Machine {
|
|
|
22978
22979
|
}
|
|
22979
22980
|
for (const entry of set) {
|
|
22980
22981
|
if (entry.handler === handler) {
|
|
22981
|
-
|
|
22982
|
+
this._unsubscribe_entry(set, entry);
|
|
22982
22983
|
return true;
|
|
22983
22984
|
}
|
|
22984
22985
|
}
|
|
22985
22986
|
return false;
|
|
22986
22987
|
}
|
|
22988
|
+
/**
|
|
22989
|
+
* Remove one event-subscription entry from its set and keep
|
|
22990
|
+
* {@link Machine._event_listener_count} in sync. The count is decremented
|
|
22991
|
+
* only when the entry was actually present, so calling a stale unsubscribe
|
|
22992
|
+
* closure (or removing an already-fired `once` entry) is idempotent and
|
|
22993
|
+
* cannot drive the count negative.
|
|
22994
|
+
*
|
|
22995
|
+
* @param set The per-event-name subscription set.
|
|
22996
|
+
* @param entry The entry to remove.
|
|
22997
|
+
* @internal
|
|
22998
|
+
*/
|
|
22999
|
+
_unsubscribe_entry(set, entry) {
|
|
23000
|
+
if (set.delete(entry)) {
|
|
23001
|
+
this._event_listener_count--;
|
|
23002
|
+
}
|
|
23003
|
+
}
|
|
22987
23004
|
/**
|
|
22988
23005
|
* Shared registration core used by {@link Machine.on} and
|
|
22989
23006
|
* {@link Machine.once}. Normalizes the optional filter argument and
|
|
@@ -23012,7 +23029,8 @@ class Machine {
|
|
|
23012
23029
|
}
|
|
23013
23030
|
const entry = { handler, filter, once };
|
|
23014
23031
|
set.add(entry);
|
|
23015
|
-
|
|
23032
|
+
this._event_listener_count++;
|
|
23033
|
+
return () => { this._unsubscribe_entry(set, entry); };
|
|
23016
23034
|
}
|
|
23017
23035
|
/**
|
|
23018
23036
|
* Dispatch an event to every registered subscriber in registration
|
|
@@ -23052,7 +23070,7 @@ class Machine {
|
|
|
23052
23070
|
// gets removed and so re-entrant `on` calls during the handler see
|
|
23053
23071
|
// the post-removal state.
|
|
23054
23072
|
if (entry.once) {
|
|
23055
|
-
|
|
23073
|
+
this._unsubscribe_entry(set, entry);
|
|
23056
23074
|
}
|
|
23057
23075
|
try {
|
|
23058
23076
|
entry.handler(detail);
|
|
@@ -23861,15 +23879,25 @@ class Machine {
|
|
|
23861
23879
|
newState = newStateOrAction;
|
|
23862
23880
|
}
|
|
23863
23881
|
}
|
|
23864
|
-
|
|
23865
|
-
|
|
23866
|
-
|
|
23867
|
-
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
|
|
23871
|
-
|
|
23872
|
-
|
|
23882
|
+
// hook_args is read only inside the `_has_hooks` / `_has_post_hooks`
|
|
23883
|
+
// blocks below. Skip building it for hook-free machines (every
|
|
23884
|
+
// chain/dense/hub/messy benchmark shape) so the hot path stops allocating
|
|
23885
|
+
// a 7-field object it never reads. The NonNullable cast keeps the type
|
|
23886
|
+
// unchanged for all downstream uses without introducing an impossible
|
|
23887
|
+
// (uncoverable) branch; the value is only dereferenced under the guards
|
|
23888
|
+
// that imply it was built. #670
|
|
23889
|
+
const hook_args_obj = (this._has_hooks || this._has_post_hooks)
|
|
23890
|
+
? {
|
|
23891
|
+
data: this._data,
|
|
23892
|
+
action: fromAction,
|
|
23893
|
+
from: this._state,
|
|
23894
|
+
to: newState,
|
|
23895
|
+
next_data: newData,
|
|
23896
|
+
forced: wasForced,
|
|
23897
|
+
trans_type
|
|
23898
|
+
}
|
|
23899
|
+
: undefined;
|
|
23900
|
+
const hook_args = hook_args_obj;
|
|
23873
23901
|
// 'action' event fires when an action is attempted, regardless of whether
|
|
23874
23902
|
// it ultimately succeeds — matches the issue spec for observation events.
|
|
23875
23903
|
if (wasAction) {
|
|
@@ -24155,46 +24183,53 @@ class Machine {
|
|
|
24155
24183
|
this._post_everything_hook(Object.assign(Object.assign({}, hook_args), { hook_name: 'post everything' }));
|
|
24156
24184
|
}
|
|
24157
24185
|
}
|
|
24158
|
-
//
|
|
24159
|
-
//
|
|
24160
|
-
//
|
|
24161
|
-
|
|
24162
|
-
|
|
24163
|
-
|
|
24164
|
-
|
|
24165
|
-
|
|
24166
|
-
|
|
24167
|
-
|
|
24168
|
-
|
|
24169
|
-
|
|
24170
|
-
|
|
24171
|
-
|
|
24172
|
-
|
|
24173
|
-
|
|
24174
|
-
|
|
24175
|
-
forced: wasForced
|
|
24176
|
-
});
|
|
24177
|
-
this._fire('entry', {
|
|
24178
|
-
state: newState,
|
|
24179
|
-
from: fromState,
|
|
24180
|
-
action: fromAction,
|
|
24181
|
-
data: newData_after
|
|
24182
|
-
});
|
|
24183
|
-
if (oldData !== newData_after) {
|
|
24184
|
-
this._fire('data-change', {
|
|
24186
|
+
// Observation events (#638) fire after the state is committed. Each call
|
|
24187
|
+
// builds a detail literal at the call site, so guard the whole block on a
|
|
24188
|
+
// live subscription count: with zero listeners (the common hot-path case,
|
|
24189
|
+
// and every benchmark shape) we skip all of these allocations entirely.
|
|
24190
|
+
// Read after pre-hooks, so a listener a pre-hook installed is still seen.
|
|
24191
|
+
// ('action' above and 'rejection' on the invalid path are intentionally
|
|
24192
|
+
// NOT under this gate — they fire regardless, and `_fire` itself no-ops
|
|
24193
|
+
// cheaply when that specific event has no subscribers.) #670
|
|
24194
|
+
if (this._event_listener_count !== 0) {
|
|
24195
|
+
const newData_after = this._data;
|
|
24196
|
+
this._fire('exit', {
|
|
24197
|
+
state: fromState,
|
|
24198
|
+
to: newState,
|
|
24199
|
+
action: fromAction,
|
|
24200
|
+
data: newData_after
|
|
24201
|
+
});
|
|
24202
|
+
this._fire('transition', {
|
|
24185
24203
|
from: fromState,
|
|
24186
24204
|
to: newState,
|
|
24187
24205
|
action: fromAction,
|
|
24188
|
-
|
|
24189
|
-
|
|
24190
|
-
|
|
24206
|
+
data: newData_after,
|
|
24207
|
+
next_data: newData,
|
|
24208
|
+
trans_type,
|
|
24209
|
+
forced: wasForced
|
|
24191
24210
|
});
|
|
24192
|
-
|
|
24193
|
-
|
|
24194
|
-
|
|
24195
|
-
|
|
24196
|
-
|
|
24197
|
-
|
|
24211
|
+
this._fire('entry', {
|
|
24212
|
+
state: newState,
|
|
24213
|
+
from: fromState,
|
|
24214
|
+
action: fromAction,
|
|
24215
|
+
data: newData_after
|
|
24216
|
+
});
|
|
24217
|
+
if (oldData !== newData_after) {
|
|
24218
|
+
this._fire('data-change', {
|
|
24219
|
+
from: fromState,
|
|
24220
|
+
to: newState,
|
|
24221
|
+
action: fromAction,
|
|
24222
|
+
old_data: oldData,
|
|
24223
|
+
new_data: newData_after,
|
|
24224
|
+
cause: 'transition'
|
|
24225
|
+
});
|
|
24226
|
+
}
|
|
24227
|
+
if (this.state_is_terminal(newState)) {
|
|
24228
|
+
this._fire('terminal', { state: newState, data: newData_after });
|
|
24229
|
+
}
|
|
24230
|
+
if (this.state_is_complete(newState)) {
|
|
24231
|
+
this._fire('complete', { state: newState, data: newData_after });
|
|
24232
|
+
}
|
|
24198
24233
|
}
|
|
24199
24234
|
// possibly re-establish new 'after' clause
|
|
24200
24235
|
this.auto_set_state_timeout();
|
package/dist/cdn/viz.js
CHANGED
|
@@ -21352,7 +21352,7 @@ var constants = /*#__PURE__*/Object.freeze({
|
|
|
21352
21352
|
* Useful for runtime diagnostics and for embedding in serialized machine
|
|
21353
21353
|
* snapshots so that deserializers can detect version-skew.
|
|
21354
21354
|
*/
|
|
21355
|
-
const version = "5.141.
|
|
21355
|
+
const version = "5.141.2";
|
|
21356
21356
|
|
|
21357
21357
|
// whargarbl lots of these return arrays could/should be sets
|
|
21358
21358
|
const { state_name_chars, state_name_first_chars, action_label_chars } = constants;
|
|
@@ -21647,6 +21647,7 @@ class Machine {
|
|
|
21647
21647
|
this._timeout_target_time = undefined;
|
|
21648
21648
|
this._after_mapping = new Map();
|
|
21649
21649
|
this._event_handlers = new Map();
|
|
21650
|
+
this._event_listener_count = 0;
|
|
21650
21651
|
this._firing_error = false;
|
|
21651
21652
|
// consolidate the state declarations
|
|
21652
21653
|
if (state_declaration) {
|
|
@@ -23003,12 +23004,28 @@ class Machine {
|
|
|
23003
23004
|
}
|
|
23004
23005
|
for (const entry of set) {
|
|
23005
23006
|
if (entry.handler === handler) {
|
|
23006
|
-
|
|
23007
|
+
this._unsubscribe_entry(set, entry);
|
|
23007
23008
|
return true;
|
|
23008
23009
|
}
|
|
23009
23010
|
}
|
|
23010
23011
|
return false;
|
|
23011
23012
|
}
|
|
23013
|
+
/**
|
|
23014
|
+
* Remove one event-subscription entry from its set and keep
|
|
23015
|
+
* {@link Machine._event_listener_count} in sync. The count is decremented
|
|
23016
|
+
* only when the entry was actually present, so calling a stale unsubscribe
|
|
23017
|
+
* closure (or removing an already-fired `once` entry) is idempotent and
|
|
23018
|
+
* cannot drive the count negative.
|
|
23019
|
+
*
|
|
23020
|
+
* @param set The per-event-name subscription set.
|
|
23021
|
+
* @param entry The entry to remove.
|
|
23022
|
+
* @internal
|
|
23023
|
+
*/
|
|
23024
|
+
_unsubscribe_entry(set, entry) {
|
|
23025
|
+
if (set.delete(entry)) {
|
|
23026
|
+
this._event_listener_count--;
|
|
23027
|
+
}
|
|
23028
|
+
}
|
|
23012
23029
|
/**
|
|
23013
23030
|
* Shared registration core used by {@link Machine.on} and
|
|
23014
23031
|
* {@link Machine.once}. Normalizes the optional filter argument and
|
|
@@ -23037,7 +23054,8 @@ class Machine {
|
|
|
23037
23054
|
}
|
|
23038
23055
|
const entry = { handler, filter, once };
|
|
23039
23056
|
set.add(entry);
|
|
23040
|
-
|
|
23057
|
+
this._event_listener_count++;
|
|
23058
|
+
return () => { this._unsubscribe_entry(set, entry); };
|
|
23041
23059
|
}
|
|
23042
23060
|
/**
|
|
23043
23061
|
* Dispatch an event to every registered subscriber in registration
|
|
@@ -23077,7 +23095,7 @@ class Machine {
|
|
|
23077
23095
|
// gets removed and so re-entrant `on` calls during the handler see
|
|
23078
23096
|
// the post-removal state.
|
|
23079
23097
|
if (entry.once) {
|
|
23080
|
-
|
|
23098
|
+
this._unsubscribe_entry(set, entry);
|
|
23081
23099
|
}
|
|
23082
23100
|
try {
|
|
23083
23101
|
entry.handler(detail);
|
|
@@ -23886,15 +23904,25 @@ class Machine {
|
|
|
23886
23904
|
newState = newStateOrAction;
|
|
23887
23905
|
}
|
|
23888
23906
|
}
|
|
23889
|
-
|
|
23890
|
-
|
|
23891
|
-
|
|
23892
|
-
|
|
23893
|
-
|
|
23894
|
-
|
|
23895
|
-
|
|
23896
|
-
|
|
23897
|
-
|
|
23907
|
+
// hook_args is read only inside the `_has_hooks` / `_has_post_hooks`
|
|
23908
|
+
// blocks below. Skip building it for hook-free machines (every
|
|
23909
|
+
// chain/dense/hub/messy benchmark shape) so the hot path stops allocating
|
|
23910
|
+
// a 7-field object it never reads. The NonNullable cast keeps the type
|
|
23911
|
+
// unchanged for all downstream uses without introducing an impossible
|
|
23912
|
+
// (uncoverable) branch; the value is only dereferenced under the guards
|
|
23913
|
+
// that imply it was built. #670
|
|
23914
|
+
const hook_args_obj = (this._has_hooks || this._has_post_hooks)
|
|
23915
|
+
? {
|
|
23916
|
+
data: this._data,
|
|
23917
|
+
action: fromAction,
|
|
23918
|
+
from: this._state,
|
|
23919
|
+
to: newState,
|
|
23920
|
+
next_data: newData,
|
|
23921
|
+
forced: wasForced,
|
|
23922
|
+
trans_type
|
|
23923
|
+
}
|
|
23924
|
+
: undefined;
|
|
23925
|
+
const hook_args = hook_args_obj;
|
|
23898
23926
|
// 'action' event fires when an action is attempted, regardless of whether
|
|
23899
23927
|
// it ultimately succeeds — matches the issue spec for observation events.
|
|
23900
23928
|
if (wasAction) {
|
|
@@ -24180,46 +24208,53 @@ class Machine {
|
|
|
24180
24208
|
this._post_everything_hook(Object.assign(Object.assign({}, hook_args), { hook_name: 'post everything' }));
|
|
24181
24209
|
}
|
|
24182
24210
|
}
|
|
24183
|
-
//
|
|
24184
|
-
//
|
|
24185
|
-
//
|
|
24186
|
-
|
|
24187
|
-
|
|
24188
|
-
|
|
24189
|
-
|
|
24190
|
-
|
|
24191
|
-
|
|
24192
|
-
|
|
24193
|
-
|
|
24194
|
-
|
|
24195
|
-
|
|
24196
|
-
|
|
24197
|
-
|
|
24198
|
-
|
|
24199
|
-
|
|
24200
|
-
forced: wasForced
|
|
24201
|
-
});
|
|
24202
|
-
this._fire('entry', {
|
|
24203
|
-
state: newState,
|
|
24204
|
-
from: fromState,
|
|
24205
|
-
action: fromAction,
|
|
24206
|
-
data: newData_after
|
|
24207
|
-
});
|
|
24208
|
-
if (oldData !== newData_after) {
|
|
24209
|
-
this._fire('data-change', {
|
|
24211
|
+
// Observation events (#638) fire after the state is committed. Each call
|
|
24212
|
+
// builds a detail literal at the call site, so guard the whole block on a
|
|
24213
|
+
// live subscription count: with zero listeners (the common hot-path case,
|
|
24214
|
+
// and every benchmark shape) we skip all of these allocations entirely.
|
|
24215
|
+
// Read after pre-hooks, so a listener a pre-hook installed is still seen.
|
|
24216
|
+
// ('action' above and 'rejection' on the invalid path are intentionally
|
|
24217
|
+
// NOT under this gate — they fire regardless, and `_fire` itself no-ops
|
|
24218
|
+
// cheaply when that specific event has no subscribers.) #670
|
|
24219
|
+
if (this._event_listener_count !== 0) {
|
|
24220
|
+
const newData_after = this._data;
|
|
24221
|
+
this._fire('exit', {
|
|
24222
|
+
state: fromState,
|
|
24223
|
+
to: newState,
|
|
24224
|
+
action: fromAction,
|
|
24225
|
+
data: newData_after
|
|
24226
|
+
});
|
|
24227
|
+
this._fire('transition', {
|
|
24210
24228
|
from: fromState,
|
|
24211
24229
|
to: newState,
|
|
24212
24230
|
action: fromAction,
|
|
24213
|
-
|
|
24214
|
-
|
|
24215
|
-
|
|
24231
|
+
data: newData_after,
|
|
24232
|
+
next_data: newData,
|
|
24233
|
+
trans_type,
|
|
24234
|
+
forced: wasForced
|
|
24216
24235
|
});
|
|
24217
|
-
|
|
24218
|
-
|
|
24219
|
-
|
|
24220
|
-
|
|
24221
|
-
|
|
24222
|
-
|
|
24236
|
+
this._fire('entry', {
|
|
24237
|
+
state: newState,
|
|
24238
|
+
from: fromState,
|
|
24239
|
+
action: fromAction,
|
|
24240
|
+
data: newData_after
|
|
24241
|
+
});
|
|
24242
|
+
if (oldData !== newData_after) {
|
|
24243
|
+
this._fire('data-change', {
|
|
24244
|
+
from: fromState,
|
|
24245
|
+
to: newState,
|
|
24246
|
+
action: fromAction,
|
|
24247
|
+
old_data: oldData,
|
|
24248
|
+
new_data: newData_after,
|
|
24249
|
+
cause: 'transition'
|
|
24250
|
+
});
|
|
24251
|
+
}
|
|
24252
|
+
if (this.state_is_terminal(newState)) {
|
|
24253
|
+
this._fire('terminal', { state: newState, data: newData_after });
|
|
24254
|
+
}
|
|
24255
|
+
if (this.state_is_complete(newState)) {
|
|
24256
|
+
this._fire('complete', { state: newState, data: newData_after });
|
|
24257
|
+
}
|
|
24223
24258
|
}
|
|
24224
24259
|
// possibly re-establish new 'after' clause
|
|
24225
24260
|
this.auto_set_state_timeout();
|