@statelyai/inspect
Advanced tools
Comparing version 0.3.1 to 0.4.0
@@ -1,2 +0,2 @@ | ||
import { InspectionEvent, Snapshot, AnyActorRef, AnyEventObject, Observer, Subscribable } from 'xstate'; | ||
import { InspectionEvent, Snapshot, AnyActorRef, AnyEventObject, Observer, MachineContext, Subscribable } from 'xstate'; | ||
@@ -95,2 +95,17 @@ interface StatelyBaseInspectionEvent { | ||
autoStart?: boolean; | ||
/** | ||
* The maximum number of deferred events to hold in memory until the inspector is active. | ||
* If the number of deferred events exceeds this number, the oldest events will be dropped. | ||
* | ||
* @default 200 | ||
*/ | ||
maxDeferredEvents?: number; | ||
/** | ||
* Sanitizes events sent to actors. Only the sanitized event will be sent to the inspector. | ||
*/ | ||
sanitizeEvent?: (event: AnyEventObject) => AnyEventObject; | ||
/** | ||
* Sanitizes actor snapshot context. Only the sanitized context will be sent to the inspector. | ||
*/ | ||
sanitizeContext?: (context: MachineContext) => MachineContext; | ||
} | ||
@@ -97,0 +112,0 @@ declare function createInspector<TAdapter extends Adapter>(adapter: TAdapter, options?: InspectorOptions): Inspector<TAdapter>; |
@@ -59,13 +59,13 @@ "use strict"; | ||
"@changesets/changelog-github": "^0.5.0", | ||
"@changesets/cli": "^2.27.1", | ||
"@types/jsdom": "^21.1.6", | ||
"@changesets/cli": "^2.27.7", | ||
"@types/jsdom": "^21.1.7", | ||
"@types/uuid": "^9.0.8", | ||
jsdom: "^23.2.0", | ||
tsup: "^8.0.2", | ||
typescript: "^5.3.3", | ||
vitest: "^1.3.1", | ||
xstate: "^5.9.1" | ||
tsup: "^8.1.0", | ||
typescript: "^5.5.3", | ||
vitest: "^1.6.0", | ||
xstate: "^5.14.0" | ||
}, | ||
name: "@statelyai/inspect", | ||
version: "0.3.1", | ||
version: "0.4.0", | ||
description: "Inspection utilities for state, actors, workflows, and state machines.", | ||
@@ -129,10 +129,29 @@ main: "dist/index.js", | ||
serialize: (event) => event, | ||
autoStart: true | ||
autoStart: true, | ||
maxDeferredEvents: 200, | ||
sanitizeEvent: (event) => event, | ||
sanitizeContext: (context) => context | ||
}; | ||
function createInspector(adapter, options) { | ||
function sendAdapter(event) { | ||
if (options?.filter && !options.filter(event)) { | ||
function sendAdapter(inspectionEvent) { | ||
if (options?.filter && !options.filter(inspectionEvent)) { | ||
return; | ||
} | ||
const serializedEvent = options?.serialize?.(event) ?? event; | ||
const sanitizedEvent = options?.sanitizeContext || options?.sanitizeEvent ? inspectionEvent : { | ||
...inspectionEvent | ||
}; | ||
if (options?.sanitizeContext && (sanitizedEvent.type === "@xstate.actor" || sanitizedEvent.type === "@xstate.snapshot")) { | ||
sanitizedEvent.snapshot = { | ||
...sanitizedEvent.snapshot, | ||
// @ts-ignore | ||
context: options.sanitizeContext( | ||
// @ts-ignore | ||
sanitizedEvent.snapshot.context | ||
) | ||
}; | ||
} | ||
if (options?.sanitizeEvent && (sanitizedEvent.type === "@xstate.event" || sanitizedEvent.type === "@xstate.snapshot")) { | ||
sanitizedEvent.event = options.sanitizeEvent(sanitizedEvent.event); | ||
} | ||
const serializedEvent = options?.serialize?.(sanitizedEvent) ?? sanitizedEvent; | ||
adapter.send(serializedEvent); | ||
@@ -306,2 +325,3 @@ } | ||
const resolvedOptions = { | ||
...defaultInspectorOptions, | ||
url: "https://stately.ai/inspect", | ||
@@ -402,2 +422,5 @@ filter: () => true, | ||
this.deferredEvents.push(event); | ||
if (this.deferredEvents.length > this.options.maxDeferredEvents) { | ||
this.deferredEvents.shift(); | ||
} | ||
} | ||
@@ -462,2 +485,3 @@ }; | ||
this.options = { | ||
...defaultInspectorOptions, | ||
filter: () => true, | ||
@@ -509,2 +533,5 @@ serialize: (inspectionEvent) => JSON.parse((0, import_safe_stable_stringify2.default)(inspectionEvent)), | ||
this.deferredEvents.push(inspectionEvent); | ||
if (this.deferredEvents.length > this.options.maxDeferredEvents) { | ||
this.deferredEvents.shift(); | ||
} | ||
} | ||
@@ -511,0 +538,0 @@ } |
{ | ||
"devDependencies": { | ||
"@changesets/changelog-github": "^0.5.0", | ||
"@changesets/cli": "^2.27.1", | ||
"@types/jsdom": "^21.1.6", | ||
"@changesets/cli": "^2.27.7", | ||
"@types/jsdom": "^21.1.7", | ||
"@types/uuid": "^9.0.8", | ||
"jsdom": "^23.2.0", | ||
"tsup": "^8.0.2", | ||
"typescript": "^5.3.3", | ||
"vitest": "^1.3.1", | ||
"xstate": "^5.9.1" | ||
"tsup": "^8.1.0", | ||
"typescript": "^5.5.3", | ||
"vitest": "^1.6.0", | ||
"xstate": "^5.14.0" | ||
}, | ||
"name": "@statelyai/inspect", | ||
"version": "0.3.1", | ||
"version": "0.4.0", | ||
"description": "Inspection utilities for state, actors, workflows, and state machines.", | ||
@@ -31,6 +31,2 @@ "main": "dist/index.js", | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"packageManager": "pnpm@8.11.0", | ||
"scripts": { | ||
@@ -40,2 +36,3 @@ "build": "tsup src/index.ts --dts", | ||
"test": "vitest", | ||
"prepublishOnly": "tsup src/index.ts --dts", | ||
"changeset": "changeset", | ||
@@ -45,3 +42,7 @@ "release": "changeset publish", | ||
"dev": "yarn build && ./scripts/dev.sh" | ||
} | ||
} | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"packageManager": "pnpm@8.11.0" | ||
} |
import safeStringify from 'fast-safe-stringify'; | ||
import { AnyEventObject, Observer, Subscribable, toObserver } from 'xstate'; | ||
import { InspectorOptions, createInspector } from './createInspector'; | ||
import { | ||
InspectorOptions, | ||
createInspector, | ||
defaultInspectorOptions, | ||
} from './createInspector'; | ||
import { Adapter, Inspector, StatelyInspectionEvent } from './types'; | ||
@@ -57,2 +61,3 @@ import { UselessAdapter } from './useless'; | ||
const resolvedOptions = { | ||
...defaultInspectorOptions, | ||
url: 'https://stately.ai/inspect', | ||
@@ -193,3 +198,8 @@ filter: () => true, | ||
this.deferredEvents.push(event); | ||
// Remove the oldest event if we've reached the max deferred events | ||
if (this.deferredEvents.length > this.options.maxDeferredEvents) { | ||
this.deferredEvents.shift(); | ||
} | ||
} | ||
} |
@@ -263,2 +263,65 @@ import { expect, test } from 'vitest'; | ||
test('Sanitization options', async () => { | ||
const events: StatelyInspectionEvent[] = []; | ||
const testAdapter: Adapter = { | ||
send: (event) => { | ||
events.push(event); | ||
}, | ||
start: () => {}, | ||
stop: () => {}, | ||
}; | ||
const inspector = createInspector(testAdapter, { | ||
sanitizeContext: (ctx) => ({ | ||
...ctx, | ||
user: 'anonymous', | ||
}), | ||
sanitizeEvent: (ev) => { | ||
if ('user' in ev) { | ||
return { ...ev, user: 'anonymous' }; | ||
} else { | ||
return ev; | ||
} | ||
}, | ||
}); | ||
inspector.actor('test', { context: { user: 'David' } }); | ||
expect((events[0] as StatelyActorEvent).snapshot.context).toEqual({ | ||
user: 'anonymous', | ||
}); | ||
inspector.snapshot('test', { context: { user: 'David' } }); | ||
expect((events[1] as StatelyActorEvent).snapshot.context).toEqual({ | ||
user: 'anonymous', | ||
}); | ||
inspector.event('test', { type: 'updateUser', user: 'David' }); | ||
expect((events[2] as StatelyEventEvent).event).toEqual({ | ||
type: 'updateUser', | ||
user: 'anonymous', | ||
}); | ||
inspector.inspect.next?.({ | ||
type: '@xstate.event', | ||
actorRef: {} as any, | ||
event: { | ||
type: 'setUser', | ||
user: 'Another', | ||
}, | ||
rootId: '', | ||
sourceRef: undefined, | ||
}); | ||
await new Promise<void>((res) => { | ||
setTimeout(res, 10); | ||
}); | ||
expect((events[3] as StatelyEventEvent).event).toEqual({ | ||
type: 'setUser', | ||
user: 'anonymous', | ||
}); | ||
}); | ||
test('it safely stringifies objects with circular dependencies', () => { | ||
@@ -265,0 +328,0 @@ const events: StatelyInspectionEvent[] = []; |
@@ -10,3 +10,9 @@ import { | ||
import { Inspector } from './types'; | ||
import { AnyActorRef, InspectionEvent, Snapshot } from 'xstate'; | ||
import { | ||
AnyActorRef, | ||
AnyEventObject, | ||
InspectionEvent, | ||
MachineContext, | ||
Snapshot, | ||
} from 'xstate'; | ||
import pkg from '../package.json'; | ||
@@ -44,2 +50,19 @@ import { idleCallback } from './idleCallback'; | ||
autoStart?: boolean; | ||
/** | ||
* The maximum number of deferred events to hold in memory until the inspector is active. | ||
* If the number of deferred events exceeds this number, the oldest events will be dropped. | ||
* | ||
* @default 200 | ||
*/ | ||
maxDeferredEvents?: number; | ||
/** | ||
* Sanitizes events sent to actors. Only the sanitized event will be sent to the inspector. | ||
*/ | ||
sanitizeEvent?: (event: AnyEventObject) => AnyEventObject; | ||
/** | ||
* Sanitizes actor snapshot context. Only the sanitized context will be sent to the inspector. | ||
*/ | ||
sanitizeContext?: (context: MachineContext) => MachineContext; | ||
} | ||
@@ -51,2 +74,5 @@ | ||
autoStart: true, | ||
maxDeferredEvents: 200, | ||
sanitizeEvent: (event) => event, | ||
sanitizeContext: (context) => context, | ||
}; | ||
@@ -58,11 +84,38 @@ | ||
): Inspector<TAdapter> { | ||
function sendAdapter(event: StatelyInspectionEvent): void { | ||
if (options?.filter && !options.filter(event)) { | ||
function sendAdapter(inspectionEvent: StatelyInspectionEvent): void { | ||
if (options?.filter && !options.filter(inspectionEvent)) { | ||
// Event filtered out | ||
return; | ||
} | ||
const serializedEvent = options?.serialize?.(event) ?? event; | ||
// idleCallback(() => { | ||
const sanitizedEvent: typeof inspectionEvent = | ||
options?.sanitizeContext || options?.sanitizeEvent | ||
? inspectionEvent | ||
: { | ||
...inspectionEvent, | ||
}; | ||
if ( | ||
options?.sanitizeContext && | ||
(sanitizedEvent.type === '@xstate.actor' || | ||
sanitizedEvent.type === '@xstate.snapshot') | ||
) { | ||
sanitizedEvent.snapshot = { | ||
...sanitizedEvent.snapshot, | ||
// @ts-ignore | ||
context: options.sanitizeContext( | ||
// @ts-ignore | ||
sanitizedEvent.snapshot.context | ||
), | ||
}; | ||
} | ||
if ( | ||
options?.sanitizeEvent && | ||
(sanitizedEvent.type === '@xstate.event' || | ||
sanitizedEvent.type === '@xstate.snapshot') | ||
) { | ||
sanitizedEvent.event = options.sanitizeEvent(sanitizedEvent.event); | ||
} | ||
const serializedEvent = | ||
options?.serialize?.(sanitizedEvent) ?? sanitizedEvent; | ||
adapter.send(serializedEvent); | ||
// }) | ||
} | ||
@@ -69,0 +122,0 @@ const inspector: Inspector<TAdapter> = { |
@@ -23,2 +23,3 @@ import { | ||
this.options = { | ||
...defaultInspectorOptions, | ||
filter: () => true, | ||
@@ -78,2 +79,6 @@ serialize: (inspectionEvent) => | ||
this.deferredEvents.push(inspectionEvent); | ||
if (this.deferredEvents.length > this.options.maxDeferredEvents) { | ||
this.deferredEvents.shift(); | ||
} | ||
} | ||
@@ -80,0 +85,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
81623
26
2026