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