@xstate-devtools/adapter 0.1.4 → 0.1.6

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/react.cjs CHANGED
@@ -22,119 +22,27 @@ var react_exports = {};
22
22
  __export(react_exports, {
23
23
  InspectorProvider: () => InspectorProvider,
24
24
  useInspectedActorRef: () => useInspectedActorRef,
25
- useInspectedMachine: () => useInspectedMachine
25
+ useInspectedMachine: () => useInspectedMachine,
26
+ useRestorableInspectedMachine: () => useRestorableInspectedMachine
26
27
  });
27
28
  module.exports = __toCommonJS(react_exports);
28
- var import_react = require("@xstate/react");
29
- var import_react2 = require("react");
30
-
31
- // src/logging.ts
32
- function hasProcessEnv() {
33
- return typeof process !== "undefined" && typeof process.env !== "undefined";
34
- }
35
- function isLoggingEnabled() {
36
- if (globalThis.__XSTATE_DEVTOOLS_LOGGING__ === true) return true;
37
- if (!hasProcessEnv()) return false;
38
- const value = process.env.XSTATE_DEVTOOLS_LOGGING;
39
- return value === "1" || value === "true";
40
- }
41
- function log(level, scope, message, details) {
42
- if (!isLoggingEnabled()) return;
43
- if (details === void 0) {
44
- console[level](`[xstate-devtools:${scope}] ${message}`);
45
- return;
46
- }
47
- console[level](`[xstate-devtools:${scope}] ${message}`, details);
48
- }
49
- function debugLog(scope, message, details) {
50
- log("debug", scope, message, details);
51
- }
52
- function infoLog(scope, message, details) {
53
- log("info", scope, message, details);
54
- }
55
- function warnLog(scope, message, details) {
56
- log("warn", scope, message, details);
57
- }
58
-
59
- // src/sanitize.ts
60
- var MAX_DEPTH = 10;
61
- var MAX_STRING_LENGTH = 500;
62
- var MAX_ARRAY_LENGTH = 100;
63
- function sanitizeValue(value, depth, seen) {
64
- if (depth > MAX_DEPTH) return "[MaxDepth]";
65
- if (value === null || value === void 0) return value;
66
- if (typeof value === "boolean" || typeof value === "number") return value;
67
- if (typeof value === "string") {
68
- return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH)}\u2026` : value;
69
- }
70
- if (typeof value === "function") return `[Function: ${value.name || "(anonymous)"}]`;
71
- if (typeof value === "symbol") return `[Symbol: ${value.description ?? ""}]`;
72
- if (typeof value === "bigint") return `[BigInt: ${value}]`;
73
- if (value instanceof Error) return { __type: "Error", name: value.name, message: value.message };
74
- if (value instanceof Date) return { __type: "Date", iso: value.toISOString() };
75
- if (value instanceof RegExp) return { __type: "RegExp", source: value.source, flags: value.flags };
76
- if (typeof value === "object") {
77
- if (seen.has(value)) return "[Circular]";
78
- seen.add(value);
79
- }
80
- if (value instanceof Map) {
81
- const entries = [];
82
- for (const [k, v] of value) {
83
- if (entries.length >= MAX_ARRAY_LENGTH) break;
84
- entries.push([sanitizeValue(k, depth + 1, seen), sanitizeValue(v, depth + 1, seen)]);
85
- }
86
- return { __type: "Map", entries };
87
- }
88
- if (value instanceof Set) {
89
- const values = [];
90
- for (const v of value) {
91
- if (values.length >= MAX_ARRAY_LENGTH) break;
92
- values.push(sanitizeValue(v, depth + 1, seen));
93
- }
94
- return { __type: "Set", values };
95
- }
96
- if (value instanceof Promise) return "[Promise]";
97
- if (value instanceof WeakMap || value instanceof WeakSet) return "[WeakCollection]";
98
- if (ArrayBuffer.isView(value)) return `[TypedArray: ${value.constructor.name}]`;
99
- if (typeof Node !== "undefined" && value instanceof Node) {
100
- return `[DOMNode: ${value.tagName ?? value.nodeName}]`;
101
- }
102
- if (Array.isArray(value)) {
103
- const sliced = value.slice(0, MAX_ARRAY_LENGTH);
104
- const result = sliced.map((v) => sanitizeValue(v, depth + 1, seen));
105
- if (value.length > MAX_ARRAY_LENGTH) result.push(`[\u2026${value.length - MAX_ARRAY_LENGTH} more]`);
106
- return result;
107
- }
108
- if (typeof value === "object") {
109
- const result = {};
110
- let count = 0;
111
- for (const [k, v] of Object.entries(value)) {
112
- if (count++ >= MAX_ARRAY_LENGTH) {
113
- result["\u2026"] = "[truncated]";
114
- break;
115
- }
116
- result[k] = sanitizeValue(v, depth + 1, seen);
117
- }
118
- return result;
119
- }
120
- return String(value);
121
- }
122
- function sanitize(value, depth = 0) {
123
- return sanitizeValue(value, depth, /* @__PURE__ */ new WeakSet());
124
- }
29
+ var import_react = require("react");
30
+ var import_react2 = require("@xstate/react");
31
+ var import_xstate = require("xstate");
125
32
 
126
33
  // src/serialize.ts
127
- var MAX_SERIALIZED_NODES = 500;
128
- var MAX_TRANSITIONS_PER_NODE = 100;
129
- var MAX_CHILD_STATES = 100;
130
- var MAX_ACTIONS_PER_TRANSITION = 20;
131
- var MAX_ENTRY_EXIT_ACTIONS = 20;
132
- var MAX_INVOKES_PER_NODE = 20;
133
34
  function serializeGuard(guard) {
134
35
  if (!guard) return void 0;
135
36
  if (typeof guard === "string") return guard;
136
- if (typeof guard === "function") return guard.name || "(inline)";
137
- if (typeof guard === "object" && guard !== null) {
37
+ if (typeof guard === "function") {
38
+ const fn = guard;
39
+ if (Array.isArray(fn.guards) && typeof fn.check === "function") {
40
+ const parts = fn.guards.map((g) => serializeGuard(g) ?? "(inline)");
41
+ return `${fn.name || "(combinator)"}(${parts.join(", ")})`;
42
+ }
43
+ return fn.name || "(inline)";
44
+ }
45
+ if (typeof guard === "object") {
138
46
  const g = guard;
139
47
  return g.type ?? g.name ?? "(inline)";
140
48
  }
@@ -150,214 +58,133 @@ function serializeAction(action) {
150
58
  return String(action);
151
59
  }
152
60
  function serializeTransitionList(transitions) {
153
- return transitions.slice(0, MAX_TRANSITIONS_PER_NODE).map((t) => ({
61
+ return transitions.map((t) => ({
154
62
  eventType: t.eventType ?? "",
155
63
  targets: (t.target ?? []).map((n) => n?.id ?? String(n)).filter(Boolean),
156
64
  guard: serializeGuard(t.guard),
157
- actions: (t.actions ?? []).slice(0, MAX_ACTIONS_PER_TRANSITION).map(serializeAction).filter(Boolean)
65
+ actions: (t.actions ?? []).map(serializeAction).filter(Boolean)
158
66
  }));
159
67
  }
160
68
  function serializeInvokes(node) {
161
- return node.invoke.slice(0, MAX_INVOKES_PER_NODE).map((inv) => ({
69
+ return node.invoke.map((inv) => ({
162
70
  id: inv.id ?? "(unknown)",
163
71
  src: typeof inv.src === "string" ? inv.src : inv.src?.id ?? inv.src?.name ?? "(inline)"
164
72
  }));
165
73
  }
166
- function serializeNode(node, state) {
167
- if (!node || typeof node !== "object") {
168
- return {
169
- id: "(unknown)",
170
- key: "(unknown)",
171
- type: "atomic",
172
- states: {},
173
- on: [],
174
- always: [],
175
- entry: [],
176
- exit: [],
177
- invoke: []
178
- };
179
- }
180
- if (state.count >= MAX_SERIALIZED_NODES) {
181
- return {
182
- id: node.id ?? "(truncated)",
183
- key: node.key ?? "(truncated)",
184
- type: node.type ?? "atomic",
185
- states: {},
186
- on: [],
187
- always: [],
188
- entry: [],
189
- exit: [],
190
- invoke: []
191
- };
192
- }
193
- if (state.seen.has(node)) {
194
- return {
195
- id: node.id ?? "(circular)",
196
- key: node.key ?? "(circular)",
197
- type: node.type ?? "atomic",
198
- states: {},
199
- on: [],
200
- always: [],
201
- entry: [],
202
- exit: [],
203
- invoke: []
204
- };
205
- }
206
- state.seen.add(node);
207
- state.count += 1;
74
+ function serializeNode(node) {
208
75
  const allTransitions = [];
209
76
  if (node.transitions instanceof Map) {
210
77
  for (const [, tList] of node.transitions) {
211
- if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) break;
212
78
  allTransitions.push(...serializeTransitionList(tList));
213
- if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) {
214
- allTransitions.length = MAX_TRANSITIONS_PER_NODE;
215
- break;
216
- }
217
79
  }
218
80
  }
219
81
  const always = Array.isArray(node.always) ? serializeTransitionList(node.always) : [];
220
- const childEntries = Object.entries(node.states ?? {}).slice(0, MAX_CHILD_STATES);
221
82
  return {
222
83
  id: node.id,
223
84
  key: node.key,
224
85
  type: node.type,
225
86
  initial: node.initial?.target?.[0]?.key,
226
- states: Object.fromEntries(childEntries.map(([k, v]) => [k, serializeNode(v, state)])),
87
+ states: Object.fromEntries(
88
+ Object.entries(node.states ?? {}).map(([k, v]) => [k, serializeNode(v)])
89
+ ),
227
90
  on: allTransitions,
228
91
  always,
229
- entry: (node.entry ?? []).slice(0, MAX_ENTRY_EXIT_ACTIONS).map(serializeAction).filter(Boolean),
230
- exit: (node.exit ?? []).slice(0, MAX_ENTRY_EXIT_ACTIONS).map(serializeAction).filter(Boolean),
231
- invoke: serializeInvokes(node),
232
- sourceLocation: node.config?.__xstateDevtoolsSource ?? void 0,
233
- description: node.config?.description ?? void 0
92
+ entry: (node.entry ?? []).map(serializeAction).filter(Boolean),
93
+ exit: (node.exit ?? []).map(serializeAction).filter(Boolean),
94
+ invoke: serializeInvokes(node)
234
95
  };
235
96
  }
236
97
  function serializeMachine(machine, sourceLocation) {
237
98
  return {
238
99
  id: machine.id,
239
- root: serializeNode(machine.root, { seen: /* @__PURE__ */ new WeakSet(), count: 0 }),
100
+ root: serializeNode(machine.root),
240
101
  sourceLocation
241
102
  };
242
103
  }
243
104
 
244
- // src/core.ts
245
- function summarizeMessage(message) {
246
- const summary = { type: message.type };
247
- if ("sessionId" in message) summary.sessionId = message.sessionId;
248
- if ("stateNodeId" in message) summary.stateNodeId = message.stateNodeId;
249
- if ("parentSessionId" in message && message.parentSessionId) {
250
- summary.parentSessionId = message.parentSessionId;
105
+ // src/sanitize.ts
106
+ var MAX_DEPTH = 10;
107
+ var MAX_STRING_LENGTH = 500;
108
+ var MAX_ARRAY_LENGTH = 100;
109
+ var MAX_NODES = 1e4;
110
+ function sanitizeInner(value, ctx) {
111
+ if (ctx.depth > MAX_DEPTH) return "[MaxDepth]";
112
+ if (++ctx.budget.n > MAX_NODES) return "[Truncated]";
113
+ if (value === null || value === void 0) return value;
114
+ if (typeof value === "boolean" || typeof value === "number") return value;
115
+ if (typeof value === "string") {
116
+ return value.length > MAX_STRING_LENGTH ? value.slice(0, MAX_STRING_LENGTH) + "\u2026" : value;
251
117
  }
252
- if ("globalSeq" in message) summary.globalSeq = message.globalSeq;
253
- if ("timestamp" in message) summary.timestamp = message.timestamp;
254
- if ("event" in message && message.event && typeof message.event === "object" && "type" in message.event) {
255
- summary.eventType = message.event.type;
118
+ if (typeof value === "function") return `[Function: ${value.name || "(anonymous)"}]`;
119
+ if (typeof value === "symbol") return `[Symbol: ${value.description ?? ""}]`;
120
+ if (typeof value === "bigint") return `[BigInt: ${value}]`;
121
+ if (value instanceof Error) return { __type: "Error", name: value.name, message: value.message };
122
+ if (value instanceof Date) return { __type: "Date", iso: value.toISOString() };
123
+ if (value instanceof RegExp) return { __type: "RegExp", source: value.source, flags: value.flags };
124
+ if (value instanceof Promise) return "[Promise]";
125
+ if (value instanceof WeakMap || value instanceof WeakSet) return "[WeakCollection]";
126
+ if (ArrayBuffer.isView(value)) return `[TypedArray: ${value.constructor.name}]`;
127
+ if (typeof Node !== "undefined" && value instanceof Node) {
128
+ return `[DOMNode: ${value.tagName ?? value.nodeName}]`;
256
129
  }
257
- return summary;
258
- }
259
- function summarizeInspectionEvent(event) {
260
- return {
261
- type: event?.type,
262
- sessionId: event?.actorRef?.sessionId,
263
- eventType: event?.type === "@xstate.event" && event?.event && typeof event.event === "object" ? event.event.type : void 0
264
- };
265
- }
266
- function debugLog2(source, message, details) {
267
- debugLog(`${source}:adapter`, message, details);
268
- }
269
- function infoLog2(source, message, details) {
270
- infoLog(`${source}:adapter`, message, details);
271
- }
272
- function warnLog2(source, message, details) {
273
- warnLog(`${source}:adapter`, message, details);
274
- }
275
- function isLibraryStackFrame(line) {
276
- const normalized = line.replace(/\\/g, "/").toLowerCase();
277
- return normalized.includes("/node_modules/xstate/") || normalized.includes("/node_modules/@xstate/") || normalized.includes("/@xstate-devtools/adapter/") || normalized.includes("/packages/adapter/");
278
- }
279
- function normalizeStackFrame(line) {
280
- return line.trim().replace(/^at\s+/, "");
281
- }
282
- function extractStackFrameLocation(frame) {
283
- return frame.match(/\((.*)\)$/)?.[1] ?? frame;
284
- }
285
- function isAnonymousOrEvalLocation(location) {
286
- const normalized = location.trim().toLowerCase().replace(/^[./\\]+/, "");
287
- return normalized === "<anonymous>" || normalized === "anonymous" || normalized === "(anonymous)" || normalized === "eval" || normalized === "<eval>" || normalized === "[native code]";
288
- }
289
- function hasFilesystemBackedPath(location) {
290
- const trimmed = location.trim();
291
- if (!trimmed) return false;
292
- const match = trimmed.match(/^(.*?)(?::\d+)?(?::\d+)?$/);
293
- const rawPath = (match?.[1] ?? trimmed).trim();
294
- if (!rawPath || isAnonymousOrEvalLocation(rawPath)) return false;
295
- if (/^[a-zA-Z]:[\\/]/.test(rawPath)) return true;
296
- if (rawPath.startsWith("/") || rawPath.startsWith("./") || rawPath.startsWith("../")) return true;
297
- if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(rawPath)) {
298
- try {
299
- const url = new URL(rawPath);
300
- if (url.protocol === "file:") return true;
301
- return url.pathname.startsWith("/@fs/");
302
- } catch {
303
- return false;
130
+ if (ctx.seen.has(value)) return "[Circular]";
131
+ ctx.seen.add(value);
132
+ const child = { ...ctx, depth: ctx.depth + 1 };
133
+ if (value instanceof Map) {
134
+ const entries = [];
135
+ for (const [k, v] of value) {
136
+ if (entries.length >= MAX_ARRAY_LENGTH) break;
137
+ entries.push([sanitizeInner(k, child), sanitizeInner(v, child)]);
304
138
  }
139
+ return { __type: "Map", entries };
305
140
  }
306
- return false;
307
- }
308
- function remapWebUrlLocationToFsPath(location, source, options) {
309
- if (source !== "web" || !options?.webSourceRoot) return location;
310
- const match = location.trim().match(/^(.*?)(?::(\d+))?(?::(\d+))?$/);
311
- if (!match) return location;
312
- const [, rawPath, line, column] = match;
313
- if (!rawPath || !/^https?:\/\//.test(rawPath)) return location;
314
- try {
315
- const url = new URL(rawPath);
316
- const pathname = decodeURIComponent(url.pathname);
317
- if (!pathname.startsWith("/app/")) return location;
318
- const root = options.webSourceRoot.replace(/\/+$/, "");
319
- const filePath = `${root}${pathname}`;
320
- const suffix = line ? `:${line}${column ? `:${column}` : ""}` : "";
321
- return `${filePath}${suffix}`;
322
- } catch {
323
- return location;
141
+ if (value instanceof Set) {
142
+ const values = [];
143
+ for (const v of value) {
144
+ if (values.length >= MAX_ARRAY_LENGTH) break;
145
+ values.push(sanitizeInner(v, child));
146
+ }
147
+ return { __type: "Set", values };
324
148
  }
325
- }
326
- function getSourceLocationFromStack(stack, source = "web", options) {
327
- const lines = stack?.split("\n") ?? [];
328
- for (let i = 0; i < lines.length; i += 1) {
329
- if (i <= 2) continue;
330
- const line = lines[i];
331
- if (isLibraryStackFrame(line)) continue;
332
- const normalizedFrame = normalizeStackFrame(line);
333
- const location = remapWebUrlLocationToFsPath(
334
- extractStackFrameLocation(normalizedFrame),
335
- source,
336
- options
337
- );
338
- if (!hasFilesystemBackedPath(location)) continue;
339
- const wrapped = normalizedFrame.match(/\((.*)\)$/);
340
- if (wrapped) {
341
- return normalizedFrame.replace(wrapped[1], location);
149
+ if (Array.isArray(value)) {
150
+ const sliced = value.slice(0, MAX_ARRAY_LENGTH);
151
+ const result = sliced.map((v) => sanitizeInner(v, child));
152
+ if (value.length > MAX_ARRAY_LENGTH) result.push(`[\u2026${value.length - MAX_ARRAY_LENGTH} more]`);
153
+ return result;
154
+ }
155
+ if (typeof value === "object") {
156
+ const result = {};
157
+ let count = 0;
158
+ for (const [k, v] of Object.entries(value)) {
159
+ if (count++ >= MAX_ARRAY_LENGTH) {
160
+ result["\u2026"] = "[truncated]";
161
+ break;
162
+ }
163
+ result[k] = sanitizeInner(v, child);
342
164
  }
343
- return location;
165
+ return result;
344
166
  }
345
- return void 0;
167
+ return String(value);
168
+ }
169
+ function sanitize(value) {
170
+ return sanitizeInner(value, { depth: 0, budget: { n: 0 }, seen: /* @__PURE__ */ new WeakSet() });
346
171
  }
347
- function getSourceLocation(source, options) {
172
+
173
+ // src/core.ts
174
+ function getSourceLocation() {
348
175
  try {
349
- const oldLimit = Error.stackTraceLimit;
350
- Error.stackTraceLimit = 50;
351
- const stack = new Error().stack;
352
- Error.stackTraceLimit = oldLimit;
353
- return getSourceLocationFromStack(stack, source, options);
176
+ const lines = new Error().stack?.split("\n") ?? [];
177
+ const callerLine = lines.find(
178
+ (l, i) => i > 3 && !l.includes("xstate") && !l.includes("adapter")
179
+ );
180
+ return callerLine?.trim().replace(/^at\s+/, "");
354
181
  } catch {
355
182
  return void 0;
356
183
  }
357
184
  }
358
185
  function serializeSnapshot(snapshot) {
359
186
  return {
360
- value: snapshot?.value ?? null,
187
+ value: sanitize(snapshot?.value ?? null),
361
188
  context: sanitize(snapshot?.context),
362
189
  status: snapshot?.status ?? "active",
363
190
  error: snapshot?.error ? sanitize(snapshot.error) : void 0
@@ -370,125 +197,18 @@ function safeSerializeSnapshot(actorRef) {
370
197
  return { value: null, context: void 0, status: "active" };
371
198
  }
372
199
  }
373
- function getActorDisplayName(actorRef) {
374
- const actor = actorRef;
375
- const src = actor.src ?? actor.logic?.src;
376
- if (typeof src === "string" && src.length > 0) return src;
377
- if (typeof actor.logic?.id === "string" && actor.logic.id.length > 0) return actor.logic.id;
378
- if (typeof actor.logic?.name === "string" && actor.logic.name.length > 0) return actor.logic.name;
379
- if (src && typeof src === "object") {
380
- const namedSrc = src;
381
- if (typeof namedSrc.id === "string" && namedSrc.id.length > 0) return namedSrc.id;
382
- if (typeof namedSrc.name === "string" && namedSrc.name.length > 0) return namedSrc.name;
383
- }
384
- return void 0;
385
- }
386
- function getNodeInitialChild(node) {
387
- if (!node.states) return null;
388
- if (typeof node.initial === "string") {
389
- return node.states[node.initial] ?? null;
200
+ function safePersistedSnapshot(actorRef) {
201
+ const getPersisted = actorRef.getPersistedSnapshot;
202
+ if (typeof getPersisted !== "function") {
203
+ return { error: "Actor does not support getPersistedSnapshot." };
390
204
  }
391
- const target = Array.isArray(node.initial?.target) ? node.initial.target[0] : null;
392
- return target ?? null;
393
- }
394
- function encodeChildValue(child, childValue) {
395
- if (child.type === "atomic" || child.type === "final" || child.type === "history") {
396
- return child.key;
397
- }
398
- return { [child.key]: childValue };
399
- }
400
- function getDefaultStateValue(node) {
401
- if (node.type === "parallel") {
402
- const value = {};
403
- for (const child of Object.values(node.states ?? {})) {
404
- value[child.key] = getDefaultSelectionValue(child);
405
- }
406
- return value;
407
- }
408
- const initialChild = getNodeInitialChild(node);
409
- if (!initialChild) return {};
410
- return encodeChildValue(initialChild, getDefaultStateValue(initialChild));
411
- }
412
- function getDefaultSelectionValue(node) {
413
- if (node.type === "atomic" || node.type === "final" || node.type === "history") {
414
- return node.key;
415
- }
416
- return getDefaultStateValue(node);
417
- }
418
- function getExistingChildValue(value, childKey) {
419
- if (!value || typeof value !== "object") return void 0;
420
- return value[childKey];
421
- }
422
- function getPathToRoot(target, root) {
423
- const path = [];
424
- let current = target;
425
- while (current) {
426
- path.unshift(current);
427
- if (current.id === root.id) return path;
428
- current = current.parent;
429
- }
430
- throw new Error(`State node '${target.id}' is not part of machine '${root.id}'`);
431
- }
432
- function buildTargetStateValue(node, path, currentValue) {
433
- const [, ...restPath] = path;
434
- if (restPath.length === 0) {
435
- if (node.type === "parallel") {
436
- const next = {};
437
- for (const child2 of Object.values(node.states ?? {})) {
438
- next[child2.key] = getExistingChildValue(currentValue, child2.key) ?? getDefaultSelectionValue(child2);
439
- }
440
- return next;
441
- }
442
- if (node.type === "compound") {
443
- return getDefaultStateValue(node);
444
- }
445
- return node.key;
446
- }
447
- const child = restPath[0];
448
- if (node.type === "parallel") {
449
- const next = {};
450
- for (const sibling of Object.values(node.states ?? {})) {
451
- if (sibling.key === child.key) {
452
- next[sibling.key] = buildTargetStateValue(
453
- sibling,
454
- restPath,
455
- getExistingChildValue(currentValue, sibling.key)
456
- );
457
- } else {
458
- next[sibling.key] = getExistingChildValue(currentValue, sibling.key) ?? getDefaultSelectionValue(sibling);
459
- }
460
- }
461
- return next;
462
- }
463
- const childValue = buildTargetStateValue(
464
- child,
465
- restPath,
466
- getExistingChildValue(currentValue, child.key)
467
- );
468
- return encodeChildValue(child, childValue);
469
- }
470
- function setActiveState(actorRef, stateNodeId) {
471
- const mutableActorRef = actorRef;
472
- const machine = mutableActorRef.logic;
473
- if (!machine?.getStateNodeById || !machine.resolveState || typeof mutableActorRef.update !== "function") {
474
- throw new Error("Actor does not expose machine state mutation internals");
205
+ try {
206
+ const raw = getPersisted.call(actorRef);
207
+ if (raw === void 0) return { error: "No persisted snapshot available." };
208
+ return { persisted: JSON.parse(JSON.stringify(raw)) };
209
+ } catch (e) {
210
+ return { error: `Snapshot is not JSON-serializable: ${e.message}` };
475
211
  }
476
- const currentSnapshot = actorRef.getSnapshot();
477
- const targetNode = machine.getStateNodeById(stateNodeId);
478
- const path = getPathToRoot(targetNode, machine.root);
479
- const targetValue = buildTargetStateValue(machine.root, path, currentSnapshot?.value);
480
- const nextSnapshot = machine.resolveState({
481
- value: targetValue,
482
- context: currentSnapshot?.context,
483
- status: currentSnapshot?.status,
484
- output: currentSnapshot?.output,
485
- error: currentSnapshot?.error,
486
- historyValue: currentSnapshot?.historyValue
487
- });
488
- mutableActorRef.update(nextSnapshot, {
489
- type: "xstate.devtools.set-active-state",
490
- stateNodeId
491
- });
492
212
  }
493
213
  var SEQ_KEY = "__xstate_devtools_global_seq__";
494
214
  function nextSeq() {
@@ -498,10 +218,10 @@ function nextSeq() {
498
218
  g[SEQ_KEY] = next;
499
219
  return next;
500
220
  }
501
- function createInspector(transport, source, options = {}) {
221
+ function createInspector(transport, source) {
502
222
  const actorRefs = /* @__PURE__ */ new Map();
503
- const actorMachines = /* @__PURE__ */ new Map();
504
- const prefix = `${source}:`;
223
+ const restoreHandlers = /* @__PURE__ */ new Map();
224
+ const prefix = source + ":";
505
225
  const tag = (sessionId) => prefix + sessionId;
506
226
  const tagOptional = (id) => id ? prefix + id : void 0;
507
227
  const stripIfMine = (id) => id.startsWith(prefix) ? id.slice(prefix.length) : null;
@@ -513,278 +233,200 @@ function createInspector(transport, source, options = {}) {
513
233
  return;
514
234
  }
515
235
  if (snap?.status !== "active") {
516
- const message = {
517
- type: "XSTATE_ACTOR_STOPPED",
518
- sessionId: tag(actorRef.sessionId)
519
- };
520
- debugLog2(source, "actor stopped; notifying transport", summarizeMessage(message));
521
- transport.send(message);
236
+ transport.send({ type: "XSTATE_ACTOR_STOPPED", sessionId: tag(actorRef.sessionId) });
522
237
  actorRefs.delete(actorRef.sessionId);
523
- actorMachines.delete(actorRef.sessionId);
524
238
  }
525
239
  }
526
240
  const unsubscribe = transport.subscribe((message) => {
527
- debugLog2(source, "received message from transport", summarizeMessage(message));
528
- if (message.type === "XSTATE_PANEL_CONNECTED") {
529
- infoLog2(source, "panel connected; resyncing actors", { actorCount: actorRefs.size });
530
- actorRefs.forEach((actorRef, sessionId) => {
531
- const machine = actorMachines.get(sessionId) ?? null;
532
- const resyncMessage = {
533
- type: "XSTATE_ACTOR_REGISTERED",
534
- sessionId: tag(sessionId),
535
- parentSessionId: tagOptional(actorRef._parent?.sessionId),
536
- displayName: getActorDisplayName(actorRef),
537
- machine,
538
- snapshot: safeSerializeSnapshot(actorRef),
539
- globalSeq: nextSeq(),
540
- timestamp: Date.now()
541
- };
542
- debugLog2(source, "resyncing actor", summarizeMessage(resyncMessage));
543
- transport.send(resyncMessage);
544
- });
545
- return;
546
- }
547
241
  if (message.type === "XSTATE_DISPATCH") {
548
242
  const local = stripIfMine(message.sessionId);
549
- if (local === null) {
550
- debugLog2(source, "ignoring dispatch for different source", summarizeMessage(message));
551
- return;
552
- }
243
+ if (local === null) return;
553
244
  const ref = actorRefs.get(local);
554
245
  if (ref) {
555
246
  try {
556
- debugLog2(source, "dispatching event to actor", {
557
- sessionId: local,
558
- eventType: message.event && typeof message.event === "object" && "type" in message.event ? message.event.type : void 0
559
- });
560
247
  ref.send(message.event);
561
248
  } catch (e) {
562
- warnLog2(source, "dispatch error", { error: e, sessionId: local });
249
+ console.warn("[xstate-devtools] dispatch error:", e);
563
250
  }
564
- } else {
565
- warnLog2(source, "received dispatch for unknown actor", {
566
- sessionId: local,
567
- knownActors: actorRefs.size
568
- });
569
251
  }
570
- return;
571
- }
572
- if (message.type === "XSTATE_SET_ACTIVE_STATE") {
252
+ } else if (message.type === "XSTATE_REQUEST_PERSISTED") {
573
253
  const local = stripIfMine(message.sessionId);
574
- if (local === null) {
575
- debugLog2(
576
- source,
577
- "ignoring state activation for different source",
578
- summarizeMessage(message)
579
- );
580
- return;
581
- }
254
+ if (local === null) return;
582
255
  const ref = actorRefs.get(local);
583
- if (!ref) {
584
- warnLog2(source, "received state activation for unknown actor", {
585
- sessionId: local,
586
- knownActors: actorRefs.size
587
- });
588
- return;
256
+ if (!ref) return;
257
+ const { persisted, error } = safePersistedSnapshot(ref);
258
+ transport.send({
259
+ type: "XSTATE_PERSISTED_SNAPSHOT",
260
+ sessionId: tag(local),
261
+ persisted,
262
+ error,
263
+ timestamp: Date.now()
264
+ });
265
+ } else if (message.type === "XSTATE_RESTORE") {
266
+ const local = stripIfMine(message.sessionId);
267
+ if (local === null) return;
268
+ const handler = restoreHandlers.get(local);
269
+ if (handler) {
270
+ try {
271
+ handler(message.persisted);
272
+ } catch (e) {
273
+ console.warn("[xstate-devtools] restore error:", e);
274
+ }
589
275
  }
590
- try {
591
- debugLog2(source, "setting active state on actor", summarizeMessage(message));
592
- setActiveState(ref, message.stateNodeId);
593
- const snapshotMessage = {
276
+ }
277
+ });
278
+ const inspect = (inspectionEvent) => {
279
+ try {
280
+ if (inspectionEvent.type === "@xstate.actor") {
281
+ const actorRef = inspectionEvent.actorRef;
282
+ const sessionId = actorRef.sessionId;
283
+ actorRefs.set(sessionId, actorRef);
284
+ const logic = actorRef.logic;
285
+ const machine = logic?.root ? serializeMachine(logic, getSourceLocation()) : null;
286
+ transport.send({
287
+ type: "XSTATE_ACTOR_REGISTERED",
288
+ sessionId: tag(sessionId),
289
+ parentSessionId: tagOptional(actorRef._parent?.sessionId),
290
+ // actorRef.id is the invoke `id` for invoked actors — lets the debugger
291
+ // nest non-machine actors (promise/callback) under their state.
292
+ actorId: actorRef.id,
293
+ machine,
294
+ snapshot: safeSerializeSnapshot(actorRef),
295
+ globalSeq: nextSeq(),
296
+ timestamp: Date.now()
297
+ });
298
+ } else if (inspectionEvent.type === "@xstate.snapshot") {
299
+ transport.send({
594
300
  type: "XSTATE_SNAPSHOT",
595
- sessionId: tag(local),
596
- snapshot: safeSerializeSnapshot(ref),
301
+ sessionId: tag(inspectionEvent.actorRef.sessionId),
302
+ snapshot: serializeSnapshot(inspectionEvent.snapshot),
597
303
  timestamp: Date.now(),
598
304
  globalSeq: nextSeq()
599
- };
600
- debugLog2(
601
- source,
602
- "sending snapshot after state activation",
603
- summarizeMessage(snapshotMessage)
604
- );
605
- transport.send(snapshotMessage);
606
- } catch (error) {
607
- warnLog2(source, "failed to set active state", {
608
- error,
609
- sessionId: local,
610
- stateNodeId: message.stateNodeId
611
305
  });
306
+ checkAndNotifyStop(inspectionEvent.actorRef);
307
+ } else if (inspectionEvent.type === "@xstate.event") {
308
+ transport.send({
309
+ type: "XSTATE_EVENT",
310
+ sessionId: tag(inspectionEvent.actorRef.sessionId),
311
+ event: inspectionEvent.event,
312
+ snapshotAfter: safeSerializeSnapshot(inspectionEvent.actorRef),
313
+ timestamp: Date.now(),
314
+ globalSeq: nextSeq()
315
+ });
316
+ checkAndNotifyStop(inspectionEvent.actorRef);
612
317
  }
613
- }
614
- });
615
- transport.send({ type: "XSTATE_ADAPTER_READY" });
616
- infoLog2(source, "inspector created");
617
- const inspect = (inspectionEvent) => {
618
- debugLog2(source, "inspect callback invoked", summarizeInspectionEvent(inspectionEvent));
619
- if (inspectionEvent.type === "@xstate.actor") {
620
- const actorRef = inspectionEvent.actorRef;
621
- const sessionId = actorRef.sessionId;
622
- const actorLogic = actorRef.logic;
623
- const machine = actorLogic?.root ? serializeMachine(
624
- actorLogic,
625
- actorLogic.config?.__xstateDevtoolsSource ?? getSourceLocation(source, options)
626
- ) : null;
627
- actorRefs.set(sessionId, actorRef);
628
- actorMachines.set(sessionId, machine);
629
- const notifyStop = () => {
630
- if (actorRefs.has(sessionId)) {
631
- actorRefs.delete(sessionId);
632
- actorMachines.delete(sessionId);
633
- const stopMsg = {
634
- type: "XSTATE_ACTOR_STOPPED",
635
- sessionId: tag(sessionId)
636
- };
637
- debugLog2(source, "actor stopped; notifying transport", summarizeMessage(stopMsg));
638
- transport.send(stopMsg);
639
- }
640
- };
641
- try {
642
- actorRef.subscribe({ complete: notifyStop, error: notifyStop });
643
- } catch {
644
- }
645
- const message = {
646
- type: "XSTATE_ACTOR_REGISTERED",
647
- sessionId: tag(sessionId),
648
- parentSessionId: tagOptional(actorRef._parent?.sessionId),
649
- displayName: getActorDisplayName(actorRef),
650
- machine,
651
- snapshot: safeSerializeSnapshot(actorRef),
652
- globalSeq: nextSeq(),
653
- timestamp: Date.now()
654
- };
655
- infoLog2(source, "registering actor with transport", {
656
- message: summarizeMessage(message),
657
- actorCount: actorRefs.size,
658
- hasMachine: machine !== null
659
- });
660
- transport.send(message);
661
- } else if (inspectionEvent.type === "@xstate.snapshot") {
662
- const message = {
663
- type: "XSTATE_SNAPSHOT",
664
- sessionId: tag(inspectionEvent.actorRef.sessionId),
665
- snapshot: serializeSnapshot(inspectionEvent.snapshot),
666
- timestamp: Date.now(),
667
- globalSeq: nextSeq()
668
- };
669
- debugLog2(source, "sending snapshot to transport", summarizeMessage(message));
670
- transport.send(message);
671
- checkAndNotifyStop(inspectionEvent.actorRef);
672
- } else if (inspectionEvent.type === "@xstate.event") {
673
- const message = {
674
- type: "XSTATE_EVENT",
675
- sessionId: tag(inspectionEvent.actorRef.sessionId),
676
- // sanitize() returns unknown; the inspected event keeps its { type, ... }
677
- // shape, so it is a SerializedEvent after deep-sanitizing.
678
- event: sanitize(inspectionEvent.event),
679
- snapshotAfter: safeSerializeSnapshot(inspectionEvent.actorRef),
680
- timestamp: Date.now(),
681
- globalSeq: nextSeq()
682
- };
683
- debugLog2(source, "sending event to transport", summarizeMessage(message));
684
- transport.send(message);
685
- checkAndNotifyStop(inspectionEvent.actorRef);
686
- } else {
687
- debugLog2(
688
- source,
689
- "ignoring unsupported inspection event",
690
- summarizeInspectionEvent(inspectionEvent)
691
- );
318
+ } catch (e) {
319
+ console.warn("[xstate-devtools] inspection failed, dropping event:", e.message);
692
320
  }
693
321
  };
322
+ function registerRestore(sessionId, handler) {
323
+ restoreHandlers.set(sessionId, handler);
324
+ return () => {
325
+ if (restoreHandlers.get(sessionId) === handler) restoreHandlers.delete(sessionId);
326
+ };
327
+ }
694
328
  function dispose() {
695
- infoLog2(source, "disposing inspector", { actorCount: actorRefs.size });
696
329
  unsubscribe();
697
330
  actorRefs.clear();
698
- actorMachines.clear();
331
+ restoreHandlers.clear();
699
332
  }
700
- return { inspect, dispose };
333
+ return { inspect, dispose, registerRestore };
701
334
  }
702
335
 
703
336
  // src/index.ts
704
- function createAdapter(options = {}) {
337
+ function createAdapter() {
705
338
  if (typeof window === "undefined") {
706
- infoLog("web:adapter", "createAdapter called without window; returning no-op adapter");
707
339
  return { inspect: () => {
708
340
  }, dispose: () => {
341
+ }, registerRestore: () => () => {
709
342
  } };
710
343
  }
711
- infoLog("web:adapter", "creating browser adapter", {
712
- hookInstalled: Boolean(window.__XSTATE_DEVTOOLS__)
713
- });
714
- let warnedMissingHook = false;
715
344
  const transport = {
716
345
  send(message) {
717
- const payload = { ...message, __xstateDevtools: true };
718
- debugLog("web:adapter", "sending message via page hook", {
719
- type: message.type,
720
- sessionId: "sessionId" in message ? message.sessionId : void 0
721
- });
722
- if (window.__XSTATE_DEVTOOLS__) {
723
- window.__XSTATE_DEVTOOLS__.send(payload);
724
- return;
725
- }
726
- if (!warnedMissingHook) {
727
- warnedMissingHook = true;
728
- warnLog("web:adapter", "page hook missing; using direct window.postMessage fallback");
729
- }
730
- window.postMessage(payload, "*");
346
+ window.__XSTATE_DEVTOOLS__?.send({ ...message, __xstateDevtools: true });
731
347
  },
732
348
  subscribe(handler) {
733
- infoLog("web:adapter", "subscribing to window messages");
734
349
  const onMessage = (evt) => {
735
350
  if (evt.source !== window) return;
736
351
  const data = evt.data;
737
352
  if (!data?.__xstateDevtools) return;
738
- debugLog("web:adapter", "received message from window bridge", {
739
- type: data.type,
740
- sessionId: "sessionId" in data ? data.sessionId : void 0
741
- });
742
353
  handler(data);
743
354
  };
744
355
  window.addEventListener("message", onMessage);
745
- return () => {
746
- infoLog("web:adapter", "unsubscribing from window messages");
747
- window.removeEventListener("message", onMessage);
748
- };
356
+ return () => window.removeEventListener("message", onMessage);
749
357
  }
750
358
  };
751
- return createInspector(transport, "web", options);
359
+ return createInspector(transport, "web");
752
360
  }
753
361
 
754
362
  // src/react.tsx
755
363
  var import_jsx_runtime = require("react/jsx-runtime");
756
- var InspectorContext = (0, import_react2.createContext)(null);
757
- function InspectorProvider({ children }) {
758
- const adapterRef = (0, import_react2.useRef)(null);
759
- if (!adapterRef.current && typeof window !== "undefined") {
760
- adapterRef.current = createAdapter();
761
- }
762
- (0, import_react2.useEffect)(() => {
364
+ var InspectorContext = (0, import_react.createContext)(null);
365
+ function InspectorProvider({
366
+ children,
367
+ adapter
368
+ }) {
369
+ const ownRef = (0, import_react.useRef)(null);
370
+ const useOwn = adapter == null;
371
+ if (useOwn && !ownRef.current && typeof window !== "undefined") {
372
+ ownRef.current = createAdapter();
373
+ }
374
+ const resolved = adapter ?? ownRef.current;
375
+ (0, import_react.useEffect)(() => {
376
+ if (!useOwn) return;
763
377
  return () => {
764
- adapterRef.current?.dispose();
765
- adapterRef.current = null;
378
+ ownRef.current?.dispose();
379
+ ownRef.current = null;
766
380
  };
767
- }, []);
768
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InspectorContext.Provider, { value: adapterRef.current, children });
381
+ }, [useOwn]);
382
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InspectorContext.Provider, { value: resolved, children });
769
383
  }
770
384
  function useInspectedMachine(machine, options) {
771
- const adapter = (0, import_react2.useContext)(InspectorContext);
772
- return (0, import_react.useMachine)(machine, {
385
+ const adapter = (0, import_react.useContext)(InspectorContext);
386
+ return (0, import_react2.useMachine)(machine, {
773
387
  ...options,
774
388
  inspect: adapter?.inspect
775
389
  });
776
390
  }
777
391
  function useInspectedActorRef(machine, options) {
778
- const adapter = (0, import_react2.useContext)(InspectorContext);
779
- return (0, import_react.useActorRef)(machine, {
392
+ const adapter = (0, import_react.useContext)(InspectorContext);
393
+ return (0, import_react2.useActorRef)(machine, {
780
394
  ...options,
781
395
  inspect: adapter?.inspect
782
396
  });
783
397
  }
398
+ function useRestorableInspectedMachine(machine, options) {
399
+ const adapter = (0, import_react.useContext)(InspectorContext);
400
+ const restoreSnapshotRef = (0, import_react.useRef)(options?.snapshot);
401
+ const [generation, setGeneration] = (0, import_react.useState)(0);
402
+ const actorRef = (0, import_react.useMemo)(() => {
403
+ return (0, import_xstate.createActor)(machine, {
404
+ ...options,
405
+ snapshot: restoreSnapshotRef.current,
406
+ inspect: adapter?.inspect
407
+ });
408
+ }, [generation, adapter]);
409
+ (0, import_react.useEffect)(() => {
410
+ actorRef.start();
411
+ return () => {
412
+ actorRef.stop();
413
+ };
414
+ }, [actorRef]);
415
+ (0, import_react.useEffect)(() => {
416
+ if (!adapter?.registerRestore) return;
417
+ return adapter.registerRestore(actorRef.sessionId, (persisted) => {
418
+ restoreSnapshotRef.current = persisted;
419
+ setGeneration((g) => g + 1);
420
+ });
421
+ }, [adapter, actorRef]);
422
+ const snapshot = (0, import_react2.useSelector)(actorRef, (s) => s);
423
+ return [snapshot, actorRef.send, actorRef];
424
+ }
784
425
  // Annotate the CommonJS export names for ESM import in node:
785
426
  0 && (module.exports = {
786
427
  InspectorProvider,
787
428
  useInspectedActorRef,
788
- useInspectedMachine
429
+ useInspectedMachine,
430
+ useRestorableInspectedMachine
789
431
  });
790
432
  //# sourceMappingURL=react.cjs.map