@statelyai/inspect
Advanced tools
Comparing version 0.0.3 to 0.0.4
@@ -1,2 +0,2 @@ | ||
import { InspectionEvent, AnyActorRef, AnyEventObject, Observer, Snapshot, Subscribable } from 'xstate'; | ||
import { InspectionEvent, Snapshot, AnyActorRef, AnyEventObject, Observer, Subscribable } from 'xstate'; | ||
@@ -21,3 +21,4 @@ interface StatelyBaseInspectionEvent { | ||
}, 'type'> & { | ||
snapshot: any; | ||
name: string; | ||
snapshot: InspectedSnapshot; | ||
definition: string | undefined; | ||
@@ -34,3 +35,3 @@ parentId: string | undefined; | ||
status: Snapshot<unknown>['status']; | ||
context: any; | ||
context?: any; | ||
} | ||
@@ -42,7 +43,9 @@ interface Inspector<TAdapter extends Adapter> { | ||
*/ | ||
snapshot: (actor: AnyActorRef | string, snapshot: InspectedSnapshot) => void; | ||
snapshot: (actor: AnyActorRef | string, snapshot: InspectedSnapshot, info?: { | ||
event?: AnyEventObject; | ||
}) => void; | ||
/** | ||
* Sends an event inspection event. This represents the event that was sent to the actor. | ||
*/ | ||
event: (targetActor: AnyActorRef | string, event: AnyEventObject | string, { source, }: { | ||
event: (targetActor: AnyActorRef | string, event: AnyEventObject | string, info?: { | ||
source?: string; | ||
@@ -53,3 +56,3 @@ }) => void; | ||
*/ | ||
actor: (actor: AnyActorRef | string, snapshot: any, info?: { | ||
actor: (actor: AnyActorRef | string, snapshot?: InspectedSnapshot, info?: { | ||
definition?: string; | ||
@@ -76,2 +79,3 @@ parentId?: string; | ||
* }) | ||
* ``` | ||
*/ | ||
@@ -83,2 +87,3 @@ inspect: Observer<InspectionEvent>; | ||
filter?: (event: StatelyInspectionEvent) => boolean; | ||
serialize?: (event: StatelyInspectionEvent) => StatelyInspectionEvent; | ||
} | ||
@@ -115,2 +120,6 @@ declare function createInspector<TAdapter extends Adapter>(adapter: TAdapter): Inspector<TAdapter>; | ||
} | ||
/** | ||
* Creates a browser-based inspector that sends events to a remote inspector window. | ||
* The remote inspector opens an inspector window at the specified URL by default. | ||
*/ | ||
declare function createBrowserInspector(options?: BrowserInspectorOptions): Inspector<BrowserAdapter>; | ||
@@ -117,0 +126,0 @@ interface BrowserReceiverOptions { |
@@ -8,2 +8,5 @@ "use strict"; | ||
var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
var __commonJS = (cb, mod) => function __require() { | ||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; | ||
}; | ||
var __export = (target, all) => { | ||
@@ -31,2 +34,197 @@ for (var name in all) | ||
// node_modules/.pnpm/registry.npmjs.org+fast-safe-stringify@2.1.1/node_modules/fast-safe-stringify/index.js | ||
var require_fast_safe_stringify = __commonJS({ | ||
"node_modules/.pnpm/registry.npmjs.org+fast-safe-stringify@2.1.1/node_modules/fast-safe-stringify/index.js"(exports, module2) { | ||
"use strict"; | ||
module2.exports = stringify; | ||
stringify.default = stringify; | ||
stringify.stable = deterministicStringify; | ||
stringify.stableStringify = deterministicStringify; | ||
var LIMIT_REPLACE_NODE = "[...]"; | ||
var CIRCULAR_REPLACE_NODE = "[Circular]"; | ||
var arr = []; | ||
var replacerStack = []; | ||
function defaultOptions() { | ||
return { | ||
depthLimit: Number.MAX_SAFE_INTEGER, | ||
edgesLimit: Number.MAX_SAFE_INTEGER | ||
}; | ||
} | ||
function stringify(obj, replacer, spacer, options) { | ||
if (typeof options === "undefined") { | ||
options = defaultOptions(); | ||
} | ||
decirc(obj, "", 0, [], void 0, 0, options); | ||
var res; | ||
try { | ||
if (replacerStack.length === 0) { | ||
res = JSON.stringify(obj, replacer, spacer); | ||
} else { | ||
res = JSON.stringify(obj, replaceGetterValues(replacer), spacer); | ||
} | ||
} catch (_) { | ||
return JSON.stringify("[unable to serialize, circular reference is too complex to analyze]"); | ||
} finally { | ||
while (arr.length !== 0) { | ||
var part = arr.pop(); | ||
if (part.length === 4) { | ||
Object.defineProperty(part[0], part[1], part[3]); | ||
} else { | ||
part[0][part[1]] = part[2]; | ||
} | ||
} | ||
} | ||
return res; | ||
} | ||
function setReplace(replace, val, k, parent) { | ||
var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k); | ||
if (propertyDescriptor.get !== void 0) { | ||
if (propertyDescriptor.configurable) { | ||
Object.defineProperty(parent, k, { value: replace }); | ||
arr.push([parent, k, val, propertyDescriptor]); | ||
} else { | ||
replacerStack.push([val, k, replace]); | ||
} | ||
} else { | ||
parent[k] = replace; | ||
arr.push([parent, k, val]); | ||
} | ||
} | ||
function decirc(val, k, edgeIndex, stack, parent, depth, options) { | ||
depth += 1; | ||
var i; | ||
if (typeof val === "object" && val !== null) { | ||
for (i = 0; i < stack.length; i++) { | ||
if (stack[i] === val) { | ||
setReplace(CIRCULAR_REPLACE_NODE, val, k, parent); | ||
return; | ||
} | ||
} | ||
if (typeof options.depthLimit !== "undefined" && depth > options.depthLimit) { | ||
setReplace(LIMIT_REPLACE_NODE, val, k, parent); | ||
return; | ||
} | ||
if (typeof options.edgesLimit !== "undefined" && edgeIndex + 1 > options.edgesLimit) { | ||
setReplace(LIMIT_REPLACE_NODE, val, k, parent); | ||
return; | ||
} | ||
stack.push(val); | ||
if (Array.isArray(val)) { | ||
for (i = 0; i < val.length; i++) { | ||
decirc(val[i], i, i, stack, val, depth, options); | ||
} | ||
} else { | ||
var keys = Object.keys(val); | ||
for (i = 0; i < keys.length; i++) { | ||
var key = keys[i]; | ||
decirc(val[key], key, i, stack, val, depth, options); | ||
} | ||
} | ||
stack.pop(); | ||
} | ||
} | ||
function compareFunction(a, b) { | ||
if (a < b) { | ||
return -1; | ||
} | ||
if (a > b) { | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
function deterministicStringify(obj, replacer, spacer, options) { | ||
if (typeof options === "undefined") { | ||
options = defaultOptions(); | ||
} | ||
var tmp = deterministicDecirc(obj, "", 0, [], void 0, 0, options) || obj; | ||
var res; | ||
try { | ||
if (replacerStack.length === 0) { | ||
res = JSON.stringify(tmp, replacer, spacer); | ||
} else { | ||
res = JSON.stringify(tmp, replaceGetterValues(replacer), spacer); | ||
} | ||
} catch (_) { | ||
return JSON.stringify("[unable to serialize, circular reference is too complex to analyze]"); | ||
} finally { | ||
while (arr.length !== 0) { | ||
var part = arr.pop(); | ||
if (part.length === 4) { | ||
Object.defineProperty(part[0], part[1], part[3]); | ||
} else { | ||
part[0][part[1]] = part[2]; | ||
} | ||
} | ||
} | ||
return res; | ||
} | ||
function deterministicDecirc(val, k, edgeIndex, stack, parent, depth, options) { | ||
depth += 1; | ||
var i; | ||
if (typeof val === "object" && val !== null) { | ||
for (i = 0; i < stack.length; i++) { | ||
if (stack[i] === val) { | ||
setReplace(CIRCULAR_REPLACE_NODE, val, k, parent); | ||
return; | ||
} | ||
} | ||
try { | ||
if (typeof val.toJSON === "function") { | ||
return; | ||
} | ||
} catch (_) { | ||
return; | ||
} | ||
if (typeof options.depthLimit !== "undefined" && depth > options.depthLimit) { | ||
setReplace(LIMIT_REPLACE_NODE, val, k, parent); | ||
return; | ||
} | ||
if (typeof options.edgesLimit !== "undefined" && edgeIndex + 1 > options.edgesLimit) { | ||
setReplace(LIMIT_REPLACE_NODE, val, k, parent); | ||
return; | ||
} | ||
stack.push(val); | ||
if (Array.isArray(val)) { | ||
for (i = 0; i < val.length; i++) { | ||
deterministicDecirc(val[i], i, i, stack, val, depth, options); | ||
} | ||
} else { | ||
var tmp = {}; | ||
var keys = Object.keys(val).sort(compareFunction); | ||
for (i = 0; i < keys.length; i++) { | ||
var key = keys[i]; | ||
deterministicDecirc(val[key], key, i, stack, val, depth, options); | ||
tmp[key] = val[key]; | ||
} | ||
if (typeof parent !== "undefined") { | ||
arr.push([parent, k, val]); | ||
parent[k] = tmp; | ||
} else { | ||
return tmp; | ||
} | ||
} | ||
stack.pop(); | ||
} | ||
} | ||
function replaceGetterValues(replacer) { | ||
replacer = typeof replacer !== "undefined" ? replacer : function(k, v) { | ||
return v; | ||
}; | ||
return function(key, val) { | ||
if (replacerStack.length > 0) { | ||
for (var i = 0; i < replacerStack.length; i++) { | ||
var part = replacerStack[i]; | ||
if (part[1] === key && part[0] === val) { | ||
val = part[2]; | ||
replacerStack.splice(i, 1); | ||
break; | ||
} | ||
} | ||
} | ||
return replacer.call(this, key, val); | ||
}; | ||
} | ||
} | ||
}); | ||
// src/index.ts | ||
@@ -61,3 +259,3 @@ var src_exports = {}; | ||
name: "@statelyai/inspect", | ||
version: "0.0.3", | ||
version: "0.0.4", | ||
description: "Inspection utilities for state, actors, workflows, and state machines.", | ||
@@ -69,4 +267,5 @@ main: "dist/index.js", | ||
dependencies: { | ||
"fast-safe-stringify": "^2.1.1", | ||
"isomorphic-ws": "^5.0.0", | ||
xstate: "5.0.0-beta.44" | ||
xstate: "5.0.0-beta.53" | ||
}, | ||
@@ -102,4 +301,6 @@ scripts: { | ||
const parentId = info?.parentId ?? typeof actorRef === "string" ? void 0 : actorRef._parent?.sessionId; | ||
const name = definitionObject ? definitionObject.id : sessionId; | ||
adapter.send({ | ||
type: "@xstate.actor", | ||
name, | ||
sessionId, | ||
@@ -112,10 +313,10 @@ createdAt: Date.now().toString(), | ||
definition, | ||
snapshot | ||
snapshot: snapshot ?? { status: "active" } | ||
}); | ||
}, | ||
event(target, event, { source }) { | ||
event(target, event, extra) { | ||
const sessionId = typeof target === "string" ? target : target.sessionId; | ||
adapter.send({ | ||
type: "@xstate.event", | ||
sourceId: source, | ||
sourceId: extra?.source, | ||
sessionId, | ||
@@ -129,3 +330,3 @@ event: toEventObject(event), | ||
}, | ||
snapshot(actor, snapshot) { | ||
snapshot(actor, snapshot, extra) { | ||
const sessionId = typeof actor === "string" ? actor : actor.sessionId; | ||
@@ -135,3 +336,3 @@ adapter.send({ | ||
snapshot, | ||
event: null, | ||
event: extra?.event ?? null, | ||
sessionId, | ||
@@ -167,3 +368,5 @@ id: null, | ||
}) : void 0; | ||
const name = definitionObject ? definitionObject.id : actorRef.sessionId; | ||
return { | ||
name, | ||
type: "@xstate.actor", | ||
@@ -272,2 +475,3 @@ definition: definitionString, | ||
// src/BrowserAdapter.ts | ||
var import_fast_safe_stringify = __toESM(require_fast_safe_stringify()); | ||
var BrowserAdapter = class { | ||
@@ -282,2 +486,3 @@ status = "disconnected"; | ||
filter: () => true, | ||
serialize: (event) => JSON.parse((0, import_fast_safe_stringify.default)(event)), | ||
...options | ||
@@ -299,3 +504,4 @@ }; | ||
this.deferredEvents.forEach((event2) => { | ||
this.targetWindow?.postMessage(event2, "*"); | ||
const serializedEvent = this.options.serialize(event2); | ||
this.targetWindow?.postMessage(serializedEvent, "*"); | ||
}); | ||
@@ -314,3 +520,4 @@ } | ||
if (this.status === "connected") { | ||
this.targetWindow?.postMessage(event, "*"); | ||
const serializedEvent = this.options.serialize(event); | ||
this.targetWindow?.postMessage(serializedEvent, "*"); | ||
} | ||
@@ -317,0 +524,0 @@ this.deferredEvents.push(event); |
@@ -11,3 +11,3 @@ { | ||
"name": "@statelyai/inspect", | ||
"version": "0.0.3", | ||
"version": "0.0.4", | ||
"description": "Inspection utilities for state, actors, workflows, and state machines.", | ||
@@ -19,4 +19,5 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"fast-safe-stringify": "^2.1.1", | ||
"isomorphic-ws": "^5.0.0", | ||
"xstate": "5.0.0-beta.44" | ||
"xstate": "5.0.0-beta.53" | ||
}, | ||
@@ -23,0 +24,0 @@ "scripts": { |
@@ -6,2 +6,3 @@ # Stately.ai Inspect | ||
## Features | ||
- Visualize state machines | ||
@@ -24,3 +25,3 @@ - Visualize sequence diagrams | ||
const actor = createActor(machine, { | ||
inspect: inspector.inspect | ||
inspect: inspector.inspect, | ||
}); | ||
@@ -40,11 +41,25 @@ | ||
inspector.actor('someActor', {/* initial state */}); | ||
inspector.actor('someActor', { | ||
status: 'active', | ||
context: { | ||
/* any context data */ | ||
}, | ||
}); | ||
inspector.actor('anotherActor', {/* initial state */}); | ||
inspector.actor('anotherActor'); | ||
inspector.event('someActor', { type: 'hello' }, { | ||
target: 'anotherActor' | ||
inspector.event( | ||
'someActor', | ||
{ type: 'hello' }, | ||
{ | ||
target: 'anotherActor', | ||
} | ||
); | ||
inspector.snapshot('anotherActor', { | ||
status: 'active', | ||
context: { | ||
/* any context data */ | ||
}, | ||
}); | ||
inspector.snapshot('anotherActor', {/* updated state */}); | ||
``` |
import { expect, test } from 'vitest'; | ||
import { createActor, createMachine } from 'xstate'; | ||
import { createBrowserInspector } from './browser'; | ||
import { createBrowserInspector } from './'; | ||
import { StatelyActorEvent } from './types'; | ||
@@ -5,0 +5,0 @@ import { JSDOM } from 'jsdom'; |
@@ -33,2 +33,6 @@ import { AnyEventObject, Observer, Subscribable, toObserver } from 'xstate'; | ||
/** | ||
* Creates a browser-based inspector that sends events to a remote inspector window. | ||
* The remote inspector opens an inspector window at the specified URL by default. | ||
*/ | ||
export function createBrowserInspector( | ||
@@ -35,0 +39,0 @@ options?: BrowserInspectorOptions |
import { Adapter, StatelyInspectionEvent } from './types'; | ||
import { BrowserInspectorOptions, isEventObject } from './browser'; | ||
import safeStringify from 'fast-safe-stringify'; | ||
@@ -14,2 +15,3 @@ export class BrowserAdapter implements Adapter { | ||
filter: () => true, | ||
serialize: (event) => JSON.parse(safeStringify(event)), | ||
...options, | ||
@@ -35,3 +37,4 @@ }; | ||
this.deferredEvents.forEach((event) => { | ||
this.targetWindow?.postMessage(event, '*'); | ||
const serializedEvent = this.options.serialize(event); | ||
this.targetWindow?.postMessage(serializedEvent, '*'); | ||
}); | ||
@@ -51,3 +54,4 @@ } | ||
if (this.status === 'connected') { | ||
this.targetWindow?.postMessage(event, '*'); | ||
const serializedEvent = this.options.serialize(event); | ||
this.targetWindow?.postMessage(serializedEvent, '*'); | ||
} | ||
@@ -54,0 +58,0 @@ this.deferredEvents.push(event); |
@@ -15,3 +15,3 @@ import { expect, test } from 'vitest'; | ||
sessionId: ev.sessionId, | ||
snapshot: (ev.snapshot as any).value, | ||
snapshot: 'value' in ev.snapshot ? ev.snapshot.value : ev.snapshot, | ||
}; | ||
@@ -106,2 +106,70 @@ } | ||
test('Manually inspected events', () => { | ||
const events: StatelyInspectionEvent[] = []; | ||
const testAdapter: Adapter = { | ||
send: (event) => { | ||
events.push(event); | ||
}, | ||
start: () => {}, | ||
stop: () => {}, | ||
}; | ||
const inspector = createInspector(testAdapter); | ||
inspector.actor('test'); | ||
inspector.actor('another', { status: 'active', context: 10 }); | ||
inspector.event('test', 'stringEvent'); | ||
inspector.event('another', { type: 'objectEvent' }, { source: 'test' }); | ||
inspector.snapshot('test', { status: 'active', context: 20 }); | ||
inspector.snapshot( | ||
'another', | ||
{ status: 'done', context: { foo: 'bar' } }, | ||
{ event: { type: 'objectEvent' } } | ||
); | ||
expect(events.map(simplifyEvent)).toMatchInlineSnapshot(` | ||
[ | ||
{ | ||
"sessionId": "test", | ||
"type": "@xstate.actor", | ||
}, | ||
{ | ||
"sessionId": "another", | ||
"type": "@xstate.actor", | ||
}, | ||
{ | ||
"event": { | ||
"type": "stringEvent", | ||
}, | ||
"sessionId": "test", | ||
"type": "@xstate.event", | ||
}, | ||
{ | ||
"event": { | ||
"type": "objectEvent", | ||
}, | ||
"sessionId": "another", | ||
"type": "@xstate.event", | ||
}, | ||
{ | ||
"sessionId": "test", | ||
"snapshot": { | ||
"context": 20, | ||
"status": "active", | ||
}, | ||
"type": "@xstate.snapshot", | ||
}, | ||
{ | ||
"sessionId": "another", | ||
"snapshot": { | ||
"context": { | ||
"foo": "bar", | ||
}, | ||
"status": "done", | ||
}, | ||
"type": "@xstate.snapshot", | ||
}, | ||
] | ||
`); | ||
}); | ||
test('Inspected event includes version', () => { | ||
@@ -108,0 +176,0 @@ const events: StatelyInspectionEvent[] = []; |
@@ -34,2 +34,3 @@ import { | ||
filter?: (event: StatelyInspectionEvent) => boolean; | ||
serialize?: (event: StatelyInspectionEvent) => StatelyInspectionEvent; | ||
} | ||
@@ -39,2 +40,3 @@ | ||
filter: () => true, | ||
serialize: (event) => event, | ||
}; | ||
@@ -64,5 +66,7 @@ | ||
: actorRef._parent?.sessionId; | ||
const name = definitionObject ? definitionObject.id : sessionId; | ||
adapter.send({ | ||
type: '@xstate.actor', | ||
name, | ||
sessionId, | ||
@@ -75,10 +79,10 @@ createdAt: Date.now().toString(), | ||
definition, | ||
snapshot, | ||
snapshot: snapshot ?? { status: 'active' }, | ||
} satisfies StatelyActorEvent); | ||
}, | ||
event(target, event, { source }) { | ||
event(target, event, extra) { | ||
const sessionId = typeof target === 'string' ? target : target.sessionId; | ||
adapter.send({ | ||
type: '@xstate.event', | ||
sourceId: source, | ||
sourceId: extra?.source, | ||
sessionId, | ||
@@ -92,3 +96,3 @@ event: toEventObject(event), | ||
}, | ||
snapshot(actor, snapshot) { | ||
snapshot(actor, snapshot, extra) { | ||
const sessionId = typeof actor === 'string' ? actor : actor.sessionId; | ||
@@ -98,3 +102,3 @@ adapter.send({ | ||
snapshot: snapshot as unknown as Snapshot<unknown>, | ||
event: null as any, | ||
event: extra?.event ?? (null as any), | ||
sessionId, | ||
@@ -137,4 +141,6 @@ id: null as any, | ||
: undefined; | ||
const name = definitionObject ? definitionObject.id : actorRef.sessionId; | ||
return { | ||
name, | ||
type: '@xstate.actor', | ||
@@ -141,0 +147,0 @@ definition: definitionString, |
@@ -36,3 +36,4 @@ import { | ||
> & { | ||
snapshot: any; // JSON snapshot | ||
name: string; | ||
snapshot: InspectedSnapshot; // JSON snapshot | ||
definition: string | undefined; // JSON-stringified definition or URL | ||
@@ -55,3 +56,3 @@ parentId: string | undefined; | ||
status: Snapshot<unknown>['status']; | ||
context: any; | ||
context?: any; | ||
} | ||
@@ -64,3 +65,7 @@ | ||
*/ | ||
snapshot: (actor: AnyActorRef | string, snapshot: InspectedSnapshot) => void; | ||
snapshot: ( | ||
actor: AnyActorRef | string, | ||
snapshot: InspectedSnapshot, | ||
info?: { event?: AnyEventObject } | ||
) => void; | ||
/** | ||
@@ -72,5 +77,3 @@ * Sends an event inspection event. This represents the event that was sent to the actor. | ||
event: AnyEventObject | string, | ||
{ | ||
source, | ||
}: { | ||
info?: { | ||
source?: string; | ||
@@ -108,4 +111,5 @@ } | ||
* }) | ||
* ``` | ||
*/ | ||
inspect: Observer<InspectionEvent>; | ||
} |
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
59877
1535
63
3
+ Addedfast-safe-stringify@^2.1.1
+ Addedfast-safe-stringify@2.1.1(transitive)
+ Addedxstate@5.0.0-beta.53(transitive)
- Removedxstate@5.0.0-beta.44(transitive)
Updatedxstate@5.0.0-beta.53