@statelyai/inspect
Advanced tools
Comparing version 0.0.10 to 0.0.11
@@ -33,4 +33,6 @@ import { InspectionEvent, Snapshot, AnyActorRef, AnyEventObject, Observer, Subscribable } from 'xstate'; | ||
interface InspectedSnapshot { | ||
status: Snapshot<unknown>['status']; | ||
status?: Snapshot<unknown>['status']; | ||
context?: any; | ||
value?: any; | ||
output?: any; | ||
} | ||
@@ -94,6 +96,12 @@ interface Inspector<TAdapter extends Adapter> { | ||
serialize?: (event: StatelyInspectionEvent) => StatelyInspectionEvent; | ||
/** | ||
* Whether to automatically start the inspector. | ||
* | ||
* @default true | ||
*/ | ||
autoStart?: boolean; | ||
} | ||
declare function createInspector<TAdapter extends Adapter>(adapter: TAdapter): Inspector<TAdapter>; | ||
interface WebSocketAdapterOptions extends InspectorOptions { | ||
interface WebSocketInspectorOptions extends InspectorOptions { | ||
url: string; | ||
@@ -106,3 +114,3 @@ } | ||
private options; | ||
constructor(options: WebSocketAdapterOptions); | ||
constructor(options?: WebSocketInspectorOptions); | ||
start(): void; | ||
@@ -112,14 +120,8 @@ stop(): void; | ||
} | ||
declare function createWebSocketInspector(url: string): Inspector<WebSocketAdapter>; | ||
declare class BrowserAdapter implements Adapter { | ||
private status; | ||
private deferredEvents; | ||
targetWindow: Window | null; | ||
private options; | ||
constructor(options?: BrowserInspectorOptions); | ||
start(): void; | ||
stop(): void; | ||
send(event: StatelyInspectionEvent): void; | ||
declare function createWebSocketInspector(options?: WebSocketInspectorOptions): Inspector<WebSocketAdapter>; | ||
interface WebSocketReceiver extends Subscribable<StatelyInspectionEvent> { | ||
} | ||
declare function createWebSocketReceiver(options?: { | ||
server: string; | ||
}): WebSocketReceiver; | ||
@@ -146,3 +148,13 @@ interface BrowserReceiver extends Subscribable<StatelyInspectionEvent> { | ||
declare function createBrowserReceiver(options?: BrowserReceiverOptions): BrowserReceiver; | ||
declare class BrowserAdapter implements Adapter { | ||
options: Required<BrowserInspectorOptions>; | ||
private status; | ||
private deferredEvents; | ||
targetWindow: Window | null; | ||
constructor(options: Required<BrowserInspectorOptions>); | ||
start(): void; | ||
stop(): void; | ||
send(event: StatelyInspectionEvent): void; | ||
} | ||
export { StatelyActorEvent, StatelyEventEvent, StatelyInspectionEvent, StatelySnapshotEvent, createBrowserInspector, createBrowserReceiver, createInspector, createWebSocketInspector }; | ||
export { StatelyActorEvent, StatelyEventEvent, StatelyInspectionEvent, StatelySnapshotEvent, createBrowserInspector, createBrowserReceiver, createInspector, createWebSocketInspector, createWebSocketReceiver }; |
@@ -36,3 +36,4 @@ "use strict"; | ||
createInspector: () => createInspector, | ||
createWebSocketInspector: () => createWebSocketInspector | ||
createWebSocketInspector: () => createWebSocketInspector, | ||
createWebSocketReceiver: () => createWebSocketReceiver | ||
}); | ||
@@ -60,3 +61,3 @@ module.exports = __toCommonJS(src_exports); | ||
name: "@statelyai/inspect", | ||
version: "0.0.10", | ||
version: "0.0.11", | ||
description: "Inspection utilities for state, actors, workflows, and state machines.", | ||
@@ -144,4 +145,7 @@ main: "dist/index.js", | ||
type: "@xstate.snapshot", | ||
snapshot: info.snapshot, | ||
event: info.event ?? null, | ||
snapshot: { | ||
status: "active", | ||
...info.snapshot | ||
}, | ||
event: info.event ?? { type: "" }, | ||
sessionId, | ||
@@ -235,5 +239,6 @@ id: null, | ||
// src/webSocketAdapter.ts | ||
// src/webSocket.ts | ||
var import_isomorphic_ws = __toESM(require("isomorphic-ws")); | ||
var import_fast_safe_stringify = __toESM(require("fast-safe-stringify")); | ||
var import_xstate = require("xstate"); | ||
var WebSocketAdapter = class { | ||
@@ -248,2 +253,4 @@ ws; | ||
serialize: (event) => JSON.parse((0, import_fast_safe_stringify.default)(event)), | ||
autoStart: true, | ||
url: "ws://localhost:8080", | ||
...options | ||
@@ -292,65 +299,44 @@ }; | ||
}; | ||
function createWebSocketInspector(url) { | ||
const adapter = new WebSocketAdapter({ url }); | ||
function createWebSocketInspector(options) { | ||
const adapter = new WebSocketAdapter(options); | ||
const inspector = createInspector(adapter); | ||
return inspector; | ||
} | ||
// src/browser.ts | ||
var import_xstate = require("xstate"); | ||
// src/BrowserAdapter.ts | ||
var import_fast_safe_stringify2 = __toESM(require("fast-safe-stringify")); | ||
var BrowserAdapter = class { | ||
status = "disconnected"; | ||
deferredEvents = []; | ||
targetWindow = null; | ||
options; | ||
constructor(options = {}) { | ||
const resolvedOptions = { | ||
url: "https://stately.ai/registry/inspect", | ||
filter: () => true, | ||
serialize: (event) => JSON.parse((0, import_fast_safe_stringify2.default)(event)), | ||
iframe: null, | ||
...options, | ||
window: options.window ?? window | ||
function createWebSocketReceiver(options) { | ||
const resolvedOptions = { | ||
server: "ws://localhost:8080", | ||
...options | ||
}; | ||
const observers = /* @__PURE__ */ new Set(); | ||
const ws = new import_isomorphic_ws.default(resolvedOptions.server); | ||
ws.onopen = () => { | ||
console.log("websocket open"); | ||
ws.onmessage = (event) => { | ||
if (typeof event.data !== "string") { | ||
return; | ||
} | ||
console.log("message", event.data); | ||
const eventData = JSON.parse(event.data); | ||
observers.forEach((observer) => { | ||
observer.next?.(eventData); | ||
}); | ||
}; | ||
this.options = resolvedOptions; | ||
} | ||
start() { | ||
this.targetWindow = this.options.iframe ? null : this.options.window.open(String(this.options.url), "xstateinspector"); | ||
if (this.options.iframe) { | ||
this.options.iframe.addEventListener("load", () => { | ||
this.targetWindow = this.options.iframe?.contentWindow ?? null; | ||
}); | ||
this.options.iframe?.setAttribute("src", String(this.options.url)); | ||
}; | ||
const receiver = { | ||
subscribe(observerOrFn) { | ||
const observer = (0, import_xstate.toObserver)(observerOrFn); | ||
observers.add(observer); | ||
return { | ||
unsubscribe() { | ||
observers.delete(observer); | ||
} | ||
}; | ||
} | ||
this.options.window.addEventListener("message", (event) => { | ||
if (isEventObject(event.data) && event.data.type === "@statelyai.connected") { | ||
this.status = "connected"; | ||
this.deferredEvents.forEach((event2) => { | ||
const serializedEvent = this.options.serialize(event2); | ||
this.targetWindow?.postMessage(serializedEvent, "*"); | ||
}); | ||
} | ||
}); | ||
} | ||
stop() { | ||
this.targetWindow?.postMessage({ type: "@statelyai.disconnected" }, "*"); | ||
this.status = "disconnected"; | ||
} | ||
send(event) { | ||
const shouldSendEvent = this.options.filter(event); | ||
if (!shouldSendEvent) { | ||
return; | ||
} | ||
if (this.status === "connected") { | ||
const serializedEvent = this.options.serialize(event); | ||
this.targetWindow?.postMessage(serializedEvent, "*"); | ||
} | ||
this.deferredEvents.push(event); | ||
} | ||
}; | ||
}; | ||
return receiver; | ||
} | ||
// src/browser.ts | ||
var import_xstate2 = require("xstate"); | ||
var import_fast_safe_stringify2 = __toESM(require("fast-safe-stringify")); | ||
var CONNECTION_EVENT = "@statelyai.connected"; | ||
@@ -364,5 +350,16 @@ function isEventObject(event) { | ||
function createBrowserInspector(options) { | ||
const adapter = new BrowserAdapter(options); | ||
const resolvedOptions = { | ||
url: "https://stately.ai/inspect", | ||
filter: () => true, | ||
serialize: (event) => JSON.parse((0, import_fast_safe_stringify2.default)(event)), | ||
autoStart: true, | ||
iframe: null, | ||
...options, | ||
window: options?.window ?? window | ||
}; | ||
const adapter = new BrowserAdapter(resolvedOptions); | ||
const inspector = createInspector(adapter); | ||
inspector.start(); | ||
if (resolvedOptions.autoStart) { | ||
inspector.start(); | ||
} | ||
return inspector; | ||
@@ -390,3 +387,3 @@ } | ||
subscribe(observerOrFn) { | ||
const observer = (0, import_xstate.toObserver)(observerOrFn); | ||
const observer = (0, import_xstate2.toObserver)(observerOrFn); | ||
observers.add(observer); | ||
@@ -410,2 +407,43 @@ return { | ||
} | ||
var BrowserAdapter = class { | ||
constructor(options) { | ||
this.options = options; | ||
} | ||
status = "disconnected"; | ||
deferredEvents = []; | ||
targetWindow = null; | ||
start() { | ||
this.targetWindow = this.options.iframe ? null : this.options.window.open(String(this.options.url), "xstateinspector"); | ||
if (this.options.iframe) { | ||
this.options.iframe.addEventListener("load", () => { | ||
this.targetWindow = this.options.iframe?.contentWindow ?? null; | ||
}); | ||
this.options.iframe?.setAttribute("src", String(this.options.url)); | ||
} | ||
this.options.window.addEventListener("message", (event) => { | ||
if (isEventObject(event.data) && event.data.type === "@statelyai.connected") { | ||
this.status = "connected"; | ||
this.deferredEvents.forEach((event2) => { | ||
const serializedEvent = this.options.serialize(event2); | ||
this.targetWindow?.postMessage(serializedEvent, "*"); | ||
}); | ||
} | ||
}); | ||
} | ||
stop() { | ||
this.targetWindow?.postMessage({ type: "@statelyai.disconnected" }, "*"); | ||
this.status = "disconnected"; | ||
} | ||
send(event) { | ||
const shouldSendEvent = this.options.filter(event); | ||
if (!shouldSendEvent) { | ||
return; | ||
} | ||
if (this.status === "connected") { | ||
const serializedEvent = this.options.serialize(event); | ||
this.targetWindow?.postMessage(serializedEvent, "*"); | ||
} | ||
this.deferredEvents.push(event); | ||
} | ||
}; | ||
// Annotate the CommonJS export names for ESM import in node: | ||
@@ -416,3 +454,4 @@ 0 && (module.exports = { | ||
createInspector, | ||
createWebSocketInspector | ||
createWebSocketInspector, | ||
createWebSocketReceiver | ||
}); |
@@ -11,3 +11,3 @@ { | ||
"name": "@statelyai/inspect", | ||
"version": "0.0.10", | ||
"version": "0.0.11", | ||
"description": "Inspection utilities for state, actors, workflows, and state machines.", | ||
@@ -14,0 +14,0 @@ "main": "dist/index.js", |
@@ -7,3 +7,3 @@ import { expect, test } from 'vitest'; | ||
test('Inspector observes a state machine', async () => { | ||
test('inspector observes a state machine', async () => { | ||
const dom = new JSDOM(); | ||
@@ -10,0 +10,0 @@ |
import { AnyEventObject, Observer, Subscribable, toObserver } from 'xstate'; | ||
import { Inspector, StatelyInspectionEvent } from './types'; | ||
import { Adapter, Inspector, StatelyInspectionEvent } from './types'; | ||
import { InspectorOptions, createInspector } from './createInspector'; | ||
import { BrowserAdapter } from './BrowserAdapter'; | ||
import safeStringify from 'fast-safe-stringify'; | ||
@@ -42,7 +42,18 @@ interface BrowserReceiver extends Subscribable<StatelyInspectionEvent> {} | ||
): Inspector<BrowserAdapter> { | ||
const adapter = new BrowserAdapter(options); | ||
const resolvedOptions = { | ||
url: 'https://stately.ai/inspect', | ||
filter: () => true, | ||
serialize: (event) => JSON.parse(safeStringify(event)), | ||
autoStart: true, | ||
iframe: null, | ||
...options, | ||
window: options?.window ?? window, | ||
} satisfies Required<BrowserInspectorOptions>; | ||
const adapter = new BrowserAdapter(resolvedOptions); | ||
const inspector = createInspector(adapter); | ||
// Start immediately | ||
inspector.start(); | ||
if (resolvedOptions.autoStart) { | ||
inspector.start(); | ||
} | ||
@@ -94,11 +105,5 @@ return inspector; | ||
observers.add(observer); | ||
// const listener = (event: MessageEvent) => { | ||
// observer.next?.(event.data); | ||
// }; | ||
// window.addEventListener('message', listener); | ||
return { | ||
unsubscribe() { | ||
// window.removeEventListener('message', listener); | ||
observers.delete(observer); | ||
@@ -121,1 +126,50 @@ }, | ||
} | ||
export class BrowserAdapter implements Adapter { | ||
private status = 'disconnected' as 'disconnected' | 'connected'; | ||
private deferredEvents: StatelyInspectionEvent[] = []; | ||
public targetWindow: Window | null = null; | ||
constructor(public options: Required<BrowserInspectorOptions>) {} | ||
public start() { | ||
this.targetWindow = this.options.iframe | ||
? null | ||
: this.options.window.open(String(this.options.url), 'xstateinspector'); | ||
if (this.options.iframe) { | ||
this.options.iframe.addEventListener('load', () => { | ||
this.targetWindow = this.options.iframe?.contentWindow ?? null; | ||
}); | ||
this.options.iframe?.setAttribute('src', String(this.options.url)); | ||
} | ||
this.options.window.addEventListener('message', (event) => { | ||
if ( | ||
isEventObject(event.data) && | ||
event.data.type === '@statelyai.connected' | ||
) { | ||
this.status = 'connected'; | ||
this.deferredEvents.forEach((event) => { | ||
const serializedEvent = this.options.serialize(event); | ||
this.targetWindow?.postMessage(serializedEvent, '*'); | ||
}); | ||
} | ||
}); | ||
} | ||
public stop() { | ||
this.targetWindow?.postMessage({ type: '@statelyai.disconnected' }, '*'); | ||
this.status = 'disconnected'; | ||
} | ||
public send(event: StatelyInspectionEvent) { | ||
const shouldSendEvent = this.options.filter(event); | ||
if (!shouldSendEvent) { | ||
return; | ||
} | ||
if (this.status === 'connected') { | ||
const serializedEvent = this.options.serialize(event); | ||
this.targetWindow?.postMessage(serializedEvent, '*'); | ||
} | ||
this.deferredEvents.push(event); | ||
} | ||
} |
@@ -36,2 +36,8 @@ import { | ||
serialize?: (event: StatelyInspectionEvent) => StatelyInspectionEvent; | ||
/** | ||
* Whether to automatically start the inspector. | ||
* | ||
* @default true | ||
*/ | ||
autoStart?: boolean; | ||
} | ||
@@ -42,2 +48,3 @@ | ||
serialize: (event) => event, | ||
autoStart: true, | ||
}; | ||
@@ -105,4 +112,7 @@ | ||
type: '@xstate.snapshot', | ||
snapshot: info.snapshot as unknown as Snapshot<unknown>, | ||
event: info.event ?? (null as any), | ||
snapshot: { | ||
status: 'active', | ||
...info.snapshot, | ||
} as unknown as Snapshot<unknown>, | ||
event: info.event ?? { type: '' }, | ||
sessionId, | ||
@@ -109,0 +119,0 @@ id: null as any, |
export { createInspector } from './createInspector'; | ||
export { createWebSocketInspector } from './webSocketAdapter'; | ||
export { createWebSocketInspector, createWebSocketReceiver } from './webSocket'; | ||
export { createBrowserInspector, createBrowserReceiver } from './browser'; | ||
@@ -4,0 +4,0 @@ export type { |
@@ -54,4 +54,6 @@ import { | ||
export interface InspectedSnapshot { | ||
status: Snapshot<unknown>['status']; | ||
status?: Snapshot<unknown>['status']; | ||
context?: any; | ||
value?: any; | ||
output?: any; | ||
} | ||
@@ -58,0 +60,0 @@ |
59853
1562
19