xstate 5.0.0-beta.27 → 5.0.0-beta.29
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 +9 -7
- package/actions/dist/xstate-actions.cjs.js +11 -17
- package/actions/dist/xstate-actions.cjs.mjs +0 -8
- package/actions/dist/xstate-actions.development.cjs.js +11 -17
- package/actions/dist/xstate-actions.development.cjs.mjs +0 -8
- package/actions/dist/xstate-actions.development.esm.js +3 -1
- package/actions/dist/xstate-actions.esm.js +3 -1
- package/actions/dist/xstate-actions.umd.min.js +1 -1
- package/actions/dist/xstate-actions.umd.min.js.map +1 -1
- package/actors/dist/xstate-actors.cjs.js +395 -28
- package/actors/dist/xstate-actors.cjs.mjs +1 -3
- package/actors/dist/xstate-actors.development.cjs.js +395 -28
- package/actors/dist/xstate-actors.development.cjs.mjs +1 -3
- package/actors/dist/xstate-actors.development.esm.js +389 -21
- package/actors/dist/xstate-actors.esm.js +389 -21
- package/actors/dist/xstate-actors.umd.min.js +1 -1
- package/actors/dist/xstate-actors.umd.min.js.map +1 -1
- package/dist/declarations/src/StateNode.d.ts +3 -3
- package/dist/declarations/src/actions/assign.d.ts +9 -4
- package/dist/declarations/src/actions/cancel.d.ts +4 -4
- package/dist/declarations/src/actions/choose.d.ts +5 -4
- package/dist/declarations/src/actions/log.d.ts +4 -4
- package/dist/declarations/src/actions/pure.d.ts +6 -5
- package/dist/declarations/src/actions/raise.d.ts +2 -2
- package/dist/declarations/src/actions/send.d.ts +7 -7
- package/dist/declarations/src/actions/stop.d.ts +4 -4
- package/dist/declarations/src/actions.d.ts +8 -44
- package/dist/declarations/src/actors/callback.d.ts +2 -2
- package/dist/declarations/src/actors/index.d.ts +3 -5
- package/dist/declarations/src/constants.d.ts +1 -0
- package/dist/declarations/src/index.d.ts +9 -16
- package/dist/declarations/src/spawn.d.ts +25 -0
- package/dist/declarations/src/stateUtils.d.ts +1 -1
- package/dist/declarations/src/typegenTypes.d.ts +4 -4
- package/dist/declarations/src/types.d.ts +92 -122
- package/dist/declarations/src/utils.d.ts +2 -2
- package/dist/interpreter-498891b2.esm.js +741 -0
- package/dist/interpreter-6e7909c8.development.esm.js +749 -0
- package/dist/interpreter-c357bc50.cjs.js +773 -0
- package/dist/interpreter-e2c6a579.development.cjs.js +781 -0
- package/dist/{actions-9754d2ca.development.esm.js → raise-03e57569.cjs.js} +130 -1307
- package/dist/{actions-d1dba4ac.cjs.js → raise-59f2c242.esm.js} +65 -1267
- package/dist/{actions-020463e9.esm.js → raise-e778a828.development.esm.js} +109 -1203
- package/dist/{actions-ca622922.development.cjs.js → raise-f751dfac.development.cjs.js} +101 -1306
- package/dist/send-42c83fb2.development.esm.js +364 -0
- package/dist/send-51717e53.cjs.js +349 -0
- package/dist/send-f53778f6.development.cjs.js +374 -0
- package/dist/send-fff224db.esm.js +339 -0
- package/dist/xstate.cjs.js +114 -112
- package/dist/xstate.cjs.mjs +2 -2
- package/dist/xstate.development.cjs.js +114 -112
- package/dist/xstate.development.cjs.mjs +2 -2
- package/dist/xstate.development.esm.js +78 -74
- package/dist/xstate.esm.js +78 -74
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/guards/dist/xstate-guards.cjs.js +2 -1
- package/guards/dist/xstate-guards.development.cjs.js +2 -1
- package/guards/dist/xstate-guards.development.esm.js +2 -1
- package/guards/dist/xstate-guards.esm.js +2 -1
- package/guards/dist/xstate-guards.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/declarations/src/constantPrefixes.d.ts +0 -6
- package/dist/promise-2ad94e3b.development.esm.js +0 -406
- package/dist/promise-3b7e3357.development.cjs.js +0 -412
- package/dist/promise-5b07c38e.esm.js +0 -406
- package/dist/promise-7a8c1768.cjs.js +0 -412
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import { devToolsAdapter } from '../dev/dist/xstate-dev.development.esm.js';
|
|
2
|
+
|
|
3
|
+
const STATE_DELIMITER = '.';
|
|
4
|
+
const TARGETLESS_KEY = '';
|
|
5
|
+
const NULL_EVENT = '';
|
|
6
|
+
const STATE_IDENTIFIER = '#';
|
|
7
|
+
const WILDCARD = '*';
|
|
8
|
+
const XSTATE_INIT = 'xstate.init';
|
|
9
|
+
const XSTATE_ERROR = 'xstate.error';
|
|
10
|
+
const XSTATE_STOP = 'xstate.stop';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns an event that represents an implicit event that
|
|
14
|
+
* is sent after the specified `delay`.
|
|
15
|
+
*
|
|
16
|
+
* @param delayRef The delay in milliseconds
|
|
17
|
+
* @param id The state node ID where this event is handled
|
|
18
|
+
*/
|
|
19
|
+
function createAfterEvent(delayRef, id) {
|
|
20
|
+
const idSuffix = id ? `#${id}` : '';
|
|
21
|
+
return {
|
|
22
|
+
type: `xstate.after(${delayRef})${idSuffix}`
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns an event that represents that a final state node
|
|
28
|
+
* has been reached in the parent state node.
|
|
29
|
+
*
|
|
30
|
+
* @param id The final state node's parent state node `id`
|
|
31
|
+
* @param output The data to pass into the event
|
|
32
|
+
*/
|
|
33
|
+
function createDoneStateEvent(id, output) {
|
|
34
|
+
return {
|
|
35
|
+
type: `xstate.done.state.${id}`,
|
|
36
|
+
output
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns an event that represents that an invoked service has terminated.
|
|
42
|
+
*
|
|
43
|
+
* An invoked service is terminated when it has reached a top-level final state node,
|
|
44
|
+
* but not when it is canceled.
|
|
45
|
+
*
|
|
46
|
+
* @param invokeId The invoked service ID
|
|
47
|
+
* @param output The data to pass into the event
|
|
48
|
+
*/
|
|
49
|
+
function createDoneActorEvent(invokeId, output) {
|
|
50
|
+
return {
|
|
51
|
+
type: `xstate.done.actor.${invokeId}`,
|
|
52
|
+
output
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function createErrorActorEvent(id, data) {
|
|
56
|
+
return {
|
|
57
|
+
type: `xstate.error.actor.${id}`,
|
|
58
|
+
data
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function createInitEvent(input) {
|
|
62
|
+
return {
|
|
63
|
+
type: XSTATE_INIT,
|
|
64
|
+
input
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class Mailbox {
|
|
69
|
+
constructor(_process) {
|
|
70
|
+
this._process = _process;
|
|
71
|
+
this._active = false;
|
|
72
|
+
this._current = null;
|
|
73
|
+
this._last = null;
|
|
74
|
+
}
|
|
75
|
+
start() {
|
|
76
|
+
this._active = true;
|
|
77
|
+
this.flush();
|
|
78
|
+
}
|
|
79
|
+
clear() {
|
|
80
|
+
// we can't set _current to null because we might be currently processing
|
|
81
|
+
// and enqueue following clear shouldnt start processing the enqueued item immediately
|
|
82
|
+
if (this._current) {
|
|
83
|
+
this._current.next = null;
|
|
84
|
+
this._last = this._current;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// TODO: rethink this design
|
|
89
|
+
prepend(event) {
|
|
90
|
+
if (!this._current) {
|
|
91
|
+
this.enqueue(event);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// we know that something is already queued up
|
|
96
|
+
// so the mailbox is already flushing or it's inactive
|
|
97
|
+
// therefore the only thing that we need to do is to reassign `this._current`
|
|
98
|
+
this._current = {
|
|
99
|
+
value: event,
|
|
100
|
+
next: this._current
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
enqueue(event) {
|
|
104
|
+
const enqueued = {
|
|
105
|
+
value: event,
|
|
106
|
+
next: null
|
|
107
|
+
};
|
|
108
|
+
if (this._current) {
|
|
109
|
+
this._last.next = enqueued;
|
|
110
|
+
this._last = enqueued;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this._current = enqueued;
|
|
114
|
+
this._last = enqueued;
|
|
115
|
+
if (this._active) {
|
|
116
|
+
this.flush();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
flush() {
|
|
120
|
+
while (this._current) {
|
|
121
|
+
// atm the given _process is responsible for implementing proper try/catch handling
|
|
122
|
+
// we assume here that this won't throw in a way that can affect this mailbox
|
|
123
|
+
const consumed = this._current;
|
|
124
|
+
this._process(consumed.value);
|
|
125
|
+
// something could have been prepended in the meantime
|
|
126
|
+
// so we need to be defensive here to avoid skipping over a prepended item
|
|
127
|
+
if (consumed === this._current) {
|
|
128
|
+
this._current = this._current.next;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
this._last = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* This function makes sure that unhandled errors are thrown in a separate macrotask.
|
|
137
|
+
* It allows those errors to be detected by global error handlers and reported to bug tracking services
|
|
138
|
+
* without interrupting our own stack of execution.
|
|
139
|
+
*
|
|
140
|
+
* @param err error to be thrown
|
|
141
|
+
*/
|
|
142
|
+
function reportUnhandledError(err) {
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
throw err;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
|
|
149
|
+
|
|
150
|
+
function createSystem() {
|
|
151
|
+
let sessionIdCounter = 0;
|
|
152
|
+
const children = new Map();
|
|
153
|
+
const keyedActors = new Map();
|
|
154
|
+
const reverseKeyedActors = new WeakMap();
|
|
155
|
+
const system = {
|
|
156
|
+
_bookId: () => `x:${sessionIdCounter++}`,
|
|
157
|
+
_register: (sessionId, actorRef) => {
|
|
158
|
+
children.set(sessionId, actorRef);
|
|
159
|
+
return sessionId;
|
|
160
|
+
},
|
|
161
|
+
_unregister: actorRef => {
|
|
162
|
+
children.delete(actorRef.sessionId);
|
|
163
|
+
const systemId = reverseKeyedActors.get(actorRef);
|
|
164
|
+
if (systemId !== undefined) {
|
|
165
|
+
keyedActors.delete(systemId);
|
|
166
|
+
reverseKeyedActors.delete(actorRef);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
get: systemId => {
|
|
170
|
+
return keyedActors.get(systemId);
|
|
171
|
+
},
|
|
172
|
+
_set: (systemId, actorRef) => {
|
|
173
|
+
const existing = keyedActors.get(systemId);
|
|
174
|
+
if (existing && existing !== actorRef) {
|
|
175
|
+
throw new Error(`Actor with system ID '${systemId}' already exists.`);
|
|
176
|
+
}
|
|
177
|
+
keyedActors.set(systemId, actorRef);
|
|
178
|
+
reverseKeyedActors.set(actorRef, systemId);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
return system;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function matchesState(parentStateId, childStateId) {
|
|
185
|
+
const parentStateValue = toStateValue(parentStateId);
|
|
186
|
+
const childStateValue = toStateValue(childStateId);
|
|
187
|
+
if (typeof childStateValue === 'string') {
|
|
188
|
+
if (typeof parentStateValue === 'string') {
|
|
189
|
+
return childStateValue === parentStateValue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Parent more specific than child
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
if (typeof parentStateValue === 'string') {
|
|
196
|
+
return parentStateValue in childStateValue;
|
|
197
|
+
}
|
|
198
|
+
return Object.keys(parentStateValue).every(key => {
|
|
199
|
+
if (!(key in childStateValue)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
return matchesState(parentStateValue[key], childStateValue[key]);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
function toStatePath(stateId) {
|
|
206
|
+
try {
|
|
207
|
+
if (isArray(stateId)) {
|
|
208
|
+
return stateId;
|
|
209
|
+
}
|
|
210
|
+
return stateId.toString().split(STATE_DELIMITER);
|
|
211
|
+
} catch (e) {
|
|
212
|
+
throw new Error(`'${stateId}' is not a valid state path.`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function isStateLike(state) {
|
|
216
|
+
return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
|
|
217
|
+
}
|
|
218
|
+
function toStateValue(stateValue) {
|
|
219
|
+
if (isStateLike(stateValue)) {
|
|
220
|
+
return stateValue.value;
|
|
221
|
+
}
|
|
222
|
+
if (isArray(stateValue)) {
|
|
223
|
+
return pathToStateValue(stateValue);
|
|
224
|
+
}
|
|
225
|
+
if (typeof stateValue !== 'string') {
|
|
226
|
+
return stateValue;
|
|
227
|
+
}
|
|
228
|
+
const statePath = toStatePath(stateValue);
|
|
229
|
+
return pathToStateValue(statePath);
|
|
230
|
+
}
|
|
231
|
+
function pathToStateValue(statePath) {
|
|
232
|
+
if (statePath.length === 1) {
|
|
233
|
+
return statePath[0];
|
|
234
|
+
}
|
|
235
|
+
const value = {};
|
|
236
|
+
let marker = value;
|
|
237
|
+
for (let i = 0; i < statePath.length - 1; i++) {
|
|
238
|
+
if (i === statePath.length - 2) {
|
|
239
|
+
marker[statePath[i]] = statePath[i + 1];
|
|
240
|
+
} else {
|
|
241
|
+
const previous = marker;
|
|
242
|
+
marker = {};
|
|
243
|
+
previous[statePath[i]] = marker;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
function mapValues(collection, iteratee) {
|
|
249
|
+
const result = {};
|
|
250
|
+
const collectionKeys = Object.keys(collection);
|
|
251
|
+
for (let i = 0; i < collectionKeys.length; i++) {
|
|
252
|
+
const key = collectionKeys[i];
|
|
253
|
+
result[key] = iteratee(collection[key], key, collection, i);
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
function flatten(array) {
|
|
258
|
+
return [].concat(...array);
|
|
259
|
+
}
|
|
260
|
+
function toArrayStrict(value) {
|
|
261
|
+
if (isArray(value)) {
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
return [value];
|
|
265
|
+
}
|
|
266
|
+
function toArray(value) {
|
|
267
|
+
if (value === undefined) {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
return toArrayStrict(value);
|
|
271
|
+
}
|
|
272
|
+
function mapContext(mapper, context, event, self) {
|
|
273
|
+
if (typeof mapper === 'function') {
|
|
274
|
+
return mapper({
|
|
275
|
+
context,
|
|
276
|
+
event,
|
|
277
|
+
self
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (typeof mapper === 'object' && Object.values(mapper).some(val => typeof val === 'function')) {
|
|
281
|
+
console.warn(`Dynamically mapping values to individual properties is deprecated. Use a single function that returns the mapped object instead.\nFound object containing properties whose values are possibly mapping functions: ${Object.entries(mapper).filter(([key, value]) => typeof value === 'function').map(([key, value]) => `\n - ${key}: ${value.toString().replace(/\n\s*/g, '')}`).join('')}`);
|
|
282
|
+
}
|
|
283
|
+
return mapper;
|
|
284
|
+
}
|
|
285
|
+
function isArray(value) {
|
|
286
|
+
return Array.isArray(value);
|
|
287
|
+
}
|
|
288
|
+
function isErrorActorEvent(event) {
|
|
289
|
+
return event.type.startsWith('xstate.error.actor');
|
|
290
|
+
}
|
|
291
|
+
function toTransitionConfigArray(configLike) {
|
|
292
|
+
return toArrayStrict(configLike).map(transitionLike => {
|
|
293
|
+
if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
|
|
294
|
+
return {
|
|
295
|
+
target: transitionLike
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
return transitionLike;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
function normalizeTarget(target) {
|
|
302
|
+
if (target === undefined || target === TARGETLESS_KEY) {
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
return toArray(target);
|
|
306
|
+
}
|
|
307
|
+
function toObserver(nextHandler, errorHandler, completionHandler) {
|
|
308
|
+
const isObserver = typeof nextHandler === 'object';
|
|
309
|
+
const self = isObserver ? nextHandler : undefined;
|
|
310
|
+
return {
|
|
311
|
+
next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
|
|
312
|
+
error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
|
|
313
|
+
complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function createInvokeId(stateNodeId, index) {
|
|
317
|
+
return `${stateNodeId}:invocation[${index}]`;
|
|
318
|
+
}
|
|
319
|
+
function resolveReferencedActor(referenced) {
|
|
320
|
+
return referenced ? 'transition' in referenced ? {
|
|
321
|
+
src: referenced,
|
|
322
|
+
input: undefined
|
|
323
|
+
} : referenced : undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let ActorStatus = /*#__PURE__*/function (ActorStatus) {
|
|
327
|
+
ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
|
|
328
|
+
ActorStatus[ActorStatus["Running"] = 1] = "Running";
|
|
329
|
+
ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
|
|
330
|
+
return ActorStatus;
|
|
331
|
+
}({});
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @deprecated Use `ActorStatus` instead.
|
|
335
|
+
*/
|
|
336
|
+
const InterpreterStatus = ActorStatus;
|
|
337
|
+
const defaultOptions = {
|
|
338
|
+
clock: {
|
|
339
|
+
setTimeout: (fn, ms) => {
|
|
340
|
+
return setTimeout(fn, ms);
|
|
341
|
+
},
|
|
342
|
+
clearTimeout: id => {
|
|
343
|
+
return clearTimeout(id);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
logger: console.log.bind(console),
|
|
347
|
+
devTools: false
|
|
348
|
+
};
|
|
349
|
+
class Actor {
|
|
350
|
+
/**
|
|
351
|
+
* The current internal state of the actor.
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
|
|
356
|
+
*/
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The unique identifier for this actor relative to its parent.
|
|
360
|
+
*/
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Whether the service is started.
|
|
364
|
+
*/
|
|
365
|
+
|
|
366
|
+
// Actor Ref
|
|
367
|
+
|
|
368
|
+
// TODO: add typings for system
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* The globally unique process ID for this invocation.
|
|
372
|
+
*/
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Creates a new actor instance for the given logic with the provided options, if any.
|
|
376
|
+
*
|
|
377
|
+
* @param logic The logic to create an actor from
|
|
378
|
+
* @param options Actor options
|
|
379
|
+
*/
|
|
380
|
+
constructor(logic, options) {
|
|
381
|
+
this.logic = logic;
|
|
382
|
+
this._state = void 0;
|
|
383
|
+
this.clock = void 0;
|
|
384
|
+
this.options = void 0;
|
|
385
|
+
this.id = void 0;
|
|
386
|
+
this.mailbox = new Mailbox(this._process.bind(this));
|
|
387
|
+
this.delayedEventsMap = {};
|
|
388
|
+
this.observers = new Set();
|
|
389
|
+
this.logger = void 0;
|
|
390
|
+
this.status = ActorStatus.NotStarted;
|
|
391
|
+
this._parent = void 0;
|
|
392
|
+
this.ref = void 0;
|
|
393
|
+
this._actorContext = void 0;
|
|
394
|
+
this._systemId = void 0;
|
|
395
|
+
this.sessionId = void 0;
|
|
396
|
+
this.system = void 0;
|
|
397
|
+
this._doneEvent = void 0;
|
|
398
|
+
this.src = void 0;
|
|
399
|
+
this._deferred = [];
|
|
400
|
+
const resolvedOptions = {
|
|
401
|
+
...defaultOptions,
|
|
402
|
+
...options
|
|
403
|
+
};
|
|
404
|
+
const {
|
|
405
|
+
clock,
|
|
406
|
+
logger,
|
|
407
|
+
parent,
|
|
408
|
+
id,
|
|
409
|
+
systemId
|
|
410
|
+
} = resolvedOptions;
|
|
411
|
+
const self = this;
|
|
412
|
+
this.system = parent?.system ?? createSystem();
|
|
413
|
+
if (systemId) {
|
|
414
|
+
this._systemId = systemId;
|
|
415
|
+
this.system._set(systemId, this);
|
|
416
|
+
}
|
|
417
|
+
this.sessionId = this.system._bookId();
|
|
418
|
+
this.id = id ?? this.sessionId;
|
|
419
|
+
this.logger = logger;
|
|
420
|
+
this.clock = clock;
|
|
421
|
+
this._parent = parent;
|
|
422
|
+
this.options = resolvedOptions;
|
|
423
|
+
this.src = resolvedOptions.src;
|
|
424
|
+
this.ref = this;
|
|
425
|
+
this._actorContext = {
|
|
426
|
+
self,
|
|
427
|
+
id: this.id,
|
|
428
|
+
sessionId: this.sessionId,
|
|
429
|
+
logger: this.logger,
|
|
430
|
+
defer: fn => {
|
|
431
|
+
this._deferred.push(fn);
|
|
432
|
+
},
|
|
433
|
+
system: this.system,
|
|
434
|
+
stopChild: child => {
|
|
435
|
+
if (child._parent !== this) {
|
|
436
|
+
throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
|
|
437
|
+
}
|
|
438
|
+
child._stop();
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Ensure that the send method is bound to this Actor instance
|
|
443
|
+
// if destructured
|
|
444
|
+
this.send = this.send.bind(this);
|
|
445
|
+
this._initState();
|
|
446
|
+
}
|
|
447
|
+
_initState() {
|
|
448
|
+
this._state = this.options.state ? this.logic.restoreState ? this.logic.restoreState(this.options.state, this._actorContext) : this.options.state : this.logic.getInitialState(this._actorContext, this.options?.input);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// array of functions to defer
|
|
452
|
+
|
|
453
|
+
update(state) {
|
|
454
|
+
// Update state
|
|
455
|
+
this._state = state;
|
|
456
|
+
const snapshot = this.getSnapshot();
|
|
457
|
+
|
|
458
|
+
// Execute deferred effects
|
|
459
|
+
let deferredFn;
|
|
460
|
+
while (deferredFn = this._deferred.shift()) {
|
|
461
|
+
deferredFn();
|
|
462
|
+
}
|
|
463
|
+
for (const observer of this.observers) {
|
|
464
|
+
// TODO: should observers be notified in case of the error?
|
|
465
|
+
try {
|
|
466
|
+
observer.next?.(snapshot);
|
|
467
|
+
} catch (err) {
|
|
468
|
+
reportUnhandledError(err);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const status = this.logic.getStatus?.(state);
|
|
472
|
+
switch (status?.status) {
|
|
473
|
+
case 'done':
|
|
474
|
+
this._stopProcedure();
|
|
475
|
+
this._complete();
|
|
476
|
+
this._doneEvent = createDoneActorEvent(this.id, status.data);
|
|
477
|
+
this._parent?.send(this._doneEvent);
|
|
478
|
+
break;
|
|
479
|
+
case 'error':
|
|
480
|
+
this._stopProcedure();
|
|
481
|
+
this._error(status.data);
|
|
482
|
+
this._parent?.send(createErrorActorEvent(this.id, status.data));
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
subscribe(nextListenerOrObserver, errorListener, completeListener) {
|
|
487
|
+
const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
|
|
488
|
+
if (this.status !== ActorStatus.Stopped) {
|
|
489
|
+
this.observers.add(observer);
|
|
490
|
+
} else {
|
|
491
|
+
try {
|
|
492
|
+
observer.complete?.();
|
|
493
|
+
} catch (err) {
|
|
494
|
+
reportUnhandledError(err);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
unsubscribe: () => {
|
|
499
|
+
this.observers.delete(observer);
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Starts the Actor from the initial state
|
|
506
|
+
*/
|
|
507
|
+
start() {
|
|
508
|
+
if (this.status === ActorStatus.Running) {
|
|
509
|
+
// Do not restart the service if it is already started
|
|
510
|
+
return this;
|
|
511
|
+
}
|
|
512
|
+
this.system._register(this.sessionId, this);
|
|
513
|
+
if (this._systemId) {
|
|
514
|
+
this.system._set(this._systemId, this);
|
|
515
|
+
}
|
|
516
|
+
this.status = ActorStatus.Running;
|
|
517
|
+
const status = this.logic.getStatus?.(this._state);
|
|
518
|
+
switch (status?.status) {
|
|
519
|
+
case 'done':
|
|
520
|
+
// a state machine can be "done" upon intialization (it could reach a final state using initial microsteps)
|
|
521
|
+
// we still need to complete observers, flush deferreds etc
|
|
522
|
+
this.update(this._state);
|
|
523
|
+
// fallthrough
|
|
524
|
+
case 'error':
|
|
525
|
+
// TODO: rethink cleanup of observers, mailbox, etc
|
|
526
|
+
return this;
|
|
527
|
+
}
|
|
528
|
+
if (this.logic.start) {
|
|
529
|
+
try {
|
|
530
|
+
this.logic.start(this._state, this._actorContext);
|
|
531
|
+
} catch (err) {
|
|
532
|
+
this._stopProcedure();
|
|
533
|
+
this._error(err);
|
|
534
|
+
this._parent?.send(createErrorActorEvent(this.id, err));
|
|
535
|
+
return this;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// TODO: this notifies all subscribers but usually this is redundant
|
|
540
|
+
// there is no real change happening here
|
|
541
|
+
// we need to rethink if this needs to be refactored
|
|
542
|
+
this.update(this._state);
|
|
543
|
+
if (this.options.devTools) {
|
|
544
|
+
this.attachDevTools();
|
|
545
|
+
}
|
|
546
|
+
this.mailbox.start();
|
|
547
|
+
return this;
|
|
548
|
+
}
|
|
549
|
+
_process(event) {
|
|
550
|
+
// TODO: reexamine what happens when an action (or a guard or smth) throws
|
|
551
|
+
let nextState;
|
|
552
|
+
let caughtError;
|
|
553
|
+
try {
|
|
554
|
+
nextState = this.logic.transition(this._state, event, this._actorContext);
|
|
555
|
+
} catch (err) {
|
|
556
|
+
// we wrap it in a box so we can rethrow it later even if falsy value gets caught here
|
|
557
|
+
caughtError = {
|
|
558
|
+
err
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
if (caughtError) {
|
|
562
|
+
const {
|
|
563
|
+
err
|
|
564
|
+
} = caughtError;
|
|
565
|
+
this._stopProcedure();
|
|
566
|
+
this._error(err);
|
|
567
|
+
this._parent?.send(createErrorActorEvent(this.id, err));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
this.update(nextState);
|
|
571
|
+
if (event.type === XSTATE_STOP) {
|
|
572
|
+
this._stopProcedure();
|
|
573
|
+
this._complete();
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
_stop() {
|
|
577
|
+
if (this.status === ActorStatus.Stopped) {
|
|
578
|
+
return this;
|
|
579
|
+
}
|
|
580
|
+
this.mailbox.clear();
|
|
581
|
+
if (this.status === ActorStatus.NotStarted) {
|
|
582
|
+
this.status = ActorStatus.Stopped;
|
|
583
|
+
return this;
|
|
584
|
+
}
|
|
585
|
+
this.mailbox.enqueue({
|
|
586
|
+
type: XSTATE_STOP
|
|
587
|
+
});
|
|
588
|
+
return this;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Stops the Actor and unsubscribe all listeners.
|
|
593
|
+
*/
|
|
594
|
+
stop() {
|
|
595
|
+
if (this._parent) {
|
|
596
|
+
throw new Error('A non-root actor cannot be stopped directly.');
|
|
597
|
+
}
|
|
598
|
+
return this._stop();
|
|
599
|
+
}
|
|
600
|
+
_complete() {
|
|
601
|
+
for (const observer of this.observers) {
|
|
602
|
+
try {
|
|
603
|
+
observer.complete?.();
|
|
604
|
+
} catch (err) {
|
|
605
|
+
reportUnhandledError(err);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
this.observers.clear();
|
|
609
|
+
}
|
|
610
|
+
_error(err) {
|
|
611
|
+
if (!this.observers.size) {
|
|
612
|
+
if (!this._parent) {
|
|
613
|
+
reportUnhandledError(err);
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
let reportError = false;
|
|
618
|
+
for (const observer of this.observers) {
|
|
619
|
+
const errorListener = observer.error;
|
|
620
|
+
reportError ||= !errorListener;
|
|
621
|
+
try {
|
|
622
|
+
errorListener?.(err);
|
|
623
|
+
} catch (err2) {
|
|
624
|
+
reportUnhandledError(err2);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
this.observers.clear();
|
|
628
|
+
if (reportError) {
|
|
629
|
+
reportUnhandledError(err);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
_stopProcedure() {
|
|
633
|
+
if (this.status !== ActorStatus.Running) {
|
|
634
|
+
// Actor already stopped; do nothing
|
|
635
|
+
return this;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Cancel all delayed events
|
|
639
|
+
for (const key of Object.keys(this.delayedEventsMap)) {
|
|
640
|
+
this.clock.clearTimeout(this.delayedEventsMap[key]);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// TODO: mailbox.reset
|
|
644
|
+
this.mailbox.clear();
|
|
645
|
+
// TODO: after `stop` we must prepare ourselves for receiving events again
|
|
646
|
+
// events sent *after* stop signal must be queued
|
|
647
|
+
// it seems like this should be the common behavior for all of our consumers
|
|
648
|
+
// so perhaps this should be unified somehow for all of them
|
|
649
|
+
this.mailbox = new Mailbox(this._process.bind(this));
|
|
650
|
+
this.status = ActorStatus.Stopped;
|
|
651
|
+
this.system._unregister(this);
|
|
652
|
+
return this;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Sends an event to the running Actor to trigger a transition.
|
|
657
|
+
*
|
|
658
|
+
* @param event The event to send
|
|
659
|
+
*/
|
|
660
|
+
send(event) {
|
|
661
|
+
if (typeof event === 'string') {
|
|
662
|
+
throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
|
|
663
|
+
}
|
|
664
|
+
if (this.status === ActorStatus.Stopped) {
|
|
665
|
+
// do nothing
|
|
666
|
+
{
|
|
667
|
+
const eventString = JSON.stringify(event);
|
|
668
|
+
console.warn(`Event "${event.type}" was sent to stopped actor "${this.id} (${this.sessionId})". This actor has already reached its final state, and will not transition.\nEvent: ${eventString}`);
|
|
669
|
+
}
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
this.mailbox.enqueue(event);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// TODO: make private (and figure out a way to do this within the machine)
|
|
676
|
+
delaySend({
|
|
677
|
+
event,
|
|
678
|
+
id,
|
|
679
|
+
delay,
|
|
680
|
+
to
|
|
681
|
+
}) {
|
|
682
|
+
const timerId = this.clock.setTimeout(() => {
|
|
683
|
+
if (to) {
|
|
684
|
+
to.send(event);
|
|
685
|
+
} else {
|
|
686
|
+
this.send(event);
|
|
687
|
+
}
|
|
688
|
+
}, delay);
|
|
689
|
+
|
|
690
|
+
// TODO: consider the rehydration story here
|
|
691
|
+
if (id) {
|
|
692
|
+
this.delayedEventsMap[id] = timerId;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// TODO: make private (and figure out a way to do this within the machine)
|
|
697
|
+
cancel(sendId) {
|
|
698
|
+
this.clock.clearTimeout(this.delayedEventsMap[sendId]);
|
|
699
|
+
delete this.delayedEventsMap[sendId];
|
|
700
|
+
}
|
|
701
|
+
attachDevTools() {
|
|
702
|
+
const {
|
|
703
|
+
devTools
|
|
704
|
+
} = this.options;
|
|
705
|
+
if (devTools) {
|
|
706
|
+
const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : devToolsAdapter;
|
|
707
|
+
resolvedDevToolsAdapter(this);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
toJSON() {
|
|
711
|
+
return {
|
|
712
|
+
id: this.id
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
getPersistedState() {
|
|
716
|
+
return this.logic.getPersistedState?.(this._state);
|
|
717
|
+
}
|
|
718
|
+
[symbolObservable]() {
|
|
719
|
+
return this;
|
|
720
|
+
}
|
|
721
|
+
getSnapshot() {
|
|
722
|
+
return this.logic.getSnapshot ? this.logic.getSnapshot(this._state) : this._state;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Creates a new `ActorRef` instance for the given machine with the provided options, if any.
|
|
728
|
+
*
|
|
729
|
+
* @param machine The machine to create an actor from
|
|
730
|
+
* @param options `ActorRef` options
|
|
731
|
+
*/
|
|
732
|
+
|
|
733
|
+
function createActor(logic, options) {
|
|
734
|
+
const interpreter = new Actor(logic, options);
|
|
735
|
+
return interpreter;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Creates a new Interpreter instance for the given machine with the provided options, if any.
|
|
740
|
+
*
|
|
741
|
+
* @deprecated Use `createActor` instead
|
|
742
|
+
*/
|
|
743
|
+
const interpret = createActor;
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* @deprecated Use `Actor` instead.
|
|
747
|
+
*/
|
|
748
|
+
|
|
749
|
+
export { Actor as A, InterpreterStatus as I, NULL_EVENT as N, STATE_DELIMITER as S, WILDCARD as W, XSTATE_INIT as X, toTransitionConfigArray as a, createInitEvent as b, createInvokeId as c, createActor as d, matchesState as e, ActorStatus as f, interpret as g, toObserver as h, isErrorActorEvent as i, XSTATE_STOP as j, createErrorActorEvent as k, toStateValue as l, mapValues as m, STATE_IDENTIFIER as n, normalizeTarget as o, pathToStateValue as p, toStatePath as q, resolveReferencedActor as r, createDoneStateEvent as s, toArray as t, mapContext as u, isArray as v, createAfterEvent as w, flatten as x, XSTATE_ERROR as y };
|