@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/server.cjs CHANGED
@@ -33,115 +33,20 @@ __export(server_exports, {
33
33
  createServerAdapter: () => createServerAdapter
34
34
  });
35
35
  module.exports = __toCommonJS(server_exports);
36
- var import_node_net = require("net");
37
-
38
- // src/logging.ts
39
- function hasProcessEnv() {
40
- return typeof process !== "undefined" && typeof process.env !== "undefined";
41
- }
42
- function isLoggingEnabled() {
43
- if (globalThis.__XSTATE_DEVTOOLS_LOGGING__ === true) return true;
44
- if (!hasProcessEnv()) return false;
45
- const value = process.env.XSTATE_DEVTOOLS_LOGGING;
46
- return value === "1" || value === "true";
47
- }
48
- function log(level, scope, message, details) {
49
- if (!isLoggingEnabled()) return;
50
- if (details === void 0) {
51
- console[level](`[xstate-devtools:${scope}] ${message}`);
52
- return;
53
- }
54
- console[level](`[xstate-devtools:${scope}] ${message}`, details);
55
- }
56
- function debugLog(scope, message, details) {
57
- log("debug", scope, message, details);
58
- }
59
- function infoLog(scope, message, details) {
60
- log("info", scope, message, details);
61
- }
62
- function warnLog(scope, message, details) {
63
- log("warn", scope, message, details);
64
- }
65
-
66
- // src/sanitize.ts
67
- var MAX_DEPTH = 10;
68
- var MAX_STRING_LENGTH = 500;
69
- var MAX_ARRAY_LENGTH = 100;
70
- function sanitizeValue(value, depth, seen) {
71
- if (depth > MAX_DEPTH) return "[MaxDepth]";
72
- if (value === null || value === void 0) return value;
73
- if (typeof value === "boolean" || typeof value === "number") return value;
74
- if (typeof value === "string") {
75
- return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH)}\u2026` : value;
76
- }
77
- if (typeof value === "function") return `[Function: ${value.name || "(anonymous)"}]`;
78
- if (typeof value === "symbol") return `[Symbol: ${value.description ?? ""}]`;
79
- if (typeof value === "bigint") return `[BigInt: ${value}]`;
80
- if (value instanceof Error) return { __type: "Error", name: value.name, message: value.message };
81
- if (value instanceof Date) return { __type: "Date", iso: value.toISOString() };
82
- if (value instanceof RegExp) return { __type: "RegExp", source: value.source, flags: value.flags };
83
- if (typeof value === "object") {
84
- if (seen.has(value)) return "[Circular]";
85
- seen.add(value);
86
- }
87
- if (value instanceof Map) {
88
- const entries = [];
89
- for (const [k, v] of value) {
90
- if (entries.length >= MAX_ARRAY_LENGTH) break;
91
- entries.push([sanitizeValue(k, depth + 1, seen), sanitizeValue(v, depth + 1, seen)]);
92
- }
93
- return { __type: "Map", entries };
94
- }
95
- if (value instanceof Set) {
96
- const values = [];
97
- for (const v of value) {
98
- if (values.length >= MAX_ARRAY_LENGTH) break;
99
- values.push(sanitizeValue(v, depth + 1, seen));
100
- }
101
- return { __type: "Set", values };
102
- }
103
- if (value instanceof Promise) return "[Promise]";
104
- if (value instanceof WeakMap || value instanceof WeakSet) return "[WeakCollection]";
105
- if (ArrayBuffer.isView(value)) return `[TypedArray: ${value.constructor.name}]`;
106
- if (typeof Node !== "undefined" && value instanceof Node) {
107
- return `[DOMNode: ${value.tagName ?? value.nodeName}]`;
108
- }
109
- if (Array.isArray(value)) {
110
- const sliced = value.slice(0, MAX_ARRAY_LENGTH);
111
- const result = sliced.map((v) => sanitizeValue(v, depth + 1, seen));
112
- if (value.length > MAX_ARRAY_LENGTH) result.push(`[\u2026${value.length - MAX_ARRAY_LENGTH} more]`);
113
- return result;
114
- }
115
- if (typeof value === "object") {
116
- const result = {};
117
- let count = 0;
118
- for (const [k, v] of Object.entries(value)) {
119
- if (count++ >= MAX_ARRAY_LENGTH) {
120
- result["\u2026"] = "[truncated]";
121
- break;
122
- }
123
- result[k] = sanitizeValue(v, depth + 1, seen);
124
- }
125
- return result;
126
- }
127
- return String(value);
128
- }
129
- function sanitize(value, depth = 0) {
130
- return sanitizeValue(value, depth, /* @__PURE__ */ new WeakSet());
131
- }
132
36
 
133
37
  // src/serialize.ts
134
- var MAX_SERIALIZED_NODES = 500;
135
- var MAX_TRANSITIONS_PER_NODE = 100;
136
- var MAX_CHILD_STATES = 100;
137
- var MAX_ACTIONS_PER_TRANSITION = 20;
138
- var MAX_ENTRY_EXIT_ACTIONS = 20;
139
- var MAX_INVOKES_PER_NODE = 20;
140
38
  function serializeGuard(guard) {
141
39
  if (!guard) return void 0;
142
40
  if (typeof guard === "string") return guard;
143
- if (typeof guard === "function") return guard.name || "(inline)";
144
- if (typeof guard === "object" && guard !== null) {
41
+ if (typeof guard === "function") {
42
+ const fn = guard;
43
+ if (Array.isArray(fn.guards) && typeof fn.check === "function") {
44
+ const parts = fn.guards.map((g) => serializeGuard(g) ?? "(inline)");
45
+ return `${fn.name || "(combinator)"}(${parts.join(", ")})`;
46
+ }
47
+ return fn.name || "(inline)";
48
+ }
49
+ if (typeof guard === "object") {
145
50
  const g = guard;
146
51
  return g.type ?? g.name ?? "(inline)";
147
52
  }
@@ -157,214 +62,133 @@ function serializeAction(action) {
157
62
  return String(action);
158
63
  }
159
64
  function serializeTransitionList(transitions) {
160
- return transitions.slice(0, MAX_TRANSITIONS_PER_NODE).map((t) => ({
65
+ return transitions.map((t) => ({
161
66
  eventType: t.eventType ?? "",
162
67
  targets: (t.target ?? []).map((n) => n?.id ?? String(n)).filter(Boolean),
163
68
  guard: serializeGuard(t.guard),
164
- actions: (t.actions ?? []).slice(0, MAX_ACTIONS_PER_TRANSITION).map(serializeAction).filter(Boolean)
69
+ actions: (t.actions ?? []).map(serializeAction).filter(Boolean)
165
70
  }));
166
71
  }
167
72
  function serializeInvokes(node) {
168
- return node.invoke.slice(0, MAX_INVOKES_PER_NODE).map((inv) => ({
73
+ return node.invoke.map((inv) => ({
169
74
  id: inv.id ?? "(unknown)",
170
75
  src: typeof inv.src === "string" ? inv.src : inv.src?.id ?? inv.src?.name ?? "(inline)"
171
76
  }));
172
77
  }
173
- function serializeNode(node, state) {
174
- if (!node || typeof node !== "object") {
175
- return {
176
- id: "(unknown)",
177
- key: "(unknown)",
178
- type: "atomic",
179
- states: {},
180
- on: [],
181
- always: [],
182
- entry: [],
183
- exit: [],
184
- invoke: []
185
- };
186
- }
187
- if (state.count >= MAX_SERIALIZED_NODES) {
188
- return {
189
- id: node.id ?? "(truncated)",
190
- key: node.key ?? "(truncated)",
191
- type: node.type ?? "atomic",
192
- states: {},
193
- on: [],
194
- always: [],
195
- entry: [],
196
- exit: [],
197
- invoke: []
198
- };
199
- }
200
- if (state.seen.has(node)) {
201
- return {
202
- id: node.id ?? "(circular)",
203
- key: node.key ?? "(circular)",
204
- type: node.type ?? "atomic",
205
- states: {},
206
- on: [],
207
- always: [],
208
- entry: [],
209
- exit: [],
210
- invoke: []
211
- };
212
- }
213
- state.seen.add(node);
214
- state.count += 1;
78
+ function serializeNode(node) {
215
79
  const allTransitions = [];
216
80
  if (node.transitions instanceof Map) {
217
81
  for (const [, tList] of node.transitions) {
218
- if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) break;
219
82
  allTransitions.push(...serializeTransitionList(tList));
220
- if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) {
221
- allTransitions.length = MAX_TRANSITIONS_PER_NODE;
222
- break;
223
- }
224
83
  }
225
84
  }
226
85
  const always = Array.isArray(node.always) ? serializeTransitionList(node.always) : [];
227
- const childEntries = Object.entries(node.states ?? {}).slice(0, MAX_CHILD_STATES);
228
86
  return {
229
87
  id: node.id,
230
88
  key: node.key,
231
89
  type: node.type,
232
90
  initial: node.initial?.target?.[0]?.key,
233
- states: Object.fromEntries(childEntries.map(([k, v]) => [k, serializeNode(v, state)])),
91
+ states: Object.fromEntries(
92
+ Object.entries(node.states ?? {}).map(([k, v]) => [k, serializeNode(v)])
93
+ ),
234
94
  on: allTransitions,
235
95
  always,
236
- entry: (node.entry ?? []).slice(0, MAX_ENTRY_EXIT_ACTIONS).map(serializeAction).filter(Boolean),
237
- exit: (node.exit ?? []).slice(0, MAX_ENTRY_EXIT_ACTIONS).map(serializeAction).filter(Boolean),
238
- invoke: serializeInvokes(node),
239
- sourceLocation: node.config?.__xstateDevtoolsSource ?? void 0,
240
- description: node.config?.description ?? void 0
96
+ entry: (node.entry ?? []).map(serializeAction).filter(Boolean),
97
+ exit: (node.exit ?? []).map(serializeAction).filter(Boolean),
98
+ invoke: serializeInvokes(node)
241
99
  };
242
100
  }
243
101
  function serializeMachine(machine, sourceLocation) {
244
102
  return {
245
103
  id: machine.id,
246
- root: serializeNode(machine.root, { seen: /* @__PURE__ */ new WeakSet(), count: 0 }),
104
+ root: serializeNode(machine.root),
247
105
  sourceLocation
248
106
  };
249
107
  }
250
108
 
251
- // src/core.ts
252
- function summarizeMessage(message) {
253
- const summary = { type: message.type };
254
- if ("sessionId" in message) summary.sessionId = message.sessionId;
255
- if ("stateNodeId" in message) summary.stateNodeId = message.stateNodeId;
256
- if ("parentSessionId" in message && message.parentSessionId) {
257
- summary.parentSessionId = message.parentSessionId;
109
+ // src/sanitize.ts
110
+ var MAX_DEPTH = 10;
111
+ var MAX_STRING_LENGTH = 500;
112
+ var MAX_ARRAY_LENGTH = 100;
113
+ var MAX_NODES = 1e4;
114
+ function sanitizeInner(value, ctx) {
115
+ if (ctx.depth > MAX_DEPTH) return "[MaxDepth]";
116
+ if (++ctx.budget.n > MAX_NODES) return "[Truncated]";
117
+ if (value === null || value === void 0) return value;
118
+ if (typeof value === "boolean" || typeof value === "number") return value;
119
+ if (typeof value === "string") {
120
+ return value.length > MAX_STRING_LENGTH ? value.slice(0, MAX_STRING_LENGTH) + "\u2026" : value;
258
121
  }
259
- if ("globalSeq" in message) summary.globalSeq = message.globalSeq;
260
- if ("timestamp" in message) summary.timestamp = message.timestamp;
261
- if ("event" in message && message.event && typeof message.event === "object" && "type" in message.event) {
262
- summary.eventType = message.event.type;
122
+ if (typeof value === "function") return `[Function: ${value.name || "(anonymous)"}]`;
123
+ if (typeof value === "symbol") return `[Symbol: ${value.description ?? ""}]`;
124
+ if (typeof value === "bigint") return `[BigInt: ${value}]`;
125
+ if (value instanceof Error) return { __type: "Error", name: value.name, message: value.message };
126
+ if (value instanceof Date) return { __type: "Date", iso: value.toISOString() };
127
+ if (value instanceof RegExp) return { __type: "RegExp", source: value.source, flags: value.flags };
128
+ if (value instanceof Promise) return "[Promise]";
129
+ if (value instanceof WeakMap || value instanceof WeakSet) return "[WeakCollection]";
130
+ if (ArrayBuffer.isView(value)) return `[TypedArray: ${value.constructor.name}]`;
131
+ if (typeof Node !== "undefined" && value instanceof Node) {
132
+ return `[DOMNode: ${value.tagName ?? value.nodeName}]`;
263
133
  }
264
- return summary;
265
- }
266
- function summarizeInspectionEvent(event) {
267
- return {
268
- type: event?.type,
269
- sessionId: event?.actorRef?.sessionId,
270
- eventType: event?.type === "@xstate.event" && event?.event && typeof event.event === "object" ? event.event.type : void 0
271
- };
272
- }
273
- function debugLog2(source, message, details) {
274
- debugLog(`${source}:adapter`, message, details);
275
- }
276
- function infoLog2(source, message, details) {
277
- infoLog(`${source}:adapter`, message, details);
278
- }
279
- function warnLog2(source, message, details) {
280
- warnLog(`${source}:adapter`, message, details);
281
- }
282
- function isLibraryStackFrame(line) {
283
- const normalized = line.replace(/\\/g, "/").toLowerCase();
284
- return normalized.includes("/node_modules/xstate/") || normalized.includes("/node_modules/@xstate/") || normalized.includes("/@xstate-devtools/adapter/") || normalized.includes("/packages/adapter/");
285
- }
286
- function normalizeStackFrame(line) {
287
- return line.trim().replace(/^at\s+/, "");
288
- }
289
- function extractStackFrameLocation(frame) {
290
- return frame.match(/\((.*)\)$/)?.[1] ?? frame;
291
- }
292
- function isAnonymousOrEvalLocation(location) {
293
- const normalized = location.trim().toLowerCase().replace(/^[./\\]+/, "");
294
- return normalized === "<anonymous>" || normalized === "anonymous" || normalized === "(anonymous)" || normalized === "eval" || normalized === "<eval>" || normalized === "[native code]";
295
- }
296
- function hasFilesystemBackedPath(location) {
297
- const trimmed = location.trim();
298
- if (!trimmed) return false;
299
- const match = trimmed.match(/^(.*?)(?::\d+)?(?::\d+)?$/);
300
- const rawPath = (match?.[1] ?? trimmed).trim();
301
- if (!rawPath || isAnonymousOrEvalLocation(rawPath)) return false;
302
- if (/^[a-zA-Z]:[\\/]/.test(rawPath)) return true;
303
- if (rawPath.startsWith("/") || rawPath.startsWith("./") || rawPath.startsWith("../")) return true;
304
- if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(rawPath)) {
305
- try {
306
- const url = new URL(rawPath);
307
- if (url.protocol === "file:") return true;
308
- return url.pathname.startsWith("/@fs/");
309
- } catch {
310
- return false;
134
+ if (ctx.seen.has(value)) return "[Circular]";
135
+ ctx.seen.add(value);
136
+ const child = { ...ctx, depth: ctx.depth + 1 };
137
+ if (value instanceof Map) {
138
+ const entries = [];
139
+ for (const [k, v] of value) {
140
+ if (entries.length >= MAX_ARRAY_LENGTH) break;
141
+ entries.push([sanitizeInner(k, child), sanitizeInner(v, child)]);
311
142
  }
143
+ return { __type: "Map", entries };
312
144
  }
313
- return false;
314
- }
315
- function remapWebUrlLocationToFsPath(location, source, options) {
316
- if (source !== "web" || !options?.webSourceRoot) return location;
317
- const match = location.trim().match(/^(.*?)(?::(\d+))?(?::(\d+))?$/);
318
- if (!match) return location;
319
- const [, rawPath, line, column] = match;
320
- if (!rawPath || !/^https?:\/\//.test(rawPath)) return location;
321
- try {
322
- const url = new URL(rawPath);
323
- const pathname = decodeURIComponent(url.pathname);
324
- if (!pathname.startsWith("/app/")) return location;
325
- const root = options.webSourceRoot.replace(/\/+$/, "");
326
- const filePath = `${root}${pathname}`;
327
- const suffix = line ? `:${line}${column ? `:${column}` : ""}` : "";
328
- return `${filePath}${suffix}`;
329
- } catch {
330
- return location;
145
+ if (value instanceof Set) {
146
+ const values = [];
147
+ for (const v of value) {
148
+ if (values.length >= MAX_ARRAY_LENGTH) break;
149
+ values.push(sanitizeInner(v, child));
150
+ }
151
+ return { __type: "Set", values };
331
152
  }
332
- }
333
- function getSourceLocationFromStack(stack, source = "web", options) {
334
- const lines = stack?.split("\n") ?? [];
335
- for (let i = 0; i < lines.length; i += 1) {
336
- if (i <= 2) continue;
337
- const line = lines[i];
338
- if (isLibraryStackFrame(line)) continue;
339
- const normalizedFrame = normalizeStackFrame(line);
340
- const location = remapWebUrlLocationToFsPath(
341
- extractStackFrameLocation(normalizedFrame),
342
- source,
343
- options
344
- );
345
- if (!hasFilesystemBackedPath(location)) continue;
346
- const wrapped = normalizedFrame.match(/\((.*)\)$/);
347
- if (wrapped) {
348
- return normalizedFrame.replace(wrapped[1], location);
153
+ if (Array.isArray(value)) {
154
+ const sliced = value.slice(0, MAX_ARRAY_LENGTH);
155
+ const result = sliced.map((v) => sanitizeInner(v, child));
156
+ if (value.length > MAX_ARRAY_LENGTH) result.push(`[\u2026${value.length - MAX_ARRAY_LENGTH} more]`);
157
+ return result;
158
+ }
159
+ if (typeof value === "object") {
160
+ const result = {};
161
+ let count = 0;
162
+ for (const [k, v] of Object.entries(value)) {
163
+ if (count++ >= MAX_ARRAY_LENGTH) {
164
+ result["\u2026"] = "[truncated]";
165
+ break;
166
+ }
167
+ result[k] = sanitizeInner(v, child);
349
168
  }
350
- return location;
169
+ return result;
351
170
  }
352
- return void 0;
171
+ return String(value);
172
+ }
173
+ function sanitize(value) {
174
+ return sanitizeInner(value, { depth: 0, budget: { n: 0 }, seen: /* @__PURE__ */ new WeakSet() });
353
175
  }
354
- function getSourceLocation(source, options) {
176
+
177
+ // src/core.ts
178
+ function getSourceLocation() {
355
179
  try {
356
- const oldLimit = Error.stackTraceLimit;
357
- Error.stackTraceLimit = 50;
358
- const stack = new Error().stack;
359
- Error.stackTraceLimit = oldLimit;
360
- return getSourceLocationFromStack(stack, source, options);
180
+ const lines = new Error().stack?.split("\n") ?? [];
181
+ const callerLine = lines.find(
182
+ (l, i) => i > 3 && !l.includes("xstate") && !l.includes("adapter")
183
+ );
184
+ return callerLine?.trim().replace(/^at\s+/, "");
361
185
  } catch {
362
186
  return void 0;
363
187
  }
364
188
  }
365
189
  function serializeSnapshot(snapshot) {
366
190
  return {
367
- value: snapshot?.value ?? null,
191
+ value: sanitize(snapshot?.value ?? null),
368
192
  context: sanitize(snapshot?.context),
369
193
  status: snapshot?.status ?? "active",
370
194
  error: snapshot?.error ? sanitize(snapshot.error) : void 0
@@ -377,125 +201,18 @@ function safeSerializeSnapshot(actorRef) {
377
201
  return { value: null, context: void 0, status: "active" };
378
202
  }
379
203
  }
380
- function getActorDisplayName(actorRef) {
381
- const actor = actorRef;
382
- const src = actor.src ?? actor.logic?.src;
383
- if (typeof src === "string" && src.length > 0) return src;
384
- if (typeof actor.logic?.id === "string" && actor.logic.id.length > 0) return actor.logic.id;
385
- if (typeof actor.logic?.name === "string" && actor.logic.name.length > 0) return actor.logic.name;
386
- if (src && typeof src === "object") {
387
- const namedSrc = src;
388
- if (typeof namedSrc.id === "string" && namedSrc.id.length > 0) return namedSrc.id;
389
- if (typeof namedSrc.name === "string" && namedSrc.name.length > 0) return namedSrc.name;
390
- }
391
- return void 0;
392
- }
393
- function getNodeInitialChild(node) {
394
- if (!node.states) return null;
395
- if (typeof node.initial === "string") {
396
- return node.states[node.initial] ?? null;
397
- }
398
- const target = Array.isArray(node.initial?.target) ? node.initial.target[0] : null;
399
- return target ?? null;
400
- }
401
- function encodeChildValue(child, childValue) {
402
- if (child.type === "atomic" || child.type === "final" || child.type === "history") {
403
- return child.key;
404
- }
405
- return { [child.key]: childValue };
406
- }
407
- function getDefaultStateValue(node) {
408
- if (node.type === "parallel") {
409
- const value = {};
410
- for (const child of Object.values(node.states ?? {})) {
411
- value[child.key] = getDefaultSelectionValue(child);
412
- }
413
- return value;
414
- }
415
- const initialChild = getNodeInitialChild(node);
416
- if (!initialChild) return {};
417
- return encodeChildValue(initialChild, getDefaultStateValue(initialChild));
418
- }
419
- function getDefaultSelectionValue(node) {
420
- if (node.type === "atomic" || node.type === "final" || node.type === "history") {
421
- return node.key;
422
- }
423
- return getDefaultStateValue(node);
424
- }
425
- function getExistingChildValue(value, childKey) {
426
- if (!value || typeof value !== "object") return void 0;
427
- return value[childKey];
428
- }
429
- function getPathToRoot(target, root) {
430
- const path = [];
431
- let current = target;
432
- while (current) {
433
- path.unshift(current);
434
- if (current.id === root.id) return path;
435
- current = current.parent;
436
- }
437
- throw new Error(`State node '${target.id}' is not part of machine '${root.id}'`);
438
- }
439
- function buildTargetStateValue(node, path, currentValue) {
440
- const [, ...restPath] = path;
441
- if (restPath.length === 0) {
442
- if (node.type === "parallel") {
443
- const next = {};
444
- for (const child2 of Object.values(node.states ?? {})) {
445
- next[child2.key] = getExistingChildValue(currentValue, child2.key) ?? getDefaultSelectionValue(child2);
446
- }
447
- return next;
448
- }
449
- if (node.type === "compound") {
450
- return getDefaultStateValue(node);
451
- }
452
- return node.key;
204
+ function safePersistedSnapshot(actorRef) {
205
+ const getPersisted = actorRef.getPersistedSnapshot;
206
+ if (typeof getPersisted !== "function") {
207
+ return { error: "Actor does not support getPersistedSnapshot." };
453
208
  }
454
- const child = restPath[0];
455
- if (node.type === "parallel") {
456
- const next = {};
457
- for (const sibling of Object.values(node.states ?? {})) {
458
- if (sibling.key === child.key) {
459
- next[sibling.key] = buildTargetStateValue(
460
- sibling,
461
- restPath,
462
- getExistingChildValue(currentValue, sibling.key)
463
- );
464
- } else {
465
- next[sibling.key] = getExistingChildValue(currentValue, sibling.key) ?? getDefaultSelectionValue(sibling);
466
- }
467
- }
468
- return next;
469
- }
470
- const childValue = buildTargetStateValue(
471
- child,
472
- restPath,
473
- getExistingChildValue(currentValue, child.key)
474
- );
475
- return encodeChildValue(child, childValue);
476
- }
477
- function setActiveState(actorRef, stateNodeId) {
478
- const mutableActorRef = actorRef;
479
- const machine = mutableActorRef.logic;
480
- if (!machine?.getStateNodeById || !machine.resolveState || typeof mutableActorRef.update !== "function") {
481
- throw new Error("Actor does not expose machine state mutation internals");
209
+ try {
210
+ const raw = getPersisted.call(actorRef);
211
+ if (raw === void 0) return { error: "No persisted snapshot available." };
212
+ return { persisted: JSON.parse(JSON.stringify(raw)) };
213
+ } catch (e) {
214
+ return { error: `Snapshot is not JSON-serializable: ${e.message}` };
482
215
  }
483
- const currentSnapshot = actorRef.getSnapshot();
484
- const targetNode = machine.getStateNodeById(stateNodeId);
485
- const path = getPathToRoot(targetNode, machine.root);
486
- const targetValue = buildTargetStateValue(machine.root, path, currentSnapshot?.value);
487
- const nextSnapshot = machine.resolveState({
488
- value: targetValue,
489
- context: currentSnapshot?.context,
490
- status: currentSnapshot?.status,
491
- output: currentSnapshot?.output,
492
- error: currentSnapshot?.error,
493
- historyValue: currentSnapshot?.historyValue
494
- });
495
- mutableActorRef.update(nextSnapshot, {
496
- type: "xstate.devtools.set-active-state",
497
- stateNodeId
498
- });
499
216
  }
500
217
  var SEQ_KEY = "__xstate_devtools_global_seq__";
501
218
  function nextSeq() {
@@ -505,10 +222,10 @@ function nextSeq() {
505
222
  g[SEQ_KEY] = next;
506
223
  return next;
507
224
  }
508
- function createInspector(transport, source, options = {}) {
225
+ function createInspector(transport, source) {
509
226
  const actorRefs = /* @__PURE__ */ new Map();
510
- const actorMachines = /* @__PURE__ */ new Map();
511
- const prefix = `${source}:`;
227
+ const restoreHandlers = /* @__PURE__ */ new Map();
228
+ const prefix = source + ":";
512
229
  const tag = (sessionId) => prefix + sessionId;
513
230
  const tagOptional = (id) => id ? prefix + id : void 0;
514
231
  const stripIfMine = (id) => id.startsWith(prefix) ? id.slice(prefix.length) : null;
@@ -520,300 +237,166 @@ function createInspector(transport, source, options = {}) {
520
237
  return;
521
238
  }
522
239
  if (snap?.status !== "active") {
523
- const message = {
524
- type: "XSTATE_ACTOR_STOPPED",
525
- sessionId: tag(actorRef.sessionId)
526
- };
527
- debugLog2(source, "actor stopped; notifying transport", summarizeMessage(message));
528
- transport.send(message);
240
+ transport.send({ type: "XSTATE_ACTOR_STOPPED", sessionId: tag(actorRef.sessionId) });
529
241
  actorRefs.delete(actorRef.sessionId);
530
- actorMachines.delete(actorRef.sessionId);
531
242
  }
532
243
  }
533
244
  const unsubscribe = transport.subscribe((message) => {
534
- debugLog2(source, "received message from transport", summarizeMessage(message));
535
- if (message.type === "XSTATE_PANEL_CONNECTED") {
536
- infoLog2(source, "panel connected; resyncing actors", { actorCount: actorRefs.size });
537
- actorRefs.forEach((actorRef, sessionId) => {
538
- const machine = actorMachines.get(sessionId) ?? null;
539
- const resyncMessage = {
540
- type: "XSTATE_ACTOR_REGISTERED",
541
- sessionId: tag(sessionId),
542
- parentSessionId: tagOptional(actorRef._parent?.sessionId),
543
- displayName: getActorDisplayName(actorRef),
544
- machine,
545
- snapshot: safeSerializeSnapshot(actorRef),
546
- globalSeq: nextSeq(),
547
- timestamp: Date.now()
548
- };
549
- debugLog2(source, "resyncing actor", summarizeMessage(resyncMessage));
550
- transport.send(resyncMessage);
551
- });
552
- return;
553
- }
554
245
  if (message.type === "XSTATE_DISPATCH") {
555
246
  const local = stripIfMine(message.sessionId);
556
- if (local === null) {
557
- debugLog2(source, "ignoring dispatch for different source", summarizeMessage(message));
558
- return;
559
- }
247
+ if (local === null) return;
560
248
  const ref = actorRefs.get(local);
561
249
  if (ref) {
562
250
  try {
563
- debugLog2(source, "dispatching event to actor", {
564
- sessionId: local,
565
- eventType: message.event && typeof message.event === "object" && "type" in message.event ? message.event.type : void 0
566
- });
567
251
  ref.send(message.event);
568
252
  } catch (e) {
569
- warnLog2(source, "dispatch error", { error: e, sessionId: local });
253
+ console.warn("[xstate-devtools] dispatch error:", e);
570
254
  }
571
- } else {
572
- warnLog2(source, "received dispatch for unknown actor", {
573
- sessionId: local,
574
- knownActors: actorRefs.size
575
- });
576
255
  }
577
- return;
578
- }
579
- if (message.type === "XSTATE_SET_ACTIVE_STATE") {
256
+ } else if (message.type === "XSTATE_REQUEST_PERSISTED") {
580
257
  const local = stripIfMine(message.sessionId);
581
- if (local === null) {
582
- debugLog2(
583
- source,
584
- "ignoring state activation for different source",
585
- summarizeMessage(message)
586
- );
587
- return;
588
- }
258
+ if (local === null) return;
589
259
  const ref = actorRefs.get(local);
590
- if (!ref) {
591
- warnLog2(source, "received state activation for unknown actor", {
592
- sessionId: local,
593
- knownActors: actorRefs.size
594
- });
595
- return;
260
+ if (!ref) return;
261
+ const { persisted, error } = safePersistedSnapshot(ref);
262
+ transport.send({
263
+ type: "XSTATE_PERSISTED_SNAPSHOT",
264
+ sessionId: tag(local),
265
+ persisted,
266
+ error,
267
+ timestamp: Date.now()
268
+ });
269
+ } else if (message.type === "XSTATE_RESTORE") {
270
+ const local = stripIfMine(message.sessionId);
271
+ if (local === null) return;
272
+ const handler = restoreHandlers.get(local);
273
+ if (handler) {
274
+ try {
275
+ handler(message.persisted);
276
+ } catch (e) {
277
+ console.warn("[xstate-devtools] restore error:", e);
278
+ }
596
279
  }
597
- try {
598
- debugLog2(source, "setting active state on actor", summarizeMessage(message));
599
- setActiveState(ref, message.stateNodeId);
600
- const snapshotMessage = {
280
+ }
281
+ });
282
+ const inspect = (inspectionEvent) => {
283
+ try {
284
+ if (inspectionEvent.type === "@xstate.actor") {
285
+ const actorRef = inspectionEvent.actorRef;
286
+ const sessionId = actorRef.sessionId;
287
+ actorRefs.set(sessionId, actorRef);
288
+ const logic = actorRef.logic;
289
+ const machine = logic?.root ? serializeMachine(logic, getSourceLocation()) : null;
290
+ transport.send({
291
+ type: "XSTATE_ACTOR_REGISTERED",
292
+ sessionId: tag(sessionId),
293
+ parentSessionId: tagOptional(actorRef._parent?.sessionId),
294
+ // actorRef.id is the invoke `id` for invoked actors — lets the debugger
295
+ // nest non-machine actors (promise/callback) under their state.
296
+ actorId: actorRef.id,
297
+ machine,
298
+ snapshot: safeSerializeSnapshot(actorRef),
299
+ globalSeq: nextSeq(),
300
+ timestamp: Date.now()
301
+ });
302
+ } else if (inspectionEvent.type === "@xstate.snapshot") {
303
+ transport.send({
601
304
  type: "XSTATE_SNAPSHOT",
602
- sessionId: tag(local),
603
- snapshot: safeSerializeSnapshot(ref),
305
+ sessionId: tag(inspectionEvent.actorRef.sessionId),
306
+ snapshot: serializeSnapshot(inspectionEvent.snapshot),
604
307
  timestamp: Date.now(),
605
308
  globalSeq: nextSeq()
606
- };
607
- debugLog2(
608
- source,
609
- "sending snapshot after state activation",
610
- summarizeMessage(snapshotMessage)
611
- );
612
- transport.send(snapshotMessage);
613
- } catch (error) {
614
- warnLog2(source, "failed to set active state", {
615
- error,
616
- sessionId: local,
617
- stateNodeId: message.stateNodeId
618
309
  });
310
+ checkAndNotifyStop(inspectionEvent.actorRef);
311
+ } else if (inspectionEvent.type === "@xstate.event") {
312
+ transport.send({
313
+ type: "XSTATE_EVENT",
314
+ sessionId: tag(inspectionEvent.actorRef.sessionId),
315
+ event: inspectionEvent.event,
316
+ snapshotAfter: safeSerializeSnapshot(inspectionEvent.actorRef),
317
+ timestamp: Date.now(),
318
+ globalSeq: nextSeq()
319
+ });
320
+ checkAndNotifyStop(inspectionEvent.actorRef);
619
321
  }
620
- }
621
- });
622
- transport.send({ type: "XSTATE_ADAPTER_READY" });
623
- infoLog2(source, "inspector created");
624
- const inspect = (inspectionEvent) => {
625
- debugLog2(source, "inspect callback invoked", summarizeInspectionEvent(inspectionEvent));
626
- if (inspectionEvent.type === "@xstate.actor") {
627
- const actorRef = inspectionEvent.actorRef;
628
- const sessionId = actorRef.sessionId;
629
- const actorLogic = actorRef.logic;
630
- const machine = actorLogic?.root ? serializeMachine(
631
- actorLogic,
632
- actorLogic.config?.__xstateDevtoolsSource ?? getSourceLocation(source, options)
633
- ) : null;
634
- actorRefs.set(sessionId, actorRef);
635
- actorMachines.set(sessionId, machine);
636
- const notifyStop = () => {
637
- if (actorRefs.has(sessionId)) {
638
- actorRefs.delete(sessionId);
639
- actorMachines.delete(sessionId);
640
- const stopMsg = {
641
- type: "XSTATE_ACTOR_STOPPED",
642
- sessionId: tag(sessionId)
643
- };
644
- debugLog2(source, "actor stopped; notifying transport", summarizeMessage(stopMsg));
645
- transport.send(stopMsg);
646
- }
647
- };
648
- try {
649
- actorRef.subscribe({ complete: notifyStop, error: notifyStop });
650
- } catch {
651
- }
652
- const message = {
653
- type: "XSTATE_ACTOR_REGISTERED",
654
- sessionId: tag(sessionId),
655
- parentSessionId: tagOptional(actorRef._parent?.sessionId),
656
- displayName: getActorDisplayName(actorRef),
657
- machine,
658
- snapshot: safeSerializeSnapshot(actorRef),
659
- globalSeq: nextSeq(),
660
- timestamp: Date.now()
661
- };
662
- infoLog2(source, "registering actor with transport", {
663
- message: summarizeMessage(message),
664
- actorCount: actorRefs.size,
665
- hasMachine: machine !== null
666
- });
667
- transport.send(message);
668
- } else if (inspectionEvent.type === "@xstate.snapshot") {
669
- const message = {
670
- type: "XSTATE_SNAPSHOT",
671
- sessionId: tag(inspectionEvent.actorRef.sessionId),
672
- snapshot: serializeSnapshot(inspectionEvent.snapshot),
673
- timestamp: Date.now(),
674
- globalSeq: nextSeq()
675
- };
676
- debugLog2(source, "sending snapshot to transport", summarizeMessage(message));
677
- transport.send(message);
678
- checkAndNotifyStop(inspectionEvent.actorRef);
679
- } else if (inspectionEvent.type === "@xstate.event") {
680
- const message = {
681
- type: "XSTATE_EVENT",
682
- sessionId: tag(inspectionEvent.actorRef.sessionId),
683
- // sanitize() returns unknown; the inspected event keeps its { type, ... }
684
- // shape, so it is a SerializedEvent after deep-sanitizing.
685
- event: sanitize(inspectionEvent.event),
686
- snapshotAfter: safeSerializeSnapshot(inspectionEvent.actorRef),
687
- timestamp: Date.now(),
688
- globalSeq: nextSeq()
689
- };
690
- debugLog2(source, "sending event to transport", summarizeMessage(message));
691
- transport.send(message);
692
- checkAndNotifyStop(inspectionEvent.actorRef);
693
- } else {
694
- debugLog2(
695
- source,
696
- "ignoring unsupported inspection event",
697
- summarizeInspectionEvent(inspectionEvent)
698
- );
322
+ } catch (e) {
323
+ console.warn("[xstate-devtools] inspection failed, dropping event:", e.message);
699
324
  }
700
325
  };
326
+ function registerRestore(sessionId, handler) {
327
+ restoreHandlers.set(sessionId, handler);
328
+ return () => {
329
+ if (restoreHandlers.get(sessionId) === handler) restoreHandlers.delete(sessionId);
330
+ };
331
+ }
701
332
  function dispose() {
702
- infoLog2(source, "disposing inspector", { actorCount: actorRefs.size });
703
333
  unsubscribe();
704
334
  actorRefs.clear();
705
- actorMachines.clear();
335
+ restoreHandlers.clear();
706
336
  }
707
- return { inspect, dispose };
337
+ return { inspect, dispose, registerRestore };
708
338
  }
709
339
 
710
340
  // src/server.ts
711
- function getAvailablePort(start) {
712
- return new Promise((resolve, reject) => {
713
- const probe = (0, import_node_net.createServer)();
714
- probe.unref();
715
- probe.on("error", (err) => {
716
- if (err.code === "EADDRINUSE") {
717
- resolve(getAvailablePort(start + 1));
718
- } else {
719
- reject(err);
720
- }
721
- });
722
- probe.listen(start, "127.0.0.1", () => {
723
- const port = probe.address().port;
724
- probe.close(() => resolve(port));
725
- });
726
- });
727
- }
728
341
  var OPEN_STATE = 1;
729
- function summarizeMessage2(message) {
730
- const summary = { type: message.type };
731
- if ("sessionId" in message) summary.sessionId = message.sessionId;
732
- if ("parentSessionId" in message && message.parentSessionId) {
733
- summary.parentSessionId = message.parentSessionId;
734
- }
735
- if ("globalSeq" in message) summary.globalSeq = message.globalSeq;
736
- if ("timestamp" in message) summary.timestamp = message.timestamp;
737
- if ("event" in message && message.event && typeof message.event === "object" && "type" in message.event) {
738
- summary.eventType = message.event.type;
739
- }
740
- return summary;
741
- }
742
- function debugLog3(message, details) {
743
- debugLog("server", message, details);
744
- }
745
- function infoLog3(message, details) {
746
- infoLog("server", message, details);
747
- }
748
- function warnLog3(message, details) {
749
- warnLog("server", message, details);
750
- }
751
- function stringifyOutgoingMessage(message) {
752
- const payload = { ...message, __xstateDevtools: true };
753
- try {
754
- return JSON.stringify(payload);
755
- } catch (error) {
756
- warnLog3("failed to stringify adapter message; retrying with sanitized payload", {
757
- error,
758
- message: summarizeMessage2(message)
759
- });
760
- }
761
- try {
762
- return JSON.stringify(sanitize(payload));
763
- } catch (error) {
764
- warnLog3("dropping adapter message that could not be stringified", {
765
- error,
766
- message: summarizeMessage2(message)
767
- });
768
- return null;
342
+ function trackLive(server, message) {
343
+ switch (message.type) {
344
+ case "XSTATE_ACTOR_REGISTERED":
345
+ server.liveActors.set(message.sessionId, { reg: message, snapshot: message.snapshot });
346
+ break;
347
+ case "XSTATE_SNAPSHOT": {
348
+ const live = server.liveActors.get(message.sessionId);
349
+ if (live) {
350
+ live.snapshot = message.snapshot;
351
+ }
352
+ break;
353
+ }
354
+ case "XSTATE_EVENT": {
355
+ const live = server.liveActors.get(message.sessionId);
356
+ if (live) {
357
+ live.snapshot = message.snapshotAfter;
358
+ }
359
+ break;
360
+ }
361
+ case "XSTATE_ACTOR_STOPPED":
362
+ server.liveActors.delete(message.sessionId);
363
+ break;
769
364
  }
770
365
  }
771
366
  function createServerAdapter(options = {}) {
772
367
  const port = options.port ?? (Number(process.env.XSTATE_DEVTOOLS_PORT) || 9301);
773
368
  const host = options.host ?? "127.0.0.1";
774
369
  const bufferSize = options.bufferSize ?? 200;
775
- infoLog3("createServerAdapter called", { host, port, bufferSize });
776
370
  const key = `__xstate_devtools_server_${port}__`;
777
371
  const cache = globalThis[key];
778
372
  let server;
779
373
  if (cache) {
780
374
  server = cache;
781
- infoLog3("reusing cached WebSocket server", {
782
- host,
783
- port,
784
- clientCount: server.clients.size,
785
- bufferedMessages: server.buffer.length
786
- });
787
375
  if (bufferSize > server.bufferSize) server.bufferSize = bufferSize;
788
376
  } else {
789
377
  const clients = /* @__PURE__ */ new Set();
790
378
  const dispatchHandlers = /* @__PURE__ */ new Set();
791
- const buffer = [];
379
+ const liveActors = /* @__PURE__ */ new Map();
380
+ const recentEvents = [];
792
381
  let wss = null;
793
382
  let closed = false;
794
- let portResolve;
795
- let portReject;
796
- const portPromise = new Promise((res, rej) => {
797
- portResolve = res;
798
- portReject = rej;
799
- });
800
383
  server = {
801
384
  clients,
802
385
  dispatchHandlers,
803
- buffer,
386
+ liveActors,
387
+ recentEvents,
804
388
  bufferSize,
805
389
  activated: false,
806
- port: portPromise,
807
390
  close: () => {
808
391
  closed = true;
809
- infoLog3("closing WebSocket server", { host, port, clientCount: clients.size });
810
392
  try {
811
393
  wss?.close();
812
394
  } catch {
813
395
  }
814
396
  clients.clear();
815
397
  dispatchHandlers.clear();
816
- buffer.length = 0;
398
+ liveActors.clear();
399
+ recentEvents.length = 0;
817
400
  delete globalThis[key];
818
401
  }
819
402
  };
@@ -822,112 +405,93 @@ function createServerAdapter(options = {}) {
822
405
  const mod = await import("ws");
823
406
  const WSServer = mod.WebSocketServer ?? mod.Server;
824
407
  if (closed) return;
825
- const actualPort = await getAvailablePort(port);
826
- if (closed) return;
827
- wss = new WSServer({ port: actualPort, host });
828
- process.env.XSTATE_DEVTOOLS_PORT = String(actualPort);
829
- portResolve(actualPort);
830
- infoLog3("WebSocket server listening", { host, port: actualPort });
408
+ wss = new WSServer({ port, host });
831
409
  wss.on("connection", (ws) => {
832
- infoLog3("panel connected to WebSocket server", {
833
- host,
834
- port,
835
- activated: server.activated,
836
- bufferedMessages: server.buffer.length
837
- });
410
+ for (const { reg, snapshot } of server.liveActors.values()) {
411
+ try {
412
+ ws.send(JSON.stringify({ ...reg, __xstateDevtools: true }));
413
+ } catch {
414
+ }
415
+ if (snapshot !== reg.snapshot) {
416
+ try {
417
+ ws.send(JSON.stringify({
418
+ type: "XSTATE_SNAPSHOT",
419
+ sessionId: reg.sessionId,
420
+ snapshot,
421
+ timestamp: reg.timestamp,
422
+ globalSeq: reg.globalSeq,
423
+ __xstateDevtools: true
424
+ }));
425
+ } catch {
426
+ }
427
+ }
428
+ }
429
+ try {
430
+ ws.send(JSON.stringify({
431
+ type: "XSTATE_REPLAY_DONE",
432
+ sessionIds: [...server.liveActors.keys()],
433
+ __xstateDevtools: true
434
+ }));
435
+ } catch {
436
+ }
838
437
  if (!server.activated) {
839
438
  server.activated = true;
840
- infoLog3("flushing bootstrap buffer to first panel", {
841
- host,
842
- port,
843
- bufferedMessages: server.buffer.length
844
- });
845
- for (const payload of server.buffer) {
439
+ for (const payload of server.recentEvents) {
846
440
  try {
847
441
  ws.send(payload);
848
442
  } catch {
849
443
  }
850
444
  }
851
- server.buffer.length = 0;
445
+ server.recentEvents.length = 0;
852
446
  }
853
447
  server.clients.add(ws);
854
448
  ws.on("message", (raw) => {
855
449
  try {
856
450
  const text = typeof raw === "string" ? raw : raw.toString("utf8");
857
451
  const msg = JSON.parse(text);
858
- debugLog3("received dispatch from panel", summarizeMessage2(msg));
859
452
  for (const cb of server.dispatchHandlers) cb(msg);
860
- } catch (error) {
861
- warnLog3("failed to parse panel message", { error });
453
+ } catch {
862
454
  }
863
455
  });
864
- ws.on("close", () => {
865
- server.clients.delete(ws);
866
- infoLog3("panel disconnected from WebSocket server", {
867
- host,
868
- port,
869
- clientCount: server.clients.size
870
- });
871
- });
872
- ws.on("error", (error) => {
873
- server.clients.delete(ws);
874
- warnLog3("WebSocket client error", { error });
875
- });
456
+ ws.on("close", () => server.clients.delete(ws));
457
+ ws.on("error", () => server.clients.delete(ws));
876
458
  });
877
459
  wss.on("error", (err) => {
878
- warnLog3("WS server error", { host, port, message: err.message });
460
+ console.warn("[xstate-devtools] WS server error:", err.message);
879
461
  });
880
462
  } catch (e) {
881
- portReject(e);
882
- warnLog3("could not start server adapter \u2014 install `ws` to enable", {
883
- host,
884
- port,
885
- message: e.message
886
- });
463
+ console.warn(
464
+ "[xstate-devtools] could not start server adapter \u2014 install `ws` to enable.",
465
+ e.message
466
+ );
887
467
  }
888
468
  })();
889
469
  globalThis[key] = server;
890
470
  }
891
471
  const transport = {
892
472
  send(message) {
893
- const payload = stringifyOutgoingMessage(message);
894
- if (payload === null) return;
895
- if (!server.activated) {
896
- if (server.buffer.length >= server.bufferSize) server.buffer.shift();
897
- server.buffer.push(payload);
898
- debugLog3("buffered outgoing adapter message; no panel connected yet", {
899
- bufferedMessages: server.buffer.length,
900
- message: summarizeMessage2(message)
901
- });
902
- return;
473
+ trackLive(server, message);
474
+ const payload = JSON.stringify({ ...message, __xstateDevtools: true });
475
+ if (!server.activated && (message.type === "XSTATE_EVENT" || message.type === "XSTATE_SNAPSHOT")) {
476
+ server.recentEvents.push(payload);
477
+ if (server.recentEvents.length > server.bufferSize) server.recentEvents.shift();
903
478
  }
904
- let sentCount = 0;
905
479
  for (const ws of server.clients) {
906
480
  if (ws.readyState === OPEN_STATE) {
907
481
  try {
908
482
  ws.send(payload);
909
- sentCount += 1;
910
483
  } catch {
911
484
  }
912
485
  }
913
486
  }
914
- debugLog3("sent adapter message to connected panels", {
915
- sentCount,
916
- clientCount: server.clients.size,
917
- message: summarizeMessage2(message)
918
- });
919
487
  },
920
488
  subscribe(handler) {
921
489
  server.dispatchHandlers.add(handler);
922
- debugLog3("registered dispatch handler", { handlerCount: server.dispatchHandlers.size });
923
- return () => {
924
- server.dispatchHandlers.delete(handler);
925
- debugLog3("removed dispatch handler", { handlerCount: server.dispatchHandlers.size });
926
- };
490
+ return () => server.dispatchHandlers.delete(handler);
927
491
  }
928
492
  };
929
493
  const inspector = createInspector(transport, "srv");
930
- return { ...inspector, close: server.close, port: server.port };
494
+ return { ...inspector, close: server.close };
931
495
  }
932
496
  // Annotate the CommonJS export names for ESM import in node:
933
497
  0 && (module.exports = {