jssm 5.133.0 → 5.134.0
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/viz.js +421 -2
- 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 +131 -1
- package/dist/deno/jssm.js +1 -1
- package/dist/deno/jssm_types.d.ts +208 -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 +337 -0
- package/jssm.es6.d.ts +337 -0
- package/jssm_viz.es5.d.cts +337 -0
- package/jssm_viz.es6.d.ts +337 -0
- package/package.json +1 -1
package/dist/cli/fsl.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var fs=require("fs");var path=require("path");var child_process=require("child_process");const IS_WINDOWS=process.platform==="win32";const PATH_SEP=IS_WINDOWS?";":":";const PATHEXT=IS_WINDOWS?(process.env.PATHEXT??".COM;.EXE;.BAT;.CMD").split(";").map(s=>s.toLowerCase()):[""];async function findPluginOnPath(subcommand,pathEnv){if(!pathEnv)return null;const dirs=pathEnv.split(PATH_SEP).filter(d=>d.length>0);const baseName=`fsl-${subcommand}`;const NODE_EXTS=[".cjs",".mjs",".js"];const exts=IS_WINDOWS?[...PATHEXT,...NODE_EXTS]:["",...NODE_EXTS];for(const dir of dirs){for(const ext of exts){const candidate=path.join(dir,baseName+ext);try{const st=await fs.promises.stat(candidate);if(st.isFile())return candidate}catch{}}}return null}function isInProcessEligible(resolvedPath){const ext=path.extname(resolvedPath).toLowerCase();if(ext!==".js"&&ext!==".mjs"&&ext!==".cjs")return false;const norm=resolvedPath.replace(/\\/g,"/");return norm.includes("/node_modules/")}async function invokeInProcess(pluginPath,argv){const originalExit=process.exit;const originalArgv=process.argv;let interceptedExit=null;const ExitInterception=Symbol("ExitInterception");process.exit=code=>{interceptedExit=typeof code==="number"?code:0;throw ExitInterception};process.argv=[originalArgv[0],pluginPath,...argv];let result;try{const mod=await import(pluginPath);const cli=mod&&(mod.default??mod);if(typeof cli!=="function"){process.stderr.write(`fsl: error: plugin ${pluginPath} is missing default cli() export\n`);result=2}else{const r=await cli(argv);result=typeof r==="number"?r:0}}catch(e){if(e===ExitInterception){result=interceptedExit}else{process.stderr.write(`fsl: error: plugin threw: ${e.message??String(e)}\n`);result=2}}process.exit=originalExit;process.argv=originalArgv;return result}async function invokeBySpawn(pluginPath,argv){return new Promise(res=>{const ext=path.extname(pluginPath).toLowerCase();const isCmdScript=IS_WINDOWS&&(ext===".cmd"||ext===".bat");const isNodeScript=ext===".cjs"||ext===".mjs"||ext===".js";const[spawnCmd,spawnArgs]=isCmdScript?["cmd.exe",["/c",pluginPath,...argv]]:isNodeScript?[process.execPath,[pluginPath,...argv]]:[pluginPath,argv];const child=child_process.spawn(spawnCmd,spawnArgs,{stdio:"inherit"});child.on("exit",code=>res(code));child.on("error",err=>{process.stderr.write(`fsl: error: failed to spawn plugin: ${err.message}\n`);res(2)})})}const RESERVED_FLAGS=new Set(["--help","-h","--version","-V"]);const RESERVED_NAMES=new Set(["help","version"]);const getDispatcherVersion=()=>"5.
|
|
2
|
+
"use strict";var fs=require("fs");var path=require("path");var child_process=require("child_process");const IS_WINDOWS=process.platform==="win32";const PATH_SEP=IS_WINDOWS?";":":";const PATHEXT=IS_WINDOWS?(process.env.PATHEXT??".COM;.EXE;.BAT;.CMD").split(";").map(s=>s.toLowerCase()):[""];async function findPluginOnPath(subcommand,pathEnv){if(!pathEnv)return null;const dirs=pathEnv.split(PATH_SEP).filter(d=>d.length>0);const baseName=`fsl-${subcommand}`;const NODE_EXTS=[".cjs",".mjs",".js"];const exts=IS_WINDOWS?[...PATHEXT,...NODE_EXTS]:["",...NODE_EXTS];for(const dir of dirs){for(const ext of exts){const candidate=path.join(dir,baseName+ext);try{const st=await fs.promises.stat(candidate);if(st.isFile())return candidate}catch{}}}return null}function isInProcessEligible(resolvedPath){const ext=path.extname(resolvedPath).toLowerCase();if(ext!==".js"&&ext!==".mjs"&&ext!==".cjs")return false;const norm=resolvedPath.replace(/\\/g,"/");return norm.includes("/node_modules/")}async function invokeInProcess(pluginPath,argv){const originalExit=process.exit;const originalArgv=process.argv;let interceptedExit=null;const ExitInterception=Symbol("ExitInterception");process.exit=code=>{interceptedExit=typeof code==="number"?code:0;throw ExitInterception};process.argv=[originalArgv[0],pluginPath,...argv];let result;try{const mod=await import(pluginPath);const cli=mod&&(mod.default??mod);if(typeof cli!=="function"){process.stderr.write(`fsl: error: plugin ${pluginPath} is missing default cli() export\n`);result=2}else{const r=await cli(argv);result=typeof r==="number"?r:0}}catch(e){if(e===ExitInterception){result=interceptedExit}else{process.stderr.write(`fsl: error: plugin threw: ${e.message??String(e)}\n`);result=2}}process.exit=originalExit;process.argv=originalArgv;return result}async function invokeBySpawn(pluginPath,argv){return new Promise(res=>{const ext=path.extname(pluginPath).toLowerCase();const isCmdScript=IS_WINDOWS&&(ext===".cmd"||ext===".bat");const isNodeScript=ext===".cjs"||ext===".mjs"||ext===".js";const[spawnCmd,spawnArgs]=isCmdScript?["cmd.exe",["/c",pluginPath,...argv]]:isNodeScript?[process.execPath,[pluginPath,...argv]]:[pluginPath,argv];const child=child_process.spawn(spawnCmd,spawnArgs,{stdio:"inherit"});child.on("exit",code=>res(code));child.on("error",err=>{process.stderr.write(`fsl: error: failed to spawn plugin: ${err.message}\n`);res(2)})})}const RESERVED_FLAGS=new Set(["--help","-h","--version","-V"]);const RESERVED_NAMES=new Set(["help","version"]);const getDispatcherVersion=()=>"5.134.0";const printDispatcherHelp=()=>{process.stdout.write(`fsl — finite-state language toolchain dispatcher\n\nUsage:\n fsl <subcommand> [options] [args...]\n fsl [--help|--version]\n\nBuilt-in subcommands (resolved via PATH):\n render Render FSL machines to SVG, DOT, PNG, JPEG, or HTML\n\nDiscovery:\n Any \`fsl-<name>\` executable on PATH is dispatched when you run\n \`fsl <name>\`. Third-party plugins follow the same contract as\n first-party ones.\n\n See: https://github.com/StoneCypher/jssm\n`)};async function dispatch(argv){let verbose=false;if(argv[0]==="--verbose"){verbose=true;argv=argv.slice(1)}if(argv.length===0||RESERVED_FLAGS.has(argv[0])){if(argv[0]==="--version"||argv[0]==="-V"){process.stdout.write(`fsl ${getDispatcherVersion()}\n`);return 0}printDispatcherHelp();return 0}const subcommand=argv[0];const rest=argv.slice(1);if(RESERVED_NAMES.has(subcommand)){if(subcommand==="version"){process.stdout.write(`fsl ${getDispatcherVersion()}\n`);return 0}printDispatcherHelp();return 0}const pluginPath=await findPluginOnPath(subcommand,process.env.PATH);if(!pluginPath){process.stderr.write(`fsl: '${subcommand}' is not a known subcommand and no \`fsl-${subcommand}\` was found on PATH\n`);return 1}if(verbose){process.stderr.write(`fsl: resolved '${subcommand}' to ${pluginPath}\n`)}if(isInProcessEligible(pluginPath)){return invokeInProcess(pluginPath,rest)}return invokeBySpawn(pluginPath,rest)}async function main(){const argv=process.argv.slice(2);const code=await dispatch(argv);process.exit(code)}void main();
|
package/dist/deno/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.
|
|
21
|
+
* Generated for version 5.134.0 at 5/27/2026, 10:16:05 PM
|
|
22
22
|
|
|
23
23
|
-->
|
|
24
|
-
# jssm 5.
|
|
24
|
+
# jssm 5.134.0
|
|
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,220 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,220 tests***, run 57,007 times.
|
|
418
418
|
|
|
419
|
-
- 5,
|
|
420
|
-
- 513 fuzz tests with 4.
|
|
421
|
-
- 4,
|
|
419
|
+
- 5,707 specs with 100.0% coverage
|
|
420
|
+
- 513 fuzz tests with 4.0% coverage
|
|
421
|
+
- 4,856 TypeScript lines - 1.3 tests per line, 11.7 generated tests per line
|
|
422
422
|
|
|
423
423
|
[](https://github.com/StoneCypher/jssm/actions)
|
|
424
424
|
[](https://www.npmjs.com/package/jssm)
|
package/dist/deno/jssm.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
declare type StateType = string;
|
|
2
2
|
import { JssmGenericState, JssmGenericConfig, JssmStateConfig, JssmTransition, JssmTransitionList, // JssmTransitionRule,
|
|
3
|
-
JssmMachineInternalState, JssmAllowsOverride, JssmStateDeclaration, JssmStateStyleKeyList, JssmLayout, JssmHistory, JssmSerialization, FslDirection, FslDirections, FslTheme, HookDescription, HookHandler, HookContext, HookResult, HookComplexResult, EverythingHookContext, EverythingHookHandler, PostEverythingHookHandler, JssmRng } from './jssm_types';
|
|
3
|
+
JssmMachineInternalState, JssmAllowsOverride, JssmStateDeclaration, JssmStateStyleKeyList, JssmLayout, JssmHistory, JssmSerialization, FslDirection, FslDirections, FslTheme, HookDescription, HookHandler, HookContext, HookResult, HookComplexResult, EverythingHookContext, EverythingHookHandler, PostEverythingHookHandler, JssmEventName, JssmEventDetailMap, JssmEventFilter, JssmEventHandler, JssmUnsubscribe, JssmRng } from './jssm_types';
|
|
4
4
|
import { arrow_direction, arrow_left_kind, arrow_right_kind } from './jssm_arrow';
|
|
5
5
|
import { compile, make, wrap_parse } from './jssm_compiler';
|
|
6
6
|
import { seq, unique, find_repeated, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key, gen_splitmix32, sleep } from './jssm_util';
|
|
@@ -16,6 +16,18 @@ declare const shapes: string[], gviz_shapes: string[], named_colors: string[], s
|
|
|
16
16
|
to: string;
|
|
17
17
|
}[];
|
|
18
18
|
import { version, build_time } from './version';
|
|
19
|
+
/**
|
|
20
|
+
* Internal record holding a single registered event subscription: the
|
|
21
|
+
* handler, its optional filter, and a flag for `once` semantics. Not
|
|
22
|
+
* exported.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
declare type JssmEventEntry<mDT, Ev extends JssmEventName> = {
|
|
27
|
+
handler: JssmEventHandler<mDT, Ev>;
|
|
28
|
+
filter?: JssmEventFilter<mDT, Ev>;
|
|
29
|
+
once: boolean;
|
|
30
|
+
};
|
|
19
31
|
/*********
|
|
20
32
|
*
|
|
21
33
|
* An internal method meant to take a series of declarations and fold them into
|
|
@@ -188,6 +200,8 @@ declare class Machine<mDT> {
|
|
|
188
200
|
_timeout_handle: number | undefined;
|
|
189
201
|
_timeout_target: string | undefined;
|
|
190
202
|
_timeout_target_time: number | undefined;
|
|
203
|
+
_event_handlers: Map<JssmEventName, Set<JssmEventEntry<any, any>>>;
|
|
204
|
+
_firing_error: boolean;
|
|
191
205
|
constructor({ start_states, end_states, initial_state, start_states_no_enforce, complete, transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, property_definition, state_property, fsl_version, dot_preamble, arrange_declaration, arrange_start_declaration, arrange_end_declaration, theme, flow, graph_layout, instance_name, history, data, default_state_config, default_active_state_config, default_hooked_state_config, default_terminal_state_config, default_start_state_config, default_end_state_config, allows_override, config_allows_override, rng_seed, time_source, timeout_source, clear_timeout_source }: JssmGenericConfig<StateType, mDT>);
|
|
192
206
|
/********
|
|
193
207
|
*
|
|
@@ -996,6 +1010,100 @@ declare class Machine<mDT> {
|
|
|
996
1010
|
* @returns `true` if at least one state is complete.
|
|
997
1011
|
*/
|
|
998
1012
|
has_completes(): boolean;
|
|
1013
|
+
/**
|
|
1014
|
+
* Subscribe to a typed observation event. Hooks (`set_hook` and friends)
|
|
1015
|
+
* intercept and may cancel a transition; events fire alongside the same
|
|
1016
|
+
* state-machine moments but cannot influence the outcome. This is the
|
|
1017
|
+
* surface most users actually want for "tell me when state changes".
|
|
1018
|
+
*
|
|
1019
|
+
* Handlers run synchronously, in registration order. A throwing handler
|
|
1020
|
+
* does not block subsequent handlers — its exception is caught and
|
|
1021
|
+
* re-emitted as an `error` event whose detail names the original event
|
|
1022
|
+
* and the offending handler.
|
|
1023
|
+
*
|
|
1024
|
+
* ```typescript
|
|
1025
|
+
* const m = sm`a -> b -> c;`;
|
|
1026
|
+
*
|
|
1027
|
+
* m.on('transition', e => console.log(`${e.from} -> ${e.to}`));
|
|
1028
|
+
* m.on('entry', { state: 'b' }, e => console.log(`entered ${e.state}`));
|
|
1029
|
+
*
|
|
1030
|
+
* const off = m.on('transition', () => {});
|
|
1031
|
+
* off(); // unsubscribe
|
|
1032
|
+
* ```
|
|
1033
|
+
*
|
|
1034
|
+
* @typeparam Ev The event name (drives the detail type).
|
|
1035
|
+
* @param name The event name to subscribe to.
|
|
1036
|
+
* @param filterOrFn Either a filter object or, when calling the no-filter
|
|
1037
|
+
* form, the handler itself.
|
|
1038
|
+
* @param maybeFn The handler, when a filter object was supplied.
|
|
1039
|
+
* @returns A function that unsubscribes when called.
|
|
1040
|
+
*
|
|
1041
|
+
* @see Machine.off
|
|
1042
|
+
* @see Machine.once
|
|
1043
|
+
*/
|
|
1044
|
+
on<Ev extends JssmEventName>(name: Ev, handler: JssmEventHandler<mDT, Ev>): JssmUnsubscribe;
|
|
1045
|
+
on<Ev extends JssmEventName>(name: Ev, filter: JssmEventFilter<mDT, Ev>, handler: JssmEventHandler<mDT, Ev>): JssmUnsubscribe;
|
|
1046
|
+
/**
|
|
1047
|
+
* Subscribe to a typed observation event for one matching delivery, then
|
|
1048
|
+
* auto-remove. Accepts the same `(name, handler)` and `(name, filter,
|
|
1049
|
+
* handler)` shapes as {@link Machine.on}.
|
|
1050
|
+
*
|
|
1051
|
+
* ```typescript
|
|
1052
|
+
* m.once('terminal', e => console.log(`done at ${e.state}`));
|
|
1053
|
+
* ```
|
|
1054
|
+
*
|
|
1055
|
+
* @typeparam Ev The event name.
|
|
1056
|
+
* @param name The event name.
|
|
1057
|
+
* @param filterOrFn A filter object or the handler (no-filter form).
|
|
1058
|
+
* @param maybeFn The handler, when a filter was supplied.
|
|
1059
|
+
* @returns A function that unsubscribes early if called before the
|
|
1060
|
+
* handler has fired.
|
|
1061
|
+
*
|
|
1062
|
+
* @see Machine.on
|
|
1063
|
+
* @see Machine.off
|
|
1064
|
+
*/
|
|
1065
|
+
once<Ev extends JssmEventName>(name: Ev, handler: JssmEventHandler<mDT, Ev>): JssmUnsubscribe;
|
|
1066
|
+
once<Ev extends JssmEventName>(name: Ev, filter: JssmEventFilter<mDT, Ev>, handler: JssmEventHandler<mDT, Ev>): JssmUnsubscribe;
|
|
1067
|
+
/**
|
|
1068
|
+
* Remove a previously-registered event handler. Match is by reference —
|
|
1069
|
+
* the same function value passed to {@link Machine.on} or
|
|
1070
|
+
* {@link Machine.once}. Returns `true` if a subscription was found and
|
|
1071
|
+
* removed, `false` otherwise.
|
|
1072
|
+
*
|
|
1073
|
+
* ```typescript
|
|
1074
|
+
* const fn = (e: any) => console.log(e);
|
|
1075
|
+
* m.on('transition', fn);
|
|
1076
|
+
* m.off('transition', fn); // true
|
|
1077
|
+
* m.off('transition', fn); // false
|
|
1078
|
+
* ```
|
|
1079
|
+
*
|
|
1080
|
+
* @param name The event name.
|
|
1081
|
+
* @param handler The handler reference to remove.
|
|
1082
|
+
* @returns `true` if removed, `false` if no match was registered.
|
|
1083
|
+
*/
|
|
1084
|
+
off<Ev extends JssmEventName>(name: Ev, handler: JssmEventHandler<mDT, Ev>): boolean;
|
|
1085
|
+
/**
|
|
1086
|
+
* Shared registration core used by {@link Machine.on} and
|
|
1087
|
+
* {@link Machine.once}. Normalizes the optional filter argument and
|
|
1088
|
+
* installs the entry into the per-event subscription set.
|
|
1089
|
+
*
|
|
1090
|
+
* @internal
|
|
1091
|
+
*/
|
|
1092
|
+
_subscribe<Ev extends JssmEventName>(name: Ev, filterOrFn: JssmEventFilter<mDT, Ev> | JssmEventHandler<mDT, Ev>, maybeFn: JssmEventHandler<mDT, Ev> | undefined, once: boolean): JssmUnsubscribe;
|
|
1093
|
+
/**
|
|
1094
|
+
* Dispatch an event to every registered subscriber in registration
|
|
1095
|
+
* order. Filters are checked first; non-matching handlers are skipped
|
|
1096
|
+
* without invoking the handler. Exceptions thrown by a handler are
|
|
1097
|
+
* caught and re-emitted as an `error` event so subsequent handlers
|
|
1098
|
+
* still run.
|
|
1099
|
+
*
|
|
1100
|
+
* Re-entry into the `error` event itself is guarded — if an `error`
|
|
1101
|
+
* handler throws, the new exception is swallowed rather than rebroadcast
|
|
1102
|
+
* to avoid an infinite loop.
|
|
1103
|
+
*
|
|
1104
|
+
* @internal
|
|
1105
|
+
*/
|
|
1106
|
+
_fire<Ev extends JssmEventName>(name: Ev, detail: JssmEventDetailMap<mDT>[Ev]): void;
|
|
999
1107
|
/** Low-level hook registration. Installs a handler described by a
|
|
1000
1108
|
* {@link HookDescription} into the appropriate internal map. Prefer the
|
|
1001
1109
|
* convenience wrappers ({@link hook}, {@link hook_entry}, etc.) over
|
|
@@ -1003,6 +1111,28 @@ declare class Machine<mDT> {
|
|
|
1003
1111
|
* @param HookDesc - A hook descriptor specifying kind, states, and handler.
|
|
1004
1112
|
*/
|
|
1005
1113
|
set_hook(HookDesc: HookDescription<mDT>): void;
|
|
1114
|
+
/**
|
|
1115
|
+
* Remove a previously-registered hook described by a
|
|
1116
|
+
* {@link HookDescription}. Match is by `kind` + identifying keys
|
|
1117
|
+
* (`from`/`to`/`action`/etc.), not by handler reference — there is one
|
|
1118
|
+
* hook per slot in the registry, so the description uniquely identifies
|
|
1119
|
+
* which one to clear. Fires a `hook-removal` event for inspector tools.
|
|
1120
|
+
*
|
|
1121
|
+
* This is the symmetric counterpart of {@link Machine.set_hook} for the
|
|
1122
|
+
* event-bridging use case (#638). Reasoning about hooks via observation
|
|
1123
|
+
* events requires being able to observe their disappearance too.
|
|
1124
|
+
*
|
|
1125
|
+
* ```typescript
|
|
1126
|
+
* const m = sm`a -> b;`;
|
|
1127
|
+
* const fn = () => true;
|
|
1128
|
+
* m.set_hook({ kind: 'hook', from: 'a', to: 'b', handler: fn });
|
|
1129
|
+
* m.remove_hook({ kind: 'hook', from: 'a', to: 'b', handler: fn });
|
|
1130
|
+
* ```
|
|
1131
|
+
*
|
|
1132
|
+
* @param HookDesc - A hook descriptor identifying the hook to remove.
|
|
1133
|
+
* @returns `true` if a hook was removed, `false` otherwise.
|
|
1134
|
+
*/
|
|
1135
|
+
remove_hook(HookDesc: HookDescription<mDT>): boolean;
|
|
1006
1136
|
/** Register a pre-transition hook on a specific edge. Fires before
|
|
1007
1137
|
* transitioning from `from` to `to`. If the handler returns `false`, the
|
|
1008
1138
|
* transition is blocked.
|