@ironflow/browser 0.1.0-test.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/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # @ironflow/browser
2
+
3
+ Browser client for [Ironflow](https://github.com/anthropics/ironflow) workflow engine. Provides real-time subscriptions, workflow triggers, and event emission for web applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @ironflow/browser
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { ironflow } from '@ironflow/browser';
15
+
16
+ // Configure once at app startup
17
+ ironflow.configure({
18
+ serverUrl: 'http://localhost:9123',
19
+ });
20
+
21
+ // Subscribe to events
22
+ const sub = ironflow.subscribe('events:order.*', {
23
+ onEvent: (event) => console.log('Order:', event),
24
+ });
25
+
26
+ // Trigger a workflow
27
+ const run = await ironflow.trigger('process-order', {
28
+ data: { orderId: '123' },
29
+ });
30
+
31
+ // Emit events
32
+ await ironflow.emit('order.approved', { orderId: '123' });
33
+
34
+ // Cleanup
35
+ sub.unsubscribe();
36
+ ```
37
+
38
+ ## Features
39
+
40
+ - **Real-time subscriptions** with WebSocket/ConnectRPC
41
+ - **Workflow triggers** from the browser
42
+ - **Event emission** for workflow coordination
43
+ - **Auto-reconnect** with exponential backoff
44
+ - **Type-safe** API with full TypeScript support
45
+
46
+ ## Key APIs
47
+
48
+ | Method | Description |
49
+ |--------|-------------|
50
+ | `ironflow.configure(options)` | Configure the client |
51
+ | `ironflow.connect()` | Connect to the server |
52
+ | `ironflow.subscribe(pattern, options)` | Subscribe to events |
53
+ | `ironflow.trigger(functionId, options)` | Trigger a workflow |
54
+ | `ironflow.emit(eventName, data)` | Emit an event |
55
+ | `ironflow.getRun(runId)` | Get run status |
56
+
57
+ ## Browser Compatibility
58
+
59
+ - Chrome 80+
60
+ - Firefox 75+
61
+ - Safari 13.1+
62
+ - Edge 80+
63
+
64
+ ## Documentation
65
+
66
+ For the full API reference, see the [Browser Package Documentation](https://ironflow.dev/docs/api-reference/js-sdk/browser).
67
+
68
+ ## License
69
+
70
+ MIT
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Ironflow Browser Client
3
+ *
4
+ * Singleton client for browser-based real-time interactions with Ironflow.
5
+ */
6
+ import type { Run, ListRunsOptions, ListRunsResult, TriggerResult, EmitOptions, EmitResult, SubscriptionErrorInfo, SubscriptionCallbacks, Subscription, AckableSubscription, ConnectionState } from "@ironflow/core";
7
+ import type { IronflowConfig, IronflowConfigOptions } from "./config.js";
8
+ import { type BrowserSubscribeOptions, type SubscriptionGroup } from "./subscription.js";
9
+ /**
10
+ * Ironflow browser client singleton
11
+ */
12
+ declare class IronflowClient {
13
+ private config;
14
+ private logger;
15
+ private transport;
16
+ private subscriptionManager;
17
+ private visibilityHandler;
18
+ /**
19
+ * Configure the client
20
+ *
21
+ * Must be called before any other operations.
22
+ */
23
+ configure(options?: IronflowConfigOptions): void;
24
+ /**
25
+ * Check if the client is configured
26
+ */
27
+ get isConfigured(): boolean;
28
+ /**
29
+ * Detect which transport the server supports
30
+ *
31
+ * Returns the best available transport based on server capabilities.
32
+ * ConnectRPC is preferred over WebSocket.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const transport = await ironflow.detectTransport();
37
+ * // Returns 'connectrpc' | 'websocket'
38
+ * ```
39
+ */
40
+ detectTransport(): Promise<"connectrpc" | "websocket">;
41
+ /**
42
+ * Get the current configuration
43
+ */
44
+ getConfig(): IronflowConfig;
45
+ /**
46
+ * Connect to the Ironflow server
47
+ */
48
+ connect(): Promise<void>;
49
+ /**
50
+ * Disconnect from the Ironflow server
51
+ */
52
+ disconnect(): void;
53
+ /**
54
+ * Register a callback for connection state changes
55
+ */
56
+ onConnectionChange(callback: (state: ConnectionState) => void): () => void;
57
+ /**
58
+ * Get current connection state
59
+ */
60
+ get connectionState(): ConnectionState;
61
+ /**
62
+ * Subscribe to events matching a pattern
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * // Basic subscription
67
+ * const sub = await ironflow.subscribe('events:order.*', {
68
+ * onEvent: (event) => console.log(event),
69
+ * });
70
+ *
71
+ * // Multiple patterns
72
+ * const sub = await ironflow.subscribe(['system.run.*', 'events:order.*'], {
73
+ * onEvent: (event) => { ... }
74
+ * });
75
+ *
76
+ * // With options
77
+ * const sub = await ironflow.subscribe('events:*', {
78
+ * onEvent: (e) => { ... },
79
+ * replay: 10,
80
+ * trackState: true,
81
+ * ackMode: 'manual',
82
+ * });
83
+ * ```
84
+ */
85
+ subscribe<T = unknown>(pattern: string | string[], callbacksAndOptions: SubscriptionCallbacks<T> & BrowserSubscribeOptions): Promise<Subscription | AckableSubscription>;
86
+ /**
87
+ * Create a subscription group for batch management
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const group = ironflow.subscriptionGroup();
92
+ * await group.add('system.run.*', { onEvent: handleRun });
93
+ * await group.add('events:payment.*', { onEvent: handlePayment });
94
+ * // Later:
95
+ * group.unsubscribeAll();
96
+ * ```
97
+ */
98
+ subscriptionGroup(): SubscriptionGroup;
99
+ /**
100
+ * Register a global error handler
101
+ */
102
+ onError(callback: (error: SubscriptionErrorInfo) => void): () => void;
103
+ /**
104
+ * Trigger a workflow
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const run = await ironflow.trigger<OrderInput, OrderOutput>('process-order', {
109
+ * data: { orderId: '123' }
110
+ * });
111
+ * ```
112
+ */
113
+ trigger<TInput = unknown>(functionId: string, options: {
114
+ data: TInput;
115
+ }): Promise<TriggerResult>;
116
+ /**
117
+ * Get run status
118
+ */
119
+ getRun(runId: string): Promise<Run>;
120
+ /**
121
+ * List runs with filtering
122
+ */
123
+ listRuns(options?: ListRunsOptions): Promise<ListRunsResult>;
124
+ /**
125
+ * Cancel a running run
126
+ */
127
+ cancelRun(runId: string, reason?: string): Promise<Run>;
128
+ /**
129
+ * Emit an event
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * await ironflow.emit('order.approved', {
134
+ * orderId: '123',
135
+ * approvedBy: 'user@example.com'
136
+ * });
137
+ * ```
138
+ */
139
+ emit(eventName: string, data: unknown, options?: EmitOptions): Promise<EmitResult>;
140
+ /**
141
+ * Join a consumer group for load-balanced event processing
142
+ */
143
+ joinConsumerGroup<T = unknown>(groupName: string, pattern: string, callbacksAndOptions: SubscriptionCallbacks<T> & BrowserSubscribeOptions): Promise<AckableSubscription>;
144
+ /**
145
+ * Pattern helpers for building subscription patterns
146
+ */
147
+ static patterns: {
148
+ readonly allRuns: () => string;
149
+ readonly run: (runId: string) => string;
150
+ readonly runLifecycle: (runId: string) => string;
151
+ readonly runSteps: (runId: string) => string;
152
+ readonly allFunctions: () => string;
153
+ readonly function: (functionId: string) => string;
154
+ readonly userEvent: (eventName: string) => string;
155
+ readonly allUserEvents: () => string;
156
+ };
157
+ private ensureConfigured;
158
+ private cleanup;
159
+ private setupVisibilityHandling;
160
+ private request;
161
+ private mapRunResponse;
162
+ }
163
+ /**
164
+ * Singleton instance
165
+ */
166
+ export declare const ironflow: IronflowClient;
167
+ /**
168
+ * Export the class for advanced usage
169
+ */
170
+ export { IronflowClient };
171
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js ADDED
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Ironflow Browser Client
3
+ *
4
+ * Singleton client for browser-based real-time interactions with Ironflow.
5
+ */
6
+ import { NotConfiguredError, IronflowError, ValidationError, createLogger, createNoopLogger, DEFAULT_TIMEOUTS, TriggerResponseSchema, RunResponseSchema, ListRunsResponseSchema, RunStatusSchema, ErrorResponseSchema, safeJsonParse, patterns, } 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
+ /**
12
+ * Ironflow browser client singleton
13
+ */
14
+ class IronflowClient {
15
+ config = null;
16
+ logger = createNoopLogger();
17
+ transport = null;
18
+ subscriptionManager = null;
19
+ visibilityHandler = null;
20
+ /**
21
+ * Configure the client
22
+ *
23
+ * Must be called before any other operations.
24
+ */
25
+ configure(options = {}) {
26
+ this.config = mergeConfig(options);
27
+ // Set up logger
28
+ if (this.config.logger === false) {
29
+ this.logger = createNoopLogger();
30
+ }
31
+ else if (this.config.logger) {
32
+ this.logger = this.config.logger;
33
+ }
34
+ else {
35
+ this.logger = createLogger({ prefix: "[ironflow]" });
36
+ }
37
+ // Clean up existing resources
38
+ this.cleanup();
39
+ // Create transport
40
+ const transportOptions = {
41
+ auth: this.config.auth,
42
+ autoReconnect: this.config.reconnect.enabled,
43
+ reconnectDelay: this.config.reconnect.backoff.initial,
44
+ maxReconnectDelay: this.config.reconnect.backoff.max,
45
+ reconnectBackoff: this.config.reconnect.backoff.multiplier,
46
+ };
47
+ // Create transport based on config (ConnectRPC by default)
48
+ if (this.config.transport === "websocket") {
49
+ this.transport = createWebSocketTransport(this.config.serverUrl, transportOptions);
50
+ }
51
+ else {
52
+ // Default to ConnectRPC
53
+ this.transport = createConnectRPCTransport(this.config.serverUrl, transportOptions);
54
+ }
55
+ // Create subscription manager
56
+ this.subscriptionManager = new SubscriptionManager(this.transport, this.config.logger);
57
+ // Set up visibility handling
58
+ if (this.config.visibility.pauseOnHidden && typeof document !== "undefined") {
59
+ this.setupVisibilityHandling();
60
+ }
61
+ this.logger.info("Ironflow client configured", {
62
+ serverUrl: this.config.serverUrl,
63
+ transport: this.config.transport,
64
+ });
65
+ }
66
+ /**
67
+ * Check if the client is configured
68
+ */
69
+ get isConfigured() {
70
+ return this.config !== null;
71
+ }
72
+ /**
73
+ * Detect which transport the server supports
74
+ *
75
+ * Returns the best available transport based on server capabilities.
76
+ * ConnectRPC is preferred over WebSocket.
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * const transport = await ironflow.detectTransport();
81
+ * // Returns 'connectrpc' | 'websocket'
82
+ * ```
83
+ */
84
+ async detectTransport() {
85
+ const serverUrl = this.config?.serverUrl ?? "http://localhost:9123";
86
+ try {
87
+ // Try to get server capabilities via ConnectRPC endpoint
88
+ const response = await fetch(`${serverUrl}/ironflow.v1.IronflowService/GetCapabilities`, {
89
+ method: "POST",
90
+ headers: {
91
+ "Content-Type": "application/json",
92
+ },
93
+ body: "{}",
94
+ });
95
+ if (response.ok) {
96
+ const data = await response.json();
97
+ const transports = data.transports;
98
+ // Check if server explicitly supports connectrpc
99
+ if (transports?.includes("connectrpc") || transports?.includes("grpc")) {
100
+ return "connectrpc";
101
+ }
102
+ // Server responded, so ConnectRPC endpoint works
103
+ return "connectrpc";
104
+ }
105
+ }
106
+ catch {
107
+ // ConnectRPC not available
108
+ }
109
+ // Fall back to WebSocket
110
+ return "websocket";
111
+ }
112
+ /**
113
+ * Get the current configuration
114
+ */
115
+ getConfig() {
116
+ if (!this.config) {
117
+ throw new NotConfiguredError();
118
+ }
119
+ return this.config;
120
+ }
121
+ // ============================================================================
122
+ // Connection Management
123
+ // ============================================================================
124
+ /**
125
+ * Connect to the Ironflow server
126
+ */
127
+ async connect() {
128
+ this.ensureConfigured();
129
+ await this.subscriptionManager.connect();
130
+ }
131
+ /**
132
+ * Disconnect from the Ironflow server
133
+ */
134
+ disconnect() {
135
+ if (this.subscriptionManager) {
136
+ this.subscriptionManager.disconnect();
137
+ }
138
+ }
139
+ /**
140
+ * Register a callback for connection state changes
141
+ */
142
+ onConnectionChange(callback) {
143
+ this.ensureConfigured();
144
+ return this.subscriptionManager.onConnectionChange(callback);
145
+ }
146
+ /**
147
+ * Get current connection state
148
+ */
149
+ get connectionState() {
150
+ if (!this.subscriptionManager) {
151
+ return "disconnected";
152
+ }
153
+ return this.subscriptionManager.connectionState;
154
+ }
155
+ // ============================================================================
156
+ // Subscriptions
157
+ // ============================================================================
158
+ /**
159
+ * Subscribe to events matching a pattern
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * // Basic subscription
164
+ * const sub = await ironflow.subscribe('events:order.*', {
165
+ * onEvent: (event) => console.log(event),
166
+ * });
167
+ *
168
+ * // Multiple patterns
169
+ * const sub = await ironflow.subscribe(['system.run.*', 'events:order.*'], {
170
+ * onEvent: (event) => { ... }
171
+ * });
172
+ *
173
+ * // With options
174
+ * const sub = await ironflow.subscribe('events:*', {
175
+ * onEvent: (e) => { ... },
176
+ * replay: 10,
177
+ * trackState: true,
178
+ * ackMode: 'manual',
179
+ * });
180
+ * ```
181
+ */
182
+ subscribe(pattern, callbacksAndOptions) {
183
+ this.ensureConfigured();
184
+ return this.subscriptionManager.subscribe(pattern, callbacksAndOptions);
185
+ }
186
+ /**
187
+ * Create a subscription group for batch management
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * const group = ironflow.subscriptionGroup();
192
+ * await group.add('system.run.*', { onEvent: handleRun });
193
+ * await group.add('events:payment.*', { onEvent: handlePayment });
194
+ * // Later:
195
+ * group.unsubscribeAll();
196
+ * ```
197
+ */
198
+ subscriptionGroup() {
199
+ this.ensureConfigured();
200
+ return this.subscriptionManager.createGroup();
201
+ }
202
+ /**
203
+ * Register a global error handler
204
+ */
205
+ onError(callback) {
206
+ this.ensureConfigured();
207
+ return this.subscriptionManager.onError(callback);
208
+ }
209
+ // ============================================================================
210
+ // Workflow Operations
211
+ // ============================================================================
212
+ /**
213
+ * Trigger a workflow
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * const run = await ironflow.trigger<OrderInput, OrderOutput>('process-order', {
218
+ * data: { orderId: '123' }
219
+ * });
220
+ * ```
221
+ */
222
+ async trigger(functionId, options) {
223
+ this.ensureConfigured();
224
+ const response = await this.request(TriggerResponseSchema, "POST", "/ironflow.v1.IronflowService/Trigger", {
225
+ event: functionId,
226
+ data: options.data,
227
+ });
228
+ return {
229
+ runIds: response.runIds ?? [],
230
+ eventId: response.eventId,
231
+ };
232
+ }
233
+ /**
234
+ * Get run status
235
+ */
236
+ async getRun(runId) {
237
+ this.ensureConfigured();
238
+ const response = await this.request(RunResponseSchema, "POST", "/ironflow.v1.IronflowService/GetRun", { id: runId });
239
+ return this.mapRunResponse(response);
240
+ }
241
+ /**
242
+ * List runs with filtering
243
+ */
244
+ async listRuns(options) {
245
+ this.ensureConfigured();
246
+ const response = await this.request(ListRunsResponseSchema, "POST", "/ironflow.v1.IronflowService/ListRuns", {
247
+ function_id: options?.functionId,
248
+ status: options?.status?.toUpperCase(),
249
+ limit: options?.limit,
250
+ cursor: options?.cursor,
251
+ });
252
+ return {
253
+ runs: (response.runs ?? []).map((r) => this.mapRunResponse(r)),
254
+ nextCursor: response.nextCursor,
255
+ totalCount: response.totalCount ?? 0,
256
+ };
257
+ }
258
+ /**
259
+ * Cancel a running run
260
+ */
261
+ async cancelRun(runId, reason) {
262
+ this.ensureConfigured();
263
+ const response = await this.request(RunResponseSchema, "POST", "/ironflow.v1.IronflowService/CancelRun", { id: runId, reason });
264
+ return this.mapRunResponse(response);
265
+ }
266
+ // ============================================================================
267
+ // Event Emission
268
+ // ============================================================================
269
+ /**
270
+ * Emit an event
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * await ironflow.emit('order.approved', {
275
+ * orderId: '123',
276
+ * approvedBy: 'user@example.com'
277
+ * });
278
+ * ```
279
+ */
280
+ async emit(eventName, data, options) {
281
+ this.ensureConfigured();
282
+ const response = await this.request(TriggerResponseSchema, "POST", "/ironflow.v1.PubSubService/Emit", {
283
+ event: eventName,
284
+ data,
285
+ idempotency_key: options?.idempotencyKey,
286
+ metadata: options?.metadata,
287
+ namespace: options?.namespace,
288
+ });
289
+ return {
290
+ runIds: response.runIds ?? [],
291
+ eventId: response.eventId,
292
+ };
293
+ }
294
+ // ============================================================================
295
+ // Consumer Groups
296
+ // ============================================================================
297
+ /**
298
+ * Join a consumer group for load-balanced event processing
299
+ */
300
+ async joinConsumerGroup(groupName, pattern, callbacksAndOptions) {
301
+ this.ensureConfigured();
302
+ const sub = await this.subscribe(pattern, {
303
+ ...callbacksAndOptions,
304
+ consumerGroup: groupName,
305
+ ackMode: "manual",
306
+ });
307
+ return sub;
308
+ }
309
+ // ============================================================================
310
+ // Pattern Helpers (static)
311
+ // ============================================================================
312
+ /**
313
+ * Pattern helpers for building subscription patterns
314
+ */
315
+ static patterns = patterns;
316
+ // ============================================================================
317
+ // Internal Methods
318
+ // ============================================================================
319
+ ensureConfigured() {
320
+ if (!this.config) {
321
+ throw new NotConfiguredError();
322
+ }
323
+ }
324
+ cleanup() {
325
+ if (this.visibilityHandler) {
326
+ document.removeEventListener("visibilitychange", this.visibilityHandler);
327
+ this.visibilityHandler = null;
328
+ }
329
+ if (this.subscriptionManager) {
330
+ this.subscriptionManager.disconnect();
331
+ this.subscriptionManager = null;
332
+ }
333
+ this.transport = null;
334
+ }
335
+ setupVisibilityHandling() {
336
+ this.visibilityHandler = () => {
337
+ if (document.hidden) {
338
+ this.logger.debug("Tab hidden, pausing subscriptions");
339
+ this.subscriptionManager?.pause();
340
+ }
341
+ else {
342
+ if (this.config?.visibility.reconnectOnVisible) {
343
+ this.logger.debug("Tab visible, resuming subscriptions");
344
+ this.subscriptionManager?.resume();
345
+ }
346
+ }
347
+ };
348
+ document.addEventListener("visibilitychange", this.visibilityHandler);
349
+ }
350
+ async request(schema, method, path, body) {
351
+ const url = `${this.config.serverUrl}${path}`;
352
+ const timeout = this.config.timeout ?? DEFAULT_TIMEOUTS.CLIENT;
353
+ const controller = new AbortController();
354
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
355
+ try {
356
+ const headers = {
357
+ "Content-Type": "application/json",
358
+ };
359
+ if (this.config.auth?.apiKey) {
360
+ headers["Authorization"] = `Bearer ${this.config.auth.apiKey}`;
361
+ }
362
+ else if (this.config.auth?.token) {
363
+ headers["Authorization"] = `Bearer ${this.config.auth.token}`;
364
+ }
365
+ const response = await fetch(url, {
366
+ method,
367
+ headers,
368
+ body: JSON.stringify(body),
369
+ signal: controller.signal,
370
+ });
371
+ const responseBody = await response.text();
372
+ if (!response.ok) {
373
+ const errorResult = ErrorResponseSchema.safeParse(safeJsonParse(responseBody));
374
+ const errorData = errorResult.success
375
+ ? errorResult.data
376
+ : { message: responseBody };
377
+ throw new IronflowError(errorData.message ?? `Request failed: ${response.status}`, {
378
+ code: errorData.code ?? `HTTP_${response.status}`,
379
+ retryable: response.status >= 500,
380
+ });
381
+ }
382
+ const parsed = safeJsonParse(responseBody);
383
+ if (parsed === undefined) {
384
+ throw new ValidationError("Invalid JSON response from server");
385
+ }
386
+ const result = schema.safeParse(parsed);
387
+ if (!result.success) {
388
+ const issues = result.error.issues
389
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
390
+ .join(", ");
391
+ throw new ValidationError(`Invalid response from server: ${issues}`);
392
+ }
393
+ return result.data;
394
+ }
395
+ catch (error) {
396
+ if (error instanceof IronflowError || error instanceof ValidationError) {
397
+ throw error;
398
+ }
399
+ if (error instanceof Error && error.name === "AbortError") {
400
+ throw new IronflowError(`Request timeout after ${timeout}ms`, {
401
+ code: "TIMEOUT",
402
+ retryable: true,
403
+ });
404
+ }
405
+ throw new IronflowError(error instanceof Error ? error.message : "Request failed", {
406
+ code: "REQUEST_FAILED",
407
+ retryable: true,
408
+ cause: error instanceof Error ? error : undefined,
409
+ });
410
+ }
411
+ finally {
412
+ clearTimeout(timeoutId);
413
+ }
414
+ }
415
+ mapRunResponse(response) {
416
+ const statusResult = RunStatusSchema.safeParse(response.status.toLowerCase());
417
+ const status = statusResult.success ? statusResult.data : "failed";
418
+ return {
419
+ id: response.id,
420
+ functionId: response.functionId,
421
+ eventId: response.eventId,
422
+ status,
423
+ attempt: response.attempt,
424
+ maxAttempts: response.maxAttempts,
425
+ input: response.input,
426
+ output: response.output,
427
+ error: response.error,
428
+ startedAt: response.startedAt ? new Date(response.startedAt) : undefined,
429
+ endedAt: response.endedAt ? new Date(response.endedAt) : undefined,
430
+ createdAt: new Date(response.createdAt),
431
+ updatedAt: new Date(response.updatedAt),
432
+ };
433
+ }
434
+ }
435
+ /**
436
+ * Singleton instance
437
+ */
438
+ export const ironflow = new IronflowClient();
439
+ /**
440
+ * Export the class for advanced usage
441
+ */
442
+ export { IronflowClient };
443
+ //# sourceMappingURL=client.js.map