@bian-womp/spark-workbench 0.3.90 → 0.3.92
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/lib/cjs/index.cjs +19 -6
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/layout.d.ts.map +1 -1
- package/lib/cjs/src/misc/mapping.d.ts.map +1 -1
- package/lib/cjs/src/misc/value.d.ts +2 -1
- package/lib/cjs/src/misc/value.d.ts.map +1 -1
- package/lib/esm/index.js +19 -6
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/layout.d.ts.map +1 -1
- package/lib/esm/src/misc/mapping.d.ts.map +1 -1
- package/lib/esm/src/misc/value.d.ts +2 -1
- package/lib/esm/src/misc/value.d.ts.map +1 -1
- package/package.json +4 -4
- package/lib/src/adapters/cli/index.d.ts +0 -22
- package/lib/src/adapters/cli/index.d.ts.map +0 -1
- package/lib/src/adapters/cli/index.js +0 -50
- package/lib/src/adapters/cli/index.js.map +0 -1
- package/lib/src/core/AbstractWorkbench.d.ts +0 -40
- package/lib/src/core/AbstractWorkbench.d.ts.map +0 -1
- package/lib/src/core/AbstractWorkbench.js +0 -15
- package/lib/src/core/AbstractWorkbench.js.map +0 -1
- package/lib/src/core/InMemoryWorkbench.d.ts +0 -304
- package/lib/src/core/InMemoryWorkbench.d.ts.map +0 -1
- package/lib/src/core/InMemoryWorkbench.js +0 -1016
- package/lib/src/core/InMemoryWorkbench.js.map +0 -1
- package/lib/src/core/contracts.d.ts +0 -172
- package/lib/src/core/contracts.d.ts.map +0 -1
- package/lib/src/core/contracts.js +0 -2
- package/lib/src/core/contracts.js.map +0 -1
- package/lib/src/core/ui-extensions.d.ts +0 -85
- package/lib/src/core/ui-extensions.d.ts.map +0 -1
- package/lib/src/core/ui-extensions.js +0 -111
- package/lib/src/core/ui-extensions.js.map +0 -1
- package/lib/src/examples/cli.d.ts +0 -2
- package/lib/src/examples/cli.d.ts.map +0 -1
- package/lib/src/examples/cli.js +0 -244
- package/lib/src/examples/cli.js.map +0 -1
- package/lib/src/examples/reactflow/App.d.ts +0 -2
- package/lib/src/examples/reactflow/App.d.ts.map +0 -1
- package/lib/src/examples/reactflow/App.js +0 -20
- package/lib/src/examples/reactflow/App.js.map +0 -1
- package/lib/src/index.d.ts +0 -30
- package/lib/src/index.d.ts.map +0 -1
- package/lib/src/index.js +0 -30
- package/lib/src/index.js.map +0 -1
- package/lib/src/misc/DebugEvents.d.ts +0 -7
- package/lib/src/misc/DebugEvents.d.ts.map +0 -1
- package/lib/src/misc/DebugEvents.js +0 -51
- package/lib/src/misc/DebugEvents.js.map +0 -1
- package/lib/src/misc/DefaultEdge.d.ts +0 -5
- package/lib/src/misc/DefaultEdge.d.ts.map +0 -1
- package/lib/src/misc/DefaultEdge.js +0 -15
- package/lib/src/misc/DefaultEdge.js.map +0 -1
- package/lib/src/misc/DefaultNode.d.ts +0 -5
- package/lib/src/misc/DefaultNode.d.ts.map +0 -1
- package/lib/src/misc/DefaultNode.js +0 -25
- package/lib/src/misc/DefaultNode.js.map +0 -1
- package/lib/src/misc/DefaultNodeContent.d.ts +0 -4
- package/lib/src/misc/DefaultNodeContent.d.ts.map +0 -1
- package/lib/src/misc/DefaultNodeContent.js +0 -58
- package/lib/src/misc/DefaultNodeContent.js.map +0 -1
- package/lib/src/misc/DefaultNodeHeader.d.ts +0 -13
- package/lib/src/misc/DefaultNodeHeader.d.ts.map +0 -1
- package/lib/src/misc/DefaultNodeHeader.js +0 -78
- package/lib/src/misc/DefaultNodeHeader.js.map +0 -1
- package/lib/src/misc/Inspector.d.ts +0 -12
- package/lib/src/misc/Inspector.d.ts.map +0 -1
- package/lib/src/misc/Inspector.js +0 -253
- package/lib/src/misc/Inspector.js.map +0 -1
- package/lib/src/misc/IssueBadge.d.ts +0 -7
- package/lib/src/misc/IssueBadge.d.ts.map +0 -1
- package/lib/src/misc/IssueBadge.js +0 -7
- package/lib/src/misc/IssueBadge.js.map +0 -1
- package/lib/src/misc/KeyboardShortcutToast.d.ts +0 -16
- package/lib/src/misc/KeyboardShortcutToast.d.ts.map +0 -1
- package/lib/src/misc/KeyboardShortcutToast.js +0 -40
- package/lib/src/misc/KeyboardShortcutToast.js.map +0 -1
- package/lib/src/misc/NodeHandles.d.ts +0 -18
- package/lib/src/misc/NodeHandles.d.ts.map +0 -1
- package/lib/src/misc/NodeHandles.js +0 -67
- package/lib/src/misc/NodeHandles.js.map +0 -1
- package/lib/src/misc/SelectionActiveSync.d.ts +0 -10
- package/lib/src/misc/SelectionActiveSync.d.ts.map +0 -1
- package/lib/src/misc/SelectionActiveSync.js +0 -21
- package/lib/src/misc/SelectionActiveSync.js.map +0 -1
- package/lib/src/misc/WorkbenchCanvas.d.ts +0 -23
- package/lib/src/misc/WorkbenchCanvas.d.ts.map +0 -1
- package/lib/src/misc/WorkbenchCanvas.js +0 -669
- package/lib/src/misc/WorkbenchCanvas.js.map +0 -1
- package/lib/src/misc/WorkbenchStudio.d.ts +0 -43
- package/lib/src/misc/WorkbenchStudio.d.ts.map +0 -1
- package/lib/src/misc/WorkbenchStudio.js +0 -463
- package/lib/src/misc/WorkbenchStudio.js.map +0 -1
- package/lib/src/misc/constants.d.ts +0 -4
- package/lib/src/misc/constants.d.ts.map +0 -1
- package/lib/src/misc/constants.js +0 -5
- package/lib/src/misc/constants.js.map +0 -1
- package/lib/src/misc/context/WorkbenchContext.d.ts +0 -133
- package/lib/src/misc/context/WorkbenchContext.d.ts.map +0 -1
- package/lib/src/misc/context/WorkbenchContext.js +0 -9
- package/lib/src/misc/context/WorkbenchContext.js.map +0 -1
- package/lib/src/misc/context/WorkbenchContext.provider.d.ts +0 -12
- package/lib/src/misc/context/WorkbenchContext.provider.d.ts.map +0 -1
- package/lib/src/misc/context/WorkbenchContext.provider.js +0 -995
- package/lib/src/misc/context/WorkbenchContext.provider.js.map +0 -1
- package/lib/src/misc/context-menu/ContextMenuButton.d.ts +0 -8
- package/lib/src/misc/context-menu/ContextMenuButton.d.ts.map +0 -1
- package/lib/src/misc/context-menu/ContextMenuButton.js +0 -10
- package/lib/src/misc/context-menu/ContextMenuButton.js.map +0 -1
- package/lib/src/misc/context-menu/ContextMenuHandlers.d.ts +0 -85
- package/lib/src/misc/context-menu/ContextMenuHandlers.d.ts.map +0 -1
- package/lib/src/misc/context-menu/ContextMenuHandlers.js +0 -2
- package/lib/src/misc/context-menu/ContextMenuHandlers.js.map +0 -1
- package/lib/src/misc/context-menu/ContextMenuHelpers.d.ts +0 -45
- package/lib/src/misc/context-menu/ContextMenuHelpers.d.ts.map +0 -1
- package/lib/src/misc/context-menu/ContextMenuHelpers.js +0 -182
- package/lib/src/misc/context-menu/ContextMenuHelpers.js.map +0 -1
- package/lib/src/misc/context-menu/DefaultContextMenu.d.ts +0 -3
- package/lib/src/misc/context-menu/DefaultContextMenu.d.ts.map +0 -1
- package/lib/src/misc/context-menu/DefaultContextMenu.js +0 -111
- package/lib/src/misc/context-menu/DefaultContextMenu.js.map +0 -1
- package/lib/src/misc/context-menu/NodeContextMenu.d.ts +0 -3
- package/lib/src/misc/context-menu/NodeContextMenu.d.ts.map +0 -1
- package/lib/src/misc/context-menu/NodeContextMenu.js +0 -51
- package/lib/src/misc/context-menu/NodeContextMenu.js.map +0 -1
- package/lib/src/misc/context-menu/SelectionContextMenu.d.ts +0 -3
- package/lib/src/misc/context-menu/SelectionContextMenu.d.ts.map +0 -1
- package/lib/src/misc/context-menu/SelectionContextMenu.js +0 -47
- package/lib/src/misc/context-menu/SelectionContextMenu.js.map +0 -1
- package/lib/src/misc/hooks.d.ts +0 -18
- package/lib/src/misc/hooks.d.ts.map +0 -1
- package/lib/src/misc/hooks.js +0 -275
- package/lib/src/misc/hooks.js.map +0 -1
- package/lib/src/misc/layout.d.ts +0 -117
- package/lib/src/misc/layout.d.ts.map +0 -1
- package/lib/src/misc/layout.js +0 -205
- package/lib/src/misc/layout.js.map +0 -1
- package/lib/src/misc/load.d.ts +0 -5
- package/lib/src/misc/load.d.ts.map +0 -1
- package/lib/src/misc/load.js +0 -106
- package/lib/src/misc/load.js.map +0 -1
- package/lib/src/misc/mapping.d.ts +0 -128
- package/lib/src/misc/mapping.d.ts.map +0 -1
- package/lib/src/misc/mapping.js +0 -270
- package/lib/src/misc/mapping.js.map +0 -1
- package/lib/src/misc/merge-utils.d.ts +0 -12
- package/lib/src/misc/merge-utils.d.ts.map +0 -1
- package/lib/src/misc/merge-utils.js +0 -51
- package/lib/src/misc/merge-utils.js.map +0 -1
- package/lib/src/misc/thumbnail-utils.d.ts +0 -53
- package/lib/src/misc/thumbnail-utils.d.ts.map +0 -1
- package/lib/src/misc/thumbnail-utils.js +0 -629
- package/lib/src/misc/thumbnail-utils.js.map +0 -1
- package/lib/src/misc/types.d.ts +0 -18
- package/lib/src/misc/types.d.ts.map +0 -1
- package/lib/src/misc/types.js +0 -2
- package/lib/src/misc/types.js.map +0 -1
- package/lib/src/misc/value.d.ts +0 -16
- package/lib/src/misc/value.d.ts.map +0 -1
- package/lib/src/misc/value.js +0 -114
- package/lib/src/misc/value.js.map +0 -1
- package/lib/src/misc/viewport-utils.d.ts +0 -6
- package/lib/src/misc/viewport-utils.d.ts.map +0 -1
- package/lib/src/misc/viewport-utils.js +0 -18
- package/lib/src/misc/viewport-utils.js.map +0 -1
- package/lib/src/runtime/AbstractGraphRunner.d.ts +0 -61
- package/lib/src/runtime/AbstractGraphRunner.d.ts.map +0 -1
- package/lib/src/runtime/AbstractGraphRunner.js +0 -63
- package/lib/src/runtime/AbstractGraphRunner.js.map +0 -1
- package/lib/src/runtime/IGraphRunner.d.ts +0 -100
- package/lib/src/runtime/IGraphRunner.d.ts.map +0 -1
- package/lib/src/runtime/IGraphRunner.js +0 -2
- package/lib/src/runtime/IGraphRunner.js.map +0 -1
- package/lib/src/runtime/LocalGraphRunner.d.ts +0 -60
- package/lib/src/runtime/LocalGraphRunner.d.ts.map +0 -1
- package/lib/src/runtime/LocalGraphRunner.js +0 -294
- package/lib/src/runtime/LocalGraphRunner.js.map +0 -1
- package/lib/src/runtime/RemoteGraphRunner.d.ts +0 -109
- package/lib/src/runtime/RemoteGraphRunner.d.ts.map +0 -1
- package/lib/src/runtime/RemoteGraphRunner.js +0 -696
- package/lib/src/runtime/RemoteGraphRunner.js.map +0 -1
|
@@ -1,696 +0,0 @@
|
|
|
1
|
-
import { RemoteRuntimeClient, } from "@bian-womp/spark-remote";
|
|
2
|
-
import { AbstractGraphRunner } from "./AbstractGraphRunner";
|
|
3
|
-
import { isValidViewport } from "../misc/viewport-utils";
|
|
4
|
-
// Counter for generating readable runner IDs
|
|
5
|
-
let remoteRunnerCounter = 0;
|
|
6
|
-
export class RemoteGraphRunner extends AbstractGraphRunner {
|
|
7
|
-
/**
|
|
8
|
-
* Generate cache key that includes io type to prevent collisions
|
|
9
|
-
* between input and output handles with the same name
|
|
10
|
-
*/
|
|
11
|
-
getCacheKey(nodeId, handle, io) {
|
|
12
|
-
return `${nodeId}.${io}.${handle}`;
|
|
13
|
-
}
|
|
14
|
-
applyRegistryDescriptor(desc) {
|
|
15
|
-
for (const t of desc.types) {
|
|
16
|
-
if (t.options) {
|
|
17
|
-
this.registry.registerEnum({
|
|
18
|
-
id: t.id,
|
|
19
|
-
options: t.options,
|
|
20
|
-
bakeTarget: t.bakeTarget,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
else if (!this.registry.types.has(t.id)) {
|
|
24
|
-
this.registry.registerType({
|
|
25
|
-
id: t.id,
|
|
26
|
-
displayName: t.displayName,
|
|
27
|
-
validate: (_v) => true,
|
|
28
|
-
bakeTarget: t.bakeTarget,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
for (const c of desc.categories || []) {
|
|
33
|
-
if (!this.registry.categories.has(c.id)) {
|
|
34
|
-
const category = {
|
|
35
|
-
id: c.id,
|
|
36
|
-
displayName: c.displayName,
|
|
37
|
-
createRuntime: () => ({
|
|
38
|
-
async onInputsChanged() { },
|
|
39
|
-
}),
|
|
40
|
-
policy: { asyncConcurrency: "switch" },
|
|
41
|
-
};
|
|
42
|
-
this.registry.categories.register(category);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
for (const c of desc.coercions) {
|
|
46
|
-
if (c.async) {
|
|
47
|
-
this.registry.registerAsyncCoercion(c.from, c.to, async (v) => v, {
|
|
48
|
-
nonTransitive: c.nonTransitive,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
this.registry.registerCoercion(c.from, c.to, (v) => v, {
|
|
53
|
-
nonTransitive: c.nonTransitive,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
for (const n of desc.nodes) {
|
|
58
|
-
if (!this.registry.nodes.has(n.id)) {
|
|
59
|
-
this.registry.registerNode({
|
|
60
|
-
id: n.id,
|
|
61
|
-
categoryId: n.categoryId,
|
|
62
|
-
displayName: n.displayName,
|
|
63
|
-
inputs: n.inputs || {},
|
|
64
|
-
outputs: n.outputs || {},
|
|
65
|
-
policy: n.policy || {},
|
|
66
|
-
impl: () => { },
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
this.registryFetched = true;
|
|
71
|
-
if (this.registryBootstrapResolve) {
|
|
72
|
-
this.registryBootstrapResolve();
|
|
73
|
-
this.registryBootstrapResolve = undefined;
|
|
74
|
-
this.registryBootstrapReject = undefined;
|
|
75
|
-
}
|
|
76
|
-
this.emit("registry", this.registry);
|
|
77
|
-
}
|
|
78
|
-
isRecord(value) {
|
|
79
|
-
return typeof value === "object" && value !== null;
|
|
80
|
-
}
|
|
81
|
-
isRegistryEvent(message) {
|
|
82
|
-
if (!this.isRecord(message))
|
|
83
|
-
return false;
|
|
84
|
-
if (message.type !== "registry")
|
|
85
|
-
return false;
|
|
86
|
-
if (!this.isRecord(message.payload))
|
|
87
|
-
return false;
|
|
88
|
-
return "registry" in message.payload;
|
|
89
|
-
}
|
|
90
|
-
waitForRegistryBootstrap() {
|
|
91
|
-
if (this.registryFetched)
|
|
92
|
-
return Promise.resolve();
|
|
93
|
-
if (this.registryBootstrapPromise)
|
|
94
|
-
return this.registryBootstrapPromise;
|
|
95
|
-
this.registryBootstrapPromise = new Promise((resolve, reject) => {
|
|
96
|
-
this.registryBootstrapResolve = resolve;
|
|
97
|
-
this.registryBootstrapReject = reject;
|
|
98
|
-
setTimeout(() => {
|
|
99
|
-
if (!this.registryFetched && this.registryBootstrapReject) {
|
|
100
|
-
this.registryBootstrapReject(new Error(`Registry bootstrap timeout (${this.REGISTRY_BOOTSTRAP_TIMEOUT_MS}ms exceeded)`));
|
|
101
|
-
this.registryBootstrapResolve = undefined;
|
|
102
|
-
this.registryBootstrapReject = undefined;
|
|
103
|
-
}
|
|
104
|
-
}, this.REGISTRY_BOOTSTRAP_TIMEOUT_MS);
|
|
105
|
-
}).finally(() => {
|
|
106
|
-
this.registryBootstrapPromise = undefined;
|
|
107
|
-
});
|
|
108
|
-
return this.registryBootstrapPromise;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Build RemoteRuntimeClient config from RemoteExecutionBackend config.
|
|
112
|
-
*/
|
|
113
|
-
buildClientConfig(backend) {
|
|
114
|
-
if (backend.kind === "remote-http") {
|
|
115
|
-
return {
|
|
116
|
-
kind: "remote-http",
|
|
117
|
-
baseUrl: backend.baseUrl,
|
|
118
|
-
connectOptions: backend.connectOptions,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
return {
|
|
123
|
-
kind: "remote-ws",
|
|
124
|
-
url: backend.url,
|
|
125
|
-
connectOptions: backend.connectOptions,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Setup event subscriptions for the client.
|
|
131
|
-
*/
|
|
132
|
-
setupClientSubscriptions(client) {
|
|
133
|
-
// Subscribe to transport status changes
|
|
134
|
-
// Convert RemoteRuntimeClient.TransportStatus to IGraphRunner.TransportStatus
|
|
135
|
-
// Only emit status if it matches this runner's ID
|
|
136
|
-
this.transportStatusUnsubscribe = client.onTransportStatus((status) => {
|
|
137
|
-
if (status.runnerId && status.runnerId !== this.runnerId)
|
|
138
|
-
return;
|
|
139
|
-
// Map remote-unix to undefined since RemoteGraphRunner doesn't support it
|
|
140
|
-
const mappedKind = status.kind === "remote-unix" ? undefined : status.kind;
|
|
141
|
-
const transportStatus = {
|
|
142
|
-
state: status.state,
|
|
143
|
-
kind: mappedKind,
|
|
144
|
-
runnerId: this.runnerId,
|
|
145
|
-
};
|
|
146
|
-
// Track current status
|
|
147
|
-
this.currentTransportStatus = transportStatus;
|
|
148
|
-
this.emit("transport", transportStatus);
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
// Ensure remote client
|
|
152
|
-
async ensureClient() {
|
|
153
|
-
if (this.disposed) {
|
|
154
|
-
throw new Error("Cannot ensure client: RemoteGraphRunner has been disposed");
|
|
155
|
-
}
|
|
156
|
-
if (this.client)
|
|
157
|
-
return this.client;
|
|
158
|
-
// If already connecting, wait for that connection to complete
|
|
159
|
-
if (this.clientPromise)
|
|
160
|
-
return this.clientPromise;
|
|
161
|
-
const backend = this.backend;
|
|
162
|
-
// Create connection promise to prevent concurrent connections
|
|
163
|
-
// Create promise wrapper first, then assign immediately before async work starts
|
|
164
|
-
let promise;
|
|
165
|
-
this.clientPromise = promise = (async () => {
|
|
166
|
-
// Build client config from backend config
|
|
167
|
-
const clientConfig = this.buildClientConfig(backend);
|
|
168
|
-
// Wrap custom event handler to intercept viewport events and emit viewport event
|
|
169
|
-
const wrappedOnCustomEvent = (event) => {
|
|
170
|
-
const msg = event.message;
|
|
171
|
-
if (this.isRecord(msg) && msg.type === "viewport" && this.isRecord(msg.payload)) {
|
|
172
|
-
const viewport = msg.payload.viewport;
|
|
173
|
-
if (isValidViewport(viewport)) {
|
|
174
|
-
this.emit("viewport", { viewport });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
else if (this.isRecord(msg) && msg.type === "flow-opened" && this.isRecord(msg.payload)) {
|
|
178
|
-
const sessionId = msg.payload.sessionId;
|
|
179
|
-
const resumed = msg.payload.resumed;
|
|
180
|
-
if (typeof sessionId === "number") {
|
|
181
|
-
console.info(`[RemoteGraphRunner] Flow opened (runner=${this.runnerId}, sessionId=${sessionId}, resumed=${Boolean(resumed)})`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
else if (this.isRecord(msg) && msg.type === "flow-closed" && this.isRecord(msg.payload)) {
|
|
185
|
-
const reason = msg.payload.reason;
|
|
186
|
-
console.warn(`[RemoteGraphRunner] Flow closed (runner=${this.runnerId}, reason=${typeof reason === "string" ? reason : "unknown"})`);
|
|
187
|
-
}
|
|
188
|
-
else if (this.isRegistryEvent(msg)) {
|
|
189
|
-
this.applyRegistryDescriptor(msg.payload.registry);
|
|
190
|
-
}
|
|
191
|
-
// Call original handler if provided
|
|
192
|
-
if (backend.onCustomEvent) {
|
|
193
|
-
backend.onCustomEvent(event);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
// Create client with wrapped custom event handler
|
|
197
|
-
const client = new RemoteRuntimeClient(clientConfig, {
|
|
198
|
-
onCustomEvent: wrappedOnCustomEvent,
|
|
199
|
-
runnerId: this.runnerId,
|
|
200
|
-
});
|
|
201
|
-
// Setup event subscriptions
|
|
202
|
-
this.setupClientSubscriptions(client);
|
|
203
|
-
// Connect the client (this will create and connect transport)
|
|
204
|
-
await client.connect();
|
|
205
|
-
this.client = client;
|
|
206
|
-
this.valueCache.clear();
|
|
207
|
-
this.listenersBound = false;
|
|
208
|
-
// Wait for registry runtime event pushed by backend on flow-open.
|
|
209
|
-
if (!this.registryFetched) {
|
|
210
|
-
console.info("[RemoteGraphRunner] Waiting for registry bootstrap event...");
|
|
211
|
-
await this.waitForRegistryBootstrap();
|
|
212
|
-
console.info("[RemoteGraphRunner] Received registry bootstrap event");
|
|
213
|
-
}
|
|
214
|
-
// Clear promise on success
|
|
215
|
-
this.clientPromise = undefined;
|
|
216
|
-
return client;
|
|
217
|
-
})();
|
|
218
|
-
return promise;
|
|
219
|
-
}
|
|
220
|
-
constructor(backend) {
|
|
221
|
-
super(backend);
|
|
222
|
-
this.disposed = false;
|
|
223
|
-
this.valueCache = new Map();
|
|
224
|
-
this.listenersBound = false;
|
|
225
|
-
this.registryFetched = false;
|
|
226
|
-
this.REGISTRY_BOOTSTRAP_TIMEOUT_MS = 15000; // 15 seconds
|
|
227
|
-
// Generate readable ID for this runner instance (e.g., remote-001, remote-002)
|
|
228
|
-
remoteRunnerCounter++;
|
|
229
|
-
this.runnerId = `remote-${String(remoteRunnerCounter).padStart(3, "0")}`;
|
|
230
|
-
console.info(`[RemoteGraphRunner] Created runner with ID: ${this.runnerId}`);
|
|
231
|
-
// Initialize transport status as "connecting" - will be updated when connection completes
|
|
232
|
-
this.currentTransportStatus = {
|
|
233
|
-
state: "connecting",
|
|
234
|
-
kind: backend.kind,
|
|
235
|
-
};
|
|
236
|
-
// Auto-handle registry-changed invalidations from remote
|
|
237
|
-
// We listen on invalidate and if reason matches, we rehydrate registry and emit a registry event
|
|
238
|
-
this.ensureClient().then(async (client) => {
|
|
239
|
-
const eng = client.engine;
|
|
240
|
-
if (!this.listenersBound) {
|
|
241
|
-
eng.on("invalidate", async (e) => {
|
|
242
|
-
if (e.reason === "registry-changed") {
|
|
243
|
-
try {
|
|
244
|
-
const deltas = Array.isArray(e.deltas) ? e.deltas : [];
|
|
245
|
-
for (const d of deltas) {
|
|
246
|
-
if (!d || typeof d !== "object")
|
|
247
|
-
continue;
|
|
248
|
-
if (d.kind === "register-enum") {
|
|
249
|
-
this.registry.registerEnum({
|
|
250
|
-
id: d.id,
|
|
251
|
-
displayName: d.displayName,
|
|
252
|
-
options: d.options,
|
|
253
|
-
opts: d.opts,
|
|
254
|
-
bakeTarget: d.bakeTarget,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
else if (d.kind === "register-type") {
|
|
258
|
-
if (!this.registry.types.has(d.id)) {
|
|
259
|
-
this.registry.registerType({
|
|
260
|
-
id: d.id,
|
|
261
|
-
displayName: d.displayName,
|
|
262
|
-
validate: (_v) => true,
|
|
263
|
-
bakeTarget: d.bakeTarget,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
else if (d.kind === "register-node") {
|
|
268
|
-
if (!this.registry.nodes.has(d.desc?.id)) {
|
|
269
|
-
this.registry.registerNode({
|
|
270
|
-
id: String(d.desc?.id || ""),
|
|
271
|
-
categoryId: String(d.desc?.categoryId || "compute"),
|
|
272
|
-
displayName: d.desc?.displayName,
|
|
273
|
-
inputs: d.desc?.inputs || {},
|
|
274
|
-
outputs: d.desc?.outputs || {},
|
|
275
|
-
impl: () => { },
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
this.emit("registry", this.registry);
|
|
281
|
-
// Trigger update so validation/UI refreshes using last known graph
|
|
282
|
-
try {
|
|
283
|
-
if (this.lastDef)
|
|
284
|
-
await this.update(this.lastDef);
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
console.error("Failed to update graph definition after registry changed");
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
catch {
|
|
291
|
-
console.error("Failed to handle registry changed event");
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
build(def) { }
|
|
299
|
-
async update(def, options) {
|
|
300
|
-
// Remote: forward update and await completion
|
|
301
|
-
const client = await this.ensureClient();
|
|
302
|
-
try {
|
|
303
|
-
await client.api.update(def, options);
|
|
304
|
-
this.emit("invalidate", { reason: "graph-updated" });
|
|
305
|
-
this.lastDef = def;
|
|
306
|
-
}
|
|
307
|
-
catch (err) {
|
|
308
|
-
// Log error but don't throw to maintain compatibility
|
|
309
|
-
console.error("[RemoteGraphRunner] Error updating graph:", err);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
launch(def, opts) {
|
|
313
|
-
if (this.engine) {
|
|
314
|
-
this.engine.dispose();
|
|
315
|
-
this.engine = undefined;
|
|
316
|
-
}
|
|
317
|
-
// Remote: build remotely then launch
|
|
318
|
-
this.ensureClient().then(async (client) => {
|
|
319
|
-
await client.api.build(def);
|
|
320
|
-
// Signal UI after remote build as well
|
|
321
|
-
this.emit("invalidate", { reason: "graph-built" });
|
|
322
|
-
this.lastDef = def;
|
|
323
|
-
// Hydrate current remote inputs/outputs (including defaults) into cache
|
|
324
|
-
try {
|
|
325
|
-
const snap = await client.api.snapshot();
|
|
326
|
-
for (const [nodeId, map] of Object.entries(snap.inputs || {})) {
|
|
327
|
-
for (const [handle, value] of Object.entries(map || {})) {
|
|
328
|
-
this.valueCache.set(this.getCacheKey(nodeId, handle, "input"), {
|
|
329
|
-
value,
|
|
330
|
-
});
|
|
331
|
-
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
for (const [nodeId, map] of Object.entries(snap.outputs || {})) {
|
|
335
|
-
for (const [handle, value] of Object.entries(map || {})) {
|
|
336
|
-
this.valueCache.set(this.getCacheKey(nodeId, handle, "output"), {
|
|
337
|
-
value,
|
|
338
|
-
});
|
|
339
|
-
this.emit("value", { nodeId, handle, value, io: "output" });
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
console.error("Failed to hydrate remote inputs/outputs");
|
|
345
|
-
}
|
|
346
|
-
await this.createAndLaunchEngine(opts);
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
async createAndLaunchEngine(opts) {
|
|
350
|
-
const client = await this.ensureClient();
|
|
351
|
-
// Configure and launch engine on the backend
|
|
352
|
-
await client.api.launch(opts);
|
|
353
|
-
// Get the remote engine proxy and wire up event listeners
|
|
354
|
-
const eng = client.engine;
|
|
355
|
-
if (!this.listenersBound) {
|
|
356
|
-
eng.on("value", (e) => {
|
|
357
|
-
this.valueCache.set(this.getCacheKey(e.nodeId, e.handle, e.io), {
|
|
358
|
-
value: e.value,
|
|
359
|
-
runtimeTypeId: e.runtimeTypeId,
|
|
360
|
-
});
|
|
361
|
-
this.emit("value", e);
|
|
362
|
-
});
|
|
363
|
-
eng.on("error", (e) => this.emit("error", e));
|
|
364
|
-
eng.on("invalidate", (e) => this.emit("invalidate", e));
|
|
365
|
-
eng.on("stats", (e) => this.emit("stats", e));
|
|
366
|
-
this.listenersBound = true;
|
|
367
|
-
}
|
|
368
|
-
this.engine = eng;
|
|
369
|
-
const runMode = opts?.runMode ?? "manual";
|
|
370
|
-
this.emit("status", { running: true, runMode });
|
|
371
|
-
// Re-apply staged inputs using client.setInputs for consistency
|
|
372
|
-
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
373
|
-
await eng.setInputs(nodeId, map, undefined).catch(() => {
|
|
374
|
-
// Ignore errors during launch - inputs will be set when user calls setInputs
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Launch using an existing backend runtime that has already been built and hydrated.
|
|
380
|
-
* This is used when resuming from a snapshot where the backend has already applied
|
|
381
|
-
* ApplySnapshotFull (which builds the graph and hydrates inputs/outputs).
|
|
382
|
-
* Unlike launch(), this method does NOT call client.build() to avoid destroying
|
|
383
|
-
* the runtime state that was just restored.
|
|
384
|
-
*/
|
|
385
|
-
launchExisting(def, opts) {
|
|
386
|
-
if (this.engine) {
|
|
387
|
-
this.engine.dispose();
|
|
388
|
-
this.engine = undefined;
|
|
389
|
-
}
|
|
390
|
-
// Remote: attach to existing runtime and launch (do NOT rebuild)
|
|
391
|
-
this.ensureClient().then(async (client) => {
|
|
392
|
-
// NOTE: We do NOT call client.build(def) here because the backend runtime
|
|
393
|
-
// has already been built and hydrated via ApplySnapshotFull.
|
|
394
|
-
// Calling build() would create a new runtime and lose the restored state.
|
|
395
|
-
this.lastDef = def;
|
|
396
|
-
await this.createAndLaunchEngine(opts);
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
setRunMode(runMode) {
|
|
400
|
-
if (this.engine) {
|
|
401
|
-
this.engine.setRunMode(runMode);
|
|
402
|
-
this.emit("status", { running: true, runMode });
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
async computeNode(nodeId) {
|
|
406
|
-
const client = await this.ensureClient();
|
|
407
|
-
await client.engine.computeNode(nodeId);
|
|
408
|
-
}
|
|
409
|
-
async runFromHere(nodeId) {
|
|
410
|
-
const client = await this.ensureClient();
|
|
411
|
-
await client.engine.runFromHere(nodeId);
|
|
412
|
-
}
|
|
413
|
-
async cancelNodeRuns(nodeIds) {
|
|
414
|
-
const client = await this.ensureClient();
|
|
415
|
-
await client.engine.cancelNodeRuns(nodeIds);
|
|
416
|
-
}
|
|
417
|
-
async setInputs(nodeId, inputs, options) {
|
|
418
|
-
// Update staged inputs (for getInputs to work correctly)
|
|
419
|
-
if (!this.stagedInputs[nodeId])
|
|
420
|
-
this.stagedInputs[nodeId] = {};
|
|
421
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
422
|
-
if (value === undefined) {
|
|
423
|
-
delete this.stagedInputs[nodeId][handle];
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
this.stagedInputs[nodeId][handle] = value;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
try {
|
|
430
|
-
const client = await this.ensureClient();
|
|
431
|
-
await client.engine.setInputs(nodeId, inputs, options);
|
|
432
|
-
}
|
|
433
|
-
catch (err) {
|
|
434
|
-
// Emit synthetic events if connection fails
|
|
435
|
-
for (const [handle, value] of Object.entries(inputs)) {
|
|
436
|
-
this.emit("value", { nodeId, handle, value, io: "input" });
|
|
437
|
-
}
|
|
438
|
-
throw err;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
async copyOutputs(fromNodeId, toNodeId, options) {
|
|
442
|
-
const client = await this.ensureClient();
|
|
443
|
-
await client.engine.copyOutputs(fromNodeId, toNodeId, options);
|
|
444
|
-
}
|
|
445
|
-
async triggerExternal(nodeId, event, options) {
|
|
446
|
-
const client = await this.ensureClient();
|
|
447
|
-
await client.engine.triggerExternal(nodeId, event, options);
|
|
448
|
-
}
|
|
449
|
-
async setViewport(viewport) {
|
|
450
|
-
const client = await this.ensureClient();
|
|
451
|
-
await client.api.setViewport(viewport);
|
|
452
|
-
}
|
|
453
|
-
async coerce(from, to, value) {
|
|
454
|
-
const client = await this.ensureClient();
|
|
455
|
-
try {
|
|
456
|
-
return await client.api.coerce(from, to, value);
|
|
457
|
-
}
|
|
458
|
-
catch {
|
|
459
|
-
return value;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
async setExtData(data) {
|
|
463
|
-
const client = await this.ensureClient();
|
|
464
|
-
await client.api.setExtData(data);
|
|
465
|
-
}
|
|
466
|
-
async updateExtData(updates) {
|
|
467
|
-
const client = await this.ensureClient();
|
|
468
|
-
return await client.api.updateExtData(updates);
|
|
469
|
-
}
|
|
470
|
-
async commit(reason) {
|
|
471
|
-
const client = await this.ensureClient();
|
|
472
|
-
try {
|
|
473
|
-
const history = await client.api.commit(reason);
|
|
474
|
-
return history;
|
|
475
|
-
}
|
|
476
|
-
catch (err) {
|
|
477
|
-
console.error("[RemoteGraphRunner] Error committing:", err);
|
|
478
|
-
throw err;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
async undo() {
|
|
482
|
-
const client = await this.ensureClient();
|
|
483
|
-
try {
|
|
484
|
-
return await client.api.undo();
|
|
485
|
-
}
|
|
486
|
-
catch {
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
async redo() {
|
|
491
|
-
const client = await this.ensureClient();
|
|
492
|
-
try {
|
|
493
|
-
return await client.api.redo();
|
|
494
|
-
}
|
|
495
|
-
catch {
|
|
496
|
-
return false;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
async snapshotFull() {
|
|
500
|
-
const client = await this.ensureClient();
|
|
501
|
-
try {
|
|
502
|
-
return await client.api.snapshotFull();
|
|
503
|
-
}
|
|
504
|
-
catch {
|
|
505
|
-
return { inputs: {}, outputs: {} };
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
async applySnapshotFull(payload, options) {
|
|
509
|
-
// Hydrate local cache first so UI can display values immediately
|
|
510
|
-
this.hydrateValueCache(payload, { dry: options?.dry });
|
|
511
|
-
// Then sync with backend
|
|
512
|
-
const client = await this.ensureClient();
|
|
513
|
-
await client.api.applySnapshotFull(payload, { skipBuild: options?.skipBuild });
|
|
514
|
-
}
|
|
515
|
-
async convertSnapshot(converterConfig, options) {
|
|
516
|
-
const client = await this.ensureClient();
|
|
517
|
-
const converted = await client.api.convertSnapshot(converterConfig, {
|
|
518
|
-
dry: options?.dry,
|
|
519
|
-
});
|
|
520
|
-
// Hydrate local cache with converted values
|
|
521
|
-
this.hydrateValueCache(converted, { dry: options?.dry });
|
|
522
|
-
return converted;
|
|
523
|
-
}
|
|
524
|
-
async pause() {
|
|
525
|
-
const client = await this.ensureClient();
|
|
526
|
-
await client.api.pause();
|
|
527
|
-
}
|
|
528
|
-
async resume() {
|
|
529
|
-
const client = await this.ensureClient();
|
|
530
|
-
await client.api.resume();
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Hydrates the local valueCache from a snapshot and emits value events.
|
|
534
|
-
* This ensures the UI can display inputs/outputs immediately without waiting
|
|
535
|
-
* for value events from the remote backend.
|
|
536
|
-
*/
|
|
537
|
-
hydrateValueCache(snapshot, opts) {
|
|
538
|
-
// Hydrate inputs
|
|
539
|
-
for (const [nodeId, map] of Object.entries(snapshot.inputs || {})) {
|
|
540
|
-
for (const [handle, value] of Object.entries(map || {})) {
|
|
541
|
-
this.valueCache.set(this.getCacheKey(nodeId, handle, "input"), {
|
|
542
|
-
value,
|
|
543
|
-
});
|
|
544
|
-
this.emit("value", {
|
|
545
|
-
nodeId,
|
|
546
|
-
handle,
|
|
547
|
-
value,
|
|
548
|
-
io: "input",
|
|
549
|
-
dry: opts?.dry,
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
// Hydrate outputs
|
|
554
|
-
for (const [nodeId, map] of Object.entries(snapshot.outputs || {})) {
|
|
555
|
-
for (const [handle, value] of Object.entries(map || {})) {
|
|
556
|
-
this.valueCache.set(this.getCacheKey(nodeId, handle, "output"), {
|
|
557
|
-
value,
|
|
558
|
-
});
|
|
559
|
-
this.emit("value", {
|
|
560
|
-
nodeId,
|
|
561
|
-
handle,
|
|
562
|
-
value,
|
|
563
|
-
io: "output",
|
|
564
|
-
dry: opts?.dry,
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
async setEnvironment(env, opts) {
|
|
570
|
-
// Use client if available, otherwise ensure client and then set environment
|
|
571
|
-
if (this.client) {
|
|
572
|
-
await this.client.api.setEnvironment(env, opts);
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
try {
|
|
576
|
-
const client = await this.ensureClient();
|
|
577
|
-
await client.api.setEnvironment(env, opts);
|
|
578
|
-
}
|
|
579
|
-
catch {
|
|
580
|
-
// Silently fail if connection not available
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
getEnvironment() {
|
|
585
|
-
// Interface requires sync return, but RemoteRuntimeClient.getEnvironment() is async.
|
|
586
|
-
// Returns undefined synchronously; callers needing the actual value should:
|
|
587
|
-
// - Use snapshotFull() which includes environment
|
|
588
|
-
// - Call client.getEnvironment() directly if they have access to the client
|
|
589
|
-
// This is a limitation of the sync interface signature.
|
|
590
|
-
return undefined;
|
|
591
|
-
}
|
|
592
|
-
getOutputs(def) {
|
|
593
|
-
const out = {};
|
|
594
|
-
const cache = this.valueCache;
|
|
595
|
-
if (!cache)
|
|
596
|
-
return out;
|
|
597
|
-
for (const n of def.nodes) {
|
|
598
|
-
const resolved = n.resolvedHandles?.outputs;
|
|
599
|
-
const desc = this.registry.nodes.get(n.typeId);
|
|
600
|
-
const handles = Object.keys(resolved ?? desc?.outputs ?? {});
|
|
601
|
-
for (const h of handles) {
|
|
602
|
-
const key = this.getCacheKey(n.nodeId, h, "output");
|
|
603
|
-
const rec = cache.get(key);
|
|
604
|
-
if (rec) {
|
|
605
|
-
if (!out[n.nodeId])
|
|
606
|
-
out[n.nodeId] = {};
|
|
607
|
-
out[n.nodeId][h] = rec.value;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return out;
|
|
612
|
-
}
|
|
613
|
-
getInputs(def) {
|
|
614
|
-
const out = {};
|
|
615
|
-
const cache = this.valueCache;
|
|
616
|
-
const inputPrefix = ".input.";
|
|
617
|
-
for (const n of def.nodes) {
|
|
618
|
-
const staged = this.stagedInputs[n.nodeId] ?? {};
|
|
619
|
-
const cur = {};
|
|
620
|
-
// Include ALL input values from cache (not just resolved/desc handles).
|
|
621
|
-
// Dynamic nodes (e.g. ext.scene.area) have geo handles that may not be in
|
|
622
|
-
// resolvedHandles yet; we must return every user-set value for duplication.
|
|
623
|
-
const prefix = `${n.nodeId}${inputPrefix}`;
|
|
624
|
-
for (const [key, rec] of cache.entries()) {
|
|
625
|
-
if (key.startsWith(prefix)) {
|
|
626
|
-
const handle = key.slice(prefix.length);
|
|
627
|
-
cur[handle] = rec.value;
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
// Build inbound handle set for this node to honor wiring precedence
|
|
631
|
-
const inbound = new Set(def.edges.filter((e) => e.target.nodeId === n.nodeId).map((e) => e.target.handle));
|
|
632
|
-
// Merge staged only for non-inbound handles so UI doesn't override runtime values
|
|
633
|
-
const merged = { ...cur };
|
|
634
|
-
for (const [h, v] of Object.entries(staged)) {
|
|
635
|
-
if (!inbound.has(h))
|
|
636
|
-
merged[h] = v;
|
|
637
|
-
}
|
|
638
|
-
if (Object.keys(merged).length > 0)
|
|
639
|
-
out[n.nodeId] = merged;
|
|
640
|
-
}
|
|
641
|
-
return out;
|
|
642
|
-
}
|
|
643
|
-
async dispose() {
|
|
644
|
-
// Idempotent: allow multiple calls safely
|
|
645
|
-
if (this.disposed)
|
|
646
|
-
return;
|
|
647
|
-
this.disposed = true;
|
|
648
|
-
console.info(`[RemoteGraphRunner] Disposing runner with ID: ${this.runnerId}`);
|
|
649
|
-
super.dispose();
|
|
650
|
-
// Clear client promise if any
|
|
651
|
-
this.clientPromise = undefined;
|
|
652
|
-
// Unsubscribe from transport status
|
|
653
|
-
// Note: Custom events are cleaned up automatically when client is disposed
|
|
654
|
-
if (this.transportStatusUnsubscribe) {
|
|
655
|
-
this.transportStatusUnsubscribe();
|
|
656
|
-
this.transportStatusUnsubscribe = undefined;
|
|
657
|
-
}
|
|
658
|
-
// Dispose client (which will close transport)
|
|
659
|
-
const clientToDispose = this.client;
|
|
660
|
-
this.client = undefined;
|
|
661
|
-
this.registryFetched = false; // Reset so registry is fetched again on reconnect
|
|
662
|
-
if (clientToDispose) {
|
|
663
|
-
try {
|
|
664
|
-
await clientToDispose.dispose();
|
|
665
|
-
}
|
|
666
|
-
catch (err) {
|
|
667
|
-
console.warn("[RemoteGraphRunner] Error disposing client:", err);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
const disconnectedStatus = {
|
|
671
|
-
state: "disconnected",
|
|
672
|
-
kind: this.backend.kind,
|
|
673
|
-
runnerId: this.runnerId,
|
|
674
|
-
};
|
|
675
|
-
this.currentTransportStatus = disconnectedStatus;
|
|
676
|
-
this.emit("transport", disconnectedStatus);
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Override on() to emit current transport status immediately when a new listener subscribes.
|
|
680
|
-
* This ensures listeners don't miss the current status when they attach after connection.
|
|
681
|
-
*/
|
|
682
|
-
on(event, handler) {
|
|
683
|
-
const unsubscribe = super.on(event, handler);
|
|
684
|
-
// If subscribing to transport events and we have a current status, emit it immediately
|
|
685
|
-
if (event === "transport" && this.currentTransportStatus) {
|
|
686
|
-
// Use setTimeout to ensure this happens after the listener is registered
|
|
687
|
-
// This prevents issues if the handler modifies state synchronously
|
|
688
|
-
setTimeout(() => {
|
|
689
|
-
// Type assertion is safe here because we checked event === "transport"
|
|
690
|
-
handler(this.currentTransportStatus);
|
|
691
|
-
}, 0);
|
|
692
|
-
}
|
|
693
|
-
return unsubscribe;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
//# sourceMappingURL=RemoteGraphRunner.js.map
|