@raisindb/client 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +281 -70
  2. package/dist/chunk-3KMOXRTK.mjs +1377 -0
  3. package/dist/chunk-3KMOXRTK.mjs.map +1 -0
  4. package/dist/chunk-B6EFDYFS.mjs +170 -0
  5. package/dist/chunk-B6EFDYFS.mjs.map +1 -0
  6. package/dist/chunk-P44XWTPA.mjs +190 -0
  7. package/dist/chunk-P44XWTPA.mjs.map +1 -0
  8. package/dist/chunk-Q4IBPW2W.mjs +866 -0
  9. package/dist/chunk-Q4IBPW2W.mjs.map +1 -0
  10. package/dist/chunk-QJKYNLT4.mjs +532 -0
  11. package/dist/chunk-QJKYNLT4.mjs.map +1 -0
  12. package/dist/chunk-T2ZSNYFG.mjs +248 -0
  13. package/dist/chunk-T2ZSNYFG.mjs.map +1 -0
  14. package/dist/conversation-list-store-S5MPSqoB.d.mts +6823 -0
  15. package/dist/conversation-list-store-S5MPSqoB.d.ts +6823 -0
  16. package/dist/index-3-fS4i8b.d.mts +301 -0
  17. package/dist/index-Bs7UkpRs.d.ts +301 -0
  18. package/dist/index.d.mts +66 -6108
  19. package/dist/index.d.ts +66 -6108
  20. package/dist/index.js +1601 -201
  21. package/dist/index.js.map +1 -0
  22. package/dist/index.mjs +1199 -3046
  23. package/dist/index.mjs.map +1 -0
  24. package/dist/react.d.mts +4 -0
  25. package/dist/react.d.ts +4 -0
  26. package/dist/react.js +2519 -0
  27. package/dist/react.js.map +1 -0
  28. package/dist/react.mjs +9 -0
  29. package/dist/react.mjs.map +1 -0
  30. package/dist/svelte-sql-BCuXrg8L.d.mts +52 -0
  31. package/dist/svelte-sql-DO1X-WO3.d.ts +52 -0
  32. package/dist/svelte.d.mts +288 -0
  33. package/dist/svelte.d.ts +288 -0
  34. package/dist/svelte.js +2432 -0
  35. package/dist/svelte.js.map +1 -0
  36. package/dist/svelte.mjs +25 -0
  37. package/dist/svelte.mjs.map +1 -0
  38. package/dist/vue.d.mts +197 -0
  39. package/dist/vue.d.ts +197 -0
  40. package/dist/vue.js +1460 -0
  41. package/dist/vue.js.map +1 -0
  42. package/dist/vue.mjs +9 -0
  43. package/dist/vue.mjs.map +1 -0
  44. package/package.json +18 -3
@@ -0,0 +1,866 @@
1
+ import {
2
+ ReconnectManager,
3
+ logger
4
+ } from "./chunk-3KMOXRTK.mjs";
5
+
6
+ // src/errors.ts
7
+ var RaisinError = class extends Error {
8
+ constructor(message, code, details) {
9
+ super(message);
10
+ this.name = "RaisinError";
11
+ this.code = code;
12
+ this.details = details;
13
+ if (Error.captureStackTrace) {
14
+ Error.captureStackTrace(this, this.constructor);
15
+ }
16
+ }
17
+ };
18
+ var RaisinConnectionError = class extends RaisinError {
19
+ constructor(message, code, options) {
20
+ super(message, code, options?.details);
21
+ this.name = "RaisinConnectionError";
22
+ this.status = options?.status;
23
+ }
24
+ };
25
+ var RaisinAuthError = class extends RaisinError {
26
+ constructor(message, code, status, details) {
27
+ super(message, code, details);
28
+ this.name = "RaisinAuthError";
29
+ this.status = status;
30
+ }
31
+ };
32
+ var RaisinFlowError = class extends RaisinError {
33
+ constructor(message, code, instanceId, details) {
34
+ super(message, code, details);
35
+ this.name = "RaisinFlowError";
36
+ this.instanceId = instanceId;
37
+ }
38
+ };
39
+ var RaisinTimeoutError = class extends RaisinError {
40
+ constructor(message, code, timeoutMs, details) {
41
+ super(message, code, details);
42
+ this.name = "RaisinTimeoutError";
43
+ this.timeoutMs = timeoutMs;
44
+ }
45
+ };
46
+ var RaisinAbortError = class extends RaisinError {
47
+ constructor(message = "Operation was aborted") {
48
+ super(message, "ABORTED");
49
+ this.name = "RaisinAbortError";
50
+ }
51
+ };
52
+ function classifyHttpError(status, message, details) {
53
+ if (status === 401) {
54
+ return new RaisinAuthError(message, "AUTH_UNAUTHORIZED", status, details);
55
+ }
56
+ if (status === 403) {
57
+ return new RaisinAuthError(message, "AUTH_FORBIDDEN", status, details);
58
+ }
59
+ if (status === 404) {
60
+ return new RaisinFlowError(message, "FLOW_NOT_FOUND", void 0, details);
61
+ }
62
+ return new RaisinConnectionError(message, "CONNECTION_FAILED", { status, details });
63
+ }
64
+
65
+ // src/streaming/sse-client.ts
66
+ function parseSSEBlock(block) {
67
+ let event = "message";
68
+ let data = "";
69
+ let id;
70
+ const lines = block.split("\n");
71
+ for (const line of lines) {
72
+ if (line.startsWith("event:")) {
73
+ event = line.slice(6).trim();
74
+ } else if (line.startsWith("data:")) {
75
+ if (data) data += "\n";
76
+ data += line.slice(5).trim();
77
+ } else if (line.startsWith("id:")) {
78
+ id = line.slice(3).trim();
79
+ }
80
+ }
81
+ if (!data) return null;
82
+ return { event, data, id };
83
+ }
84
+ var SSEClient = class {
85
+ constructor(url, options = {}) {
86
+ this.abortController = null;
87
+ this._state = "disconnected";
88
+ this.url = url;
89
+ this.options = options;
90
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
91
+ this._lastEventId = options.lastEventId;
92
+ this.autoReconnect = options.reconnect?.enabled ?? true;
93
+ this.externalSignal = options.signal;
94
+ this.inactivityTimeoutMs = options.inactivityTimeoutMs;
95
+ this.eventTypeSet = options.eventTypes?.length ? new Set(options.eventTypes) : null;
96
+ this.reconnectManager = new ReconnectManager({
97
+ initialDelay: options.reconnect?.initialDelay ?? 1e3,
98
+ maxDelay: options.reconnect?.maxDelay ?? 3e4,
99
+ backoffMultiplier: options.reconnect?.backoffMultiplier ?? 2,
100
+ maxAttempts: options.reconnect?.maxAttempts ?? Infinity
101
+ });
102
+ }
103
+ /** Current connection state */
104
+ get state() {
105
+ return this._state;
106
+ }
107
+ /**
108
+ * Returns a promise that resolves once the SSE connection is established.
109
+ *
110
+ * Useful for ensuring the connection is ready before triggering events
111
+ * on the server side (e.g., calling resume before consuming the stream).
112
+ * If the client is already connected, resolves immediately.
113
+ */
114
+ waitUntilConnected() {
115
+ if (this._state === "connected") return Promise.resolve();
116
+ return new Promise((resolve, reject) => {
117
+ const originalOnState = this.options.onStateChange;
118
+ this.options.onStateChange = (state) => {
119
+ originalOnState?.(state);
120
+ if (state === "connected") {
121
+ this.options.onStateChange = originalOnState;
122
+ resolve();
123
+ } else if (state === "disconnected") {
124
+ this.options.onStateChange = originalOnState;
125
+ reject(new Error("SSE connection failed"));
126
+ }
127
+ };
128
+ });
129
+ }
130
+ /** Last received event ID */
131
+ get lastEventId() {
132
+ return this._lastEventId;
133
+ }
134
+ setState(state) {
135
+ if (this._state !== state) {
136
+ this._state = state;
137
+ this.options.onStateChange?.(state);
138
+ }
139
+ }
140
+ /**
141
+ * Connect and consume events via callback.
142
+ *
143
+ * @param onEvent - Called for each matching SSE event
144
+ * @param onError - Called when an error occurs (optional)
145
+ */
146
+ async connect(onEvent, onError) {
147
+ this.close();
148
+ await this.startStream(onEvent, onError);
149
+ }
150
+ /**
151
+ * Close the SSE connection and stop reconnection attempts.
152
+ */
153
+ close() {
154
+ this.reconnectManager.cancelReconnect();
155
+ if (this.abortController) {
156
+ this.abortController.abort();
157
+ this.abortController = null;
158
+ }
159
+ this.setState("disconnected");
160
+ }
161
+ /**
162
+ * AsyncIterable interface: allows `for await (const event of sseClient)`.
163
+ *
164
+ * The iterator ends when close() is called or the server closes the stream
165
+ * (and auto-reconnect is disabled or max attempts are exhausted).
166
+ */
167
+ async *[Symbol.asyncIterator]() {
168
+ const queue = [];
169
+ let resolve = null;
170
+ let finished = false;
171
+ const push = (item) => {
172
+ if (finished) return;
173
+ if (resolve) {
174
+ const r = resolve;
175
+ resolve = null;
176
+ r(item);
177
+ } else {
178
+ queue.push(item);
179
+ }
180
+ };
181
+ const pull = () => {
182
+ const queued = queue.shift();
183
+ if (queued) return Promise.resolve(queued);
184
+ return new Promise((r) => {
185
+ resolve = r;
186
+ });
187
+ };
188
+ this.startStream(
189
+ (event) => push({ done: false, value: event }),
190
+ (error) => push({ done: true, error })
191
+ ).then(() => {
192
+ push({ done: true });
193
+ }).catch((error) => {
194
+ push({ done: true, error });
195
+ });
196
+ try {
197
+ while (!finished) {
198
+ const item = await pull();
199
+ if (item.done) {
200
+ finished = true;
201
+ if (item.error) {
202
+ throw item.error;
203
+ }
204
+ return;
205
+ }
206
+ yield item.value;
207
+ }
208
+ } finally {
209
+ finished = true;
210
+ this.close();
211
+ }
212
+ }
213
+ /**
214
+ * Core streaming loop: connects, reads the SSE stream, and handles reconnection.
215
+ */
216
+ async startStream(onEvent, onError) {
217
+ while (true) {
218
+ try {
219
+ await this.readStream(onEvent);
220
+ if (!this.autoReconnect || this._state === "disconnected") {
221
+ return;
222
+ }
223
+ this.setState("reconnecting");
224
+ const scheduled = await this.waitForReconnect();
225
+ if (!scheduled) {
226
+ logger.warn("SSE max reconnection attempts reached");
227
+ this.setState("disconnected");
228
+ return;
229
+ }
230
+ } catch (error) {
231
+ if (this._state === "disconnected") {
232
+ return;
233
+ }
234
+ const err = error instanceof Error ? error : new Error(String(error));
235
+ if (err.name === "AbortError" || err instanceof RaisinAbortError) {
236
+ return;
237
+ }
238
+ if (err instanceof RaisinTimeoutError && err.code === "SSE_INACTIVITY_TIMEOUT") {
239
+ logger.warn("SSE inactivity timeout:", err.message);
240
+ if (!this.autoReconnect) {
241
+ this.setState("disconnected");
242
+ onError?.(err);
243
+ throw err;
244
+ }
245
+ this.setState("reconnecting");
246
+ const scheduled2 = await this.waitForReconnect();
247
+ if (!scheduled2) {
248
+ logger.warn("SSE max reconnection attempts reached");
249
+ this.setState("disconnected");
250
+ onError?.(err);
251
+ throw err;
252
+ }
253
+ continue;
254
+ }
255
+ logger.error("SSE connection error:", err.message);
256
+ onError?.(err);
257
+ if (!this.autoReconnect) {
258
+ this.setState("disconnected");
259
+ throw err;
260
+ }
261
+ this.setState("reconnecting");
262
+ const scheduled = await this.waitForReconnect();
263
+ if (!scheduled) {
264
+ logger.warn("SSE max reconnection attempts reached");
265
+ this.setState("disconnected");
266
+ throw err;
267
+ }
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * Perform a single fetch and read the response body as SSE stream.
273
+ */
274
+ async readStream(onEvent) {
275
+ this.abortController = new AbortController();
276
+ this.setState("connecting");
277
+ if (this.externalSignal) {
278
+ if (this.externalSignal.aborted) {
279
+ this.abortController.abort();
280
+ } else {
281
+ const ctrl = this.abortController;
282
+ this.externalSignal.addEventListener("abort", () => ctrl.abort(), { once: true });
283
+ }
284
+ }
285
+ const headers = {
286
+ Accept: "text/event-stream",
287
+ "Cache-Control": "no-cache",
288
+ ...this.options.headers
289
+ };
290
+ if (this._lastEventId) {
291
+ headers["Last-Event-ID"] = this._lastEventId;
292
+ }
293
+ const method = this.options.method ?? "GET";
294
+ const fetchOptions = {
295
+ method,
296
+ headers,
297
+ signal: this.abortController.signal
298
+ };
299
+ if (method === "POST" && this.options.body !== void 0) {
300
+ fetchOptions.headers["Content-Type"] = "application/json";
301
+ fetchOptions.body = JSON.stringify(this.options.body);
302
+ }
303
+ const response = await this.withInactivityTimeout(
304
+ this.fetchImpl(this.url, fetchOptions)
305
+ );
306
+ if (!response.ok) {
307
+ const text = await response.text().catch(() => "");
308
+ const msg = `SSE connection failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`;
309
+ if (response.status === 401 || response.status === 403) {
310
+ throw new RaisinAuthError(
311
+ msg,
312
+ response.status === 401 ? "AUTH_UNAUTHORIZED" : "AUTH_FORBIDDEN",
313
+ response.status
314
+ );
315
+ }
316
+ throw new RaisinConnectionError(msg, "SSE_STREAM_ERROR", { status: response.status });
317
+ }
318
+ if (!response.body) {
319
+ throw new RaisinConnectionError("SSE response has no body", "SSE_STREAM_ERROR");
320
+ }
321
+ this.setState("connected");
322
+ this.reconnectManager.reset();
323
+ const reader = response.body.getReader();
324
+ const decoder = new TextDecoder();
325
+ let buffer = "";
326
+ try {
327
+ while (true) {
328
+ const { done, value } = await this.withInactivityTimeout(reader.read());
329
+ if (done) break;
330
+ buffer += decoder.decode(value, { stream: true });
331
+ const blocks = buffer.split("\n\n");
332
+ buffer = blocks.pop() ?? "";
333
+ for (const block of blocks) {
334
+ const trimmed = block.trim();
335
+ if (!trimmed) continue;
336
+ const parsed = parseSSEBlock(trimmed);
337
+ if (!parsed) continue;
338
+ if (parsed.id) {
339
+ this._lastEventId = parsed.id;
340
+ }
341
+ if (this.eventTypeSet && !this.eventTypeSet.has(parsed.event)) {
342
+ continue;
343
+ }
344
+ let data;
345
+ try {
346
+ data = JSON.parse(parsed.data);
347
+ } catch {
348
+ data = parsed.data;
349
+ }
350
+ const sseEvent = {
351
+ type: parsed.event,
352
+ data,
353
+ id: parsed.id
354
+ };
355
+ onEvent(sseEvent);
356
+ }
357
+ }
358
+ } finally {
359
+ try {
360
+ reader.releaseLock();
361
+ } catch {
362
+ }
363
+ }
364
+ }
365
+ /**
366
+ * Race a stream operation against the inactivity timeout.
367
+ *
368
+ * If no bytes (or response) arrive within `inactivityTimeoutMs`, the
369
+ * internal connection is aborted and the operation rejects with a
370
+ * `RaisinTimeoutError` (code `SSE_INACTIVITY_TIMEOUT`). The timer is
371
+ * effectively reset on every chunk because each `reader.read()` is
372
+ * wrapped individually.
373
+ */
374
+ withInactivityTimeout(promise) {
375
+ const timeoutMs = this.inactivityTimeoutMs;
376
+ if (!timeoutMs || timeoutMs <= 0) return promise;
377
+ return new Promise((resolve, reject) => {
378
+ const timer = setTimeout(() => {
379
+ promise.catch(() => void 0);
380
+ this.abortController?.abort();
381
+ reject(
382
+ new RaisinTimeoutError(
383
+ `SSE stream inactive for ${timeoutMs}ms`,
384
+ "SSE_INACTIVITY_TIMEOUT",
385
+ timeoutMs
386
+ )
387
+ );
388
+ }, timeoutMs);
389
+ promise.then(
390
+ (value) => {
391
+ clearTimeout(timer);
392
+ resolve(value);
393
+ },
394
+ (error) => {
395
+ clearTimeout(timer);
396
+ reject(error);
397
+ }
398
+ );
399
+ });
400
+ }
401
+ /**
402
+ * Wait for the reconnect manager to schedule a reconnection attempt.
403
+ * Returns true if reconnection was scheduled, false if max attempts reached.
404
+ */
405
+ waitForReconnect() {
406
+ return new Promise((resolve) => {
407
+ const scheduled = this.reconnectManager.scheduleReconnect(() => {
408
+ resolve(true);
409
+ });
410
+ if (!scheduled) {
411
+ resolve(false);
412
+ }
413
+ });
414
+ }
415
+ };
416
+
417
+ // src/flow-client.ts
418
+ var FlowClient = class _FlowClient {
419
+ /**
420
+ * Create a new FlowClient.
421
+ *
422
+ * @param baseUrl - Base HTTP URL of the RaisinDB server (e.g., "http://localhost:8081")
423
+ * @param repository - Repository name
424
+ * @param authManager - Auth manager for token access
425
+ * @param options - Additional options
426
+ * @param flowsApi - Optional FlowsApi for WebSocket-based flow operations
427
+ */
428
+ constructor(baseUrl, repository, authManager, options = {}, flowsApi) {
429
+ this.baseUrl = baseUrl.replace(/\/$/, "");
430
+ this.repository = repository;
431
+ this.authManager = authManager;
432
+ this.fetchImpl = options.fetch ?? fetch;
433
+ this.requestTimeout = options.requestTimeout ?? 6e4;
434
+ this.flowsApi = flowsApi;
435
+ }
436
+ /**
437
+ * Create a FlowClient from an existing RaisinHttpClient.
438
+ *
439
+ * @param httpClient - An authenticated RaisinHttpClient instance
440
+ * @param baseUrl - Base HTTP URL (same as the one used for the HTTP client)
441
+ * @param repository - Repository name
442
+ * @param options - Additional options
443
+ */
444
+ static fromHttpClient(httpClient, baseUrl, repository, options = {}, flowsApi) {
445
+ return new _FlowClient(
446
+ baseUrl,
447
+ repository,
448
+ httpClient.getAuthManager(),
449
+ options,
450
+ flowsApi
451
+ );
452
+ }
453
+ // ==========================================================================
454
+ // Flow Execution
455
+ // ==========================================================================
456
+ /**
457
+ * Execute a flow by path.
458
+ *
459
+ * Creates a FlowInstance and queues it for execution. Returns immediately
460
+ * with the instance ID - use `streamEvents()` to monitor progress.
461
+ *
462
+ * @param flowPath - Path to the raisin:Flow node (e.g., "/flows/my-flow")
463
+ * @param input - Input data passed to the flow
464
+ * @returns Flow run response with instance_id and job_id
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * const result = await flows.run('/flows/process-order', {
469
+ * orderId: 'ord-123',
470
+ * items: [{ sku: 'ABC', qty: 2 }],
471
+ * });
472
+ * console.log('Instance:', result.instance_id);
473
+ * ```
474
+ */
475
+ async run(flowPath, input = {}, options) {
476
+ if (this.flowsApi) {
477
+ return this.flowsApi.run(flowPath, input);
478
+ }
479
+ const response = await this.request({
480
+ method: "POST",
481
+ path: `/api/flows/${this.repository}/run`,
482
+ body: { flow_path: flowPath, input },
483
+ signal: options?.signal
484
+ });
485
+ return response;
486
+ }
487
+ // ==========================================================================
488
+ // Instance Status
489
+ // ==========================================================================
490
+ /**
491
+ * Get the current status of a flow instance.
492
+ *
493
+ * Returns the instance status, variables, and metadata. Useful for
494
+ * polling-based approaches where SSE is not reliable (e.g., initial
495
+ * flow execution where events may be missed).
496
+ *
497
+ * @param instanceId - Flow instance ID (from `run()`)
498
+ * @returns Instance status including variables and flow path
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * const status = await flows.getInstanceStatus('instance-123');
503
+ * console.log('Status:', status.status); // 'waiting', 'completed', etc.
504
+ * ```
505
+ */
506
+ async getInstanceStatus(instanceId, options) {
507
+ if (this.flowsApi) {
508
+ return this.flowsApi.getInstanceStatus(instanceId);
509
+ }
510
+ return this.request({
511
+ method: "GET",
512
+ path: `/api/flows/${this.repository}/instances/${instanceId}`,
513
+ signal: options?.signal
514
+ });
515
+ }
516
+ // ==========================================================================
517
+ // Event Streaming (SSE)
518
+ // ==========================================================================
519
+ /**
520
+ * Stream real-time execution events for a flow instance via SSE.
521
+ *
522
+ * Returns an async iterable that yields FlowExecutionEvent objects.
523
+ * The stream automatically closes when a terminal event is received
524
+ * (flow_completed or flow_failed).
525
+ *
526
+ * @param instanceId - Flow instance ID (from `run()`)
527
+ * @returns Async iterable of flow execution events
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * const { instance_id } = await flows.run('/flows/my-flow', { key: 'value' });
532
+ *
533
+ * for await (const event of flows.streamEvents(instance_id)) {
534
+ * switch (event.type) {
535
+ * case 'step_started':
536
+ * console.log(`Step ${event.node_id} started`);
537
+ * break;
538
+ * case 'step_completed':
539
+ * console.log(`Step ${event.node_id} completed in ${event.duration_ms}ms`);
540
+ * break;
541
+ * case 'text_chunk':
542
+ * process.stdout.write(event.text);
543
+ * break;
544
+ * case 'flow_completed':
545
+ * console.log('Flow done:', event.output);
546
+ * break;
547
+ * case 'flow_failed':
548
+ * console.error('Flow failed:', event.error);
549
+ * break;
550
+ * }
551
+ * }
552
+ * ```
553
+ */
554
+ async *streamEvents(instanceId, options) {
555
+ if (this.flowsApi) {
556
+ const sub = await this.flowsApi.subscribeEvents(instanceId);
557
+ try {
558
+ for await (const event of sub.events) {
559
+ if (options?.signal?.aborted) throw new RaisinAbortError();
560
+ yield event;
561
+ if (event.type === "flow_completed" || event.type === "flow_failed") {
562
+ return;
563
+ }
564
+ }
565
+ } finally {
566
+ await sub.unsubscribe();
567
+ }
568
+ return;
569
+ }
570
+ const url = `${this.baseUrl}/api/flows/${this.repository}/instances/${instanceId}/events`;
571
+ const headers = {};
572
+ const token = this.authManager.getAccessToken();
573
+ if (token) {
574
+ headers["Authorization"] = `Bearer ${token}`;
575
+ }
576
+ const sse = new SSEClient(url, {
577
+ headers,
578
+ eventTypes: ["flow-event", "message"],
579
+ reconnect: { enabled: false },
580
+ fetch: this.fetchImpl,
581
+ signal: options?.signal
582
+ });
583
+ try {
584
+ for await (const sseEvent of sse) {
585
+ const event = sseEvent.data;
586
+ yield event;
587
+ if (event.type === "flow_completed" || event.type === "flow_failed") {
588
+ return;
589
+ }
590
+ }
591
+ } finally {
592
+ sse.close();
593
+ }
594
+ }
595
+ /**
596
+ * Create an eagerly-connected event stream for a flow instance.
597
+ *
598
+ * Unlike `streamEvents()` (a lazy async generator), this method opens the
599
+ * SSE connection immediately and waits until it is established before
600
+ * returning. This is critical for resume flows: you must subscribe to
601
+ * events BEFORE calling `resume()`, otherwise events emitted between
602
+ * the resume and the SSE connection are lost.
603
+ *
604
+ * @param instanceId - Flow instance ID
605
+ * @returns Object with an async iterable `events` and a `close()` method
606
+ */
607
+ async createEventStream(instanceId, options) {
608
+ if (this.flowsApi) {
609
+ const sub = await this.flowsApi.subscribeEvents(instanceId);
610
+ return {
611
+ events: sub.events,
612
+ close: () => {
613
+ sub.unsubscribe();
614
+ }
615
+ };
616
+ }
617
+ const url = `${this.baseUrl}/api/flows/${this.repository}/instances/${instanceId}/events`;
618
+ const headers = {};
619
+ const token = this.authManager.getAccessToken();
620
+ if (token) {
621
+ headers["Authorization"] = `Bearer ${token}`;
622
+ }
623
+ const sse = new SSEClient(url, {
624
+ headers,
625
+ eventTypes: ["flow-event", "message"],
626
+ reconnect: { enabled: false },
627
+ fetch: this.fetchImpl,
628
+ signal: options?.signal
629
+ });
630
+ const iterator = sse[Symbol.asyncIterator]();
631
+ let pendingFirst = iterator.next();
632
+ await sse.waitUntilConnected();
633
+ const events = {
634
+ [Symbol.asyncIterator]() {
635
+ return {
636
+ async next() {
637
+ let result;
638
+ if (pendingFirst) {
639
+ result = await pendingFirst;
640
+ pendingFirst = null;
641
+ } else {
642
+ result = await iterator.next();
643
+ }
644
+ if (result.done) return { done: true, value: void 0 };
645
+ const event = result.value.data;
646
+ return { done: false, value: event };
647
+ },
648
+ async return() {
649
+ await iterator.return?.();
650
+ sse.close();
651
+ return { done: true, value: void 0 };
652
+ }
653
+ };
654
+ }
655
+ };
656
+ return { events, close: () => sse.close() };
657
+ }
658
+ // ==========================================================================
659
+ // Flow Resume & Human Task
660
+ // ==========================================================================
661
+ /**
662
+ * Resume a flow instance that is in a waiting state.
663
+ *
664
+ * Use this when a flow is waiting for external input (tool results,
665
+ * external events, etc.). The resume data is passed to the waiting step.
666
+ *
667
+ * @param instanceId - Flow instance ID
668
+ * @param data - Resume data to pass to the waiting step
669
+ *
670
+ * @example
671
+ * ```typescript
672
+ * await flows.resume('instance-123', {
673
+ * tool_name: 'get_weather',
674
+ * result: { temperature: 72, conditions: 'sunny' },
675
+ * });
676
+ * ```
677
+ */
678
+ async resume(instanceId, data, options) {
679
+ if (this.flowsApi) {
680
+ await this.flowsApi.resume(instanceId, data);
681
+ return;
682
+ }
683
+ await this.request({
684
+ method: "POST",
685
+ path: `/api/flows/${this.repository}/instances/${instanceId}/resume`,
686
+ body: { resume_data: data },
687
+ signal: options?.signal
688
+ });
689
+ }
690
+ /**
691
+ * Respond to a human task within a flow.
692
+ *
693
+ * When a flow is waiting on a human_task step (approval, input, review),
694
+ * use this method to submit the response. Completion is validated
695
+ * (caller must be the task's assignee or an admin) and resumes the flow.
696
+ *
697
+ * Note: prefer `InboxApi` for full inbox functionality (listing tasks,
698
+ * task details); this is a convenience passthrough to the same endpoint.
699
+ *
700
+ * @param instanceId - Flow instance ID (unused; kept for API stability)
701
+ * @param taskId - The inbox task node ID (or path)
702
+ * @param response - The response data
703
+ *
704
+ * @example
705
+ * ```typescript
706
+ * // Approve a task
707
+ * await flows.respondToHumanTask('instance-123', 'task-abc', {
708
+ * action: 'approve',
709
+ * comment: 'Looks good!',
710
+ * });
711
+ * ```
712
+ */
713
+ async respondToHumanTask(_instanceId, taskId, response, options) {
714
+ await this.request({
715
+ method: "POST",
716
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
717
+ body: { response },
718
+ signal: options?.signal
719
+ });
720
+ }
721
+ // ==========================================================================
722
+ // Convenience Methods
723
+ // ==========================================================================
724
+ /**
725
+ * Run a flow and wait for the final result.
726
+ *
727
+ * Starts the flow, streams all events, and returns the terminal outcome.
728
+ * This is a one-shot helper for fire-and-forget flows where you only
729
+ * care about the final output.
730
+ *
731
+ * @param flowPath - Path to the raisin:Flow node
732
+ * @param input - Input data passed to the flow
733
+ * @param options - Optional abort signal
734
+ * @returns The flow outcome including output or error
735
+ *
736
+ * @example
737
+ * ```typescript
738
+ * const result = await flows.runAndWait('/flows/process-order', { orderId: '123' });
739
+ * if (result.status === 'completed') {
740
+ * console.log('Output:', result.output);
741
+ * }
742
+ * ```
743
+ */
744
+ async runAndWait(flowPath, input = {}, options) {
745
+ const { instance_id } = await this.run(flowPath, input, options);
746
+ for await (const event of this.streamEvents(instance_id, options)) {
747
+ if (event.type === "flow_completed") {
748
+ return {
749
+ instanceId: instance_id,
750
+ status: "completed",
751
+ output: event.output
752
+ };
753
+ }
754
+ if (event.type === "flow_failed") {
755
+ return {
756
+ instanceId: instance_id,
757
+ status: "failed",
758
+ error: event.error
759
+ };
760
+ }
761
+ }
762
+ return { instanceId: instance_id, status: "failed", error: "Stream ended without terminal event" };
763
+ }
764
+ /**
765
+ * Run a flow and collect all events into an array.
766
+ *
767
+ * Starts the flow, streams all events, and returns them as a collected array.
768
+ * Useful for testing, logging, or post-processing all flow events.
769
+ *
770
+ * @param flowPath - Path to the raisin:Flow node
771
+ * @param input - Input data passed to the flow
772
+ * @param options - Optional abort signal
773
+ * @returns The instance ID and all collected events
774
+ *
775
+ * @example
776
+ * ```typescript
777
+ * const { events } = await flows.runAndCollect('/flows/etl-pipeline', { source: 'db' });
778
+ * const steps = events.filter(e => e.type === 'step_completed');
779
+ * console.log(`Completed ${steps.length} steps`);
780
+ * ```
781
+ */
782
+ async runAndCollect(flowPath, input = {}, options) {
783
+ const { instance_id } = await this.run(flowPath, input, options);
784
+ const events = [];
785
+ for await (const event of this.streamEvents(instance_id, options)) {
786
+ events.push(event);
787
+ }
788
+ return { instanceId: instance_id, events };
789
+ }
790
+ // ==========================================================================
791
+ // Internal HTTP request helper
792
+ // ==========================================================================
793
+ async request(options) {
794
+ const url = `${this.baseUrl}${options.path}`;
795
+ const headers = {
796
+ "Content-Type": "application/json"
797
+ };
798
+ const token = this.authManager.getAccessToken();
799
+ if (token) {
800
+ headers["Authorization"] = `Bearer ${token}`;
801
+ }
802
+ const controller = new AbortController();
803
+ const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
804
+ if (options.signal) {
805
+ if (options.signal.aborted) {
806
+ clearTimeout(timeoutId);
807
+ throw new RaisinAbortError();
808
+ }
809
+ options.signal.addEventListener("abort", () => controller.abort(), { once: true });
810
+ }
811
+ try {
812
+ const response = await this.fetchImpl(url, {
813
+ method: options.method,
814
+ headers,
815
+ body: options.body ? JSON.stringify(options.body) : void 0,
816
+ signal: controller.signal
817
+ });
818
+ clearTimeout(timeoutId);
819
+ if (!response.ok) {
820
+ const errorText = await response.text();
821
+ let errorMessage;
822
+ try {
823
+ const errorJson = JSON.parse(errorText);
824
+ errorMessage = errorJson.message || errorJson.error || errorText;
825
+ } catch {
826
+ errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
827
+ }
828
+ throw classifyHttpError(response.status, errorMessage);
829
+ }
830
+ const contentType = response.headers.get("content-type");
831
+ if (contentType?.includes("application/json")) {
832
+ return await response.json();
833
+ }
834
+ return void 0;
835
+ } catch (error) {
836
+ clearTimeout(timeoutId);
837
+ if (error instanceof RaisinAbortError || error instanceof RaisinTimeoutError) {
838
+ throw error;
839
+ }
840
+ if (error instanceof Error && error.name === "AbortError") {
841
+ if (options.signal?.aborted) {
842
+ throw new RaisinAbortError();
843
+ }
844
+ throw new RaisinTimeoutError(
845
+ `Request timeout after ${this.requestTimeout}ms`,
846
+ "REQUEST_TIMEOUT",
847
+ this.requestTimeout
848
+ );
849
+ }
850
+ throw error;
851
+ }
852
+ }
853
+ };
854
+
855
+ export {
856
+ RaisinError,
857
+ RaisinConnectionError,
858
+ RaisinAuthError,
859
+ RaisinFlowError,
860
+ RaisinTimeoutError,
861
+ RaisinAbortError,
862
+ classifyHttpError,
863
+ SSEClient,
864
+ FlowClient
865
+ };
866
+ //# sourceMappingURL=chunk-Q4IBPW2W.mjs.map