@ironflow/node 0.19.3 → 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.
Files changed (91) hide show
  1. package/LICENSE +181 -0
  2. package/README.md +5 -2
  3. package/package.json +20 -14
  4. package/dist/client.d.ts +0 -924
  5. package/dist/client.d.ts.map +0 -1
  6. package/dist/client.js +0 -1526
  7. package/dist/client.js.map +0 -1
  8. package/dist/command-dedup.d.ts +0 -61
  9. package/dist/command-dedup.d.ts.map +0 -1
  10. package/dist/command-dedup.js +0 -129
  11. package/dist/command-dedup.js.map +0 -1
  12. package/dist/config-client.d.ts +0 -58
  13. package/dist/config-client.d.ts.map +0 -1
  14. package/dist/config-client.js +0 -171
  15. package/dist/config-client.js.map +0 -1
  16. package/dist/function.d.ts +0 -53
  17. package/dist/function.d.ts.map +0 -1
  18. package/dist/function.js +0 -56
  19. package/dist/function.js.map +0 -1
  20. package/dist/index.d.ts +0 -71
  21. package/dist/index.d.ts.map +0 -1
  22. package/dist/index.js +0 -70
  23. package/dist/index.js.map +0 -1
  24. package/dist/internal/context.d.ts +0 -142
  25. package/dist/internal/context.d.ts.map +0 -1
  26. package/dist/internal/context.js +0 -306
  27. package/dist/internal/context.js.map +0 -1
  28. package/dist/internal/errors.d.ts +0 -66
  29. package/dist/internal/errors.d.ts.map +0 -1
  30. package/dist/internal/errors.js +0 -29
  31. package/dist/internal/errors.js.map +0 -1
  32. package/dist/internal/run-context.d.ts +0 -10
  33. package/dist/internal/run-context.d.ts.map +0 -1
  34. package/dist/internal/run-context.js +0 -23
  35. package/dist/internal/run-context.js.map +0 -1
  36. package/dist/kv.d.ts +0 -86
  37. package/dist/kv.d.ts.map +0 -1
  38. package/dist/kv.js +0 -261
  39. package/dist/kv.js.map +0 -1
  40. package/dist/projection-runner.d.ts +0 -83
  41. package/dist/projection-runner.d.ts.map +0 -1
  42. package/dist/projection-runner.js +0 -470
  43. package/dist/projection-runner.js.map +0 -1
  44. package/dist/projection.d.ts +0 -36
  45. package/dist/projection.d.ts.map +0 -1
  46. package/dist/projection.js +0 -55
  47. package/dist/projection.js.map +0 -1
  48. package/dist/secrets.d.ts +0 -6
  49. package/dist/secrets.d.ts.map +0 -1
  50. package/dist/secrets.js +0 -19
  51. package/dist/secrets.js.map +0 -1
  52. package/dist/serve.d.ts +0 -71
  53. package/dist/serve.d.ts.map +0 -1
  54. package/dist/serve.js +0 -434
  55. package/dist/serve.js.map +0 -1
  56. package/dist/step.d.ts +0 -18
  57. package/dist/step.d.ts.map +0 -1
  58. package/dist/step.js +0 -581
  59. package/dist/step.js.map +0 -1
  60. package/dist/subscribe.d.ts +0 -164
  61. package/dist/subscribe.d.ts.map +0 -1
  62. package/dist/subscribe.js +0 -487
  63. package/dist/subscribe.js.map +0 -1
  64. package/dist/test/index.d.ts +0 -22
  65. package/dist/test/index.d.ts.map +0 -1
  66. package/dist/test/index.js +0 -112
  67. package/dist/test/index.js.map +0 -1
  68. package/dist/test/test-step.d.ts +0 -21
  69. package/dist/test/test-step.d.ts.map +0 -1
  70. package/dist/test/test-step.js +0 -83
  71. package/dist/test/test-step.js.map +0 -1
  72. package/dist/types.d.ts +0 -108
  73. package/dist/types.d.ts.map +0 -1
  74. package/dist/types.js +0 -5
  75. package/dist/types.js.map +0 -1
  76. package/dist/version.d.ts +0 -2
  77. package/dist/version.d.ts.map +0 -1
  78. package/dist/version.js +0 -4
  79. package/dist/version.js.map +0 -1
  80. package/dist/webhook.d.ts +0 -22
  81. package/dist/webhook.d.ts.map +0 -1
  82. package/dist/webhook.js +0 -23
  83. package/dist/webhook.js.map +0 -1
  84. package/dist/worker-streaming.d.ts +0 -17
  85. package/dist/worker-streaming.d.ts.map +0 -1
  86. package/dist/worker-streaming.js +0 -510
  87. package/dist/worker-streaming.js.map +0 -1
  88. package/dist/worker.d.ts +0 -28
  89. package/dist/worker.d.ts.map +0 -1
  90. package/dist/worker.js +0 -540
  91. package/dist/worker.js.map +0 -1
package/dist/client.js DELETED
@@ -1,1526 +0,0 @@
1
- /**
2
- * Ironflow Node.js Client
3
- *
4
- * HTTP client for interacting with the Ironflow server.
5
- * Provides methods for registering functions, triggering events, and managing runs.
6
- */
7
- import { getCurrentRunId } from "./internal/run-context.js";
8
- import { API_ENDPOINTS, DEFAULT_SERVER_URL, getServerUrl, IronflowError, RunFailedError, RunCancelledError, UnauthenticatedError, EnterpriseRequiredError, UnauthorizedError, } from "@ironflow/core";
9
- import { KVClient } from "./kv.js";
10
- import { CommandDedup } from "./command-dedup.js";
11
- import { ConfigClient } from "./config-client.js";
12
- // ============================================================================
13
- // Client Implementation
14
- // ============================================================================
15
- /**
16
- * Ironflow client for server-side operations
17
- *
18
- * @example
19
- * ```typescript
20
- * import { createClient } from "@ironflow/node";
21
- *
22
- * const client = createClient({
23
- * serverUrl: "http://localhost:9123",
24
- * });
25
- *
26
- * // Register a function
27
- * await client.registerFunction({
28
- * id: "my-function",
29
- * name: "My Function",
30
- * triggers: [{ event: "my.event" }],
31
- * endpointUrl: "http://localhost:3000/api/ironflow",
32
- * preferredMode: "push",
33
- * });
34
- *
35
- * // Emit an event
36
- * const result = await client.emit("my.event", { data: "value" });
37
- * console.log("Created runs:", result.runIds);
38
- * ```
39
- */
40
- export class IronflowClient {
41
- serverUrl;
42
- apiKey;
43
- timeout;
44
- onErrorHandler;
45
- constructor(config = {}) {
46
- this.serverUrl = config.serverUrl || getServerUrl() || DEFAULT_SERVER_URL;
47
- this.apiKey = config.apiKey;
48
- this.timeout = config.timeout ?? 30000;
49
- this.onErrorHandler = config.onError;
50
- }
51
- /**
52
- * Register a function with the Ironflow server
53
- */
54
- async registerFunction(request) {
55
- const body = {
56
- id: request.id,
57
- };
58
- if (request.name)
59
- body.name = request.name;
60
- if (request.description)
61
- body.description = request.description;
62
- if (request.triggers)
63
- body.triggers = request.triggers;
64
- if (request.retry)
65
- body.retry = request.retry;
66
- if (request.timeoutMs)
67
- body.timeoutMs = request.timeoutMs;
68
- if (request.concurrency)
69
- body.concurrency = request.concurrency;
70
- if (request.preferredMode)
71
- body.preferredMode = request.preferredMode;
72
- if (request.endpointUrl)
73
- body.endpointUrl = request.endpointUrl;
74
- if (request.actorKey)
75
- body.actorKey = request.actorKey;
76
- if (request.pauseBehavior)
77
- body.pauseBehavior = request.pauseBehavior;
78
- const response = await this.request(API_ENDPOINTS.REGISTER_FUNCTION, body, "registerFunction");
79
- return { created: response.created };
80
- }
81
- /**
82
- * Emit an event to trigger workflows
83
- *
84
- * @example
85
- * ```typescript
86
- * const result = await client.emit("order.placed", {
87
- * orderId: "123",
88
- * total: 99.99,
89
- * });
90
- * console.log("Created runs:", result.runIds);
91
- * ```
92
- */
93
- async emit(eventName, data, options) {
94
- const body = {
95
- event: eventName,
96
- data,
97
- };
98
- if (options?.version)
99
- body.version = options.version;
100
- if (options?.idempotencyKey)
101
- body.idempotencyKey = options.idempotencyKey;
102
- if (options?.metadata)
103
- body.metadata = options.metadata;
104
- const response = await this.request(API_ENDPOINTS.TRIGGER, body, "emit");
105
- return {
106
- runIds: response.runIds || [],
107
- eventId: response.eventId,
108
- };
109
- }
110
- /**
111
- * Emit an event synchronously — waits for the triggered run to complete and returns the result.
112
- *
113
- * Calls the TriggerSync endpoint, which blocks until the run finishes or the timeout elapses.
114
- * Throws RunFailedError if the run fails, RunCancelledError if it is cancelled.
115
- *
116
- * @example
117
- * ```typescript
118
- * const result = await client.emitSync("order.placed", { orderId: "123" });
119
- * console.log("Output:", result.output);
120
- * ```
121
- */
122
- async emitSync(eventName, data, options) {
123
- const timeout = options?.timeout ?? 30000;
124
- const fetchTimeout = timeout + 5000;
125
- const url = `${this.serverUrl}/ironflow.v1.IronflowService/TriggerSync`;
126
- const headers = {
127
- "Content-Type": "application/json",
128
- };
129
- if (this.apiKey) {
130
- headers["Authorization"] = `Bearer ${this.apiKey}`;
131
- }
132
- const controller = new AbortController();
133
- const timeoutId = setTimeout(() => controller.abort(), fetchTimeout);
134
- let status;
135
- try {
136
- const response = await fetch(url, {
137
- method: "POST",
138
- headers,
139
- body: JSON.stringify({ event: eventName, data, timeout_ms: timeout }),
140
- signal: controller.signal,
141
- });
142
- status = response.status;
143
- if (!response.ok) {
144
- const errorBody = await response.text();
145
- let errorMessage = `Request failed with status ${response.status}`;
146
- try {
147
- const errorJson = JSON.parse(errorBody);
148
- if (errorJson.message)
149
- errorMessage = errorJson.message;
150
- else if (errorJson.code)
151
- errorMessage = `Error code: ${errorJson.code}`;
152
- else
153
- errorMessage = errorBody;
154
- }
155
- catch {
156
- errorMessage = errorBody;
157
- }
158
- this.throwTypedError(response.status, errorMessage);
159
- }
160
- const body = await response.json();
161
- if (!body.results?.length) {
162
- throw new IronflowError("No results returned from TriggerSync", { code: "NO_RESULTS", retryable: false });
163
- }
164
- const result = body.results[0];
165
- if (!result) {
166
- throw new IronflowError("No results returned from TriggerSync", { code: "NO_RESULTS", retryable: false });
167
- }
168
- if (result.status === "failed") {
169
- throw new RunFailedError(result.runId, result.error);
170
- }
171
- if (result.status === "cancelled") {
172
- throw new RunCancelledError(result.runId);
173
- }
174
- return {
175
- runId: result.runId,
176
- functionId: result.functionId,
177
- status: result.status,
178
- output: result.output,
179
- durationMs: result.durationMs,
180
- };
181
- }
182
- catch (error) {
183
- await this.callOnError(error, { method: "emitSync", endpoint: "/ironflow.v1.IronflowService/TriggerSync", statusCode: status });
184
- throw error;
185
- }
186
- finally {
187
- clearTimeout(timeoutId);
188
- }
189
- }
190
- /**
191
- * Publish a message to a developer pub/sub topic.
192
- * Unlike emit(), this does NOT trigger workflow functions.
193
- *
194
- * @example
195
- * ```typescript
196
- * const result = await client.publish("notifications", {
197
- * userId: "123",
198
- * message: "Hello!",
199
- * });
200
- * console.log("Published:", result.eventId, result.sequence);
201
- * ```
202
- */
203
- async publish(topic, data, options) {
204
- const body = {
205
- topic,
206
- data: data ?? {},
207
- };
208
- if (options?.idempotencyKey) {
209
- body.idempotencyKey = options.idempotencyKey;
210
- }
211
- const response = await this.request("/ironflow.v1.PubSubService/Publish", body, "publish");
212
- return {
213
- eventId: response.eventId,
214
- sequence: parseInt(response.sequence, 10) || 0,
215
- };
216
- }
217
- /**
218
- * List all active developer pub/sub topics.
219
- *
220
- * @example
221
- * ```typescript
222
- * const topics = await client.listTopics();
223
- * for (const t of topics) {
224
- * console.log(t.name, t.messageCount);
225
- * }
226
- * ```
227
- */
228
- async listTopics() {
229
- const response = await this.request("/ironflow.v1.PubSubService/ListTopics", {}, "listTopics");
230
- return (response.topics ?? []).map((t) => ({
231
- name: String(t.name ?? ""),
232
- messageCount: Number(t.messageCount ?? 0),
233
- consumerCount: Number(t.consumerCount ?? 0),
234
- firstMessageAt: t.firstMessageAt ? String(t.firstMessageAt) : undefined,
235
- lastMessageAt: t.lastMessageAt ? String(t.lastMessageAt) : undefined,
236
- }));
237
- }
238
- /**
239
- * Get detailed statistics for a topic.
240
- *
241
- * @example
242
- * ```typescript
243
- * const stats = await client.getTopicStats("notifications");
244
- * console.log("Messages:", stats.messageCount, "Lag:", stats.lag);
245
- * ```
246
- */
247
- async getTopicStats(topic) {
248
- const response = await this.request("/ironflow.v1.PubSubService/GetTopicStats", { topic }, "getTopicStats");
249
- return {
250
- name: String(response.name ?? ""),
251
- messageCount: Number(response.messageCount ?? 0),
252
- consumerCount: Number(response.consumerCount ?? 0),
253
- lag: Number(response.lag ?? 0),
254
- firstSeq: Number(response.firstSeq ?? 0),
255
- lastSeq: Number(response.lastSeq ?? 0),
256
- };
257
- }
258
- /**
259
- * Get a run by ID
260
- */
261
- async getRun(runId) {
262
- return this.request(API_ENDPOINTS.GET_RUN, { id: runId }, "getRun");
263
- }
264
- /**
265
- * List runs with optional filtering
266
- */
267
- async listRuns(options) {
268
- const body = {};
269
- if (options?.functionId)
270
- body.functionId = options.functionId;
271
- if (options?.status)
272
- body.status = options.status;
273
- if (options?.limit)
274
- body.limit = options.limit;
275
- if (options?.cursor)
276
- body.cursor = options.cursor;
277
- return this.request(API_ENDPOINTS.LIST_RUNS, body, "listRuns");
278
- }
279
- /**
280
- * Cancel a running workflow
281
- */
282
- async cancelRun(runId, reason) {
283
- return this.request(API_ENDPOINTS.CANCEL_RUN, {
284
- id: runId,
285
- reason: reason || "",
286
- }, "cancelRun");
287
- }
288
- /**
289
- * Retry a failed run
290
- */
291
- async retryRun(runId, fromStep) {
292
- const body = { id: runId };
293
- if (fromStep)
294
- body.fromStep = fromStep;
295
- return this.request(API_ENDPOINTS.RETRY_RUN, body, "retryRun");
296
- }
297
- /**
298
- * Health check
299
- */
300
- async health() {
301
- const response = await this.request(API_ENDPOINTS.HEALTH, {}, "health");
302
- return response.status;
303
- }
304
- /**
305
- * Entity stream operations
306
- *
307
- * @example
308
- * ```typescript
309
- * // Append an event to a stream
310
- * const result = await client.streams.append("order-123", {
311
- * name: "order.created",
312
- * data: { total: 100 },
313
- * entityType: "order",
314
- * });
315
- *
316
- * // Read events from a stream
317
- * const { events } = await client.streams.read("order-123", { limit: 10 });
318
- *
319
- * // Get stream info
320
- * const info = await client.streams.getInfo("order-123");
321
- * ```
322
- */
323
- streams = {
324
- /**
325
- * Append an event to an entity stream
326
- */
327
- append: async (entityId, input, options) => {
328
- const body = {
329
- entity_id: entityId,
330
- entity_type: input.entityType,
331
- event_name: input.name,
332
- data: input.data,
333
- expected_version: options?.expectedVersion ?? -1,
334
- idempotency_key: options?.idempotencyKey ?? "",
335
- version: options?.version ?? 1,
336
- };
337
- if (options?.metadata !== undefined) {
338
- body.metadata = options.metadata;
339
- }
340
- const response = await this.request("/ironflow.v1.EntityStreamService/AppendEvent", body, "streams.append");
341
- return {
342
- entityVersion: response.entity_version,
343
- eventId: response.event_id,
344
- };
345
- },
346
- /**
347
- * Read events from an entity stream
348
- */
349
- read: async (entityId, options) => {
350
- const response = await this.request("/ironflow.v1.EntityStreamService/ReadStream", {
351
- entity_id: entityId,
352
- from_version: options?.fromVersion ?? 0,
353
- limit: options?.limit ?? 0,
354
- direction: options?.direction ?? "forward",
355
- }, "streams.read");
356
- return {
357
- events: (response.events ?? []).map((e) => ({
358
- id: e.id,
359
- name: e.name,
360
- data: e.data ?? {},
361
- entityVersion: e.entity_version,
362
- version: e.version,
363
- timestamp: e.timestamp,
364
- source: e.source,
365
- metadata: e.metadata,
366
- })),
367
- totalCount: response.total_count ?? 0,
368
- };
369
- },
370
- /**
371
- * Get information about an entity stream.
372
- *
373
- * Returns `null` if no events have been written to this stream yet — safe to
374
- * pass `expectedVersion: 0` to `append()` in that case to create the first event.
375
- *
376
- * @example
377
- * ```typescript
378
- * const info = await client.streams.getInfo("order-123");
379
- * await client.streams.append("order-123", event, {
380
- * expectedVersion: info ? info.version : 0,
381
- * });
382
- * ```
383
- */
384
- getInfo: async (entityId) => {
385
- try {
386
- const response = await this.request("/ironflow.v1.EntityStreamService/GetStreamInfo", {
387
- entity_id: entityId,
388
- }, "streams.getInfo");
389
- return {
390
- entityId: response.entity_id,
391
- entityType: response.entity_type,
392
- version: response.version,
393
- eventCount: response.event_count,
394
- createdAt: response.created_at,
395
- updatedAt: response.updated_at,
396
- };
397
- }
398
- catch (err) {
399
- if (err instanceof IronflowError && err.message === "stream not found") {
400
- return null;
401
- }
402
- throw err;
403
- }
404
- },
405
- /**
406
- * Create a snapshot of the materialized state at a specific stream version.
407
- * Use snapshots to speed up state reconstruction for long-lived entity streams.
408
- */
409
- createSnapshot: async (entityId, input) => {
410
- const response = await this.request("/ironflow.v1.EntityStreamService/CreateSnapshot", {
411
- entity_id: entityId,
412
- entity_type: input.entityType,
413
- entity_version: input.entityVersion,
414
- state: input.state,
415
- }, "streams.createSnapshot");
416
- return { snapshotId: response.snapshot_id };
417
- },
418
- /**
419
- * Get the latest snapshot at or before a given version.
420
- * Returns the snapshot closest to the requested version without exceeding it.
421
- */
422
- getSnapshot: async (entityId, options) => {
423
- const response = await this.request("/ironflow.v1.EntityStreamService/GetSnapshot", {
424
- entity_id: entityId,
425
- before_version: options?.beforeVersion ?? 0,
426
- }, "streams.getSnapshot");
427
- return {
428
- snapshotId: response.snapshot_id,
429
- entityId: response.entity_id,
430
- entityType: response.entity_type,
431
- entityVersion: response.entity_version,
432
- state: response.state,
433
- createdAt: response.created_at,
434
- };
435
- },
436
- /**
437
- * List all entity streams.
438
- */
439
- listStreams: async () => {
440
- const resp = await this.restRequest("GET", "/api/v1/streams", undefined, "streams.listStreams");
441
- return resp.streams ?? [];
442
- },
443
- /**
444
- * Get the full event history for an entity.
445
- */
446
- getEntityHistory: async (entityId) => {
447
- const resp = await this.restRequest("GET", `/api/v1/streams/${encodeURIComponent(entityId)}/history`, undefined, "streams.getEntityHistory");
448
- return resp.events ?? [];
449
- },
450
- };
451
- /**
452
- * SQL-backed projections
453
- *
454
- * Create materialized SQL tables from event streams. Events are processed
455
- * server-side using parameterized SQL handlers.
456
- *
457
- * @example
458
- * ```typescript
459
- * // Create a SQL projection
460
- * await client.sqlProjections.create({
461
- * name: "board",
462
- * tableSql: "CREATE TABLE proj_board (id TEXT PRIMARY KEY, title TEXT, status TEXT)",
463
- * eventHandlers: {
464
- * "issue.created": "INSERT INTO proj_board (id, title, status) VALUES (:entity_id, :data.title, 'OPEN')",
465
- * "issue.status_changed": "UPDATE proj_board SET status = :data.to WHERE id = :entity_id",
466
- * },
467
- * events: ["issue.created", "issue.status_changed"],
468
- * });
469
- *
470
- * // Query the projection
471
- * const result = await client.sqlProjections.query("board", {
472
- * where: "status = 'OPEN'",
473
- * orderBy: "title ASC",
474
- * limit: 50,
475
- * });
476
- * ```
477
- */
478
- sqlProjections = {
479
- /**
480
- * Create a SQL-backed projection with a materialized table and event handlers.
481
- */
482
- create: async (input) => {
483
- const response = await this.request("/ironflow.v1.ProjectionService/CreateSQLProjection", {
484
- name: input.name,
485
- table_sql: input.tableSql,
486
- event_handlers: input.eventHandlers,
487
- events: input.events,
488
- description: input.description ?? "",
489
- }, "sqlProjections.create");
490
- return { name: response.name, status: response.status };
491
- },
492
- /**
493
- * Query a SQL-backed projection table with optional filtering, ordering, and pagination.
494
- */
495
- query: async (name, options) => {
496
- const response = await this.request("/ironflow.v1.ProjectionService/QuerySQLProjection", {
497
- name,
498
- where: options?.where ?? "",
499
- order_by: options?.orderBy ?? "",
500
- limit: options?.limit ?? 100,
501
- offset: options?.offset ?? 0,
502
- }, "sqlProjections.query");
503
- return {
504
- columns: response.columns ?? [],
505
- rows: (response.rows ?? []).map((r) => r.values),
506
- totalCount: response.total_count ?? 0,
507
- };
508
- },
509
- };
510
- /**
511
- * API key management
512
- *
513
- * @example
514
- * ```typescript
515
- * // Create an API key
516
- * const { key } = await client.apiKeys.create({ name: "ci-key" });
517
- *
518
- * // List all API keys
519
- * const keys = await client.apiKeys.list();
520
- *
521
- * // Rotate a key
522
- * const rotated = await client.apiKeys.rotate(keys[0].id);
523
- * ```
524
- */
525
- apiKeys = {
526
- /** Create a new API key */
527
- create: async (input) => {
528
- return this.restRequest("POST", "/api/v1/apikeys", input, "apiKeys.create");
529
- },
530
- /** List all API keys */
531
- list: async () => {
532
- return this.restRequest("GET", "/api/v1/apikeys", undefined, "apiKeys.list");
533
- },
534
- /** Get an API key by ID */
535
- get: async (id) => {
536
- return this.restRequest("GET", `/api/v1/apikeys/${id}`, undefined, "apiKeys.get");
537
- },
538
- /** Delete an API key */
539
- delete: async (id) => {
540
- await this.restRequest("DELETE", `/api/v1/apikeys/${id}`, undefined, "apiKeys.delete");
541
- },
542
- /** Rotate an API key (generates a new secret) */
543
- rotate: async (id) => {
544
- return this.restRequest("POST", `/api/v1/apikeys/${id}/rotate`, undefined, "apiKeys.rotate");
545
- },
546
- };
547
- /**
548
- * Organization management (enterprise)
549
- *
550
- * @example
551
- * ```typescript
552
- * const org = await client.orgs.create({ name: "Acme Corp" });
553
- * const orgs = await client.orgs.list();
554
- * await client.orgs.update(org.id, { name: "Acme Inc" });
555
- * ```
556
- */
557
- orgs = {
558
- /** Create a new organization */
559
- create: async (input) => {
560
- return this.restRequest("POST", "/api/v1/orgs", input, "orgs.create");
561
- },
562
- /** List all organizations */
563
- list: async () => {
564
- return this.restRequest("GET", "/api/v1/orgs", undefined, "orgs.list");
565
- },
566
- /** Get an organization by ID */
567
- get: async (id) => {
568
- return this.restRequest("GET", `/api/v1/orgs/${id}`, undefined, "orgs.get");
569
- },
570
- /** Update an organization */
571
- update: async (id, input) => {
572
- return this.restRequest("PATCH", `/api/v1/orgs/${id}`, input, "orgs.update");
573
- },
574
- /** Delete an organization */
575
- delete: async (id) => {
576
- await this.restRequest("DELETE", `/api/v1/orgs/${id}`, undefined, "orgs.delete");
577
- },
578
- };
579
- /**
580
- * Role management (enterprise)
581
- *
582
- * @example
583
- * ```typescript
584
- * const role = await client.roles.create({ name: "deployer", org_id: orgId });
585
- * await client.roles.assignPolicy(role.id, policyId);
586
- * const roles = await client.roles.list(orgId);
587
- * ```
588
- */
589
- roles = {
590
- /** Create a new role */
591
- create: async (input) => {
592
- return this.restRequest("POST", "/api/v1/roles", input, "roles.create");
593
- },
594
- /** List roles, optionally filtered by organization */
595
- list: async (orgId) => {
596
- const query = orgId ? `?org_id=${encodeURIComponent(orgId)}` : "";
597
- return this.restRequest("GET", `/api/v1/roles${query}`, undefined, "roles.list");
598
- },
599
- /** Get a role by ID */
600
- get: async (id) => {
601
- return this.restRequest("GET", `/api/v1/roles/${id}`, undefined, "roles.get");
602
- },
603
- /** Update a role */
604
- update: async (id, input) => {
605
- return this.restRequest("PATCH", `/api/v1/roles/${id}`, input, "roles.update");
606
- },
607
- /** Delete a role */
608
- delete: async (id) => {
609
- await this.restRequest("DELETE", `/api/v1/roles/${id}`, undefined, "roles.delete");
610
- },
611
- /** Assign a policy to a role */
612
- assignPolicy: async (roleId, policyId) => {
613
- await this.restRequest("POST", `/api/v1/roles/${roleId}/policies`, {
614
- policy_id: policyId,
615
- }, "roles.assignPolicy");
616
- },
617
- /** Remove a policy from a role */
618
- removePolicy: async (roleId, policyId) => {
619
- await this.restRequest("DELETE", `/api/v1/roles/${roleId}/policies/${policyId}`, undefined, "roles.removePolicy");
620
- },
621
- };
622
- /**
623
- * Policy management (enterprise)
624
- *
625
- * @example
626
- * ```typescript
627
- * const policy = await client.policies.create({
628
- * name: "allow-emit",
629
- * effect: "allow",
630
- * actions: "emit:*",
631
- * resources: "*",
632
- * org_id: orgId,
633
- * });
634
- * const policies = await client.policies.list(orgId);
635
- * ```
636
- */
637
- policies = {
638
- /** Create a new policy */
639
- create: async (input) => {
640
- return this.restRequest("POST", "/api/v1/policies", input, "policies.create");
641
- },
642
- /** List policies, optionally filtered by organization */
643
- list: async (orgId) => {
644
- const query = orgId ? `?org_id=${encodeURIComponent(orgId)}` : "";
645
- return this.restRequest("GET", `/api/v1/policies${query}`, undefined, "policies.list");
646
- },
647
- /** Get a policy by ID */
648
- get: async (id) => {
649
- return this.restRequest("GET", `/api/v1/policies/${id}`, undefined, "policies.get");
650
- },
651
- /** Update a policy */
652
- update: async (id, input) => {
653
- return this.restRequest("PATCH", `/api/v1/policies/${id}`, input, "policies.update");
654
- },
655
- /** Delete a policy */
656
- delete: async (id) => {
657
- await this.restRequest("DELETE", `/api/v1/policies/${id}`, undefined, "policies.delete");
658
- },
659
- };
660
- /**
661
- * Projection management
662
- *
663
- * @example
664
- * ```typescript
665
- * const state = await client.projections.get("order-summary");
666
- * const statuses = await client.projections.list();
667
- * await client.projections.rebuild("order-summary");
668
- * ```
669
- */
670
- projections = {
671
- /** Get the current materialized state of a projection */
672
- get: async (name) => {
673
- return this.restRequest("GET", `/api/v1/projections/${encodeURIComponent(name)}`, undefined, "projections.get");
674
- },
675
- /** List all projection statuses */
676
- list: async () => {
677
- return this.restRequest("GET", "/api/v1/projections", undefined, "projections.list");
678
- },
679
- /** Get operational status for a projection */
680
- getStatus: async (name) => {
681
- return this.restRequest("GET", `/api/v1/projections/${encodeURIComponent(name)}/status`, undefined, "projections.getStatus");
682
- },
683
- /** Trigger a full rebuild of a projection */
684
- rebuild: async (name) => {
685
- return this.restRequest("POST", `/api/v1/projections/${encodeURIComponent(name)}/rebuild`, undefined, "projections.rebuild");
686
- },
687
- /** Get the status of an in-progress or completed rebuild job */
688
- getRebuildJob: async (name) => {
689
- return this.restRequest("GET", `/api/v1/projections/${encodeURIComponent(name)}/rebuild`, undefined, "projections.getRebuildJob");
690
- },
691
- /** Delete a projection */
692
- delete: async (name) => {
693
- await this.restRequest("DELETE", `/api/v1/projections/${encodeURIComponent(name)}`, undefined, "projections.delete");
694
- },
695
- /** Pause a projection (stop consuming new events) */
696
- pause: async (name) => {
697
- await this.restRequest("POST", `/api/v1/projections/${encodeURIComponent(name)}/pause`, undefined, "projections.pause");
698
- },
699
- /** Resume a paused projection */
700
- resume: async (name) => {
701
- await this.restRequest("POST", `/api/v1/projections/${encodeURIComponent(name)}/resume`, undefined, "projections.resume");
702
- },
703
- /** Cancel an in-progress rebuild */
704
- cancelRebuild: async (name) => {
705
- await this.restRequest("POST", `/api/v1/projections/${encodeURIComponent(name)}/cancel`, undefined, "projections.cancelRebuild");
706
- },
707
- /**
708
- * Wait until the named projection has processed events up to `minSeq`,
709
- * or the timeout elapses. Read-your-writes primitive for CQRS: pair
710
- * with `sequence` from a `streams.append` response.
711
- *
712
- * ```typescript
713
- * const { sequence } = await client.streams.append(orderId, event);
714
- * await client.projections.waitForCatchup("order-detail-view", {
715
- * minSeq: sequence,
716
- * partition: orderId,
717
- * timeoutMs: 5000,
718
- * });
719
- * ```
720
- *
721
- * Errors: 404 (projection not found), 409 (paused/rebuilding/partition
722
- * unsupported for external), 429 (wait capacity exceeded).
723
- *
724
- * Issue #473.
725
- */
726
- waitForCatchup: async (name, opts) => {
727
- const params = new URLSearchParams();
728
- params.set("minSeq", String(opts.minSeq));
729
- if (opts.timeoutMs !== undefined) {
730
- params.set("timeout", String(opts.timeoutMs));
731
- }
732
- if (opts.partition) {
733
- params.set("partition", opts.partition);
734
- }
735
- return this.restRequest("GET", `/api/v1/projections/${encodeURIComponent(name)}/catchup?${params.toString()}`, undefined, "projections.waitForCatchup");
736
- },
737
- /**
738
- * Wait on multiple projections in a single request. All items share
739
- * a single timeout deadline and a single atomic slot reservation on
740
- * the server — if the server's cap cannot absorb N items, the whole
741
- * batch is rejected with 429. Per-item failures are returned per
742
- * element via `error` fields.
743
- *
744
- * Max 16 items. Issue #473.
745
- */
746
- waitForCatchupBatch: async (items, opts = {}) => {
747
- // Always send minSeq as a string. uint64 sequences can exceed JS's
748
- // safe-integer range (2^53-1); stringifying keeps the value exact
749
- // across the JSON boundary and matches protojson's convention for
750
- // 64-bit ints.
751
- const body = {
752
- items: items.map((i) => ({
753
- name: i.name,
754
- minSeq: String(i.minSeq),
755
- ...(i.partition ? { partition: i.partition } : {}),
756
- })),
757
- ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
758
- };
759
- const resp = await this.restRequest("POST", "/api/v1/projections/catchup/batch", body, "projections.waitForCatchupBatch");
760
- return resp.results ?? [];
761
- },
762
- /**
763
- * Wait for a specific event (identified by `eventId` from a
764
- * `streams.append` response) to be processed by the given projection.
765
- * The server resolves eventId → NATS seq internally.
766
- *
767
- * Errors: 404 (event not found), 409 (event predates sequence
768
- * tracking — fall back to waitForCatchup with minSeq from a
769
- * fresh write), plus the standard wait errors.
770
- *
771
- * Issue #473.
772
- */
773
- waitForEvent: async (eventId, projection, opts = {}) => {
774
- const body = {
775
- eventId,
776
- projection,
777
- ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
778
- ...(opts.partition ? { partition: opts.partition } : {}),
779
- };
780
- return this.restRequest("POST", "/api/v1/projections/wait-for-event", body, "projections.waitForEvent");
781
- },
782
- };
783
- /**
784
- * Secrets management
785
- *
786
- * @example
787
- * ```typescript
788
- * await client.secrets.set("stripe-key", "sk_live_...");
789
- * const secret = await client.secrets.get("stripe-key");
790
- * const all = await client.secrets.list();
791
- * await client.secrets.delete("stripe-key");
792
- * ```
793
- */
794
- secrets = {
795
- /** Get a secret by name (returns value) */
796
- get: async (name) => {
797
- return this.restRequest("GET", `/api/v1/secrets/${encodeURIComponent(name)}`, undefined, "secrets.get");
798
- },
799
- /** Create a new secret */
800
- set: async (name, value) => {
801
- return this.restRequest("POST", "/api/v1/secrets", { name, value }, "secrets.set");
802
- },
803
- /** Update an existing secret's value */
804
- update: async (name, value) => {
805
- return this.restRequest("PUT", `/api/v1/secrets/${encodeURIComponent(name)}`, { value }, "secrets.update");
806
- },
807
- /** List all secrets (names only, no values) */
808
- list: async () => {
809
- return this.restRequest("GET", "/api/v1/secrets", undefined, "secrets.list");
810
- },
811
- /** Delete a secret */
812
- delete: async (name) => {
813
- await this.restRequest("DELETE", `/api/v1/secrets/${encodeURIComponent(name)}`, undefined, "secrets.delete");
814
- },
815
- };
816
- /**
817
- * Project management
818
- *
819
- * @example
820
- * ```typescript
821
- * const project = await client.projects.create({ name: "my-service" });
822
- * const projects = await client.projects.list();
823
- * await client.projects.update(project.id, { name: "renamed-service" });
824
- * await client.projects.delete(project.id);
825
- * ```
826
- */
827
- projects = {
828
- /** List all projects */
829
- list: async () => {
830
- return this.restRequest("GET", "/api/v1/projects", undefined, "projects.list");
831
- },
832
- /** Create a new project */
833
- create: async (input) => {
834
- return this.restRequest("POST", "/api/v1/projects", input, "projects.create");
835
- },
836
- /** Update a project */
837
- update: async (id, input) => {
838
- return this.restRequest("PUT", `/api/v1/projects/${encodeURIComponent(id)}`, input, "projects.update");
839
- },
840
- /** Delete a project */
841
- delete: async (id) => {
842
- await this.restRequest("DELETE", `/api/v1/projects/${encodeURIComponent(id)}`, undefined, "projects.delete");
843
- },
844
- };
845
- /**
846
- * Environment management
847
- *
848
- * @example
849
- * ```typescript
850
- * const env = await client.environments.create({ name: "staging", projectId: "proj_..." });
851
- * const envs = await client.environments.list();
852
- * await client.environments.update(env.id, { name: "staging-v2" });
853
- * await client.environments.delete(env.id);
854
- * ```
855
- */
856
- environments = {
857
- /** List all environments */
858
- list: async () => {
859
- return this.restRequest("GET", "/api/v1/environments", undefined, "environments.list");
860
- },
861
- /** Create a new environment */
862
- create: async (input) => {
863
- return this.restRequest("POST", "/api/v1/environments", input, "environments.create");
864
- },
865
- /** Update an environment */
866
- update: async (id, input) => {
867
- return this.restRequest("PUT", `/api/v1/environments/${encodeURIComponent(id)}`, input, "environments.update");
868
- },
869
- /** Delete an environment */
870
- delete: async (id) => {
871
- await this.restRequest("DELETE", `/api/v1/environments/${encodeURIComponent(id)}`, undefined, "environments.delete");
872
- },
873
- };
874
- /**
875
- * Event schema registry operations
876
- *
877
- * @example
878
- * ```typescript
879
- * // Register a schema
880
- * const schema = await client.schemas.register({
881
- * name: "order.placed",
882
- * version: 1,
883
- * schema: { type: "object", properties: { orderId: { type: "string" } } },
884
- * });
885
- *
886
- * // List all schemas
887
- * const schemas = await client.schemas.list();
888
- *
889
- * // Get latest version of a schema
890
- * const latest = await client.schemas.get("order.placed");
891
- *
892
- * // Get a specific version
893
- * const v1 = await client.schemas.getVersion("order.placed", 1);
894
- *
895
- * // Test an upcast transformation
896
- * const result = await client.schemas.testUpcast({
897
- * eventName: "order.placed",
898
- * fromVersion: 1,
899
- * toVersion: 2,
900
- * data: { orderId: "123" },
901
- * });
902
- * ```
903
- */
904
- schemas = {
905
- /** Register a new event schema (or a new version of an existing schema) */
906
- register: async (input) => {
907
- return this.restRequest("POST", "/api/v1/events/schemas", {
908
- event_name: input.name,
909
- version: input.version,
910
- schema_json: JSON.stringify(input.schema),
911
- }, "schemas.register");
912
- },
913
- /** List all registered event schemas */
914
- list: async () => {
915
- const resp = await this.restRequest("GET", "/api/v1/events/schemas", undefined, "schemas.list");
916
- return resp.schemas ?? [];
917
- },
918
- /** Get the latest version of an event schema by name */
919
- get: async (name) => {
920
- return this.restRequest("GET", `/api/v1/events/schemas/${encodeURIComponent(name)}`, undefined, "schemas.get");
921
- },
922
- /** Get a specific version of an event schema */
923
- getVersion: async (name, version) => {
924
- return this.restRequest("GET", `/api/v1/events/schemas/${encodeURIComponent(name)}/${version}`, undefined, "schemas.getVersion");
925
- },
926
- /** Delete a specific version of an event schema */
927
- delete: async (name, version) => {
928
- await this.restRequest("DELETE", `/api/v1/events/schemas/${encodeURIComponent(name)}/${version}`, undefined, "schemas.delete");
929
- },
930
- /** Test an upcast transformation between two schema versions */
931
- testUpcast: async (input) => {
932
- return this.restRequest("POST", "/api/v1/events/upcast", input, "schemas.testUpcast");
933
- },
934
- };
935
- /**
936
- * Get the reconstructed state of a run at a specific point in time.
937
- *
938
- * @param runId The run ID to query
939
- * @param timestamp The point in time to reconstruct state at
940
- */
941
- async getRunStateAt(runId, timestamp) {
942
- return this.request("/ironflow.v1.TimeTravelService/GetRunStateAt", { run_id: runId, timestamp: timestamp.toISOString() }, "getRunStateAt");
943
- }
944
- /**
945
- * Get the timeline of events for a run (for time-travel debugging).
946
- *
947
- * @param runId The run ID to query
948
- */
949
- async getRunTimeline(runId) {
950
- const response = await this.request("/ironflow.v1.TimeTravelService/GetRunTimeline", { run_id: runId }, "getRunTimeline");
951
- return response.events ?? [];
952
- }
953
- /**
954
- * Get the output of a specific step at a point in time.
955
- *
956
- * @param runId The run ID
957
- * @param stepId The step ID
958
- * @param timestamp The point in time to query
959
- */
960
- async getStepOutputAt(runId, stepId, timestamp) {
961
- return this.request("/ironflow.v1.TimeTravelService/GetStepOutputAt", { run_id: runId, step_id: stepId, timestamp: timestamp.toISOString() }, "getStepOutputAt");
962
- }
963
- /**
964
- * Get the audit trail for a run.
965
- *
966
- * @param runId The run ID to retrieve the audit trail for
967
- */
968
- async getAuditTrail(runId) {
969
- const response = await this.request("/ironflow.v1.AuditService/GetAuditTrail", { run_id: runId }, "getAuditTrail");
970
- return response.entries ?? [];
971
- }
972
- /**
973
- * Webhook management operations
974
- *
975
- * @example
976
- * ```typescript
977
- * // List all webhook sources
978
- * const sources = await client.webhooks.listSources();
979
- *
980
- * // Delete a webhook source
981
- * await client.webhooks.deleteSource("my-webhook");
982
- *
983
- * // List deliveries for a source
984
- * const { deliveries } = await client.webhooks.listDeliveries({ sourceId: "my-webhook" });
985
- * ```
986
- */
987
- webhooks = {
988
- /** Create a new webhook source */
989
- create: async (input) => {
990
- const response = await this.request("/ironflow.v1.WebhookService/CreateWebhookSource", {
991
- id: input.id,
992
- event_prefix: input.eventPrefix,
993
- verify_header: input.verifyHeader ?? "",
994
- verify_algorithm: input.verifyAlgorithm ?? "",
995
- verify_secret: input.verifySecret ?? "",
996
- metadata: input.metadata,
997
- }, "webhooks.create");
998
- return {
999
- id: response.id,
1000
- eventPrefix: response.event_prefix,
1001
- verifyHeader: response.verify_header,
1002
- verifyAlgorithm: response.verify_algorithm,
1003
- sourceType: response.source_type,
1004
- metadata: response.metadata,
1005
- createdAt: response.created_at,
1006
- updatedAt: response.updated_at,
1007
- };
1008
- },
1009
- /** List all registered webhook sources */
1010
- listSources: async () => {
1011
- const response = await this.request("/ironflow.v1.WebhookService/ListWebhookSources", { limit: 0, offset: 0 }, "webhooks.listSources");
1012
- return (response.sources ?? []).map((s) => ({
1013
- id: s.id,
1014
- eventPrefix: s.event_prefix,
1015
- verifyHeader: s.verify_header,
1016
- verifyAlgorithm: s.verify_algorithm,
1017
- sourceType: s.source_type,
1018
- metadata: s.metadata,
1019
- createdAt: s.created_at,
1020
- updatedAt: s.updated_at,
1021
- }));
1022
- },
1023
- /** Delete a webhook source by ID */
1024
- deleteSource: async (id) => {
1025
- await this.request("/ironflow.v1.WebhookService/DeleteWebhookSource", { id }, "webhooks.deleteSource");
1026
- },
1027
- /** List webhook deliveries with optional filtering */
1028
- listDeliveries: async (opts) => {
1029
- const response = await this.request("/ironflow.v1.WebhookService/ListWebhookDeliveries", {
1030
- source_id: opts?.sourceId ?? "",
1031
- status: opts?.status ?? "",
1032
- limit: opts?.limit ?? 0,
1033
- offset: opts?.offset ?? 0,
1034
- }, "webhooks.listDeliveries");
1035
- return {
1036
- deliveries: (response.deliveries ?? []).map((d) => ({
1037
- id: d.id,
1038
- sourceId: d.source_id,
1039
- externalId: d.external_id,
1040
- status: d.status,
1041
- eventId: d.event_id,
1042
- error: d.error,
1043
- createdAt: d.created_at,
1044
- })),
1045
- totalCount: response.total_count ?? 0,
1046
- };
1047
- },
1048
- };
1049
- /**
1050
- * User management operations
1051
- *
1052
- * @example
1053
- * ```typescript
1054
- * // Create a user
1055
- * const user = await client.users.create({ email: "alice@example.com", password: "secret", roles: ["admin"] });
1056
- *
1057
- * // List users
1058
- * const users = await client.users.list();
1059
- *
1060
- * // Update a user
1061
- * await client.users.update(user.id, { name: "Alice" });
1062
- *
1063
- * // Delete a user
1064
- * await client.users.delete(user.id);
1065
- * ```
1066
- */
1067
- users = {
1068
- /** Create a new user (admin only) */
1069
- create: async (input) => {
1070
- return this.restRequest("POST", "/api/v1/users", input, "users.create");
1071
- },
1072
- /** List all users in the current organization (admin only) */
1073
- list: async () => {
1074
- return this.restRequest("GET", "/api/v1/users", undefined, "users.list");
1075
- },
1076
- /** Get a user by ID */
1077
- get: async (id) => {
1078
- return this.restRequest("GET", `/api/v1/users/${encodeURIComponent(id)}`, undefined, "users.get");
1079
- },
1080
- /** Update a user's profile (admin only) */
1081
- update: async (id, input) => {
1082
- return this.restRequest("PATCH", `/api/v1/users/${encodeURIComponent(id)}`, input, "users.update");
1083
- },
1084
- /** Delete a user (admin only) */
1085
- delete: async (id) => {
1086
- await this.restRequest("DELETE", `/api/v1/users/${encodeURIComponent(id)}`, undefined, "users.delete");
1087
- },
1088
- };
1089
- /**
1090
- * Tenant management operations (enterprise-only)
1091
- *
1092
- * @example
1093
- * ```typescript
1094
- * // List all tenants
1095
- * const tenants = await client.tenants.list();
1096
- * console.log(tenants.map(t => t.name));
1097
- * ```
1098
- */
1099
- tenants = {
1100
- /** List all tenants (enterprise-only) */
1101
- list: async () => {
1102
- return this.restRequest("GET", "/api/v1/tenants", undefined, "tenants.list");
1103
- },
1104
- };
1105
- /**
1106
- * KV store operations
1107
- *
1108
- * @example
1109
- * ```typescript
1110
- * const kv = client.kv();
1111
- * const bucket = await kv.createBucket({ name: "sessions", ttlSeconds: 3600 });
1112
- * const handle = kv.bucket("sessions");
1113
- * const { revision } = await handle.put("user.123", { token: "abc" });
1114
- * const entry = await handle.get("user.123");
1115
- * ```
1116
- */
1117
- kv() {
1118
- return new KVClient({
1119
- serverUrl: this.serverUrl,
1120
- apiKey: this.apiKey,
1121
- timeout: this.timeout,
1122
- onError: this.onErrorHandler,
1123
- });
1124
- }
1125
- /**
1126
- * Create a CommandDedup instance for atomic command-level idempotency.
1127
- *
1128
- * Uses the claim-first pattern backed by NATS KV. The KV bucket is created
1129
- * lazily on the first operation. Store the returned instance and reuse it
1130
- * across requests — do not call commandDedup() per request.
1131
- *
1132
- * @example
1133
- * ```typescript
1134
- * const dedup = client.commandDedup<OrderResult>("order-commands");
1135
- * const prior = await dedup.tryClaim(commandId, { orderId, claimedAt: new Date().toISOString() });
1136
- * if (prior !== null) return prior;
1137
- * try {
1138
- * const result = await runOrderHandler();
1139
- * await dedup.finalize(commandId, result);
1140
- * return result;
1141
- * } catch (err) {
1142
- * await dedup.release(commandId).catch(() => {}); // swallow — don't mask the original error
1143
- * throw err;
1144
- * }
1145
- * ```
1146
- */
1147
- commandDedup(bucketName, options) {
1148
- return new CommandDedup(this.kv(), bucketName, options?.ttlSeconds);
1149
- }
1150
- /**
1151
- * Config management operations
1152
- *
1153
- * @example
1154
- * ```typescript
1155
- * const config = client.config();
1156
- * await config.set("app", { featureX: true });
1157
- * const { data } = await config.get("app");
1158
- * await config.patch("app", { maxRetries: 5 });
1159
- * const configs = await config.list();
1160
- * await config.delete("app");
1161
- * ```
1162
- */
1163
- config() {
1164
- return new ConfigClient({
1165
- serverUrl: this.serverUrl,
1166
- apiKey: this.apiKey,
1167
- timeout: this.timeout,
1168
- onError: this.onErrorHandler,
1169
- });
1170
- }
1171
- /**
1172
- * Patch a step's output (hot patching)
1173
- */
1174
- async patchStep(stepId, output, reason) {
1175
- const endpoint = "/api/v1/steps/patch";
1176
- const url = `${this.serverUrl}${endpoint}`;
1177
- const headers = {
1178
- "Content-Type": "application/json",
1179
- };
1180
- if (this.apiKey) {
1181
- headers["Authorization"] = `Bearer ${this.apiKey}`;
1182
- }
1183
- const controller = new AbortController();
1184
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1185
- let status;
1186
- try {
1187
- const response = await fetch(url, {
1188
- method: "POST",
1189
- headers,
1190
- body: JSON.stringify({ step_id: stepId, output, reason: reason || "" }),
1191
- signal: controller.signal,
1192
- });
1193
- status = response.status;
1194
- if (!response.ok) {
1195
- const errorBody = await response.text();
1196
- throw new Error(errorBody || `Patch step failed: ${response.status}`);
1197
- }
1198
- }
1199
- catch (error) {
1200
- await this.callOnError(error, { method: "patchStep", endpoint, statusCode: status });
1201
- throw error;
1202
- }
1203
- finally {
1204
- clearTimeout(timeoutId);
1205
- }
1206
- }
1207
- /**
1208
- * Resume a paused or failed run
1209
- */
1210
- async resumeRun(runId, fromStep) {
1211
- const endpoint = "/api/v1/runs/resume";
1212
- const url = `${this.serverUrl}${endpoint}`;
1213
- const headers = {
1214
- "Content-Type": "application/json",
1215
- };
1216
- if (this.apiKey) {
1217
- headers["Authorization"] = `Bearer ${this.apiKey}`;
1218
- }
1219
- const controller = new AbortController();
1220
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1221
- let status;
1222
- try {
1223
- const response = await fetch(url, {
1224
- method: "POST",
1225
- headers,
1226
- body: JSON.stringify({ run_id: runId, from_step: fromStep || "" }),
1227
- signal: controller.signal,
1228
- });
1229
- status = response.status;
1230
- if (!response.ok) {
1231
- const errorBody = await response.text();
1232
- throw new Error(errorBody || `Resume run failed: ${response.status}`);
1233
- }
1234
- return response.json();
1235
- }
1236
- catch (error) {
1237
- await this.callOnError(error, { method: "resumeRun", endpoint, statusCode: status });
1238
- throw error;
1239
- }
1240
- finally {
1241
- clearTimeout(timeoutId);
1242
- }
1243
- }
1244
- /**
1245
- * Pause a running workflow run (scoped injection).
1246
- *
1247
- * @example
1248
- * ```typescript
1249
- * const result = await client.pauseRun("run_abc123");
1250
- * console.log(result.status); // "paused"
1251
- * ```
1252
- */
1253
- async pauseRun(runId) {
1254
- return this.request("/ironflow.v1.IronflowService/PauseRun", { run_id: runId }, "pauseRun");
1255
- }
1256
- /**
1257
- * Get the paused state of a run, including completed steps and next step hint.
1258
- *
1259
- * @example
1260
- * ```typescript
1261
- * const state = await client.getPausedState("run_abc123");
1262
- * for (const step of state.steps) {
1263
- * console.log(step.name, step.output, step.injected);
1264
- * }
1265
- * console.log("Next step:", state.nextStepHint);
1266
- * ```
1267
- */
1268
- async getPausedState(runId) {
1269
- const response = await this.request("/ironflow.v1.IronflowService/GetPausedState", { run_id: runId }, "getPausedState");
1270
- return {
1271
- steps: (response.steps || []).map((s) => ({
1272
- id: s.id,
1273
- name: s.name,
1274
- output: s.output ? JSON.parse(s.output) : null,
1275
- injected: s.injected,
1276
- completedAt: s.completed_at,
1277
- })),
1278
- nextStepHint: response.next_step_hint,
1279
- pauseReason: response.pause_reason,
1280
- };
1281
- }
1282
- /**
1283
- * Inject new output for a step in a paused run (scoped injection).
1284
- *
1285
- * @example
1286
- * ```typescript
1287
- * const result = await client.injectStepOutput(
1288
- * "run_abc123",
1289
- * "step_xyz",
1290
- * { corrected: true },
1291
- * "Manual correction"
1292
- * );
1293
- * console.log("Previous output:", result.previousOutput);
1294
- * ```
1295
- */
1296
- async injectStepOutput(runId, stepId, newOutput, reason) {
1297
- const response = await this.request("/ironflow.v1.IronflowService/InjectStepOutput", {
1298
- run_id: runId,
1299
- step_id: stepId,
1300
- new_output: JSON.stringify(newOutput),
1301
- reason: reason ?? "",
1302
- }, "injectStepOutput");
1303
- return {
1304
- stepId: response.step_id,
1305
- previousOutput: response.previous_output
1306
- ? JSON.parse(response.previous_output)
1307
- : null,
1308
- };
1309
- }
1310
- /**
1311
- * List registered functions
1312
- */
1313
- async listFunctions() {
1314
- const endpoint = "/api/v1/functions";
1315
- const url = `${this.serverUrl}${endpoint}`;
1316
- const headers = {};
1317
- if (this.apiKey) {
1318
- headers["Authorization"] = `Bearer ${this.apiKey}`;
1319
- }
1320
- const controller = new AbortController();
1321
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1322
- let status;
1323
- try {
1324
- const response = await fetch(url, {
1325
- method: "GET",
1326
- headers,
1327
- signal: controller.signal,
1328
- });
1329
- status = response.status;
1330
- if (!response.ok) {
1331
- throw new Error(`List functions failed: ${response.status}`);
1332
- }
1333
- const data = (await response.json());
1334
- return data.functions || [];
1335
- }
1336
- catch (error) {
1337
- await this.callOnError(error, { method: "listFunctions", endpoint, statusCode: status });
1338
- throw error;
1339
- }
1340
- finally {
1341
- clearTimeout(timeoutId);
1342
- }
1343
- }
1344
- /**
1345
- * List connected workers
1346
- */
1347
- async listWorkers() {
1348
- const endpoint = "/api/v1/workers";
1349
- const url = `${this.serverUrl}${endpoint}`;
1350
- const headers = {};
1351
- if (this.apiKey) {
1352
- headers["Authorization"] = `Bearer ${this.apiKey}`;
1353
- }
1354
- const controller = new AbortController();
1355
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1356
- let status;
1357
- try {
1358
- const response = await fetch(url, {
1359
- method: "GET",
1360
- headers,
1361
- signal: controller.signal,
1362
- });
1363
- status = response.status;
1364
- if (!response.ok) {
1365
- throw new Error(`List workers failed: ${response.status}`);
1366
- }
1367
- const data = (await response.json());
1368
- return data.workers || [];
1369
- }
1370
- catch (error) {
1371
- await this.callOnError(error, { method: "listWorkers", endpoint, statusCode: status });
1372
- throw error;
1373
- }
1374
- finally {
1375
- clearTimeout(timeoutId);
1376
- }
1377
- }
1378
- /**
1379
- * Make an HTTP request to the server
1380
- */
1381
- async request(endpoint, body, method) {
1382
- const url = `${this.serverUrl}${endpoint}`;
1383
- const headers = {
1384
- "Content-Type": "application/json",
1385
- };
1386
- if (this.apiKey) {
1387
- headers["Authorization"] = `Bearer ${this.apiKey}`;
1388
- }
1389
- const runId = getCurrentRunId();
1390
- if (runId) {
1391
- headers["X-Ironflow-Run-ID"] = runId;
1392
- }
1393
- const controller = new AbortController();
1394
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1395
- let status;
1396
- try {
1397
- const response = await fetch(url, {
1398
- method: "POST",
1399
- headers,
1400
- body: JSON.stringify(body),
1401
- signal: controller.signal,
1402
- });
1403
- status = response.status;
1404
- if (!response.ok) {
1405
- const errorBody = await response.text();
1406
- let errorMessage = `Request failed with status ${response.status}`;
1407
- if (errorBody) {
1408
- try {
1409
- const errorJson = JSON.parse(errorBody);
1410
- if (errorJson.message) {
1411
- errorMessage = errorJson.message;
1412
- }
1413
- else if (errorJson.code) {
1414
- errorMessage = `Error code: ${errorJson.code}`;
1415
- }
1416
- else {
1417
- errorMessage = errorBody;
1418
- }
1419
- }
1420
- catch {
1421
- // Not a JSON response, use raw text.
1422
- errorMessage = errorBody;
1423
- }
1424
- }
1425
- this.throwTypedError(response.status, errorMessage);
1426
- }
1427
- return response.json();
1428
- }
1429
- catch (error) {
1430
- if (method) {
1431
- await this.callOnError(error, { method, endpoint, statusCode: status });
1432
- }
1433
- throw error;
1434
- }
1435
- finally {
1436
- clearTimeout(timeoutId);
1437
- }
1438
- }
1439
- /**
1440
- * Throw a typed error based on HTTP status code.
1441
- */
1442
- throwTypedError(status, message) {
1443
- switch (status) {
1444
- case 401:
1445
- throw new UnauthenticatedError(message);
1446
- case 402:
1447
- throw new EnterpriseRequiredError(message);
1448
- case 403:
1449
- throw new UnauthorizedError(message);
1450
- default:
1451
- throw new IronflowError(message);
1452
- }
1453
- }
1454
- /**
1455
- * Make a REST HTTP request to the server (supports GET, POST, PATCH, DELETE)
1456
- */
1457
- async restRequest(httpMethod, path, body, method) {
1458
- const url = `${this.serverUrl}${path}`;
1459
- const headers = {};
1460
- if (this.apiKey) {
1461
- headers["Authorization"] = `Bearer ${this.apiKey}`;
1462
- }
1463
- const options = { method: httpMethod, headers };
1464
- if (body && (httpMethod === "POST" || httpMethod === "PATCH" || httpMethod === "PUT")) {
1465
- headers["Content-Type"] = "application/json";
1466
- options.body = JSON.stringify(body);
1467
- }
1468
- const controller = new AbortController();
1469
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1470
- let status;
1471
- try {
1472
- const response = await fetch(url, {
1473
- ...options,
1474
- signal: controller.signal,
1475
- });
1476
- status = response.status;
1477
- if (!response.ok) {
1478
- const errBody = await response
1479
- .json()
1480
- .catch(() => ({ error: response.statusText }));
1481
- const message = errBody.error ||
1482
- errBody.message ||
1483
- response.statusText;
1484
- this.throwTypedError(response.status, message);
1485
- }
1486
- if (response.status === 204)
1487
- return undefined;
1488
- return response.json();
1489
- }
1490
- catch (error) {
1491
- if (method) {
1492
- await this.callOnError(error, { method, endpoint: path, statusCode: status });
1493
- }
1494
- throw error;
1495
- }
1496
- finally {
1497
- clearTimeout(timeoutId);
1498
- }
1499
- }
1500
- /**
1501
- * Call the global onError handler if registered.
1502
- * Swallows any errors thrown by the callback.
1503
- */
1504
- async callOnError(error, context) {
1505
- if (!this.onErrorHandler)
1506
- return;
1507
- try {
1508
- await this.onErrorHandler(error, context);
1509
- }
1510
- catch (callbackError) {
1511
- console.error("[ironflow] onError callback threw:", callbackError);
1512
- }
1513
- }
1514
- }
1515
- /**
1516
- * Create a new Ironflow client
1517
- *
1518
- * @example
1519
- * ```typescript
1520
- * const client = createClient({ serverUrl: "http://localhost:9123" });
1521
- * ```
1522
- */
1523
- export function createClient(config) {
1524
- return new IronflowClient(config);
1525
- }
1526
- //# sourceMappingURL=client.js.map