jssm 5.162.13 → 5.162.16

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.
@@ -38,12 +38,18 @@ type JssmPermitted = 'required' | 'disallowed';
38
38
  */
39
39
  type JssmPermittedOpt = 'required' | 'disallowed' | 'optional';
40
40
  /**
41
- * The set of ASCII arrow tokens recognized by the FSL grammar. Each arrow
42
- * encodes a direction (one-way left/right, or two-way) and a "kind" for
41
+ * Every arrow token recognized by the FSL grammar — all 42 spellings. Each
42
+ * arrow encodes a direction (one-way left/right, or two-way) and a "kind" for
43
43
  * each direction (`-` legal, `=` main path, `~` forced-only). See the
44
44
  * Language Reference docs for the full semantic table.
45
+ *
46
+ * Every arrow has an ASCII spelling and a unicode spelling, and each half of a
47
+ * two-way arrow may be spelled independently, so the two-way arrows also have
48
+ * mixed ASCII/unicode spellings (`←=>`, `<-⇒`, and so on). All of them are
49
+ * accepted, by the grammar and by {@link arrow_direction},
50
+ * {@link arrow_left_kind}, and {@link arrow_right_kind}.
45
51
  */
46
- type JssmArrow = '->' | '<-' | '<->' | '<=->' | '<~->' | '=>' | '<=' | '<=>' | '<-=>' | '<~=>' | '~>' | '<~' | '<~>' | '<-~>' | '<=~>';
52
+ type JssmArrow = '->' | '' | '=>' | '' | '~>' | '' | '<-' | '←' | '<=' | '' | '<~' | '' | '<->' | '' | '<=>' | '⇔' | '<~>' | '↮' | '<-=>' | '←⇒' | '←=>' | '<-⇒' | '<-~>' | '←↛' | '←~>' | '<-↛' | '<=->' | '⇐→' | '⇐->' | '<=→' | '<=~>' | '⇐↛' | '⇐~>' | '<=↛' | '<~->' | '↚→' | '↚->' | '<~→' | '<~=>' | '↚⇒' | '↚=>' | '<~⇒';
47
53
  /**
48
54
  * A type teaching Typescript the various supported shapes for nodes, mostly inherited from GraphViz
49
55
  */
@@ -786,8 +792,22 @@ type JssmGenericConfig<StateType, DataType> = {
786
792
  state_hooks?: JssmStateHooks;
787
793
  rng_seed?: number | undefined;
788
794
  time_source?: () => number;
789
- timeout_source?: (Function: any, number: any) => number;
790
- clear_timeout_source?: (number: any) => void;
795
+ /**
796
+ * Schedules `fn` to run after `delay_ms`, and returns a handle that will be
797
+ * handed back to `clear_timeout_source` untouched. Defaults to `setTimeout`.
798
+ *
799
+ * The handle is typed `number` — the browser shape. Node's `setTimeout`
800
+ * returns a `Timeout` object instead, so a Node-shaped source casts it (as
801
+ * jssm's own `DEFAULT_TIMEOUT_SOURCE` does); jssm never inspects the handle,
802
+ * it only stores it and gives it back.
803
+ *
804
+ * (Before 5.162.14 these read `(Function, number) => number`, in which
805
+ * `Function` and `number` were *parameter names*, not types — so both
806
+ * parameters were silently `any`.)
807
+ */
808
+ timeout_source?: (fn: () => void, delay_ms: number) => number;
809
+ /** Cancels a timer previously scheduled by `timeout_source`. Defaults to `clearTimeout`. */
810
+ clear_timeout_source?: (handle: number) => void;
791
811
  };
792
812
  /**
793
813
  * Internal compiler intermediate: a single aggregated rule produced while
@@ -810,7 +830,15 @@ type JssmCompileRule<StateType> = {
810
830
  type JssmCompileSe<StateType, mDT> = {
811
831
  to: StateType;
812
832
  se?: JssmCompileSe<StateType, mDT>;
813
- kind: JssmArrow;
833
+ /**
834
+ * The arrow token as the parser emitted it. Deliberately `string` and not
835
+ * {@link JssmArrow}: this internal intermediate flows through the whole
836
+ * compiler, and threading a 42-member string-literal union through that much
837
+ * control-flow analysis overflows `tsc`'s stack (it type-checks standalone
838
+ * but dies under `npm run make`). The value *is* a `JssmArrow` — the two
839
+ * places that care re-assert it on the way into the arrow classifiers.
840
+ */
841
+ kind: string;
814
842
  l_action?: StateType;
815
843
  r_action?: StateType;
816
844
  l_probability: number;
@@ -1101,10 +1129,33 @@ type HookResult<mDT> = true | false | undefined | void | HookComplexResult<mDT>;
1101
1129
  * the payload that will be committed if the transition is accepted —
1102
1130
  * handlers may inspect or mutate the latter via a
1103
1131
  * {@link HookComplexResult} return value.
1132
+ *
1133
+ * The remaining fields describe the transition the hook is firing on. They
1134
+ * are optional because a handler is not obliged to care about them, but the
1135
+ * transition path always supplies all of them; `action` is `undefined` when
1136
+ * the transition was not driven by an action.
1104
1137
  */
1105
1138
  type HookContext<mDT> = {
1106
1139
  data: mDT;
1107
1140
  next_data: mDT;
1141
+ /** The state being left. */
1142
+ from?: string;
1143
+ /** The state being entered. */
1144
+ to?: string;
1145
+ /** The action that drove the transition, or `undefined` if none did. */
1146
+ action?: string;
1147
+ /** Whether this transition came from `force_transition` rather than `transition`. */
1148
+ forced?: boolean;
1149
+ /**
1150
+ * Which arrow kind the traversed edge carries — `legal`, `main`, or `forced`.
1151
+ *
1152
+ * Populated **only when a transition-kind hook is installed** (a standard,
1153
+ * main, or forced transition hook, or their post- equivalents). With no such
1154
+ * hook registered there is nothing to switch on, so jssm skips resolving the
1155
+ * edge's kind and this is `undefined`. Install `hook_standard_transition`
1156
+ * (or a sibling) if a general handler needs to read it.
1157
+ */
1158
+ trans_type?: JssmArrowKind;
1108
1159
  };
1109
1160
  /**
1110
1161
  * Context object passed to "everything" hooks ({@link EverythingHookHandler}
@@ -63,7 +63,7 @@ declare const array_box_if_string: (n: any) => any;
63
63
  * @throws {TypeError} If `options` is not a non-empty array of objects.
64
64
  *
65
65
  */
66
- declare const weighted_rand_select: (options: Array<any>, probability_property: string, rng: JssmRng) => any;
66
+ declare const weighted_rand_select: (options: Array<any>, probability_property?: string, rng?: JssmRng) => any;
67
67
  /*******
68
68
  *
69
69
  * Returns, for a non-negative integer argument `n`, the series `[0 .. n]`.
@@ -22318,9 +22318,12 @@ function makeTransition(this_se, from, to, isRight, _wasList, _wasIndex) {
22318
22318
  if (to === '') {
22319
22319
  throw new JssmError(undefined, 'A state name may not be the empty string (transition target)');
22320
22320
  }
22321
- const kind = isRight
22322
- ? arrow_right_kind(this_se.kind)
22323
- : arrow_left_kind(this_se.kind),
22321
+ // `this_se.kind` is typed `string` rather than JssmArrow — see the field's own
22322
+ // note in jssm_types.ts. The classifiers reject anything that isn't a real
22323
+ // arrow, so an unsound value throws here rather than passing silently.
22324
+ const arrow = this_se.kind, kind = isRight
22325
+ ? arrow_right_kind(arrow)
22326
+ : arrow_left_kind(arrow),
22324
22327
  // action and probability are pre-declared (as after_time always was)
22325
22328
  // so every compiled edge shares ONE hidden class regardless of which
22326
22329
  // optional fields its declaration carries. The conditional assigns
@@ -24223,7 +24226,7 @@ function fslSemanticSpans(text) {
24223
24226
  * Useful for runtime diagnostics and for embedding in serialized machine
24224
24227
  * snapshots so that deserializers can detect version-skew.
24225
24228
  */
24226
- const version = "5.162.13";
24229
+ const version = "5.162.16";
24227
24230
 
24228
24231
  /**
24229
24232
  * The FSL Markdown fence convention parser — pure, host-agnostic logic that
@@ -27883,7 +27886,13 @@ class Machine {
27883
27886
  *
27884
27887
  */
27885
27888
  transition_impl(newStateOrAction, newData, wasForced, wasAction, dataProvided = newData !== undefined) {
27886
- let valid = false, trans_type, newState, newStateId = NaN, actionId = NaN, fromAction;
27889
+ let valid = false,
27890
+ // deliberately `string`, not `JssmArrowKind`, though only arrow kinds are
27891
+ // ever assigned: declaring this local as the 4-member union makes tsc's
27892
+ // control-flow analysis narrow it across the whole of this (very large)
27893
+ // function, which overflows the checker's stack under `npm run make`.
27894
+ // The union is recovered at the hook boundary below -- see hook_args_obj.
27895
+ trans_type, newState, newStateId = NaN, actionId = NaN, fromAction;
27887
27896
  if (wasForced) {
27888
27897
  // numeric inline of valid_force_transition: any existing edge
27889
27898
  // qualifies, forced or not. one string probe (the user's target name)
@@ -27955,7 +27964,10 @@ class Machine {
27955
27964
  to: newState,
27956
27965
  next_data: newData,
27957
27966
  forced: wasForced,
27958
- trans_type
27967
+ // sound: the only values ever assigned to trans_type are an edge's
27968
+ // `kind` and the literal 'forced'. The local is typed `string` only
27969
+ // to keep tsc's flow analysis off it (see its declaration above).
27970
+ trans_type: trans_type
27959
27971
  }
27960
27972
  : undefined;
27961
27973
  const hook_args = hook_args_obj;
@@ -29963,6 +29975,50 @@ function vc(col) {
29963
29975
  function doublequote(txt) {
29964
29976
  return txt.replace(/"/g, String.raw `\"`);
29965
29977
  }
29978
+ /**
29979
+ * URL schemes that are safe to emit into a rendered diagram's `<a xlink:href>`.
29980
+ * Anything not in this set (notably `javascript:`, `data:`, `vbscript:`) is a
29981
+ * script-execution vector once the SVG is injected into a live DOM.
29982
+ * @internal
29983
+ */
29984
+ const SAFE_URL_SCHEMES = new Set(['http', 'https', 'mailto', 'ftp', 'ftps', 'tel']);
29985
+ /**
29986
+ * Sanitize a state's `url` before it is embedded in DOT (and thence in the
29987
+ * rendered SVG's `xlink:href`). A URL with a scheme is kept only when that
29988
+ * scheme is on the {@link SAFE_URL_SCHEMES} allowlist; a schemeless URL
29989
+ * (relative path, `#anchor`, query) is always safe and kept as-is. An unsafe
29990
+ * URL is dropped (returns `''`) and a warning is emitted, so the diagram still
29991
+ * renders — just without the dangerous link.
29992
+ *
29993
+ * Leading ASCII control characters and whitespace are stripped **before**
29994
+ * inspecting the scheme, because browsers ignore them when parsing a URL — so
29995
+ * `java\tscript:alert(1)` is recognized and dropped rather than smuggled past.
29996
+ *
29997
+ * ```typescript
29998
+ * render_safe_url('https://example.com'); // 'https://example.com'
29999
+ * render_safe_url('#node'); // '#node'
30000
+ * render_safe_url('javascript:alert(1)'); // '' (dropped + warned)
30001
+ * ```
30002
+ * @param raw The raw `url` value from a state's config.
30003
+ * @returns The original URL if its scheme is safe or absent, otherwise `''`.
30004
+ * @internal
30005
+ */
30006
+ function render_safe_url(raw) {
30007
+ if (!raw) {
30008
+ return '';
30009
+ }
30010
+ // eslint-disable-next-line no-control-regex -- browsers strip these before scheme parsing
30011
+ const stripped = raw.replace(/[\u{0}-\u{20}]+/gu, '');
30012
+ const scheme_match = /^([a-z][a-z0-9+.-]*):/i.exec(stripped);
30013
+ if (scheme_match) {
30014
+ const scheme = scheme_match[1].toLowerCase();
30015
+ if (!SAFE_URL_SCHEMES.has(scheme)) {
30016
+ console.warn(`jssm-viz: dropping state url with unsafe scheme "${scheme}:"`);
30017
+ return '';
30018
+ }
30019
+ }
30020
+ return raw;
30021
+ }
29966
30022
  /**
29967
30023
  * Reverse {@link doublequote}: turn DOT's `\"` escape back into the literal
29968
30024
  * `"` that graphviz renders into SVG `<text>` content. Used by
@@ -30398,7 +30454,7 @@ function state_node_line(u_jssm, s, state_index, state_kinds, hide_state_labels,
30398
30454
  ['style', compose_style_string(style)],
30399
30455
  ['fontcolor', style.textColor || ''],
30400
30456
  ['image', style.image || ''],
30401
- ['URL', style.url || ''],
30457
+ ['URL', render_safe_url(style.url || '')],
30402
30458
  ['fillcolor', fillcolor]
30403
30459
  ]
30404
30460
  .filter(r => r[1])