@xstate-devtools/adapter 0.1.4 → 0.1.5

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,108 +24,7 @@ __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;
@@ -146,214 +45,133 @@ function serializeAction(action) {
146
45
  return String(action);
147
46
  }
148
47
  function serializeTransitionList(transitions) {
149
- return transitions.slice(0, MAX_TRANSITIONS_PER_NODE).map((t) => ({
48
+ return transitions.map((t) => ({
150
49
  eventType: t.eventType ?? "",
151
50
  targets: (t.target ?? []).map((n) => n?.id ?? String(n)).filter(Boolean),
152
51
  guard: serializeGuard(t.guard),
153
- actions: (t.actions ?? []).slice(0, MAX_ACTIONS_PER_TRANSITION).map(serializeAction).filter(Boolean)
52
+ actions: (t.actions ?? []).map(serializeAction).filter(Boolean)
154
53
  }));
155
54
  }
156
55
  function serializeInvokes(node) {
157
- return node.invoke.slice(0, MAX_INVOKES_PER_NODE).map((inv) => ({
56
+ return node.invoke.map((inv) => ({
158
57
  id: inv.id ?? "(unknown)",
159
58
  src: typeof inv.src === "string" ? inv.src : inv.src?.id ?? inv.src?.name ?? "(inline)"
160
59
  }));
161
60
  }
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;
61
+ function serializeNode(node) {
204
62
  const allTransitions = [];
205
63
  if (node.transitions instanceof Map) {
206
64
  for (const [, tList] of node.transitions) {
207
- if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) break;
208
65
  allTransitions.push(...serializeTransitionList(tList));
209
- if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) {
210
- allTransitions.length = MAX_TRANSITIONS_PER_NODE;
211
- break;
212
- }
213
66
  }
214
67
  }
215
68
  const always = Array.isArray(node.always) ? serializeTransitionList(node.always) : [];
216
- const childEntries = Object.entries(node.states ?? {}).slice(0, MAX_CHILD_STATES);
217
69
  return {
218
70
  id: node.id,
219
71
  key: node.key,
220
72
  type: node.type,
221
73
  initial: node.initial?.target?.[0]?.key,
222
- states: Object.fromEntries(childEntries.map(([k, v]) => [k, serializeNode(v, state)])),
74
+ states: Object.fromEntries(
75
+ Object.entries(node.states ?? {}).map(([k, v]) => [k, serializeNode(v)])
76
+ ),
223
77
  on: allTransitions,
224
78
  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
79
+ entry: (node.entry ?? []).map(serializeAction).filter(Boolean),
80
+ exit: (node.exit ?? []).map(serializeAction).filter(Boolean),
81
+ invoke: serializeInvokes(node)
230
82
  };
231
83
  }
232
84
  function serializeMachine(machine, sourceLocation) {
233
85
  return {
234
86
  id: machine.id,
235
- root: serializeNode(machine.root, { seen: /* @__PURE__ */ new WeakSet(), count: 0 }),
87
+ root: serializeNode(machine.root),
236
88
  sourceLocation
237
89
  };
238
90
  }
239
91
 
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;
92
+ // src/sanitize.ts
93
+ var MAX_DEPTH = 10;
94
+ var MAX_STRING_LENGTH = 500;
95
+ var MAX_ARRAY_LENGTH = 100;
96
+ var MAX_NODES = 1e4;
97
+ function sanitizeInner(value, ctx) {
98
+ if (ctx.depth > MAX_DEPTH) return "[MaxDepth]";
99
+ if (++ctx.budget.n > MAX_NODES) return "[Truncated]";
100
+ if (value === null || value === void 0) return value;
101
+ if (typeof value === "boolean" || typeof value === "number") return value;
102
+ if (typeof value === "string") {
103
+ return value.length > MAX_STRING_LENGTH ? value.slice(0, MAX_STRING_LENGTH) + "\u2026" : value;
247
104
  }
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;
105
+ if (typeof value === "function") return `[Function: ${value.name || "(anonymous)"}]`;
106
+ if (typeof value === "symbol") return `[Symbol: ${value.description ?? ""}]`;
107
+ if (typeof value === "bigint") return `[BigInt: ${value}]`;
108
+ if (value instanceof Error) return { __type: "Error", name: value.name, message: value.message };
109
+ if (value instanceof Date) return { __type: "Date", iso: value.toISOString() };
110
+ if (value instanceof RegExp) return { __type: "RegExp", source: value.source, flags: value.flags };
111
+ if (value instanceof Promise) return "[Promise]";
112
+ if (value instanceof WeakMap || value instanceof WeakSet) return "[WeakCollection]";
113
+ if (ArrayBuffer.isView(value)) return `[TypedArray: ${value.constructor.name}]`;
114
+ if (typeof Node !== "undefined" && value instanceof Node) {
115
+ return `[DOMNode: ${value.tagName ?? value.nodeName}]`;
252
116
  }
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;
117
+ if (ctx.seen.has(value)) return "[Circular]";
118
+ ctx.seen.add(value);
119
+ const child = { ...ctx, depth: ctx.depth + 1 };
120
+ if (value instanceof Map) {
121
+ const entries = [];
122
+ for (const [k, v] of value) {
123
+ if (entries.length >= MAX_ARRAY_LENGTH) break;
124
+ entries.push([sanitizeInner(k, child), sanitizeInner(v, child)]);
300
125
  }
126
+ return { __type: "Map", entries };
301
127
  }
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;
128
+ if (value instanceof Set) {
129
+ const values = [];
130
+ for (const v of value) {
131
+ if (values.length >= MAX_ARRAY_LENGTH) break;
132
+ values.push(sanitizeInner(v, child));
133
+ }
134
+ return { __type: "Set", values };
320
135
  }
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);
136
+ if (Array.isArray(value)) {
137
+ const sliced = value.slice(0, MAX_ARRAY_LENGTH);
138
+ const result = sliced.map((v) => sanitizeInner(v, child));
139
+ if (value.length > MAX_ARRAY_LENGTH) result.push(`[\u2026${value.length - MAX_ARRAY_LENGTH} more]`);
140
+ return result;
141
+ }
142
+ if (typeof value === "object") {
143
+ const result = {};
144
+ let count = 0;
145
+ for (const [k, v] of Object.entries(value)) {
146
+ if (count++ >= MAX_ARRAY_LENGTH) {
147
+ result["\u2026"] = "[truncated]";
148
+ break;
149
+ }
150
+ result[k] = sanitizeInner(v, child);
338
151
  }
339
- return location;
152
+ return result;
340
153
  }
341
- return void 0;
154
+ return String(value);
342
155
  }
343
- function getSourceLocation(source, options) {
156
+ function sanitize(value) {
157
+ return sanitizeInner(value, { depth: 0, budget: { n: 0 }, seen: /* @__PURE__ */ new WeakSet() });
158
+ }
159
+
160
+ // src/core.ts
161
+ function getSourceLocation() {
344
162
  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);
163
+ const lines = new Error().stack?.split("\n") ?? [];
164
+ const callerLine = lines.find(
165
+ (l, i) => i > 3 && !l.includes("xstate") && !l.includes("adapter")
166
+ );
167
+ return callerLine?.trim().replace(/^at\s+/, "");
350
168
  } catch {
351
169
  return void 0;
352
170
  }
353
171
  }
354
172
  function serializeSnapshot(snapshot) {
355
173
  return {
356
- value: snapshot?.value ?? null,
174
+ value: sanitize(snapshot?.value ?? null),
357
175
  context: sanitize(snapshot?.context),
358
176
  status: snapshot?.status ?? "active",
359
177
  error: snapshot?.error ? sanitize(snapshot.error) : void 0
@@ -366,125 +184,18 @@ function safeSerializeSnapshot(actorRef) {
366
184
  return { value: null, context: void 0, status: "active" };
367
185
  }
368
186
  }
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;
187
+ function safePersistedSnapshot(actorRef) {
188
+ const getPersisted = actorRef.getPersistedSnapshot;
189
+ if (typeof getPersisted !== "function") {
190
+ return { error: "Actor does not support getPersistedSnapshot." };
411
191
  }
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;
458
- }
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");
192
+ try {
193
+ const raw = getPersisted.call(actorRef);
194
+ if (raw === void 0) return { error: "No persisted snapshot available." };
195
+ return { persisted: JSON.parse(JSON.stringify(raw)) };
196
+ } catch (e) {
197
+ return { error: `Snapshot is not JSON-serializable: ${e.message}` };
471
198
  }
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
199
  }
489
200
  var SEQ_KEY = "__xstate_devtools_global_seq__";
490
201
  function nextSeq() {
@@ -494,10 +205,10 @@ function nextSeq() {
494
205
  g[SEQ_KEY] = next;
495
206
  return next;
496
207
  }
497
- function createInspector(transport, source, options = {}) {
208
+ function createInspector(transport, source) {
498
209
  const actorRefs = /* @__PURE__ */ new Map();
499
- const actorMachines = /* @__PURE__ */ new Map();
500
- const prefix = `${source}:`;
210
+ const restoreHandlers = /* @__PURE__ */ new Map();
211
+ const prefix = source + ":";
501
212
  const tag = (sessionId) => prefix + sessionId;
502
213
  const tagOptional = (id) => id ? prefix + id : void 0;
503
214
  const stripIfMine = (id) => id.startsWith(prefix) ? id.slice(prefix.length) : null;
@@ -509,242 +220,130 @@ function createInspector(transport, source, options = {}) {
509
220
  return;
510
221
  }
511
222
  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);
223
+ transport.send({ type: "XSTATE_ACTOR_STOPPED", sessionId: tag(actorRef.sessionId) });
518
224
  actorRefs.delete(actorRef.sessionId);
519
- actorMachines.delete(actorRef.sessionId);
520
225
  }
521
226
  }
522
227
  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
228
  if (message.type === "XSTATE_DISPATCH") {
544
229
  const local = stripIfMine(message.sessionId);
545
- if (local === null) {
546
- debugLog2(source, "ignoring dispatch for different source", summarizeMessage(message));
547
- return;
548
- }
230
+ if (local === null) return;
549
231
  const ref = actorRefs.get(local);
550
232
  if (ref) {
551
233
  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
234
  ref.send(message.event);
557
235
  } catch (e) {
558
- warnLog2(source, "dispatch error", { error: e, sessionId: local });
236
+ console.warn("[xstate-devtools] dispatch error:", e);
559
237
  }
560
- } else {
561
- warnLog2(source, "received dispatch for unknown actor", {
562
- sessionId: local,
563
- knownActors: actorRefs.size
564
- });
565
238
  }
566
- return;
567
- }
568
- if (message.type === "XSTATE_SET_ACTIVE_STATE") {
239
+ } else if (message.type === "XSTATE_REQUEST_PERSISTED") {
569
240
  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
- }
241
+ if (local === null) return;
578
242
  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;
243
+ if (!ref) return;
244
+ const { persisted, error } = safePersistedSnapshot(ref);
245
+ transport.send({
246
+ type: "XSTATE_PERSISTED_SNAPSHOT",
247
+ sessionId: tag(local),
248
+ persisted,
249
+ error,
250
+ timestamp: Date.now()
251
+ });
252
+ } else if (message.type === "XSTATE_RESTORE") {
253
+ const local = stripIfMine(message.sessionId);
254
+ if (local === null) return;
255
+ const handler = restoreHandlers.get(local);
256
+ if (handler) {
257
+ try {
258
+ handler(message.persisted);
259
+ } catch (e) {
260
+ console.warn("[xstate-devtools] restore error:", e);
261
+ }
585
262
  }
586
- try {
587
- debugLog2(source, "setting active state on actor", summarizeMessage(message));
588
- setActiveState(ref, message.stateNodeId);
589
- const snapshotMessage = {
263
+ }
264
+ });
265
+ const inspect = (inspectionEvent) => {
266
+ try {
267
+ if (inspectionEvent.type === "@xstate.actor") {
268
+ const actorRef = inspectionEvent.actorRef;
269
+ const sessionId = actorRef.sessionId;
270
+ actorRefs.set(sessionId, actorRef);
271
+ const logic = actorRef.logic;
272
+ const machine = logic?.root ? serializeMachine(logic, getSourceLocation()) : null;
273
+ transport.send({
274
+ type: "XSTATE_ACTOR_REGISTERED",
275
+ sessionId: tag(sessionId),
276
+ parentSessionId: tagOptional(actorRef._parent?.sessionId),
277
+ // actorRef.id is the invoke `id` for invoked actors — lets the debugger
278
+ // nest non-machine actors (promise/callback) under their state.
279
+ actorId: actorRef.id,
280
+ machine,
281
+ snapshot: safeSerializeSnapshot(actorRef),
282
+ globalSeq: nextSeq(),
283
+ timestamp: Date.now()
284
+ });
285
+ } else if (inspectionEvent.type === "@xstate.snapshot") {
286
+ transport.send({
590
287
  type: "XSTATE_SNAPSHOT",
591
- sessionId: tag(local),
592
- snapshot: safeSerializeSnapshot(ref),
288
+ sessionId: tag(inspectionEvent.actorRef.sessionId),
289
+ snapshot: serializeSnapshot(inspectionEvent.snapshot),
290
+ timestamp: Date.now(),
291
+ globalSeq: nextSeq()
292
+ });
293
+ checkAndNotifyStop(inspectionEvent.actorRef);
294
+ } else if (inspectionEvent.type === "@xstate.event") {
295
+ transport.send({
296
+ type: "XSTATE_EVENT",
297
+ sessionId: tag(inspectionEvent.actorRef.sessionId),
298
+ event: inspectionEvent.event,
299
+ snapshotAfter: safeSerializeSnapshot(inspectionEvent.actorRef),
593
300
  timestamp: Date.now(),
594
301
  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
302
  });
303
+ checkAndNotifyStop(inspectionEvent.actorRef);
608
304
  }
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
- );
305
+ } catch (e) {
306
+ console.warn("[xstate-devtools] inspection failed, dropping event:", e.message);
688
307
  }
689
308
  };
309
+ function registerRestore(sessionId, handler) {
310
+ restoreHandlers.set(sessionId, handler);
311
+ return () => {
312
+ if (restoreHandlers.get(sessionId) === handler) restoreHandlers.delete(sessionId);
313
+ };
314
+ }
690
315
  function dispose() {
691
- infoLog2(source, "disposing inspector", { actorCount: actorRefs.size });
692
316
  unsubscribe();
693
317
  actorRefs.clear();
694
- actorMachines.clear();
318
+ restoreHandlers.clear();
695
319
  }
696
- return { inspect, dispose };
320
+ return { inspect, dispose, registerRestore };
697
321
  }
698
322
 
699
323
  // src/index.ts
700
- function createAdapter(options = {}) {
324
+ function createAdapter() {
701
325
  if (typeof window === "undefined") {
702
- infoLog("web:adapter", "createAdapter called without window; returning no-op adapter");
703
326
  return { inspect: () => {
704
327
  }, dispose: () => {
328
+ }, registerRestore: () => () => {
705
329
  } };
706
330
  }
707
- infoLog("web:adapter", "creating browser adapter", {
708
- hookInstalled: Boolean(window.__XSTATE_DEVTOOLS__)
709
- });
710
- let warnedMissingHook = false;
711
331
  const transport = {
712
332
  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, "*");
333
+ window.__XSTATE_DEVTOOLS__?.send({ ...message, __xstateDevtools: true });
727
334
  },
728
335
  subscribe(handler) {
729
- infoLog("web:adapter", "subscribing to window messages");
730
336
  const onMessage = (evt) => {
731
337
  if (evt.source !== window) return;
732
338
  const data = evt.data;
733
339
  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
340
  handler(data);
739
341
  };
740
342
  window.addEventListener("message", onMessage);
741
- return () => {
742
- infoLog("web:adapter", "unsubscribing from window messages");
743
- window.removeEventListener("message", onMessage);
744
- };
343
+ return () => window.removeEventListener("message", onMessage);
745
344
  }
746
345
  };
747
- return createInspector(transport, "web", options);
346
+ return createInspector(transport, "web");
748
347
  }
749
348
  // Annotate the CommonJS export names for ESM import in node:
750
349
  0 && (module.exports = {