@stepflowjs/client-ts 0.0.1
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/dist/index.d.ts +366 -0
- package/dist/index.js +746 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
type RealtimeStrategy = "sse" | "websocket" | "auto";
|
|
2
|
+
interface RealtimeEvent {
|
|
3
|
+
type: string;
|
|
4
|
+
data: unknown;
|
|
5
|
+
}
|
|
6
|
+
interface RealtimeConnectionConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Connection strategy
|
|
9
|
+
* @default 'auto'
|
|
10
|
+
*/
|
|
11
|
+
strategy?: RealtimeStrategy;
|
|
12
|
+
/**
|
|
13
|
+
* SSE endpoint URL
|
|
14
|
+
*/
|
|
15
|
+
sseUrl: string;
|
|
16
|
+
/**
|
|
17
|
+
* WebSocket endpoint URL (optional, derived from sseUrl if not provided)
|
|
18
|
+
*/
|
|
19
|
+
wsUrl?: string;
|
|
20
|
+
/**
|
|
21
|
+
* HTTP headers for SSE connection
|
|
22
|
+
*/
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Number of reconnection attempts
|
|
26
|
+
* @default 3
|
|
27
|
+
*/
|
|
28
|
+
reconnectAttempts?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Delay between reconnection attempts in milliseconds
|
|
31
|
+
* @default 1000
|
|
32
|
+
*/
|
|
33
|
+
reconnectDelayMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Called when connection is established
|
|
36
|
+
*/
|
|
37
|
+
onConnect?: () => void;
|
|
38
|
+
/**
|
|
39
|
+
* Called when connection is closed
|
|
40
|
+
*/
|
|
41
|
+
onDisconnect?: () => void;
|
|
42
|
+
/**
|
|
43
|
+
* Called when an error occurs
|
|
44
|
+
*/
|
|
45
|
+
onError?: (error: Error) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Called when a message is received
|
|
48
|
+
*/
|
|
49
|
+
onMessage?: (event: RealtimeEvent) => void;
|
|
50
|
+
}
|
|
51
|
+
declare class RealtimeConnection {
|
|
52
|
+
private config;
|
|
53
|
+
private eventSource;
|
|
54
|
+
private webSocket;
|
|
55
|
+
private activeStrategy;
|
|
56
|
+
private reconnectCount;
|
|
57
|
+
private reconnectTimeout;
|
|
58
|
+
private connectionTimeout;
|
|
59
|
+
private isManualDisconnect;
|
|
60
|
+
constructor(config: RealtimeConnectionConfig);
|
|
61
|
+
/**
|
|
62
|
+
* Connect using the configured strategy
|
|
63
|
+
*/
|
|
64
|
+
connect(): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Disconnect from the active connection
|
|
67
|
+
*/
|
|
68
|
+
disconnect(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Check if currently connected
|
|
71
|
+
*/
|
|
72
|
+
isConnected(): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Get the currently active strategy
|
|
75
|
+
*/
|
|
76
|
+
getActiveStrategy(): "sse" | "websocket" | null;
|
|
77
|
+
private connectAuto;
|
|
78
|
+
private connectSSE;
|
|
79
|
+
private setupSSEEventListeners;
|
|
80
|
+
private connectWebSocket;
|
|
81
|
+
private handleMessage;
|
|
82
|
+
private handleDisconnect;
|
|
83
|
+
private attemptReconnect;
|
|
84
|
+
private closeConnections;
|
|
85
|
+
private closeSSE;
|
|
86
|
+
private closeWebSocket;
|
|
87
|
+
private clearTimeouts;
|
|
88
|
+
private deriveWebSocketUrl;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Storage interface for tokens
|
|
93
|
+
*/
|
|
94
|
+
interface TokenStorage {
|
|
95
|
+
get(key: string): Promise<string | null>;
|
|
96
|
+
set(key: string, value: string): Promise<void>;
|
|
97
|
+
remove(key: string): Promise<void>;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Token manager configuration
|
|
101
|
+
*/
|
|
102
|
+
interface TokenManagerConfig {
|
|
103
|
+
/**
|
|
104
|
+
* Storage implementation
|
|
105
|
+
* @default MemoryTokenStorage
|
|
106
|
+
*/
|
|
107
|
+
storage?: TokenStorage;
|
|
108
|
+
/**
|
|
109
|
+
* Prefix for storage keys
|
|
110
|
+
* @default 'stepflow_token_'
|
|
111
|
+
*/
|
|
112
|
+
storageKeyPrefix?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Refresh threshold in milliseconds before expiry
|
|
115
|
+
* @default 300000 (5 minutes)
|
|
116
|
+
*/
|
|
117
|
+
refreshThresholdMs?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Callback when token is refreshed
|
|
120
|
+
*/
|
|
121
|
+
onTokenRefresh?: (runId: string, newToken: string) => void;
|
|
122
|
+
/**
|
|
123
|
+
* Callback when token expires
|
|
124
|
+
*/
|
|
125
|
+
onTokenExpired?: (runId: string) => void;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* In-memory token storage (default for SSR/testing)
|
|
129
|
+
*/
|
|
130
|
+
declare class MemoryTokenStorage implements TokenStorage {
|
|
131
|
+
private store;
|
|
132
|
+
get(key: string): Promise<string | null>;
|
|
133
|
+
set(key: string, value: string): Promise<void>;
|
|
134
|
+
remove(key: string): Promise<void>;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Browser localStorage wrapper
|
|
138
|
+
*/
|
|
139
|
+
declare class LocalStorageTokenStorage implements TokenStorage {
|
|
140
|
+
get(key: string): Promise<string | null>;
|
|
141
|
+
set(key: string, value: string): Promise<void>;
|
|
142
|
+
remove(key: string): Promise<void>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Manages API tokens with storage and auto-refresh
|
|
146
|
+
*/
|
|
147
|
+
declare class TokenManager {
|
|
148
|
+
private storage;
|
|
149
|
+
private storageKeyPrefix;
|
|
150
|
+
private refreshThresholdMs;
|
|
151
|
+
private onTokenRefresh?;
|
|
152
|
+
private onTokenExpired?;
|
|
153
|
+
private refreshTimers;
|
|
154
|
+
constructor(config?: TokenManagerConfig);
|
|
155
|
+
/**
|
|
156
|
+
* Store a token with expiry
|
|
157
|
+
*/
|
|
158
|
+
storeToken(runId: string, token: string, expiresAt: Date): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Get a token if it's still valid
|
|
161
|
+
* @returns Token string or null if expired/not found
|
|
162
|
+
*/
|
|
163
|
+
getToken(runId: string): Promise<string | null>;
|
|
164
|
+
/**
|
|
165
|
+
* Remove a token from storage
|
|
166
|
+
*/
|
|
167
|
+
removeToken(runId: string): Promise<void>;
|
|
168
|
+
/**
|
|
169
|
+
* Check if a token is valid (exists and not expired)
|
|
170
|
+
*/
|
|
171
|
+
isTokenValid(runId: string): Promise<boolean>;
|
|
172
|
+
/**
|
|
173
|
+
* Get token expiry date
|
|
174
|
+
* @returns Expiry date or null if not found
|
|
175
|
+
*/
|
|
176
|
+
getTokenExpiry(runId: string): Promise<Date | null>;
|
|
177
|
+
/**
|
|
178
|
+
* Start auto-refresh for a token
|
|
179
|
+
*/
|
|
180
|
+
startAutoRefresh(runId: string, refreshFn: () => Promise<{
|
|
181
|
+
token: string;
|
|
182
|
+
expiresAt: Date;
|
|
183
|
+
}>): void;
|
|
184
|
+
/**
|
|
185
|
+
* Stop auto-refresh for a specific token
|
|
186
|
+
*/
|
|
187
|
+
stopAutoRefresh(runId: string): void;
|
|
188
|
+
/**
|
|
189
|
+
* Stop all auto-refresh timers
|
|
190
|
+
*/
|
|
191
|
+
stopAllAutoRefresh(): void;
|
|
192
|
+
/**
|
|
193
|
+
* Schedule a refresh based on token expiry
|
|
194
|
+
*/
|
|
195
|
+
private scheduleRefresh;
|
|
196
|
+
/**
|
|
197
|
+
* Get storage key for a run ID
|
|
198
|
+
*/
|
|
199
|
+
private getStorageKey;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
interface StepflowClientConfig {
|
|
203
|
+
/**
|
|
204
|
+
* Base URL of the Stepflow API
|
|
205
|
+
*/
|
|
206
|
+
baseUrl: string;
|
|
207
|
+
/**
|
|
208
|
+
* API key for server-side requests
|
|
209
|
+
*/
|
|
210
|
+
apiKey?: string;
|
|
211
|
+
/**
|
|
212
|
+
* Public API key for client-side requests
|
|
213
|
+
*/
|
|
214
|
+
publicApiKey?: string;
|
|
215
|
+
/**
|
|
216
|
+
* WebSocket URL for real-time updates
|
|
217
|
+
*/
|
|
218
|
+
wsUrl?: string;
|
|
219
|
+
}
|
|
220
|
+
type ExecutionStatus = "pending" | "running" | "completed" | "failed" | "waiting" | "sleeping" | "cancelled";
|
|
221
|
+
interface Execution<TPayload = unknown, TResult = unknown> {
|
|
222
|
+
id: string;
|
|
223
|
+
runId: string;
|
|
224
|
+
workflowId: string;
|
|
225
|
+
eventName: string;
|
|
226
|
+
payload: TPayload;
|
|
227
|
+
status: ExecutionStatus;
|
|
228
|
+
result?: TResult;
|
|
229
|
+
error?: ExecutionError;
|
|
230
|
+
steps: StepExecution[];
|
|
231
|
+
metadata: Record<string, unknown>;
|
|
232
|
+
attempt: number;
|
|
233
|
+
startedAt: Date;
|
|
234
|
+
completedAt?: Date;
|
|
235
|
+
}
|
|
236
|
+
interface ExecutionError {
|
|
237
|
+
name: string;
|
|
238
|
+
message: string;
|
|
239
|
+
stack?: string;
|
|
240
|
+
code?: string;
|
|
241
|
+
}
|
|
242
|
+
interface StepExecution {
|
|
243
|
+
name: string;
|
|
244
|
+
status: "pending" | "running" | "completed" | "failed" | "cached";
|
|
245
|
+
result?: unknown;
|
|
246
|
+
error?: ExecutionError;
|
|
247
|
+
startedAt?: Date;
|
|
248
|
+
completedAt?: Date;
|
|
249
|
+
durationMs?: number;
|
|
250
|
+
}
|
|
251
|
+
interface TriggerResult {
|
|
252
|
+
runId: string;
|
|
253
|
+
executionId: string;
|
|
254
|
+
publicAccessToken: string;
|
|
255
|
+
}
|
|
256
|
+
interface NotifyResult {
|
|
257
|
+
waiters: number;
|
|
258
|
+
executions: string[];
|
|
259
|
+
}
|
|
260
|
+
interface TriggerOptions {
|
|
261
|
+
runId?: string;
|
|
262
|
+
metadata?: Record<string, unknown>;
|
|
263
|
+
delay?: number;
|
|
264
|
+
}
|
|
265
|
+
interface SubscribeOptions<TPayload = unknown, TResult = unknown> {
|
|
266
|
+
accessToken?: string;
|
|
267
|
+
throttleMs?: number;
|
|
268
|
+
strategy?: RealtimeStrategy;
|
|
269
|
+
onUpdate?: (run: Execution<TPayload, TResult>) => void;
|
|
270
|
+
onStepComplete?: (step: StepExecution) => void;
|
|
271
|
+
onComplete?: (result: TResult) => void;
|
|
272
|
+
onError?: (error: Error) => void;
|
|
273
|
+
onConnect?: () => void;
|
|
274
|
+
onDisconnect?: () => void;
|
|
275
|
+
}
|
|
276
|
+
interface StreamOptions {
|
|
277
|
+
signal?: AbortSignal;
|
|
278
|
+
}
|
|
279
|
+
interface StreamEvent {
|
|
280
|
+
type: string;
|
|
281
|
+
data: unknown;
|
|
282
|
+
stepName?: string;
|
|
283
|
+
}
|
|
284
|
+
interface ListRunsOptions {
|
|
285
|
+
workflowId?: string;
|
|
286
|
+
status?: ExecutionStatus;
|
|
287
|
+
limit?: number;
|
|
288
|
+
offset?: number;
|
|
289
|
+
}
|
|
290
|
+
interface PaginatedResult<T> {
|
|
291
|
+
data: T[];
|
|
292
|
+
total: number;
|
|
293
|
+
limit: number;
|
|
294
|
+
offset: number;
|
|
295
|
+
}
|
|
296
|
+
type Unsubscribe = () => void;
|
|
297
|
+
declare class StepflowError extends Error {
|
|
298
|
+
readonly code?: string | undefined;
|
|
299
|
+
readonly status?: number | undefined;
|
|
300
|
+
constructor(message: string, code?: string | undefined, status?: number | undefined);
|
|
301
|
+
}
|
|
302
|
+
declare class StepflowClient {
|
|
303
|
+
private config;
|
|
304
|
+
constructor(config: StepflowClientConfig);
|
|
305
|
+
/**
|
|
306
|
+
* Trigger a workflow execution
|
|
307
|
+
*/
|
|
308
|
+
trigger<TPayload = unknown>(workflowId: string, payload: TPayload, options?: TriggerOptions): Promise<TriggerResult>;
|
|
309
|
+
/**
|
|
310
|
+
* Subscribe to a run's real-time updates via SSE or WebSocket
|
|
311
|
+
*/
|
|
312
|
+
subscribeToRun<TPayload = unknown, TResult = unknown>(runId: string, options: SubscribeOptions<TPayload, TResult>): Unsubscribe;
|
|
313
|
+
/**
|
|
314
|
+
* Subscribe using RealtimeConnection with SSE/WebSocket fallback
|
|
315
|
+
*/
|
|
316
|
+
private subscribeWithRealtimeConnection;
|
|
317
|
+
/**
|
|
318
|
+
* Derive WebSocket URL from HTTP URL
|
|
319
|
+
*/
|
|
320
|
+
private deriveWebSocketUrl;
|
|
321
|
+
/**
|
|
322
|
+
* Stream a workflow execution (trigger + stream events)
|
|
323
|
+
*/
|
|
324
|
+
stream<TPayload = unknown, TResult = unknown>(workflowId: string, payload: TPayload, options?: StreamOptions): AsyncGenerator<StreamEvent, void, unknown>;
|
|
325
|
+
/**
|
|
326
|
+
* Get a run by ID
|
|
327
|
+
*/
|
|
328
|
+
getRun<TPayload = unknown, TResult = unknown>(runId: string, options?: {
|
|
329
|
+
accessToken?: string;
|
|
330
|
+
}): Promise<Execution<TPayload, TResult> | null>;
|
|
331
|
+
/**
|
|
332
|
+
* Get execution by ID
|
|
333
|
+
*/
|
|
334
|
+
getExecution<TPayload = unknown, TResult = unknown>(executionId: string): Promise<Execution<TPayload, TResult> | null>;
|
|
335
|
+
/**
|
|
336
|
+
* List runs with optional filters
|
|
337
|
+
*/
|
|
338
|
+
listRuns(options?: ListRunsOptions): Promise<PaginatedResult<Execution>>;
|
|
339
|
+
/**
|
|
340
|
+
* Notify an event (for waitForEvent)
|
|
341
|
+
*/
|
|
342
|
+
notify(eventId: string, data: unknown): Promise<NotifyResult>;
|
|
343
|
+
/**
|
|
344
|
+
* Create a public access token for a run
|
|
345
|
+
*/
|
|
346
|
+
createAccessToken(runId: string): Promise<{
|
|
347
|
+
token: string;
|
|
348
|
+
expiresAt: Date;
|
|
349
|
+
}>;
|
|
350
|
+
/**
|
|
351
|
+
* Health check
|
|
352
|
+
*/
|
|
353
|
+
health(): Promise<{
|
|
354
|
+
ok: boolean;
|
|
355
|
+
}>;
|
|
356
|
+
/**
|
|
357
|
+
* Internal fetch helper
|
|
358
|
+
*/
|
|
359
|
+
private fetch;
|
|
360
|
+
/**
|
|
361
|
+
* Get auth headers
|
|
362
|
+
*/
|
|
363
|
+
private getAuthHeaders;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export { type Execution, type ExecutionError, type ExecutionStatus, type ListRunsOptions, LocalStorageTokenStorage, MemoryTokenStorage, type NotifyResult, type PaginatedResult, RealtimeConnection, type RealtimeConnectionConfig, type RealtimeEvent, type RealtimeStrategy, type StepExecution, StepflowClient, type StepflowClientConfig, StepflowError, type StreamEvent, type StreamOptions, type SubscribeOptions, TokenManager, type TokenManagerConfig, type TokenStorage, type TriggerOptions, type TriggerResult, type Unsubscribe, StepflowClient as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { EventSourcePolyfill as EventSourcePolyfill2 } from "event-source-polyfill";
|
|
3
|
+
|
|
4
|
+
// src/realtime.ts
|
|
5
|
+
import { EventSourcePolyfill } from "event-source-polyfill";
|
|
6
|
+
var RealtimeConnection = class {
|
|
7
|
+
config;
|
|
8
|
+
eventSource = null;
|
|
9
|
+
webSocket = null;
|
|
10
|
+
activeStrategy = null;
|
|
11
|
+
reconnectCount = 0;
|
|
12
|
+
reconnectTimeout = null;
|
|
13
|
+
connectionTimeout = null;
|
|
14
|
+
isManualDisconnect = false;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = {
|
|
17
|
+
strategy: config.strategy ?? "auto",
|
|
18
|
+
sseUrl: config.sseUrl,
|
|
19
|
+
wsUrl: config.wsUrl,
|
|
20
|
+
headers: config.headers,
|
|
21
|
+
reconnectAttempts: config.reconnectAttempts ?? 3,
|
|
22
|
+
reconnectDelayMs: config.reconnectDelayMs ?? 1e3,
|
|
23
|
+
onConnect: config.onConnect,
|
|
24
|
+
onDisconnect: config.onDisconnect,
|
|
25
|
+
onError: config.onError,
|
|
26
|
+
onMessage: config.onMessage
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Connect using the configured strategy
|
|
31
|
+
*/
|
|
32
|
+
async connect() {
|
|
33
|
+
this.isManualDisconnect = false;
|
|
34
|
+
this.reconnectCount = 0;
|
|
35
|
+
const strategy = this.config.strategy;
|
|
36
|
+
if (strategy === "sse") {
|
|
37
|
+
await this.connectSSE();
|
|
38
|
+
} else if (strategy === "websocket") {
|
|
39
|
+
await this.connectWebSocket();
|
|
40
|
+
} else {
|
|
41
|
+
await this.connectAuto();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Disconnect from the active connection
|
|
46
|
+
*/
|
|
47
|
+
disconnect() {
|
|
48
|
+
this.isManualDisconnect = true;
|
|
49
|
+
this.clearTimeouts();
|
|
50
|
+
this.closeConnections();
|
|
51
|
+
this.activeStrategy = null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if currently connected
|
|
55
|
+
*/
|
|
56
|
+
isConnected() {
|
|
57
|
+
if (this.activeStrategy === "sse") {
|
|
58
|
+
return this.eventSource !== null && this.eventSource.readyState === EventSourcePolyfill.OPEN;
|
|
59
|
+
} else if (this.activeStrategy === "websocket") {
|
|
60
|
+
return this.webSocket !== null && this.webSocket.readyState === WebSocket.OPEN;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the currently active strategy
|
|
66
|
+
*/
|
|
67
|
+
getActiveStrategy() {
|
|
68
|
+
return this.activeStrategy;
|
|
69
|
+
}
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// Private Methods - Connection Strategies
|
|
72
|
+
// ============================================================================
|
|
73
|
+
async connectAuto() {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
let sseResolved = false;
|
|
76
|
+
let sseError = null;
|
|
77
|
+
this.connectionTimeout = setTimeout(() => {
|
|
78
|
+
if (!sseResolved) {
|
|
79
|
+
sseResolved = true;
|
|
80
|
+
this.closeSSE();
|
|
81
|
+
this.connectWebSocket().then(resolve).catch(reject);
|
|
82
|
+
}
|
|
83
|
+
}, 5e3);
|
|
84
|
+
this.connectSSE().then(() => {
|
|
85
|
+
if (!sseResolved) {
|
|
86
|
+
sseResolved = true;
|
|
87
|
+
if (this.connectionTimeout) {
|
|
88
|
+
clearTimeout(this.connectionTimeout);
|
|
89
|
+
this.connectionTimeout = null;
|
|
90
|
+
}
|
|
91
|
+
resolve();
|
|
92
|
+
}
|
|
93
|
+
}).catch((error) => {
|
|
94
|
+
sseError = error;
|
|
95
|
+
if (!sseResolved) {
|
|
96
|
+
sseResolved = true;
|
|
97
|
+
if (this.connectionTimeout) {
|
|
98
|
+
clearTimeout(this.connectionTimeout);
|
|
99
|
+
this.connectionTimeout = null;
|
|
100
|
+
}
|
|
101
|
+
this.connectWebSocket().then(resolve).catch((wsError) => {
|
|
102
|
+
const combinedError = new Error(
|
|
103
|
+
`Failed to connect via SSE (${sseError?.message}) and WebSocket (${wsError.message})`
|
|
104
|
+
);
|
|
105
|
+
this.config.onError?.(combinedError);
|
|
106
|
+
reject(combinedError);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async connectSSE() {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
try {
|
|
115
|
+
const eventSource = new EventSourcePolyfill(this.config.sseUrl, {
|
|
116
|
+
headers: this.config.headers ?? {}
|
|
117
|
+
});
|
|
118
|
+
eventSource.onopen = () => {
|
|
119
|
+
this.activeStrategy = "sse";
|
|
120
|
+
this.reconnectCount = 0;
|
|
121
|
+
this.config.onConnect?.();
|
|
122
|
+
resolve();
|
|
123
|
+
};
|
|
124
|
+
eventSource.onerror = () => {
|
|
125
|
+
if (this.activeStrategy === null) {
|
|
126
|
+
this.closeSSE();
|
|
127
|
+
reject(new Error("SSE connection failed"));
|
|
128
|
+
} else {
|
|
129
|
+
this.handleDisconnect();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
eventSource.onmessage = (event) => {
|
|
133
|
+
this.handleMessage(event);
|
|
134
|
+
};
|
|
135
|
+
this.setupSSEEventListeners(eventSource);
|
|
136
|
+
this.eventSource = eventSource;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
reject(error);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
setupSSEEventListeners(eventSource) {
|
|
143
|
+
const originalAddEventListener = eventSource.addEventListener.bind(eventSource);
|
|
144
|
+
const eventTypes = [
|
|
145
|
+
"update",
|
|
146
|
+
"step:complete",
|
|
147
|
+
"execution:complete",
|
|
148
|
+
"execution:failed"
|
|
149
|
+
];
|
|
150
|
+
eventTypes.forEach((type) => {
|
|
151
|
+
originalAddEventListener(type, (event) => {
|
|
152
|
+
this.handleMessage(event);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async connectWebSocket() {
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
try {
|
|
159
|
+
const wsUrl = this.config.wsUrl ?? this.deriveWebSocketUrl(this.config.sseUrl);
|
|
160
|
+
const webSocket = new WebSocket(wsUrl);
|
|
161
|
+
webSocket.onopen = () => {
|
|
162
|
+
this.activeStrategy = "websocket";
|
|
163
|
+
this.reconnectCount = 0;
|
|
164
|
+
this.config.onConnect?.();
|
|
165
|
+
resolve();
|
|
166
|
+
};
|
|
167
|
+
webSocket.onerror = () => {
|
|
168
|
+
if (this.activeStrategy === null) {
|
|
169
|
+
this.closeWebSocket();
|
|
170
|
+
reject(new Error("WebSocket connection failed"));
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
webSocket.onclose = () => {
|
|
174
|
+
this.handleDisconnect();
|
|
175
|
+
};
|
|
176
|
+
webSocket.onmessage = (event) => {
|
|
177
|
+
try {
|
|
178
|
+
const data = JSON.parse(event.data);
|
|
179
|
+
this.config.onMessage?.({
|
|
180
|
+
type: data.type ?? "message",
|
|
181
|
+
data: data.data ?? data
|
|
182
|
+
});
|
|
183
|
+
} catch (error) {
|
|
184
|
+
this.config.onError?.(
|
|
185
|
+
new Error(
|
|
186
|
+
`Failed to parse WebSocket message: ${error.message}`
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
this.webSocket = webSocket;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
reject(error);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// Private Methods - Event Handling
|
|
199
|
+
// ============================================================================
|
|
200
|
+
handleMessage(event) {
|
|
201
|
+
try {
|
|
202
|
+
const data = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
|
|
203
|
+
this.config.onMessage?.({
|
|
204
|
+
type: event.type ?? "message",
|
|
205
|
+
data
|
|
206
|
+
});
|
|
207
|
+
} catch (error) {
|
|
208
|
+
this.config.onError?.(
|
|
209
|
+
new Error(`Failed to parse message: ${error.message}`)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
handleDisconnect() {
|
|
214
|
+
this.config.onDisconnect?.();
|
|
215
|
+
if (!this.isManualDisconnect && this.reconnectCount < this.config.reconnectAttempts) {
|
|
216
|
+
this.attemptReconnect();
|
|
217
|
+
} else if (!this.isManualDisconnect) {
|
|
218
|
+
this.config.onError?.(new Error("Max reconnection attempts reached"));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
attemptReconnect() {
|
|
222
|
+
this.reconnectCount++;
|
|
223
|
+
const delay = this.config.reconnectDelayMs * Math.pow(2, this.reconnectCount - 1);
|
|
224
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
225
|
+
if (this.activeStrategy === "sse") {
|
|
226
|
+
this.closeSSE();
|
|
227
|
+
this.connectSSE().catch(() => {
|
|
228
|
+
this.handleDisconnect();
|
|
229
|
+
});
|
|
230
|
+
} else if (this.activeStrategy === "websocket") {
|
|
231
|
+
this.closeWebSocket();
|
|
232
|
+
this.connectWebSocket().catch(() => {
|
|
233
|
+
this.handleDisconnect();
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}, delay);
|
|
237
|
+
}
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// Private Methods - Cleanup
|
|
240
|
+
// ============================================================================
|
|
241
|
+
closeConnections() {
|
|
242
|
+
this.closeSSE();
|
|
243
|
+
this.closeWebSocket();
|
|
244
|
+
}
|
|
245
|
+
closeSSE() {
|
|
246
|
+
if (this.eventSource) {
|
|
247
|
+
this.eventSource.close();
|
|
248
|
+
this.eventSource = null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
closeWebSocket() {
|
|
252
|
+
if (this.webSocket) {
|
|
253
|
+
this.webSocket.close();
|
|
254
|
+
this.webSocket = null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
clearTimeouts() {
|
|
258
|
+
if (this.reconnectTimeout) {
|
|
259
|
+
clearTimeout(this.reconnectTimeout);
|
|
260
|
+
this.reconnectTimeout = null;
|
|
261
|
+
}
|
|
262
|
+
if (this.connectionTimeout) {
|
|
263
|
+
clearTimeout(this.connectionTimeout);
|
|
264
|
+
this.connectionTimeout = null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// Private Methods - Utilities
|
|
269
|
+
// ============================================================================
|
|
270
|
+
deriveWebSocketUrl(sseUrl) {
|
|
271
|
+
const url = new URL(sseUrl);
|
|
272
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
273
|
+
return url.toString();
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/token-manager.ts
|
|
278
|
+
var MemoryTokenStorage = class {
|
|
279
|
+
store = /* @__PURE__ */ new Map();
|
|
280
|
+
async get(key) {
|
|
281
|
+
return this.store.get(key) ?? null;
|
|
282
|
+
}
|
|
283
|
+
async set(key, value) {
|
|
284
|
+
this.store.set(key, value);
|
|
285
|
+
}
|
|
286
|
+
async remove(key) {
|
|
287
|
+
this.store.delete(key);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var LocalStorageTokenStorage = class {
|
|
291
|
+
async get(key) {
|
|
292
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
return window.localStorage.getItem(key);
|
|
296
|
+
}
|
|
297
|
+
async set(key, value) {
|
|
298
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
window.localStorage.setItem(key, value);
|
|
302
|
+
}
|
|
303
|
+
async remove(key) {
|
|
304
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
window.localStorage.removeItem(key);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
var TokenManager = class {
|
|
311
|
+
storage;
|
|
312
|
+
storageKeyPrefix;
|
|
313
|
+
refreshThresholdMs;
|
|
314
|
+
onTokenRefresh;
|
|
315
|
+
onTokenExpired;
|
|
316
|
+
refreshTimers = /* @__PURE__ */ new Map();
|
|
317
|
+
constructor(config) {
|
|
318
|
+
this.storage = config?.storage ?? new MemoryTokenStorage();
|
|
319
|
+
this.storageKeyPrefix = config?.storageKeyPrefix ?? "stepflow_token_";
|
|
320
|
+
this.refreshThresholdMs = config?.refreshThresholdMs ?? 3e5;
|
|
321
|
+
this.onTokenRefresh = config?.onTokenRefresh;
|
|
322
|
+
this.onTokenExpired = config?.onTokenExpired;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Store a token with expiry
|
|
326
|
+
*/
|
|
327
|
+
async storeToken(runId, token, expiresAt) {
|
|
328
|
+
const stored = {
|
|
329
|
+
token,
|
|
330
|
+
expiresAt: expiresAt.getTime()
|
|
331
|
+
};
|
|
332
|
+
const key = this.getStorageKey(runId);
|
|
333
|
+
await this.storage.set(key, JSON.stringify(stored));
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Get a token if it's still valid
|
|
337
|
+
* @returns Token string or null if expired/not found
|
|
338
|
+
*/
|
|
339
|
+
async getToken(runId) {
|
|
340
|
+
const key = this.getStorageKey(runId);
|
|
341
|
+
const value = await this.storage.get(key);
|
|
342
|
+
if (!value) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const stored = JSON.parse(value);
|
|
347
|
+
const now = Date.now();
|
|
348
|
+
if (stored.expiresAt <= now) {
|
|
349
|
+
await this.removeToken(runId);
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
return stored.token;
|
|
353
|
+
} catch {
|
|
354
|
+
await this.removeToken(runId);
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Remove a token from storage
|
|
360
|
+
*/
|
|
361
|
+
async removeToken(runId) {
|
|
362
|
+
const key = this.getStorageKey(runId);
|
|
363
|
+
await this.storage.remove(key);
|
|
364
|
+
this.stopAutoRefresh(runId);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Check if a token is valid (exists and not expired)
|
|
368
|
+
*/
|
|
369
|
+
async isTokenValid(runId) {
|
|
370
|
+
const token = await this.getToken(runId);
|
|
371
|
+
return token !== null;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get token expiry date
|
|
375
|
+
* @returns Expiry date or null if not found
|
|
376
|
+
*/
|
|
377
|
+
async getTokenExpiry(runId) {
|
|
378
|
+
const key = this.getStorageKey(runId);
|
|
379
|
+
const value = await this.storage.get(key);
|
|
380
|
+
if (!value) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const stored = JSON.parse(value);
|
|
385
|
+
return new Date(stored.expiresAt);
|
|
386
|
+
} catch {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Start auto-refresh for a token
|
|
392
|
+
*/
|
|
393
|
+
startAutoRefresh(runId, refreshFn) {
|
|
394
|
+
this.stopAutoRefresh(runId);
|
|
395
|
+
this.scheduleRefresh(runId, refreshFn);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Stop auto-refresh for a specific token
|
|
399
|
+
*/
|
|
400
|
+
stopAutoRefresh(runId) {
|
|
401
|
+
const timer = this.refreshTimers.get(runId);
|
|
402
|
+
if (timer) {
|
|
403
|
+
clearTimeout(timer);
|
|
404
|
+
this.refreshTimers.delete(runId);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Stop all auto-refresh timers
|
|
409
|
+
*/
|
|
410
|
+
stopAllAutoRefresh() {
|
|
411
|
+
for (const timer of this.refreshTimers.values()) {
|
|
412
|
+
clearTimeout(timer);
|
|
413
|
+
}
|
|
414
|
+
this.refreshTimers.clear();
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Schedule a refresh based on token expiry
|
|
418
|
+
*/
|
|
419
|
+
async scheduleRefresh(runId, refreshFn) {
|
|
420
|
+
const expiry = await this.getTokenExpiry(runId);
|
|
421
|
+
if (!expiry) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const now = Date.now();
|
|
425
|
+
const expiryTime = expiry.getTime();
|
|
426
|
+
const refreshTime = expiryTime - this.refreshThresholdMs;
|
|
427
|
+
const delay = Math.max(0, refreshTime - now);
|
|
428
|
+
const timer = setTimeout(async () => {
|
|
429
|
+
try {
|
|
430
|
+
const { token, expiresAt } = await refreshFn();
|
|
431
|
+
await this.storeToken(runId, token, expiresAt);
|
|
432
|
+
this.onTokenRefresh?.(runId, token);
|
|
433
|
+
this.scheduleRefresh(runId, refreshFn);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
this.onTokenExpired?.(runId);
|
|
436
|
+
this.stopAutoRefresh(runId);
|
|
437
|
+
}
|
|
438
|
+
}, delay);
|
|
439
|
+
this.refreshTimers.set(runId, timer);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Get storage key for a run ID
|
|
443
|
+
*/
|
|
444
|
+
getStorageKey(runId) {
|
|
445
|
+
return `${this.storageKeyPrefix}${runId}`;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// src/index.ts
|
|
450
|
+
var StepflowError = class extends Error {
|
|
451
|
+
constructor(message, code, status) {
|
|
452
|
+
super(message);
|
|
453
|
+
this.code = code;
|
|
454
|
+
this.status = status;
|
|
455
|
+
this.name = "StepflowError";
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
var StepflowClient = class {
|
|
459
|
+
constructor(config) {
|
|
460
|
+
this.config = config;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Trigger a workflow execution
|
|
464
|
+
*/
|
|
465
|
+
async trigger(workflowId, payload, options) {
|
|
466
|
+
const response = await this.fetch(`/workflows/${workflowId}/trigger`, {
|
|
467
|
+
method: "POST",
|
|
468
|
+
body: JSON.stringify({ payload, ...options })
|
|
469
|
+
});
|
|
470
|
+
return await response.json();
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Subscribe to a run's real-time updates via SSE or WebSocket
|
|
474
|
+
*/
|
|
475
|
+
subscribeToRun(runId, options) {
|
|
476
|
+
const {
|
|
477
|
+
accessToken,
|
|
478
|
+
throttleMs,
|
|
479
|
+
strategy,
|
|
480
|
+
onUpdate,
|
|
481
|
+
onStepComplete,
|
|
482
|
+
onComplete,
|
|
483
|
+
onError,
|
|
484
|
+
onConnect,
|
|
485
|
+
onDisconnect
|
|
486
|
+
} = options;
|
|
487
|
+
if (strategy) {
|
|
488
|
+
return this.subscribeWithRealtimeConnection(runId, {
|
|
489
|
+
accessToken,
|
|
490
|
+
throttleMs,
|
|
491
|
+
strategy,
|
|
492
|
+
onUpdate,
|
|
493
|
+
onStepComplete,
|
|
494
|
+
onComplete,
|
|
495
|
+
onError,
|
|
496
|
+
onConnect,
|
|
497
|
+
onDisconnect
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
const url = new URL(`${this.config.baseUrl}/runs/${runId}/stream`);
|
|
501
|
+
if (accessToken) {
|
|
502
|
+
url.searchParams.set("token", accessToken);
|
|
503
|
+
}
|
|
504
|
+
if (throttleMs) {
|
|
505
|
+
url.searchParams.set("throttle", String(throttleMs));
|
|
506
|
+
}
|
|
507
|
+
const headers = {
|
|
508
|
+
Accept: "text/event-stream",
|
|
509
|
+
...this.getAuthHeaders(accessToken)
|
|
510
|
+
};
|
|
511
|
+
const eventSource = new EventSourcePolyfill2(url.toString(), { headers });
|
|
512
|
+
eventSource.onopen = () => {
|
|
513
|
+
onConnect?.();
|
|
514
|
+
};
|
|
515
|
+
eventSource.onerror = () => {
|
|
516
|
+
onDisconnect?.();
|
|
517
|
+
};
|
|
518
|
+
eventSource.addEventListener("update", (event) => {
|
|
519
|
+
const data = JSON.parse(event.data);
|
|
520
|
+
onUpdate?.(data);
|
|
521
|
+
});
|
|
522
|
+
eventSource.addEventListener("step:complete", (event) => {
|
|
523
|
+
const data = JSON.parse(event.data);
|
|
524
|
+
onStepComplete?.(data);
|
|
525
|
+
});
|
|
526
|
+
eventSource.addEventListener("execution:complete", (event) => {
|
|
527
|
+
const data = JSON.parse(event.data);
|
|
528
|
+
onComplete?.(data.result);
|
|
529
|
+
});
|
|
530
|
+
eventSource.addEventListener("execution:failed", (event) => {
|
|
531
|
+
const data = JSON.parse(event.data);
|
|
532
|
+
onError?.(new Error(data.error.message));
|
|
533
|
+
});
|
|
534
|
+
return () => {
|
|
535
|
+
eventSource.close();
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Subscribe using RealtimeConnection with SSE/WebSocket fallback
|
|
540
|
+
*/
|
|
541
|
+
subscribeWithRealtimeConnection(runId, options) {
|
|
542
|
+
const {
|
|
543
|
+
accessToken,
|
|
544
|
+
throttleMs,
|
|
545
|
+
strategy,
|
|
546
|
+
onUpdate,
|
|
547
|
+
onStepComplete,
|
|
548
|
+
onComplete,
|
|
549
|
+
onError,
|
|
550
|
+
onConnect,
|
|
551
|
+
onDisconnect
|
|
552
|
+
} = options;
|
|
553
|
+
const url = new URL(`${this.config.baseUrl}/runs/${runId}/stream`);
|
|
554
|
+
if (accessToken) {
|
|
555
|
+
url.searchParams.set("token", accessToken);
|
|
556
|
+
}
|
|
557
|
+
if (throttleMs) {
|
|
558
|
+
url.searchParams.set("throttle", String(throttleMs));
|
|
559
|
+
}
|
|
560
|
+
const headers = {
|
|
561
|
+
Accept: "text/event-stream",
|
|
562
|
+
...this.getAuthHeaders(accessToken)
|
|
563
|
+
};
|
|
564
|
+
const wsUrl = this.config.wsUrl ?? this.deriveWebSocketUrl(url.toString());
|
|
565
|
+
const connection = new RealtimeConnection({
|
|
566
|
+
strategy,
|
|
567
|
+
sseUrl: url.toString(),
|
|
568
|
+
wsUrl,
|
|
569
|
+
headers,
|
|
570
|
+
onConnect,
|
|
571
|
+
onDisconnect,
|
|
572
|
+
onError,
|
|
573
|
+
onMessage: (event) => {
|
|
574
|
+
if (event.type === "update") {
|
|
575
|
+
onUpdate?.(event.data);
|
|
576
|
+
} else if (event.type === "step:complete") {
|
|
577
|
+
onStepComplete?.(event.data);
|
|
578
|
+
} else if (event.type === "execution:complete") {
|
|
579
|
+
const data = event.data;
|
|
580
|
+
onComplete?.(data.result);
|
|
581
|
+
} else if (event.type === "execution:failed") {
|
|
582
|
+
const data = event.data;
|
|
583
|
+
onError?.(new Error(data.error.message));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
connection.connect().catch((error) => {
|
|
588
|
+
onError?.(error);
|
|
589
|
+
});
|
|
590
|
+
return () => {
|
|
591
|
+
connection.disconnect();
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Derive WebSocket URL from HTTP URL
|
|
596
|
+
*/
|
|
597
|
+
deriveWebSocketUrl(httpUrl) {
|
|
598
|
+
const url = new URL(httpUrl);
|
|
599
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
600
|
+
return url.toString();
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Stream a workflow execution (trigger + stream events)
|
|
604
|
+
*/
|
|
605
|
+
async *stream(workflowId, payload, options) {
|
|
606
|
+
const response = await this.fetch(`/workflows/${workflowId}/stream`, {
|
|
607
|
+
method: "POST",
|
|
608
|
+
body: JSON.stringify({ payload }),
|
|
609
|
+
signal: options?.signal
|
|
610
|
+
});
|
|
611
|
+
if (!response.body) {
|
|
612
|
+
throw new StepflowError("No response body");
|
|
613
|
+
}
|
|
614
|
+
const reader = response.body.getReader();
|
|
615
|
+
const decoder = new TextDecoder();
|
|
616
|
+
let buffer = "";
|
|
617
|
+
while (true) {
|
|
618
|
+
const { done, value } = await reader.read();
|
|
619
|
+
if (done) break;
|
|
620
|
+
buffer += decoder.decode(value, { stream: true });
|
|
621
|
+
const lines = buffer.split("\n");
|
|
622
|
+
buffer = lines.pop() || "";
|
|
623
|
+
for (const line of lines) {
|
|
624
|
+
if (line.startsWith("data: ")) {
|
|
625
|
+
try {
|
|
626
|
+
const data = JSON.parse(line.slice(6));
|
|
627
|
+
yield data;
|
|
628
|
+
} catch {
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Get a run by ID
|
|
636
|
+
*/
|
|
637
|
+
async getRun(runId, options) {
|
|
638
|
+
const response = await this.fetch(`/runs/${runId}`, {
|
|
639
|
+
headers: this.getAuthHeaders(options?.accessToken)
|
|
640
|
+
});
|
|
641
|
+
if (response.status === 404) {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
return await response.json();
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Get execution by ID
|
|
648
|
+
*/
|
|
649
|
+
async getExecution(executionId) {
|
|
650
|
+
const response = await this.fetch(`/executions/${executionId}`);
|
|
651
|
+
if (response.status === 404) {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
return await response.json();
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* List runs with optional filters
|
|
658
|
+
*/
|
|
659
|
+
async listRuns(options) {
|
|
660
|
+
const params = new URLSearchParams();
|
|
661
|
+
if (options?.workflowId) params.set("workflowId", options.workflowId);
|
|
662
|
+
if (options?.status) params.set("status", options.status);
|
|
663
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
664
|
+
if (options?.offset) params.set("offset", String(options.offset));
|
|
665
|
+
const response = await this.fetch(`/runs?${params}`);
|
|
666
|
+
return await response.json();
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Notify an event (for waitForEvent)
|
|
670
|
+
*/
|
|
671
|
+
async notify(eventId, data) {
|
|
672
|
+
const response = await this.fetch(`/events/${eventId}/notify`, {
|
|
673
|
+
method: "POST",
|
|
674
|
+
body: JSON.stringify({ data })
|
|
675
|
+
});
|
|
676
|
+
return await response.json();
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Create a public access token for a run
|
|
680
|
+
*/
|
|
681
|
+
async createAccessToken(runId) {
|
|
682
|
+
const response = await this.fetch(`/runs/${runId}/token`, {
|
|
683
|
+
method: "POST"
|
|
684
|
+
});
|
|
685
|
+
return await response.json();
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Health check
|
|
689
|
+
*/
|
|
690
|
+
async health() {
|
|
691
|
+
const response = await this.fetch("/health");
|
|
692
|
+
return await response.json();
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Internal fetch helper
|
|
696
|
+
*/
|
|
697
|
+
async fetch(path, options = {}) {
|
|
698
|
+
const url = `${this.config.baseUrl}${path}`;
|
|
699
|
+
const headers = {
|
|
700
|
+
"Content-Type": "application/json",
|
|
701
|
+
...this.getAuthHeaders(),
|
|
702
|
+
...options.headers
|
|
703
|
+
};
|
|
704
|
+
const response = await fetch(url, { ...options, headers });
|
|
705
|
+
if (!response.ok && response.status !== 404) {
|
|
706
|
+
let errorData = {};
|
|
707
|
+
try {
|
|
708
|
+
errorData = await response.json();
|
|
709
|
+
} catch {
|
|
710
|
+
errorData = { message: response.statusText };
|
|
711
|
+
}
|
|
712
|
+
throw new StepflowError(
|
|
713
|
+
errorData.message || "Request failed",
|
|
714
|
+
errorData.code,
|
|
715
|
+
response.status
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
return response;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Get auth headers
|
|
722
|
+
*/
|
|
723
|
+
getAuthHeaders(accessToken) {
|
|
724
|
+
if (accessToken) {
|
|
725
|
+
return { Authorization: `Bearer ${accessToken}` };
|
|
726
|
+
}
|
|
727
|
+
if (this.config.apiKey) {
|
|
728
|
+
return { "X-API-Key": this.config.apiKey };
|
|
729
|
+
}
|
|
730
|
+
if (this.config.publicApiKey) {
|
|
731
|
+
return { "X-Public-Key": this.config.publicApiKey };
|
|
732
|
+
}
|
|
733
|
+
return {};
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
var index_default = StepflowClient;
|
|
737
|
+
export {
|
|
738
|
+
LocalStorageTokenStorage,
|
|
739
|
+
MemoryTokenStorage,
|
|
740
|
+
RealtimeConnection,
|
|
741
|
+
StepflowClient,
|
|
742
|
+
StepflowError,
|
|
743
|
+
TokenManager,
|
|
744
|
+
index_default as default
|
|
745
|
+
};
|
|
746
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/realtime.ts","../src/token-manager.ts"],"sourcesContent":["// ============================================================================\n// Stepflow TypeScript Client\n// ============================================================================\n\nimport { EventSourcePolyfill } from \"event-source-polyfill\";\nimport type { RealtimeStrategy } from \"./realtime.js\";\nimport { RealtimeConnection } from \"./realtime.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface StepflowClientConfig {\n /**\n * Base URL of the Stepflow API\n */\n baseUrl: string;\n\n /**\n * API key for server-side requests\n */\n apiKey?: string;\n\n /**\n * Public API key for client-side requests\n */\n publicApiKey?: string;\n\n /**\n * WebSocket URL for real-time updates\n */\n wsUrl?: string;\n}\n\nexport type ExecutionStatus =\n | \"pending\"\n | \"running\"\n | \"completed\"\n | \"failed\"\n | \"waiting\"\n | \"sleeping\"\n | \"cancelled\";\n\nexport interface Execution<TPayload = unknown, TResult = unknown> {\n id: string;\n runId: string;\n workflowId: string;\n eventName: string;\n payload: TPayload;\n status: ExecutionStatus;\n result?: TResult;\n error?: ExecutionError;\n steps: StepExecution[];\n metadata: Record<string, unknown>;\n attempt: number;\n startedAt: Date;\n completedAt?: Date;\n}\n\nexport interface ExecutionError {\n name: string;\n message: string;\n stack?: string;\n code?: string;\n}\n\nexport interface StepExecution {\n name: string;\n status: \"pending\" | \"running\" | \"completed\" | \"failed\" | \"cached\";\n result?: unknown;\n error?: ExecutionError;\n startedAt?: Date;\n completedAt?: Date;\n durationMs?: number;\n}\n\nexport interface TriggerResult {\n runId: string;\n executionId: string;\n publicAccessToken: string;\n}\n\nexport interface NotifyResult {\n waiters: number;\n executions: string[];\n}\n\nexport interface TriggerOptions {\n runId?: string;\n metadata?: Record<string, unknown>;\n delay?: number;\n}\n\nexport interface SubscribeOptions<TPayload = unknown, TResult = unknown> {\n accessToken?: string;\n throttleMs?: number;\n strategy?: RealtimeStrategy;\n onUpdate?: (run: Execution<TPayload, TResult>) => void;\n onStepComplete?: (step: StepExecution) => void;\n onComplete?: (result: TResult) => void;\n onError?: (error: Error) => void;\n onConnect?: () => void;\n onDisconnect?: () => void;\n}\n\nexport interface StreamOptions {\n signal?: AbortSignal;\n}\n\nexport interface StreamEvent {\n type: string;\n data: unknown;\n stepName?: string;\n}\n\nexport interface ListRunsOptions {\n workflowId?: string;\n status?: ExecutionStatus;\n limit?: number;\n offset?: number;\n}\n\nexport interface PaginatedResult<T> {\n data: T[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport type Unsubscribe = () => void;\n\n// ============================================================================\n// Client Error\n// ============================================================================\n\nexport class StepflowError extends Error {\n constructor(\n message: string,\n public readonly code?: string,\n public readonly status?: number,\n ) {\n super(message);\n this.name = \"StepflowError\";\n }\n}\n\n// ============================================================================\n// Client Implementation\n// ============================================================================\n\nexport class StepflowClient {\n constructor(private config: StepflowClientConfig) {}\n\n /**\n * Trigger a workflow execution\n */\n async trigger<TPayload = unknown>(\n workflowId: string,\n payload: TPayload,\n options?: TriggerOptions,\n ): Promise<TriggerResult> {\n const response = await this.fetch(`/workflows/${workflowId}/trigger`, {\n method: \"POST\",\n body: JSON.stringify({ payload, ...options }),\n });\n\n return (await response.json()) as TriggerResult;\n }\n\n /**\n * Subscribe to a run's real-time updates via SSE or WebSocket\n */\n subscribeToRun<TPayload = unknown, TResult = unknown>(\n runId: string,\n options: SubscribeOptions<TPayload, TResult>,\n ): Unsubscribe {\n const {\n accessToken,\n throttleMs,\n strategy,\n onUpdate,\n onStepComplete,\n onComplete,\n onError,\n onConnect,\n onDisconnect,\n } = options;\n\n // If strategy is provided, use RealtimeConnection\n if (strategy) {\n return this.subscribeWithRealtimeConnection(runId, {\n accessToken,\n throttleMs,\n strategy,\n onUpdate,\n onStepComplete,\n onComplete,\n onError,\n onConnect,\n onDisconnect,\n });\n }\n\n // Default behavior: use EventSource directly (backward compatibility)\n const url = new URL(`${this.config.baseUrl}/runs/${runId}/stream`);\n if (accessToken) {\n url.searchParams.set(\"token\", accessToken);\n }\n if (throttleMs) {\n url.searchParams.set(\"throttle\", String(throttleMs));\n }\n\n const headers: Record<string, string> = {\n Accept: \"text/event-stream\",\n ...this.getAuthHeaders(accessToken),\n };\n\n const eventSource = new EventSourcePolyfill(url.toString(), { headers });\n\n eventSource.onopen = () => {\n onConnect?.();\n };\n\n eventSource.onerror = () => {\n onDisconnect?.();\n };\n\n // Type-safe event listeners using any to bypass EventSource typing issues\n eventSource.addEventListener(\"update\", (event: any) => {\n const data = JSON.parse(event.data) as Execution<TPayload, TResult>;\n onUpdate?.(data);\n });\n\n eventSource.addEventListener(\"step:complete\", (event: any) => {\n const data = JSON.parse(event.data) as StepExecution;\n onStepComplete?.(data);\n });\n\n eventSource.addEventListener(\"execution:complete\", (event: any) => {\n const data = JSON.parse(event.data) as { result: TResult };\n onComplete?.(data.result);\n });\n\n eventSource.addEventListener(\"execution:failed\", (event: any) => {\n const data = JSON.parse(event.data) as { error: { message: string } };\n onError?.(new Error(data.error.message));\n });\n\n return () => {\n eventSource.close();\n };\n }\n\n /**\n * Subscribe using RealtimeConnection with SSE/WebSocket fallback\n */\n private subscribeWithRealtimeConnection<\n TPayload = unknown,\n TResult = unknown,\n >(\n runId: string,\n options: Required<Pick<SubscribeOptions<TPayload, TResult>, \"strategy\">> &\n SubscribeOptions<TPayload, TResult>,\n ): Unsubscribe {\n const {\n accessToken,\n throttleMs,\n strategy,\n onUpdate,\n onStepComplete,\n onComplete,\n onError,\n onConnect,\n onDisconnect,\n } = options;\n\n const url = new URL(`${this.config.baseUrl}/runs/${runId}/stream`);\n if (accessToken) {\n url.searchParams.set(\"token\", accessToken);\n }\n if (throttleMs) {\n url.searchParams.set(\"throttle\", String(throttleMs));\n }\n\n const headers: Record<string, string> = {\n Accept: \"text/event-stream\",\n ...this.getAuthHeaders(accessToken),\n };\n\n // Derive WebSocket URL from config or SSE URL\n const wsUrl = this.config.wsUrl ?? this.deriveWebSocketUrl(url.toString());\n\n const connection = new RealtimeConnection({\n strategy,\n sseUrl: url.toString(),\n wsUrl,\n headers,\n onConnect,\n onDisconnect,\n onError,\n onMessage: (event) => {\n // Route events to appropriate handlers\n if (event.type === \"update\") {\n onUpdate?.(event.data as Execution<TPayload, TResult>);\n } else if (event.type === \"step:complete\") {\n onStepComplete?.(event.data as StepExecution);\n } else if (event.type === \"execution:complete\") {\n const data = event.data as { result: TResult };\n onComplete?.(data.result);\n } else if (event.type === \"execution:failed\") {\n const data = event.data as { error: { message: string } };\n onError?.(new Error(data.error.message));\n }\n },\n });\n\n // Connect asynchronously\n connection.connect().catch((error) => {\n onError?.(error as Error);\n });\n\n return () => {\n connection.disconnect();\n };\n }\n\n /**\n * Derive WebSocket URL from HTTP URL\n */\n private deriveWebSocketUrl(httpUrl: string): string {\n const url = new URL(httpUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return url.toString();\n }\n\n /**\n * Stream a workflow execution (trigger + stream events)\n */\n async *stream<TPayload = unknown, TResult = unknown>(\n workflowId: string,\n payload: TPayload,\n options?: StreamOptions,\n ): AsyncGenerator<StreamEvent, void, unknown> {\n const response = await this.fetch(`/workflows/${workflowId}/stream`, {\n method: \"POST\",\n body: JSON.stringify({ payload }),\n signal: options?.signal,\n });\n\n if (!response.body) {\n throw new StepflowError(\"No response body\");\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n try {\n const data = JSON.parse(line.slice(6));\n yield data as StreamEvent;\n } catch {\n // Skip malformed events\n }\n }\n }\n }\n }\n\n /**\n * Get a run by ID\n */\n async getRun<TPayload = unknown, TResult = unknown>(\n runId: string,\n options?: { accessToken?: string },\n ): Promise<Execution<TPayload, TResult> | null> {\n const response = await this.fetch(`/runs/${runId}`, {\n headers: this.getAuthHeaders(options?.accessToken),\n });\n\n if (response.status === 404) {\n return null;\n }\n\n return (await response.json()) as Execution<TPayload, TResult>;\n }\n\n /**\n * Get execution by ID\n */\n async getExecution<TPayload = unknown, TResult = unknown>(\n executionId: string,\n ): Promise<Execution<TPayload, TResult> | null> {\n const response = await this.fetch(`/executions/${executionId}`);\n\n if (response.status === 404) {\n return null;\n }\n\n return (await response.json()) as Execution<TPayload, TResult>;\n }\n\n /**\n * List runs with optional filters\n */\n async listRuns(\n options?: ListRunsOptions,\n ): Promise<PaginatedResult<Execution>> {\n const params = new URLSearchParams();\n if (options?.workflowId) params.set(\"workflowId\", options.workflowId);\n if (options?.status) params.set(\"status\", options.status);\n if (options?.limit) params.set(\"limit\", String(options.limit));\n if (options?.offset) params.set(\"offset\", String(options.offset));\n\n const response = await this.fetch(`/runs?${params}`);\n return (await response.json()) as PaginatedResult<Execution>;\n }\n\n /**\n * Notify an event (for waitForEvent)\n */\n async notify(eventId: string, data: unknown): Promise<NotifyResult> {\n const response = await this.fetch(`/events/${eventId}/notify`, {\n method: \"POST\",\n body: JSON.stringify({ data }),\n });\n return (await response.json()) as NotifyResult;\n }\n\n /**\n * Create a public access token for a run\n */\n async createAccessToken(\n runId: string,\n ): Promise<{ token: string; expiresAt: Date }> {\n const response = await this.fetch(`/runs/${runId}/token`, {\n method: \"POST\",\n });\n return (await response.json()) as { token: string; expiresAt: Date };\n }\n\n /**\n * Health check\n */\n async health(): Promise<{ ok: boolean }> {\n const response = await this.fetch(\"/health\");\n return (await response.json()) as { ok: boolean };\n }\n\n /**\n * Internal fetch helper\n */\n private async fetch(\n path: string,\n options: RequestInit = {},\n ): Promise<Response> {\n const url = `${this.config.baseUrl}${path}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...this.getAuthHeaders(),\n ...(options.headers as Record<string, string>),\n };\n\n const response = await fetch(url, { ...options, headers });\n\n if (!response.ok && response.status !== 404) {\n let errorData: { message?: string; code?: string } = {};\n try {\n errorData = (await response.json()) as {\n message?: string;\n code?: string;\n };\n } catch {\n errorData = { message: response.statusText };\n }\n\n throw new StepflowError(\n errorData.message || \"Request failed\",\n errorData.code,\n response.status,\n );\n }\n\n return response;\n }\n\n /**\n * Get auth headers\n */\n private getAuthHeaders(accessToken?: string): Record<string, string> {\n if (accessToken) {\n return { Authorization: `Bearer ${accessToken}` };\n }\n if (this.config.apiKey) {\n return { \"X-API-Key\": this.config.apiKey };\n }\n if (this.config.publicApiKey) {\n return { \"X-Public-Key\": this.config.publicApiKey };\n }\n return {};\n }\n}\n\n// ============================================================================\n// Token Manager Exports\n// ============================================================================\n\nexport {\n TokenManager,\n MemoryTokenStorage,\n LocalStorageTokenStorage,\n} from \"./token-manager.js\";\n\nexport type { TokenStorage, TokenManagerConfig } from \"./token-manager.js\";\n\n// ============================================================================\n// Realtime Connection Exports\n// ============================================================================\n\nexport { RealtimeConnection } from \"./realtime.js\";\n\nexport type {\n RealtimeConnectionConfig,\n RealtimeEvent,\n RealtimeStrategy,\n} from \"./realtime.js\";\n\n// Default export\nexport default StepflowClient;\n","// ============================================================================\n// Realtime Connection - SSE/WebSocket Fallback Strategy\n// ============================================================================\n\nimport { EventSourcePolyfill } from \"event-source-polyfill\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type RealtimeStrategy = \"sse\" | \"websocket\" | \"auto\";\n\nexport interface RealtimeEvent {\n type: string;\n data: unknown;\n}\n\nexport interface RealtimeConnectionConfig {\n /**\n * Connection strategy\n * @default 'auto'\n */\n strategy?: RealtimeStrategy;\n\n /**\n * SSE endpoint URL\n */\n sseUrl: string;\n\n /**\n * WebSocket endpoint URL (optional, derived from sseUrl if not provided)\n */\n wsUrl?: string;\n\n /**\n * HTTP headers for SSE connection\n */\n headers?: Record<string, string>;\n\n /**\n * Number of reconnection attempts\n * @default 3\n */\n reconnectAttempts?: number;\n\n /**\n * Delay between reconnection attempts in milliseconds\n * @default 1000\n */\n reconnectDelayMs?: number;\n\n /**\n * Called when connection is established\n */\n onConnect?: () => void;\n\n /**\n * Called when connection is closed\n */\n onDisconnect?: () => void;\n\n /**\n * Called when an error occurs\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when a message is received\n */\n onMessage?: (event: RealtimeEvent) => void;\n}\n\n// ============================================================================\n// RealtimeConnection Implementation\n// ============================================================================\n\nexport class RealtimeConnection {\n private config: Required<\n Omit<\n RealtimeConnectionConfig,\n | \"wsUrl\"\n | \"headers\"\n | \"onConnect\"\n | \"onDisconnect\"\n | \"onError\"\n | \"onMessage\"\n >\n > &\n Pick<\n RealtimeConnectionConfig,\n | \"wsUrl\"\n | \"headers\"\n | \"onConnect\"\n | \"onDisconnect\"\n | \"onError\"\n | \"onMessage\"\n >;\n\n private eventSource: EventSourcePolyfill | null = null;\n private webSocket: WebSocket | null = null;\n private activeStrategy: \"sse\" | \"websocket\" | null = null;\n private reconnectCount = 0;\n private reconnectTimeout: ReturnType<typeof setTimeout> | null = null;\n private connectionTimeout: ReturnType<typeof setTimeout> | null = null;\n private isManualDisconnect = false;\n\n constructor(config: RealtimeConnectionConfig) {\n this.config = {\n strategy: config.strategy ?? \"auto\",\n sseUrl: config.sseUrl,\n wsUrl: config.wsUrl,\n headers: config.headers,\n reconnectAttempts: config.reconnectAttempts ?? 3,\n reconnectDelayMs: config.reconnectDelayMs ?? 1000,\n onConnect: config.onConnect,\n onDisconnect: config.onDisconnect,\n onError: config.onError,\n onMessage: config.onMessage,\n };\n }\n\n /**\n * Connect using the configured strategy\n */\n async connect(): Promise<void> {\n this.isManualDisconnect = false;\n this.reconnectCount = 0;\n\n const strategy = this.config.strategy;\n\n if (strategy === \"sse\") {\n await this.connectSSE();\n } else if (strategy === \"websocket\") {\n await this.connectWebSocket();\n } else {\n // Auto strategy: try SSE first, fallback to WebSocket\n await this.connectAuto();\n }\n }\n\n /**\n * Disconnect from the active connection\n */\n disconnect(): void {\n this.isManualDisconnect = true;\n this.clearTimeouts();\n this.closeConnections();\n this.activeStrategy = null;\n }\n\n /**\n * Check if currently connected\n */\n isConnected(): boolean {\n if (this.activeStrategy === \"sse\") {\n return (\n this.eventSource !== null &&\n this.eventSource.readyState === EventSourcePolyfill.OPEN\n );\n } else if (this.activeStrategy === \"websocket\") {\n return (\n this.webSocket !== null && this.webSocket.readyState === WebSocket.OPEN\n );\n }\n return false;\n }\n\n /**\n * Get the currently active strategy\n */\n getActiveStrategy(): \"sse\" | \"websocket\" | null {\n return this.activeStrategy;\n }\n\n // ============================================================================\n // Private Methods - Connection Strategies\n // ============================================================================\n\n private async connectAuto(): Promise<void> {\n return new Promise((resolve, reject) => {\n let sseResolved = false;\n let sseError: Error | null = null;\n\n // Set a timeout for SSE connection\n this.connectionTimeout = setTimeout(() => {\n if (!sseResolved) {\n sseResolved = true;\n // SSE failed to connect in time, try WebSocket\n this.closeSSE();\n this.connectWebSocket().then(resolve).catch(reject);\n }\n }, 5000);\n\n // Try SSE first\n this.connectSSE()\n .then(() => {\n if (!sseResolved) {\n sseResolved = true;\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n resolve();\n }\n })\n .catch((error) => {\n sseError = error as Error;\n if (!sseResolved) {\n sseResolved = true;\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n // SSE failed, try WebSocket\n this.connectWebSocket()\n .then(resolve)\n .catch((wsError) => {\n // Both failed\n const combinedError = new Error(\n `Failed to connect via SSE (${sseError?.message}) and WebSocket (${(wsError as Error).message})`,\n );\n this.config.onError?.(combinedError);\n reject(combinedError);\n });\n }\n });\n });\n }\n\n private async connectSSE(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n const eventSource = new EventSourcePolyfill(this.config.sseUrl, {\n headers: this.config.headers ?? {},\n });\n\n eventSource.onopen = () => {\n this.activeStrategy = \"sse\";\n this.reconnectCount = 0;\n this.config.onConnect?.();\n resolve();\n };\n\n eventSource.onerror = () => {\n if (this.activeStrategy === null) {\n // Connection failed before opening\n this.closeSSE();\n reject(new Error(\"SSE connection failed\"));\n } else {\n // Connection was open but now errored\n this.handleDisconnect();\n }\n };\n\n eventSource.onmessage = (event: any) => {\n this.handleMessage(event);\n };\n\n // Listen for custom events\n this.setupSSEEventListeners(eventSource);\n\n this.eventSource = eventSource;\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private setupSSEEventListeners(eventSource: EventSourcePolyfill): void {\n // Generic event listener for all custom events\n const originalAddEventListener =\n eventSource.addEventListener.bind(eventSource);\n\n // Listen for common event types\n const eventTypes = [\n \"update\",\n \"step:complete\",\n \"execution:complete\",\n \"execution:failed\",\n ];\n\n eventTypes.forEach((type) => {\n originalAddEventListener(type, (event: any) => {\n this.handleMessage(event);\n });\n });\n }\n\n private async connectWebSocket(): Promise<void> {\n return new Promise((resolve, reject) => {\n try {\n // Derive WebSocket URL from SSE URL if not provided\n const wsUrl =\n this.config.wsUrl ?? this.deriveWebSocketUrl(this.config.sseUrl);\n\n const webSocket = new WebSocket(wsUrl);\n\n webSocket.onopen = () => {\n this.activeStrategy = \"websocket\";\n this.reconnectCount = 0;\n this.config.onConnect?.();\n resolve();\n };\n\n webSocket.onerror = () => {\n if (this.activeStrategy === null) {\n // Connection failed before opening\n this.closeWebSocket();\n reject(new Error(\"WebSocket connection failed\"));\n }\n };\n\n webSocket.onclose = () => {\n this.handleDisconnect();\n };\n\n webSocket.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data);\n this.config.onMessage?.({\n type: data.type ?? \"message\",\n data: data.data ?? data,\n });\n } catch (error) {\n this.config.onError?.(\n new Error(\n `Failed to parse WebSocket message: ${(error as Error).message}`,\n ),\n );\n }\n };\n\n this.webSocket = webSocket;\n } catch (error) {\n reject(error);\n }\n });\n }\n\n // ============================================================================\n // Private Methods - Event Handling\n // ============================================================================\n\n private handleMessage(event: any): void {\n try {\n const data =\n typeof event.data === \"string\" ? JSON.parse(event.data) : event.data;\n this.config.onMessage?.({\n type: event.type ?? \"message\",\n data,\n });\n } catch (error) {\n this.config.onError?.(\n new Error(`Failed to parse message: ${(error as Error).message}`),\n );\n }\n }\n\n private handleDisconnect(): void {\n this.config.onDisconnect?.();\n\n if (\n !this.isManualDisconnect &&\n this.reconnectCount < this.config.reconnectAttempts\n ) {\n this.attemptReconnect();\n } else if (!this.isManualDisconnect) {\n this.config.onError?.(new Error(\"Max reconnection attempts reached\"));\n }\n }\n\n private attemptReconnect(): void {\n this.reconnectCount++;\n const delay =\n this.config.reconnectDelayMs * Math.pow(2, this.reconnectCount - 1); // Exponential backoff\n\n this.reconnectTimeout = setTimeout(() => {\n if (this.activeStrategy === \"sse\") {\n this.closeSSE();\n this.connectSSE().catch(() => {\n this.handleDisconnect();\n });\n } else if (this.activeStrategy === \"websocket\") {\n this.closeWebSocket();\n this.connectWebSocket().catch(() => {\n this.handleDisconnect();\n });\n }\n }, delay);\n }\n\n // ============================================================================\n // Private Methods - Cleanup\n // ============================================================================\n\n private closeConnections(): void {\n this.closeSSE();\n this.closeWebSocket();\n }\n\n private closeSSE(): void {\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n }\n\n private closeWebSocket(): void {\n if (this.webSocket) {\n this.webSocket.close();\n this.webSocket = null;\n }\n }\n\n private clearTimeouts(): void {\n if (this.reconnectTimeout) {\n clearTimeout(this.reconnectTimeout);\n this.reconnectTimeout = null;\n }\n if (this.connectionTimeout) {\n clearTimeout(this.connectionTimeout);\n this.connectionTimeout = null;\n }\n }\n\n // ============================================================================\n // Private Methods - Utilities\n // ============================================================================\n\n private deriveWebSocketUrl(sseUrl: string): string {\n // Convert HTTP(S) URL to WS(S) URL\n const url = new URL(sseUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return url.toString();\n }\n}\n","// ============================================================================\n// Token Manager - Storage and auto-refresh for API tokens\n// ============================================================================\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Storage interface for tokens\n */\nexport interface TokenStorage {\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n remove(key: string): Promise<void>;\n}\n\n/**\n * Stored token format\n */\ninterface StoredToken {\n token: string;\n expiresAt: number; // Unix timestamp ms\n}\n\n/**\n * Token manager configuration\n */\nexport interface TokenManagerConfig {\n /**\n * Storage implementation\n * @default MemoryTokenStorage\n */\n storage?: TokenStorage;\n\n /**\n * Prefix for storage keys\n * @default 'stepflow_token_'\n */\n storageKeyPrefix?: string;\n\n /**\n * Refresh threshold in milliseconds before expiry\n * @default 300000 (5 minutes)\n */\n refreshThresholdMs?: number;\n\n /**\n * Callback when token is refreshed\n */\n onTokenRefresh?: (runId: string, newToken: string) => void;\n\n /**\n * Callback when token expires\n */\n onTokenExpired?: (runId: string) => void;\n}\n\n// ============================================================================\n// Storage Implementations\n// ============================================================================\n\n/**\n * In-memory token storage (default for SSR/testing)\n */\nexport class MemoryTokenStorage implements TokenStorage {\n private store = new Map<string, string>();\n\n async get(key: string): Promise<string | null> {\n return this.store.get(key) ?? null;\n }\n\n async set(key: string, value: string): Promise<void> {\n this.store.set(key, value);\n }\n\n async remove(key: string): Promise<void> {\n this.store.delete(key);\n }\n}\n\n/**\n * Browser localStorage wrapper\n */\nexport class LocalStorageTokenStorage implements TokenStorage {\n async get(key: string): Promise<string | null> {\n if (typeof window === \"undefined\" || !window.localStorage) {\n return null;\n }\n return window.localStorage.getItem(key);\n }\n\n async set(key: string, value: string): Promise<void> {\n if (typeof window === \"undefined\" || !window.localStorage) {\n return;\n }\n window.localStorage.setItem(key, value);\n }\n\n async remove(key: string): Promise<void> {\n if (typeof window === \"undefined\" || !window.localStorage) {\n return;\n }\n window.localStorage.removeItem(key);\n }\n}\n\n// ============================================================================\n// Token Manager\n// ============================================================================\n\n/**\n * Manages API tokens with storage and auto-refresh\n */\nexport class TokenManager {\n private storage: TokenStorage;\n private storageKeyPrefix: string;\n private refreshThresholdMs: number;\n private onTokenRefresh?: (runId: string, newToken: string) => void;\n private onTokenExpired?: (runId: string) => void;\n private refreshTimers = new Map<string, NodeJS.Timeout>();\n\n constructor(config?: TokenManagerConfig) {\n this.storage = config?.storage ?? new MemoryTokenStorage();\n this.storageKeyPrefix = config?.storageKeyPrefix ?? \"stepflow_token_\";\n this.refreshThresholdMs = config?.refreshThresholdMs ?? 300000; // 5 minutes\n this.onTokenRefresh = config?.onTokenRefresh;\n this.onTokenExpired = config?.onTokenExpired;\n }\n\n /**\n * Store a token with expiry\n */\n async storeToken(\n runId: string,\n token: string,\n expiresAt: Date,\n ): Promise<void> {\n const stored: StoredToken = {\n token,\n expiresAt: expiresAt.getTime(),\n };\n\n const key = this.getStorageKey(runId);\n await this.storage.set(key, JSON.stringify(stored));\n }\n\n /**\n * Get a token if it's still valid\n * @returns Token string or null if expired/not found\n */\n async getToken(runId: string): Promise<string | null> {\n const key = this.getStorageKey(runId);\n const value = await this.storage.get(key);\n\n if (!value) {\n return null;\n }\n\n try {\n const stored = JSON.parse(value) as StoredToken;\n const now = Date.now();\n\n // Check if expired\n if (stored.expiresAt <= now) {\n await this.removeToken(runId);\n return null;\n }\n\n return stored.token;\n } catch {\n // Invalid format, remove it\n await this.removeToken(runId);\n return null;\n }\n }\n\n /**\n * Remove a token from storage\n */\n async removeToken(runId: string): Promise<void> {\n const key = this.getStorageKey(runId);\n await this.storage.remove(key);\n this.stopAutoRefresh(runId);\n }\n\n /**\n * Check if a token is valid (exists and not expired)\n */\n async isTokenValid(runId: string): Promise<boolean> {\n const token = await this.getToken(runId);\n return token !== null;\n }\n\n /**\n * Get token expiry date\n * @returns Expiry date or null if not found\n */\n async getTokenExpiry(runId: string): Promise<Date | null> {\n const key = this.getStorageKey(runId);\n const value = await this.storage.get(key);\n\n if (!value) {\n return null;\n }\n\n try {\n const stored = JSON.parse(value) as StoredToken;\n return new Date(stored.expiresAt);\n } catch {\n return null;\n }\n }\n\n /**\n * Start auto-refresh for a token\n */\n startAutoRefresh(\n runId: string,\n refreshFn: () => Promise<{ token: string; expiresAt: Date }>,\n ): void {\n // Stop existing refresh if any\n this.stopAutoRefresh(runId);\n\n // Schedule refresh\n this.scheduleRefresh(runId, refreshFn);\n }\n\n /**\n * Stop auto-refresh for a specific token\n */\n stopAutoRefresh(runId: string): void {\n const timer = this.refreshTimers.get(runId);\n if (timer) {\n clearTimeout(timer);\n this.refreshTimers.delete(runId);\n }\n }\n\n /**\n * Stop all auto-refresh timers\n */\n stopAllAutoRefresh(): void {\n for (const timer of this.refreshTimers.values()) {\n clearTimeout(timer);\n }\n this.refreshTimers.clear();\n }\n\n /**\n * Schedule a refresh based on token expiry\n */\n private async scheduleRefresh(\n runId: string,\n refreshFn: () => Promise<{ token: string; expiresAt: Date }>,\n ): Promise<void> {\n const expiry = await this.getTokenExpiry(runId);\n if (!expiry) {\n return;\n }\n\n const now = Date.now();\n const expiryTime = expiry.getTime();\n const refreshTime = expiryTime - this.refreshThresholdMs;\n const delay = Math.max(0, refreshTime - now);\n\n const timer = setTimeout(async () => {\n try {\n // Attempt refresh\n const { token, expiresAt } = await refreshFn();\n\n // Store new token\n await this.storeToken(runId, token, expiresAt);\n\n // Notify callback\n this.onTokenRefresh?.(runId, token);\n\n // Schedule next refresh\n this.scheduleRefresh(runId, refreshFn);\n } catch (error) {\n // Refresh failed, notify expiry\n this.onTokenExpired?.(runId);\n this.stopAutoRefresh(runId);\n }\n }, delay);\n\n this.refreshTimers.set(runId, timer);\n }\n\n /**\n * Get storage key for a run ID\n */\n private getStorageKey(runId: string): string {\n return `${this.storageKeyPrefix}${runId}`;\n }\n}\n"],"mappings":"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","names":["EventSourcePolyfill","EventSourcePolyfill"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stepflowjs/client-ts",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "TypeScript client for Stepflow workflow orchestration",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"event-source-polyfill": "^1.0.31"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/event-source-polyfill": "^1.0.5",
|
|
23
|
+
"tsup": "^8.5.1",
|
|
24
|
+
"vitest": "^4.0.17"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"typescript": "^5.0.0"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "Stepflow Contributors",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://stepflow-production.up.railway.app",
|
|
34
|
+
"directory": "packages/client-ts"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://stepflow-production.up.railway.app",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://stepflow-production.up.railway.app"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"stepflow",
|
|
42
|
+
"workflow",
|
|
43
|
+
"orchestration",
|
|
44
|
+
"client",
|
|
45
|
+
"sdk",
|
|
46
|
+
"typescript"
|
|
47
|
+
],
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"test": "vitest",
|
|
56
|
+
"clean": "rm -rf dist"
|
|
57
|
+
}
|
|
58
|
+
}
|