@xstate-devtools/adapter 0.1.3 → 0.1.4

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/src/index.ts DELETED
@@ -1,73 +0,0 @@
1
- // Browser entrypoint — uses window.postMessage via the extension's injected bridge.
2
- import type {
3
- ExtensionToPageMessage,
4
- PageToExtensionMessage,
5
- } from '../../extension/src/shared/types.js'
6
- import { createInspector, type InspectorOptions, type Transport } from './core.js'
7
- import { debugLog, infoLog, warnLog } from './logging.js'
8
-
9
- declare global {
10
- interface Window {
11
- __XSTATE_DEVTOOLS__?: {
12
- send: (message: unknown) => void
13
- }
14
- }
15
- }
16
-
17
- export function createAdapter(options: InspectorOptions = {}) {
18
- if (typeof window === 'undefined') {
19
- // Non-browser env (SSR/SSG/server) — return a no-op so importing this module is safe.
20
- infoLog('web:adapter', 'createAdapter called without window; returning no-op adapter')
21
- return { inspect: () => {}, dispose: () => {} }
22
- }
23
-
24
- infoLog('web:adapter', 'creating browser adapter', {
25
- hookInstalled: Boolean(window.__XSTATE_DEVTOOLS__),
26
- })
27
-
28
- let warnedMissingHook = false
29
-
30
- const transport: Transport = {
31
- send(message: PageToExtensionMessage) {
32
- const payload = { ...message, __xstateDevtools: true as const }
33
- debugLog('web:adapter', 'sending message via page hook', {
34
- type: message.type,
35
- sessionId: 'sessionId' in message ? message.sessionId : undefined,
36
- })
37
- if (window.__XSTATE_DEVTOOLS__) {
38
- window.__XSTATE_DEVTOOLS__.send(payload)
39
- return
40
- }
41
-
42
- if (!warnedMissingHook) {
43
- warnedMissingHook = true
44
- warnLog('web:adapter', 'page hook missing; using direct window.postMessage fallback')
45
- }
46
- // Fallback keeps inspection working if MAIN-world injection is unavailable.
47
- window.postMessage(payload, '*')
48
- },
49
- subscribe(handler) {
50
- infoLog('web:adapter', 'subscribing to window messages')
51
- const onMessage = (evt: MessageEvent) => {
52
- if (evt.source !== window) return
53
- const data = evt.data
54
- if (!data?.__xstateDevtools) return
55
- debugLog('web:adapter', 'received message from window bridge', {
56
- type: (data as ExtensionToPageMessage).type,
57
- sessionId:
58
- 'sessionId' in (data as ExtensionToPageMessage)
59
- ? (data as ExtensionToPageMessage & { sessionId?: string }).sessionId
60
- : undefined,
61
- })
62
- handler(data as ExtensionToPageMessage)
63
- }
64
- window.addEventListener('message', onMessage)
65
- return () => {
66
- infoLog('web:adapter', 'unsubscribing from window messages')
67
- window.removeEventListener('message', onMessage)
68
- }
69
- },
70
- }
71
-
72
- return createInspector(transport, 'web', options)
73
- }
@@ -1,39 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest'
2
- import { debugLog, infoLog, isLoggingEnabled, warnLog } from './logging.js'
3
-
4
- describe('adapter logging', () => {
5
- afterEach(() => {
6
- delete globalThis.__XSTATE_DEVTOOLS_LOGGING__
7
- vi.unstubAllEnvs()
8
- vi.restoreAllMocks()
9
- })
10
-
11
- it('is disabled by default', () => {
12
- const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {})
13
- const infoSpy = vi.spyOn(console, 'info').mockImplementation(() => {})
14
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
15
-
16
- expect(isLoggingEnabled()).toBe(false)
17
-
18
- debugLog('web:adapter', 'debug message')
19
- infoLog('web:adapter', 'info message')
20
- warnLog('web:adapter', 'warn message')
21
-
22
- expect(debugSpy).not.toHaveBeenCalled()
23
- expect(infoSpy).not.toHaveBeenCalled()
24
- expect(warnSpy).not.toHaveBeenCalled()
25
- })
26
-
27
- it('can be enabled explicitly', () => {
28
- vi.stubEnv('XSTATE_DEVTOOLS_LOGGING', 'true')
29
- const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {})
30
-
31
- expect(isLoggingEnabled()).toBe(true)
32
-
33
- debugLog('web:adapter', 'debug message', { enabled: true })
34
-
35
- expect(debugSpy).toHaveBeenCalledWith('[xstate-devtools:web:adapter] debug message', {
36
- enabled: true,
37
- })
38
- })
39
- })
package/src/logging.ts DELETED
@@ -1,40 +0,0 @@
1
- type LogLevel = 'debug' | 'info' | 'warn'
2
-
3
- declare global {
4
- var __XSTATE_DEVTOOLS_LOGGING__: boolean | undefined
5
- }
6
-
7
- function hasProcessEnv() {
8
- return typeof process !== 'undefined' && typeof process.env !== 'undefined'
9
- }
10
-
11
- export function isLoggingEnabled() {
12
- if (globalThis.__XSTATE_DEVTOOLS_LOGGING__ === true) return true
13
- if (!hasProcessEnv()) return false
14
-
15
- const value = process.env.XSTATE_DEVTOOLS_LOGGING
16
- return value === '1' || value === 'true'
17
- }
18
-
19
- function log(level: LogLevel, scope: string, message: string, details?: unknown) {
20
- if (!isLoggingEnabled()) return
21
-
22
- if (details === undefined) {
23
- console[level](`[xstate-devtools:${scope}] ${message}`)
24
- return
25
- }
26
-
27
- console[level](`[xstate-devtools:${scope}] ${message}`, details)
28
- }
29
-
30
- export function debugLog(scope: string, message: string, details?: unknown) {
31
- log('debug', scope, message, details)
32
- }
33
-
34
- export function infoLog(scope: string, message: string, details?: unknown) {
35
- log('info', scope, message, details)
36
- }
37
-
38
- export function warnLog(scope: string, message: string, details?: unknown) {
39
- log('warn', scope, message, details)
40
- }
package/src/react.tsx DELETED
@@ -1,50 +0,0 @@
1
- // packages/adapter/src/react.tsx
2
-
3
- import { useActorRef as useXStateActorRef, useMachine as useXStateMachine } from '@xstate/react'
4
- import { createContext, type ReactNode, useContext, useEffect, useRef } from 'react'
5
- import type { ActorOptions, AnyStateMachine } from 'xstate'
6
- import { createAdapter } from './index.js'
7
-
8
- type AdapterContext = ReturnType<typeof createAdapter> | null
9
-
10
- const InspectorContext = createContext<AdapterContext>(null)
11
-
12
- export function InspectorProvider({ children }: { children: ReactNode }) {
13
- const adapterRef = useRef<ReturnType<typeof createAdapter> | null>(null)
14
- if (!adapterRef.current && typeof window !== 'undefined') {
15
- adapterRef.current = createAdapter()
16
- }
17
-
18
- useEffect(() => {
19
- return () => {
20
- adapterRef.current?.dispose()
21
- adapterRef.current = null
22
- }
23
- }, [])
24
-
25
- return (
26
- <InspectorContext.Provider value={adapterRef.current}>{children}</InspectorContext.Provider>
27
- )
28
- }
29
-
30
- export function useInspectedMachine<T extends AnyStateMachine>(
31
- machine: T,
32
- options?: ActorOptions<T>,
33
- ) {
34
- const adapter = useContext(InspectorContext)
35
- return useXStateMachine(machine, {
36
- ...options,
37
- inspect: adapter?.inspect,
38
- })
39
- }
40
-
41
- export function useInspectedActorRef<T extends AnyStateMachine>(
42
- machine: T,
43
- options?: ActorOptions<T>,
44
- ) {
45
- const adapter = useContext(InspectorContext)
46
- return useXStateActorRef(machine, {
47
- ...options,
48
- inspect: adapter?.inspect,
49
- })
50
- }
@@ -1,68 +0,0 @@
1
- // packages/adapter/src/sanitize.test.ts
2
- import { describe, expect, it } from 'vitest'
3
- import { sanitize } from './sanitize.js'
4
-
5
- describe('sanitize', () => {
6
- it('passes primitives through unchanged', () => {
7
- expect(sanitize(42)).toBe(42)
8
- expect(sanitize(true)).toBe(true)
9
- expect(sanitize(null)).toBe(null)
10
- expect(sanitize('hello')).toBe('hello')
11
- })
12
-
13
- it('replaces functions with a descriptor string', () => {
14
- expect(sanitize(function myFn() {})).toBe('[Function: myFn]')
15
- expect(sanitize(() => {})).toBe('[Function: (anonymous)]')
16
- })
17
-
18
- it('truncates long strings', () => {
19
- const long = 'x'.repeat(600)
20
- const result = sanitize(long) as string
21
- expect(result.length).toBeLessThan(520)
22
- expect(result.endsWith('…')).toBe(true)
23
- })
24
-
25
- it('handles nested objects', () => {
26
- const result = sanitize({ a: 1, b: { c: 'hello' } })
27
- expect(result).toEqual({ a: 1, b: { c: 'hello' } })
28
- })
29
-
30
- it('handles Maps', () => {
31
- const m = new Map([['key', 'value']])
32
- const result = sanitize(m) as any
33
- expect(result.__type).toBe('Map')
34
- expect(result.entries).toEqual([['key', 'value']])
35
- })
36
-
37
- it('handles actual circular references', () => {
38
- const a: any = {}
39
- a.self = a
40
- expect(() => sanitize(a)).not.toThrow()
41
- expect(JSON.stringify(sanitize(a))).toContain('[Circular]')
42
- })
43
-
44
- it('handles deep linear nesting', () => {
45
- const deep: any = {}
46
- let curr = deep
47
- for (let i = 0; i < 15; i++) {
48
- curr.child = {}
49
- curr = curr.child
50
- }
51
- curr.value = 'bottom'
52
- const result = JSON.stringify(sanitize(deep))
53
- expect(result).toContain('[MaxDepth]')
54
- })
55
-
56
- it('does not duplicate shared objects across branches', () => {
57
- const shared = { nested: { value: 'shared' } }
58
- const value = {
59
- first: shared,
60
- second: shared,
61
- }
62
-
63
- const result = sanitize(value) as Record<string, unknown>
64
-
65
- expect(result.first).toEqual({ nested: { value: 'shared' } })
66
- expect(result.second).toBe('[Circular]')
67
- })
68
- })
package/src/sanitize.ts DELETED
@@ -1,70 +0,0 @@
1
- // packages/adapter/src/sanitize.ts
2
-
3
- const MAX_DEPTH = 10
4
- const MAX_STRING_LENGTH = 500
5
- const MAX_ARRAY_LENGTH = 100
6
-
7
- function sanitizeValue(value: unknown, depth: number, seen: WeakSet<object>): unknown {
8
- if (depth > MAX_DEPTH) return '[MaxDepth]'
9
- if (value === null || value === undefined) return value
10
- if (typeof value === 'boolean' || typeof value === 'number') return value
11
- if (typeof value === 'string') {
12
- return value.length > MAX_STRING_LENGTH ? `${value.slice(0, MAX_STRING_LENGTH)}…` : value
13
- }
14
- if (typeof value === 'function') return `[Function: ${value.name || '(anonymous)'}]`
15
- if (typeof value === 'symbol') return `[Symbol: ${value.description ?? ''}]`
16
- if (typeof value === 'bigint') return `[BigInt: ${value}]`
17
- if (value instanceof Error) return { __type: 'Error', name: value.name, message: value.message }
18
- if (value instanceof Date) return { __type: 'Date', iso: value.toISOString() }
19
- if (value instanceof RegExp) return { __type: 'RegExp', source: value.source, flags: value.flags }
20
- if (typeof value === 'object') {
21
- if (seen.has(value)) return '[Circular]'
22
- seen.add(value)
23
- }
24
- if (value instanceof Map) {
25
- const entries: [unknown, unknown][] = []
26
- for (const [k, v] of value as Map<unknown, unknown>) {
27
- if (entries.length >= MAX_ARRAY_LENGTH) break
28
- entries.push([sanitizeValue(k, depth + 1, seen), sanitizeValue(v, depth + 1, seen)])
29
- }
30
- return { __type: 'Map', entries }
31
- }
32
- if (value instanceof Set) {
33
- const values: unknown[] = []
34
- for (const v of value as Set<unknown>) {
35
- if (values.length >= MAX_ARRAY_LENGTH) break
36
- values.push(sanitizeValue(v, depth + 1, seen))
37
- }
38
- return { __type: 'Set', values }
39
- }
40
- if (value instanceof Promise) return '[Promise]'
41
- if (value instanceof WeakMap || value instanceof WeakSet) return '[WeakCollection]'
42
- if (ArrayBuffer.isView(value)) return `[TypedArray: ${(value as any).constructor.name}]`
43
- // Detect DOM nodes (works in browser and is safe to check)
44
- if (typeof Node !== 'undefined' && value instanceof Node) {
45
- return `[DOMNode: ${(value as Element).tagName ?? value.nodeName}]`
46
- }
47
- if (Array.isArray(value)) {
48
- const sliced = value.slice(0, MAX_ARRAY_LENGTH)
49
- const result = sliced.map((v) => sanitizeValue(v, depth + 1, seen))
50
- if (value.length > MAX_ARRAY_LENGTH) result.push(`[…${value.length - MAX_ARRAY_LENGTH} more]`)
51
- return result
52
- }
53
- if (typeof value === 'object') {
54
- const result: Record<string, unknown> = {}
55
- let count = 0
56
- for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
57
- if (count++ >= MAX_ARRAY_LENGTH) {
58
- result['…'] = '[truncated]'
59
- break
60
- }
61
- result[k] = sanitizeValue(v, depth + 1, seen)
62
- }
63
- return result
64
- }
65
- return String(value)
66
- }
67
-
68
- export function sanitize(value: unknown, depth = 0): unknown {
69
- return sanitizeValue(value, depth, new WeakSet<object>())
70
- }
@@ -1,170 +0,0 @@
1
- // packages/adapter/src/serialize.test.ts
2
- import { describe, expect, it } from 'vitest'
3
- import { createMachine, fromPromise, setup } from 'xstate'
4
- import { serializeMachine } from './serialize.js'
5
-
6
- describe('serializeMachine', () => {
7
- it('serializes a simple compound machine', () => {
8
- const machine = createMachine({
9
- id: 'test',
10
- initial: 'idle',
11
- states: {
12
- idle: { on: { START: 'running' } },
13
- running: { on: { STOP: 'idle' } },
14
- },
15
- })
16
- const result = serializeMachine(machine)
17
- expect(result.id).toBe('test')
18
- expect(result.root.type).toBe('compound')
19
- expect(result.root.initial).toBe('idle')
20
- expect(Object.keys(result.root.states)).toEqual(['idle', 'running'])
21
- expect(result.root.states.idle.on).toHaveLength(1)
22
- expect(result.root.states.idle.on[0].eventType).toBe('START')
23
- expect(result.root.states.idle.on[0].targets).toEqual(['test.running'])
24
- })
25
-
26
- it('serializes parallel states', () => {
27
- const machine = createMachine({
28
- id: 'parallel',
29
- type: 'parallel',
30
- states: {
31
- a: { initial: 'on', states: { on: {}, off: {} } },
32
- b: { initial: 'on', states: { on: {}, off: {} } },
33
- },
34
- })
35
- const result = serializeMachine(machine)
36
- expect(result.root.type).toBe('parallel')
37
- expect(Object.keys(result.root.states)).toEqual(['a', 'b'])
38
- })
39
-
40
- it('serializes named guards and actions from setup()', () => {
41
- const machine = setup({
42
- guards: { isReady: () => true },
43
- actions: { doSomething: () => {} },
44
- }).createMachine({
45
- id: 'guarded',
46
- initial: 'idle',
47
- states: {
48
- idle: {
49
- on: {
50
- GO: {
51
- target: 'active',
52
- guard: 'isReady',
53
- actions: 'doSomething',
54
- },
55
- },
56
- },
57
- active: {},
58
- },
59
- })
60
- const result = serializeMachine(machine)
61
- const transition = result.root.states.idle.on[0]
62
- expect(transition.guard).toBe('isReady')
63
- expect(transition.actions).toEqual(['doSomething'])
64
- })
65
-
66
- it('serializes always transitions', () => {
67
- const machine = createMachine({
68
- id: 'always-test',
69
- initial: 'checking',
70
- states: {
71
- checking: {
72
- always: [{ target: 'done' }],
73
- },
74
- done: {},
75
- },
76
- })
77
- const result = serializeMachine(machine)
78
- expect(result.root.states.checking.always).toHaveLength(1)
79
- expect(result.root.states.checking.always[0].targets).toContain('always-test.done')
80
- })
81
-
82
- it('serializes invoked services', () => {
83
- const fetchActor = fromPromise(async () => ({ data: 'ok' }))
84
- const machine = setup({
85
- actors: { fetchData: fetchActor },
86
- }).createMachine({
87
- id: 'invoke-test',
88
- initial: 'loading',
89
- states: {
90
- loading: {
91
- invoke: {
92
- id: 'fetch',
93
- src: 'fetchData',
94
- onDone: 'done',
95
- },
96
- },
97
- done: {},
98
- },
99
- })
100
- const result = serializeMachine(machine)
101
- expect(result.root.states.loading.invoke).toHaveLength(1)
102
- expect(result.root.states.loading.invoke[0].id).toBe('fetch')
103
- expect(result.root.states.loading.invoke[0].src).toBeTruthy()
104
- })
105
-
106
- it('includes sourceLocation when provided', () => {
107
- const machine = createMachine({ id: 'm', initial: 'a', states: { a: {} } })
108
- const result = serializeMachine(machine, 'src/auth.ts:42')
109
- expect(result.sourceLocation).toBe('src/auth.ts:42')
110
- })
111
-
112
- it('handles cyclic machine node graphs without recursing forever', () => {
113
- const root: any = {
114
- id: 'root',
115
- key: 'root',
116
- type: 'compound',
117
- states: {},
118
- transitions: new Map(),
119
- always: [],
120
- entry: [],
121
- exit: [],
122
- invoke: [],
123
- }
124
- root.states.self = root
125
-
126
- const result = serializeMachine({ id: 'cyclic', root } as any)
127
-
128
- expect(result.root.id).toBe('root')
129
- expect(result.root.states.self.id).toBe('root')
130
- expect(result.root.states.self.states).toEqual({})
131
- })
132
-
133
- it('caps the number of serialized child states', () => {
134
- const states = Object.fromEntries(
135
- Array.from({ length: 150 }, (_, index) => [
136
- `state${index}`,
137
- {
138
- id: `machine.state${index}`,
139
- key: `state${index}`,
140
- type: 'atomic',
141
- states: {},
142
- transitions: new Map(),
143
- always: [],
144
- entry: [],
145
- exit: [],
146
- invoke: [],
147
- },
148
- ]),
149
- )
150
-
151
- const machine = {
152
- id: 'bounded',
153
- root: {
154
- id: 'bounded',
155
- key: 'bounded',
156
- type: 'compound',
157
- states,
158
- transitions: new Map(),
159
- always: [],
160
- entry: [],
161
- exit: [],
162
- invoke: [],
163
- },
164
- } as any
165
-
166
- const result = serializeMachine(machine)
167
-
168
- expect(Object.keys(result.root.states)).toHaveLength(100)
169
- })
170
- })
package/src/serialize.ts DELETED
@@ -1,148 +0,0 @@
1
- // packages/adapter/src/serialize.ts
2
- import type { AnyStateMachine } from 'xstate'
3
- import type {
4
- SerializedInvoke,
5
- SerializedMachine,
6
- SerializedStateNode,
7
- SerializedTransition,
8
- } from '../../extension/src/shared/types.js'
9
-
10
- const MAX_SERIALIZED_NODES = 500
11
- const MAX_TRANSITIONS_PER_NODE = 100
12
- const MAX_CHILD_STATES = 100
13
- const MAX_ACTIONS_PER_TRANSITION = 20
14
- const MAX_ENTRY_EXIT_ACTIONS = 20
15
- const MAX_INVOKES_PER_NODE = 20
16
-
17
- function serializeGuard(guard: unknown): string | undefined {
18
- if (!guard) return undefined
19
- if (typeof guard === 'string') return guard
20
- if (typeof guard === 'function') return (guard as Function).name || '(inline)'
21
- if (typeof guard === 'object' && guard !== null) {
22
- const g = guard as any
23
- return g.type ?? g.name ?? '(inline)'
24
- }
25
- return '(inline)'
26
- }
27
-
28
- function serializeAction(action: unknown): string {
29
- if (typeof action === 'string') return action
30
- if (typeof action === 'function') return (action as Function).name || '(anonymous)'
31
- if (typeof action === 'object' && action !== null) {
32
- const a = action as any
33
- return a.type ?? a.name ?? String(action)
34
- }
35
- return String(action)
36
- }
37
-
38
- function serializeTransitionList(transitions: any[]): SerializedTransition[] {
39
- return transitions.slice(0, MAX_TRANSITIONS_PER_NODE).map((t: any) => ({
40
- eventType: t.eventType ?? '',
41
- targets: (t.target ?? []).map((n: any) => n?.id ?? String(n)).filter(Boolean),
42
- guard: serializeGuard(t.guard),
43
- actions: (t.actions ?? [])
44
- .slice(0, MAX_ACTIONS_PER_TRANSITION)
45
- .map(serializeAction)
46
- .filter(Boolean),
47
- }))
48
- }
49
-
50
- function serializeInvokes(node: any): SerializedInvoke[] {
51
- return (node.invoke as any[]).slice(0, MAX_INVOKES_PER_NODE).map((inv: any) => ({
52
- id: inv.id ?? '(unknown)',
53
- src: typeof inv.src === 'string' ? inv.src : (inv.src?.id ?? inv.src?.name ?? '(inline)'),
54
- }))
55
- }
56
-
57
- interface SerializeState {
58
- seen: WeakSet<object>
59
- count: number
60
- }
61
-
62
- function serializeNode(node: any, state: SerializeState): SerializedStateNode {
63
- if (!node || typeof node !== 'object') {
64
- return {
65
- id: '(unknown)',
66
- key: '(unknown)',
67
- type: 'atomic',
68
- states: {},
69
- on: [],
70
- always: [],
71
- entry: [],
72
- exit: [],
73
- invoke: [],
74
- }
75
- }
76
-
77
- if (state.count >= MAX_SERIALIZED_NODES) {
78
- return {
79
- id: node.id ?? '(truncated)',
80
- key: node.key ?? '(truncated)',
81
- type: node.type ?? 'atomic',
82
- states: {},
83
- on: [],
84
- always: [],
85
- entry: [],
86
- exit: [],
87
- invoke: [],
88
- }
89
- }
90
-
91
- if (state.seen.has(node)) {
92
- return {
93
- id: node.id ?? '(circular)',
94
- key: node.key ?? '(circular)',
95
- type: node.type ?? 'atomic',
96
- states: {},
97
- on: [],
98
- always: [],
99
- entry: [],
100
- exit: [],
101
- invoke: [],
102
- }
103
- }
104
-
105
- state.seen.add(node)
106
- state.count += 1
107
-
108
- const allTransitions: SerializedTransition[] = []
109
- if (node.transitions instanceof Map) {
110
- for (const [, tList] of node.transitions) {
111
- if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) break
112
- allTransitions.push(...serializeTransitionList(tList))
113
- if (allTransitions.length >= MAX_TRANSITIONS_PER_NODE) {
114
- allTransitions.length = MAX_TRANSITIONS_PER_NODE
115
- break
116
- }
117
- }
118
- }
119
-
120
- const always = Array.isArray(node.always) ? serializeTransitionList(node.always) : []
121
- const childEntries = Object.entries(node.states ?? {}).slice(0, MAX_CHILD_STATES)
122
-
123
- return {
124
- id: node.id,
125
- key: node.key,
126
- type: node.type,
127
- initial: node.initial?.target?.[0]?.key,
128
- states: Object.fromEntries(childEntries.map(([k, v]) => [k, serializeNode(v, state)])),
129
- on: allTransitions,
130
- always,
131
- entry: (node.entry ?? []).slice(0, MAX_ENTRY_EXIT_ACTIONS).map(serializeAction).filter(Boolean),
132
- exit: (node.exit ?? []).slice(0, MAX_ENTRY_EXIT_ACTIONS).map(serializeAction).filter(Boolean),
133
- invoke: serializeInvokes(node),
134
- sourceLocation: node.config?.__xstateDevtoolsSource ?? undefined,
135
- description: node.config?.description ?? undefined,
136
- }
137
- }
138
-
139
- export function serializeMachine(
140
- machine: AnyStateMachine,
141
- sourceLocation?: string,
142
- ): SerializedMachine {
143
- return {
144
- id: machine.id,
145
- root: serializeNode(machine.root, { seen: new WeakSet<object>(), count: 0 }),
146
- sourceLocation,
147
- }
148
- }