@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 +70 -0
- package/dist/client.d.ts +171 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +443 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +87 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +53 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/subscription.d.ts +85 -0
- package/dist/subscription.d.ts.map +1 -0
- package/dist/subscription.js +263 -0
- package/dist/subscription.js.map +1 -0
- package/dist/transport/connectrpc.d.ts +60 -0
- package/dist/transport/connectrpc.d.ts.map +1 -0
- package/dist/transport/connectrpc.js +298 -0
- package/dist/transport/connectrpc.js.map +1 -0
- package/dist/transport/index.d.ts +7 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +6 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/types.d.ts +65 -0
- package/dist/transport/types.d.ts.map +1 -0
- package/dist/transport/types.js +5 -0
- package/dist/transport/types.js.map +1 -0
- package/dist/transport/websocket.d.ts +38 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +240 -0
- package/dist/transport/websocket.js.map +1 -0
- package/package.json +70 -0
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
|
package/dist/client.d.ts
ADDED
|
@@ -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
|