@vedmalex/statemachine 1.0.0-beta.2 → 1.0.0-beta.3
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 +104 -4
- package/dist/index.cjs +105 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +104 -72
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/types/index.d.ts +23 -0
- package/types/scheduler.d.ts +31 -1
- package/types/state_machine.d.ts +2 -0
- package/types/types.d.ts +13 -0
package/README.md
CHANGED
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
[](https://github.com/vedmalex/statemachine/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@vedmalex/statemachine)
|
|
5
5
|
|
|
6
|
-
Hierarchical state machine for TypeScript with monitoring, validation, and persistence.
|
|
6
|
+
Hierarchical state machine for TypeScript with orthogonal (parallel) regions, SCXML/UML entry-exit semantics, monitoring, validation, and persistence.
|
|
7
|
+
|
|
8
|
+
**Features:**
|
|
9
|
+
|
|
10
|
+
- Hierarchical (nested) states addressed by dotted paths
|
|
11
|
+
- Orthogonal **parallel regions** with SCXML ancestor-first entry / descendant-first exit
|
|
12
|
+
- **UML all-regions-final join** via `final` states, the engine-raised `done.state.<id>` event, and the `isDone()` guard
|
|
13
|
+
- Parallel-exit (LCCA) — a transition from a composite parent preempts and exits all active regions
|
|
14
|
+
- Guards, before/enter/after + exit/transition actions, and timed `invoke` transitions
|
|
15
|
+
- Pluggable monitoring, validation, persistence, and timer scheduling (7 extension points)
|
|
16
|
+
- ESM + CJS dual bundle; DI-free lite surface
|
|
7
17
|
|
|
8
18
|
The package ships only the DI-free lite surface. The legacy DI-aware factory from `@grainjs/statemachine` is intentionally not carried over.
|
|
9
19
|
|
|
@@ -61,7 +71,20 @@ const sm = createMachine({
|
|
|
61
71
|
- **Parallel-exit** — a plain transition `from: 'proc'` on a user event preempts and exits all active regions immediately (LCCA).
|
|
62
72
|
- **Join** — when all regions are `final`, the engine raises `done.state.proc`; `sm.isDone('proc')` reflects the all-final configuration. (`done.state.*` is never matched by a `from: '*'` wildcard.)
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
Author the join either as the `done.state.<id>` transition above, **or** as a guard on any event:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
events: {
|
|
78
|
+
tryFinish: {
|
|
79
|
+
// fires only once every region of `proc` has reached a `final` state
|
|
80
|
+
transitions: [{ from: 'proc', to: 'complete', guard: () => sm.isDone('proc') }],
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Composites nest: a parent's `done.state` is raised only after every region — **including any nested composite** — is final, innermost-first. `done.state.<id>` is edge-triggered (raised once on entering the done configuration, not re-raised while the composite merely stays all-final).
|
|
86
|
+
|
|
87
|
+
See [`docs/regions-and-parallel.md`](./docs/regions-and-parallel.md) for the full model, ordering rules, nesting, and validation.
|
|
65
88
|
|
|
66
89
|
## Documentation
|
|
67
90
|
|
|
@@ -71,13 +94,90 @@ Full API documentation: [https://vedmalex.github.io/statemachine/](https://vedma
|
|
|
71
94
|
|
|
72
95
|
This package exposes 7 extension points (`IMonitor`, `ITimerScheduler`, `IErrorHandler`, `Adapter<T>`, `ILogger`, `StatePersistenceAdapter`, `validateConfig`) for host integration. Callbacks resolved from config or `setContext()` receive the underlying owner object directly, so host code does not need to unwrap `Adapter<T>` inside each callback. See [`docs/extension-points.md`](./docs/extension-points.md) for the full catalog.
|
|
73
96
|
|
|
97
|
+
## Deterministic testing (DST)
|
|
98
|
+
|
|
99
|
+
Machines that use `invoke` delays or a `transitionTimeout` normally depend on real wall-clock time (`Date.now` + `setTimeout`). That makes tests slow, flaky, and sensitive to scheduling jitter. The DST API swaps the clock and the timer scheduler for virtual counterparts so timer-driven behavior replays deterministically with **zero** real time elapsed.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { StateMachine, createVirtualScheduler } from '@vedmalex/statemachine'
|
|
103
|
+
|
|
104
|
+
let t = 0
|
|
105
|
+
const clock = () => t
|
|
106
|
+
const scheduler = createVirtualScheduler(clock)
|
|
107
|
+
|
|
108
|
+
const sm = new StateMachine(config, adapter, { clock, scheduler })
|
|
109
|
+
// ... arm the initial state's invoke timers
|
|
110
|
+
await Promise.resolve() // flush microtasks
|
|
111
|
+
|
|
112
|
+
t = 1000
|
|
113
|
+
scheduler.process() // fire every timer whose deadline <= 1000
|
|
114
|
+
await Promise.resolve() // flush microtasks so the transition settles
|
|
115
|
+
// assert sm.currentState === 'next'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### How it works
|
|
119
|
+
|
|
120
|
+
- `clock` replaces `Date.now` for `stateEntryTimes`, `resumeTimers`, and `getQueuedEvents` age math (and for the event-queue timestamps those ages are measured against, so age stays coherent under virtual time).
|
|
121
|
+
- `createVirtualScheduler(clock)` returns an `ITimerScheduler` whose `isActive()` is always `true`, so the `StateMachine` routes **all** `invoke` timers and the `transitionTimeout` through it.
|
|
122
|
+
- An **explicitly provided** scheduler is always used — the machine never falls back to real `setTimeout` while one is injected.
|
|
123
|
+
- `scheduler.process(now?)` drains every timer whose deadline `<= now` (default `now` is `clock()`), advancing zero real time. It is idempotent — draining twice does not re-fire a timer.
|
|
124
|
+
- `invoke` callbacks are async (they raise an event and queue the transition on a microtask), so after each `process()` you must flush microtasks (`await Promise.resolve()`, a few times for chained transitions) before asserting.
|
|
125
|
+
|
|
126
|
+
### Replaying serialized state
|
|
127
|
+
|
|
128
|
+
`toJSON()` / `fromJSON()` round-trips the recorded entry times as raw numbers. Restore into a fresh machine whose clock already reads the serialize time, and the remaining invoke delay is recomputed correctly:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// Original machine, invoke delay 1000ms, entered at t=0:
|
|
132
|
+
let t = 0
|
|
133
|
+
const clock = () => t
|
|
134
|
+
const sm = new StateMachine(config, adapter, { clock, scheduler: createVirtualScheduler(clock) })
|
|
135
|
+
|
|
136
|
+
t = 400
|
|
137
|
+
const json = sm.toJSON() // snapshot 400ms in
|
|
138
|
+
|
|
139
|
+
const scheduler2 = createVirtualScheduler(clock)
|
|
140
|
+
const sm2 = StateMachine.fromJSON(json, freshAdapter, { clock, scheduler: scheduler2 })
|
|
141
|
+
// 600ms remain:
|
|
142
|
+
t = 1000
|
|
143
|
+
scheduler2.process() // the invoke fires here, not at t=1400
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### transitionTimeout under virtual time
|
|
147
|
+
|
|
148
|
+
The `transitionTimeout` deadline is also routed through the injected scheduler, so it triggers on a virtual-time advance rather than a real timer:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
const sm = new StateMachine(config, adapter, { clock, scheduler, transitionTimeout: 500 })
|
|
152
|
+
const fired = sm.fireEvent('go') // enters a state whose action never resolves
|
|
153
|
+
await Promise.resolve()
|
|
154
|
+
t = 500
|
|
155
|
+
scheduler.process() // the race rejects deterministically
|
|
156
|
+
await expect(fired).rejects.toThrow(/timeout/i)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
When the action wins the race instead, the pending timeout token is auto-cancelled so no ghost rejection fires on a later `process()`.
|
|
160
|
+
|
|
161
|
+
### Back-compatibility
|
|
162
|
+
|
|
163
|
+
> Omitting **both** `clock` and `scheduler` keeps runtime behavior byte-identical to prior releases: `createDefaultScheduler()` uses `Date.now`, the `isActive()`-gated `setTimer` fallback to native `setTimeout` is unchanged, and `process()`'s default argument resolves to the same value it did before. The DST machinery only engages when you opt in by injecting a scheduler.
|
|
164
|
+
|
|
165
|
+
### API reference
|
|
166
|
+
|
|
167
|
+
| Symbol | Kind | Purpose |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| `createVirtualScheduler(clock)` | function | Build a deterministic, non-real-time `ITimerScheduler`. |
|
|
170
|
+
| `Clock` | type | `() => number`; matches the `clock` option signature. |
|
|
171
|
+
| `StateMachineOptions.clock?` | option | Inject a virtual clock (default `Date.now`). |
|
|
172
|
+
| `ITimerScheduler.process?(now?)` | method | Optional manual drain; implemented by `createVirtualScheduler`. |
|
|
173
|
+
|
|
74
174
|
## Stability policy
|
|
75
175
|
|
|
76
|
-
5 firm `@stable` symbols: `createMachine`, `StateMachine`, `StateMachineConfig`, `Transition`, `State`. Other exports are `@unstable` and may evolve between minor versions. See [`STABILITY.md`](./STABILITY.md) for the full policy.
|
|
176
|
+
5 firm `@stable` symbols: `createMachine`, `StateMachine`, `StateMachineConfig`, `Transition`, `State`. The all-regions-final join API lives on these stable symbols — `State.final?: boolean`, `StateMachine.isDone(compositeId)`, and the engine-raised `done.state.<id>` event (all reflected in `etc/statemachine.api.md`). Other exports are `@unstable` and may evolve between minor versions. See [`STABILITY.md`](./STABILITY.md) for the full policy.
|
|
77
177
|
|
|
78
178
|
## Status & module format
|
|
79
179
|
|
|
80
|
-
`1.0.0-beta.x
|
|
180
|
+
`1.0.0-beta.x` (current published version: see the npm badge above; both the `latest` and `beta` dist-tags track the newest release). Stability: experimental. SCXML/UML parallel regions, ancestor-first / descendant-first ordering, and the all-regions-final join landed in **`1.0.0-beta.2`**. The full API surface is `@unstable` per the package's STABILITY policy except the 5 firm `@stable` symbols; per-symbol stability tagging completes before `1.0.0` stable.
|
|
81
181
|
|
|
82
182
|
**Module format**: ESM + CJS dual bundle (TASK-005). `require('@vedmalex/statemachine')` works in CommonJS runtimes via `dist/index.cjs`. `import` works via `dist/index.js`. The `exports` map resolves automatically.
|
|
83
183
|
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
StateMachineError: () => StateMachineError,
|
|
36
36
|
createEnhancedError: () => createEnhancedError,
|
|
37
37
|
createMachine: () => createMachine,
|
|
38
|
+
createVirtualScheduler: () => createVirtualScheduler,
|
|
38
39
|
isAdapter: () => isAdapter,
|
|
39
40
|
isRecoverableError: () => isRecoverableError,
|
|
40
41
|
isValidConfig: () => isValidConfig,
|
|
@@ -53,7 +54,9 @@ var TimerScheduler = class {
|
|
|
53
54
|
intervalId = null;
|
|
54
55
|
pollingInterval = 100;
|
|
55
56
|
// ms
|
|
56
|
-
|
|
57
|
+
clock;
|
|
58
|
+
constructor(clock = Date.now) {
|
|
59
|
+
this.clock = clock;
|
|
57
60
|
}
|
|
58
61
|
/**
|
|
59
62
|
* Настройка режима опроса
|
|
@@ -77,7 +80,7 @@ var TimerScheduler = class {
|
|
|
77
80
|
*/
|
|
78
81
|
start() {
|
|
79
82
|
if (this.intervalId) return;
|
|
80
|
-
this.intervalId = setInterval(() => this.process(), this.pollingInterval);
|
|
83
|
+
this.intervalId = setInterval(() => this.process(this.clock()), this.pollingInterval);
|
|
81
84
|
}
|
|
82
85
|
/**
|
|
83
86
|
* Остановка единого таймера
|
|
@@ -96,7 +99,7 @@ var TimerScheduler = class {
|
|
|
96
99
|
*/
|
|
97
100
|
schedule(delay, callback) {
|
|
98
101
|
const token = {};
|
|
99
|
-
const executeAt =
|
|
102
|
+
const executeAt = this.clock() + delay;
|
|
100
103
|
const task = { token, executeAt, callback };
|
|
101
104
|
this.activeTokens.add(token);
|
|
102
105
|
this.insert(task);
|
|
@@ -112,7 +115,7 @@ var TimerScheduler = class {
|
|
|
112
115
|
/**
|
|
113
116
|
* Обработать очередь (вызывается таймером или вручную)
|
|
114
117
|
*/
|
|
115
|
-
process(now =
|
|
118
|
+
process(now = this.clock()) {
|
|
116
119
|
while (this.heap.length > 0) {
|
|
117
120
|
const task = this.heap[0];
|
|
118
121
|
if (task === void 0) break;
|
|
@@ -203,6 +206,23 @@ var TimerScheduler = class {
|
|
|
203
206
|
function createDefaultScheduler() {
|
|
204
207
|
return new TimerScheduler();
|
|
205
208
|
}
|
|
209
|
+
function createVirtualScheduler(clock) {
|
|
210
|
+
const inner = new TimerScheduler(clock);
|
|
211
|
+
return {
|
|
212
|
+
isActive() {
|
|
213
|
+
return true;
|
|
214
|
+
},
|
|
215
|
+
schedule(delay, callback) {
|
|
216
|
+
return inner.schedule(delay, callback);
|
|
217
|
+
},
|
|
218
|
+
cancel(token) {
|
|
219
|
+
inner.cancel(token);
|
|
220
|
+
},
|
|
221
|
+
process(now) {
|
|
222
|
+
inner.process(now ?? clock());
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
206
226
|
|
|
207
227
|
// src/logger.ts
|
|
208
228
|
var LogLevel = {
|
|
@@ -1749,6 +1769,8 @@ var StateMachine = class _StateMachine {
|
|
|
1749
1769
|
monitor;
|
|
1750
1770
|
scheduler;
|
|
1751
1771
|
errorHandler;
|
|
1772
|
+
clock;
|
|
1773
|
+
schedulerProvided;
|
|
1752
1774
|
// Свойства
|
|
1753
1775
|
states;
|
|
1754
1776
|
events;
|
|
@@ -1799,7 +1821,9 @@ var StateMachine = class _StateMachine {
|
|
|
1799
1821
|
this.initialState = config.initialState;
|
|
1800
1822
|
this.logger = options?.logger ?? ConsoleLogger;
|
|
1801
1823
|
this.monitor = options?.monitor ?? createDefaultMonitor();
|
|
1824
|
+
this.schedulerProvided = options?.scheduler !== void 0;
|
|
1802
1825
|
this.scheduler = options?.scheduler ?? createDefaultScheduler();
|
|
1826
|
+
this.clock = options?.clock ?? Date.now;
|
|
1803
1827
|
this.errorHandler = options?.errorHandler ?? createDefaultErrorHandler();
|
|
1804
1828
|
if (adaptee) {
|
|
1805
1829
|
if (!isAdapter(adaptee)) {
|
|
@@ -1873,7 +1897,7 @@ var StateMachine = class _StateMachine {
|
|
|
1873
1897
|
args,
|
|
1874
1898
|
resolve,
|
|
1875
1899
|
reject,
|
|
1876
|
-
timestamp:
|
|
1900
|
+
timestamp: this.clock(),
|
|
1877
1901
|
type: "external"
|
|
1878
1902
|
});
|
|
1879
1903
|
this.scheduleProcessing();
|
|
@@ -1884,7 +1908,7 @@ var StateMachine = class _StateMachine {
|
|
|
1884
1908
|
eventName,
|
|
1885
1909
|
obj,
|
|
1886
1910
|
args,
|
|
1887
|
-
timestamp:
|
|
1911
|
+
timestamp: this.clock(),
|
|
1888
1912
|
type: "internal"
|
|
1889
1913
|
});
|
|
1890
1914
|
return Promise.resolve(true);
|
|
@@ -1895,7 +1919,7 @@ var StateMachine = class _StateMachine {
|
|
|
1895
1919
|
eventName,
|
|
1896
1920
|
obj,
|
|
1897
1921
|
args,
|
|
1898
|
-
timestamp:
|
|
1922
|
+
timestamp: this.clock(),
|
|
1899
1923
|
type: "internal"
|
|
1900
1924
|
});
|
|
1901
1925
|
}
|
|
@@ -2056,7 +2080,7 @@ var StateMachine = class _StateMachine {
|
|
|
2056
2080
|
};
|
|
2057
2081
|
}
|
|
2058
2082
|
getQueuedEvents() {
|
|
2059
|
-
const now =
|
|
2083
|
+
const now = this.clock();
|
|
2060
2084
|
const mapEvent = (evt) => ({
|
|
2061
2085
|
id: evt.id,
|
|
2062
2086
|
event: evt.eventName,
|
|
@@ -2175,53 +2199,51 @@ var StateMachine = class _StateMachine {
|
|
|
2175
2199
|
return currentParts.every((part, index) => part === expectedParts[index]);
|
|
2176
2200
|
}
|
|
2177
2201
|
attachToObject(object, eventMap) {
|
|
2178
|
-
for (const objectEventName
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
};
|
|
2224
|
-
}
|
|
2202
|
+
for (const objectEventName of Object.keys(eventMap)) {
|
|
2203
|
+
const stateMachineEventName = eventMap[objectEventName];
|
|
2204
|
+
if (stateMachineEventName === void 0) continue;
|
|
2205
|
+
if (typeof object.addEventListener === "function") {
|
|
2206
|
+
object.addEventListener(objectEventName, (...args) => {
|
|
2207
|
+
this.fireEvent(stateMachineEventName, object, ...args).catch(
|
|
2208
|
+
(e) => this.logger.error(
|
|
2209
|
+
"Error firing event",
|
|
2210
|
+
{
|
|
2211
|
+
objectEventName,
|
|
2212
|
+
stateMachineEventName
|
|
2213
|
+
},
|
|
2214
|
+
/* c8 ignore next */
|
|
2215
|
+
e instanceof Error ? e : new Error(String(e))
|
|
2216
|
+
)
|
|
2217
|
+
);
|
|
2218
|
+
});
|
|
2219
|
+
} else if (typeof object.on === "function") {
|
|
2220
|
+
object.on(objectEventName, (...args) => {
|
|
2221
|
+
this.fireEvent(stateMachineEventName, object, ...args).catch(
|
|
2222
|
+
(e) => this.logger.error(
|
|
2223
|
+
"Error firing event",
|
|
2224
|
+
{
|
|
2225
|
+
objectEventName,
|
|
2226
|
+
stateMachineEventName
|
|
2227
|
+
},
|
|
2228
|
+
/* c8 ignore next */
|
|
2229
|
+
e instanceof Error ? e : new Error(String(e))
|
|
2230
|
+
)
|
|
2231
|
+
);
|
|
2232
|
+
});
|
|
2233
|
+
} else {
|
|
2234
|
+
object[`on${objectEventName}`] = async (...args) => {
|
|
2235
|
+
return this.fireEvent(stateMachineEventName, object, ...args).catch(
|
|
2236
|
+
(e) => this.logger.error(
|
|
2237
|
+
"Error firing event",
|
|
2238
|
+
{
|
|
2239
|
+
objectEventName,
|
|
2240
|
+
stateMachineEventName
|
|
2241
|
+
},
|
|
2242
|
+
/* c8 ignore next */
|
|
2243
|
+
e instanceof Error ? e : new Error(String(e))
|
|
2244
|
+
)
|
|
2245
|
+
);
|
|
2246
|
+
};
|
|
2225
2247
|
}
|
|
2226
2248
|
}
|
|
2227
2249
|
}
|
|
@@ -3011,19 +3033,22 @@ var StateMachine = class _StateMachine {
|
|
|
3011
3033
|
}
|
|
3012
3034
|
};
|
|
3013
3035
|
if (this.transitionTimeout && this.transitionTimeout > 0) {
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
)
|
|
3026
|
-
|
|
3036
|
+
const timeoutMs = this.transitionTimeout;
|
|
3037
|
+
let timeoutHandle;
|
|
3038
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
3039
|
+
const fire = () => reject(new StateMachineError("Transition timeout", {
|
|
3040
|
+
/* c8 ignore next */
|
|
3041
|
+
action: typeof actionName === "string" ? actionName : "anonymous",
|
|
3042
|
+
phase: "action"
|
|
3043
|
+
}));
|
|
3044
|
+
timeoutHandle = this.setTimer(fire, timeoutMs);
|
|
3045
|
+
});
|
|
3046
|
+
if (this.schedulerProvided) {
|
|
3047
|
+
return Promise.race([executeAction(), timeoutPromise]).finally(() => {
|
|
3048
|
+
this.clearTimer(timeoutHandle);
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
return Promise.race([executeAction(), timeoutPromise]);
|
|
3027
3052
|
}
|
|
3028
3053
|
return executeAction();
|
|
3029
3054
|
}
|
|
@@ -3240,7 +3265,7 @@ var StateMachine = class _StateMachine {
|
|
|
3240
3265
|
}
|
|
3241
3266
|
if (toState.invoke && toState.invoke.length > 0) {
|
|
3242
3267
|
if (!this.stateEntryTimes.has(toStateName)) {
|
|
3243
|
-
this.stateEntryTimes.set(toStateName,
|
|
3268
|
+
this.stateEntryTimes.set(toStateName, this.clock());
|
|
3244
3269
|
}
|
|
3245
3270
|
const timers = [];
|
|
3246
3271
|
for (const invocation of toState.invoke) {
|
|
@@ -3286,6 +3311,9 @@ var StateMachine = class _StateMachine {
|
|
|
3286
3311
|
*/
|
|
3287
3312
|
setTimer(callback, delay) {
|
|
3288
3313
|
const scheduler = this.scheduler;
|
|
3314
|
+
if (this.schedulerProvided) {
|
|
3315
|
+
return scheduler.schedule(delay, callback);
|
|
3316
|
+
}
|
|
3289
3317
|
if (scheduler.isActive()) {
|
|
3290
3318
|
return scheduler.schedule(delay, callback);
|
|
3291
3319
|
}
|
|
@@ -3296,6 +3324,10 @@ var StateMachine = class _StateMachine {
|
|
|
3296
3324
|
*/
|
|
3297
3325
|
clearTimer(timerId) {
|
|
3298
3326
|
const scheduler = this.scheduler;
|
|
3327
|
+
if (this.schedulerProvided) {
|
|
3328
|
+
if (timerId !== void 0) scheduler.cancel(timerId);
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3299
3331
|
if (scheduler.isActive() && typeof timerId === "object" && timerId !== null && !("ref" in timerId)) {
|
|
3300
3332
|
scheduler.cancel(timerId);
|
|
3301
3333
|
} else {
|
|
@@ -3453,13 +3485,13 @@ var StateMachine = class _StateMachine {
|
|
|
3453
3485
|
this.stateEntryTimes.delete(stateName);
|
|
3454
3486
|
}
|
|
3455
3487
|
}
|
|
3456
|
-
const now =
|
|
3488
|
+
const now = this.clock();
|
|
3457
3489
|
for (const stateName of activeStates) {
|
|
3458
3490
|
const state = this.states.get(stateName);
|
|
3459
3491
|
if (!state || !state.invoke || state.invoke.length === 0) continue;
|
|
3460
3492
|
const entryTime = this.stateEntryTimes.get(stateName);
|
|
3461
|
-
const startTime = entryTime
|
|
3462
|
-
if (
|
|
3493
|
+
const startTime = entryTime ?? now;
|
|
3494
|
+
if (entryTime === void 0) {
|
|
3463
3495
|
this.stateEntryTimes.set(stateName, startTime);
|
|
3464
3496
|
}
|
|
3465
3497
|
const elapsed = now - startTime;
|
|
@@ -4396,6 +4428,7 @@ function isValidConfig(config) {
|
|
|
4396
4428
|
StateMachineError,
|
|
4397
4429
|
createEnhancedError,
|
|
4398
4430
|
createMachine,
|
|
4431
|
+
createVirtualScheduler,
|
|
4399
4432
|
isAdapter,
|
|
4400
4433
|
isRecoverableError,
|
|
4401
4434
|
isValidConfig,
|