jssm 5.72.5 → 5.74.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/dist/es6/jssm.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // whargarbl lots of these return arrays could/should be sets
2
2
  import { reduce as reduce_to_639 } from 'reduce-to-639-1';
3
+ import { circular_buffer } from 'circular_buffer_js';
3
4
  import { seq, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key, array_box_if_string, hook_name, named_hook_name } from './jssm_util';
4
5
  import { shapes, gviz_shapes, named_colors } from './jssm_constants';
5
6
  import { parse } from './jssm-dot';
@@ -534,7 +535,7 @@ function transfer_state_properties(state_decl) {
534
535
  // TODO add a lotta docblock here
535
536
  class Machine {
536
537
  // whargarbl this badly needs to be broken up, monolith master
537
- constructor({ start_states, complete = [], transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, fsl_version, dot_preamble = undefined, arrange_declaration = [], arrange_start_declaration = [], arrange_end_declaration = [], theme = 'default', flow = 'down', graph_layout = 'dot', instance_name, data }) {
538
+ constructor({ start_states, complete = [], transitions, machine_author, machine_comment, machine_contributor, machine_definition, machine_language, machine_license, machine_name, machine_version, state_declaration, fsl_version, dot_preamble = undefined, arrange_declaration = [], arrange_start_declaration = [], arrange_end_declaration = [], theme = 'default', flow = 'down', graph_layout = 'dot', instance_name, history, data }) {
538
539
  this._instance_name = instance_name;
539
540
  this._state = start_states[0];
540
541
  this._states = new Map();
@@ -569,7 +570,7 @@ class Machine {
569
570
  this._has_exit_hooks = false;
570
571
  this._has_global_action_hooks = false;
571
572
  this._has_transition_hooks = true;
572
- // no need for a boolean has any transition hook, as it's one or nothing, so just test that for undefinedness
573
+ // no need for a boolean for single hooks, just test for undefinedness
573
574
  this._hooks = new Map();
574
575
  this._named_hooks = new Map();
575
576
  this._entry_hooks = new Map();
@@ -580,8 +581,27 @@ class Machine {
580
581
  this._main_transition_hook = undefined;
581
582
  this._forced_transition_hook = undefined;
582
583
  this._any_transition_hook = undefined;
583
- this._standard_transition_hook = undefined;
584
+ this._has_post_hooks = false;
585
+ this._has_post_basic_hooks = false;
586
+ this._has_post_named_hooks = false;
587
+ this._has_post_entry_hooks = false;
588
+ this._has_post_exit_hooks = false;
589
+ this._has_post_global_action_hooks = false;
590
+ this._has_post_transition_hooks = true;
591
+ // no need for a boolean for single hooks, just test for undefinedness
592
+ this._post_hooks = new Map();
593
+ this._post_named_hooks = new Map();
594
+ this._post_entry_hooks = new Map();
595
+ this._post_exit_hooks = new Map();
596
+ this._post_global_action_hooks = new Map();
597
+ this._post_any_action_hook = undefined;
598
+ this._post_standard_transition_hook = undefined;
599
+ this._post_main_transition_hook = undefined;
600
+ this._post_forced_transition_hook = undefined;
601
+ this._post_any_transition_hook = undefined;
584
602
  this._data = data;
603
+ this._history_length = history || 0;
604
+ this._history = new circular_buffer(this._history_length);
585
605
  if (state_declaration) {
586
606
  state_declaration.map((state_decl) => {
587
607
  if (this._state_declarations.has(state_decl.state)) { // no repeats
@@ -1249,6 +1269,54 @@ class Machine {
1249
1269
  this._has_hooks = true;
1250
1270
  this._has_exit_hooks = true;
1251
1271
  break;
1272
+ case 'post hook':
1273
+ this._post_hooks.set(hook_name(HookDesc.from, HookDesc.to), HookDesc.handler);
1274
+ this._has_post_hooks = true;
1275
+ this._has_post_basic_hooks = true;
1276
+ break;
1277
+ case 'post named':
1278
+ this._post_named_hooks.set(named_hook_name(HookDesc.from, HookDesc.to, HookDesc.action), HookDesc.handler);
1279
+ this._has_post_hooks = true;
1280
+ this._has_post_named_hooks = true;
1281
+ break;
1282
+ case 'post global action':
1283
+ this._post_global_action_hooks.set(HookDesc.action, HookDesc.handler);
1284
+ this._has_post_hooks = true;
1285
+ this._has_post_global_action_hooks = true;
1286
+ break;
1287
+ case 'post any action':
1288
+ this._post_any_action_hook = HookDesc.handler;
1289
+ this._has_post_hooks = true;
1290
+ break;
1291
+ case 'post standard transition':
1292
+ this._post_standard_transition_hook = HookDesc.handler;
1293
+ this._has_post_transition_hooks = true;
1294
+ this._has_post_hooks = true;
1295
+ break;
1296
+ case 'post main transition':
1297
+ this._post_main_transition_hook = HookDesc.handler;
1298
+ this._has_post_transition_hooks = true;
1299
+ this._has_post_hooks = true;
1300
+ break;
1301
+ case 'post forced transition':
1302
+ this._post_forced_transition_hook = HookDesc.handler;
1303
+ this._has_post_transition_hooks = true;
1304
+ this._has_post_hooks = true;
1305
+ break;
1306
+ case 'post any transition':
1307
+ this._post_any_transition_hook = HookDesc.handler;
1308
+ this._has_post_hooks = true;
1309
+ break;
1310
+ case 'post entry':
1311
+ this._post_entry_hooks.set(HookDesc.to, HookDesc.handler);
1312
+ this._has_post_entry_hooks = true;
1313
+ this._has_post_hooks = true;
1314
+ break;
1315
+ case 'post exit':
1316
+ this._post_exit_hooks.set(HookDesc.from, HookDesc.handler);
1317
+ this._has_post_exit_hooks = true;
1318
+ this._has_post_hooks = true;
1319
+ break;
1252
1320
  default:
1253
1321
  throw new JssmError(this, `Unknown hook type ${HookDesc.kind}, should be impossible`);
1254
1322
  }
@@ -1293,6 +1361,46 @@ class Machine {
1293
1361
  this.set_hook({ kind: 'exit', from, handler });
1294
1362
  return this;
1295
1363
  }
1364
+ post_hook(from, to, handler) {
1365
+ this.set_hook({ kind: 'post hook', from, to, handler });
1366
+ return this;
1367
+ }
1368
+ post_hook_action(from, to, action, handler) {
1369
+ this.set_hook({ kind: 'post named', from, to, action, handler });
1370
+ return this;
1371
+ }
1372
+ post_hook_global_action(action, handler) {
1373
+ this.set_hook({ kind: 'post global action', action, handler });
1374
+ return this;
1375
+ }
1376
+ post_hook_any_action(handler) {
1377
+ this.set_hook({ kind: 'post any action', handler });
1378
+ return this;
1379
+ }
1380
+ post_hook_standard_transition(handler) {
1381
+ this.set_hook({ kind: 'post standard transition', handler });
1382
+ return this;
1383
+ }
1384
+ post_hook_main_transition(handler) {
1385
+ this.set_hook({ kind: 'post main transition', handler });
1386
+ return this;
1387
+ }
1388
+ post_hook_forced_transition(handler) {
1389
+ this.set_hook({ kind: 'post forced transition', handler });
1390
+ return this;
1391
+ }
1392
+ post_hook_any_transition(handler) {
1393
+ this.set_hook({ kind: 'post any transition', handler });
1394
+ return this;
1395
+ }
1396
+ post_hook_entry(to, handler) {
1397
+ this.set_hook({ kind: 'post entry', to, handler });
1398
+ return this;
1399
+ }
1400
+ post_hook_exit(from, handler) {
1401
+ this.set_hook({ kind: 'post exit', from, handler });
1402
+ return this;
1403
+ }
1296
1404
  // remove_hook(HookDesc: HookDescription) {
1297
1405
  // throw new JssmError(this, 'TODO: Should remove hook here');
1298
1406
  // }
@@ -1328,15 +1436,16 @@ class Machine {
1328
1436
  newState = newStateOrAction;
1329
1437
  }
1330
1438
  }
1439
+ const hook_args = {
1440
+ data: this._data,
1441
+ action: fromAction,
1442
+ from: this._state,
1443
+ to: newState,
1444
+ forced: wasForced,
1445
+ trans_type
1446
+ };
1331
1447
  if (valid) {
1332
1448
  if (this._has_hooks) {
1333
- const hook_args = {
1334
- data: this._data,
1335
- action: fromAction,
1336
- from: this._state,
1337
- to: newState,
1338
- forced: wasForced
1339
- };
1340
1449
  function update_fields(res) {
1341
1450
  if (res.hasOwnProperty('data')) {
1342
1451
  hook_args.data = res.data;
@@ -1426,22 +1535,191 @@ class Machine {
1426
1535
  update_fields(outcome);
1427
1536
  }
1428
1537
  // all hooks passed! let's now establish the result
1538
+ if (this._history_length) {
1539
+ this._history.shove([this._state, this._data]);
1540
+ }
1429
1541
  this._state = newState;
1430
1542
  if (data_changed) {
1431
1543
  this._data = hook_args.data;
1432
1544
  }
1433
- return true;
1545
+ // success fallthrough to posthooks; intentionally no return here
1546
+ // look for "posthooks begin here"
1434
1547
  // or without hooks
1435
1548
  }
1436
1549
  else {
1550
+ if (this._history_length) {
1551
+ this._history.shove([this._state, this._data]);
1552
+ }
1437
1553
  this._state = newState;
1438
- return true;
1554
+ // success fallthrough to posthooks; intentionally no return here
1555
+ // look for "posthooks begin here"
1439
1556
  }
1440
1557
  // not valid
1441
1558
  }
1442
1559
  else {
1443
1560
  return false;
1444
1561
  }
1562
+ // posthooks begin here
1563
+ if (this._has_post_hooks) {
1564
+ if (wasAction) {
1565
+ // 1. any action posthook
1566
+ if (this._post_any_action_hook !== undefined) {
1567
+ this._post_any_action_hook(hook_args);
1568
+ }
1569
+ // 2. global specific action hook
1570
+ const pgah = this._post_global_action_hooks.get(hook_args.action);
1571
+ if (pgah !== undefined) {
1572
+ pgah(hook_args);
1573
+ }
1574
+ }
1575
+ // 3. any transition hook
1576
+ if (this._post_any_transition_hook !== undefined) {
1577
+ this._post_any_transition_hook(hook_args);
1578
+ }
1579
+ // 4. exit hook
1580
+ if (this._has_post_exit_hooks) {
1581
+ const peh = this._post_exit_hooks.get(hook_args.from); // todo this is probably from instead
1582
+ if (peh !== undefined) {
1583
+ peh(hook_args);
1584
+ }
1585
+ }
1586
+ // 5. named transition / action hook
1587
+ if (this._has_post_named_hooks) {
1588
+ if (wasAction) {
1589
+ const nhn = named_hook_name(hook_args.from, hook_args.to, hook_args.action), pnh = this._post_named_hooks.get(nhn);
1590
+ if (pnh !== undefined) {
1591
+ pnh(hook_args);
1592
+ }
1593
+ }
1594
+ }
1595
+ // 6. regular hook
1596
+ if (this._has_post_basic_hooks) {
1597
+ const hook = this._post_hooks.get(hook_name(hook_args.from, hook_args.to));
1598
+ if (hook !== undefined) {
1599
+ hook(hook_args);
1600
+ }
1601
+ }
1602
+ // 7. edge type hook
1603
+ // 7a. standard transition hook
1604
+ if (trans_type === 'legal') {
1605
+ if (this._post_standard_transition_hook !== undefined) {
1606
+ this._post_standard_transition_hook(hook_args);
1607
+ }
1608
+ }
1609
+ // 7b. main type hook
1610
+ if (trans_type === 'main') {
1611
+ if (this._post_main_transition_hook !== undefined) {
1612
+ this._post_main_transition_hook(hook_args);
1613
+ }
1614
+ }
1615
+ // 7c. forced transition hook
1616
+ if (trans_type === 'forced') {
1617
+ if (this._post_forced_transition_hook !== undefined) {
1618
+ this._post_forced_transition_hook(hook_args);
1619
+ }
1620
+ }
1621
+ // 8. entry hook
1622
+ if (this._has_post_entry_hooks) {
1623
+ const hook = this._post_entry_hooks.get(hook_args.to);
1624
+ if (hook !== undefined) {
1625
+ hook(hook_args);
1626
+ }
1627
+ }
1628
+ }
1629
+ return true;
1630
+ }
1631
+ /*********
1632
+ *
1633
+ * Get a truncated history of the recent states and data of the machine.
1634
+ * Turned off by default; configure with `.from('...', {data: 5})` by length,
1635
+ * or set `.history_length` at runtime.
1636
+ *
1637
+ * History *does not contain the current state*. If you want that, call
1638
+ * `.history_inclusive` instead.
1639
+ *
1640
+ * ```typescript
1641
+ * const foo = jssm.from(
1642
+ * "a 'next' -> b 'next' -> c 'next' -> d 'next' -> e;",
1643
+ * { history: 3 }
1644
+ * );
1645
+ *
1646
+ * foo.action('next');
1647
+ * foo.action('next');
1648
+ * foo.action('next');
1649
+ * foo.action('next');
1650
+ *
1651
+ * foo.history; // [ ['b',undefined], ['c',undefined], ['d',undefined] ]
1652
+ * ```
1653
+ *
1654
+ * Notice that the machine's current state, `e`, is not in the returned list.
1655
+ *
1656
+ * @typeparam mDT The type of the machine data member; usually omitted
1657
+ *
1658
+ */
1659
+ get history() {
1660
+ return this._history.toArray();
1661
+ }
1662
+ /*********
1663
+ *
1664
+ * Get a truncated history of the recent states and data of the machine,
1665
+ * including the current state. Turned off by default; configure with
1666
+ * `.from('...', {data: 5})` by length, or set `.history_length` at runtime.
1667
+ *
1668
+ * History inclusive contains the current state. If you only want past
1669
+ * states, call `.history` instead.
1670
+ *
1671
+ * The list returned will be one longer than the history buffer kept, as the
1672
+ * history buffer kept gets the current state added to it to produce this
1673
+ * list.
1674
+ *
1675
+ * ```typescript
1676
+ * const foo = jssm.from(
1677
+ * "a 'next' -> b 'next' -> c 'next' -> d 'next' -> e;",
1678
+ * { history: 3 }
1679
+ * );
1680
+ *
1681
+ * foo.action('next');
1682
+ * foo.action('next');
1683
+ * foo.action('next');
1684
+ * foo.action('next');
1685
+ *
1686
+ * foo.history_inclusive; // [ ['b',undefined], ['c',undefined], ['d',undefined], ['e',undefined] ]
1687
+ * ```
1688
+ *
1689
+ * Notice that the machine's current state, `e`, is in the returned list.
1690
+ *
1691
+ * @typeparam mDT The type of the machine data member; usually omitted
1692
+ *
1693
+ */
1694
+ get history_inclusive() {
1695
+ const ret = this._history.toArray();
1696
+ ret.push([this.state(), this.data()]);
1697
+ return ret;
1698
+ }
1699
+ /*********
1700
+ *
1701
+ * Find out how long a history this machine is keeping. Defaults to zero.
1702
+ * Settable directly.
1703
+ *
1704
+ * ```typescript
1705
+ * const foo = jssm.from("a -> b;");
1706
+ * foo.history_length; // 0
1707
+ *
1708
+ * const bar = jssm.from("a -> b;", { history: 3 });
1709
+ * foo.history_length; // 3
1710
+ * foo.history_length = 5;
1711
+ * foo.history_length; // 5
1712
+ * ```
1713
+ *
1714
+ * @typeparam mDT The type of the machine data member; usually omitted
1715
+ *
1716
+ */
1717
+ get history_length() {
1718
+ return this._history_length;
1719
+ }
1720
+ set history_length(to) {
1721
+ this._history_length = to;
1722
+ this._history.resize(to, true);
1445
1723
  }
1446
1724
  /********
1447
1725
  *
@@ -102,6 +102,7 @@ declare type JssmGenericConfig<DataType> = {
102
102
  data?: DataType;
103
103
  nodes?: Array<StateType>;
104
104
  check?: JssmStatePermitterMaybeArray<DataType>;
105
+ history?: number;
105
106
  min_exits?: number;
106
107
  max_exits?: number;
107
108
  allow_islands?: false;
@@ -197,7 +198,55 @@ declare type ExitHook<mDT> = {
197
198
  from: string;
198
199
  handler: HookHandler<mDT>;
199
200
  };
200
- declare type HookDescription<mDT> = BasicHookDescription<mDT> | HookDescriptionWithAction<mDT> | GlobalActionHook<mDT> | AnyActionHook<mDT> | StandardTransitionHook<mDT> | MainTransitionHook<mDT> | ForcedTransitionHook<mDT> | AnyTransitionHook<mDT> | EntryHook<mDT> | ExitHook<mDT>;
201
+ declare type PostBasicHookDescription<mDT> = {
202
+ kind: 'post hook';
203
+ from: string;
204
+ to: string;
205
+ handler: PostHookHandler<mDT>;
206
+ };
207
+ declare type PostHookDescriptionWithAction<mDT> = {
208
+ kind: 'post named';
209
+ from: string;
210
+ to: string;
211
+ action: string;
212
+ handler: PostHookHandler<mDT>;
213
+ };
214
+ declare type PostStandardTransitionHook<mDT> = {
215
+ kind: 'post standard transition';
216
+ handler: PostHookHandler<mDT>;
217
+ };
218
+ declare type PostMainTransitionHook<mDT> = {
219
+ kind: 'post main transition';
220
+ handler: PostHookHandler<mDT>;
221
+ };
222
+ declare type PostForcedTransitionHook<mDT> = {
223
+ kind: 'post forced transition';
224
+ handler: PostHookHandler<mDT>;
225
+ };
226
+ declare type PostAnyTransitionHook<mDT> = {
227
+ kind: 'post any transition';
228
+ handler: PostHookHandler<mDT>;
229
+ };
230
+ declare type PostGlobalActionHook<mDT> = {
231
+ kind: 'post global action';
232
+ action: string;
233
+ handler: PostHookHandler<mDT>;
234
+ };
235
+ declare type PostAnyActionHook<mDT> = {
236
+ kind: 'post any action';
237
+ handler: PostHookHandler<mDT>;
238
+ };
239
+ declare type PostEntryHook<mDT> = {
240
+ kind: 'post entry';
241
+ to: string;
242
+ handler: PostHookHandler<mDT>;
243
+ };
244
+ declare type PostExitHook<mDT> = {
245
+ kind: 'post exit';
246
+ from: string;
247
+ handler: PostHookHandler<mDT>;
248
+ };
249
+ declare type HookDescription<mDT> = BasicHookDescription<mDT> | HookDescriptionWithAction<mDT> | GlobalActionHook<mDT> | AnyActionHook<mDT> | StandardTransitionHook<mDT> | MainTransitionHook<mDT> | ForcedTransitionHook<mDT> | AnyTransitionHook<mDT> | EntryHook<mDT> | ExitHook<mDT> | PostBasicHookDescription<mDT> | PostHookDescriptionWithAction<mDT> | PostGlobalActionHook<mDT> | PostAnyActionHook<mDT> | PostStandardTransitionHook<mDT> | PostMainTransitionHook<mDT> | PostForcedTransitionHook<mDT> | PostAnyTransitionHook<mDT> | PostEntryHook<mDT> | PostExitHook<mDT>;
201
250
  declare type HookComplexResult<mDT> = {
202
251
  pass: boolean;
203
252
  state?: StateType;
@@ -208,6 +257,7 @@ declare type HookContext<mDT> = {
208
257
  data: mDT;
209
258
  };
210
259
  declare type HookHandler<mDT> = (hook_context: HookContext<mDT>) => HookResult<mDT>;
260
+ declare type PostHookHandler<mDT> = (hook_context: HookContext<mDT>) => void;
211
261
  declare type JssmErrorExtendedInfo = {
212
262
  requested_state?: StateType | undefined;
213
263
  };
@@ -1,2 +1,2 @@
1
- const version = "5.72.5";
1
+ const version = "5.74.0";
2
2
  export { version };