@ironflow/browser 0.20.0 → 0.20.2
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/LICENSE +181 -0
- package/package.json +9 -9
- package/dist/agents/index.d.ts +0 -34
- package/dist/agents/index.d.ts.map +0 -1
- package/dist/agents/index.js +0 -24
- package/dist/agents/index.js.map +0 -1
- package/dist/agents/invoke.d.ts +0 -16
- package/dist/agents/invoke.d.ts.map +0 -1
- package/dist/agents/invoke.js +0 -222
- package/dist/agents/invoke.js.map +0 -1
- package/dist/agents/readMemory.d.ts +0 -18
- package/dist/agents/readMemory.d.ts.map +0 -1
- package/dist/agents/readMemory.js +0 -87
- package/dist/agents/readMemory.js.map +0 -1
- package/dist/agents/subscribe.d.ts +0 -23
- package/dist/agents/subscribe.d.ts.map +0 -1
- package/dist/agents/subscribe.js +0 -117
- package/dist/agents/subscribe.js.map +0 -1
- package/dist/agents/types.d.ts +0 -156
- package/dist/agents/types.d.ts.map +0 -1
- package/dist/agents/types.js +0 -7
- package/dist/agents/types.js.map +0 -1
- package/dist/client.d.ts +0 -844
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -2243
- package/dist/client.js.map +0 -1
- package/dist/config-client.d.ts +0 -43
- package/dist/config-client.d.ts.map +0 -1
- package/dist/config-client.js +0 -134
- package/dist/config-client.js.map +0 -1
- package/dist/config.d.ts +0 -91
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -55
- package/dist/config.js.map +0 -1
- package/dist/index.d.ts +0 -53
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -53
- package/dist/index.js.map +0 -1
- package/dist/kv.d.ts +0 -79
- package/dist/kv.d.ts.map +0 -1
- package/dist/kv.js +0 -289
- package/dist/kv.js.map +0 -1
- package/dist/projection-stream.d.ts +0 -42
- package/dist/projection-stream.d.ts.map +0 -1
- package/dist/projection-stream.js +0 -48
- package/dist/projection-stream.js.map +0 -1
- package/dist/subscription.d.ts +0 -107
- package/dist/subscription.d.ts.map +0 -1
- package/dist/subscription.js +0 -450
- package/dist/subscription.js.map +0 -1
- package/dist/transport/connectrpc.d.ts +0 -74
- package/dist/transport/connectrpc.d.ts.map +0 -1
- package/dist/transport/connectrpc.js +0 -393
- package/dist/transport/connectrpc.js.map +0 -1
- package/dist/transport/index.d.ts +0 -7
- package/dist/transport/index.d.ts.map +0 -1
- package/dist/transport/index.js +0 -6
- package/dist/transport/index.js.map +0 -1
- package/dist/transport/types.d.ts +0 -69
- package/dist/transport/types.d.ts.map +0 -1
- package/dist/transport/types.js +0 -5
- package/dist/transport/types.js.map +0 -1
- package/dist/transport/websocket.d.ts +0 -38
- package/dist/transport/websocket.d.ts.map +0 -1
- package/dist/transport/websocket.js +0 -276
- package/dist/transport/websocket.js.map +0 -1
package/dist/client.js
DELETED
|
@@ -1,2243 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ironflow Browser Client
|
|
3
|
-
*
|
|
4
|
-
* Singleton client for browser-based real-time interactions with Ironflow.
|
|
5
|
-
*/
|
|
6
|
-
import { NotConfiguredError, IronflowError, RunFailedError, RunCancelledError, UnauthenticatedError, EnterpriseRequiredError, UnauthorizedError, ValidationError, createLogger, createNoopLogger, DEFAULT_TIMEOUTS, HEADERS, TriggerResponseSchema, TriggerSyncResponseSchema, RunResponseSchema, ListRunsResponseSchema, RunStatusSchema, ErrorResponseSchema, safeJsonParse, patterns, peelProjectionEnvelope, } from "@ironflow/core";
|
|
7
|
-
import { mergeConfig } from "./config.js";
|
|
8
|
-
import { SubscriptionManager, } from "./subscription.js";
|
|
9
|
-
import { createWebSocketTransport } from "./transport/websocket.js";
|
|
10
|
-
import { createConnectRPCTransport } from "./transport/connectrpc.js";
|
|
11
|
-
import { filterWaitStreamFrames } from "./projection-stream.js";
|
|
12
|
-
import { BrowserKVClient } from "./kv.js";
|
|
13
|
-
import { BrowserConfigClient } from "./config-client.js";
|
|
14
|
-
import { createAgentsNamespace } from "./agents/index.js";
|
|
15
|
-
/**
|
|
16
|
-
* Ironflow browser client singleton
|
|
17
|
-
*/
|
|
18
|
-
class IronflowClient {
|
|
19
|
-
config = null;
|
|
20
|
-
logger = createNoopLogger();
|
|
21
|
-
transport = null;
|
|
22
|
-
subscriptionManager = null;
|
|
23
|
-
visibilityHandler = null;
|
|
24
|
-
/**
|
|
25
|
-
* `agents` namespace — browser helpers for `agent()` functions.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```typescript
|
|
29
|
-
* const result = await ironflow.agents.invoke("my-agent", { task: "..." });
|
|
30
|
-
* console.log(result.runId, result.output);
|
|
31
|
-
*
|
|
32
|
-
* const sub = await ironflow.agents.subscribe(runId, {
|
|
33
|
-
* onStep: (e) => console.log("step", e.stepId, e.type),
|
|
34
|
-
* onComplete: (r) => console.log("done", r.output),
|
|
35
|
-
* });
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
agents = createAgentsNamespace({
|
|
39
|
-
invoke: (functionId, options) => this.invoke(functionId, options),
|
|
40
|
-
subscribe: (pattern, cbs) => this.subscribe(pattern, cbs),
|
|
41
|
-
cancelRun: (runId, reason) => this.cancelRun(runId, reason),
|
|
42
|
-
getProjection: (name, options) => this.getProjection(name, options),
|
|
43
|
-
waitForProjectionCatchup: (name, opts) => this.waitForProjectionCatchup(name, opts),
|
|
44
|
-
});
|
|
45
|
-
/**
|
|
46
|
-
* Configure the client
|
|
47
|
-
*
|
|
48
|
-
* Must be called before any other operations.
|
|
49
|
-
*/
|
|
50
|
-
configure(options = {}) {
|
|
51
|
-
this.config = mergeConfig(options);
|
|
52
|
-
// Set up logger
|
|
53
|
-
if (this.config.logger === false) {
|
|
54
|
-
this.logger = createNoopLogger();
|
|
55
|
-
}
|
|
56
|
-
else if (this.config.logger) {
|
|
57
|
-
this.logger = this.config.logger;
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
this.logger = createLogger({ prefix: "[ironflow]" });
|
|
61
|
-
}
|
|
62
|
-
// Clean up existing resources
|
|
63
|
-
this.cleanup();
|
|
64
|
-
// Create transport
|
|
65
|
-
const transportOptions = {
|
|
66
|
-
auth: this.config.auth,
|
|
67
|
-
autoReconnect: this.config.reconnect.enabled,
|
|
68
|
-
reconnectDelay: this.config.reconnect.backoff.initial,
|
|
69
|
-
maxReconnectDelay: this.config.reconnect.backoff.max,
|
|
70
|
-
reconnectBackoff: this.config.reconnect.backoff.multiplier,
|
|
71
|
-
environment: this.config.environment,
|
|
72
|
-
};
|
|
73
|
-
// Create transport based on config (ConnectRPC by default)
|
|
74
|
-
if (this.config.transport === "websocket") {
|
|
75
|
-
this.transport = createWebSocketTransport(this.config.serverUrl, transportOptions);
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
// Default to ConnectRPC
|
|
79
|
-
this.transport = createConnectRPCTransport(this.config.serverUrl, transportOptions);
|
|
80
|
-
}
|
|
81
|
-
// Create subscription manager
|
|
82
|
-
this.subscriptionManager = new SubscriptionManager(this.transport, this.config.logger);
|
|
83
|
-
// Set up visibility handling
|
|
84
|
-
if (this.config.visibility.pauseOnHidden && typeof document !== "undefined") {
|
|
85
|
-
this.setupVisibilityHandling();
|
|
86
|
-
}
|
|
87
|
-
this.logger.info("Ironflow client configured", {
|
|
88
|
-
serverUrl: this.config.serverUrl,
|
|
89
|
-
transport: this.config.transport,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Check if the client is configured
|
|
94
|
-
*/
|
|
95
|
-
get isConfigured() {
|
|
96
|
-
return this.config !== null;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Detect which transport the server supports
|
|
100
|
-
*
|
|
101
|
-
* Returns the best available transport based on server capabilities.
|
|
102
|
-
* ConnectRPC is preferred over WebSocket.
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```typescript
|
|
106
|
-
* const transport = await ironflow.detectTransport();
|
|
107
|
-
* // Returns 'connectrpc' | 'websocket'
|
|
108
|
-
* ```
|
|
109
|
-
*/
|
|
110
|
-
async detectTransport() {
|
|
111
|
-
const serverUrl = this.config?.serverUrl ?? "http://localhost:9123";
|
|
112
|
-
try {
|
|
113
|
-
// Try to get server capabilities via ConnectRPC endpoint
|
|
114
|
-
const detectHeaders = {
|
|
115
|
-
"Content-Type": "application/json",
|
|
116
|
-
};
|
|
117
|
-
if (this.config?.environment) {
|
|
118
|
-
detectHeaders[HEADERS.ENVIRONMENT] = this.config.environment;
|
|
119
|
-
}
|
|
120
|
-
const response = await fetch(`${serverUrl}/ironflow.v1.IronflowService/GetCapabilities`, {
|
|
121
|
-
method: "POST",
|
|
122
|
-
headers: detectHeaders,
|
|
123
|
-
body: "{}",
|
|
124
|
-
});
|
|
125
|
-
if (response.ok) {
|
|
126
|
-
const data = await response.json();
|
|
127
|
-
const transports = data.transports;
|
|
128
|
-
// Check if server explicitly supports connectrpc
|
|
129
|
-
if (transports?.includes("connectrpc") || transports?.includes("grpc")) {
|
|
130
|
-
return "connectrpc";
|
|
131
|
-
}
|
|
132
|
-
// Server responded, so ConnectRPC endpoint works
|
|
133
|
-
return "connectrpc";
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
// ConnectRPC not available
|
|
138
|
-
}
|
|
139
|
-
// Fall back to WebSocket
|
|
140
|
-
return "websocket";
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Get the current configuration
|
|
144
|
-
*/
|
|
145
|
-
getConfig() {
|
|
146
|
-
if (!this.config) {
|
|
147
|
-
throw new NotConfiguredError();
|
|
148
|
-
}
|
|
149
|
-
return this.config;
|
|
150
|
-
}
|
|
151
|
-
// ============================================================================
|
|
152
|
-
// Connection Management
|
|
153
|
-
// ============================================================================
|
|
154
|
-
/**
|
|
155
|
-
* Connect to the Ironflow server
|
|
156
|
-
*/
|
|
157
|
-
async connect() {
|
|
158
|
-
this.ensureConfigured();
|
|
159
|
-
await this.subscriptionManager.connect();
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Disconnect from the Ironflow server
|
|
163
|
-
*/
|
|
164
|
-
disconnect() {
|
|
165
|
-
if (this.subscriptionManager) {
|
|
166
|
-
this.subscriptionManager.disconnect();
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Register a callback for connection state changes
|
|
171
|
-
*/
|
|
172
|
-
onConnectionChange(callback) {
|
|
173
|
-
this.ensureConfigured();
|
|
174
|
-
return this.subscriptionManager.onConnectionChange(callback);
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Get current connection state
|
|
178
|
-
*/
|
|
179
|
-
get connectionState() {
|
|
180
|
-
if (!this.subscriptionManager) {
|
|
181
|
-
return "disconnected";
|
|
182
|
-
}
|
|
183
|
-
return this.subscriptionManager.connectionState;
|
|
184
|
-
}
|
|
185
|
-
// ============================================================================
|
|
186
|
-
// Subscriptions
|
|
187
|
-
// ============================================================================
|
|
188
|
-
/**
|
|
189
|
-
* Subscribe to events matching a pattern
|
|
190
|
-
*
|
|
191
|
-
* @example
|
|
192
|
-
* ```typescript
|
|
193
|
-
* // Basic subscription
|
|
194
|
-
* const sub = await ironflow.subscribe('events:order.*', {
|
|
195
|
-
* onEvent: (event) => console.log(event),
|
|
196
|
-
* });
|
|
197
|
-
*
|
|
198
|
-
* // Multiple patterns
|
|
199
|
-
* const sub = await ironflow.subscribe(['system.run.*', 'events:order.*'], {
|
|
200
|
-
* onEvent: (event) => { ... }
|
|
201
|
-
* });
|
|
202
|
-
*
|
|
203
|
-
* // With options
|
|
204
|
-
* const sub = await ironflow.subscribe('events:*', {
|
|
205
|
-
* onEvent: (e) => { ... },
|
|
206
|
-
* replay: 10,
|
|
207
|
-
* trackState: true,
|
|
208
|
-
* ackMode: 'manual',
|
|
209
|
-
* });
|
|
210
|
-
* ```
|
|
211
|
-
*/
|
|
212
|
-
subscribe(pattern, callbacksAndOptions) {
|
|
213
|
-
this.ensureConfigured();
|
|
214
|
-
return this.subscriptionManager.subscribe(pattern, callbacksAndOptions);
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Create a subscription group for batch management
|
|
218
|
-
*
|
|
219
|
-
* @example
|
|
220
|
-
* ```typescript
|
|
221
|
-
* const group = ironflow.subscriptionGroup();
|
|
222
|
-
* await group.add('system.run.*', { onEvent: handleRun });
|
|
223
|
-
* await group.add('events:payment.*', { onEvent: handlePayment });
|
|
224
|
-
* // Later:
|
|
225
|
-
* group.unsubscribeAll();
|
|
226
|
-
* ```
|
|
227
|
-
*/
|
|
228
|
-
subscriptionGroup() {
|
|
229
|
-
this.ensureConfigured();
|
|
230
|
-
return this.subscriptionManager.createGroup();
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Register a global error handler
|
|
234
|
-
*/
|
|
235
|
-
onError(callback) {
|
|
236
|
-
this.ensureConfigured();
|
|
237
|
-
return this.subscriptionManager.onError(callback);
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Number of currently active subscriptions. Useful for leak audits and
|
|
241
|
-
* diagnostics. Returns 0 if the client is not configured.
|
|
242
|
-
*/
|
|
243
|
-
getActiveSubscriptionCount() {
|
|
244
|
-
return this.subscriptionManager?.activeSubscriptionCount ?? 0;
|
|
245
|
-
}
|
|
246
|
-
// ============================================================================
|
|
247
|
-
// Workflow Operations
|
|
248
|
-
// ============================================================================
|
|
249
|
-
/**
|
|
250
|
-
* Invoke a workflow function by ID
|
|
251
|
-
*
|
|
252
|
-
* @example
|
|
253
|
-
* ```typescript
|
|
254
|
-
* const run = await ironflow.invoke<OrderInput, OrderOutput>('process-order', {
|
|
255
|
-
* data: { orderId: '123' }
|
|
256
|
-
* });
|
|
257
|
-
* ```
|
|
258
|
-
*/
|
|
259
|
-
async invoke(functionId, options) {
|
|
260
|
-
this.ensureConfigured();
|
|
261
|
-
const response = await this.request(TriggerResponseSchema, "POST", "/ironflow.v1.IronflowService/Trigger", {
|
|
262
|
-
event: functionId,
|
|
263
|
-
data: options.data,
|
|
264
|
-
idempotency_key: options.idempotencyKey,
|
|
265
|
-
});
|
|
266
|
-
return {
|
|
267
|
-
runIds: response.runIds ?? [],
|
|
268
|
-
eventId: response.eventId,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Get run status
|
|
273
|
-
*/
|
|
274
|
-
async getRun(runId) {
|
|
275
|
-
this.ensureConfigured();
|
|
276
|
-
const response = await this.request(RunResponseSchema, "POST", "/ironflow.v1.IronflowService/GetRun", { id: runId });
|
|
277
|
-
return this.mapRunResponse(response);
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* List runs with filtering
|
|
281
|
-
*/
|
|
282
|
-
async listRuns(options) {
|
|
283
|
-
this.ensureConfigured();
|
|
284
|
-
const response = await this.request(ListRunsResponseSchema, "POST", "/ironflow.v1.IronflowService/ListRuns", {
|
|
285
|
-
function_id: options?.functionId,
|
|
286
|
-
status: options?.status?.toUpperCase(),
|
|
287
|
-
limit: options?.limit,
|
|
288
|
-
cursor: options?.cursor,
|
|
289
|
-
});
|
|
290
|
-
return {
|
|
291
|
-
runs: (response.runs ?? []).map((r) => this.mapRunResponse(r)),
|
|
292
|
-
nextCursor: response.nextCursor,
|
|
293
|
-
totalCount: response.totalCount ?? 0,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Cancel a running run
|
|
298
|
-
*/
|
|
299
|
-
async cancelRun(runId, reason) {
|
|
300
|
-
this.ensureConfigured();
|
|
301
|
-
const response = await this.request(RunResponseSchema, "POST", "/ironflow.v1.IronflowService/CancelRun", { id: runId, reason });
|
|
302
|
-
return this.mapRunResponse(response);
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Retry a failed run
|
|
306
|
-
*/
|
|
307
|
-
async retryRun(runId, fromStep) {
|
|
308
|
-
this.ensureConfigured();
|
|
309
|
-
const response = await this.request(RunResponseSchema, "POST", "/ironflow.v1.IronflowService/RetryRun", { id: runId, fromStep });
|
|
310
|
-
return this.mapRunResponse(response);
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Patch a step's output (hot patching)
|
|
314
|
-
*/
|
|
315
|
-
async patchStep(stepId, output, reason) {
|
|
316
|
-
this.ensureConfigured();
|
|
317
|
-
const url = `${this.config.serverUrl}/api/v1/steps/patch`;
|
|
318
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
319
|
-
const controller = new AbortController();
|
|
320
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
321
|
-
try {
|
|
322
|
-
const headers = {
|
|
323
|
-
"Content-Type": "application/json",
|
|
324
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
325
|
-
};
|
|
326
|
-
if (this.config.auth?.apiKey) {
|
|
327
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
328
|
-
}
|
|
329
|
-
const response = await fetch(url, {
|
|
330
|
-
method: "POST",
|
|
331
|
-
headers,
|
|
332
|
-
body: JSON.stringify({ step_id: stepId, output, reason: reason || "" }),
|
|
333
|
-
signal: controller.signal,
|
|
334
|
-
});
|
|
335
|
-
if (!response.ok) {
|
|
336
|
-
const error = safeJsonParse(await response.text());
|
|
337
|
-
throw new IronflowError(error?.message || `Patch step failed: ${response.status}`, { code: error?.code || "PATCH_FAILED" });
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
finally {
|
|
341
|
-
clearTimeout(timeoutId);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Resume a paused or failed run
|
|
346
|
-
*/
|
|
347
|
-
async resumeRun(runId, fromStep) {
|
|
348
|
-
this.ensureConfigured();
|
|
349
|
-
const url = `${this.config.serverUrl}/api/v1/runs/resume`;
|
|
350
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
351
|
-
const controller = new AbortController();
|
|
352
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
353
|
-
try {
|
|
354
|
-
const headers = {
|
|
355
|
-
"Content-Type": "application/json",
|
|
356
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
357
|
-
};
|
|
358
|
-
if (this.config.auth?.apiKey) {
|
|
359
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
360
|
-
}
|
|
361
|
-
const response = await fetch(url, {
|
|
362
|
-
method: "POST",
|
|
363
|
-
headers,
|
|
364
|
-
body: JSON.stringify({ run_id: runId, from_step: fromStep || "" }),
|
|
365
|
-
signal: controller.signal,
|
|
366
|
-
});
|
|
367
|
-
if (!response.ok) {
|
|
368
|
-
const error = safeJsonParse(await response.text());
|
|
369
|
-
throw new IronflowError(error?.message || `Resume run failed: ${response.status}`, { code: error?.code || "RESUME_FAILED" });
|
|
370
|
-
}
|
|
371
|
-
return response.json();
|
|
372
|
-
}
|
|
373
|
-
finally {
|
|
374
|
-
clearTimeout(timeoutId);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Pause a running workflow at the next step boundary (scoped injection).
|
|
379
|
-
*/
|
|
380
|
-
async pauseRun(runId) {
|
|
381
|
-
this.ensureConfigured();
|
|
382
|
-
const url = `${this.config.serverUrl}/ironflow.v1.IronflowService/PauseRun`;
|
|
383
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
384
|
-
const controller = new AbortController();
|
|
385
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
386
|
-
try {
|
|
387
|
-
const headers = {
|
|
388
|
-
"Content-Type": "application/json",
|
|
389
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
390
|
-
};
|
|
391
|
-
if (this.config.auth?.apiKey) {
|
|
392
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
393
|
-
}
|
|
394
|
-
const response = await fetch(url, {
|
|
395
|
-
method: "POST",
|
|
396
|
-
headers,
|
|
397
|
-
body: JSON.stringify({ run_id: runId }),
|
|
398
|
-
signal: controller.signal,
|
|
399
|
-
});
|
|
400
|
-
if (!response.ok) {
|
|
401
|
-
const error = safeJsonParse(await response.text());
|
|
402
|
-
throw new IronflowError(error?.message || `Pause run failed: ${response.status}`, { code: error?.code || "PAUSE_FAILED" });
|
|
403
|
-
}
|
|
404
|
-
return response.json();
|
|
405
|
-
}
|
|
406
|
-
finally {
|
|
407
|
-
clearTimeout(timeoutId);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Get the paused state of a run, including completed steps and next step hint.
|
|
412
|
-
*/
|
|
413
|
-
async getPausedState(runId) {
|
|
414
|
-
this.ensureConfigured();
|
|
415
|
-
const url = `${this.config.serverUrl}/ironflow.v1.IronflowService/GetPausedState`;
|
|
416
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
417
|
-
const controller = new AbortController();
|
|
418
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
419
|
-
try {
|
|
420
|
-
const headers = {
|
|
421
|
-
"Content-Type": "application/json",
|
|
422
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
423
|
-
};
|
|
424
|
-
if (this.config.auth?.apiKey) {
|
|
425
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
426
|
-
}
|
|
427
|
-
const response = await fetch(url, {
|
|
428
|
-
method: "POST",
|
|
429
|
-
headers,
|
|
430
|
-
body: JSON.stringify({ run_id: runId }),
|
|
431
|
-
signal: controller.signal,
|
|
432
|
-
});
|
|
433
|
-
if (!response.ok) {
|
|
434
|
-
const error = safeJsonParse(await response.text());
|
|
435
|
-
throw new IronflowError(error?.message || `Get paused state failed: ${response.status}`, { code: error?.code || "GET_PAUSED_STATE_FAILED" });
|
|
436
|
-
}
|
|
437
|
-
const data = (await response.json());
|
|
438
|
-
return {
|
|
439
|
-
steps: (data.steps || []).map((s) => ({
|
|
440
|
-
id: s.id,
|
|
441
|
-
name: s.name,
|
|
442
|
-
output: s.output ? JSON.parse(s.output) : null,
|
|
443
|
-
injected: s.injected,
|
|
444
|
-
completedAt: s.completed_at,
|
|
445
|
-
})),
|
|
446
|
-
nextStepHint: data.next_step_hint,
|
|
447
|
-
pauseReason: data.pause_reason,
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
finally {
|
|
451
|
-
clearTimeout(timeoutId);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Inject new output for a step in a paused run (scoped injection).
|
|
456
|
-
*/
|
|
457
|
-
async injectStepOutput(runId, stepId, newOutput, reason) {
|
|
458
|
-
this.ensureConfigured();
|
|
459
|
-
const url = `${this.config.serverUrl}/ironflow.v1.IronflowService/InjectStepOutput`;
|
|
460
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
461
|
-
const controller = new AbortController();
|
|
462
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
463
|
-
try {
|
|
464
|
-
const headers = {
|
|
465
|
-
"Content-Type": "application/json",
|
|
466
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
467
|
-
};
|
|
468
|
-
if (this.config.auth?.apiKey) {
|
|
469
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
470
|
-
}
|
|
471
|
-
const response = await fetch(url, {
|
|
472
|
-
method: "POST",
|
|
473
|
-
headers,
|
|
474
|
-
body: JSON.stringify({
|
|
475
|
-
run_id: runId,
|
|
476
|
-
step_id: stepId,
|
|
477
|
-
new_output: JSON.stringify(newOutput),
|
|
478
|
-
reason: reason ?? "",
|
|
479
|
-
}),
|
|
480
|
-
signal: controller.signal,
|
|
481
|
-
});
|
|
482
|
-
if (!response.ok) {
|
|
483
|
-
const error = safeJsonParse(await response.text());
|
|
484
|
-
throw new IronflowError(error?.message || `Inject step output failed: ${response.status}`, { code: error?.code || "INJECT_FAILED" });
|
|
485
|
-
}
|
|
486
|
-
const data = (await response.json());
|
|
487
|
-
return {
|
|
488
|
-
stepId: data.step_id,
|
|
489
|
-
previousOutput: data.previous_output
|
|
490
|
-
? JSON.parse(data.previous_output)
|
|
491
|
-
: null,
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
finally {
|
|
495
|
-
clearTimeout(timeoutId);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
// ==========================================================================
|
|
499
|
-
// Time-Travel Debugging
|
|
500
|
-
// ==========================================================================
|
|
501
|
-
/**
|
|
502
|
-
* Get the reconstructed state of a run at a specific timestamp.
|
|
503
|
-
*/
|
|
504
|
-
async getRunStateAt(runId, timestamp) {
|
|
505
|
-
this.ensureConfigured();
|
|
506
|
-
const url = `${this.config.serverUrl}/ironflow.v1.TimeTravelService/GetRunStateAt`;
|
|
507
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
508
|
-
const controller = new AbortController();
|
|
509
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
510
|
-
try {
|
|
511
|
-
const headers = {
|
|
512
|
-
"Content-Type": "application/json",
|
|
513
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
514
|
-
};
|
|
515
|
-
if (this.config.auth?.apiKey) {
|
|
516
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
517
|
-
}
|
|
518
|
-
const response = await fetch(url, {
|
|
519
|
-
method: "POST",
|
|
520
|
-
headers,
|
|
521
|
-
body: JSON.stringify({
|
|
522
|
-
runId,
|
|
523
|
-
timestamp: timestamp.toISOString(),
|
|
524
|
-
}),
|
|
525
|
-
signal: controller.signal,
|
|
526
|
-
});
|
|
527
|
-
if (!response.ok) {
|
|
528
|
-
const error = safeJsonParse(await response.text());
|
|
529
|
-
throw new IronflowError(error?.message || `Get run state failed: ${response.status}`, { code: error?.code || "GET_RUN_STATE_FAILED" });
|
|
530
|
-
}
|
|
531
|
-
const data = (await response.json());
|
|
532
|
-
const s = data.snapshot;
|
|
533
|
-
return {
|
|
534
|
-
runId: s.runId,
|
|
535
|
-
functionId: s.functionId,
|
|
536
|
-
status: s.status,
|
|
537
|
-
input: s.input ? JSON.parse(atob(s.input)) : null,
|
|
538
|
-
steps: (s.steps || []).map((step) => ({
|
|
539
|
-
stepId: step.stepId,
|
|
540
|
-
name: step.name,
|
|
541
|
-
type: step.type,
|
|
542
|
-
sequence: step.sequence,
|
|
543
|
-
status: step.status,
|
|
544
|
-
output: step.output ? JSON.parse(atob(step.output)) : null,
|
|
545
|
-
error: step.error ? JSON.parse(atob(step.error)) : null,
|
|
546
|
-
originalOutput: step.originalOutput ? JSON.parse(atob(step.originalOutput)) : null,
|
|
547
|
-
startedAt: step.startedAt ? new Date(step.startedAt) : null,
|
|
548
|
-
completedAt: step.completedAt ? new Date(step.completedAt) : null,
|
|
549
|
-
durationMs: step.durationMs ?? null,
|
|
550
|
-
injected: step.injected,
|
|
551
|
-
patched: step.patched,
|
|
552
|
-
})),
|
|
553
|
-
timestamp: new Date(s.timestamp),
|
|
554
|
-
createdAt: s.createdAt ? new Date(s.createdAt) : null,
|
|
555
|
-
};
|
|
556
|
-
}
|
|
557
|
-
finally {
|
|
558
|
-
clearTimeout(timeoutId);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Get the timeline of audit events for a run.
|
|
563
|
-
*/
|
|
564
|
-
async getRunTimeline(runId) {
|
|
565
|
-
this.ensureConfigured();
|
|
566
|
-
const url = `${this.config.serverUrl}/ironflow.v1.TimeTravelService/GetRunTimeline`;
|
|
567
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
568
|
-
const controller = new AbortController();
|
|
569
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
570
|
-
try {
|
|
571
|
-
const headers = {
|
|
572
|
-
"Content-Type": "application/json",
|
|
573
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
574
|
-
};
|
|
575
|
-
if (this.config.auth?.apiKey) {
|
|
576
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
577
|
-
}
|
|
578
|
-
const response = await fetch(url, {
|
|
579
|
-
method: "POST",
|
|
580
|
-
headers,
|
|
581
|
-
body: JSON.stringify({ runId }),
|
|
582
|
-
signal: controller.signal,
|
|
583
|
-
});
|
|
584
|
-
if (!response.ok) {
|
|
585
|
-
const error = safeJsonParse(await response.text());
|
|
586
|
-
throw new IronflowError(error?.message || `Get run timeline failed: ${response.status}`, { code: error?.code || "GET_TIMELINE_FAILED" });
|
|
587
|
-
}
|
|
588
|
-
const data = (await response.json());
|
|
589
|
-
return (data.events || []).map((e) => ({
|
|
590
|
-
id: e.id,
|
|
591
|
-
eventType: e.eventType,
|
|
592
|
-
stepId: e.stepId || "",
|
|
593
|
-
stepName: e.stepName || "",
|
|
594
|
-
summary: e.summary,
|
|
595
|
-
significant: e.significant,
|
|
596
|
-
timestamp: new Date(e.timestamp),
|
|
597
|
-
}));
|
|
598
|
-
}
|
|
599
|
-
finally {
|
|
600
|
-
clearTimeout(timeoutId);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Get the output of a specific step at a specific timestamp.
|
|
605
|
-
*/
|
|
606
|
-
async getStepOutputAt(runId, stepId, timestamp) {
|
|
607
|
-
this.ensureConfigured();
|
|
608
|
-
const url = `${this.config.serverUrl}/ironflow.v1.TimeTravelService/GetStepOutputAt`;
|
|
609
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
610
|
-
const controller = new AbortController();
|
|
611
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
612
|
-
try {
|
|
613
|
-
const headers = {
|
|
614
|
-
"Content-Type": "application/json",
|
|
615
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
616
|
-
};
|
|
617
|
-
if (this.config.auth?.apiKey) {
|
|
618
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
619
|
-
}
|
|
620
|
-
const response = await fetch(url, {
|
|
621
|
-
method: "POST",
|
|
622
|
-
headers,
|
|
623
|
-
body: JSON.stringify({
|
|
624
|
-
runId,
|
|
625
|
-
stepId,
|
|
626
|
-
timestamp: timestamp.toISOString(),
|
|
627
|
-
}),
|
|
628
|
-
signal: controller.signal,
|
|
629
|
-
});
|
|
630
|
-
if (!response.ok) {
|
|
631
|
-
const error = safeJsonParse(await response.text());
|
|
632
|
-
throw new IronflowError(error?.message || `Get step output failed: ${response.status}`, { code: error?.code || "GET_STEP_OUTPUT_FAILED" });
|
|
633
|
-
}
|
|
634
|
-
const data = (await response.json());
|
|
635
|
-
return {
|
|
636
|
-
stepId: data.stepId,
|
|
637
|
-
status: data.status,
|
|
638
|
-
output: data.output ? JSON.parse(atob(data.output)) : null,
|
|
639
|
-
originalOutput: data.originalOutput ? JSON.parse(atob(data.originalOutput)) : null,
|
|
640
|
-
patched: data.patched,
|
|
641
|
-
injected: data.injected,
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
finally {
|
|
645
|
-
clearTimeout(timeoutId);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* List registered functions
|
|
650
|
-
*/
|
|
651
|
-
async listFunctions() {
|
|
652
|
-
this.ensureConfigured();
|
|
653
|
-
const url = `${this.config.serverUrl}/api/v1/functions`;
|
|
654
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
655
|
-
const controller = new AbortController();
|
|
656
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
657
|
-
try {
|
|
658
|
-
const headers = {
|
|
659
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
660
|
-
};
|
|
661
|
-
if (this.config.auth?.apiKey) {
|
|
662
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
663
|
-
}
|
|
664
|
-
const response = await fetch(url, {
|
|
665
|
-
method: "GET",
|
|
666
|
-
headers,
|
|
667
|
-
signal: controller.signal,
|
|
668
|
-
});
|
|
669
|
-
if (!response.ok) {
|
|
670
|
-
throw new IronflowError(`List functions failed: ${response.status}`, { code: "LIST_FUNCTIONS_FAILED" });
|
|
671
|
-
}
|
|
672
|
-
const data = await response.json();
|
|
673
|
-
return data.functions || [];
|
|
674
|
-
}
|
|
675
|
-
finally {
|
|
676
|
-
clearTimeout(timeoutId);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* List connected workers
|
|
681
|
-
*/
|
|
682
|
-
async listWorkers() {
|
|
683
|
-
this.ensureConfigured();
|
|
684
|
-
const url = `${this.config.serverUrl}/api/v1/workers`;
|
|
685
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
686
|
-
const controller = new AbortController();
|
|
687
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
688
|
-
try {
|
|
689
|
-
const headers = {
|
|
690
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
691
|
-
};
|
|
692
|
-
if (this.config.auth?.apiKey) {
|
|
693
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
694
|
-
}
|
|
695
|
-
const response = await fetch(url, {
|
|
696
|
-
method: "GET",
|
|
697
|
-
headers,
|
|
698
|
-
signal: controller.signal,
|
|
699
|
-
});
|
|
700
|
-
if (!response.ok) {
|
|
701
|
-
throw new IronflowError(`List workers failed: ${response.status}`, { code: "LIST_WORKERS_FAILED" });
|
|
702
|
-
}
|
|
703
|
-
const data = await response.json();
|
|
704
|
-
return data.workers || [];
|
|
705
|
-
}
|
|
706
|
-
finally {
|
|
707
|
-
clearTimeout(timeoutId);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* Health check
|
|
712
|
-
*/
|
|
713
|
-
async health() {
|
|
714
|
-
this.ensureConfigured();
|
|
715
|
-
const url = `${this.config.serverUrl}/health`;
|
|
716
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
717
|
-
const controller = new AbortController();
|
|
718
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
719
|
-
try {
|
|
720
|
-
const response = await fetch(url, {
|
|
721
|
-
method: "GET",
|
|
722
|
-
signal: controller.signal,
|
|
723
|
-
});
|
|
724
|
-
if (!response.ok) {
|
|
725
|
-
throw new IronflowError(`Health check failed: ${response.status}`, { code: "HEALTH_FAILED" });
|
|
726
|
-
}
|
|
727
|
-
return response.json();
|
|
728
|
-
}
|
|
729
|
-
finally {
|
|
730
|
-
clearTimeout(timeoutId);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Get server capabilities
|
|
735
|
-
*/
|
|
736
|
-
async getCapabilities() {
|
|
737
|
-
this.ensureConfigured();
|
|
738
|
-
const url = `${this.config.serverUrl}/api/v1/capabilities`;
|
|
739
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
740
|
-
const controller = new AbortController();
|
|
741
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
742
|
-
try {
|
|
743
|
-
const response = await fetch(url, {
|
|
744
|
-
method: "GET",
|
|
745
|
-
signal: controller.signal,
|
|
746
|
-
});
|
|
747
|
-
if (!response.ok) {
|
|
748
|
-
throw new IronflowError(`Get capabilities failed: ${response.status}`, { code: "CAPABILITIES_FAILED" });
|
|
749
|
-
}
|
|
750
|
-
return response.json();
|
|
751
|
-
}
|
|
752
|
-
finally {
|
|
753
|
-
clearTimeout(timeoutId);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
// ============================================================================
|
|
757
|
-
// Event Emission
|
|
758
|
-
// ============================================================================
|
|
759
|
-
/**
|
|
760
|
-
* Emit an event
|
|
761
|
-
*
|
|
762
|
-
* @example
|
|
763
|
-
* ```typescript
|
|
764
|
-
* await ironflow.emit('order.approved', {
|
|
765
|
-
* orderId: '123',
|
|
766
|
-
* approvedBy: 'user@example.com'
|
|
767
|
-
* });
|
|
768
|
-
* ```
|
|
769
|
-
*/
|
|
770
|
-
async emit(eventName, data, options) {
|
|
771
|
-
this.ensureConfigured();
|
|
772
|
-
const response = await this.request(TriggerResponseSchema, "POST", "/ironflow.v1.PubSubService/Emit", {
|
|
773
|
-
event: eventName,
|
|
774
|
-
data,
|
|
775
|
-
...(options?.version ? { version: options.version } : {}),
|
|
776
|
-
idempotency_key: options?.idempotencyKey,
|
|
777
|
-
metadata: options?.metadata,
|
|
778
|
-
namespace: options?.namespace,
|
|
779
|
-
});
|
|
780
|
-
return {
|
|
781
|
-
runIds: response.runIds ?? [],
|
|
782
|
-
eventId: response.eventId,
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
/**
|
|
786
|
-
* Emit an event synchronously — waits for the triggered run to complete and returns the result.
|
|
787
|
-
*
|
|
788
|
-
* Calls the TriggerSync endpoint, which blocks until the run finishes or the timeout elapses.
|
|
789
|
-
* Throws RunFailedError if the run fails, RunCancelledError if it is cancelled.
|
|
790
|
-
*
|
|
791
|
-
* @example
|
|
792
|
-
* ```typescript
|
|
793
|
-
* const result = await ironflow.emitSync("order.placed", { orderId: "123" });
|
|
794
|
-
* console.log("Output:", result.output);
|
|
795
|
-
* ```
|
|
796
|
-
*/
|
|
797
|
-
async emitSync(eventName, data, options) {
|
|
798
|
-
this.ensureConfigured();
|
|
799
|
-
const timeout = options?.timeout ?? 30000;
|
|
800
|
-
const response = await this.request(TriggerSyncResponseSchema, "POST", "/ironflow.v1.IronflowService/TriggerSync", { event: eventName, data, timeout_ms: timeout });
|
|
801
|
-
const results = response.results;
|
|
802
|
-
if (!results?.length) {
|
|
803
|
-
throw new IronflowError("No results returned from TriggerSync", { code: "NO_RESULTS", retryable: false });
|
|
804
|
-
}
|
|
805
|
-
const result = results[0];
|
|
806
|
-
if (!result) {
|
|
807
|
-
throw new IronflowError("No results returned from TriggerSync", { code: "NO_RESULTS", retryable: false });
|
|
808
|
-
}
|
|
809
|
-
if (result.status === "failed") {
|
|
810
|
-
throw new RunFailedError(result.runId, result.error);
|
|
811
|
-
}
|
|
812
|
-
if (result.status === "cancelled") {
|
|
813
|
-
throw new RunCancelledError(result.runId);
|
|
814
|
-
}
|
|
815
|
-
return {
|
|
816
|
-
runId: result.runId,
|
|
817
|
-
functionId: result.functionId,
|
|
818
|
-
status: result.status,
|
|
819
|
-
output: result.output,
|
|
820
|
-
durationMs: result.durationMs,
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
// ============================================================================
|
|
824
|
-
// Consumer Groups
|
|
825
|
-
// ============================================================================
|
|
826
|
-
/**
|
|
827
|
-
* Join a consumer group for load-balanced event processing
|
|
828
|
-
*/
|
|
829
|
-
async joinConsumerGroup(groupName, pattern, callbacksAndOptions) {
|
|
830
|
-
this.ensureConfigured();
|
|
831
|
-
const sub = await this.subscribe(pattern, {
|
|
832
|
-
...callbacksAndOptions,
|
|
833
|
-
consumerGroup: groupName,
|
|
834
|
-
ackMode: "manual",
|
|
835
|
-
});
|
|
836
|
-
return sub;
|
|
837
|
-
}
|
|
838
|
-
// ============================================================================
|
|
839
|
-
// Entity Streams
|
|
840
|
-
// ============================================================================
|
|
841
|
-
/**
|
|
842
|
-
* Entity stream operations
|
|
843
|
-
*
|
|
844
|
-
* @example
|
|
845
|
-
* ```typescript
|
|
846
|
-
* // Append an event to a stream
|
|
847
|
-
* const result = await ironflow.streams.append("order-123", {
|
|
848
|
-
* name: "order.created",
|
|
849
|
-
* data: { total: 100 },
|
|
850
|
-
* entityType: "order",
|
|
851
|
-
* });
|
|
852
|
-
*
|
|
853
|
-
* // Read events from a stream
|
|
854
|
-
* const { events } = await ironflow.streams.read("order-123", { limit: 10 });
|
|
855
|
-
*
|
|
856
|
-
* // Get stream info
|
|
857
|
-
* const info = await ironflow.streams.getInfo("order-123");
|
|
858
|
-
* ```
|
|
859
|
-
*/
|
|
860
|
-
streams = {
|
|
861
|
-
/**
|
|
862
|
-
* Append an event to an entity stream
|
|
863
|
-
*/
|
|
864
|
-
append: async (entityId, input, options) => {
|
|
865
|
-
this.ensureConfigured();
|
|
866
|
-
const body = {
|
|
867
|
-
entity_id: entityId,
|
|
868
|
-
entity_type: input.entityType,
|
|
869
|
-
event_name: input.name,
|
|
870
|
-
data: input.data,
|
|
871
|
-
expected_version: options?.expectedVersion ?? -1,
|
|
872
|
-
idempotency_key: options?.idempotencyKey ?? "",
|
|
873
|
-
version: options?.version ?? 1,
|
|
874
|
-
};
|
|
875
|
-
if (options?.metadata !== undefined) {
|
|
876
|
-
body.metadata = options.metadata;
|
|
877
|
-
}
|
|
878
|
-
const response = await this.streamRequest("/ironflow.v1.EntityStreamService/AppendEvent", body);
|
|
879
|
-
return {
|
|
880
|
-
entityVersion: Number(response.entityVersion ?? 0),
|
|
881
|
-
eventId: response.eventId ?? "",
|
|
882
|
-
};
|
|
883
|
-
},
|
|
884
|
-
/**
|
|
885
|
-
* Read events from an entity stream
|
|
886
|
-
*/
|
|
887
|
-
read: async (entityId, options) => {
|
|
888
|
-
this.ensureConfigured();
|
|
889
|
-
const response = await this.streamRequest("/ironflow.v1.EntityStreamService/ReadStream", {
|
|
890
|
-
entity_id: entityId,
|
|
891
|
-
from_version: options?.fromVersion ?? 0,
|
|
892
|
-
limit: options?.limit ?? 0,
|
|
893
|
-
direction: options?.direction ?? "forward",
|
|
894
|
-
});
|
|
895
|
-
return {
|
|
896
|
-
events: (response.events ?? []).map((e) => ({
|
|
897
|
-
id: e.id,
|
|
898
|
-
name: e.name,
|
|
899
|
-
data: e.data ?? {},
|
|
900
|
-
entityVersion: Number(e.entityVersion ?? 0),
|
|
901
|
-
version: e.version,
|
|
902
|
-
timestamp: e.timestamp,
|
|
903
|
-
source: e.source,
|
|
904
|
-
metadata: e.metadata,
|
|
905
|
-
})),
|
|
906
|
-
totalCount: Number(response.totalCount ?? 0),
|
|
907
|
-
};
|
|
908
|
-
},
|
|
909
|
-
/**
|
|
910
|
-
* Get information about an entity stream.
|
|
911
|
-
*
|
|
912
|
-
* Returns `null` if no events have been written to this stream yet — safe to
|
|
913
|
-
* pass `expectedVersion: 0` to `append()` in that case to create the first event.
|
|
914
|
-
*
|
|
915
|
-
* @example
|
|
916
|
-
* ```typescript
|
|
917
|
-
* const info = await ironflow.streams.getInfo("order-123");
|
|
918
|
-
* await ironflow.streams.append("order-123", event, {
|
|
919
|
-
* expectedVersion: info ? info.version : 0,
|
|
920
|
-
* });
|
|
921
|
-
* ```
|
|
922
|
-
*/
|
|
923
|
-
getInfo: async (entityId) => {
|
|
924
|
-
this.ensureConfigured();
|
|
925
|
-
try {
|
|
926
|
-
const response = await this.streamRequest("/ironflow.v1.EntityStreamService/GetStreamInfo", {
|
|
927
|
-
entity_id: entityId,
|
|
928
|
-
});
|
|
929
|
-
return {
|
|
930
|
-
entityId: response.entityId ?? "",
|
|
931
|
-
entityType: response.entityType ?? "",
|
|
932
|
-
version: Number(response.version ?? 0),
|
|
933
|
-
eventCount: Number(response.eventCount ?? 0),
|
|
934
|
-
createdAt: response.createdAt ?? "",
|
|
935
|
-
updatedAt: response.updatedAt ?? "",
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
catch (err) {
|
|
939
|
-
if (err instanceof IronflowError && err.message === "stream not found") {
|
|
940
|
-
return null;
|
|
941
|
-
}
|
|
942
|
-
throw err;
|
|
943
|
-
}
|
|
944
|
-
},
|
|
945
|
-
/**
|
|
946
|
-
* Create a snapshot of the materialized state at a specific stream version.
|
|
947
|
-
*/
|
|
948
|
-
createSnapshot: async (entityId, input) => {
|
|
949
|
-
this.ensureConfigured();
|
|
950
|
-
const response = await this.streamRequest("/ironflow.v1.EntityStreamService/CreateSnapshot", {
|
|
951
|
-
entity_id: entityId,
|
|
952
|
-
entity_type: input.entityType,
|
|
953
|
-
entity_version: input.entityVersion,
|
|
954
|
-
state: input.state,
|
|
955
|
-
});
|
|
956
|
-
return { snapshotId: response.snapshotId ?? "" };
|
|
957
|
-
},
|
|
958
|
-
/**
|
|
959
|
-
* Get the latest snapshot at or before a given version.
|
|
960
|
-
*/
|
|
961
|
-
getSnapshot: async (entityId, options) => {
|
|
962
|
-
this.ensureConfigured();
|
|
963
|
-
const response = await this.streamRequest("/ironflow.v1.EntityStreamService/GetSnapshot", {
|
|
964
|
-
entity_id: entityId,
|
|
965
|
-
before_version: options?.beforeVersion ?? 0,
|
|
966
|
-
});
|
|
967
|
-
return {
|
|
968
|
-
snapshotId: response.snapshotId ?? "",
|
|
969
|
-
entityId: response.entityId ?? "",
|
|
970
|
-
entityType: response.entityType ?? "",
|
|
971
|
-
entityVersion: Number(response.entityVersion ?? 0),
|
|
972
|
-
state: response.state ?? {},
|
|
973
|
-
createdAt: response.createdAt ?? "",
|
|
974
|
-
};
|
|
975
|
-
},
|
|
976
|
-
/**
|
|
977
|
-
* Subscribe to real-time events for an entity stream
|
|
978
|
-
*
|
|
979
|
-
* @example
|
|
980
|
-
* ```typescript
|
|
981
|
-
* const sub = await ironflow.streams.subscribe("order-123", {
|
|
982
|
-
* entityType: "order",
|
|
983
|
-
* onEvent: (event) => console.log(event),
|
|
984
|
-
* replay: 100,
|
|
985
|
-
* });
|
|
986
|
-
*
|
|
987
|
-
* // Cleanup
|
|
988
|
-
* sub.unsubscribe();
|
|
989
|
-
* ```
|
|
990
|
-
*/
|
|
991
|
-
subscribe: async (entityId, options) => {
|
|
992
|
-
this.ensureConfigured();
|
|
993
|
-
const pattern = `entity:${options.entityType}.${entityId}.>`;
|
|
994
|
-
const sub = await this.subscribe(pattern, {
|
|
995
|
-
onEvent: (event) => {
|
|
996
|
-
const data = event.data;
|
|
997
|
-
const streamEvent = {
|
|
998
|
-
id: data.id ?? "",
|
|
999
|
-
name: data.name ?? "",
|
|
1000
|
-
data: data.data ?? {},
|
|
1001
|
-
entityVersion: data.entityVersion ?? 0,
|
|
1002
|
-
version: data.version ?? 0,
|
|
1003
|
-
timestamp: data.timestamp ?? "",
|
|
1004
|
-
source: data.source,
|
|
1005
|
-
metadata: data.metadata,
|
|
1006
|
-
};
|
|
1007
|
-
options.onEvent(streamEvent);
|
|
1008
|
-
},
|
|
1009
|
-
onError: options.onError
|
|
1010
|
-
? (info) => options.onError(new Error(info.message))
|
|
1011
|
-
: undefined,
|
|
1012
|
-
replay: options.replay,
|
|
1013
|
-
});
|
|
1014
|
-
return sub;
|
|
1015
|
-
},
|
|
1016
|
-
};
|
|
1017
|
-
// ============================================================================
|
|
1018
|
-
// Projections
|
|
1019
|
-
// ============================================================================
|
|
1020
|
-
/**
|
|
1021
|
-
* Get the current state of a projection
|
|
1022
|
-
*
|
|
1023
|
-
* @example
|
|
1024
|
-
* ```typescript
|
|
1025
|
-
* const result = await ironflow.getProjection<OrderStats>('order-stats');
|
|
1026
|
-
* console.log(result.state); // { totalOrders: 42, ... }
|
|
1027
|
-
*
|
|
1028
|
-
* // With partition
|
|
1029
|
-
* const result = await ironflow.getProjection('order-stats', { partition: 'customer-123' });
|
|
1030
|
-
* ```
|
|
1031
|
-
*/
|
|
1032
|
-
async getProjection(name, options) {
|
|
1033
|
-
this.ensureConfigured();
|
|
1034
|
-
// Normalize empty-string partition to undefined so peel falls back to
|
|
1035
|
-
// "__global__" instead of returning empty-string partition.
|
|
1036
|
-
const partition = options?.partition ? options.partition : undefined;
|
|
1037
|
-
let url = `${this.config.serverUrl}/api/v1/projections/${encodeURIComponent(name)}`;
|
|
1038
|
-
if (partition) {
|
|
1039
|
-
url += `?partition=${encodeURIComponent(partition)}`;
|
|
1040
|
-
}
|
|
1041
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
1042
|
-
const controller = new AbortController();
|
|
1043
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1044
|
-
try {
|
|
1045
|
-
const headers = {
|
|
1046
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1047
|
-
};
|
|
1048
|
-
if (this.config.auth?.apiKey) {
|
|
1049
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1050
|
-
}
|
|
1051
|
-
const response = await fetch(url, {
|
|
1052
|
-
method: "GET",
|
|
1053
|
-
headers,
|
|
1054
|
-
signal: controller.signal,
|
|
1055
|
-
});
|
|
1056
|
-
if (!response.ok) {
|
|
1057
|
-
const error = safeJsonParse(await response.text());
|
|
1058
|
-
throw new IronflowError(error?.message || `Get projection failed: ${response.status}`, { code: error?.code || "GET_PROJECTION_FAILED" });
|
|
1059
|
-
}
|
|
1060
|
-
const data = await response.json();
|
|
1061
|
-
return peelProjectionEnvelope(data, partition);
|
|
1062
|
-
}
|
|
1063
|
-
finally {
|
|
1064
|
-
clearTimeout(timeoutId);
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
/**
|
|
1068
|
-
* Query a SQL-backed projection table with optional filtering, ordering, and pagination.
|
|
1069
|
-
*
|
|
1070
|
-
* @example
|
|
1071
|
-
* ```typescript
|
|
1072
|
-
* const result = await ironflow.querySQLProjection('board', {
|
|
1073
|
-
* where: "status = 'OPEN'",
|
|
1074
|
-
* orderBy: "title ASC",
|
|
1075
|
-
* limit: 50,
|
|
1076
|
-
* });
|
|
1077
|
-
* ```
|
|
1078
|
-
*/
|
|
1079
|
-
async querySQLProjection(name, options) {
|
|
1080
|
-
this.ensureConfigured();
|
|
1081
|
-
const response = await this.streamRequest("/ironflow.v1.ProjectionService/QuerySQLProjection", {
|
|
1082
|
-
name,
|
|
1083
|
-
where: options?.where ?? "",
|
|
1084
|
-
order_by: options?.orderBy ?? "",
|
|
1085
|
-
limit: options?.limit ?? 100,
|
|
1086
|
-
offset: options?.offset ?? 0,
|
|
1087
|
-
});
|
|
1088
|
-
return {
|
|
1089
|
-
columns: response.columns ?? [],
|
|
1090
|
-
rows: (response.rows ?? []).map((r) => r.values),
|
|
1091
|
-
totalCount: Number(response.totalCount ?? 0),
|
|
1092
|
-
};
|
|
1093
|
-
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Get the status of a projection
|
|
1096
|
-
*
|
|
1097
|
-
* @example
|
|
1098
|
-
* ```typescript
|
|
1099
|
-
* const status = await ironflow.getProjectionStatus('order-stats');
|
|
1100
|
-
* console.log(status.status); // 'active' | 'rebuilding' | 'paused' | 'error'
|
|
1101
|
-
* ```
|
|
1102
|
-
*/
|
|
1103
|
-
async getProjectionStatus(name) {
|
|
1104
|
-
this.ensureConfigured();
|
|
1105
|
-
const url = `${this.config.serverUrl}/api/v1/projections/${encodeURIComponent(name)}/status`;
|
|
1106
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
1107
|
-
const controller = new AbortController();
|
|
1108
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1109
|
-
try {
|
|
1110
|
-
const headers = {
|
|
1111
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1112
|
-
};
|
|
1113
|
-
if (this.config.auth?.apiKey) {
|
|
1114
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1115
|
-
}
|
|
1116
|
-
const response = await fetch(url, {
|
|
1117
|
-
method: "GET",
|
|
1118
|
-
headers,
|
|
1119
|
-
signal: controller.signal,
|
|
1120
|
-
});
|
|
1121
|
-
if (!response.ok) {
|
|
1122
|
-
const error = safeJsonParse(await response.text());
|
|
1123
|
-
throw new IronflowError(error?.message || `Get projection status failed: ${response.status}`, { code: error?.code || "GET_PROJECTION_STATUS_FAILED" });
|
|
1124
|
-
}
|
|
1125
|
-
const data = await response.json();
|
|
1126
|
-
return {
|
|
1127
|
-
name: data.name,
|
|
1128
|
-
status: data.status,
|
|
1129
|
-
mode: data.mode,
|
|
1130
|
-
lastEventSeq: data.last_event_seq ?? 0,
|
|
1131
|
-
lag: data.lag ?? 0,
|
|
1132
|
-
errorMessage: data.error_message || undefined,
|
|
1133
|
-
updatedAt: data.updated_at ? new Date(data.updated_at) : new Date(),
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
finally {
|
|
1137
|
-
clearTimeout(timeoutId);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Trigger a rebuild of a projection
|
|
1142
|
-
*
|
|
1143
|
-
* @example
|
|
1144
|
-
* ```typescript
|
|
1145
|
-
* const result = await ironflow.rebuildProjection('order-stats');
|
|
1146
|
-
* ```
|
|
1147
|
-
*/
|
|
1148
|
-
async rebuildProjection(name, options) {
|
|
1149
|
-
this.ensureConfigured();
|
|
1150
|
-
const url = `${this.config.serverUrl}/api/v1/projections/${encodeURIComponent(name)}/rebuild`;
|
|
1151
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
1152
|
-
const controller = new AbortController();
|
|
1153
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1154
|
-
try {
|
|
1155
|
-
const headers = {
|
|
1156
|
-
"Content-Type": "application/json",
|
|
1157
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1158
|
-
};
|
|
1159
|
-
if (this.config.auth?.apiKey) {
|
|
1160
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1161
|
-
}
|
|
1162
|
-
const response = await fetch(url, {
|
|
1163
|
-
method: "POST",
|
|
1164
|
-
headers,
|
|
1165
|
-
body: JSON.stringify({
|
|
1166
|
-
partition: options?.partition,
|
|
1167
|
-
from_event_id: options?.fromEventId,
|
|
1168
|
-
dry_run: options?.dryRun,
|
|
1169
|
-
}),
|
|
1170
|
-
signal: controller.signal,
|
|
1171
|
-
});
|
|
1172
|
-
if (!response.ok) {
|
|
1173
|
-
const error = safeJsonParse(await response.text());
|
|
1174
|
-
throw new IronflowError(error?.message || `Rebuild projection failed: ${response.status}`, { code: error?.code || "REBUILD_PROJECTION_FAILED" });
|
|
1175
|
-
}
|
|
1176
|
-
return response.json();
|
|
1177
|
-
}
|
|
1178
|
-
finally {
|
|
1179
|
-
clearTimeout(timeoutId);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
/**
|
|
1183
|
-
* Wait until the named projection has processed events up to `minSeq`,
|
|
1184
|
-
* or the timeout elapses. Read-your-writes primitive for CQRS UIs:
|
|
1185
|
-
* pair with `sequence` from an `appendEntityEvent` response.
|
|
1186
|
-
*
|
|
1187
|
-
* Issue #473.
|
|
1188
|
-
*/
|
|
1189
|
-
async waitForProjectionCatchup(name, opts) {
|
|
1190
|
-
this.ensureConfigured();
|
|
1191
|
-
const params = new URLSearchParams();
|
|
1192
|
-
params.set("minSeq", String(opts.minSeq));
|
|
1193
|
-
if (opts.timeoutMs !== undefined)
|
|
1194
|
-
params.set("timeout", String(opts.timeoutMs));
|
|
1195
|
-
if (opts.partition)
|
|
1196
|
-
params.set("partition", opts.partition);
|
|
1197
|
-
const url = `${this.config.serverUrl}/api/v1/projections/${encodeURIComponent(name)}/catchup?${params.toString()}`;
|
|
1198
|
-
// The wait timeout may exceed the normal client timeout — use the
|
|
1199
|
-
// opts.timeoutMs (plus a small grace period) so the client doesn't
|
|
1200
|
-
// abort the request before the server gets a chance to time out.
|
|
1201
|
-
const clientTimeoutMs = (opts.timeoutMs ?? 30_000) + 2_000;
|
|
1202
|
-
const controller = new AbortController();
|
|
1203
|
-
const timeoutId = setTimeout(() => controller.abort(), clientTimeoutMs);
|
|
1204
|
-
try {
|
|
1205
|
-
const headers = {
|
|
1206
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1207
|
-
};
|
|
1208
|
-
if (this.config.auth?.apiKey) {
|
|
1209
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1210
|
-
}
|
|
1211
|
-
const response = await fetch(url, {
|
|
1212
|
-
method: "GET",
|
|
1213
|
-
headers,
|
|
1214
|
-
signal: controller.signal,
|
|
1215
|
-
});
|
|
1216
|
-
if (!response.ok) {
|
|
1217
|
-
const error = safeJsonParse(await response.text());
|
|
1218
|
-
throw new IronflowError(error?.message || `Wait for projection catchup failed: ${response.status}`, { code: error?.code || "WAIT_FOR_PROJECTION_FAILED" });
|
|
1219
|
-
}
|
|
1220
|
-
return (await response.json());
|
|
1221
|
-
}
|
|
1222
|
-
finally {
|
|
1223
|
-
clearTimeout(timeoutId);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
/**
|
|
1227
|
-
* Streaming wait for a projection to catch up to `minSeq` (issue #476).
|
|
1228
|
-
*
|
|
1229
|
-
* Returns an `AsyncIterable<WaitProgress>` that yields a frame each time
|
|
1230
|
-
* the projection cursor advances, plus one terminal frame (`terminal:
|
|
1231
|
-
* true`) on catch-up, timeout, or error, after which iteration ends.
|
|
1232
|
-
* Heartbeats are filtered inside the SDK — callers never see them.
|
|
1233
|
-
*
|
|
1234
|
-
* Unlike the unary `waitForProjectionCatchup`, this path uses a
|
|
1235
|
-
* server-streaming ConnectRPC connection. The server caps the wait at
|
|
1236
|
-
* 300s regardless of `timeoutMs`. Pass `opts.signal` to cancel early
|
|
1237
|
-
* (e.g., on React unmount); breaking out of `for await` also aborts
|
|
1238
|
-
* the stream.
|
|
1239
|
-
*
|
|
1240
|
-
* Example:
|
|
1241
|
-
* ```ts
|
|
1242
|
-
* for await (const p of ironflow.waitForProjectionCatchupStream("order-view", { minSeq: 42 })) {
|
|
1243
|
-
* if (p.terminal) {
|
|
1244
|
-
* if (p.caughtUp) return;
|
|
1245
|
-
* throw new Error(`wait failed: timedOut=${p.timedOut} error=${p.error}`);
|
|
1246
|
-
* }
|
|
1247
|
-
* console.log(`progress: ${p.currentSeq}/${p.targetSeq} behind=${p.behindByEvents}`);
|
|
1248
|
-
* }
|
|
1249
|
-
* ```
|
|
1250
|
-
*/
|
|
1251
|
-
async *waitForProjectionCatchupStream(name, opts) {
|
|
1252
|
-
this.ensureConfigured();
|
|
1253
|
-
// Lazy-load the generated projection client deps only when someone
|
|
1254
|
-
// actually calls the streaming API — keeps the tree-shaken bundle small
|
|
1255
|
-
// for apps that only use the unary wait.
|
|
1256
|
-
const { createClient } = await import("@connectrpc/connect");
|
|
1257
|
-
const { createConnectTransport } = await import("@connectrpc/connect-web");
|
|
1258
|
-
const { create } = await import("@bufbuild/protobuf");
|
|
1259
|
-
const { ProjectionService, WaitProjectionCatchupRequestSchema, WaitStreamFrameKind } = await import("@ironflow/core/gen");
|
|
1260
|
-
const transport = createConnectTransport({
|
|
1261
|
-
baseUrl: this.config.serverUrl,
|
|
1262
|
-
interceptors: [
|
|
1263
|
-
(next) => async (req) => {
|
|
1264
|
-
req.header.set(HEADERS.ENVIRONMENT, this.config.environment);
|
|
1265
|
-
const token = this.config.auth?.apiKey || this.config.auth?.token;
|
|
1266
|
-
if (token)
|
|
1267
|
-
req.header.set("Authorization", `Bearer ${token}`);
|
|
1268
|
-
return next(req);
|
|
1269
|
-
},
|
|
1270
|
-
],
|
|
1271
|
-
});
|
|
1272
|
-
const client = createClient(ProjectionService, transport);
|
|
1273
|
-
const request = create(WaitProjectionCatchupRequestSchema, {
|
|
1274
|
-
name,
|
|
1275
|
-
minSeq: typeof opts.minSeq === "bigint" ? opts.minSeq : BigInt(opts.minSeq),
|
|
1276
|
-
partition: opts.partition ?? "",
|
|
1277
|
-
timeout: opts.timeoutMs !== undefined
|
|
1278
|
-
? {
|
|
1279
|
-
$typeName: "google.protobuf.Duration",
|
|
1280
|
-
seconds: BigInt(Math.floor(opts.timeoutMs / 1000)),
|
|
1281
|
-
nanos: (opts.timeoutMs % 1000) * 1_000_000,
|
|
1282
|
-
}
|
|
1283
|
-
: undefined,
|
|
1284
|
-
});
|
|
1285
|
-
const stream = client.waitProjectionCatchupStream(request, { signal: opts.signal });
|
|
1286
|
-
yield* filterWaitStreamFrames(stream, WaitStreamFrameKind);
|
|
1287
|
-
}
|
|
1288
|
-
/**
|
|
1289
|
-
* Wait for a specific event to be processed by the given projection.
|
|
1290
|
-
* The server resolves `eventId` to its NATS sequence internally.
|
|
1291
|
-
*
|
|
1292
|
-
* Issue #473.
|
|
1293
|
-
*/
|
|
1294
|
-
async waitForEvent(eventId, projection, opts = {}) {
|
|
1295
|
-
this.ensureConfigured();
|
|
1296
|
-
const url = `${this.config.serverUrl}/api/v1/projections/wait-for-event`;
|
|
1297
|
-
const clientTimeoutMs = (opts.timeoutMs ?? 30_000) + 2_000;
|
|
1298
|
-
const controller = new AbortController();
|
|
1299
|
-
const timeoutId = setTimeout(() => controller.abort(), clientTimeoutMs);
|
|
1300
|
-
try {
|
|
1301
|
-
const headers = {
|
|
1302
|
-
"Content-Type": "application/json",
|
|
1303
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1304
|
-
};
|
|
1305
|
-
if (this.config.auth?.apiKey) {
|
|
1306
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1307
|
-
}
|
|
1308
|
-
const body = { eventId, projection };
|
|
1309
|
-
if (opts.timeoutMs !== undefined)
|
|
1310
|
-
body.timeoutMs = opts.timeoutMs;
|
|
1311
|
-
if (opts.partition)
|
|
1312
|
-
body.partition = opts.partition;
|
|
1313
|
-
const response = await fetch(url, {
|
|
1314
|
-
method: "POST",
|
|
1315
|
-
headers,
|
|
1316
|
-
body: JSON.stringify(body),
|
|
1317
|
-
signal: controller.signal,
|
|
1318
|
-
});
|
|
1319
|
-
if (!response.ok) {
|
|
1320
|
-
const error = safeJsonParse(await response.text());
|
|
1321
|
-
throw new IronflowError(error?.message || `Wait for event failed: ${response.status}`, { code: error?.code || "WAIT_FOR_EVENT_FAILED" });
|
|
1322
|
-
}
|
|
1323
|
-
return (await response.json());
|
|
1324
|
-
}
|
|
1325
|
-
finally {
|
|
1326
|
-
clearTimeout(timeoutId);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
/**
|
|
1330
|
-
* List all registered projections
|
|
1331
|
-
*
|
|
1332
|
-
* @example
|
|
1333
|
-
* ```typescript
|
|
1334
|
-
* const projections = await ironflow.listProjections();
|
|
1335
|
-
* projections.forEach(p => console.log(p.name, p.status));
|
|
1336
|
-
* ```
|
|
1337
|
-
*/
|
|
1338
|
-
async listProjections() {
|
|
1339
|
-
this.ensureConfigured();
|
|
1340
|
-
const url = `${this.config.serverUrl}/api/v1/projections`;
|
|
1341
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
1342
|
-
const controller = new AbortController();
|
|
1343
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1344
|
-
try {
|
|
1345
|
-
const headers = {
|
|
1346
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
1347
|
-
};
|
|
1348
|
-
if (this.config.auth?.apiKey) {
|
|
1349
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
1350
|
-
}
|
|
1351
|
-
const response = await fetch(url, {
|
|
1352
|
-
method: "GET",
|
|
1353
|
-
headers,
|
|
1354
|
-
signal: controller.signal,
|
|
1355
|
-
});
|
|
1356
|
-
if (!response.ok) {
|
|
1357
|
-
throw new IronflowError(`List projections failed: ${response.status}`, { code: "LIST_PROJECTIONS_FAILED" });
|
|
1358
|
-
}
|
|
1359
|
-
const data = await response.json();
|
|
1360
|
-
const projections = data.projections || [];
|
|
1361
|
-
return projections.map((p) => ({
|
|
1362
|
-
name: p.name,
|
|
1363
|
-
status: p.status,
|
|
1364
|
-
mode: p.mode,
|
|
1365
|
-
lastEventSeq: p.last_event_seq ?? 0,
|
|
1366
|
-
lag: 0,
|
|
1367
|
-
errorMessage: p.error_message || undefined,
|
|
1368
|
-
updatedAt: p.updated_at ? new Date(p.updated_at) : new Date(),
|
|
1369
|
-
}));
|
|
1370
|
-
}
|
|
1371
|
-
finally {
|
|
1372
|
-
clearTimeout(timeoutId);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
/**
|
|
1376
|
-
* Subscribe to real-time updates for a projection
|
|
1377
|
-
*
|
|
1378
|
-
* @example
|
|
1379
|
-
* ```typescript
|
|
1380
|
-
* const sub = await ironflow.subscribeToProjection<OrderStats>('order-stats', {
|
|
1381
|
-
* onUpdate: (state, event) => console.log('Updated:', state),
|
|
1382
|
-
* onError: (error) => console.error(error),
|
|
1383
|
-
* });
|
|
1384
|
-
*
|
|
1385
|
-
* // With partition
|
|
1386
|
-
* const sub = await ironflow.subscribeToProjection('order-stats', {
|
|
1387
|
-
* onUpdate: (state, event) => console.log('Updated:', state),
|
|
1388
|
-
* }, { partition: 'customer-123' });
|
|
1389
|
-
*
|
|
1390
|
-
* // Cleanup
|
|
1391
|
-
* sub.unsubscribe();
|
|
1392
|
-
* ```
|
|
1393
|
-
*/
|
|
1394
|
-
async subscribeToProjection(name, callbacks, options) {
|
|
1395
|
-
this.ensureConfigured();
|
|
1396
|
-
// Build the subscription pattern
|
|
1397
|
-
let pattern;
|
|
1398
|
-
if (options?.partition) {
|
|
1399
|
-
pattern = `system.projection.${name}.${options.partition}.updated`;
|
|
1400
|
-
}
|
|
1401
|
-
else {
|
|
1402
|
-
pattern = `system.projection.${name}.>`;
|
|
1403
|
-
}
|
|
1404
|
-
const sub = await this.subscribe(pattern, {
|
|
1405
|
-
onEvent: (event) => {
|
|
1406
|
-
const payload = event.data;
|
|
1407
|
-
const state = payload.state ?? {};
|
|
1408
|
-
callbacks.onUpdate(state, {
|
|
1409
|
-
id: payload.last_event_id ?? "",
|
|
1410
|
-
name: payload.last_event_name ?? "",
|
|
1411
|
-
});
|
|
1412
|
-
},
|
|
1413
|
-
onError: callbacks.onError
|
|
1414
|
-
? (info) => callbacks.onError(new Error(info.message))
|
|
1415
|
-
: undefined,
|
|
1416
|
-
replay: options?.replay,
|
|
1417
|
-
});
|
|
1418
|
-
return sub;
|
|
1419
|
-
}
|
|
1420
|
-
// ============================================================================
|
|
1421
|
-
// KV Store
|
|
1422
|
-
// ============================================================================
|
|
1423
|
-
/**
|
|
1424
|
-
* KV store operations
|
|
1425
|
-
*
|
|
1426
|
-
* @example
|
|
1427
|
-
* ```typescript
|
|
1428
|
-
* const kv = ironflow.kv();
|
|
1429
|
-
* const bucket = await kv.createBucket({ name: "sessions", ttlSeconds: 3600 });
|
|
1430
|
-
* const handle = kv.bucket("sessions");
|
|
1431
|
-
* const { revision } = await handle.put("user.123", { token: "abc" });
|
|
1432
|
-
* const entry = await handle.get("user.123");
|
|
1433
|
-
*
|
|
1434
|
-
* // Watch for changes
|
|
1435
|
-
* const watcher = handle.watch({
|
|
1436
|
-
* onUpdate: (event) => console.log(event),
|
|
1437
|
-
* }, { key: "user.*" });
|
|
1438
|
-
* ```
|
|
1439
|
-
*/
|
|
1440
|
-
kv() {
|
|
1441
|
-
this.ensureConfigured();
|
|
1442
|
-
return new BrowserKVClient(this.config);
|
|
1443
|
-
}
|
|
1444
|
-
// ============================================================================
|
|
1445
|
-
// Config Management
|
|
1446
|
-
// ============================================================================
|
|
1447
|
-
/**
|
|
1448
|
-
* Config management operations
|
|
1449
|
-
*
|
|
1450
|
-
* @example
|
|
1451
|
-
* ```typescript
|
|
1452
|
-
* const cfg = ironflow.configManager();
|
|
1453
|
-
* await cfg.set("app", { featureX: true, maxRetries: 3 });
|
|
1454
|
-
* const { data, revision } = await cfg.get("app");
|
|
1455
|
-
* await cfg.patch("app", { maxRetries: 5 });
|
|
1456
|
-
* const configs = await cfg.list();
|
|
1457
|
-
* await cfg.delete("app");
|
|
1458
|
-
*
|
|
1459
|
-
* // Watch for changes
|
|
1460
|
-
* const sub = await cfg.watch("app", {
|
|
1461
|
-
* onEvent: (config) => console.log(config),
|
|
1462
|
-
* });
|
|
1463
|
-
* sub.unsubscribe();
|
|
1464
|
-
* ```
|
|
1465
|
-
*/
|
|
1466
|
-
configManager() {
|
|
1467
|
-
this.ensureConfigured();
|
|
1468
|
-
return new BrowserConfigClient(this.config, (pattern, callbacks) => this.subscribe(pattern, callbacks));
|
|
1469
|
-
}
|
|
1470
|
-
// ============================================================================
|
|
1471
|
-
// Auth Management
|
|
1472
|
-
// ============================================================================
|
|
1473
|
-
/**
|
|
1474
|
-
* API key management operations
|
|
1475
|
-
*
|
|
1476
|
-
* @example
|
|
1477
|
-
* ```typescript
|
|
1478
|
-
* // Create an API key
|
|
1479
|
-
* const key = await ironflow.apiKeys.create({ name: "my-key" });
|
|
1480
|
-
* console.log(key.key); // Only shown once
|
|
1481
|
-
*
|
|
1482
|
-
* // List API keys
|
|
1483
|
-
* const keys = await ironflow.apiKeys.list();
|
|
1484
|
-
*
|
|
1485
|
-
* // Delete an API key
|
|
1486
|
-
* await ironflow.apiKeys.delete(key.id);
|
|
1487
|
-
* ```
|
|
1488
|
-
*/
|
|
1489
|
-
apiKeys = {
|
|
1490
|
-
/**
|
|
1491
|
-
* Create a new API key
|
|
1492
|
-
*/
|
|
1493
|
-
create: async (input) => {
|
|
1494
|
-
this.ensureConfigured();
|
|
1495
|
-
return this.restRequest("POST", "/api/v1/apikeys", input);
|
|
1496
|
-
},
|
|
1497
|
-
/**
|
|
1498
|
-
* List all API keys
|
|
1499
|
-
*/
|
|
1500
|
-
list: async () => {
|
|
1501
|
-
this.ensureConfigured();
|
|
1502
|
-
return this.restRequest("GET", "/api/v1/apikeys");
|
|
1503
|
-
},
|
|
1504
|
-
/**
|
|
1505
|
-
* Get an API key by ID
|
|
1506
|
-
*/
|
|
1507
|
-
get: async (id) => {
|
|
1508
|
-
this.ensureConfigured();
|
|
1509
|
-
return this.restRequest("GET", `/api/v1/apikeys/${encodeURIComponent(id)}`);
|
|
1510
|
-
},
|
|
1511
|
-
/**
|
|
1512
|
-
* Delete an API key
|
|
1513
|
-
*/
|
|
1514
|
-
delete: async (id) => {
|
|
1515
|
-
this.ensureConfigured();
|
|
1516
|
-
await this.restRequest("DELETE", `/api/v1/apikeys/${encodeURIComponent(id)}`);
|
|
1517
|
-
},
|
|
1518
|
-
/**
|
|
1519
|
-
* Rotate an API key, returning a new secret
|
|
1520
|
-
*/
|
|
1521
|
-
rotate: async (id) => {
|
|
1522
|
-
this.ensureConfigured();
|
|
1523
|
-
return this.restRequest("POST", `/api/v1/apikeys/${encodeURIComponent(id)}/rotate`);
|
|
1524
|
-
},
|
|
1525
|
-
};
|
|
1526
|
-
/**
|
|
1527
|
-
* Organization management operations (enterprise-only)
|
|
1528
|
-
*
|
|
1529
|
-
* @example
|
|
1530
|
-
* ```typescript
|
|
1531
|
-
* // Create an organization
|
|
1532
|
-
* const org = await ironflow.orgs.create({ name: "Acme Corp" });
|
|
1533
|
-
*
|
|
1534
|
-
* // List organizations
|
|
1535
|
-
* const orgs = await ironflow.orgs.list();
|
|
1536
|
-
*
|
|
1537
|
-
* // Update an organization
|
|
1538
|
-
* await ironflow.orgs.update(org.id, { name: "Acme Inc" });
|
|
1539
|
-
* ```
|
|
1540
|
-
*/
|
|
1541
|
-
orgs = {
|
|
1542
|
-
/**
|
|
1543
|
-
* Create a new organization
|
|
1544
|
-
*/
|
|
1545
|
-
create: async (input) => {
|
|
1546
|
-
this.ensureConfigured();
|
|
1547
|
-
return this.restRequest("POST", "/api/v1/orgs", input);
|
|
1548
|
-
},
|
|
1549
|
-
/**
|
|
1550
|
-
* List all organizations
|
|
1551
|
-
*/
|
|
1552
|
-
list: async () => {
|
|
1553
|
-
this.ensureConfigured();
|
|
1554
|
-
return this.restRequest("GET", "/api/v1/orgs");
|
|
1555
|
-
},
|
|
1556
|
-
/**
|
|
1557
|
-
* Get an organization by ID
|
|
1558
|
-
*/
|
|
1559
|
-
get: async (id) => {
|
|
1560
|
-
this.ensureConfigured();
|
|
1561
|
-
return this.restRequest("GET", `/api/v1/orgs/${encodeURIComponent(id)}`);
|
|
1562
|
-
},
|
|
1563
|
-
/**
|
|
1564
|
-
* Update an organization
|
|
1565
|
-
*/
|
|
1566
|
-
update: async (id, input) => {
|
|
1567
|
-
this.ensureConfigured();
|
|
1568
|
-
return this.restRequest("PATCH", `/api/v1/orgs/${encodeURIComponent(id)}`, input);
|
|
1569
|
-
},
|
|
1570
|
-
/**
|
|
1571
|
-
* Delete an organization
|
|
1572
|
-
*/
|
|
1573
|
-
delete: async (id) => {
|
|
1574
|
-
this.ensureConfigured();
|
|
1575
|
-
await this.restRequest("DELETE", `/api/v1/orgs/${encodeURIComponent(id)}`);
|
|
1576
|
-
},
|
|
1577
|
-
};
|
|
1578
|
-
/**
|
|
1579
|
-
* Role management operations (enterprise-only)
|
|
1580
|
-
*
|
|
1581
|
-
* @example
|
|
1582
|
-
* ```typescript
|
|
1583
|
-
* // Create a role
|
|
1584
|
-
* const role = await ironflow.roles.create({ name: "editor", org_id: "org-1" });
|
|
1585
|
-
*
|
|
1586
|
-
* // Assign a policy to the role
|
|
1587
|
-
* await ironflow.roles.assignPolicy(role.id, policyId);
|
|
1588
|
-
*
|
|
1589
|
-
* // List roles
|
|
1590
|
-
* const roles = await ironflow.roles.list();
|
|
1591
|
-
* ```
|
|
1592
|
-
*/
|
|
1593
|
-
roles = {
|
|
1594
|
-
/**
|
|
1595
|
-
* Create a new role
|
|
1596
|
-
*/
|
|
1597
|
-
create: async (input) => {
|
|
1598
|
-
this.ensureConfigured();
|
|
1599
|
-
return this.restRequest("POST", "/api/v1/roles", input);
|
|
1600
|
-
},
|
|
1601
|
-
/**
|
|
1602
|
-
* List all roles
|
|
1603
|
-
*/
|
|
1604
|
-
list: async (orgId) => {
|
|
1605
|
-
this.ensureConfigured();
|
|
1606
|
-
const query = orgId ? `?org_id=${encodeURIComponent(orgId)}` : "";
|
|
1607
|
-
return this.restRequest("GET", `/api/v1/roles${query}`);
|
|
1608
|
-
},
|
|
1609
|
-
/**
|
|
1610
|
-
* Get a role by ID
|
|
1611
|
-
*/
|
|
1612
|
-
get: async (id) => {
|
|
1613
|
-
this.ensureConfigured();
|
|
1614
|
-
return this.restRequest("GET", `/api/v1/roles/${encodeURIComponent(id)}`);
|
|
1615
|
-
},
|
|
1616
|
-
/**
|
|
1617
|
-
* Update a role
|
|
1618
|
-
*/
|
|
1619
|
-
update: async (id, input) => {
|
|
1620
|
-
this.ensureConfigured();
|
|
1621
|
-
return this.restRequest("PATCH", `/api/v1/roles/${encodeURIComponent(id)}`, input);
|
|
1622
|
-
},
|
|
1623
|
-
/**
|
|
1624
|
-
* Delete a role
|
|
1625
|
-
*/
|
|
1626
|
-
delete: async (id) => {
|
|
1627
|
-
this.ensureConfigured();
|
|
1628
|
-
await this.restRequest("DELETE", `/api/v1/roles/${encodeURIComponent(id)}`);
|
|
1629
|
-
},
|
|
1630
|
-
/**
|
|
1631
|
-
* Assign a policy to a role
|
|
1632
|
-
*/
|
|
1633
|
-
assignPolicy: async (roleId, policyId) => {
|
|
1634
|
-
this.ensureConfigured();
|
|
1635
|
-
await this.restRequest("POST", `/api/v1/roles/${encodeURIComponent(roleId)}/policies`, { policy_id: policyId });
|
|
1636
|
-
},
|
|
1637
|
-
/**
|
|
1638
|
-
* Remove a policy from a role
|
|
1639
|
-
*/
|
|
1640
|
-
removePolicy: async (roleId, policyId) => {
|
|
1641
|
-
this.ensureConfigured();
|
|
1642
|
-
await this.restRequest("DELETE", `/api/v1/roles/${encodeURIComponent(roleId)}/policies/${encodeURIComponent(policyId)}`);
|
|
1643
|
-
},
|
|
1644
|
-
};
|
|
1645
|
-
/**
|
|
1646
|
-
* Policy management operations (enterprise-only)
|
|
1647
|
-
*
|
|
1648
|
-
* @example
|
|
1649
|
-
* ```typescript
|
|
1650
|
-
* // Create a policy
|
|
1651
|
-
* const policy = await ironflow.policies.create({
|
|
1652
|
-
* name: "allow-read",
|
|
1653
|
-
* effect: "allow",
|
|
1654
|
-
* actions: "read",
|
|
1655
|
-
* resources: "*",
|
|
1656
|
-
* org_id: "org-1",
|
|
1657
|
-
* });
|
|
1658
|
-
*
|
|
1659
|
-
* // List policies
|
|
1660
|
-
* const policies = await ironflow.policies.list();
|
|
1661
|
-
* ```
|
|
1662
|
-
*/
|
|
1663
|
-
policies = {
|
|
1664
|
-
/**
|
|
1665
|
-
* Create a new policy
|
|
1666
|
-
*/
|
|
1667
|
-
create: async (input) => {
|
|
1668
|
-
this.ensureConfigured();
|
|
1669
|
-
return this.restRequest("POST", "/api/v1/policies", input);
|
|
1670
|
-
},
|
|
1671
|
-
/**
|
|
1672
|
-
* List all policies
|
|
1673
|
-
*/
|
|
1674
|
-
list: async (orgId) => {
|
|
1675
|
-
this.ensureConfigured();
|
|
1676
|
-
const query = orgId ? `?org_id=${encodeURIComponent(orgId)}` : "";
|
|
1677
|
-
return this.restRequest("GET", `/api/v1/policies${query}`);
|
|
1678
|
-
},
|
|
1679
|
-
/**
|
|
1680
|
-
* Get a policy by ID
|
|
1681
|
-
*/
|
|
1682
|
-
get: async (id) => {
|
|
1683
|
-
this.ensureConfigured();
|
|
1684
|
-
return this.restRequest("GET", `/api/v1/policies/${encodeURIComponent(id)}`);
|
|
1685
|
-
},
|
|
1686
|
-
/**
|
|
1687
|
-
* Update a policy
|
|
1688
|
-
*/
|
|
1689
|
-
update: async (id, input) => {
|
|
1690
|
-
this.ensureConfigured();
|
|
1691
|
-
return this.restRequest("PATCH", `/api/v1/policies/${encodeURIComponent(id)}`, input);
|
|
1692
|
-
},
|
|
1693
|
-
/**
|
|
1694
|
-
* Delete a policy
|
|
1695
|
-
*/
|
|
1696
|
-
delete: async (id) => {
|
|
1697
|
-
this.ensureConfigured();
|
|
1698
|
-
await this.restRequest("DELETE", `/api/v1/policies/${encodeURIComponent(id)}`);
|
|
1699
|
-
},
|
|
1700
|
-
};
|
|
1701
|
-
/**
|
|
1702
|
-
* Event schema registry operations
|
|
1703
|
-
*
|
|
1704
|
-
* @example
|
|
1705
|
-
* ```typescript
|
|
1706
|
-
* // Register a schema
|
|
1707
|
-
* const schema = await ironflow.schemas.register({
|
|
1708
|
-
* name: "order.placed",
|
|
1709
|
-
* version: 1,
|
|
1710
|
-
* schema: { type: "object", properties: { orderId: { type: "string" } } },
|
|
1711
|
-
* });
|
|
1712
|
-
*
|
|
1713
|
-
* // List all schemas
|
|
1714
|
-
* const schemas = await ironflow.schemas.list();
|
|
1715
|
-
*
|
|
1716
|
-
* // Get latest version of a schema
|
|
1717
|
-
* const latest = await ironflow.schemas.get("order.placed");
|
|
1718
|
-
*
|
|
1719
|
-
* // Test an upcast transformation
|
|
1720
|
-
* const result = await ironflow.schemas.testUpcast({
|
|
1721
|
-
* eventName: "order.placed",
|
|
1722
|
-
* fromVersion: 1,
|
|
1723
|
-
* toVersion: 2,
|
|
1724
|
-
* data: { orderId: "123" },
|
|
1725
|
-
* });
|
|
1726
|
-
* ```
|
|
1727
|
-
*/
|
|
1728
|
-
schemas = {
|
|
1729
|
-
/** Register a new event schema (or a new version of an existing schema) */
|
|
1730
|
-
register: async (input) => {
|
|
1731
|
-
this.ensureConfigured();
|
|
1732
|
-
return this.restRequest("POST", "/api/v1/events/schemas", {
|
|
1733
|
-
event_name: input.name,
|
|
1734
|
-
version: input.version,
|
|
1735
|
-
schema_json: JSON.stringify(input.schema),
|
|
1736
|
-
});
|
|
1737
|
-
},
|
|
1738
|
-
/** List all registered event schemas */
|
|
1739
|
-
list: async () => {
|
|
1740
|
-
this.ensureConfigured();
|
|
1741
|
-
const resp = await this.restRequest("GET", "/api/v1/events/schemas");
|
|
1742
|
-
return resp.schemas ?? [];
|
|
1743
|
-
},
|
|
1744
|
-
/** Get the latest version of an event schema by name */
|
|
1745
|
-
get: async (name) => {
|
|
1746
|
-
this.ensureConfigured();
|
|
1747
|
-
return this.restRequest("GET", `/api/v1/events/schemas/${encodeURIComponent(name)}`);
|
|
1748
|
-
},
|
|
1749
|
-
/** Get a specific version of an event schema */
|
|
1750
|
-
getVersion: async (name, version) => {
|
|
1751
|
-
this.ensureConfigured();
|
|
1752
|
-
return this.restRequest("GET", `/api/v1/events/schemas/${encodeURIComponent(name)}/${version}`);
|
|
1753
|
-
},
|
|
1754
|
-
/** Delete a specific version of an event schema */
|
|
1755
|
-
delete: async (name, version) => {
|
|
1756
|
-
this.ensureConfigured();
|
|
1757
|
-
await this.restRequest("DELETE", `/api/v1/events/schemas/${encodeURIComponent(name)}/${version}`);
|
|
1758
|
-
},
|
|
1759
|
-
/** Test an upcast transformation between two schema versions */
|
|
1760
|
-
testUpcast: async (input) => {
|
|
1761
|
-
this.ensureConfigured();
|
|
1762
|
-
return this.restRequest("POST", "/api/v1/events/upcast", input);
|
|
1763
|
-
},
|
|
1764
|
-
};
|
|
1765
|
-
// ============================================================================
|
|
1766
|
-
// Audit Trail
|
|
1767
|
-
// ============================================================================
|
|
1768
|
-
/**
|
|
1769
|
-
* Get the audit trail for a run.
|
|
1770
|
-
*
|
|
1771
|
-
* @param runId The run ID to retrieve the audit trail for
|
|
1772
|
-
* @param options Optional filtering options
|
|
1773
|
-
*
|
|
1774
|
-
* @example
|
|
1775
|
-
* ```typescript
|
|
1776
|
-
* const result = await ironflow.getAuditTrail("run-abc123");
|
|
1777
|
-
* for (const event of result.events) {
|
|
1778
|
-
* console.log(event.eventType, event.createdAt);
|
|
1779
|
-
* }
|
|
1780
|
-
* ```
|
|
1781
|
-
*/
|
|
1782
|
-
async getAuditTrail(runId, options) {
|
|
1783
|
-
this.ensureConfigured();
|
|
1784
|
-
const response = await this.streamRequest("/ironflow.v1.AuditService/GetAuditTrail", {
|
|
1785
|
-
run_id: runId,
|
|
1786
|
-
event_type: options?.eventType ?? "",
|
|
1787
|
-
from_timestamp: options?.fromTimestamp ?? "",
|
|
1788
|
-
to_timestamp: options?.toTimestamp ?? "",
|
|
1789
|
-
limit: options?.limit ?? 0,
|
|
1790
|
-
cursor: options?.cursor ?? "",
|
|
1791
|
-
});
|
|
1792
|
-
return {
|
|
1793
|
-
events: (response.events ?? []).map((e) => ({
|
|
1794
|
-
id: e.id,
|
|
1795
|
-
runId: e.run_id,
|
|
1796
|
-
functionId: e.function_id,
|
|
1797
|
-
stepId: e.step_id,
|
|
1798
|
-
eventType: e.event_type,
|
|
1799
|
-
payload: e.payload ?? {},
|
|
1800
|
-
metadata: e.metadata,
|
|
1801
|
-
createdAt: e.created_at,
|
|
1802
|
-
})),
|
|
1803
|
-
totalCount: response.total_count ?? 0,
|
|
1804
|
-
nextCursor: response.next_cursor,
|
|
1805
|
-
};
|
|
1806
|
-
}
|
|
1807
|
-
// ============================================================================
|
|
1808
|
-
// Webhook Management
|
|
1809
|
-
// ============================================================================
|
|
1810
|
-
/**
|
|
1811
|
-
* Webhook management operations
|
|
1812
|
-
*
|
|
1813
|
-
* @example
|
|
1814
|
-
* ```typescript
|
|
1815
|
-
* // List all webhook sources
|
|
1816
|
-
* const sources = await ironflow.webhooks.listSources();
|
|
1817
|
-
*
|
|
1818
|
-
* // Delete a webhook source
|
|
1819
|
-
* await ironflow.webhooks.deleteSource("my-webhook");
|
|
1820
|
-
*
|
|
1821
|
-
* // List deliveries
|
|
1822
|
-
* const { deliveries } = await ironflow.webhooks.listDeliveries({ sourceId: "my-webhook" });
|
|
1823
|
-
* ```
|
|
1824
|
-
*/
|
|
1825
|
-
webhooks = {
|
|
1826
|
-
/** Create a new webhook source */
|
|
1827
|
-
create: async (input) => {
|
|
1828
|
-
this.ensureConfigured();
|
|
1829
|
-
const response = await this.streamRequest("/ironflow.v1.WebhookService/CreateWebhookSource", {
|
|
1830
|
-
id: input.id,
|
|
1831
|
-
event_prefix: input.eventPrefix,
|
|
1832
|
-
verify_header: input.verifyHeader ?? "",
|
|
1833
|
-
verify_algorithm: input.verifyAlgorithm ?? "",
|
|
1834
|
-
verify_secret: input.verifySecret ?? "",
|
|
1835
|
-
metadata: input.metadata,
|
|
1836
|
-
});
|
|
1837
|
-
return {
|
|
1838
|
-
id: response.id,
|
|
1839
|
-
eventPrefix: response.event_prefix,
|
|
1840
|
-
verifyHeader: response.verify_header,
|
|
1841
|
-
verifyAlgorithm: response.verify_algorithm,
|
|
1842
|
-
sourceType: response.source_type,
|
|
1843
|
-
metadata: response.metadata,
|
|
1844
|
-
createdAt: response.created_at,
|
|
1845
|
-
updatedAt: response.updated_at,
|
|
1846
|
-
};
|
|
1847
|
-
},
|
|
1848
|
-
/** List all registered webhook sources */
|
|
1849
|
-
listSources: async () => {
|
|
1850
|
-
this.ensureConfigured();
|
|
1851
|
-
const response = await this.streamRequest("/ironflow.v1.WebhookService/ListWebhookSources", { limit: 0, offset: 0 });
|
|
1852
|
-
return (response.sources ?? []).map((s) => ({
|
|
1853
|
-
id: s.id,
|
|
1854
|
-
eventPrefix: s.event_prefix,
|
|
1855
|
-
verifyHeader: s.verify_header,
|
|
1856
|
-
verifyAlgorithm: s.verify_algorithm,
|
|
1857
|
-
sourceType: s.source_type,
|
|
1858
|
-
metadata: s.metadata,
|
|
1859
|
-
createdAt: s.created_at,
|
|
1860
|
-
updatedAt: s.updated_at,
|
|
1861
|
-
}));
|
|
1862
|
-
},
|
|
1863
|
-
/** Delete a webhook source by ID */
|
|
1864
|
-
deleteSource: async (id) => {
|
|
1865
|
-
this.ensureConfigured();
|
|
1866
|
-
await this.streamRequest("/ironflow.v1.WebhookService/DeleteWebhookSource", { id });
|
|
1867
|
-
},
|
|
1868
|
-
/** List webhook deliveries with optional filtering */
|
|
1869
|
-
listDeliveries: async (opts) => {
|
|
1870
|
-
this.ensureConfigured();
|
|
1871
|
-
const response = await this.streamRequest("/ironflow.v1.WebhookService/ListWebhookDeliveries", {
|
|
1872
|
-
source_id: opts?.sourceId ?? "",
|
|
1873
|
-
status: opts?.status ?? "",
|
|
1874
|
-
limit: opts?.limit ?? 0,
|
|
1875
|
-
offset: opts?.offset ?? 0,
|
|
1876
|
-
});
|
|
1877
|
-
return {
|
|
1878
|
-
deliveries: (response.deliveries ?? []).map((d) => ({
|
|
1879
|
-
id: d.id,
|
|
1880
|
-
sourceId: d.source_id,
|
|
1881
|
-
externalId: d.external_id,
|
|
1882
|
-
status: d.status,
|
|
1883
|
-
eventId: d.event_id,
|
|
1884
|
-
error: d.error,
|
|
1885
|
-
createdAt: d.created_at,
|
|
1886
|
-
})),
|
|
1887
|
-
totalCount: response.total_count ?? 0,
|
|
1888
|
-
};
|
|
1889
|
-
},
|
|
1890
|
-
};
|
|
1891
|
-
// ============================================================================
|
|
1892
|
-
// User Management
|
|
1893
|
-
// ============================================================================
|
|
1894
|
-
/**
|
|
1895
|
-
* User management operations
|
|
1896
|
-
*
|
|
1897
|
-
* @example
|
|
1898
|
-
* ```typescript
|
|
1899
|
-
* // List all users
|
|
1900
|
-
* const users = await ironflow.users.list();
|
|
1901
|
-
*
|
|
1902
|
-
* // Create a user
|
|
1903
|
-
* const user = await ironflow.users.create({
|
|
1904
|
-
* email: "alice@example.com",
|
|
1905
|
-
* password: "secret",
|
|
1906
|
-
* roles: ["admin"],
|
|
1907
|
-
* });
|
|
1908
|
-
*
|
|
1909
|
-
* // Update a user
|
|
1910
|
-
* await ironflow.users.update(user.id, { name: "Alice" });
|
|
1911
|
-
* ```
|
|
1912
|
-
*/
|
|
1913
|
-
users = {
|
|
1914
|
-
/** Create a new user (admin only) */
|
|
1915
|
-
create: async (input) => {
|
|
1916
|
-
this.ensureConfigured();
|
|
1917
|
-
return this.restRequest("POST", "/api/v1/users", input);
|
|
1918
|
-
},
|
|
1919
|
-
/** List all users in the current organization (admin only) */
|
|
1920
|
-
list: async () => {
|
|
1921
|
-
this.ensureConfigured();
|
|
1922
|
-
return this.restRequest("GET", "/api/v1/users");
|
|
1923
|
-
},
|
|
1924
|
-
/** Get a user by ID */
|
|
1925
|
-
get: async (id) => {
|
|
1926
|
-
this.ensureConfigured();
|
|
1927
|
-
return this.restRequest("GET", `/api/v1/users/${encodeURIComponent(id)}`);
|
|
1928
|
-
},
|
|
1929
|
-
/** Update a user's profile (admin only) */
|
|
1930
|
-
update: async (id, input) => {
|
|
1931
|
-
this.ensureConfigured();
|
|
1932
|
-
return this.restRequest("PATCH", `/api/v1/users/${encodeURIComponent(id)}`, input);
|
|
1933
|
-
},
|
|
1934
|
-
/** Delete a user (admin only) */
|
|
1935
|
-
delete: async (id) => {
|
|
1936
|
-
this.ensureConfigured();
|
|
1937
|
-
await this.restRequest("DELETE", `/api/v1/users/${encodeURIComponent(id)}`);
|
|
1938
|
-
},
|
|
1939
|
-
};
|
|
1940
|
-
/**
|
|
1941
|
-
* Tenant management operations (enterprise-only)
|
|
1942
|
-
*
|
|
1943
|
-
* @example
|
|
1944
|
-
* ```typescript
|
|
1945
|
-
* // List all tenants
|
|
1946
|
-
* const tenants = await ironflow.tenants.list();
|
|
1947
|
-
* ```
|
|
1948
|
-
*/
|
|
1949
|
-
tenants = {
|
|
1950
|
-
/** List all tenants (enterprise-only) */
|
|
1951
|
-
list: async () => {
|
|
1952
|
-
this.ensureConfigured();
|
|
1953
|
-
return this.restRequest("GET", "/api/v1/tenants");
|
|
1954
|
-
},
|
|
1955
|
-
};
|
|
1956
|
-
// ============================================================================
|
|
1957
|
-
// Pattern Helpers (static)
|
|
1958
|
-
// ============================================================================
|
|
1959
|
-
/**
|
|
1960
|
-
* Pattern helpers for building subscription patterns
|
|
1961
|
-
*/
|
|
1962
|
-
static patterns = patterns;
|
|
1963
|
-
// ============================================================================
|
|
1964
|
-
// Internal Methods
|
|
1965
|
-
// ============================================================================
|
|
1966
|
-
ensureConfigured() {
|
|
1967
|
-
if (!this.config) {
|
|
1968
|
-
throw new NotConfiguredError();
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
cleanup() {
|
|
1972
|
-
if (this.visibilityHandler) {
|
|
1973
|
-
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
1974
|
-
this.visibilityHandler = null;
|
|
1975
|
-
}
|
|
1976
|
-
if (this.subscriptionManager) {
|
|
1977
|
-
this.subscriptionManager.disconnect();
|
|
1978
|
-
this.subscriptionManager = null;
|
|
1979
|
-
}
|
|
1980
|
-
this.transport = null;
|
|
1981
|
-
}
|
|
1982
|
-
/**
|
|
1983
|
-
* Reset client state for testing. Not intended for production use.
|
|
1984
|
-
* @internal
|
|
1985
|
-
*/
|
|
1986
|
-
_resetForTesting() {
|
|
1987
|
-
this.cleanup();
|
|
1988
|
-
this.config = null;
|
|
1989
|
-
this.logger = createNoopLogger();
|
|
1990
|
-
}
|
|
1991
|
-
setupVisibilityHandling() {
|
|
1992
|
-
this.visibilityHandler = () => {
|
|
1993
|
-
if (document.hidden) {
|
|
1994
|
-
this.logger.debug("Tab hidden, pausing subscriptions");
|
|
1995
|
-
this.subscriptionManager?.pause();
|
|
1996
|
-
}
|
|
1997
|
-
else {
|
|
1998
|
-
if (this.config?.visibility.reconnectOnVisible) {
|
|
1999
|
-
this.logger.debug("Tab visible, resuming subscriptions");
|
|
2000
|
-
this.subscriptionManager?.resume();
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
};
|
|
2004
|
-
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
2005
|
-
}
|
|
2006
|
-
async request(schema, method, path, body) {
|
|
2007
|
-
const url = `${this.config.serverUrl}${path}`;
|
|
2008
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
2009
|
-
const controller = new AbortController();
|
|
2010
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2011
|
-
try {
|
|
2012
|
-
const headers = {
|
|
2013
|
-
"Content-Type": "application/json",
|
|
2014
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
2015
|
-
};
|
|
2016
|
-
if (this.config.auth?.apiKey) {
|
|
2017
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
2018
|
-
}
|
|
2019
|
-
else if (this.config.auth?.token) {
|
|
2020
|
-
headers["Authorization"] = `Bearer ${this.config.auth.token}`;
|
|
2021
|
-
}
|
|
2022
|
-
const response = await fetch(url, {
|
|
2023
|
-
method,
|
|
2024
|
-
headers,
|
|
2025
|
-
body: JSON.stringify(body),
|
|
2026
|
-
signal: controller.signal,
|
|
2027
|
-
});
|
|
2028
|
-
const responseBody = await response.text();
|
|
2029
|
-
if (!response.ok) {
|
|
2030
|
-
const errorResult = ErrorResponseSchema.safeParse(safeJsonParse(responseBody));
|
|
2031
|
-
const errorData = errorResult.success
|
|
2032
|
-
? errorResult.data
|
|
2033
|
-
: { message: responseBody };
|
|
2034
|
-
throw new IronflowError(errorData.message ?? `Request failed: ${response.status}`, {
|
|
2035
|
-
code: errorData.code ?? `HTTP_${response.status}`,
|
|
2036
|
-
retryable: response.status >= 500,
|
|
2037
|
-
});
|
|
2038
|
-
}
|
|
2039
|
-
const parsed = safeJsonParse(responseBody);
|
|
2040
|
-
if (parsed === undefined) {
|
|
2041
|
-
throw new ValidationError("Invalid JSON response from server");
|
|
2042
|
-
}
|
|
2043
|
-
const result = schema.safeParse(parsed);
|
|
2044
|
-
if (!result.success) {
|
|
2045
|
-
const issues = result.error.issues
|
|
2046
|
-
.map((i) => `${i.path.join(".")}: ${i.message}`)
|
|
2047
|
-
.join(", ");
|
|
2048
|
-
throw new ValidationError(`Invalid response from server: ${issues}`);
|
|
2049
|
-
}
|
|
2050
|
-
return result.data;
|
|
2051
|
-
}
|
|
2052
|
-
catch (error) {
|
|
2053
|
-
if (error instanceof IronflowError || error instanceof ValidationError) {
|
|
2054
|
-
throw error;
|
|
2055
|
-
}
|
|
2056
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2057
|
-
throw new IronflowError(`Request timeout after ${timeout}ms for ${method} ${path}`, {
|
|
2058
|
-
code: "TIMEOUT",
|
|
2059
|
-
retryable: true,
|
|
2060
|
-
});
|
|
2061
|
-
}
|
|
2062
|
-
throw new IronflowError(error instanceof Error
|
|
2063
|
-
? `${method} ${path} failed: ${error.message}`
|
|
2064
|
-
: `${method} ${path} failed`, {
|
|
2065
|
-
code: "REQUEST_FAILED",
|
|
2066
|
-
retryable: true,
|
|
2067
|
-
cause: error instanceof Error ? error : undefined,
|
|
2068
|
-
});
|
|
2069
|
-
}
|
|
2070
|
-
finally {
|
|
2071
|
-
clearTimeout(timeoutId);
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
async streamRequest(path, body) {
|
|
2075
|
-
const url = `${this.config.serverUrl}${path}`;
|
|
2076
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
2077
|
-
const controller = new AbortController();
|
|
2078
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2079
|
-
try {
|
|
2080
|
-
const headers = {
|
|
2081
|
-
"Content-Type": "application/json",
|
|
2082
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
2083
|
-
};
|
|
2084
|
-
if (this.config.auth?.apiKey) {
|
|
2085
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
2086
|
-
}
|
|
2087
|
-
else if (this.config.auth?.token) {
|
|
2088
|
-
headers["Authorization"] = `Bearer ${this.config.auth.token}`;
|
|
2089
|
-
}
|
|
2090
|
-
const response = await fetch(url, {
|
|
2091
|
-
method: "POST",
|
|
2092
|
-
headers,
|
|
2093
|
-
body: JSON.stringify(body),
|
|
2094
|
-
signal: controller.signal,
|
|
2095
|
-
});
|
|
2096
|
-
if (!response.ok) {
|
|
2097
|
-
const errorBody = safeJsonParse(await response.text());
|
|
2098
|
-
throw new IronflowError(errorBody?.message ?? `Request failed: ${response.status}`, {
|
|
2099
|
-
code: errorBody?.code ?? `HTTP_${response.status}`,
|
|
2100
|
-
retryable: response.status >= 500,
|
|
2101
|
-
});
|
|
2102
|
-
}
|
|
2103
|
-
return response.json();
|
|
2104
|
-
}
|
|
2105
|
-
catch (error) {
|
|
2106
|
-
if (error instanceof IronflowError) {
|
|
2107
|
-
throw error;
|
|
2108
|
-
}
|
|
2109
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2110
|
-
throw new IronflowError(`Request timeout after ${timeout}ms for POST ${path}`, { code: "TIMEOUT", retryable: true });
|
|
2111
|
-
}
|
|
2112
|
-
throw new IronflowError(error instanceof Error
|
|
2113
|
-
? `POST ${path} failed: ${error.message}`
|
|
2114
|
-
: `POST ${path} failed`, {
|
|
2115
|
-
code: "REQUEST_FAILED",
|
|
2116
|
-
retryable: true,
|
|
2117
|
-
cause: error instanceof Error ? error : undefined,
|
|
2118
|
-
});
|
|
2119
|
-
}
|
|
2120
|
-
finally {
|
|
2121
|
-
clearTimeout(timeoutId);
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
/**
|
|
2125
|
-
* REST request helper for auth management endpoints.
|
|
2126
|
-
*
|
|
2127
|
-
* Supports GET, POST, PATCH, DELETE methods with typed error mapping:
|
|
2128
|
-
* - 401 → UnauthenticatedError
|
|
2129
|
-
* - 402 → EnterpriseRequiredError
|
|
2130
|
-
* - 403 → UnauthorizedError
|
|
2131
|
-
*/
|
|
2132
|
-
async restRequest(method, path, body) {
|
|
2133
|
-
const url = `${this.config.serverUrl}${path}`;
|
|
2134
|
-
const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
|
|
2135
|
-
const controller = new AbortController();
|
|
2136
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2137
|
-
try {
|
|
2138
|
-
const headers = {
|
|
2139
|
-
[HEADERS.ENVIRONMENT]: this.config.environment,
|
|
2140
|
-
};
|
|
2141
|
-
if (body !== undefined) {
|
|
2142
|
-
headers["Content-Type"] = "application/json";
|
|
2143
|
-
}
|
|
2144
|
-
if (this.config.auth?.apiKey) {
|
|
2145
|
-
headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
|
|
2146
|
-
}
|
|
2147
|
-
else if (this.config.auth?.token) {
|
|
2148
|
-
headers["Authorization"] = `Bearer ${this.config.auth.token}`;
|
|
2149
|
-
}
|
|
2150
|
-
const fetchOptions = {
|
|
2151
|
-
method,
|
|
2152
|
-
headers,
|
|
2153
|
-
signal: controller.signal,
|
|
2154
|
-
};
|
|
2155
|
-
if (body !== undefined) {
|
|
2156
|
-
fetchOptions.body = JSON.stringify(body);
|
|
2157
|
-
}
|
|
2158
|
-
const response = await fetch(url, fetchOptions);
|
|
2159
|
-
// Handle 204 No Content
|
|
2160
|
-
if (response.status === 204) {
|
|
2161
|
-
return undefined;
|
|
2162
|
-
}
|
|
2163
|
-
if (!response.ok) {
|
|
2164
|
-
const errorText = await response.text();
|
|
2165
|
-
let errorMessage = `Request failed with status ${response.status}`;
|
|
2166
|
-
if (errorText) {
|
|
2167
|
-
const errorJson = safeJsonParse(errorText);
|
|
2168
|
-
if (errorJson?.message) {
|
|
2169
|
-
errorMessage = errorJson.message;
|
|
2170
|
-
}
|
|
2171
|
-
else if (errorText) {
|
|
2172
|
-
errorMessage = errorText;
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
switch (response.status) {
|
|
2176
|
-
case 401:
|
|
2177
|
-
throw new UnauthenticatedError(errorMessage);
|
|
2178
|
-
case 402:
|
|
2179
|
-
throw new EnterpriseRequiredError(errorMessage);
|
|
2180
|
-
case 403:
|
|
2181
|
-
throw new UnauthorizedError(errorMessage);
|
|
2182
|
-
default:
|
|
2183
|
-
throw new IronflowError(errorMessage, {
|
|
2184
|
-
code: `HTTP_${response.status}`,
|
|
2185
|
-
retryable: response.status >= 500,
|
|
2186
|
-
});
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
return response.json();
|
|
2190
|
-
}
|
|
2191
|
-
catch (error) {
|
|
2192
|
-
if (error instanceof IronflowError ||
|
|
2193
|
-
error instanceof UnauthenticatedError ||
|
|
2194
|
-
error instanceof EnterpriseRequiredError ||
|
|
2195
|
-
error instanceof UnauthorizedError) {
|
|
2196
|
-
throw error;
|
|
2197
|
-
}
|
|
2198
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2199
|
-
throw new IronflowError(`Request timeout after ${timeout}ms for ${method} ${path}`, { code: "TIMEOUT", retryable: true });
|
|
2200
|
-
}
|
|
2201
|
-
throw new IronflowError(error instanceof Error
|
|
2202
|
-
? `${method} ${path} failed: ${error.message}`
|
|
2203
|
-
: `${method} ${path} failed`, {
|
|
2204
|
-
code: "REQUEST_FAILED",
|
|
2205
|
-
retryable: true,
|
|
2206
|
-
cause: error instanceof Error ? error : undefined,
|
|
2207
|
-
});
|
|
2208
|
-
}
|
|
2209
|
-
finally {
|
|
2210
|
-
clearTimeout(timeoutId);
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2213
|
-
mapRunResponse(response) {
|
|
2214
|
-
// ConnectRPC returns proto enum strings like "RUN_STATUS_COMPLETED" — normalize to "completed"
|
|
2215
|
-
const rawStatus = response.status.toLowerCase().replace(/^run_status_/, "");
|
|
2216
|
-
const statusResult = RunStatusSchema.safeParse(rawStatus);
|
|
2217
|
-
const status = statusResult.success ? statusResult.data : "failed";
|
|
2218
|
-
return {
|
|
2219
|
-
id: response.id,
|
|
2220
|
-
functionId: response.functionId,
|
|
2221
|
-
eventId: response.eventId,
|
|
2222
|
-
status,
|
|
2223
|
-
attempt: response.attempt,
|
|
2224
|
-
maxAttempts: response.maxAttempts,
|
|
2225
|
-
input: response.input,
|
|
2226
|
-
output: response.output,
|
|
2227
|
-
error: response.error,
|
|
2228
|
-
startedAt: response.startedAt ? new Date(response.startedAt) : undefined,
|
|
2229
|
-
endedAt: response.endedAt ? new Date(response.endedAt) : undefined,
|
|
2230
|
-
createdAt: new Date(response.createdAt),
|
|
2231
|
-
updatedAt: new Date(response.updatedAt),
|
|
2232
|
-
};
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
/**
|
|
2236
|
-
* Singleton instance
|
|
2237
|
-
*/
|
|
2238
|
-
export const ironflow = new IronflowClient();
|
|
2239
|
-
/**
|
|
2240
|
-
* Export the class for advanced usage
|
|
2241
|
-
*/
|
|
2242
|
-
export { IronflowClient };
|
|
2243
|
-
//# sourceMappingURL=client.js.map
|