@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
package/dist/index.mjs CHANGED
@@ -1,366 +1,57 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
1
+ import {
2
+ createRaisinReact,
3
+ useConversation,
4
+ useConversationList,
5
+ useFlow
6
+ } from "./chunk-QJKYNLT4.mjs";
7
+ import {
8
+ RAISIN_CONTEXT_KEY,
9
+ createConversationAdapter,
10
+ createConversationListAdapter,
11
+ createFlowAdapter
12
+ } from "./chunk-P44XWTPA.mjs";
13
+ import {
14
+ FlowClient,
15
+ RaisinAbortError,
16
+ RaisinAuthError,
17
+ RaisinConnectionError,
18
+ RaisinError,
19
+ RaisinFlowError,
20
+ RaisinTimeoutError,
21
+ SSEClient,
22
+ classifyHttpError
23
+ } from "./chunk-Q4IBPW2W.mjs";
24
+ import {
25
+ createRaisinVue
26
+ } from "./chunk-B6EFDYFS.mjs";
27
+ import {
28
+ createAuthAdapter,
29
+ createConnectionAdapter,
30
+ createSqlAdapter,
31
+ createSubscriptionAdapter
32
+ } from "./chunk-T2ZSNYFG.mjs";
33
+ import {
34
+ Connection,
35
+ ConnectionState,
36
+ ConversationListStore,
37
+ ConversationStore,
38
+ LogLevel,
39
+ ReconnectManager,
40
+ __privateAdd,
41
+ __privateGet,
42
+ __privateSet,
43
+ configureLogger,
44
+ getLogLevel,
45
+ getLoggerConfig,
46
+ isRecoveredDoneEvent,
47
+ logger,
48
+ normalizeHomePath,
49
+ projectPlansFromMessages,
50
+ setLogLevel
51
+ } from "./chunk-3KMOXRTK.mjs";
7
52
 
8
53
  // src/client.ts
9
- import { EventEmitter as EventEmitter3 } from "events";
10
-
11
- // src/connection.ts
12
- import { EventEmitter } from "events";
13
-
14
- // src/utils/reconnect.ts
15
- var ReconnectManager = class {
16
- constructor(options = {}) {
17
- this.attempts = 0;
18
- this.isReconnecting = false;
19
- this.initialDelay = options.initialDelay ?? 1e3;
20
- this.maxDelay = options.maxDelay ?? 3e4;
21
- this.backoffMultiplier = options.backoffMultiplier ?? 2;
22
- this.maxAttempts = options.maxAttempts ?? Infinity;
23
- this.jitter = options.jitter ?? true;
24
- this.currentDelay = this.initialDelay;
25
- }
26
- /**
27
- * Schedule a reconnection attempt
28
- *
29
- * @param callback - Function to call when it's time to reconnect
30
- * @returns true if reconnection was scheduled, false if max attempts reached
31
- */
32
- scheduleReconnect(callback) {
33
- if (this.attempts >= this.maxAttempts) {
34
- return false;
35
- }
36
- this.cancelReconnect();
37
- this.isReconnecting = true;
38
- this.attempts++;
39
- let delay = this.currentDelay;
40
- if (this.jitter) {
41
- const jitterRange = delay * 0.25;
42
- delay += (Math.random() * 2 - 1) * jitterRange;
43
- delay = Math.max(0, Math.round(delay));
44
- }
45
- this.reconnectTimeout = setTimeout(async () => {
46
- try {
47
- await callback();
48
- this.reset();
49
- } catch (error) {
50
- this.currentDelay = Math.min(
51
- this.currentDelay * this.backoffMultiplier,
52
- this.maxDelay
53
- );
54
- }
55
- }, delay);
56
- return true;
57
- }
58
- /**
59
- * Cancel any pending reconnection attempt
60
- */
61
- cancelReconnect() {
62
- if (this.reconnectTimeout) {
63
- clearTimeout(this.reconnectTimeout);
64
- this.reconnectTimeout = void 0;
65
- }
66
- this.isReconnecting = false;
67
- }
68
- /**
69
- * Reset the reconnection state (call after successful connection)
70
- */
71
- reset() {
72
- this.cancelReconnect();
73
- this.currentDelay = this.initialDelay;
74
- this.attempts = 0;
75
- }
76
- /**
77
- * Check if currently in reconnection mode
78
- */
79
- isActive() {
80
- return this.isReconnecting;
81
- }
82
- /**
83
- * Get the current number of reconnection attempts
84
- */
85
- getAttempts() {
86
- return this.attempts;
87
- }
88
- /**
89
- * Get the current delay in milliseconds
90
- */
91
- getCurrentDelay() {
92
- return this.currentDelay;
93
- }
94
- /**
95
- * Check if max attempts have been reached
96
- */
97
- hasReachedMaxAttempts() {
98
- return this.attempts >= this.maxAttempts;
99
- }
100
- };
101
-
102
- // src/logger.ts
103
- var LogLevel = /* @__PURE__ */ ((LogLevel3) => {
104
- LogLevel3[LogLevel3["Silent"] = 0] = "Silent";
105
- LogLevel3[LogLevel3["Error"] = 1] = "Error";
106
- LogLevel3[LogLevel3["Warn"] = 2] = "Warn";
107
- LogLevel3[LogLevel3["Info"] = 3] = "Info";
108
- LogLevel3[LogLevel3["Debug"] = 4] = "Debug";
109
- return LogLevel3;
110
- })(LogLevel || {});
111
- var config = {
112
- level: 1 /* Error */,
113
- prefix: "[RaisinDB]"
114
- };
115
- function configureLogger(options) {
116
- config = { ...config, ...options };
117
- }
118
- function getLoggerConfig() {
119
- return { ...config };
120
- }
121
- function setLogLevel(level) {
122
- config.level = level;
123
- }
124
- function getLogLevel() {
125
- return config.level;
126
- }
127
- var logger = {
128
- /**
129
- * Log debug message (only shown when level >= Debug)
130
- */
131
- debug(...args) {
132
- if (config.level >= 4 /* Debug */) {
133
- console.log(config.prefix, ...args);
134
- }
135
- },
136
- /**
137
- * Log info message (only shown when level >= Info)
138
- */
139
- info(...args) {
140
- if (config.level >= 3 /* Info */) {
141
- console.log(config.prefix, ...args);
142
- }
143
- },
144
- /**
145
- * Log warning message (only shown when level >= Warn)
146
- */
147
- warn(...args) {
148
- if (config.level >= 2 /* Warn */) {
149
- console.warn(config.prefix, ...args);
150
- }
151
- },
152
- /**
153
- * Log error message (only shown when level >= Error)
154
- */
155
- error(...args) {
156
- if (config.level >= 1 /* Error */) {
157
- console.error(config.prefix, ...args);
158
- }
159
- }
160
- };
161
- if (typeof globalThis !== "undefined") {
162
- globalThis.__raisinDebug = (enable = true) => {
163
- setLogLevel(enable ? 4 /* Debug */ : 1 /* Error */);
164
- console.log(`[RaisinDB] Debug logging ${enable ? "ENABLED" : "DISABLED"} (includes server handler logs)`);
165
- };
166
- }
167
-
168
- // src/connection.ts
169
- var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
170
- ConnectionState2["Disconnected"] = "disconnected";
171
- ConnectionState2["Connecting"] = "connecting";
172
- ConnectionState2["Connected"] = "connected";
173
- ConnectionState2["Reconnecting"] = "reconnecting";
174
- ConnectionState2["Closed"] = "closed";
175
- return ConnectionState2;
176
- })(ConnectionState || {});
177
- function getWebSocketImpl() {
178
- if (typeof WebSocket !== "undefined") {
179
- return WebSocket;
180
- }
181
- try {
182
- const ws = __require("ws");
183
- return ws.WebSocket || ws.default || ws;
184
- } catch (error) {
185
- throw new Error(
186
- 'WebSocket implementation not found. In Node.js, please install the "ws" package: npm install ws'
187
- );
188
- }
189
- }
190
- var Connection = class extends EventEmitter {
191
- constructor(url, options = {}) {
192
- super();
193
- this.state = "disconnected" /* Disconnected */;
194
- this.manualClose = false;
195
- this.url = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
196
- this.options = {
197
- autoReconnect: options.autoReconnect ?? true,
198
- heartbeatInterval: options.heartbeatInterval ?? 3e4,
199
- heartbeatTimeout: options.heartbeatTimeout ?? 5e3,
200
- protocols: options.protocols
201
- };
202
- this.reconnectManager = new ReconnectManager(options.reconnectOptions);
203
- this.WebSocketImpl = getWebSocketImpl();
204
- }
205
- /**
206
- * Connect to the WebSocket server
207
- */
208
- async connect() {
209
- if (this.state === "connected" /* Connected */ || this.state === "connecting" /* Connecting */) {
210
- return;
211
- }
212
- this.manualClose = false;
213
- this.setState("connecting" /* Connecting */);
214
- return new Promise((resolve, reject) => {
215
- try {
216
- this.ws = new this.WebSocketImpl(
217
- this.url,
218
- this.options.protocols
219
- );
220
- this.ws.binaryType = "arraybuffer";
221
- this.ws.onopen = () => {
222
- this.setState("connected" /* Connected */);
223
- this.reconnectManager.reset();
224
- this.startHeartbeat();
225
- resolve();
226
- };
227
- this.ws.onmessage = (event) => {
228
- logger.debug(`WebSocket onmessage fired - data type: ${typeof event.data}, is ArrayBuffer: ${event.data instanceof ArrayBuffer}`);
229
- this.resetHeartbeat();
230
- const data = event.data;
231
- logger.debug(`Received binary message - size: ${data.byteLength} bytes`);
232
- logger.debug(`Emitting 'message' event to client handlers`);
233
- this.emit("message", data);
234
- };
235
- this.ws.onerror = () => {
236
- const error = new Error("WebSocket error occurred");
237
- this.emit("error", error);
238
- if (this.state === "connecting" /* Connecting */) {
239
- reject(error);
240
- }
241
- };
242
- this.ws.onclose = (event) => {
243
- this.stopHeartbeat();
244
- this.emit("close", event);
245
- if (!this.manualClose && this.options.autoReconnect) {
246
- this.setState("reconnecting" /* Reconnecting */);
247
- this.reconnect();
248
- } else {
249
- this.setState("disconnected" /* Disconnected */);
250
- }
251
- if (this.state === "connecting" /* Connecting */) {
252
- reject(new Error(`Connection closed: ${event.reason || "Unknown reason"}`));
253
- }
254
- };
255
- } catch (error) {
256
- this.setState("disconnected" /* Disconnected */);
257
- reject(error);
258
- }
259
- });
260
- }
261
- /**
262
- * Disconnect from the WebSocket server
263
- */
264
- disconnect() {
265
- this.manualClose = true;
266
- this.reconnectManager.cancelReconnect();
267
- this.stopHeartbeat();
268
- if (this.ws) {
269
- this.ws.close(1e3, "Client disconnect");
270
- this.ws = void 0;
271
- }
272
- this.setState("closed" /* Closed */);
273
- }
274
- /**
275
- * Send data through the WebSocket
276
- *
277
- * @param data - Data to send (Uint8Array for MessagePack)
278
- */
279
- send(data) {
280
- if (this.state !== "connected" /* Connected */ || !this.ws) {
281
- throw new Error("Cannot send data: not connected");
282
- }
283
- if (this.ws.readyState !== 1) {
284
- logger.error(`WebSocket not open - readyState: ${this.ws.readyState} (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED)`);
285
- throw new Error(`Cannot send data: WebSocket not open (readyState: ${this.ws.readyState})`);
286
- }
287
- logger.debug(`Sending message - size: ${data.length} bytes, readyState: ${this.ws.readyState}`);
288
- try {
289
- this.ws.send(data);
290
- logger.debug(`Message sent successfully`);
291
- } catch (error) {
292
- logger.error(`Failed to send message:`, error);
293
- throw new Error(`Failed to send data: ${error}`);
294
- }
295
- }
296
- /**
297
- * Get current connection state
298
- */
299
- getState() {
300
- return this.state;
301
- }
302
- /**
303
- * Check if connected
304
- */
305
- isConnected() {
306
- return this.state === "connected" /* Connected */;
307
- }
308
- /**
309
- * Reconnect to the server
310
- */
311
- reconnect() {
312
- const success = this.reconnectManager.scheduleReconnect(async () => {
313
- try {
314
- await this.connect();
315
- } catch (error) {
316
- }
317
- });
318
- if (!success) {
319
- this.setState("disconnected" /* Disconnected */);
320
- this.emit("error", new Error("Max reconnection attempts reached"));
321
- }
322
- }
323
- /**
324
- * Set connection state and emit event
325
- */
326
- setState(state) {
327
- if (this.state !== state) {
328
- this.state = state;
329
- this.emit("stateChange", state);
330
- }
331
- }
332
- /**
333
- * Start heartbeat mechanism
334
- */
335
- startHeartbeat() {
336
- if (this.options.heartbeatInterval <= 0) {
337
- return;
338
- }
339
- this.stopHeartbeat();
340
- }
341
- /**
342
- * Stop heartbeat mechanism
343
- */
344
- stopHeartbeat() {
345
- if (this.heartbeatTimer) {
346
- clearInterval(this.heartbeatTimer);
347
- this.heartbeatTimer = void 0;
348
- }
349
- if (this.heartbeatTimeoutTimer) {
350
- clearTimeout(this.heartbeatTimeoutTimer);
351
- this.heartbeatTimeoutTimer = void 0;
352
- }
353
- }
354
- /**
355
- * Reset heartbeat timeout (called when message received)
356
- */
357
- resetHeartbeat() {
358
- if (this.heartbeatTimeoutTimer) {
359
- clearTimeout(this.heartbeatTimeoutTimer);
360
- this.heartbeatTimeoutTimer = void 0;
361
- }
362
- }
363
- };
54
+ import { EventEmitter as EventEmitter2 } from "events";
364
55
 
365
56
  // src/utils/request-tracker.ts
366
57
  import { v4 as uuidv4 } from "uuid";
@@ -702,6 +393,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
702
393
  RequestType2["NodeReorder"] = "node_reorder";
703
394
  RequestType2["NodeMoveChildBefore"] = "node_move_child_before";
704
395
  RequestType2["NodeMoveChildAfter"] = "node_move_child_after";
396
+ RequestType2["NodeApplyChildOrder"] = "node_apply_child_order";
705
397
  RequestType2["PropertyGet"] = "property_get";
706
398
  RequestType2["PropertyUpdate"] = "property_update";
707
399
  RequestType2["RelationAdd"] = "relation_add";
@@ -721,6 +413,8 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
721
413
  RequestType2["BranchUpdateHead"] = "branch_update_head";
722
414
  RequestType2["BranchMerge"] = "branch_merge";
723
415
  RequestType2["BranchCompare"] = "branch_compare";
416
+ RequestType2["BranchDiff"] = "branch_diff";
417
+ RequestType2["BranchCopyNodes"] = "branch_copy_nodes";
724
418
  RequestType2["TagCreate"] = "tag_create";
725
419
  RequestType2["TagGet"] = "tag_get";
726
420
  RequestType2["TagList"] = "tag_list";
@@ -741,6 +435,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
741
435
  RequestType2["ArchetypeDelete"] = "archetype_delete";
742
436
  RequestType2["ArchetypePublish"] = "archetype_publish";
743
437
  RequestType2["ArchetypeUnpublish"] = "archetype_unpublish";
438
+ RequestType2["ArchetypeGetResolved"] = "archetype_get_resolved";
744
439
  RequestType2["ElementTypeCreate"] = "element_type_create";
745
440
  RequestType2["ElementTypeGet"] = "element_type_get";
746
441
  RequestType2["ElementTypeList"] = "element_type_list";
@@ -748,6 +443,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
748
443
  RequestType2["ElementTypeDelete"] = "element_type_delete";
749
444
  RequestType2["ElementTypePublish"] = "element_type_publish";
750
445
  RequestType2["ElementTypeUnpublish"] = "element_type_unpublish";
446
+ RequestType2["ElementTypeGetResolved"] = "element_type_get_resolved";
751
447
  RequestType2["FullTextSearch"] = "full_text_search";
752
448
  RequestType2["VectorSearch"] = "vector_search";
753
449
  RequestType2["Subscribe"] = "subscribe";
@@ -772,6 +468,10 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
772
468
  RequestType2["FlowUnsubscribeEvents"] = "flow_unsubscribe_events";
773
469
  RequestType2["FunctionInvoke"] = "function_invoke";
774
470
  RequestType2["FunctionInvokeSync"] = "function_invoke_sync";
471
+ RequestType2["ScheduledInvocationCreate"] = "scheduled_invocation_create";
472
+ RequestType2["ScheduledInvocationCancel"] = "scheduled_invocation_cancel";
473
+ RequestType2["ScheduledInvocationList"] = "scheduled_invocation_list";
474
+ RequestType2["ScheduledInvocationGet"] = "scheduled_invocation_get";
775
475
  return RequestType2;
776
476
  })(RequestType || {});
777
477
  var ResponseStatus = /* @__PURE__ */ ((ResponseStatus2) => {
@@ -881,6 +581,14 @@ var EventHandler = class {
881
581
  * If a subscription with identical filters already exists, the callback
882
582
  * is added to the existing subscription instead of creating a new one.
883
583
  *
584
+ * Path filter semantics (matched server-side):
585
+ * - A plain path matches **only that exact node** — there is no implicit
586
+ * prefix matching.
587
+ * - `*` matches exactly one path segment (e.g. `/inbox/*` matches
588
+ * `/inbox/a` but not `/inbox/a/b`).
589
+ * - `**` matches recursively (e.g. `/inbox/**` matches all descendants
590
+ * of `/inbox`, but not `/inbox` itself).
591
+ *
884
592
  * @param filters - Event filters
885
593
  * @param callback - Callback function for events
886
594
  * @returns Subscription object
@@ -1071,14 +779,29 @@ var EventHandler = class {
1071
779
  * Restore subscriptions after reconnect
1072
780
  *
1073
781
  * Re-subscribes to the server with the original filters for all active
1074
- * subscription entries.
782
+ * subscription entries. Failed entries are kept (with their callbacks)
783
+ * and marked pending so a retry call can re-attempt them without
784
+ * duplicating already-restored subscriptions.
785
+ *
786
+ * @param options.retry - When true, only re-attempts entries that failed
787
+ * in a previous call (instead of re-subscribing everything)
788
+ * @throws Error if any subscription could not be restored
1075
789
  */
1076
- async restoreSubscriptions() {
1077
- const entriesToRestore = Array.from(this.filterSubscriptions.entries());
1078
- this.subscriptions.clear();
1079
- this.filterSubscriptions.clear();
790
+ async restoreSubscriptions(options) {
791
+ if (!options?.retry) {
792
+ for (const entry of this.filterSubscriptions.values()) {
793
+ this.subscriptions.delete(entry.serverId);
794
+ entry.restorePending = true;
795
+ }
796
+ }
797
+ const entriesToRestore = Array.from(this.filterSubscriptions.entries()).filter(([, entry]) => entry.restorePending);
798
+ let failureCount = 0;
799
+ let lastError;
1080
800
  for (const [hash, entry] of entriesToRestore) {
1081
- if (entry.callbacks.size === 0) continue;
801
+ if (entry.callbacks.size === 0) {
802
+ entry.restorePending = false;
803
+ continue;
804
+ }
1082
805
  try {
1083
806
  const payload = { filters: entry.filters };
1084
807
  const response = await this.sendRequest(
@@ -1086,22 +809,24 @@ var EventHandler = class {
1086
809
  "subscribe" /* Subscribe */
1087
810
  );
1088
811
  const newServerId = response.subscription_id;
1089
- this.filterSubscriptions.set(hash, {
1090
- serverId: newServerId,
1091
- filters: entry.filters,
1092
- callbacks: entry.callbacks
1093
- });
812
+ entry.serverId = newServerId;
813
+ entry.restorePending = false;
1094
814
  this.subscriptions.set(newServerId, hash);
1095
815
  for (const callback of entry.callbacks) {
1096
816
  this.callbackToHash.set(callback, hash);
1097
817
  }
1098
818
  } catch (error) {
1099
819
  logger.error("Failed to restore subscription:", error);
1100
- for (const callback of entry.callbacks) {
1101
- this.callbackToHash.delete(callback);
1102
- }
820
+ failureCount++;
821
+ lastError = error;
1103
822
  }
1104
823
  }
824
+ if (failureCount > 0) {
825
+ const message = lastError instanceof Error ? lastError.message : String(lastError);
826
+ throw new Error(
827
+ `Failed to restore ${failureCount} subscription(s) after reconnect: ${message}`
828
+ );
829
+ }
1105
830
  }
1106
831
  };
1107
832
  var EventSubscriptions = class {
@@ -1136,7 +861,15 @@ var EventSubscriptions = class {
1136
861
  /**
1137
862
  * Subscribe to events for a specific path
1138
863
  *
1139
- * @param path - Path pattern (supports wildcards like "/folder/*")
864
+ * Path pattern semantics (matched server-side):
865
+ * - A plain path matches **only that exact node** — there is no implicit
866
+ * prefix matching, so `/folder` does NOT match `/folder/child`.
867
+ * - `*` matches exactly one path segment: `/folder/*` matches
868
+ * `/folder/child` but not `/folder/child/grandchild`.
869
+ * - `**` matches recursively: `/folder/**` matches every descendant of
870
+ * `/folder` (but not `/folder` itself).
871
+ *
872
+ * @param path - Path pattern (e.g. "/folder", "/folder/*", "/folder/**")
1140
873
  * @param callback - Callback function for events
1141
874
  * @param options - Optional subscription options
1142
875
  * @param options.includeNode - Include full node data in event payload (default: false)
@@ -1183,6 +916,40 @@ var NodeOperations = class {
1183
916
  const result = await this.sendRequest(payload);
1184
917
  return result;
1185
918
  }
919
+ /**
920
+ * Create a node, auto-creating any missing ancestor folders.
921
+ *
922
+ * @param options - Node creation options (with optional `parentNodeType`)
923
+ * @returns Created node
924
+ */
925
+ async createDeep(options) {
926
+ const payload = {
927
+ node_type: options.type,
928
+ path: options.path,
929
+ properties: options.properties ?? {},
930
+ content: options.content,
931
+ parent_node_type: options.parentNodeType
932
+ };
933
+ const result = await this.sendRequest(payload, "node_create_deep");
934
+ return result;
935
+ }
936
+ /**
937
+ * Upsert a node by path (create-or-update), auto-creating any missing ancestor folders.
938
+ *
939
+ * @param options - Node creation options (with optional `parentNodeType`)
940
+ * @returns Upserted node
941
+ */
942
+ async upsertDeep(options) {
943
+ const payload = {
944
+ node_type: options.type,
945
+ path: options.path,
946
+ properties: options.properties ?? {},
947
+ content: options.content,
948
+ parent_node_type: options.parentNodeType
949
+ };
950
+ const result = await this.sendRequest(payload, "node_upsert_deep");
951
+ return result;
952
+ }
1186
953
  /**
1187
954
  * Update an existing node
1188
955
  *
@@ -1232,6 +999,75 @@ var NodeOperations = class {
1232
999
  throw error;
1233
1000
  }
1234
1001
  }
1002
+ /**
1003
+ * List a node's revision history (git-style "file history"), newest first.
1004
+ *
1005
+ * Each entry carries the `revision` (usable with `atRevision()` reads to
1006
+ * fetch the full snapshot), plus when/who and whether the node was deleted
1007
+ * at that revision. Always available regardless of the NodeType `auditable`
1008
+ * flag — it reflects the structural MVCC version history.
1009
+ *
1010
+ * @param id - Node ID
1011
+ * @param options - Optional `{ limit }` to cap the number of revisions
1012
+ * @returns Array of revision entries (newest first)
1013
+ *
1014
+ * @example
1015
+ * ```typescript
1016
+ * const history = await ws.nodes().history(nodeId, { limit: 50 });
1017
+ * for (const rev of history) {
1018
+ * const snapshot = await ws.atRevision(rev.revision).nodes().get(nodeId);
1019
+ * }
1020
+ * ```
1021
+ */
1022
+ async history(id, options) {
1023
+ const payload = {
1024
+ node_id: id,
1025
+ limit: options?.limit
1026
+ };
1027
+ const result = await this.sendRequest(payload, "node_history");
1028
+ return result ?? [];
1029
+ }
1030
+ /**
1031
+ * List a node's revision history by path (newest first).
1032
+ *
1033
+ * @param path - Node path
1034
+ * @param options - Optional `{ limit }` to cap the number of revisions
1035
+ * @returns Array of revision entries (newest first)
1036
+ */
1037
+ async historyByPath(path, options) {
1038
+ const payload = {
1039
+ path,
1040
+ limit: options?.limit
1041
+ };
1042
+ const result = await this.sendRequest(payload, "node_history");
1043
+ return result ?? [];
1044
+ }
1045
+ /**
1046
+ * Query a node's audit-log entries by ID.
1047
+ *
1048
+ * Audit logs are only produced for NodeTypes marked `auditable`. This is
1049
+ * distinct from {@link history}, which is the always-on structural revision
1050
+ * history available for every node.
1051
+ *
1052
+ * @param id - Node ID
1053
+ * @returns Array of audit-log entries
1054
+ */
1055
+ async auditLog(id) {
1056
+ const payload = { node_id: id };
1057
+ const result = await this.sendRequest(payload, "audit_query");
1058
+ return result ?? [];
1059
+ }
1060
+ /**
1061
+ * Query a node's audit-log entries by path.
1062
+ *
1063
+ * @param path - Node path
1064
+ * @returns Array of audit-log entries
1065
+ */
1066
+ async auditLogByPath(path) {
1067
+ const payload = { path };
1068
+ const result = await this.sendRequest(payload, "audit_query");
1069
+ return result ?? [];
1070
+ }
1235
1071
  /**
1236
1072
  * Query nodes
1237
1073
  *
@@ -1399,7 +1235,7 @@ var NodeOperations = class {
1399
1235
  */
1400
1236
  async rename(nodePath, newName) {
1401
1237
  const payload = {
1402
- node_path: nodePath,
1238
+ old_path: nodePath,
1403
1239
  new_name: newName
1404
1240
  };
1405
1241
  const result = await this.sendRequest(payload, "node_rename");
@@ -1415,9 +1251,8 @@ var NodeOperations = class {
1415
1251
  */
1416
1252
  async copy(fromPath, toParentPath, newName) {
1417
1253
  const payload = {
1418
- from_path: fromPath,
1419
- to_parent_path: toParentPath,
1420
- deep: false
1254
+ source_path: fromPath,
1255
+ target_parent: toParentPath
1421
1256
  };
1422
1257
  if (newName !== void 0) {
1423
1258
  payload.new_name = newName;
@@ -1435,9 +1270,8 @@ var NodeOperations = class {
1435
1270
  */
1436
1271
  async copyTree(fromPath, toParentPath, newName) {
1437
1272
  const payload = {
1438
- from_path: fromPath,
1439
- to_parent_path: toParentPath,
1440
- deep: true
1273
+ source_path: fromPath,
1274
+ target_parent: toParentPath
1441
1275
  };
1442
1276
  if (newName !== void 0) {
1443
1277
  payload.new_name = newName;
@@ -1494,6 +1328,24 @@ var NodeOperations = class {
1494
1328
  const result = await this.sendRequest(payload, "node_move_child_after");
1495
1329
  return result;
1496
1330
  }
1331
+ /**
1332
+ * Replay a parent's child order from another branch onto the current branch.
1333
+ *
1334
+ * Carries node sibling-order across branches — useful for selective publish
1335
+ * flows, where copying node content (e.g. via SQL) does not move the ordering
1336
+ * index. Only children that exist under the parent on both branches are
1337
+ * reordered. The target branch is the connection's current branch.
1338
+ *
1339
+ * @param parentPath - Parent node path
1340
+ * @param sourceBranch - Branch to copy the child order FROM
1341
+ */
1342
+ async applyChildOrder(parentPath, sourceBranch) {
1343
+ const payload = {
1344
+ parent_path: parentPath,
1345
+ source_branch: sourceBranch
1346
+ };
1347
+ await this.sendRequest(payload, "node_apply_child_order");
1348
+ }
1497
1349
  // ========================================================================
1498
1350
  // Relationship Operations
1499
1351
  // ========================================================================
@@ -1768,10 +1620,20 @@ var NodeFileSource = class {
1768
1620
  return;
1769
1621
  }
1770
1622
  if (!this._fsPromises) {
1771
- this._fsPromises = await import("fs/promises");
1772
- }
1773
- const stats = await this._fsPromises.stat(this._filePath);
1774
- this._size = stats.size;
1623
+ if (!isNodeEnvironment()) {
1624
+ throw new Error(
1625
+ "NodeFileSource requires Node.js (fs/promises is not available in browsers). In the browser, use a File or Blob object instead."
1626
+ );
1627
+ }
1628
+ const specifier = "fs/promises";
1629
+ this._fsPromises = await import(
1630
+ /* @vite-ignore */
1631
+ /* webpackIgnore: true */
1632
+ specifier
1633
+ );
1634
+ }
1635
+ const stats = await this._fsPromises.stat(this._filePath);
1636
+ this._size = stats.size;
1775
1637
  this._initialized = true;
1776
1638
  }
1777
1639
  /**
@@ -2569,6 +2431,17 @@ var Archetypes = class {
2569
2431
  "archetype_unpublish" /* ArchetypeUnpublish */
2570
2432
  );
2571
2433
  }
2434
+ /**
2435
+ * Get resolved Archetype with full inheritance applied
2436
+ */
2437
+ async getResolved(name) {
2438
+ return this.sendRequest(
2439
+ {
2440
+ name
2441
+ },
2442
+ "archetype_get_resolved" /* ArchetypeGetResolved */
2443
+ );
2444
+ }
2572
2445
  };
2573
2446
 
2574
2447
  // src/element-types.ts
@@ -2656,6 +2529,17 @@ var ElementTypes = class {
2656
2529
  "element_type_unpublish" /* ElementTypeUnpublish */
2657
2530
  );
2658
2531
  }
2532
+ /**
2533
+ * Get resolved ElementType with full inheritance applied
2534
+ */
2535
+ async getResolved(name) {
2536
+ return this.sendRequest(
2537
+ {
2538
+ name
2539
+ },
2540
+ "element_type_get_resolved" /* ElementTypeGetResolved */
2541
+ );
2542
+ }
2659
2543
  };
2660
2544
 
2661
2545
  // src/branches.ts
@@ -2757,6 +2641,102 @@ var Branches = class {
2757
2641
  "branch_compare" /* BranchCompare */
2758
2642
  );
2759
2643
  }
2644
+ /**
2645
+ * Per-node diff of `branch` relative to `baseBranch` (added/modified/deleted).
2646
+ *
2647
+ * Cheaper than reading both branch listings: cost scales with the number of
2648
+ * changed nodes since the branches diverged, not the total content.
2649
+ */
2650
+ async diff(branch, baseBranch) {
2651
+ return this.sendRequest(
2652
+ {
2653
+ branch,
2654
+ base_branch: baseBranch
2655
+ },
2656
+ "branch_diff" /* BranchDiff */
2657
+ );
2658
+ }
2659
+ /**
2660
+ * Copy a node set from `sourceBranch` onto `targetBranch` (branch
2661
+ * promotion): node ids are preserved, so repeated copies update the same
2662
+ * target nodes; all changes land in ONE atomic commit on the target branch.
2663
+ *
2664
+ * Each root's parent path must already exist on the target branch. With
2665
+ * `deleteMissing`, target nodes under the roots that are absent from the
2666
+ * copied source set are pruned (one-way sync).
2667
+ */
2668
+ async copyNodes(sourceBranch, targetBranch, opts) {
2669
+ return this.sendRequest(
2670
+ {
2671
+ source_branch: sourceBranch,
2672
+ target_branch: targetBranch,
2673
+ workspace: opts.workspace,
2674
+ roots: opts.roots,
2675
+ recursive: opts.recursive ?? true,
2676
+ delete_missing: opts.deleteMissing ?? false
2677
+ },
2678
+ "branch_copy_nodes" /* BranchCopyNodes */
2679
+ );
2680
+ }
2681
+ };
2682
+
2683
+ // src/scheduler.ts
2684
+ var SchedulerApi = class {
2685
+ constructor(_context, sendRequest) {
2686
+ this.sendRequest = sendRequest;
2687
+ }
2688
+ /**
2689
+ * Schedule a one-shot invocation of a function or flow at a fixed time.
2690
+ */
2691
+ async schedule(options) {
2692
+ const runAt = options.runAt instanceof Date ? options.runAt.toISOString() : options.runAt;
2693
+ return this.sendRequest(
2694
+ {
2695
+ target_kind: options.targetKind,
2696
+ target_path: options.targetPath,
2697
+ input: options.input ?? {},
2698
+ run_at: runAt,
2699
+ external_key: options.externalKey,
2700
+ branch: options.branch,
2701
+ workspace: options.workspace,
2702
+ max_retries: options.maxRetries
2703
+ },
2704
+ "scheduled_invocation_create" /* ScheduledInvocationCreate */
2705
+ );
2706
+ }
2707
+ /**
2708
+ * Cancel a pending invocation before it fires, by job id or external key.
2709
+ */
2710
+ async cancel(ref) {
2711
+ const payload = typeof ref === "string" ? { job_id: ref } : { job_id: ref.jobId, external_key: ref.externalKey };
2712
+ return this.sendRequest(
2713
+ payload,
2714
+ "scheduled_invocation_cancel" /* ScheduledInvocationCancel */
2715
+ );
2716
+ }
2717
+ /**
2718
+ * List this repository's scheduled invocations.
2719
+ */
2720
+ async list(filter) {
2721
+ const result = await this.sendRequest(
2722
+ {
2723
+ external_key: filter?.externalKey,
2724
+ status: filter?.status
2725
+ },
2726
+ "scheduled_invocation_list" /* ScheduledInvocationList */
2727
+ );
2728
+ return result?.invocations ?? [];
2729
+ }
2730
+ /**
2731
+ * Fetch a single scheduled invocation, by job id or external key.
2732
+ */
2733
+ async get(ref) {
2734
+ const payload = typeof ref === "string" ? { job_id: ref } : { job_id: ref.jobId, external_key: ref.externalKey };
2735
+ return this.sendRequest(
2736
+ payload,
2737
+ "scheduled_invocation_get" /* ScheduledInvocationGet */
2738
+ );
2739
+ }
2760
2740
  };
2761
2741
 
2762
2742
  // src/tags.ts
@@ -3024,367 +3004,8 @@ var HttpFunctionsApi = class {
3024
3004
  }
3025
3005
  };
3026
3006
 
3027
- // src/errors.ts
3028
- var RaisinError = class extends Error {
3029
- constructor(message, code, details) {
3030
- super(message);
3031
- this.name = "RaisinError";
3032
- this.code = code;
3033
- this.details = details;
3034
- if (Error.captureStackTrace) {
3035
- Error.captureStackTrace(this, this.constructor);
3036
- }
3037
- }
3038
- };
3039
- var RaisinConnectionError = class extends RaisinError {
3040
- constructor(message, code, options) {
3041
- super(message, code, options?.details);
3042
- this.name = "RaisinConnectionError";
3043
- this.status = options?.status;
3044
- }
3045
- };
3046
- var RaisinAuthError = class extends RaisinError {
3047
- constructor(message, code, status, details) {
3048
- super(message, code, details);
3049
- this.name = "RaisinAuthError";
3050
- this.status = status;
3051
- }
3052
- };
3053
- var RaisinFlowError = class extends RaisinError {
3054
- constructor(message, code, instanceId, details) {
3055
- super(message, code, details);
3056
- this.name = "RaisinFlowError";
3057
- this.instanceId = instanceId;
3058
- }
3059
- };
3060
- var RaisinTimeoutError = class extends RaisinError {
3061
- constructor(message, code, timeoutMs, details) {
3062
- super(message, code, details);
3063
- this.name = "RaisinTimeoutError";
3064
- this.timeoutMs = timeoutMs;
3065
- }
3066
- };
3067
- var RaisinAbortError = class extends RaisinError {
3068
- constructor(message = "Operation was aborted") {
3069
- super(message, "ABORTED");
3070
- this.name = "RaisinAbortError";
3071
- }
3072
- };
3073
- function classifyHttpError(status, message, details) {
3074
- if (status === 401) {
3075
- return new RaisinAuthError(message, "AUTH_UNAUTHORIZED", status, details);
3076
- }
3077
- if (status === 403) {
3078
- return new RaisinAuthError(message, "AUTH_FORBIDDEN", status, details);
3079
- }
3080
- if (status === 404) {
3081
- return new RaisinFlowError(message, "FLOW_NOT_FOUND", void 0, details);
3082
- }
3083
- return new RaisinConnectionError(message, "CONNECTION_FAILED", { status, details });
3084
- }
3085
-
3086
- // src/streaming/sse-client.ts
3087
- function parseSSEBlock(block) {
3088
- let event = "message";
3089
- let data = "";
3090
- let id;
3091
- const lines = block.split("\n");
3092
- for (const line of lines) {
3093
- if (line.startsWith("event:")) {
3094
- event = line.slice(6).trim();
3095
- } else if (line.startsWith("data:")) {
3096
- if (data) data += "\n";
3097
- data += line.slice(5).trim();
3098
- } else if (line.startsWith("id:")) {
3099
- id = line.slice(3).trim();
3100
- }
3101
- }
3102
- if (!data) return null;
3103
- return { event, data, id };
3104
- }
3105
- var SSEClient = class {
3106
- constructor(url, options = {}) {
3107
- this.abortController = null;
3108
- this._state = "disconnected";
3109
- this.url = url;
3110
- this.options = options;
3111
- this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
3112
- this._lastEventId = options.lastEventId;
3113
- this.autoReconnect = options.reconnect?.enabled ?? true;
3114
- this.externalSignal = options.signal;
3115
- this.eventTypeSet = options.eventTypes?.length ? new Set(options.eventTypes) : null;
3116
- this.reconnectManager = new ReconnectManager({
3117
- initialDelay: options.reconnect?.initialDelay ?? 1e3,
3118
- maxDelay: options.reconnect?.maxDelay ?? 3e4,
3119
- backoffMultiplier: options.reconnect?.backoffMultiplier ?? 2,
3120
- maxAttempts: options.reconnect?.maxAttempts ?? Infinity
3121
- });
3122
- }
3123
- /** Current connection state */
3124
- get state() {
3125
- return this._state;
3126
- }
3127
- /**
3128
- * Returns a promise that resolves once the SSE connection is established.
3129
- *
3130
- * Useful for ensuring the connection is ready before triggering events
3131
- * on the server side (e.g., calling resume before consuming the stream).
3132
- * If the client is already connected, resolves immediately.
3133
- */
3134
- waitUntilConnected() {
3135
- if (this._state === "connected") return Promise.resolve();
3136
- return new Promise((resolve, reject) => {
3137
- const originalOnState = this.options.onStateChange;
3138
- this.options.onStateChange = (state) => {
3139
- originalOnState?.(state);
3140
- if (state === "connected") {
3141
- this.options.onStateChange = originalOnState;
3142
- resolve();
3143
- } else if (state === "disconnected") {
3144
- this.options.onStateChange = originalOnState;
3145
- reject(new Error("SSE connection failed"));
3146
- }
3147
- };
3148
- });
3149
- }
3150
- /** Last received event ID */
3151
- get lastEventId() {
3152
- return this._lastEventId;
3153
- }
3154
- setState(state) {
3155
- if (this._state !== state) {
3156
- this._state = state;
3157
- this.options.onStateChange?.(state);
3158
- }
3159
- }
3160
- /**
3161
- * Connect and consume events via callback.
3162
- *
3163
- * @param onEvent - Called for each matching SSE event
3164
- * @param onError - Called when an error occurs (optional)
3165
- */
3166
- async connect(onEvent, onError) {
3167
- this.close();
3168
- await this.startStream(onEvent, onError);
3169
- }
3170
- /**
3171
- * Close the SSE connection and stop reconnection attempts.
3172
- */
3173
- close() {
3174
- this.reconnectManager.cancelReconnect();
3175
- if (this.abortController) {
3176
- this.abortController.abort();
3177
- this.abortController = null;
3178
- }
3179
- this.setState("disconnected");
3180
- }
3181
- /**
3182
- * AsyncIterable interface: allows `for await (const event of sseClient)`.
3183
- *
3184
- * The iterator ends when close() is called or the server closes the stream
3185
- * (and auto-reconnect is disabled or max attempts are exhausted).
3186
- */
3187
- async *[Symbol.asyncIterator]() {
3188
- const queue = [];
3189
- let resolve = null;
3190
- let finished = false;
3191
- const push = (item) => {
3192
- if (finished) return;
3193
- if (resolve) {
3194
- const r = resolve;
3195
- resolve = null;
3196
- r(item);
3197
- } else {
3198
- queue.push(item);
3199
- }
3200
- };
3201
- const pull = () => {
3202
- const queued = queue.shift();
3203
- if (queued) return Promise.resolve(queued);
3204
- return new Promise((r) => {
3205
- resolve = r;
3206
- });
3207
- };
3208
- this.startStream(
3209
- (event) => push({ done: false, value: event }),
3210
- (error) => push({ done: true, error })
3211
- ).then(() => {
3212
- push({ done: true });
3213
- }).catch((error) => {
3214
- push({ done: true, error });
3215
- });
3216
- try {
3217
- while (!finished) {
3218
- const item = await pull();
3219
- if (item.done) {
3220
- finished = true;
3221
- if (item.error) {
3222
- throw item.error;
3223
- }
3224
- return;
3225
- }
3226
- yield item.value;
3227
- }
3228
- } finally {
3229
- finished = true;
3230
- this.close();
3231
- }
3232
- }
3233
- /**
3234
- * Core streaming loop: connects, reads the SSE stream, and handles reconnection.
3235
- */
3236
- async startStream(onEvent, onError) {
3237
- while (true) {
3238
- try {
3239
- await this.readStream(onEvent);
3240
- if (!this.autoReconnect || this._state === "disconnected") {
3241
- return;
3242
- }
3243
- this.setState("reconnecting");
3244
- const scheduled = await this.waitForReconnect();
3245
- if (!scheduled) {
3246
- logger.warn("SSE max reconnection attempts reached");
3247
- this.setState("disconnected");
3248
- return;
3249
- }
3250
- } catch (error) {
3251
- if (this._state === "disconnected") {
3252
- return;
3253
- }
3254
- const err = error instanceof Error ? error : new Error(String(error));
3255
- if (err.name === "AbortError" || err instanceof RaisinAbortError) {
3256
- return;
3257
- }
3258
- logger.error("SSE connection error:", err.message);
3259
- onError?.(err);
3260
- if (!this.autoReconnect) {
3261
- this.setState("disconnected");
3262
- throw err;
3263
- }
3264
- this.setState("reconnecting");
3265
- const scheduled = await this.waitForReconnect();
3266
- if (!scheduled) {
3267
- logger.warn("SSE max reconnection attempts reached");
3268
- this.setState("disconnected");
3269
- throw err;
3270
- }
3271
- }
3272
- }
3273
- }
3274
- /**
3275
- * Perform a single fetch and read the response body as SSE stream.
3276
- */
3277
- async readStream(onEvent) {
3278
- this.abortController = new AbortController();
3279
- this.setState("connecting");
3280
- if (this.externalSignal) {
3281
- if (this.externalSignal.aborted) {
3282
- this.abortController.abort();
3283
- } else {
3284
- const ctrl = this.abortController;
3285
- this.externalSignal.addEventListener("abort", () => ctrl.abort(), { once: true });
3286
- }
3287
- }
3288
- const headers = {
3289
- Accept: "text/event-stream",
3290
- "Cache-Control": "no-cache",
3291
- ...this.options.headers
3292
- };
3293
- if (this._lastEventId) {
3294
- headers["Last-Event-ID"] = this._lastEventId;
3295
- }
3296
- const method = this.options.method ?? "GET";
3297
- const fetchOptions = {
3298
- method,
3299
- headers,
3300
- signal: this.abortController.signal
3301
- };
3302
- if (method === "POST" && this.options.body !== void 0) {
3303
- fetchOptions.headers["Content-Type"] = "application/json";
3304
- fetchOptions.body = JSON.stringify(this.options.body);
3305
- }
3306
- const response = await this.fetchImpl(this.url, fetchOptions);
3307
- if (!response.ok) {
3308
- const text = await response.text().catch(() => "");
3309
- const msg = `SSE connection failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`;
3310
- if (response.status === 401 || response.status === 403) {
3311
- throw new RaisinAuthError(
3312
- msg,
3313
- response.status === 401 ? "AUTH_UNAUTHORIZED" : "AUTH_FORBIDDEN",
3314
- response.status
3315
- );
3316
- }
3317
- throw new RaisinConnectionError(msg, "SSE_STREAM_ERROR", { status: response.status });
3318
- }
3319
- if (!response.body) {
3320
- throw new RaisinConnectionError("SSE response has no body", "SSE_STREAM_ERROR");
3321
- }
3322
- this.setState("connected");
3323
- this.reconnectManager.reset();
3324
- const reader = response.body.getReader();
3325
- const decoder = new TextDecoder();
3326
- let buffer = "";
3327
- try {
3328
- while (true) {
3329
- const { done, value } = await reader.read();
3330
- if (done) break;
3331
- buffer += decoder.decode(value, { stream: true });
3332
- const blocks = buffer.split("\n\n");
3333
- buffer = blocks.pop() ?? "";
3334
- for (const block of blocks) {
3335
- const trimmed = block.trim();
3336
- if (!trimmed) continue;
3337
- const parsed = parseSSEBlock(trimmed);
3338
- if (!parsed) continue;
3339
- if (parsed.id) {
3340
- this._lastEventId = parsed.id;
3341
- }
3342
- if (this.eventTypeSet && !this.eventTypeSet.has(parsed.event)) {
3343
- continue;
3344
- }
3345
- let data;
3346
- try {
3347
- data = JSON.parse(parsed.data);
3348
- } catch {
3349
- data = parsed.data;
3350
- }
3351
- const sseEvent = {
3352
- type: parsed.event,
3353
- data,
3354
- id: parsed.id
3355
- };
3356
- onEvent(sseEvent);
3357
- }
3358
- }
3359
- } finally {
3360
- reader.releaseLock();
3361
- }
3362
- }
3363
- /**
3364
- * Wait for the reconnect manager to schedule a reconnection attempt.
3365
- * Returns true if reconnection was scheduled, false if max attempts reached.
3366
- */
3367
- waitForReconnect() {
3368
- return new Promise((resolve) => {
3369
- const scheduled = this.reconnectManager.scheduleReconnect(() => {
3370
- resolve(true);
3371
- });
3372
- if (!scheduled) {
3373
- resolve(false);
3374
- }
3375
- });
3376
- }
3377
- };
3378
-
3379
- // src/utils/chat-events.ts
3380
- function isRecoveredDoneEvent(event) {
3381
- if (event.type !== "done") return false;
3382
- const done = event;
3383
- if (done.recovered !== true) return false;
3384
- return typeof done.content !== "string" || done.content.trim().length === 0;
3385
- }
3386
-
3387
3007
  // src/conversations.ts
3008
+ var DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS = 12e4;
3388
3009
  var ConversationManager = class {
3389
3010
  constructor(baseUrl, repository, authManager, options = {}, executeSql) {
3390
3011
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -3406,10 +3027,11 @@ var ConversationManager = class {
3406
3027
  );
3407
3028
  if (!result?.rows?.[0]) throw new Error("User not authenticated");
3408
3029
  const row = result.rows[0];
3409
- this.cachedUserHome = row.home;
3410
- this.cachedUserId = row.user_id || this.cachedUserHome;
3411
- if (!this.cachedUserHome) throw new Error("User home path not found");
3412
- return { userId: this.cachedUserId, userHome: this.cachedUserHome };
3030
+ const userHome = normalizeHomePath(row.home);
3031
+ if (!userHome) throw new Error("User home path not found");
3032
+ this.cachedUserHome = userHome;
3033
+ this.cachedUserId = row.user_id || userHome;
3034
+ return { userId: this.cachedUserId, userHome };
3413
3035
  }
3414
3036
  // ==========================================================================
3415
3037
  // Conversation listing
@@ -3604,13 +3226,15 @@ var ConversationManager = class {
3604
3226
  fetch: this.fetchImpl,
3605
3227
  signal: options?.signal,
3606
3228
  method: "POST",
3607
- body: { channel: streamTarget.channel, path: streamTarget.path }
3229
+ body: { channel: streamTarget.channel, path: streamTarget.path },
3230
+ inactivityTimeoutMs: options?.inactivityTimeoutMs ?? DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS
3608
3231
  });
3609
3232
  const iterator = sse[Symbol.asyncIterator]();
3610
3233
  let pendingFirst = iterator.next();
3611
3234
  try {
3612
3235
  await sse.waitUntilConnected();
3613
3236
  } catch {
3237
+ pendingFirst?.catch(() => void 0);
3614
3238
  pendingFirst = null;
3615
3239
  sse.close();
3616
3240
  logger.debug("SSE connection failed, falling back to create-only");
@@ -3647,6 +3271,15 @@ var ConversationManager = class {
3647
3271
  return;
3648
3272
  }
3649
3273
  }
3274
+ } catch (error) {
3275
+ if (error instanceof RaisinTimeoutError && error.code === "SSE_INACTIVITY_TIMEOUT") {
3276
+ logger.warn(
3277
+ `[ConversationManager] SSE stream inactive for ${error.timeoutMs}ms, ending turn with synthetic waiting event`
3278
+ );
3279
+ yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
3280
+ return;
3281
+ }
3282
+ throw error;
3650
3283
  } finally {
3651
3284
  sse.close();
3652
3285
  }
@@ -4132,383 +3765,69 @@ var ConversationManager = class {
4132
3765
  }
4133
3766
  };
4134
3767
 
4135
- // src/flow-client.ts
4136
- var FlowClient = class _FlowClient {
4137
- /**
4138
- * Create a new FlowClient.
4139
- *
4140
- * @param baseUrl - Base HTTP URL of the RaisinDB server (e.g., "http://localhost:8081")
4141
- * @param repository - Repository name
4142
- * @param authManager - Auth manager for token access
4143
- * @param options - Additional options
4144
- * @param flowsApi - Optional FlowsApi for WebSocket-based flow operations
4145
- */
4146
- constructor(baseUrl, repository, authManager, options = {}, flowsApi) {
3768
+ // src/inbox.ts
3769
+ var InboxApi = class {
3770
+ constructor(baseUrl, repository, authManager, options = {}) {
4147
3771
  this.baseUrl = baseUrl.replace(/\/$/, "");
4148
3772
  this.repository = repository;
4149
3773
  this.authManager = authManager;
4150
3774
  this.fetchImpl = options.fetch ?? fetch;
4151
- this.requestTimeout = options.requestTimeout ?? 6e4;
4152
- this.flowsApi = flowsApi;
3775
+ this.requestTimeout = options.requestTimeout ?? 3e4;
4153
3776
  }
4154
3777
  /**
4155
- * Create a FlowClient from an existing RaisinHttpClient.
3778
+ * List the caller's inbox tasks.
4156
3779
  *
4157
- * @param httpClient - An authenticated RaisinHttpClient instance
4158
- * @param baseUrl - Base HTTP URL (same as the one used for the HTTP client)
4159
- * @param repository - Repository name
4160
- * @param options - Additional options
3780
+ * Tasks are sorted pending-first, then by priority (highest first) and
3781
+ * due time (earliest first).
4161
3782
  */
4162
- static fromHttpClient(httpClient, baseUrl, repository, options = {}, flowsApi) {
4163
- return new _FlowClient(
4164
- baseUrl,
4165
- repository,
4166
- httpClient.getAuthManager(),
4167
- options,
4168
- flowsApi
4169
- );
3783
+ async listTasks(options = {}) {
3784
+ const params = new URLSearchParams();
3785
+ if (options.status) params.set("status", options.status);
3786
+ if (options.assignee) params.set("assignee", options.assignee);
3787
+ const query = params.size > 0 ? `?${params.toString()}` : "";
3788
+ return this.request({
3789
+ method: "GET",
3790
+ path: `/api/inbox/${this.repository}${query}`,
3791
+ signal: options.signal
3792
+ });
4170
3793
  }
4171
- // ==========================================================================
4172
- // Flow Execution
4173
- // ==========================================================================
4174
3794
  /**
4175
- * Execute a flow by path.
4176
- *
4177
- * Creates a FlowInstance and queues it for execution. Returns immediately
4178
- * with the instance ID - use `streamEvents()` to monitor progress.
4179
- *
4180
- * @param flowPath - Path to the raisin:Flow node (e.g., "/flows/my-flow")
4181
- * @param input - Input data passed to the flow
4182
- * @returns Flow run response with instance_id and job_id
4183
- *
4184
- * @example
4185
- * ```typescript
4186
- * const result = await flows.run('/flows/process-order', {
4187
- * orderId: 'ord-123',
4188
- * items: [{ sku: 'ABC', qty: 2 }],
4189
- * });
4190
- * console.log('Instance:', result.instance_id);
4191
- * ```
3795
+ * Get a single inbox task by ID (or path).
4192
3796
  */
4193
- async run(flowPath, input = {}, options) {
4194
- if (this.flowsApi) {
4195
- return this.flowsApi.run(flowPath, input);
4196
- }
4197
- const response = await this.request({
4198
- method: "POST",
4199
- path: `/api/flows/${this.repository}/run`,
4200
- body: { flow_path: flowPath, input },
3797
+ async getTask(taskId, options) {
3798
+ return this.request({
3799
+ method: "GET",
3800
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}`,
4201
3801
  signal: options?.signal
4202
3802
  });
4203
- return response;
4204
3803
  }
4205
- // ==========================================================================
4206
- // Instance Status
4207
- // ==========================================================================
4208
3804
  /**
4209
- * Get the current status of a flow instance.
3805
+ * Complete an inbox task.
4210
3806
  *
4211
- * Returns the instance status, variables, and metadata. Useful for
4212
- * polling-based approaches where SSE is not reliable (e.g., initial
4213
- * flow execution where events may be missed).
3807
+ * The caller must be the task's assignee (or an admin). Completion marks
3808
+ * the task done, records who decided, and resumes the owning flow.
4214
3809
  *
4215
- * @param instanceId - Flow instance ID (from `run()`)
4216
- * @returns Instance status including variables and flow path
3810
+ * @param taskId - Task node ID (or path)
3811
+ * @param response - The response payload:
3812
+ * - approval tasks: `{ action: '<option value>', comment?: string }`
3813
+ * - input tasks: the input value(s) matching the task's input_schema
3814
+ * - review/action tasks: any acknowledgement payload
4217
3815
  *
4218
3816
  * @example
4219
3817
  * ```typescript
4220
- * const status = await flows.getInstanceStatus('instance-123');
4221
- * console.log('Status:', status.status); // 'waiting', 'completed', etc.
3818
+ * await inbox.completeTask('task-123', { action: 'approve' });
4222
3819
  * ```
4223
3820
  */
4224
- async getInstanceStatus(instanceId, options) {
4225
- if (this.flowsApi) {
4226
- return this.flowsApi.getInstanceStatus(instanceId);
4227
- }
3821
+ async completeTask(taskId, response, options) {
4228
3822
  return this.request({
4229
- method: "GET",
4230
- path: `/api/flows/${this.repository}/instances/${instanceId}`,
3823
+ method: "POST",
3824
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
3825
+ body: { response },
4231
3826
  signal: options?.signal
4232
3827
  });
4233
3828
  }
4234
3829
  // ==========================================================================
4235
- // Event Streaming (SSE)
4236
- // ==========================================================================
4237
- /**
4238
- * Stream real-time execution events for a flow instance via SSE.
4239
- *
4240
- * Returns an async iterable that yields FlowExecutionEvent objects.
4241
- * The stream automatically closes when a terminal event is received
4242
- * (flow_completed or flow_failed).
4243
- *
4244
- * @param instanceId - Flow instance ID (from `run()`)
4245
- * @returns Async iterable of flow execution events
4246
- *
4247
- * @example
4248
- * ```typescript
4249
- * const { instance_id } = await flows.run('/flows/my-flow', { key: 'value' });
4250
- *
4251
- * for await (const event of flows.streamEvents(instance_id)) {
4252
- * switch (event.type) {
4253
- * case 'step_started':
4254
- * console.log(`Step ${event.node_id} started`);
4255
- * break;
4256
- * case 'step_completed':
4257
- * console.log(`Step ${event.node_id} completed in ${event.duration_ms}ms`);
4258
- * break;
4259
- * case 'text_chunk':
4260
- * process.stdout.write(event.text);
4261
- * break;
4262
- * case 'flow_completed':
4263
- * console.log('Flow done:', event.output);
4264
- * break;
4265
- * case 'flow_failed':
4266
- * console.error('Flow failed:', event.error);
4267
- * break;
4268
- * }
4269
- * }
4270
- * ```
4271
- */
4272
- async *streamEvents(instanceId, options) {
4273
- if (this.flowsApi) {
4274
- const sub = await this.flowsApi.subscribeEvents(instanceId);
4275
- try {
4276
- for await (const event of sub.events) {
4277
- if (options?.signal?.aborted) throw new RaisinAbortError();
4278
- yield event;
4279
- if (event.type === "flow_completed" || event.type === "flow_failed") {
4280
- return;
4281
- }
4282
- }
4283
- } finally {
4284
- await sub.unsubscribe();
4285
- }
4286
- return;
4287
- }
4288
- const url = `${this.baseUrl}/api/flows/${this.repository}/instances/${instanceId}/events`;
4289
- const headers = {};
4290
- const token = this.authManager.getAccessToken();
4291
- if (token) {
4292
- headers["Authorization"] = `Bearer ${token}`;
4293
- }
4294
- const sse = new SSEClient(url, {
4295
- headers,
4296
- eventTypes: ["flow-event", "message"],
4297
- reconnect: { enabled: false },
4298
- fetch: this.fetchImpl,
4299
- signal: options?.signal
4300
- });
4301
- try {
4302
- for await (const sseEvent of sse) {
4303
- const event = sseEvent.data;
4304
- yield event;
4305
- if (event.type === "flow_completed" || event.type === "flow_failed") {
4306
- return;
4307
- }
4308
- }
4309
- } finally {
4310
- sse.close();
4311
- }
4312
- }
4313
- /**
4314
- * Create an eagerly-connected event stream for a flow instance.
4315
- *
4316
- * Unlike `streamEvents()` (a lazy async generator), this method opens the
4317
- * SSE connection immediately and waits until it is established before
4318
- * returning. This is critical for resume flows: you must subscribe to
4319
- * events BEFORE calling `resume()`, otherwise events emitted between
4320
- * the resume and the SSE connection are lost.
4321
- *
4322
- * @param instanceId - Flow instance ID
4323
- * @returns Object with an async iterable `events` and a `close()` method
4324
- */
4325
- async createEventStream(instanceId, options) {
4326
- if (this.flowsApi) {
4327
- const sub = await this.flowsApi.subscribeEvents(instanceId);
4328
- return {
4329
- events: sub.events,
4330
- close: () => {
4331
- sub.unsubscribe();
4332
- }
4333
- };
4334
- }
4335
- const url = `${this.baseUrl}/api/flows/${this.repository}/instances/${instanceId}/events`;
4336
- const headers = {};
4337
- const token = this.authManager.getAccessToken();
4338
- if (token) {
4339
- headers["Authorization"] = `Bearer ${token}`;
4340
- }
4341
- const sse = new SSEClient(url, {
4342
- headers,
4343
- eventTypes: ["flow-event", "message"],
4344
- reconnect: { enabled: false },
4345
- fetch: this.fetchImpl,
4346
- signal: options?.signal
4347
- });
4348
- const iterator = sse[Symbol.asyncIterator]();
4349
- let pendingFirst = iterator.next();
4350
- await sse.waitUntilConnected();
4351
- const events = {
4352
- [Symbol.asyncIterator]() {
4353
- return {
4354
- async next() {
4355
- let result;
4356
- if (pendingFirst) {
4357
- result = await pendingFirst;
4358
- pendingFirst = null;
4359
- } else {
4360
- result = await iterator.next();
4361
- }
4362
- if (result.done) return { done: true, value: void 0 };
4363
- const event = result.value.data;
4364
- return { done: false, value: event };
4365
- },
4366
- async return() {
4367
- await iterator.return?.();
4368
- sse.close();
4369
- return { done: true, value: void 0 };
4370
- }
4371
- };
4372
- }
4373
- };
4374
- return { events, close: () => sse.close() };
4375
- }
4376
- // ==========================================================================
4377
- // Flow Resume & Human Task
4378
- // ==========================================================================
4379
- /**
4380
- * Resume a flow instance that is in a waiting state.
4381
- *
4382
- * Use this when a flow is waiting for external input (tool results,
4383
- * external events, etc.). The resume data is passed to the waiting step.
4384
- *
4385
- * @param instanceId - Flow instance ID
4386
- * @param data - Resume data to pass to the waiting step
4387
- *
4388
- * @example
4389
- * ```typescript
4390
- * await flows.resume('instance-123', {
4391
- * tool_name: 'get_weather',
4392
- * result: { temperature: 72, conditions: 'sunny' },
4393
- * });
4394
- * ```
4395
- */
4396
- async resume(instanceId, data, options) {
4397
- if (this.flowsApi) {
4398
- await this.flowsApi.resume(instanceId, data);
4399
- return;
4400
- }
4401
- await this.request({
4402
- method: "POST",
4403
- path: `/api/flows/${this.repository}/instances/${instanceId}/resume`,
4404
- body: { resume_data: data },
4405
- signal: options?.signal
4406
- });
4407
- }
4408
- /**
4409
- * Respond to a human task within a flow.
4410
- *
4411
- * When a flow is waiting on a human_task step (approval, input, review),
4412
- * use this method to submit the response and resume the flow.
4413
- *
4414
- * @param instanceId - Flow instance ID
4415
- * @param taskId - The human task node ID
4416
- * @param response - The human's response data
4417
- *
4418
- * @example
4419
- * ```typescript
4420
- * // Approve a task
4421
- * await flows.respondToHumanTask('instance-123', 'step-5', {
4422
- * action: 'approve',
4423
- * comment: 'Looks good!',
4424
- * });
4425
- *
4426
- * // Provide input for an input task
4427
- * await flows.respondToHumanTask('instance-123', 'step-3', {
4428
- * name: 'John Doe',
4429
- * email: 'john@example.com',
4430
- * });
4431
- * ```
4432
- */
4433
- async respondToHumanTask(instanceId, taskId, response, options) {
4434
- await this.request({
4435
- method: "POST",
4436
- path: `/api/flows/${this.repository}/instances/${instanceId}/tasks/${taskId}/respond`,
4437
- body: { response },
4438
- signal: options?.signal
4439
- });
4440
- }
4441
- // ==========================================================================
4442
- // Convenience Methods
4443
- // ==========================================================================
4444
- /**
4445
- * Run a flow and wait for the final result.
4446
- *
4447
- * Starts the flow, streams all events, and returns the terminal outcome.
4448
- * This is a one-shot helper for fire-and-forget flows where you only
4449
- * care about the final output.
4450
- *
4451
- * @param flowPath - Path to the raisin:Flow node
4452
- * @param input - Input data passed to the flow
4453
- * @param options - Optional abort signal
4454
- * @returns The flow outcome including output or error
4455
- *
4456
- * @example
4457
- * ```typescript
4458
- * const result = await flows.runAndWait('/flows/process-order', { orderId: '123' });
4459
- * if (result.status === 'completed') {
4460
- * console.log('Output:', result.output);
4461
- * }
4462
- * ```
4463
- */
4464
- async runAndWait(flowPath, input = {}, options) {
4465
- const { instance_id } = await this.run(flowPath, input, options);
4466
- for await (const event of this.streamEvents(instance_id, options)) {
4467
- if (event.type === "flow_completed") {
4468
- return {
4469
- instanceId: instance_id,
4470
- status: "completed",
4471
- output: event.output
4472
- };
4473
- }
4474
- if (event.type === "flow_failed") {
4475
- return {
4476
- instanceId: instance_id,
4477
- status: "failed",
4478
- error: event.error
4479
- };
4480
- }
4481
- }
4482
- return { instanceId: instance_id, status: "failed", error: "Stream ended without terminal event" };
4483
- }
4484
- /**
4485
- * Run a flow and collect all events into an array.
4486
- *
4487
- * Starts the flow, streams all events, and returns them as a collected array.
4488
- * Useful for testing, logging, or post-processing all flow events.
4489
- *
4490
- * @param flowPath - Path to the raisin:Flow node
4491
- * @param input - Input data passed to the flow
4492
- * @param options - Optional abort signal
4493
- * @returns The instance ID and all collected events
4494
- *
4495
- * @example
4496
- * ```typescript
4497
- * const { events } = await flows.runAndCollect('/flows/etl-pipeline', { source: 'db' });
4498
- * const steps = events.filter(e => e.type === 'step_completed');
4499
- * console.log(`Completed ${steps.length} steps`);
4500
- * ```
4501
- */
4502
- async runAndCollect(flowPath, input = {}, options) {
4503
- const { instance_id } = await this.run(flowPath, input, options);
4504
- const events = [];
4505
- for await (const event of this.streamEvents(instance_id, options)) {
4506
- events.push(event);
4507
- }
4508
- return { instanceId: instance_id, events };
4509
- }
4510
- // ==========================================================================
4511
- // Internal HTTP request helper
3830
+ // Internal HTTP request helper
4512
3831
  // ==========================================================================
4513
3832
  async request(options) {
4514
3833
  const url = `${this.baseUrl}${options.path}`;
@@ -4526,7 +3845,9 @@ var FlowClient = class _FlowClient {
4526
3845
  clearTimeout(timeoutId);
4527
3846
  throw new RaisinAbortError();
4528
3847
  }
4529
- options.signal.addEventListener("abort", () => controller.abort(), { once: true });
3848
+ options.signal.addEventListener("abort", () => controller.abort(), {
3849
+ once: true
3850
+ });
4530
3851
  }
4531
3852
  try {
4532
3853
  const response = await this.fetchImpl(url, {
@@ -4761,6 +4082,13 @@ var Database = class _Database {
4761
4082
  tags() {
4762
4083
  return new Tags(this._context, this.sendRequest);
4763
4084
  }
4085
+ /**
4086
+ * Get SchedulerApi for one-shot scheduled invocations (time-based
4087
+ * function/flow runs).
4088
+ */
4089
+ scheduler() {
4090
+ return new SchedulerApi(this._context, this.sendRequest);
4091
+ }
4764
4092
  /**
4765
4093
  * Get FlowsApi for running flows via WebSocket
4766
4094
  */
@@ -4848,8 +4176,44 @@ var Database = class _Database {
4848
4176
  }
4849
4177
  return this._flowClient;
4850
4178
  }
4179
+ /**
4180
+ * Get a pre-configured InboxApi for human-in-the-loop tasks.
4181
+ *
4182
+ * Requires the Database to have been created via `RaisinClient.database()`
4183
+ * (which automatically provides HTTP context).
4184
+ *
4185
+ * @example
4186
+ * ```typescript
4187
+ * const db = client.database('my-repo');
4188
+ * const { tasks } = await db.inbox.listTasks({ status: 'pending' });
4189
+ * await db.inbox.completeTask(tasks[0].id, { action: 'approve' });
4190
+ * ```
4191
+ */
4192
+ get inbox() {
4193
+ if (!this._inboxApi) {
4194
+ if (!this.httpOptions?.httpBaseUrl || !this.httpOptions?.authManager) {
4195
+ throw new Error(
4196
+ "db.inbox requires HTTP context. Use client.database() to get a Database with inbox support."
4197
+ );
4198
+ }
4199
+ this._inboxApi = new InboxApi(
4200
+ this.httpOptions.httpBaseUrl,
4201
+ this.repository,
4202
+ this.httpOptions.authManager
4203
+ );
4204
+ }
4205
+ return this._inboxApi;
4206
+ }
4851
4207
  /**
4852
4208
  * Get EventHandler for subscribing to real-time events
4209
+ *
4210
+ * Path filter semantics for subscriptions (matched server-side):
4211
+ * - A plain path matches **only that exact node** — there is no implicit
4212
+ * prefix matching.
4213
+ * - `*` matches exactly one path segment (`/inbox/*` matches `/inbox/a`,
4214
+ * not `/inbox/a/b`).
4215
+ * - `**` matches recursively (`/inbox/**` matches all descendants of
4216
+ * `/inbox`, but not `/inbox` itself).
4853
4217
  */
4854
4218
  events() {
4855
4219
  return this.eventHandler;
@@ -4857,7 +4221,7 @@ var Database = class _Database {
4857
4221
  };
4858
4222
 
4859
4223
  // src/http-client.ts
4860
- import { EventEmitter as EventEmitter2 } from "events";
4224
+ import { EventEmitter } from "events";
4861
4225
 
4862
4226
  // src/upload/types.ts
4863
4227
  var UploadErrorCode = /* @__PURE__ */ ((UploadErrorCode2) => {
@@ -5220,7 +4584,7 @@ var BatchUploader = class {
5220
4584
  /**
5221
4585
  * Create a batch uploader
5222
4586
  */
5223
- constructor(config2, files, options) {
4587
+ constructor(config, files, options) {
5224
4588
  this.fileStates = /* @__PURE__ */ new Map();
5225
4589
  this.queue = [];
5226
4590
  this.speedTracker = new BatchSpeedTracker();
@@ -5230,7 +4594,7 @@ var BatchUploader = class {
5230
4594
  this.started = false;
5231
4595
  this.resolveWhenResumed = null;
5232
4596
  this.lastBytesUploaded = 0;
5233
- this.config = config2;
4597
+ this.config = config;
5234
4598
  this.files = files;
5235
4599
  this.options = {
5236
4600
  repository: options.repository,
@@ -5684,7 +5048,7 @@ var Uploader = class {
5684
5048
  /**
5685
5049
  * Create a new Uploader
5686
5050
  */
5687
- constructor(config2, fileSource, options, uploadId) {
5051
+ constructor(config, fileSource, options, uploadId) {
5688
5052
  this.session = null;
5689
5053
  this.status = "pending";
5690
5054
  this.bytesUploaded = 0;
@@ -5693,10 +5057,10 @@ var Uploader = class {
5693
5057
  this.speedTracker = new SpeedTracker();
5694
5058
  this.pauseRequested = false;
5695
5059
  this.abortController = null;
5696
- this.baseUrl = config2.baseUrl.replace(/\/$/, "");
5697
- this.authManager = config2.authManager;
5698
- this.fetchImpl = config2.fetchImpl ?? fetch;
5699
- this.requestTimeout = config2.requestTimeout ?? 3e4;
5060
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
5061
+ this.authManager = config.authManager;
5062
+ this.fetchImpl = config.fetchImpl ?? fetch;
5063
+ this.requestTimeout = config.requestTimeout ?? 3e4;
5700
5064
  this.fileSource = fileSource;
5701
5065
  this.id = uploadId ?? generateUploadId();
5702
5066
  this.options = {
@@ -6062,9 +5426,9 @@ var UploadManager = class {
6062
5426
  /**
6063
5427
  * Create an upload manager
6064
5428
  */
6065
- constructor(config2) {
5429
+ constructor(config) {
6066
5430
  this.uploads = /* @__PURE__ */ new Map();
6067
- this.config = config2;
5431
+ this.config = config;
6068
5432
  }
6069
5433
  /**
6070
5434
  * Create a new upload
@@ -6128,10 +5492,194 @@ var UploadManager = class {
6128
5492
  }
6129
5493
  };
6130
5494
 
5495
+ // src/http-schema.ts
5496
+ var HttpSchemaBase = class {
5497
+ constructor(repository, branch, client) {
5498
+ this.repository = repository;
5499
+ this.branch = branch;
5500
+ this.client = client;
5501
+ }
5502
+ /** Base management URL for this schema kind. */
5503
+ base() {
5504
+ return `/api/management/${this.repository}/${this.branch}/${this.kind}`;
5505
+ }
5506
+ /** List all schemas (or only published ones). */
5507
+ async list(publishedOnly = false) {
5508
+ const path = publishedOnly ? `${this.base()}/published` : this.base();
5509
+ const result = await this.client.managementRequest("GET", path);
5510
+ return Array.isArray(result) ? result : [];
5511
+ }
5512
+ /** Get a single schema by name. */
5513
+ async get(name) {
5514
+ return this.client.managementRequest("GET", `${this.base()}/${encodeURIComponent(name)}`);
5515
+ }
5516
+ /** Get the schema with full inheritance (`extends`) applied. */
5517
+ async getResolved(name) {
5518
+ return this.client.managementRequest(
5519
+ "GET",
5520
+ `${this.base()}/${encodeURIComponent(name)}/resolved`
5521
+ );
5522
+ }
5523
+ /** Create a schema. `name` is folded into the definition body if absent. */
5524
+ async create(name, definition, commit) {
5525
+ return this.client.managementRequest("POST", this.base(), {
5526
+ [this.defField]: { name, ...definition },
5527
+ commit
5528
+ });
5529
+ }
5530
+ /** Update an existing schema. */
5531
+ async update(name, definition, commit) {
5532
+ return this.client.managementRequest(
5533
+ "PUT",
5534
+ `${this.base()}/${encodeURIComponent(name)}`,
5535
+ { [this.defField]: { name, ...definition }, commit }
5536
+ );
5537
+ }
5538
+ /** Delete a schema. */
5539
+ async delete(name, commit) {
5540
+ return this.client.managementRequest(
5541
+ "DELETE",
5542
+ `${this.base()}/${encodeURIComponent(name)}`,
5543
+ commit
5544
+ );
5545
+ }
5546
+ /** Publish a schema. */
5547
+ async publish(name, commit) {
5548
+ return this.client.managementRequest(
5549
+ "POST",
5550
+ `${this.base()}/${encodeURIComponent(name)}/publish`,
5551
+ commit
5552
+ );
5553
+ }
5554
+ /** Unpublish a schema. */
5555
+ async unpublish(name, commit) {
5556
+ return this.client.managementRequest(
5557
+ "POST",
5558
+ `${this.base()}/${encodeURIComponent(name)}/unpublish`,
5559
+ commit
5560
+ );
5561
+ }
5562
+ };
5563
+ var HttpNodeTypes = class extends HttpSchemaBase {
5564
+ constructor() {
5565
+ super(...arguments);
5566
+ this.kind = "nodetypes";
5567
+ this.defField = "node_type";
5568
+ }
5569
+ /**
5570
+ * Get the resolved NodeType. Unlike archetypes/element types, NodeType
5571
+ * resolution can be workspace-aware (honors workspace NodeType pins).
5572
+ */
5573
+ async getResolved(name, opts) {
5574
+ const query = opts?.workspace ? `?workspace=${encodeURIComponent(opts.workspace)}` : "";
5575
+ return this.client.managementRequest(
5576
+ "GET",
5577
+ `${this.base()}/${encodeURIComponent(name)}/resolved${query}`
5578
+ );
5579
+ }
5580
+ };
5581
+ var HttpArchetypes = class extends HttpSchemaBase {
5582
+ constructor() {
5583
+ super(...arguments);
5584
+ this.kind = "archetypes";
5585
+ this.defField = "archetype";
5586
+ }
5587
+ };
5588
+ var HttpElementTypes = class extends HttpSchemaBase {
5589
+ constructor() {
5590
+ super(...arguments);
5591
+ this.kind = "elementtypes";
5592
+ this.defField = "element_type";
5593
+ }
5594
+ };
5595
+
5596
+ // src/mcp.ts
5597
+ var McpClient = class {
5598
+ constructor(transport) {
5599
+ this.transport = transport;
5600
+ }
5601
+ /**
5602
+ * List the tools this MCP server advertises to the calling identity.
5603
+ *
5604
+ * Wraps the `tools/list` JSON-RPC method. The visible set is RLS-scoped to
5605
+ * the authenticated caller (or the anonymous role).
5606
+ *
5607
+ * @returns The advertised tool descriptors.
5608
+ */
5609
+ async listTools() {
5610
+ const result = await this.transport.rpc("tools/list");
5611
+ return { tools: result?.tools ?? [] };
5612
+ }
5613
+ /**
5614
+ * Invoke a tool by name.
5615
+ *
5616
+ * Wraps the `tools/call` JSON-RPC method. The underlying `raisin:Function`
5617
+ * runs RLS-scoped to the calling identity. A domain-level tool failure is
5618
+ * reported via {@link McpCallToolResult.isError} (not thrown); transport and
5619
+ * protocol errors are thrown.
5620
+ *
5621
+ * @param name - Tool name from {@link listTools}.
5622
+ * @param args - Arguments object matching the tool's `inputSchema`.
5623
+ * @returns The tool's content blocks and optional structured content.
5624
+ */
5625
+ async callTool(name, args) {
5626
+ const result = await this.transport.rpc("tools/call", {
5627
+ name,
5628
+ arguments: args ?? {}
5629
+ });
5630
+ return result;
5631
+ }
5632
+ /**
5633
+ * Read a resource by its `raisin://` URI.
5634
+ *
5635
+ * Wraps the `resources/read` JSON-RPC method. JSON-only resources return
5636
+ * their properties as a `text` entry; byte-serving resources return base64
5637
+ * in `blob`.
5638
+ *
5639
+ * @param uri - `raisin://{workspace}/{path}` URI to read.
5640
+ * @returns One content entry per URI read.
5641
+ */
5642
+ async readResource(uri) {
5643
+ const result = await this.transport.rpc("resources/read", {
5644
+ uri
5645
+ });
5646
+ return { contents: result?.contents ?? [] };
5647
+ }
5648
+ /**
5649
+ * Subscribe to change notifications for a resource URI (or URI prefix).
5650
+ *
5651
+ * Wraps the `resources/subscribe` JSON-RPC method, which upgrades to an SSE
5652
+ * stream. The returned {@link McpResourceSubscription} is an async iterable of
5653
+ * {@link McpResourceUpdate}; the server's initial subscription ack is consumed
5654
+ * internally and not yielded. Call `close()` to end the stream.
5655
+ *
5656
+ * @param uri - `raisin://` URI (or prefix) to watch.
5657
+ * @returns A live, closeable subscription.
5658
+ */
5659
+ subscribeResource(uri) {
5660
+ const stream = this.transport.subscribe("resources/subscribe", { uri });
5661
+ return {
5662
+ close: () => stream.close(),
5663
+ async *[Symbol.asyncIterator]() {
5664
+ for await (const frame of stream) {
5665
+ if (frame.method === "notifications/resources/updated") {
5666
+ const params = frame.params ?? {};
5667
+ if (typeof params.uri === "string") {
5668
+ yield { uri: params.uri };
5669
+ }
5670
+ }
5671
+ }
5672
+ }
5673
+ };
5674
+ }
5675
+ };
5676
+
6131
5677
  // src/http-client.ts
6132
- var RaisinHttpClient = class extends EventEmitter2 {
5678
+ var RaisinHttpClient = class extends EventEmitter {
6133
5679
  constructor(baseUrl, options = {}) {
6134
5680
  super();
5681
+ /** Monotonic JSON-RPC id counter for MCP requests. */
5682
+ this._mcpRequestId = 0;
6135
5683
  this._currentUser = null;
6136
5684
  this._uploadManager = null;
6137
5685
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -6177,9 +5725,13 @@ var RaisinHttpClient = class extends EventEmitter2 {
6177
5725
  body: payload,
6178
5726
  skipAuth: true
6179
5727
  });
6180
- this.authManager.setTokens(response.data);
5728
+ if (response.data.token) {
5729
+ this.authManager.storage.setAccessToken(response.data.token);
5730
+ } else {
5731
+ this.authManager.setTokens(response.data);
5732
+ }
6181
5733
  this._currentUser = {
6182
- userId: "admin",
5734
+ userId: response.data.user_id ?? "admin",
6183
5735
  anonymous: false
6184
5736
  };
6185
5737
  this.emit("authenticated", response.data);
@@ -6369,14 +5921,14 @@ var RaisinHttpClient = class extends EventEmitter2 {
6369
5921
  * @param config - Repository configuration
6370
5922
  * @returns Repository creation result
6371
5923
  */
6372
- async createRepository(repositoryId, description, config2) {
5924
+ async createRepository(repositoryId, description, config) {
6373
5925
  const response = await this.request({
6374
5926
  method: "POST",
6375
5927
  path: "/api/repositories",
6376
5928
  body: {
6377
5929
  repository_id: repositoryId,
6378
5930
  description,
6379
- config: config2
5931
+ config
6380
5932
  }
6381
5933
  });
6382
5934
  return response.data;
@@ -6414,13 +5966,13 @@ var RaisinHttpClient = class extends EventEmitter2 {
6414
5966
  * @param config - New repository configuration
6415
5967
  * @returns Repository update result
6416
5968
  */
6417
- async updateRepository(repositoryId, description, config2) {
5969
+ async updateRepository(repositoryId, description, config) {
6418
5970
  const response = await this.request({
6419
5971
  method: "PUT",
6420
5972
  path: `/api/repositories/${repositoryId}`,
6421
5973
  body: {
6422
5974
  description,
6423
- config: config2
5975
+ config
6424
5976
  }
6425
5977
  });
6426
5978
  return response.data;
@@ -6823,6 +6375,107 @@ var RaisinHttpClient = class extends EventEmitter2 {
6823
6375
  });
6824
6376
  return response.data;
6825
6377
  }
6378
+ /**
6379
+ * Send one JSON-RPC request to an MCP server endpoint and return its
6380
+ * unwrapped `result`.
6381
+ *
6382
+ * Calls `POST /mcp/{repo}/{branch}/{slug}` carrying a single JSON-RPC 2.0
6383
+ * message. MCP errors are returned in the JSON-RPC envelope (HTTP `200`), so
6384
+ * this method inspects the body's `error` member and throws on failure.
6385
+ *
6386
+ * @internal Used by {@link McpClient} via the transport built in `mcp()`.
6387
+ */
6388
+ async mcpRpc(repository, branch, slug, method, params) {
6389
+ const id = ++this._mcpRequestId;
6390
+ const response = await this.request({
6391
+ method: "POST",
6392
+ path: `/mcp/${repository}/${branch}/${slug}`,
6393
+ body: {
6394
+ jsonrpc: "2.0",
6395
+ id,
6396
+ method,
6397
+ ...params !== void 0 ? { params } : {}
6398
+ }
6399
+ });
6400
+ const frame = response.data;
6401
+ if (frame?.error) {
6402
+ const err = new Error(frame.error.message || `MCP error ${frame.error.code}`);
6403
+ err.code = frame.error.code;
6404
+ err.data = frame.error.data;
6405
+ throw err;
6406
+ }
6407
+ return frame?.result;
6408
+ }
6409
+ /**
6410
+ * Open the `resources/subscribe` SSE stream for an MCP server endpoint.
6411
+ *
6412
+ * Streams JSON-RPC frames (the subscription ack followed by
6413
+ * `notifications/resources/updated` frames) from
6414
+ * `POST /mcp/{repo}/{branch}/{slug}`.
6415
+ *
6416
+ * @internal Used by {@link McpClient} via the transport built in `mcp()`.
6417
+ */
6418
+ mcpSubscribe(repository, branch, slug, method, params) {
6419
+ const headers = {};
6420
+ if (this.authManager.isAuthenticated()) {
6421
+ const token = this.authManager.getAccessToken();
6422
+ if (token) {
6423
+ headers["Authorization"] = `Bearer ${token}`;
6424
+ }
6425
+ }
6426
+ const sse = new SSEClient(
6427
+ `${this.baseUrl}/mcp/${repository}/${branch}/${slug}`,
6428
+ {
6429
+ method: "POST",
6430
+ body: {
6431
+ jsonrpc: "2.0",
6432
+ id: ++this._mcpRequestId,
6433
+ method,
6434
+ params
6435
+ },
6436
+ headers,
6437
+ fetch: this.fetchImpl,
6438
+ reconnect: { enabled: false }
6439
+ }
6440
+ );
6441
+ return {
6442
+ close: () => sse.close(),
6443
+ async *[Symbol.asyncIterator]() {
6444
+ for await (const event of sse) {
6445
+ yield event.data;
6446
+ }
6447
+ }
6448
+ };
6449
+ }
6450
+ /**
6451
+ * Get an {@link McpClient} for a content-declared MCP server.
6452
+ *
6453
+ * @param slug - The `raisin:McpServer` slug (resolved in the `mcp` workspace).
6454
+ * @param branch - Branch to address; defaults to the client's current branch.
6455
+ */
6456
+ mcp(slug, branch) {
6457
+ const repository = this._context.repository;
6458
+ if (!repository) {
6459
+ throw new Error("mcp() requires a repository; call database(name) first");
6460
+ }
6461
+ const effectiveBranch = branch ?? this.getBranch();
6462
+ const transport = {
6463
+ rpc: (m, p) => this.mcpRpc(repository, effectiveBranch, slug, m, p),
6464
+ subscribe: (m, p) => this.mcpSubscribe(repository, effectiveBranch, slug, m, p)
6465
+ };
6466
+ return new McpClient(transport);
6467
+ }
6468
+ /**
6469
+ * Make a management REST request and return the parsed body.
6470
+ *
6471
+ * @internal Used by the HTTP schema-management helpers
6472
+ * ({@link HttpNodeTypes}/{@link HttpArchetypes}/{@link HttpElementTypes}) to
6473
+ * reuse the client's centralized auth, timeout, and base-URL handling.
6474
+ */
6475
+ async managementRequest(method, path, body) {
6476
+ const response = await this.request({ method, path, body });
6477
+ return response.data;
6478
+ }
6826
6479
  /**
6827
6480
  * Get a simplified database interface for SSR
6828
6481
  * This returns an HTTP-based database client
@@ -6910,6 +6563,36 @@ var HttpDatabase = class _HttpDatabase {
6910
6563
  (repo, name, input) => this.client.invokeFunctionSync(repo, name, input)
6911
6564
  );
6912
6565
  }
6566
+ /**
6567
+ * Get an {@link McpClient} for a content-declared MCP server on this database.
6568
+ *
6569
+ * @param slug - The `raisin:McpServer` slug (resolved in the `mcp` workspace).
6570
+ * @returns A client scoped to `{repo}/{branch}/{slug}`, using this database's
6571
+ * branch (falling back to the HTTP client's current branch).
6572
+ */
6573
+ mcp(slug) {
6574
+ const transport = {
6575
+ rpc: (m, p) => this.client.mcpRpc(this.repository, this.schemaBranch(), slug, m, p),
6576
+ subscribe: (m, p) => this.client.mcpSubscribe(this.repository, this.schemaBranch(), slug, m, p)
6577
+ };
6578
+ return new McpClient(transport);
6579
+ }
6580
+ /** Effective branch for management calls. */
6581
+ schemaBranch() {
6582
+ return this.branch ?? this.client.getBranch();
6583
+ }
6584
+ /** NodeType management (get/list/getResolved/create/update/delete/publish/unpublish). */
6585
+ nodeTypes() {
6586
+ return new HttpNodeTypes(this.repository, this.schemaBranch(), this.client);
6587
+ }
6588
+ /** Archetype management (get/list/getResolved/create/update/delete/publish/unpublish). */
6589
+ archetypes() {
6590
+ return new HttpArchetypes(this.repository, this.schemaBranch(), this.client);
6591
+ }
6592
+ /** ElementType management (get/list/getResolved/create/update/delete/publish/unpublish). */
6593
+ elementTypes() {
6594
+ return new HttpElementTypes(this.repository, this.schemaBranch(), this.client);
6595
+ }
6913
6596
  };
6914
6597
  var HttpWorkspaceClient = class {
6915
6598
  constructor(repository, workspace, client) {
@@ -7067,10 +6750,13 @@ var HttpWorkspaceClient = class {
7067
6750
  };
7068
6751
 
7069
6752
  // src/client.ts
7070
- var RaisinClient = class _RaisinClient extends EventEmitter3 {
6753
+ var _refreshingUser;
6754
+ var _RaisinClient = class _RaisinClient extends EventEmitter2 {
7071
6755
  constructor(url, options = {}) {
7072
6756
  super();
7073
6757
  this._currentUser = null;
6758
+ /** In-flight role re-resolve, used to coalesce concurrent refreshCurrentUser() calls. */
6759
+ __privateAdd(this, _refreshingUser, null);
7074
6760
  // Auth state change listeners (Firebase/Supabase pattern)
7075
6761
  this._authListeners = /* @__PURE__ */ new Set();
7076
6762
  this._userChangeListeners = /* @__PURE__ */ new Set();
@@ -7081,23 +6767,43 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7081
6767
  // Reconnection tracking
7082
6768
  this._previousConnectionState = "disconnected" /* Disconnected */;
7083
6769
  this._reconnectedListeners = /* @__PURE__ */ new Set();
6770
+ this._everConnected = false;
6771
+ // Requests queued while the connection is re-establishing.
6772
+ // Flushed after reconnect + re-auth; bounded by MAX_QUEUED_REQUESTS.
6773
+ this._queuedRequests = [];
6774
+ /**
6775
+ * Backoff delays (ms) between subscription-restore retries after reconnect.
6776
+ * @internal - overridable in tests
6777
+ */
6778
+ this._restoreRetryDelaysMs = [1e3, 3e3, 9e3];
7084
6779
  // Upload manager (uses HTTP under the hood)
7085
6780
  this._uploadManager = null;
6781
+ /** In-flight refresh shared by concurrent refreshToken() callers. */
6782
+ this._refreshInFlight = null;
7086
6783
  if (options.logLevel !== void 0) {
7087
6784
  setLogLevel(options.logLevel);
7088
6785
  }
6786
+ const connectionUrl = this.buildConnectionUrl(url, options.repository);
7089
6787
  this.options = {
7090
6788
  requestTimeout: options.requestTimeout ?? 3e4,
7091
- tenantId: options.tenantId ?? this.extractTenantFromUrl(url),
6789
+ tenantId: options.tenantId ?? this.extractTenantFromUrl(connectionUrl),
7092
6790
  defaultBranch: options.defaultBranch ?? "main"
7093
6791
  };
7094
- this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(url);
7095
- this._repository = this.extractRepositoryFromUrl(url);
6792
+ this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(connectionUrl);
6793
+ this._repository = options.repository ?? this.extractRepositoryFromUrl(connectionUrl);
7096
6794
  this._context = {
7097
6795
  tenant_id: this.options.tenantId,
7098
6796
  branch: this.options.defaultBranch
7099
6797
  };
7100
- this.connection = new Connection(url, options.connection);
6798
+ const explicitTenant = options.tenantId ?? (this.extractTenantFromUrl(url) !== "default" ? this.options.tenantId : void 0);
6799
+ const connectionOptions = {
6800
+ ...options.connection,
6801
+ headers: {
6802
+ ...explicitTenant ? { "x-tenant-id": explicitTenant } : {},
6803
+ ...options.connection?.headers
6804
+ }
6805
+ };
6806
+ this.connection = this.createConnection(connectionUrl, connectionOptions);
7101
6807
  this.requestTracker = new RequestTracker({
7102
6808
  defaultTimeout: this.options.requestTimeout
7103
6809
  });
@@ -7107,6 +6813,14 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7107
6813
  );
7108
6814
  this.setupConnectionHandlers();
7109
6815
  }
6816
+ /**
6817
+ * Create the underlying WebSocket connection.
6818
+ * Seam for tests to inject a fake connection.
6819
+ * @internal
6820
+ */
6821
+ createConnection(url, options) {
6822
+ return new Connection(url, options);
6823
+ }
7110
6824
  /**
7111
6825
  * Derive HTTP URL from WebSocket URL
7112
6826
  * ws://host:port/path -> http://host:port
@@ -7122,15 +6836,45 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7122
6836
  return "http://localhost:8081";
7123
6837
  }
7124
6838
  }
6839
+ /**
6840
+ * Build the WebSocket connection URL.
6841
+ *
6842
+ * When a repository option is provided and the URL carries no path
6843
+ * (e.g. `ws://host:8081`), build the tenant-less `/ws/{repository}`
6844
+ * route internally. URLs that already carry a path are used as-is.
6845
+ */
6846
+ buildConnectionUrl(url, repository) {
6847
+ if (!repository) {
6848
+ return url;
6849
+ }
6850
+ try {
6851
+ const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
6852
+ const parsed = new URL(wsUrl);
6853
+ const hasPath = parsed.pathname.split("/").some((p) => p.length > 0);
6854
+ if (hasPath) {
6855
+ return url;
6856
+ }
6857
+ return `${url.replace(/\/+$/, "")}/ws/${repository}`;
6858
+ } catch {
6859
+ return url;
6860
+ }
6861
+ }
7125
6862
  /**
7126
6863
  * Extract repository from URL
7127
- * URL format: raisin://host:port/tenant/repository or ws://host:port/tenant/repository
6864
+ * URL forms: /ws/{repo} (tenant-less, preferred), /sys/{tenant}/{repo}
6865
+ * (operator/multi-tenant), /{tenant}/{repo} (legacy).
7128
6866
  */
7129
6867
  extractRepositoryFromUrl(url) {
7130
6868
  try {
7131
6869
  const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
7132
6870
  const parsed = new URL(wsUrl);
7133
6871
  const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
6872
+ if (parts[0] === "ws") {
6873
+ return parts.length > 1 ? parts[1] : "";
6874
+ }
6875
+ if (parts[0] === "sys") {
6876
+ return parts.length > 2 ? parts[2] : "";
6877
+ }
7134
6878
  return parts.length > 1 ? parts[1] : "";
7135
6879
  } catch {
7136
6880
  return "";
@@ -7138,14 +6882,24 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7138
6882
  }
7139
6883
  /**
7140
6884
  * Extract tenant ID from URL
7141
- * URL format: raisin://host:port/tenant/repository or ws://host:port/tenant/repository
6885
+ * URL forms: /ws/{repo} (tenant-less — no tenant in URL),
6886
+ * /sys/{tenant}/{repo} (canonical), /{tenant}/{repo} (legacy).
6887
+ * When the URL doesn't carry a tenant, fall back to 'default' — the
6888
+ * server's tenant middleware resolves it anyway (production derives the
6889
+ * tenant from the request itself), so an explicit tenantId is optional.
7142
6890
  */
7143
6891
  extractTenantFromUrl(url) {
7144
6892
  try {
7145
6893
  const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
7146
6894
  const parsed = new URL(wsUrl);
7147
6895
  const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
7148
- return parts.length > 0 ? parts[0] : "default";
6896
+ if (parts[0] === "ws") {
6897
+ return "default";
6898
+ }
6899
+ if (parts[0] === "sys") {
6900
+ return parts.length > 1 ? parts[1] : "default";
6901
+ }
6902
+ return parts.length >= 2 ? parts[0] : "default";
7149
6903
  } catch (error) {
7150
6904
  return "default";
7151
6905
  }
@@ -7159,7 +6913,11 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7159
6913
  });
7160
6914
  this.connection.on("stateChange", (state) => {
7161
6915
  const wasDisconnected = this._previousConnectionState === "disconnected" /* Disconnected */ || this._previousConnectionState === "reconnecting" /* Reconnecting */;
6916
+ if (state === "connected" /* Connected */) {
6917
+ this._everConnected = true;
6918
+ }
7162
6919
  if (state === "disconnected" /* Disconnected */ || state === "closed" /* Closed */) {
6920
+ this._queuedRequests = [];
7163
6921
  this.requestTracker.cancelAll();
7164
6922
  }
7165
6923
  if (state === "connected" /* Connected */ && wasDisconnected && this._previousConnectionState !== "disconnected" /* Disconnected */) {
@@ -7190,8 +6948,13 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7190
6948
  if (isConnectedMessage(message)) {
7191
6949
  this.handleConnectedMessage(message);
7192
6950
  } else if (isEventMessage(message)) {
7193
- logger.debug(`Routing to event handler`);
7194
- this.eventHandler.handleEvent(message);
6951
+ if (message.event_type === "permissions:changed") {
6952
+ logger.info("[permissions:changed] server signaled a role/permission change; re-resolving");
6953
+ void this.refreshCurrentUser();
6954
+ } else {
6955
+ logger.debug(`Routing to event handler`);
6956
+ this.eventHandler.handleEvent(message);
6957
+ }
7195
6958
  } else if (isResponseEnvelope(message)) {
7196
6959
  logger.debug(`Routing to response handler - request_id: ${message.request_id}, status: ${message.status}`);
7197
6960
  this.handleResponse(message);
@@ -7224,19 +6987,36 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7224
6987
  };
7225
6988
  logger.debug(`Anonymous user info stored: ${message.user_id}`);
7226
6989
  this._updateReadyState();
6990
+ this._flushQueuedRequests();
7227
6991
  if (hasActiveSubscriptions) {
7228
6992
  this._restoreSubscriptionsAndNotify();
7229
6993
  }
7230
6994
  } else {
7231
6995
  logger.info(`[handleConnectedMessage] Stored token found, auto-authenticating...`);
7232
- this.autoReauthenticate().then(async () => {
6996
+ this.autoReauthenticate().then(async (authenticated) => {
6997
+ if (authenticated) {
6998
+ this._flushQueuedRequests();
6999
+ } else {
7000
+ this._rejectQueuedRequests(
7001
+ new RaisinAuthError(
7002
+ "Re-authentication failed after reconnect",
7003
+ "AUTH_TOKEN_EXPIRED",
7004
+ 401
7005
+ )
7006
+ );
7007
+ }
7233
7008
  if (hasActiveSubscriptions) {
7234
7009
  await this._restoreSubscriptionsAndNotify();
7235
7010
  }
7236
7011
  }).catch((err) => {
7237
7012
  logger.error(`[handleConnectedMessage] Auto-authenticate failed:`, err);
7013
+ this._rejectQueuedRequests(
7014
+ err instanceof Error ? err : new Error("Re-authentication failed after reconnect")
7015
+ );
7238
7016
  });
7239
7017
  }
7018
+ } else {
7019
+ this._flushQueuedRequests();
7240
7020
  }
7241
7021
  this.emit("connected", {
7242
7022
  connectionId: message.connection_id,
@@ -7245,18 +7025,41 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7245
7025
  });
7246
7026
  }
7247
7027
  /**
7248
- * Restore subscriptions after reconnection and notify listeners
7028
+ * Restore subscriptions after reconnection and notify listeners.
7029
+ *
7030
+ * Retries with backoff (see `_restoreRetryDelaysMs`); on final failure a
7031
+ * `subscription_restore_failed` event is emitted so applications can
7032
+ * resync their state (e.g. reload lists and re-subscribe manually).
7249
7033
  * @internal
7250
7034
  */
7251
7035
  async _restoreSubscriptionsAndNotify() {
7252
- try {
7253
- logger.info("[_restoreSubscriptionsAndNotify] Restoring subscriptions...");
7254
- await this.eventHandler.restoreSubscriptions();
7255
- logger.info("[_restoreSubscriptionsAndNotify] Subscriptions restored successfully");
7256
- this._emitReconnected();
7257
- } catch (error) {
7258
- logger.error("[_restoreSubscriptionsAndNotify] Failed to restore subscriptions:", error);
7036
+ const delays = this._restoreRetryDelaysMs;
7037
+ let lastError;
7038
+ for (let attempt = 0; attempt <= delays.length; attempt++) {
7039
+ try {
7040
+ logger.info(
7041
+ `[_restoreSubscriptionsAndNotify] Restoring subscriptions (attempt ${attempt + 1}/${delays.length + 1})...`
7042
+ );
7043
+ await this.eventHandler.restoreSubscriptions({ retry: attempt > 0 });
7044
+ logger.info("[_restoreSubscriptionsAndNotify] Subscriptions restored successfully");
7045
+ this._emitReconnected();
7046
+ return;
7047
+ } catch (error) {
7048
+ lastError = error;
7049
+ logger.error(
7050
+ `[_restoreSubscriptionsAndNotify] Restore attempt ${attempt + 1} failed:`,
7051
+ error
7052
+ );
7053
+ if (attempt < delays.length) {
7054
+ await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
7055
+ }
7056
+ }
7259
7057
  }
7058
+ logger.error("[_restoreSubscriptionsAndNotify] Giving up after retries; events may be stale");
7059
+ this.emit(
7060
+ "subscription_restore_failed",
7061
+ lastError instanceof Error ? lastError : new Error(String(lastError))
7062
+ );
7260
7063
  }
7261
7064
  /**
7262
7065
  * Emit reconnected event to all listeners
@@ -7275,13 +7078,15 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7275
7078
  }
7276
7079
  /**
7277
7080
  * Auto-reauthenticate with stored JWT on reconnection
7081
+ *
7082
+ * @returns true if the session was re-authenticated, false otherwise
7278
7083
  * @internal
7279
7084
  */
7280
7085
  async autoReauthenticate() {
7281
7086
  const token = this.authManager.storage.getAccessToken();
7282
7087
  if (!token) {
7283
7088
  logger.warn("[autoReauthenticate] No stored token");
7284
- return;
7089
+ return false;
7285
7090
  }
7286
7091
  const payload = AuthManager.parseToken(token);
7287
7092
  if (payload?.exp && typeof payload.exp === "number") {
@@ -7293,18 +7098,20 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7293
7098
  this.authManager.clear();
7294
7099
  this._currentUser = null;
7295
7100
  this._emitAuthEvent("SESSION_EXPIRED");
7296
- return;
7101
+ return false;
7297
7102
  }
7298
7103
  const newToken = this.authManager.storage.getAccessToken();
7299
7104
  if (newToken) {
7300
7105
  await this.authenticate({ type: "jwt", token: newToken });
7106
+ return true;
7301
7107
  }
7302
- return;
7108
+ return false;
7303
7109
  }
7304
7110
  }
7305
7111
  try {
7306
7112
  await this.authenticate({ type: "jwt", token });
7307
7113
  logger.info("[autoReauthenticate] Successfully re-authenticated");
7114
+ return true;
7308
7115
  } catch (err) {
7309
7116
  logger.error("[autoReauthenticate] Failed to authenticate:", err);
7310
7117
  const refreshed = await this.refreshToken();
@@ -7312,11 +7119,14 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7312
7119
  const newToken = this.authManager.storage.getAccessToken();
7313
7120
  if (newToken) {
7314
7121
  await this.authenticate({ type: "jwt", token: newToken });
7122
+ return true;
7315
7123
  }
7124
+ return false;
7316
7125
  } else {
7317
7126
  this.authManager.clear();
7318
7127
  this._currentUser = null;
7319
7128
  this._emitAuthEvent("SESSION_EXPIRED");
7129
+ return false;
7320
7130
  }
7321
7131
  }
7322
7132
  }
@@ -7432,18 +7242,18 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7432
7242
  }
7433
7243
  const url = `${this._httpBaseUrl}/auth/${repository}/login`;
7434
7244
  logger.debug("[loginWithEmail] POST", url);
7435
- const response = await fetch(url, {
7245
+ const response = await this._fetchWithTimeout(url, {
7436
7246
  method: "POST",
7437
7247
  headers: { "Content-Type": "application/json" },
7438
7248
  body: JSON.stringify({ email, password })
7439
7249
  });
7440
7250
  if (!response.ok) {
7441
7251
  const error = await response.json().catch(() => ({ message: response.statusText }));
7442
- const authError = {
7443
- code: error.code || "LOGIN_FAILED",
7444
- message: error.message || "Login failed"
7445
- };
7446
- throw authError;
7252
+ throw new RaisinAuthError(
7253
+ error.message || "Login failed",
7254
+ error.code || "LOGIN_FAILED",
7255
+ response.status
7256
+ );
7447
7257
  }
7448
7258
  const tokens = await response.json();
7449
7259
  logger.info("[loginWithEmail] HTTP login successful, user_id:", tokens.identity.id);
@@ -7472,11 +7282,11 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7472
7282
  }
7473
7283
  this._currentUser = {
7474
7284
  userId: user.id,
7475
- roles: [],
7285
+ roles: this._currentUser?.roles ?? [],
7476
7286
  anonymous: false,
7477
7287
  node: userNode ?? void 0
7478
7288
  };
7479
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
7289
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7480
7290
  if (expiresInMs > 0) {
7481
7291
  this.scheduleAutoRefresh(expiresInMs);
7482
7292
  }
@@ -7500,18 +7310,18 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7500
7310
  async registerWithEmail(email, password, repository, displayName) {
7501
7311
  logger.info("[registerWithEmail] Starting registration...");
7502
7312
  const url = `${this._httpBaseUrl}/auth/${repository}/register`;
7503
- const response = await fetch(url, {
7313
+ const response = await this._fetchWithTimeout(url, {
7504
7314
  method: "POST",
7505
7315
  headers: { "Content-Type": "application/json" },
7506
7316
  body: JSON.stringify({ email, password, display_name: displayName })
7507
7317
  });
7508
7318
  if (!response.ok) {
7509
7319
  const error = await response.json().catch(() => ({ message: response.statusText }));
7510
- const authError = {
7511
- code: error.code || "REGISTRATION_FAILED",
7512
- message: error.message || "Registration failed"
7513
- };
7514
- throw authError;
7320
+ throw new RaisinAuthError(
7321
+ error.message || "Registration failed",
7322
+ error.code || "REGISTRATION_FAILED",
7323
+ response.status
7324
+ );
7515
7325
  }
7516
7326
  const tokens = await response.json();
7517
7327
  logger.info("[registerWithEmail] Registration successful, user_id:", tokens.identity.id);
@@ -7538,11 +7348,11 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7538
7348
  }
7539
7349
  this._currentUser = {
7540
7350
  userId: user.id,
7541
- roles: [],
7351
+ roles: this._currentUser?.roles ?? [],
7542
7352
  anonymous: false,
7543
7353
  node: userNode ?? void 0
7544
7354
  };
7545
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
7355
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7546
7356
  if (expiresInMs > 0) {
7547
7357
  this.scheduleAutoRefresh(expiresInMs);
7548
7358
  }
@@ -7634,39 +7444,18 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7634
7444
  if (payload && payload.exp && typeof payload.exp === "number") {
7635
7445
  if (payload.exp * 1e3 < Date.now()) {
7636
7446
  logger.info("[initSession] Token expired, attempting refresh...");
7637
- const refreshToken = this.authManager.storage.getRefreshToken();
7638
- if (refreshToken) {
7639
- try {
7640
- const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
7641
- const response = await fetch(url, {
7642
- method: "POST",
7643
- headers: { "Content-Type": "application/json" },
7644
- body: JSON.stringify({ refresh_token: refreshToken })
7645
- });
7646
- if (response.ok) {
7647
- const tokens = await response.json();
7648
- this.authManager.storage.setAccessToken(tokens.access_token);
7649
- this.authManager.storage.setRefreshToken(tokens.refresh_token);
7650
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
7651
- if (expiresInMs > 0) {
7652
- this.scheduleAutoRefresh(expiresInMs);
7653
- }
7654
- logger.info("[initSession] Token refreshed successfully");
7655
- } else {
7656
- logger.warn("[initSession] Token refresh failed, clearing session");
7657
- this.authManager.clear();
7658
- return null;
7659
- }
7660
- } catch (err) {
7661
- logger.warn("[initSession] Token refresh error:", err);
7662
- this.authManager.clear();
7663
- return null;
7664
- }
7665
- } else {
7447
+ if (!this.authManager.storage.getRefreshToken()) {
7666
7448
  logger.warn("[initSession] No refresh token, clearing session");
7667
7449
  this.authManager.clear();
7668
7450
  return null;
7669
7451
  }
7452
+ const refreshed = await this.refreshToken();
7453
+ if (!refreshed) {
7454
+ logger.warn("[initSession] Token refresh failed, clearing session");
7455
+ this.authManager.clear();
7456
+ return null;
7457
+ }
7458
+ logger.info("[initSession] Token refreshed successfully");
7670
7459
  }
7671
7460
  }
7672
7461
  try {
@@ -7700,7 +7489,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7700
7489
  }
7701
7490
  this._currentUser = {
7702
7491
  userId: user.id,
7703
- roles: [],
7492
+ roles: this._currentUser?.roles ?? [],
7704
7493
  anonymous: false,
7705
7494
  node: userNode ?? void 0
7706
7495
  };
@@ -7756,6 +7545,16 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7756
7545
  * @throws Error if no refresh token is stored or refresh fails
7757
7546
  */
7758
7547
  async refreshToken() {
7548
+ if (this._refreshInFlight) {
7549
+ logger.debug("[refreshToken] Refresh already in flight, awaiting it");
7550
+ return this._refreshInFlight;
7551
+ }
7552
+ this._refreshInFlight = this._refreshToken().finally(() => {
7553
+ this._refreshInFlight = null;
7554
+ });
7555
+ return this._refreshInFlight;
7556
+ }
7557
+ async _refreshToken() {
7759
7558
  const refreshToken = this.authManager.storage.getRefreshToken();
7760
7559
  if (!refreshToken) {
7761
7560
  logger.warn("[refreshToken] No refresh token available");
@@ -7764,7 +7563,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7764
7563
  try {
7765
7564
  logger.info("[refreshToken] Refreshing access token...");
7766
7565
  const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
7767
- const response = await fetch(url, {
7566
+ const response = await this._fetchWithTimeout(url, {
7768
7567
  method: "POST",
7769
7568
  headers: { "Content-Type": "application/json" },
7770
7569
  body: JSON.stringify({ refresh_token: refreshToken })
@@ -7772,6 +7571,11 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7772
7571
  if (!response.ok) {
7773
7572
  const errorText = await response.text();
7774
7573
  logger.error("[refreshToken] Refresh failed:", response.status, errorText);
7574
+ if (/TOKEN_REUSE_DETECTED|TOKEN_FAMILY_MISMATCH/.test(errorText)) {
7575
+ logger.error(
7576
+ "[refreshToken] Session revoked by the server as a suspected token replay. If this happened on a normal page load, two refreshes raced on the same token."
7577
+ );
7578
+ }
7775
7579
  if (response.status === 401 || response.status === 403) {
7776
7580
  this.authManager.clear();
7777
7581
  this._currentUser = null;
@@ -7794,7 +7598,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7794
7598
  if (this.authManager.storage instanceof LocalStorageTokenStorage) {
7795
7599
  this.authManager.storage.setUser(user);
7796
7600
  }
7797
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
7601
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7798
7602
  if (expiresInMs > 0) {
7799
7603
  this.scheduleAutoRefresh(expiresInMs);
7800
7604
  }
@@ -7807,6 +7611,53 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7807
7611
  return null;
7808
7612
  }
7809
7613
  }
7614
+ /**
7615
+ * Fetch with a timeout derived from `requestTimeout`.
7616
+ *
7617
+ * Aborts the request after `requestTimeout` ms and rejects with a
7618
+ * `RaisinTimeoutError`. If the caller provides its own AbortSignal in
7619
+ * `init.signal`, it is respected: an external abort rejects with the
7620
+ * original AbortError instead of a timeout error.
7621
+ * @internal
7622
+ */
7623
+ async _fetchWithTimeout(url, init = {}) {
7624
+ const timeoutMs = this.options.requestTimeout;
7625
+ const controller = new AbortController();
7626
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
7627
+ const externalSignal = init.signal ?? void 0;
7628
+ if (externalSignal) {
7629
+ if (externalSignal.aborted) {
7630
+ controller.abort();
7631
+ } else {
7632
+ externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
7633
+ }
7634
+ }
7635
+ try {
7636
+ return await fetch(url, { ...init, signal: controller.signal });
7637
+ } catch (error) {
7638
+ if (error instanceof Error && error.name === "AbortError" && !externalSignal?.aborted) {
7639
+ throw new RaisinTimeoutError(
7640
+ `Request to ${url} timed out after ${timeoutMs}ms`,
7641
+ "REQUEST_TIMEOUT",
7642
+ timeoutMs
7643
+ );
7644
+ }
7645
+ throw error;
7646
+ } finally {
7647
+ clearTimeout(timer);
7648
+ }
7649
+ }
7650
+ /**
7651
+ * Normalize a token `expires_at` value to epoch milliseconds.
7652
+ *
7653
+ * Server endpoints are inconsistent: some return seconds, others
7654
+ * milliseconds. Anything above 1e11 can't be a seconds-epoch (year 5138),
7655
+ * so treat it as milliseconds.
7656
+ * @internal
7657
+ */
7658
+ static expiresAtToMs(expiresAt) {
7659
+ return expiresAt > 1e11 ? expiresAt : expiresAt * 1e3;
7660
+ }
7810
7661
  /**
7811
7662
  * Schedule automatic token refresh
7812
7663
  * @internal
@@ -7815,11 +7666,12 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7815
7666
  if (this._refreshTimer) {
7816
7667
  clearTimeout(this._refreshTimer);
7817
7668
  }
7818
- logger.debug(`[scheduleAutoRefresh] Scheduling refresh in ${Math.round(delayMs / 1e3 / 60)} minutes`);
7669
+ const clamped = Math.min(Math.max(delayMs, 3e4), 2147483647);
7670
+ logger.debug(`[scheduleAutoRefresh] Scheduling refresh in ${Math.round(clamped / 1e3 / 60)} minutes`);
7819
7671
  this._refreshTimer = setTimeout(async () => {
7820
7672
  logger.info("[autoRefresh] Auto-refreshing token...");
7821
7673
  await this.refreshToken();
7822
- }, delayMs);
7674
+ }, clamped);
7823
7675
  }
7824
7676
  /**
7825
7677
  * Get a database/repository interface
@@ -7843,10 +7695,26 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7843
7695
  }
7844
7696
  /**
7845
7697
  * Send a request to the server
7698
+ *
7699
+ * If the connection is temporarily down (connecting/reconnecting, or
7700
+ * disconnected with auto-reconnect enabled), the request is queued and
7701
+ * flushed once the connection is re-established and re-authenticated.
7702
+ * Queued requests are still subject to their own request timeout, and the
7703
+ * queue is bounded (overflow rejects immediately).
7846
7704
  */
7847
7705
  async sendRequestInternal(payload, requestType, contextOverride, requestOptions) {
7848
- if (!this.connection.isConnected()) {
7849
- throw new Error("Not connected to server");
7706
+ const connected = this.connection.isConnected();
7707
+ if (!connected) {
7708
+ const state = this.connection.getState();
7709
+ const willReconnect = state === "connecting" /* Connecting */ || state === "reconnecting" /* Reconnecting */ || state === "disconnected" /* Disconnected */ && this._everConnected && this.connection.isAutoReconnectEnabled();
7710
+ if (!willReconnect) {
7711
+ throw new Error("Not connected to server");
7712
+ }
7713
+ if (this._queuedRequests.length >= _RaisinClient.MAX_QUEUED_REQUESTS) {
7714
+ throw new Error(
7715
+ `Request queue is full (${_RaisinClient.MAX_QUEUED_REQUESTS} requests pending while reconnecting)`
7716
+ );
7717
+ }
7850
7718
  }
7851
7719
  const requestId = this.requestTracker.generateRequestId();
7852
7720
  const context = contextOverride || this._context;
@@ -7865,8 +7733,15 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7865
7733
  try {
7866
7734
  const encoded = encodeMessage(request);
7867
7735
  logger.debug(`Encoded request - ID: ${requestId}, type: ${requestType}, size: ${encoded.length} bytes`);
7868
- this.connection.send(encoded);
7869
- logger.debug(`Request sent - ID: ${requestId}`);
7736
+ if (connected) {
7737
+ this.connection.send(encoded);
7738
+ logger.debug(`Request sent - ID: ${requestId}`);
7739
+ } else {
7740
+ this._queuedRequests.push({ requestId, encoded });
7741
+ logger.debug(
7742
+ `Request queued while reconnecting - ID: ${requestId}, queue size: ${this._queuedRequests.length}`
7743
+ );
7744
+ }
7870
7745
  } catch (error) {
7871
7746
  logger.error(`Failed to send request - ID: ${requestId}:`, error);
7872
7747
  this.requestTracker.rejectRequest(
@@ -7877,6 +7752,42 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7877
7752
  }
7878
7753
  return responsePromise;
7879
7754
  }
7755
+ /**
7756
+ * Flush requests queued while the connection was re-establishing.
7757
+ * Called after the connection is connected and (re-)authenticated.
7758
+ * @internal
7759
+ */
7760
+ _flushQueuedRequests() {
7761
+ if (this._queuedRequests.length === 0) return;
7762
+ const queued = this._queuedRequests;
7763
+ this._queuedRequests = [];
7764
+ logger.info(`[_flushQueuedRequests] Flushing ${queued.length} queued request(s)`);
7765
+ for (const { requestId, encoded } of queued) {
7766
+ if (!this.requestTracker.hasPendingRequest(requestId)) continue;
7767
+ try {
7768
+ this.connection.send(encoded);
7769
+ } catch (error) {
7770
+ this.requestTracker.rejectRequest(
7771
+ requestId,
7772
+ error instanceof Error ? error : new Error("Failed to send queued request")
7773
+ );
7774
+ }
7775
+ }
7776
+ }
7777
+ /**
7778
+ * Reject all requests queued while the connection was re-establishing
7779
+ * (e.g. re-authentication failed after reconnect).
7780
+ * @internal
7781
+ */
7782
+ _rejectQueuedRequests(error) {
7783
+ if (this._queuedRequests.length === 0) return;
7784
+ const queued = this._queuedRequests;
7785
+ this._queuedRequests = [];
7786
+ logger.warn(`[_rejectQueuedRequests] Rejecting ${queued.length} queued request(s): ${error.message}`);
7787
+ for (const { requestId } of queued) {
7788
+ this.requestTracker.rejectRequest(requestId, error);
7789
+ }
7790
+ }
7880
7791
  /**
7881
7792
  * Get the HTTP base URL derived from the WebSocket URL
7882
7793
  */
@@ -7954,6 +7865,44 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
7954
7865
  getCurrentUser() {
7955
7866
  return this._currentUser;
7956
7867
  }
7868
+ /**
7869
+ * Re-resolve the current user's EFFECTIVE roles from the server and refresh
7870
+ * `getCurrentUser()`.
7871
+ *
7872
+ * Roles are deliberately not embedded in the JWT (they are resolved live
7873
+ * server-side), so when an admin changes a user's roles/groups the token does
7874
+ * not change — the client must re-ask. This re-runs the WS JWT auth handshake
7875
+ * (which returns freshly resolved effective roles), updates `_currentUser`, and
7876
+ * emits `USER_UPDATED` so role-gated UI can react without a reload.
7877
+ *
7878
+ * Called automatically when the server pushes `permissions:changed` after an
7879
+ * access-control write, and exposed for on-demand refresh. Safe no-op when not
7880
+ * authenticated.
7881
+ */
7882
+ async refreshCurrentUser() {
7883
+ if (__privateGet(this, _refreshingUser)) return __privateGet(this, _refreshingUser);
7884
+ const token = this.authManager.storage.getAccessToken();
7885
+ if (!token) return;
7886
+ __privateSet(this, _refreshingUser, (async () => {
7887
+ const before = JSON.stringify(this._currentUser?.roles ?? null);
7888
+ const prevNode = this._currentUser?.node;
7889
+ try {
7890
+ await this.authenticate({ type: "jwt", token });
7891
+ if (this._currentUser && prevNode && !this._currentUser.node) {
7892
+ this._currentUser.node = prevNode;
7893
+ }
7894
+ const after = JSON.stringify(this._currentUser?.roles ?? null);
7895
+ if (after !== before) {
7896
+ this._emitAuthEvent("USER_UPDATED");
7897
+ }
7898
+ } catch (err) {
7899
+ logger.warn("[refreshCurrentUser] role re-resolve failed:", err);
7900
+ } finally {
7901
+ __privateSet(this, _refreshingUser, null);
7902
+ }
7903
+ })());
7904
+ return __privateGet(this, _refreshingUser);
7905
+ }
7957
7906
  /**
7958
7907
  * Get current user ID
7959
7908
  *
@@ -8162,7 +8111,10 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
8162
8111
  _emitAuthEvent(event) {
8163
8112
  const session = {
8164
8113
  user: this.getStoredUser(),
8165
- accessToken: this.authManager.storage.getAccessToken()
8114
+ accessToken: this.authManager.storage.getAccessToken(),
8115
+ // Surface effective roles so role-gated consumers can react to
8116
+ // SIGNED_IN / USER_UPDATED without calling getCurrentUser() themselves.
8117
+ roles: this._currentUser?.roles ?? null
8166
8118
  };
8167
8119
  const change = { event, session };
8168
8120
  for (const callback of this._authListeners) {
@@ -8202,6 +8154,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
8202
8154
  */
8203
8155
  async _subscribeToUserHome(repository, userHome) {
8204
8156
  await this._unsubscribeFromUserHome();
8157
+ userHome = normalizeHomePath(userHome) ?? userHome;
8205
8158
  try {
8206
8159
  const db = this.database(repository);
8207
8160
  const pathParts = userHome.split("/").filter((p) => p.length > 0);
@@ -8254,12 +8207,12 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
8254
8207
  * @param config - Repository configuration
8255
8208
  * @returns Repository creation result
8256
8209
  */
8257
- async createRepository(repositoryId, description, config2) {
8210
+ async createRepository(repositoryId, description, config) {
8258
8211
  return this.sendRequestInternal(
8259
8212
  {
8260
8213
  repository_id: repositoryId,
8261
8214
  description,
8262
- config: config2
8215
+ config
8263
8216
  },
8264
8217
  "repository_create" /* RepositoryCreate */
8265
8218
  );
@@ -8298,12 +8251,12 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
8298
8251
  * @param config - New repository configuration
8299
8252
  * @returns Repository update result
8300
8253
  */
8301
- async updateRepository(repositoryId, description, config2) {
8254
+ async updateRepository(repositoryId, description, config) {
8302
8255
  return this.sendRequestInternal(
8303
8256
  {
8304
8257
  repository_id: repositoryId,
8305
8258
  description,
8306
- config: config2
8259
+ config
8307
8260
  },
8308
8261
  "repository_update" /* RepositoryUpdate */
8309
8262
  );
@@ -8501,7 +8454,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
8501
8454
  const branch = options.branch ?? this._context.branch ?? "main";
8502
8455
  const pathWithProperty = options.propertyPath ? `${options.path}@${options.propertyPath}` : options.path;
8503
8456
  const endpoint = `/api/repository/${options.repository}/${branch}/head/${options.workspace}${pathWithProperty}/raisin:sign`;
8504
- const response = await fetch(`${this._httpBaseUrl}${endpoint}`, {
8457
+ const response = await this._fetchWithTimeout(`${this._httpBaseUrl}${endpoint}`, {
8505
8458
  method: "POST",
8506
8459
  headers: {
8507
8460
  "Content-Type": "application/json",
@@ -8556,6 +8509,10 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
8556
8509
  return _RaisinClient.forSSR(baseUrl, options);
8557
8510
  }
8558
8511
  };
8512
+ _refreshingUser = new WeakMap();
8513
+ /** Hard cap on requests held while reconnecting; overflow rejects immediately. */
8514
+ _RaisinClient.MAX_QUEUED_REQUESTS = 100;
8515
+ var RaisinClient = _RaisinClient;
8559
8516
 
8560
8517
  // src/node-builder.ts
8561
8518
  var NodeBuilder = class _NodeBuilder {
@@ -8840,17 +8797,17 @@ var NodeEventType = {
8840
8797
  var AllNodeEventTypes = Object.values(NodeEventType);
8841
8798
 
8842
8799
  // src/integrations/react-router.ts
8843
- function createSSRClient(config2) {
8844
- const client = RaisinClient.forSSR(config2.httpBaseUrl, config2.httpOptions);
8845
- if (config2.credentials) {
8800
+ function createSSRClient(config) {
8801
+ const client = RaisinClient.forSSR(config.httpBaseUrl, config.httpOptions);
8802
+ if (config.credentials) {
8846
8803
  }
8847
8804
  return client;
8848
8805
  }
8849
- function createLoader(config2, loaderFn) {
8806
+ function createLoader(config, loaderFn) {
8850
8807
  return async (args) => {
8851
- const client = createSSRClient(config2);
8852
- if (config2.credentials) {
8853
- await client.authenticate(config2.credentials);
8808
+ const client = createSSRClient(config);
8809
+ if (config.credentials) {
8810
+ await client.authenticate(config.credentials);
8854
8811
  }
8855
8812
  try {
8856
8813
  return await loaderFn(client, args);
@@ -8890,1846 +8847,27 @@ function isTerminalEvent(event) {
8890
8847
  return event.type === "flow_completed" || event.type === "flow_failed";
8891
8848
  }
8892
8849
 
8893
- // src/utils/plan-projection.ts
8894
- function asRecord(value) {
8895
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
8896
- return value;
8897
- }
8898
- function asString(value) {
8899
- return typeof value === "string" && value.length > 0 ? value : void 0;
8900
- }
8901
- function parsePlanTasks(rawTasks) {
8902
- if (!Array.isArray(rawTasks)) return [];
8903
- return rawTasks.map((rawTask, index) => {
8904
- if (typeof rawTask === "string") {
8905
- return {
8906
- title: rawTask,
8907
- status: "pending"
8908
- };
8909
- }
8910
- const task = asRecord(rawTask);
8911
- if (!task) return null;
8912
- return {
8913
- taskId: asString(task.task_id) ?? asString(task.id),
8914
- title: asString(task.title) ?? `Task ${index + 1}`,
8915
- status: asString(task.status) ?? "pending",
8916
- description: asString(task.description),
8917
- priority: asString(task.priority)
8918
- };
8919
- }).filter((task) => task !== null);
8920
- }
8921
- function upsertTask(plan, nextTask) {
8922
- const existingIndex = plan.tasks.findIndex(
8923
- (task) => nextTask.taskId && task.taskId === nextTask.taskId || !nextTask.taskId && task.title === nextTask.title
8924
- );
8925
- if (existingIndex === -1) {
8926
- plan.tasks.push(nextTask);
8927
- return;
8928
- }
8929
- plan.tasks[existingIndex] = {
8930
- ...plan.tasks[existingIndex],
8931
- ...nextTask
8932
- };
8850
+ // src/types/flow-definition.ts
8851
+ function getRefDisplayName(ref) {
8852
+ const path = ref["raisin:path"] || ref["raisin:ref"];
8853
+ const segments = path.split("/").filter(Boolean);
8854
+ return segments[segments.length - 1] || path;
8933
8855
  }
8934
- function ensurePlan(plans, key, fallbackTitle, timestamp) {
8935
- const existing = plans.get(key);
8936
- if (existing) {
8937
- if (timestamp) existing.updatedAt = timestamp;
8938
- return existing;
8939
- }
8940
- const created = {
8941
- key,
8942
- title: fallbackTitle,
8943
- status: "pending_approval",
8944
- requiresApproval: true,
8945
- tasks: [],
8946
- updatedAt: timestamp
8947
- };
8948
- plans.set(key, created);
8949
- return created;
8856
+ function getRefPath(ref) {
8857
+ return ref["raisin:path"] || ref["raisin:ref"];
8950
8858
  }
8951
- function projectPlansFromMessages(messages) {
8952
- const ordered = [...messages].sort((a, b) => {
8953
- const aTime = Date.parse(a.timestamp || "");
8954
- const bTime = Date.parse(b.timestamp || "");
8955
- return (Number.isNaN(aTime) ? 0 : aTime) - (Number.isNaN(bTime) ? 0 : bTime);
8956
- });
8957
- const plans = /* @__PURE__ */ new Map();
8958
- for (const message of ordered) {
8959
- const timestamp = message.timestamp;
8960
- const data = asRecord(message.data);
8961
- if (message.messageType === "ai_plan" && data) {
8962
- const planPath = asString(data.plan_path);
8963
- const planId = asString(data.plan_id);
8964
- const key = planPath || planId || message.path || message.id || `plan:${timestamp}`;
8965
- const plan = ensurePlan(
8966
- plans,
8967
- key,
8968
- asString(data.title) || message.content || "Plan",
8969
- timestamp
8970
- );
8971
- plan.planPath = planPath ?? plan.planPath;
8972
- plan.planId = planId ?? plan.planId;
8973
- plan.title = asString(data.title) || plan.title;
8974
- plan.status = asString(data.status) || plan.status;
8975
- plan.requiresApproval = Boolean(data.requires_approval) || plan.status === "pending_approval";
8976
- plan.sourceMessagePath = message.path ?? plan.sourceMessagePath;
8977
- const parsedTasks = parsePlanTasks(data.tasks);
8978
- if (parsedTasks.length > 0) {
8979
- plan.tasks = parsedTasks;
8980
- }
8981
- }
8982
- if (message.messageType === "ai_task_update" && data) {
8983
- const planPath = asString(data.plan_path);
8984
- const planId = asString(data.plan_id);
8985
- const key = planPath || planId;
8986
- if (!key) continue;
8987
- const plan = ensurePlan(plans, key, "Plan", timestamp);
8988
- plan.planPath = planPath ?? plan.planPath;
8989
- plan.planId = planId ?? plan.planId;
8990
- plan.status = asString(data.plan_status) || plan.status;
8991
- plan.requiresApproval = plan.status === "pending_approval";
8992
- const taskUpdate = {
8993
- taskId: asString(data.task_id),
8994
- title: asString(data.task_title) || asString(data.title) || "Task",
8995
- status: asString(data.status) || "pending"
8996
- };
8997
- upsertTask(plan, taskUpdate);
8998
- }
8999
- if (Array.isArray(message.children)) {
9000
- for (const child of message.children) {
9001
- if (child.type !== "plan") continue;
9002
- const key = child.path || message.path || message.id || `plan-child:${child.id}:${timestamp}`;
9003
- const plan = ensurePlan(plans, key, child.planTitle || message.content || "Plan", timestamp);
9004
- plan.title = child.planTitle || plan.title;
9005
- plan.status = child.status || plan.status;
9006
- plan.requiresApproval = plan.status === "pending_approval";
9007
- if (Array.isArray(child.tasks) && child.tasks.length > 0) {
9008
- plan.tasks = child.tasks.map((task) => ({
9009
- taskId: task.id,
9010
- title: task.title,
9011
- status: task.status
9012
- }));
9013
- }
9014
- }
9015
- }
9016
- }
9017
- return [...plans.values()].sort((a, b) => {
9018
- const aTime = Date.parse(a.updatedAt || "");
9019
- const bTime = Date.parse(b.updatedAt || "");
9020
- return (Number.isNaN(bTime) ? 0 : bTime) - (Number.isNaN(aTime) ? 0 : aTime);
9021
- });
8859
+ var RETRY_STRATEGIES = {
8860
+ none: { max_retries: 0, base_delay_ms: 0, max_delay_ms: 0 },
8861
+ quick: { max_retries: 3, base_delay_ms: 1e3, max_delay_ms: 1e4 },
8862
+ standard: { max_retries: 5, base_delay_ms: 2e3, max_delay_ms: 6e4 },
8863
+ aggressive: { max_retries: 10, base_delay_ms: 5e3, max_delay_ms: 12e4 },
8864
+ llm: { max_retries: 5, base_delay_ms: 1e4, max_delay_ms: 12e4 }
8865
+ };
8866
+ function isFlowDefinitionStep(node) {
8867
+ return node.node_type === "raisin:FlowStep";
9022
8868
  }
9023
-
9024
- // src/stores/conversation-store.ts
9025
- var ConversationStore = class {
9026
- constructor(options) {
9027
- this.subscribers = /* @__PURE__ */ new Set();
9028
- // Persistent SSE subscription
9029
- this.subscription = null;
9030
- this._messages = [];
9031
- this._isStreaming = false;
9032
- this._isWaiting = false;
9033
- this._streamingText = "";
9034
- this._error = null;
9035
- this._activeToolCalls = [];
9036
- this._isLoading = false;
9037
- this._conversationType = null;
9038
- this._streamingTimer = null;
9039
- this._watchdogTimer = null;
9040
- this.manager = options.database.conversations;
9041
- this._conversationPath = options.conversationPath ?? null;
9042
- this.createOptions = options.createOptions;
9043
- this.onEvent = options.onEvent;
9044
- this._streamingTimeoutMs = options.streamingTimeoutMs ?? 12e4;
9045
- this._watchdogIntervalMs = options.watchdogIntervalMs ?? 3e4;
9046
- if (this._conversationPath) {
9047
- this.ensureSubscription();
9048
- }
9049
- }
9050
- // ==========================================================================
9051
- // Subscription
9052
- // ==========================================================================
9053
- /**
9054
- * Subscribe to state changes.
9055
- * Returns an unsubscribe function.
9056
- */
9057
- subscribe(callback) {
9058
- this.subscribers.add(callback);
9059
- callback(this.getSnapshot());
9060
- return () => {
9061
- this.subscribers.delete(callback);
9062
- };
9063
- }
9064
- /**
9065
- * Get the current state snapshot.
9066
- */
9067
- getSnapshot() {
9068
- return {
9069
- conversation: this._conversationPath ? { conversationPath: this._conversationPath, type: this._conversationType || "ai_chat" } : null,
9070
- messages: [...this._messages],
9071
- isStreaming: this._isStreaming,
9072
- isWaiting: this._isWaiting,
9073
- streamingText: this._streamingText,
9074
- error: this._error,
9075
- activeToolCalls: [...this._activeToolCalls],
9076
- plans: projectPlansFromMessages(this._messages),
9077
- isLoading: this._isLoading,
9078
- conversationPath: this._conversationPath
9079
- };
9080
- }
9081
- // ==========================================================================
9082
- // Actions
9083
- // ==========================================================================
9084
- /**
9085
- * Send a user message and stream the AI response.
9086
- *
9087
- * If no conversation exists yet and `createOptions` was provided,
9088
- * the conversation is created automatically on the first call.
9089
- */
9090
- async sendMessage(content) {
9091
- if (this._isStreaming) {
9092
- logger.warn("[ConversationStore] Cannot send message while streaming");
9093
- return;
9094
- }
9095
- logger.info("[ConversationStore] Sending message", { path: this._conversationPath });
9096
- this._error = null;
9097
- if (!this._conversationPath) {
9098
- if (!this.createOptions) {
9099
- throw new Error("No conversationPath and no createOptions provided");
9100
- }
9101
- try {
9102
- const convo = await this.manager.create({
9103
- participant: this.createOptions.participant,
9104
- input: this.createOptions.input
9105
- });
9106
- this._conversationPath = convo.conversationPath;
9107
- this._conversationType = convo.type;
9108
- for (const event of convo.initialEvents ?? []) {
9109
- this.onEvent?.(event);
9110
- this.handleEvent(event);
9111
- }
9112
- this.notify();
9113
- } catch (err) {
9114
- this._error = err instanceof Error ? err.message : String(err);
9115
- this.notify();
9116
- return;
9117
- }
9118
- }
9119
- const userMessage = {
9120
- role: "user",
9121
- content,
9122
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9123
- };
9124
- this._messages.push(userMessage);
9125
- this._isStreaming = true;
9126
- this._isWaiting = false;
9127
- this._streamingText = "";
9128
- this._activeToolCalls = [];
9129
- this.resetStreamingTimer();
9130
- this.startWatchdog();
9131
- this.notify();
9132
- try {
9133
- this.ensureSubscription();
9134
- await this.subscription.waitUntilConnected();
9135
- await this.manager.createUserMessage(this._conversationPath, content);
9136
- } catch (err) {
9137
- logger.error("[ConversationStore] Send failed", err);
9138
- this._error = err instanceof Error ? err.message : String(err);
9139
- this._isStreaming = false;
9140
- this.clearStreamingTimer();
9141
- this.stopWatchdog();
9142
- this.notify();
9143
- }
9144
- }
9145
- /**
9146
- * Load messages from the persisted node tree.
9147
- * Useful for loading history on page reload.
9148
- */
9149
- async loadMessages() {
9150
- if (!this._conversationPath) return [];
9151
- this._isLoading = true;
9152
- this.notify();
9153
- try {
9154
- const messages = await this.manager.getMessages(this._conversationPath);
9155
- logger.debug(`[ConversationStore] Loaded ${messages.length} messages`);
9156
- if (messages.length > 0) {
9157
- this._messages = messages;
9158
- this._activeToolCalls = [];
9159
- }
9160
- return messages;
9161
- } finally {
9162
- this._isLoading = false;
9163
- this.notify();
9164
- }
9165
- }
9166
- /**
9167
- * Approve a pending plan.
9168
- */
9169
- async approvePlan(planPath) {
9170
- await this.manager.approvePlan(planPath);
9171
- }
9172
- /**
9173
- * Reject a pending plan.
9174
- */
9175
- async rejectPlan(planPath, feedback) {
9176
- await this.manager.rejectPlan(planPath, feedback);
9177
- }
9178
- /**
9179
- * Mark a single message as read.
9180
- */
9181
- async markMessageAsRead(messagePath) {
9182
- await this.manager.markMessageAsRead(messagePath);
9183
- }
9184
- /**
9185
- * Stop the current streaming response.
9186
- */
9187
- stop() {
9188
- this._isStreaming = false;
9189
- this.clearStreamingTimer();
9190
- this.stopWatchdog();
9191
- this.notify();
9192
- }
9193
- /**
9194
- * Clean up the store and release resources.
9195
- */
9196
- destroy() {
9197
- this.stop();
9198
- this.clearStreamingTimer();
9199
- this.stopWatchdog();
9200
- this.subscription?.unsubscribe();
9201
- this.subscription = null;
9202
- this.subscribers.clear();
9203
- }
9204
- /**
9205
- * Get the conversation path.
9206
- */
9207
- getConversationPath() {
9208
- return this._conversationPath;
9209
- }
9210
- // ==========================================================================
9211
- // Internal
9212
- // ==========================================================================
9213
- ensureSubscription() {
9214
- if (this.subscription) return;
9215
- if (!this._conversationPath) return;
9216
- this.subscription = this.manager.subscribe(
9217
- this._conversationPath,
9218
- (event) => {
9219
- this.onEvent?.(event);
9220
- this.handleEvent(event);
9221
- }
9222
- );
9223
- }
9224
- handleEvent(event) {
9225
- logger.debug("[ConversationStore] SSE:", event.type);
9226
- if (this._isStreaming) {
9227
- this.resetStreamingTimer();
9228
- }
9229
- switch (event.type) {
9230
- case "text_chunk":
9231
- this._streamingText += event.text;
9232
- this.notify();
9233
- break;
9234
- case "thought_chunk":
9235
- this.notify();
9236
- break;
9237
- case "assistant_message":
9238
- this._messages.push(event.message);
9239
- this._streamingText = "";
9240
- this.notify();
9241
- break;
9242
- case "tool_call_started": {
9243
- logger.debug("[ConversationStore] Tool started:", event.functionName);
9244
- const info = {
9245
- id: event.toolCallId,
9246
- functionName: event.functionName,
9247
- arguments: event.arguments,
9248
- status: "running"
9249
- };
9250
- this._activeToolCalls.push(info);
9251
- this.notify();
9252
- break;
9253
- }
9254
- case "tool_call_completed": {
9255
- logger.debug("[ConversationStore] Tool completed:", event.toolCallId);
9256
- const idx = this._activeToolCalls.findIndex((tc) => tc.id === event.toolCallId);
9257
- if (idx >= 0) {
9258
- this._activeToolCalls[idx] = {
9259
- ...this._activeToolCalls[idx],
9260
- status: event.error ? "failed" : "completed",
9261
- result: event.result,
9262
- durationMs: event.durationMs
9263
- };
9264
- }
9265
- this.notify();
9266
- break;
9267
- }
9268
- case "waiting": {
9269
- logger.info("[ConversationStore] Waiting for input");
9270
- this.flushStreamingText(event.timestamp);
9271
- this._isStreaming = false;
9272
- this._isWaiting = true;
9273
- this._activeToolCalls = [];
9274
- this.clearStreamingTimer();
9275
- this.stopWatchdog();
9276
- this.notify();
9277
- this.deferredReload();
9278
- break;
9279
- }
9280
- case "done": {
9281
- if (isRecoveredDoneEvent(event)) {
9282
- logger.debug("[ConversationStore] Ignoring recovered done event");
9283
- this.reconcileRecoveredDone();
9284
- break;
9285
- }
9286
- const doneEvent = event;
9287
- if (doneEvent.dispatchPhase && doneEvent.dispatchPhase !== "terminal") {
9288
- logger.debug("[ConversationStore] Ignoring non-terminal done event");
9289
- break;
9290
- }
9291
- logger.info("[ConversationStore] Turn done");
9292
- this.flushStreamingText(event.timestamp);
9293
- if (doneEvent.content && !this.lastMessageIsAssistant()) {
9294
- this._messages.push({
9295
- role: doneEvent.role || "assistant",
9296
- content: doneEvent.content,
9297
- timestamp: event.timestamp,
9298
- senderDisplayName: doneEvent.senderDisplayName,
9299
- finishReason: doneEvent.finishReason
9300
- });
9301
- }
9302
- this._isStreaming = false;
9303
- this._isWaiting = true;
9304
- this._activeToolCalls = [];
9305
- this.clearStreamingTimer();
9306
- this.stopWatchdog();
9307
- this.notify();
9308
- this.deferredReload();
9309
- break;
9310
- }
9311
- case "completed": {
9312
- this.flushStreamingText(event.timestamp);
9313
- if (event.messages && event.messages.length > 0) {
9314
- this._messages = event.messages;
9315
- }
9316
- this._isStreaming = false;
9317
- this._isWaiting = false;
9318
- this._activeToolCalls = [];
9319
- this.clearStreamingTimer();
9320
- this.stopWatchdog();
9321
- this.notify();
9322
- this.deferredReload();
9323
- break;
9324
- }
9325
- case "failed":
9326
- logger.error("[ConversationStore] Turn failed", event.error);
9327
- this._error = event.error;
9328
- this._isStreaming = false;
9329
- this._streamingText = "";
9330
- this._activeToolCalls = [];
9331
- this.clearStreamingTimer();
9332
- this.stopWatchdog();
9333
- this.notify();
9334
- break;
9335
- case "conversation_created":
9336
- this._conversationPath = event.conversationPath;
9337
- this.notify();
9338
- break;
9339
- case "message_saved":
9340
- this.silentReload().catch(() => {
9341
- });
9342
- this.notify();
9343
- break;
9344
- case "message_delivered":
9345
- this._messages.push(event.message);
9346
- this.notify();
9347
- break;
9348
- case "log": {
9349
- const logEvent = event;
9350
- const currentLevel = getLogLevel();
9351
- const levelMap = {
9352
- debug: 4 /* Debug */,
9353
- info: 3 /* Info */,
9354
- warn: 2 /* Warn */,
9355
- error: 1 /* Error */
9356
- };
9357
- const eventLevel = levelMap[logEvent.level] ?? 4 /* Debug */;
9358
- if (currentLevel >= eventLevel) {
9359
- const fn = logEvent.level === "error" ? console.error : logEvent.level === "warn" ? console.warn : logEvent.level === "debug" ? console.debug : console.info;
9360
- fn("[server]", logEvent.message);
9361
- }
9362
- break;
9363
- }
9364
- }
9365
- }
9366
- /**
9367
- * If accumulated streaming text hasn't been captured in an assistant_message,
9368
- * synthesize one.
9369
- */
9370
- flushStreamingText(timestamp) {
9371
- if (this._streamingText && !this.lastMessageIsAssistant()) {
9372
- this._messages.push({
9373
- role: "assistant",
9374
- content: this._streamingText,
9375
- timestamp
9376
- });
9377
- }
9378
- this._streamingText = "";
9379
- }
9380
- lastMessageIsAssistant() {
9381
- const last = this._messages[this._messages.length - 1];
9382
- return last?.role === "assistant";
9383
- }
9384
- /**
9385
- * After a turn completes, reload canonical messages from the node tree
9386
- * to catch any events missed during SSE gaps.
9387
- */
9388
- deferredReload() {
9389
- if (!this._conversationPath) return;
9390
- logger.debug("[ConversationStore] Scheduling deferred reload");
9391
- const attemptReload = (delay, retriesLeft) => {
9392
- setTimeout(async () => {
9393
- try {
9394
- const prevCount = this._messages.length;
9395
- await this.silentReload();
9396
- if (this._messages.length === prevCount && retriesLeft > 0) {
9397
- attemptReload(delay * 2, retriesLeft - 1);
9398
- }
9399
- } catch {
9400
- }
9401
- }, delay);
9402
- };
9403
- attemptReload(500, 2);
9404
- }
9405
- async silentReload() {
9406
- if (!this._conversationPath) return;
9407
- try {
9408
- const fresh = await this.manager.getMessages(this._conversationPath);
9409
- if (fresh.length === 0) return;
9410
- let matchCount = 0;
9411
- const limit = Math.min(this._messages.length, fresh.length);
9412
- for (let i = 0; i < limit; i++) {
9413
- if (this._messages[i].role !== fresh[i].role || this._messages[i].content !== fresh[i].content) break;
9414
- matchCount++;
9415
- }
9416
- if (matchCount === this._messages.length && fresh.length === matchCount) {
9417
- for (let i = 0; i < matchCount; i++) {
9418
- const f = fresh[i];
9419
- const e = this._messages[i];
9420
- if (f.id) e.id = f.id;
9421
- if (f.path) e.path = f.path;
9422
- if (f.messageType) e.messageType = f.messageType;
9423
- if (f.data) e.data = f.data;
9424
- if (f.children) e.children = f.children;
9425
- if (f.finishReason) e.finishReason = f.finishReason;
9426
- }
9427
- await this.rebuildToolCallState();
9428
- return;
9429
- }
9430
- if (matchCount === this._messages.length && fresh.length > matchCount) {
9431
- for (let i = matchCount; i < fresh.length; i++) {
9432
- this._messages.push(fresh[i]);
9433
- }
9434
- for (let i = 0; i < matchCount; i++) {
9435
- const f = fresh[i];
9436
- const e = this._messages[i];
9437
- if (f.id) e.id = f.id;
9438
- if (f.path) e.path = f.path;
9439
- if (f.messageType) e.messageType = f.messageType;
9440
- if (f.data) e.data = f.data;
9441
- if (f.children) e.children = f.children;
9442
- }
9443
- await this.rebuildToolCallState();
9444
- this.notify();
9445
- return;
9446
- }
9447
- if (matchCount === fresh.length && fresh.length < this._messages.length) {
9448
- for (let i = 0; i < matchCount; i++) {
9449
- const f = fresh[i];
9450
- const e = this._messages[i];
9451
- if (f.id) e.id = f.id;
9452
- if (f.path) e.path = f.path;
9453
- if (f.messageType) e.messageType = f.messageType;
9454
- if (f.data) e.data = f.data;
9455
- if (f.children) e.children = f.children;
9456
- }
9457
- await this.rebuildToolCallState();
9458
- return;
9459
- }
9460
- this._messages = fresh;
9461
- await this.rebuildToolCallState();
9462
- this.notify();
9463
- } catch (err) {
9464
- logger.debug("[ConversationStore] Silent reload failed:", err);
9465
- }
9466
- }
9467
- // ==========================================================================
9468
- // Streaming timeout (Fix A) — auto-recover on SSE inactivity
9469
- // ==========================================================================
9470
- resetStreamingTimer() {
9471
- this.clearStreamingTimer();
9472
- if (!this._isStreaming) return;
9473
- this._streamingTimer = setTimeout(() => {
9474
- if (this._isStreaming) {
9475
- logger.warn("[ConversationStore] Streaming timeout \u2014 no SSE activity for", this._streamingTimeoutMs, "ms");
9476
- this.recoverFromHang();
9477
- }
9478
- }, this._streamingTimeoutMs);
9479
- }
9480
- clearStreamingTimer() {
9481
- if (this._streamingTimer) {
9482
- clearTimeout(this._streamingTimer);
9483
- this._streamingTimer = null;
9484
- }
9485
- }
9486
- // ==========================================================================
9487
- // Activity watchdog (Fix B) — periodic backend health check
9488
- // ==========================================================================
9489
- startWatchdog() {
9490
- this.stopWatchdog();
9491
- if (!this._conversationPath) return;
9492
- this._watchdogTimer = setInterval(async () => {
9493
- if (!this._isStreaming || !this._conversationPath) {
9494
- this.stopWatchdog();
9495
- return;
9496
- }
9497
- try {
9498
- const health = await this.manager.checkTurnHealth(this._conversationPath);
9499
- if (health === "done" && this._isStreaming) {
9500
- logger.warn("[ConversationStore] Watchdog detected completed turn while still streaming \u2014 recovering");
9501
- this.recoverFromHang();
9502
- }
9503
- } catch {
9504
- }
9505
- }, this._watchdogIntervalMs);
9506
- }
9507
- stopWatchdog() {
9508
- if (this._watchdogTimer) {
9509
- clearInterval(this._watchdogTimer);
9510
- this._watchdogTimer = null;
9511
- }
9512
- }
9513
- // ==========================================================================
9514
- // Hang recovery (shared by timeout + watchdog)
9515
- // ==========================================================================
9516
- recoverFromHang() {
9517
- logger.info("[ConversationStore] Recovering from hang");
9518
- this.clearStreamingTimer();
9519
- this.stopWatchdog();
9520
- this._isStreaming = false;
9521
- this._isWaiting = true;
9522
- this._streamingText = "";
9523
- this.notify();
9524
- this.silentReload().catch(() => {
9525
- });
9526
- }
9527
- /**
9528
- * Reconcile a recovered done event against persisted state.
9529
- * If the backend turn is actually complete, finalize the local state.
9530
- * Otherwise keep streaming active and let normal events continue.
9531
- */
9532
- reconcileRecoveredDone() {
9533
- if (!this._conversationPath) return;
9534
- void this.manager.checkTurnHealth(this._conversationPath).then((health) => {
9535
- if (health !== "done") return;
9536
- if (!this._isStreaming) return;
9537
- this._isStreaming = false;
9538
- this._isWaiting = true;
9539
- this._activeToolCalls = [];
9540
- this.clearStreamingTimer();
9541
- this.stopWatchdog();
9542
- this.notify();
9543
- this.deferredReload();
9544
- }).catch(() => {
9545
- });
9546
- }
9547
- // ==========================================================================
9548
- // Tool call state rebuild (Fix C) — reconstruct from node tree on reload
9549
- // ==========================================================================
9550
- async rebuildToolCallState() {
9551
- if (!this._conversationPath) {
9552
- this._activeToolCalls = [];
9553
- return;
9554
- }
9555
- try {
9556
- const pendingCalls = await this.manager.getActiveToolCalls(this._conversationPath);
9557
- if (pendingCalls.length > 0) {
9558
- this._activeToolCalls = pendingCalls.map((tc) => ({
9559
- id: tc.id,
9560
- functionName: tc.name,
9561
- arguments: void 0,
9562
- status: "running"
9563
- }));
9564
- if (!this._isStreaming) {
9565
- this._isStreaming = true;
9566
- this.resetStreamingTimer();
9567
- this.startWatchdog();
9568
- }
9569
- } else {
9570
- this._activeToolCalls = [];
9571
- if (this._isStreaming) {
9572
- const lastMsg = this._messages[this._messages.length - 1];
9573
- const isLastAssistantDone = lastMsg?.role === "assistant" && (lastMsg.dispatchPhase ? lastMsg.dispatchPhase === "terminal" : !!lastMsg.finishReason && lastMsg.finishReason !== "tool_calls");
9574
- if (isLastAssistantDone) {
9575
- this._isStreaming = false;
9576
- this._isWaiting = true;
9577
- this.clearStreamingTimer();
9578
- this.stopWatchdog();
9579
- }
9580
- }
9581
- }
9582
- } catch {
9583
- this._activeToolCalls = [];
9584
- }
9585
- }
9586
- notify() {
9587
- const snapshot = this.getSnapshot();
9588
- for (const sub of this.subscribers) {
9589
- try {
9590
- sub(snapshot);
9591
- } catch (err) {
9592
- logger.error("Error in conversation store subscriber:", err);
9593
- }
9594
- }
9595
- }
9596
- };
9597
-
9598
- // src/stores/conversation-list-store.ts
9599
- var ConversationListStore = class {
9600
- constructor(options) {
9601
- this.subscribers = /* @__PURE__ */ new Set();
9602
- // Internal state
9603
- this._conversations = [];
9604
- this._isLoading = false;
9605
- this._error = null;
9606
- // Cached ConversationStore instances
9607
- this._stores = /* @__PURE__ */ new Map();
9608
- // Real-time subscription
9609
- this._realtimeSubscription = null;
9610
- this.database = options.database;
9611
- this.type = options.type;
9612
- this.realtime = options.realtime ?? false;
9613
- }
9614
- // ==========================================================================
9615
- // Subscription
9616
- // ==========================================================================
9617
- subscribe(callback) {
9618
- this.subscribers.add(callback);
9619
- callback(this.getSnapshot());
9620
- return () => {
9621
- this.subscribers.delete(callback);
9622
- };
9623
- }
9624
- getSnapshot() {
9625
- return {
9626
- conversations: [...this._conversations],
9627
- totalUnreadCount: this._conversations.reduce((sum, c) => sum + (c.unreadCount ?? 0), 0),
9628
- isLoading: this._isLoading,
9629
- error: this._error
9630
- };
9631
- }
9632
- // ==========================================================================
9633
- // Actions
9634
- // ==========================================================================
9635
- /**
9636
- * Load conversations from the server.
9637
- */
9638
- async load() {
9639
- this._isLoading = true;
9640
- this._error = null;
9641
- this.notify();
9642
- try {
9643
- const conversations = await this.database.conversations.list({
9644
- type: this.type
9645
- });
9646
- logger.debug(`[ConversationListStore] Loaded ${conversations.length} conversations`);
9647
- this._conversations = conversations;
9648
- if (this.realtime) {
9649
- this.startRealtimeSubscription();
9650
- }
9651
- } catch (err) {
9652
- logger.error("[ConversationListStore] Failed to load conversations", err);
9653
- this._error = err instanceof Error ? err.message : String(err);
9654
- } finally {
9655
- this._isLoading = false;
9656
- this.notify();
9657
- }
9658
- }
9659
- /**
9660
- * Create a new conversation.
9661
- */
9662
- async createConversation(options) {
9663
- const convo = await this.database.conversations.create(options);
9664
- const listItem = {
9665
- id: convo.id,
9666
- type: convo.type,
9667
- conversationPath: convo.conversationPath,
9668
- conversationWorkspace: convo.conversationWorkspace,
9669
- agentRef: convo.agentRef,
9670
- participants: convo.participants,
9671
- unreadCount: 0,
9672
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
9673
- };
9674
- this._conversations = [listItem, ...this._conversations];
9675
- this.notify();
9676
- return listItem;
9677
- }
9678
- /**
9679
- * Delete a conversation.
9680
- */
9681
- async deleteConversation(conversationPath) {
9682
- const store = this._stores.get(conversationPath);
9683
- if (store) {
9684
- store.destroy();
9685
- this._stores.delete(conversationPath);
9686
- }
9687
- await this.database.conversations.delete(conversationPath);
9688
- this._conversations = this._conversations.filter((c) => c.conversationPath !== conversationPath);
9689
- this.notify();
9690
- }
9691
- /**
9692
- * Mark a conversation as read.
9693
- */
9694
- async markAsRead(conversationPath) {
9695
- await this.database.conversations.markAsRead(conversationPath);
9696
- this._conversations = this._conversations.map(
9697
- (c) => c.conversationPath === conversationPath ? { ...c, unreadCount: 0 } : c
9698
- );
9699
- this.notify();
9700
- }
9701
- /**
9702
- * Get or create a ConversationStore for a specific conversation.
9703
- * Caches instances so repeated calls return the same store.
9704
- */
9705
- getConversationStore(conversationPath, overrides) {
9706
- let store = this._stores.get(conversationPath);
9707
- if (!store) {
9708
- store = new ConversationStore({
9709
- database: this.database,
9710
- conversationPath,
9711
- ...overrides
9712
- });
9713
- this._stores.set(conversationPath, store);
9714
- }
9715
- return store;
9716
- }
9717
- /**
9718
- * Clean up all resources.
9719
- */
9720
- destroy() {
9721
- this._realtimeSubscription?.unsubscribe();
9722
- this._realtimeSubscription = null;
9723
- for (const store of this._stores.values()) {
9724
- store.destroy();
9725
- }
9726
- this._stores.clear();
9727
- this.subscribers.clear();
9728
- }
9729
- // ==========================================================================
9730
- // Internal
9731
- // ==========================================================================
9732
- async startRealtimeSubscription() {
9733
- if (this._realtimeSubscription) return;
9734
- logger.debug("[ConversationListStore] Starting realtime subscription");
9735
- try {
9736
- const workspace = this.database.workspace("raisin:access_control");
9737
- const events = workspace.events();
9738
- const result = await this.database.executeSql(
9739
- `SELECT RAISIN_CURRENT_USER()->>'path'::String as home`
9740
- );
9741
- const rows = result.rows;
9742
- const userHome = rows?.[0]?.home;
9743
- if (!userHome) return;
9744
- const chatsPath = `${userHome}/inbox/chats`;
9745
- this._realtimeSubscription = await events.subscribe(
9746
- {
9747
- workspace: "raisin:access_control",
9748
- path: chatsPath,
9749
- event_types: ["node:created", "node:updated"]
9750
- },
9751
- async (event) => {
9752
- const eventData = event;
9753
- const nodeId = eventData.payload?.node_id;
9754
- if (!nodeId) return;
9755
- try {
9756
- const nodeResult = await this.database.executeSql(
9757
- `SELECT id, path, node_type, properties, created_at, updated_at FROM 'raisin:access_control' WHERE id = $1 LIMIT 1`,
9758
- [nodeId]
9759
- );
9760
- const row = nodeResult.rows?.[0];
9761
- if (!row || row.node_type !== "raisin:Conversation") return;
9762
- const convPath = row.path;
9763
- const props = row.properties ?? {};
9764
- const item = {
9765
- id: row.id,
9766
- type: props.conversation_type ?? "ai_chat",
9767
- conversationPath: convPath,
9768
- conversationWorkspace: "raisin:access_control",
9769
- agentRef: props.agent_ref,
9770
- participants: props.participants,
9771
- unreadCount: props.unread_count ?? 0,
9772
- lastMessage: props.last_message,
9773
- updatedAt: row.updated_at ?? row.created_at
9774
- };
9775
- const existingIdx = this._conversations.findIndex((c) => c.conversationPath === convPath);
9776
- if (existingIdx >= 0) {
9777
- this._conversations.splice(existingIdx, 1);
9778
- }
9779
- this._conversations = [item, ...this._conversations];
9780
- this.notify();
9781
- } catch (err) {
9782
- logger.debug("Failed to process conversation event:", err);
9783
- }
9784
- }
9785
- );
9786
- } catch (err) {
9787
- logger.debug("Failed to start realtime subscription:", err);
9788
- }
9789
- }
9790
- notify() {
9791
- const snapshot = this.getSnapshot();
9792
- for (const sub of this.subscribers) {
9793
- try {
9794
- sub(snapshot);
9795
- } catch (err) {
9796
- logger.error("Error in conversation list store subscriber:", err);
9797
- }
9798
- }
9799
- }
9800
- };
9801
-
9802
- // src/integrations/react-conversation.ts
9803
- var INITIAL_SNAPSHOT = {
9804
- conversation: null,
9805
- messages: [],
9806
- isStreaming: false,
9807
- isWaiting: false,
9808
- streamingText: "",
9809
- error: null,
9810
- activeToolCalls: [],
9811
- plans: [],
9812
- isLoading: false,
9813
- conversationPath: null
9814
- };
9815
- function useConversation(react, options) {
9816
- const { useState, useEffect, useRef, useCallback } = react;
9817
- const [snapshot, setSnapshot] = useState(INITIAL_SNAPSHOT);
9818
- const storeRef = useRef(null);
9819
- useEffect(() => {
9820
- const store = new ConversationStore(options);
9821
- storeRef.current = store;
9822
- const unsubscribe = store.subscribe((next) => setSnapshot(next));
9823
- return () => {
9824
- unsubscribe();
9825
- store.destroy();
9826
- storeRef.current = null;
9827
- };
9828
- }, [options.conversationPath]);
9829
- const sendMessage = useCallback(async (content) => {
9830
- await storeRef.current?.sendMessage(content);
9831
- }, []);
9832
- const approvePlan = useCallback(async (planPath) => {
9833
- await storeRef.current?.approvePlan(planPath);
9834
- }, []);
9835
- const rejectPlan = useCallback(async (planPath, feedback) => {
9836
- await storeRef.current?.rejectPlan(planPath, feedback);
9837
- }, []);
9838
- const stop = useCallback(() => {
9839
- storeRef.current?.stop();
9840
- }, []);
9841
- const loadMessages = useCallback(async () => {
9842
- return await storeRef.current?.loadMessages() ?? [];
9843
- }, []);
9844
- return {
9845
- ...snapshot,
9846
- sendMessage,
9847
- approvePlan,
9848
- rejectPlan,
9849
- stop,
9850
- loadMessages
9851
- };
9852
- }
9853
- var INITIAL_LIST_SNAPSHOT = {
9854
- conversations: [],
9855
- totalUnreadCount: 0,
9856
- isLoading: false,
9857
- error: null
9858
- };
9859
- function useConversationList(react, options) {
9860
- const { useState, useEffect, useRef, useCallback } = react;
9861
- const [snapshot, setSnapshot] = useState(INITIAL_LIST_SNAPSHOT);
9862
- const storeRef = useRef(null);
9863
- useEffect(() => {
9864
- const store = new ConversationListStore(options);
9865
- storeRef.current = store;
9866
- const unsubscribe = store.subscribe((next) => setSnapshot(next));
9867
- store.load();
9868
- return () => {
9869
- unsubscribe();
9870
- store.destroy();
9871
- storeRef.current = null;
9872
- };
9873
- }, [options.type, options.realtime]);
9874
- const createConversation = useCallback(async (opts) => {
9875
- return storeRef.current.createConversation(opts);
9876
- }, []);
9877
- const deleteConversation = useCallback(async (conversationPath) => {
9878
- await storeRef.current?.deleteConversation(conversationPath);
9879
- }, []);
9880
- const markAsRead = useCallback(async (conversationPath) => {
9881
- await storeRef.current?.markAsRead(conversationPath);
9882
- }, []);
9883
- return {
9884
- ...snapshot,
9885
- createConversation,
9886
- deleteConversation,
9887
- markAsRead
9888
- };
9889
- }
9890
-
9891
- // src/integrations/svelte-conversation.ts
9892
- function createConversationAdapter(options) {
9893
- const store = new ConversationStore(options);
9894
- return {
9895
- /** The underlying ConversationStore instance */
9896
- store,
9897
- /** Get the current snapshot */
9898
- getSnapshot: () => store.getSnapshot(),
9899
- /** Subscribe to snapshot changes. Returns unsubscribe function. */
9900
- subscribe: (cb) => store.subscribe(cb),
9901
- /** Send a user message */
9902
- sendMessage: (content) => store.sendMessage(content),
9903
- /** Load persisted messages */
9904
- loadMessages: () => store.loadMessages(),
9905
- /** Approve a pending plan */
9906
- approvePlan: (planPath) => store.approvePlan(planPath),
9907
- /** Reject a pending plan */
9908
- rejectPlan: (planPath, feedback) => store.rejectPlan(planPath, feedback),
9909
- /** Mark a message as read */
9910
- markMessageAsRead: (messagePath) => store.markMessageAsRead(messagePath),
9911
- /** Stop streaming */
9912
- stop: () => store.stop(),
9913
- /** Get the conversation path */
9914
- getConversationPath: () => store.getConversationPath(),
9915
- /** Destroy the store and release resources */
9916
- destroy: () => store.destroy()
9917
- };
9918
- }
9919
- function createConversationListAdapter(options) {
9920
- const store = new ConversationListStore(options);
9921
- return {
9922
- /** The underlying ConversationListStore instance */
9923
- store,
9924
- /** Get the current snapshot */
9925
- getSnapshot: () => store.getSnapshot(),
9926
- /** Subscribe to snapshot changes. Returns unsubscribe function. */
9927
- subscribe: (cb) => store.subscribe(cb),
9928
- /** Load conversations from the server */
9929
- load: () => store.load(),
9930
- /** Create a new conversation */
9931
- createConversation: (opts) => store.createConversation(opts),
9932
- /** Delete a conversation */
9933
- deleteConversation: (path) => store.deleteConversation(path),
9934
- /** Mark a conversation as read */
9935
- markAsRead: (path) => store.markAsRead(path),
9936
- /** Get or create a ConversationStore for a specific path */
9937
- getConversationStore: (path) => store.getConversationStore(path),
9938
- /** Destroy the store and release resources */
9939
- destroy: () => store.destroy()
9940
- };
9941
- }
9942
-
9943
- // src/integrations/svelte/svelte-auth.ts
9944
- function createAuthAdapter(client) {
9945
- let snapshot = {
9946
- user: client.getStoredUser(),
9947
- isAuthenticated: client.getStoredUser() !== null,
9948
- isLoading: false
9949
- };
9950
- const listeners = /* @__PURE__ */ new Set();
9951
- function emit() {
9952
- for (const cb of listeners) cb(snapshot);
9953
- }
9954
- function update(partial) {
9955
- snapshot = { ...snapshot, ...partial };
9956
- emit();
9957
- }
9958
- const unsubAuth = client.onAuthStateChange(({ event, session }) => {
9959
- if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED" || event === "USER_UPDATED") {
9960
- update({ user: session.user, isAuthenticated: true });
9961
- } else if (event === "SIGNED_OUT" || event === "SESSION_EXPIRED") {
9962
- update({ user: null, isAuthenticated: false });
9963
- }
9964
- });
9965
- const unsubUser = client.onUserChange(({ node }) => {
9966
- if (!snapshot.user) return;
9967
- update({
9968
- user: {
9969
- ...snapshot.user,
9970
- home: node.path,
9971
- displayName: node.properties.display_name ?? snapshot.user.displayName,
9972
- avatarUrl: node.properties.avatar_url ?? snapshot.user.avatarUrl
9973
- }
9974
- });
9975
- });
9976
- return {
9977
- subscribe(cb) {
9978
- listeners.add(cb);
9979
- return () => {
9980
- listeners.delete(cb);
9981
- };
9982
- },
9983
- getSnapshot: () => snapshot,
9984
- async login(email, password, repository) {
9985
- update({ isLoading: true });
9986
- try {
9987
- return await client.loginWithEmail(email, password, repository);
9988
- } finally {
9989
- update({ isLoading: false });
9990
- }
9991
- },
9992
- async register(email, password, repository, displayName) {
9993
- update({ isLoading: true });
9994
- try {
9995
- return await client.registerWithEmail(email, password, repository, displayName);
9996
- } finally {
9997
- update({ isLoading: false });
9998
- }
9999
- },
10000
- async logout(options) {
10001
- update({ isLoading: true });
10002
- try {
10003
- await client.logout(options);
10004
- } finally {
10005
- update({ isLoading: false });
10006
- }
10007
- },
10008
- async initSession(repository) {
10009
- update({ isLoading: true });
10010
- try {
10011
- return await client.initSession(repository);
10012
- } finally {
10013
- update({ isLoading: false });
10014
- }
10015
- },
10016
- destroy() {
10017
- unsubAuth();
10018
- unsubUser();
10019
- listeners.clear();
10020
- }
10021
- };
10022
- }
10023
-
10024
- // src/integrations/svelte/svelte-connection.ts
10025
- function createConnectionAdapter(client) {
10026
- let snapshot = {
10027
- state: client.getConnectionState(),
10028
- isConnected: client.getConnectionState() === "connected" /* Connected */,
10029
- isReady: client.isReady()
10030
- };
10031
- const listeners = /* @__PURE__ */ new Set();
10032
- function emit() {
10033
- for (const cb of listeners) cb(snapshot);
10034
- }
10035
- const unsubConn = client.onConnectionStateChange((newState) => {
10036
- snapshot = {
10037
- state: newState,
10038
- isConnected: newState === "connected" /* Connected */,
10039
- isReady: snapshot.isReady
10040
- };
10041
- emit();
10042
- });
10043
- const unsubReady = client.onReadyStateChange((ready) => {
10044
- snapshot = { ...snapshot, isReady: ready };
10045
- emit();
10046
- });
10047
- return {
10048
- subscribe(cb) {
10049
- listeners.add(cb);
10050
- return () => {
10051
- listeners.delete(cb);
10052
- };
10053
- },
10054
- getSnapshot: () => snapshot,
10055
- async connect() {
10056
- await client.connect();
10057
- },
10058
- disconnect() {
10059
- client.disconnect();
10060
- },
10061
- destroy() {
10062
- unsubConn();
10063
- unsubReady();
10064
- listeners.clear();
10065
- }
10066
- };
10067
- }
10068
-
10069
- // src/integrations/svelte/svelte-sql.ts
10070
- function rowsToObjects2(result) {
10071
- return result.rows;
10072
- }
10073
- function createSqlAdapter(database, sql, params, options) {
10074
- let snapshot = {
10075
- data: null,
10076
- isLoading: false,
10077
- error: null
10078
- };
10079
- const listeners = /* @__PURE__ */ new Set();
10080
- let destroyed = false;
10081
- function emit() {
10082
- for (const cb of listeners) cb(snapshot);
10083
- }
10084
- function update(partial) {
10085
- snapshot = { ...snapshot, ...partial };
10086
- emit();
10087
- }
10088
- async function fetchData() {
10089
- if (destroyed) return;
10090
- update({ isLoading: true, error: null });
10091
- try {
10092
- const result = await database.executeSql(sql, params);
10093
- if (!destroyed) {
10094
- update({ data: rowsToObjects2(result), isLoading: false });
10095
- }
10096
- } catch (err) {
10097
- if (!destroyed) {
10098
- update({
10099
- error: err instanceof Error ? err : new Error(String(err)),
10100
- isLoading: false
10101
- });
10102
- }
10103
- }
10104
- }
10105
- const cleanups = [];
10106
- const enabled = options?.enabled !== false;
10107
- if (enabled) {
10108
- fetchData();
10109
- }
10110
- const refetchOnReconnect = options?.refetchOnReconnect !== false;
10111
- if (enabled && refetchOnReconnect) {
10112
- const client = database._client;
10113
- if (client?.onReconnected) {
10114
- const unsub = client.onReconnected(() => {
10115
- fetchData();
10116
- });
10117
- cleanups.push(unsub);
10118
- }
10119
- }
10120
- const realtime = options?.realtime;
10121
- if (enabled && realtime) {
10122
- let unsubscribe;
10123
- let cancelled = false;
10124
- database.events().subscribe(
10125
- {
10126
- workspace: realtime.workspace,
10127
- event_types: realtime.eventTypes,
10128
- path: realtime.path,
10129
- node_type: realtime.nodeType
10130
- },
10131
- () => {
10132
- fetchData();
10133
- }
10134
- ).then((sub) => {
10135
- if (cancelled) {
10136
- sub.unsubscribe();
10137
- } else {
10138
- unsubscribe = () => sub.unsubscribe();
10139
- }
10140
- });
10141
- cleanups.push(() => {
10142
- cancelled = true;
10143
- unsubscribe?.();
10144
- });
10145
- }
10146
- return {
10147
- subscribe(cb) {
10148
- listeners.add(cb);
10149
- return () => {
10150
- listeners.delete(cb);
10151
- };
10152
- },
10153
- getSnapshot: () => snapshot,
10154
- refetch: fetchData,
10155
- destroy() {
10156
- destroyed = true;
10157
- for (const cleanup of cleanups) cleanup();
10158
- listeners.clear();
10159
- }
10160
- };
10161
- }
10162
-
10163
- // src/integrations/svelte/svelte-subscription.ts
10164
- function createSubscriptionAdapter(database, filters, callback) {
10165
- let unsubscribe;
10166
- let cancelled = false;
10167
- database.events().subscribe(filters, (event) => {
10168
- if (!cancelled) callback(event);
10169
- }).then((sub) => {
10170
- if (cancelled) {
10171
- sub.unsubscribe();
10172
- } else {
10173
- unsubscribe = () => sub.unsubscribe();
10174
- }
10175
- });
10176
- return {
10177
- destroy() {
10178
- cancelled = true;
10179
- unsubscribe?.();
10180
- }
10181
- };
10182
- }
10183
-
10184
- // src/integrations/svelte/svelte-flow.ts
10185
- function createFlowAdapter(options) {
10186
- let snapshot = {
10187
- events: [],
10188
- status: "idle",
10189
- isRunning: false,
10190
- error: null,
10191
- output: null,
10192
- instanceId: null
10193
- };
10194
- const listeners = /* @__PURE__ */ new Set();
10195
- let aborted = false;
10196
- function emit() {
10197
- for (const cb of listeners) cb(snapshot);
10198
- }
10199
- function update(partial) {
10200
- snapshot = { ...snapshot, ...partial };
10201
- if ("status" in partial) {
10202
- snapshot.isRunning = snapshot.status === "running";
10203
- }
10204
- emit();
10205
- }
10206
- let flowClient = null;
10207
- if (options.database) {
10208
- flowClient = options.database.flow;
10209
- } else if (options.baseUrl && options.repository && options.authManager) {
10210
- flowClient = new FlowClient(
10211
- options.baseUrl,
10212
- options.repository,
10213
- options.authManager,
10214
- options.clientOptions,
10215
- options.flowsApi
10216
- );
10217
- }
10218
- return {
10219
- subscribe(cb) {
10220
- listeners.add(cb);
10221
- return () => {
10222
- listeners.delete(cb);
10223
- };
10224
- },
10225
- getSnapshot: () => snapshot,
10226
- async run(flowPath, input = {}) {
10227
- if (!flowClient) return;
10228
- aborted = false;
10229
- update({
10230
- events: [],
10231
- error: null,
10232
- output: null,
10233
- status: "running"
10234
- });
10235
- try {
10236
- const result = await flowClient.run(flowPath, input);
10237
- update({ instanceId: result.instance_id });
10238
- for await (const event of flowClient.streamEvents(result.instance_id)) {
10239
- if (aborted) break;
10240
- update({ events: [...snapshot.events, event] });
10241
- if (event.type === "flow_waiting") {
10242
- update({ status: "waiting" });
10243
- } else if (event.type === "flow_completed") {
10244
- update({ output: event.output, status: "completed" });
10245
- } else if (event.type === "flow_failed") {
10246
- update({ error: event.error, status: "failed" });
10247
- }
10248
- }
10249
- } catch (err) {
10250
- update({
10251
- error: err instanceof Error ? err.message : String(err),
10252
- status: "failed"
10253
- });
10254
- }
10255
- },
10256
- async resume(data) {
10257
- if (!flowClient || !snapshot.instanceId) return;
10258
- update({ status: "running", error: null });
10259
- try {
10260
- const stream = await flowClient.createEventStream(snapshot.instanceId);
10261
- await flowClient.resume(snapshot.instanceId, data);
10262
- for await (const event of stream.events) {
10263
- if (aborted) break;
10264
- update({ events: [...snapshot.events, event] });
10265
- if (event.type === "flow_waiting") {
10266
- update({ status: "waiting" });
10267
- stream.close();
10268
- return;
10269
- } else if (event.type === "flow_completed") {
10270
- update({ output: event.output, status: "completed" });
10271
- stream.close();
10272
- return;
10273
- } else if (event.type === "flow_failed") {
10274
- update({ error: event.error, status: "failed" });
10275
- stream.close();
10276
- return;
10277
- }
10278
- }
10279
- stream.close();
10280
- } catch (err) {
10281
- update({
10282
- error: err instanceof Error ? err.message : String(err),
10283
- status: "failed"
10284
- });
10285
- }
10286
- },
10287
- reset() {
10288
- aborted = true;
10289
- update({
10290
- events: [],
10291
- status: "idle",
10292
- error: null,
10293
- output: null,
10294
- instanceId: null
10295
- });
10296
- },
10297
- destroy() {
10298
- aborted = true;
10299
- listeners.clear();
10300
- }
10301
- };
10302
- }
10303
-
10304
- // src/integrations/svelte/svelte-context.ts
10305
- var RAISIN_CONTEXT_KEY = /* @__PURE__ */ Symbol("raisin");
10306
-
10307
- // src/integrations/react-flow.ts
10308
- function useFlow(react, options) {
10309
- const { useState, useEffect, useRef, useCallback } = react;
10310
- const [events, setEvents] = useState([]);
10311
- const [status, setStatus] = useState("idle");
10312
- const [error, setError] = useState(null);
10313
- const [output, setOutput] = useState(null);
10314
- const [instanceId, setInstanceId] = useState(null);
10315
- const clientRef = useRef(null);
10316
- const abortRef = useRef(false);
10317
- useEffect(() => {
10318
- if (options.database) {
10319
- clientRef.current = options.database.flow;
10320
- } else if (options.baseUrl && options.repository && options.authManager) {
10321
- clientRef.current = new FlowClient(
10322
- options.baseUrl,
10323
- options.repository,
10324
- options.authManager,
10325
- options.clientOptions,
10326
- options.flowsApi
10327
- );
10328
- }
10329
- return () => {
10330
- abortRef.current = true;
10331
- clientRef.current = null;
10332
- };
10333
- }, [options.database, options.baseUrl, options.repository]);
10334
- const run = useCallback(async (flowPath, input = {}) => {
10335
- const client = clientRef.current;
10336
- if (!client) return;
10337
- abortRef.current = false;
10338
- setEvents([]);
10339
- setError(null);
10340
- setOutput(null);
10341
- setStatus("running");
10342
- try {
10343
- const result = await client.run(flowPath, input);
10344
- setInstanceId(result.instance_id);
10345
- for await (const event of client.streamEvents(result.instance_id)) {
10346
- if (abortRef.current) break;
10347
- setEvents((prev) => [...prev, event]);
10348
- if (event.type === "flow_waiting") {
10349
- setStatus("waiting");
10350
- } else if (event.type === "flow_completed") {
10351
- setOutput(event.output);
10352
- setStatus("completed");
10353
- } else if (event.type === "flow_failed") {
10354
- setError(event.error);
10355
- setStatus("failed");
10356
- }
10357
- }
10358
- } catch (err) {
10359
- setError(err instanceof Error ? err.message : String(err));
10360
- setStatus("failed");
10361
- }
10362
- }, []);
10363
- const resume = useCallback(async (data) => {
10364
- const client = clientRef.current;
10365
- if (!client || !instanceId) return;
10366
- setStatus("running");
10367
- setError(null);
10368
- try {
10369
- const stream = await client.createEventStream(instanceId);
10370
- await client.resume(instanceId, data);
10371
- for await (const event of stream.events) {
10372
- if (abortRef.current) break;
10373
- setEvents((prev) => [...prev, event]);
10374
- if (event.type === "flow_waiting") {
10375
- setStatus("waiting");
10376
- stream.close();
10377
- return;
10378
- } else if (event.type === "flow_completed") {
10379
- setOutput(event.output);
10380
- setStatus("completed");
10381
- stream.close();
10382
- return;
10383
- } else if (event.type === "flow_failed") {
10384
- setError(event.error);
10385
- setStatus("failed");
10386
- stream.close();
10387
- return;
10388
- }
10389
- }
10390
- stream.close();
10391
- } catch (err) {
10392
- setError(err instanceof Error ? err.message : String(err));
10393
- setStatus("failed");
10394
- }
10395
- }, [instanceId]);
10396
- const reset = useCallback(() => {
10397
- abortRef.current = true;
10398
- setEvents([]);
10399
- setStatus("idle");
10400
- setError(null);
10401
- setOutput(null);
10402
- setInstanceId(null);
10403
- }, []);
10404
- return {
10405
- events,
10406
- status,
10407
- isRunning: status === "running",
10408
- error,
10409
- output,
10410
- instanceId,
10411
- run,
10412
- resume,
10413
- reset
10414
- };
10415
- }
10416
-
10417
- // src/integrations/react/use-auth.ts
10418
- function createUseAuth(react, context) {
10419
- return function useAuth() {
10420
- const { useState, useEffect, useRef, useCallback, useContext } = react;
10421
- const ctx = useContext(context);
10422
- if (!ctx) {
10423
- throw new Error("useAuth must be used within a <RaisinProvider>");
10424
- }
10425
- const { client } = ctx;
10426
- const [user, setUser] = useState(client.getStoredUser());
10427
- const [isLoading, setIsLoading] = useState(false);
10428
- const mountedRef = useRef(true);
10429
- useEffect(() => {
10430
- mountedRef.current = true;
10431
- const unsubAuth = client.onAuthStateChange(({ event, session }) => {
10432
- if (!mountedRef.current) return;
10433
- if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED" || event === "USER_UPDATED") {
10434
- setUser(session.user);
10435
- } else if (event === "SIGNED_OUT" || event === "SESSION_EXPIRED") {
10436
- setUser(null);
10437
- }
10438
- });
10439
- const unsubUser = client.onUserChange(({ node }) => {
10440
- if (!mountedRef.current) return;
10441
- setUser((prev) => {
10442
- if (!prev) return prev;
10443
- return {
10444
- ...prev,
10445
- home: node.path,
10446
- displayName: node.properties.display_name ?? prev.displayName,
10447
- avatarUrl: node.properties.avatar_url ?? prev.avatarUrl
10448
- };
10449
- });
10450
- });
10451
- return () => {
10452
- mountedRef.current = false;
10453
- unsubAuth();
10454
- unsubUser();
10455
- };
10456
- }, [client]);
10457
- const login = useCallback(async (email, password, repository) => {
10458
- setIsLoading(true);
10459
- try {
10460
- const result = await client.loginWithEmail(email, password, repository);
10461
- return result;
10462
- } finally {
10463
- if (mountedRef.current) setIsLoading(false);
10464
- }
10465
- }, [client]);
10466
- const register = useCallback(async (email, password, repository, displayName) => {
10467
- setIsLoading(true);
10468
- try {
10469
- const result = await client.registerWithEmail(email, password, repository, displayName);
10470
- return result;
10471
- } finally {
10472
- if (mountedRef.current) setIsLoading(false);
10473
- }
10474
- }, [client]);
10475
- const logout = useCallback(async (options) => {
10476
- setIsLoading(true);
10477
- try {
10478
- await client.logout(options);
10479
- } finally {
10480
- if (mountedRef.current) setIsLoading(false);
10481
- }
10482
- }, [client]);
10483
- const initSession = useCallback(async (repository) => {
10484
- setIsLoading(true);
10485
- try {
10486
- const result = await client.initSession(repository);
10487
- return result;
10488
- } finally {
10489
- if (mountedRef.current) setIsLoading(false);
10490
- }
10491
- }, [client]);
10492
- return {
10493
- user,
10494
- isAuthenticated: user !== null,
10495
- isLoading,
10496
- login,
10497
- register,
10498
- logout,
10499
- initSession
10500
- };
10501
- };
10502
- }
10503
-
10504
- // src/integrations/react/use-connection.ts
10505
- function createUseConnection(react, context) {
10506
- return function useConnection() {
10507
- const { useState, useEffect, useCallback, useContext } = react;
10508
- const ctx = useContext(context);
10509
- if (!ctx) {
10510
- throw new Error("useConnection must be used within a <RaisinProvider>");
10511
- }
10512
- const { client } = ctx;
10513
- const [state, setState] = useState(client.getConnectionState());
10514
- const [isReady, setIsReady] = useState(client.isReady());
10515
- useEffect(() => {
10516
- const unsubConn = client.onConnectionStateChange((newState) => {
10517
- setState(newState);
10518
- });
10519
- const unsubReady = client.onReadyStateChange((ready) => {
10520
- setIsReady(ready);
10521
- });
10522
- return () => {
10523
- unsubConn();
10524
- unsubReady();
10525
- };
10526
- }, [client]);
10527
- const connect = useCallback(async () => {
10528
- await client.connect();
10529
- }, [client]);
10530
- const disconnect = useCallback(() => {
10531
- client.disconnect();
10532
- }, [client]);
10533
- return {
10534
- state,
10535
- isConnected: state === "connected" /* Connected */,
10536
- isReady,
10537
- connect,
10538
- disconnect
10539
- };
10540
- };
10541
- }
10542
-
10543
- // src/integrations/react/use-sql.ts
10544
- function rowsToObjects3(result) {
10545
- return result.rows;
10546
- }
10547
- function createUseSql(react, context) {
10548
- return function useSql(sql, params, options) {
10549
- const { useState, useEffect, useRef, useCallback, useContext } = react;
10550
- const ctx = useContext(context);
10551
- if (!ctx) {
10552
- throw new Error("useSql must be used within a <RaisinProvider>");
10553
- }
10554
- const { client, repository: defaultRepo } = ctx;
10555
- const [data, setData] = useState(null);
10556
- const [isLoading, setIsLoading] = useState(false);
10557
- const [error, setError] = useState(null);
10558
- const mountedRef = useRef(true);
10559
- const enabled = options?.enabled !== false;
10560
- const repo = options?.repository ?? defaultRepo;
10561
- const refetchOnReconnect = options?.refetchOnReconnect !== false;
10562
- const realtime = options?.realtime;
10563
- const paramsKey = params ? JSON.stringify(params) : "";
10564
- const fetchData = useCallback(async () => {
10565
- if (!repo) return;
10566
- setIsLoading(true);
10567
- setError(null);
10568
- try {
10569
- const db = client.database(repo);
10570
- const result = await db.executeSql(sql, params);
10571
- if (mountedRef.current) {
10572
- setData(rowsToObjects3(result));
10573
- }
10574
- } catch (err) {
10575
- if (mountedRef.current) {
10576
- setError(err instanceof Error ? err : new Error(String(err)));
10577
- }
10578
- } finally {
10579
- if (mountedRef.current) {
10580
- setIsLoading(false);
10581
- }
10582
- }
10583
- }, [client, repo, sql, paramsKey]);
10584
- useEffect(() => {
10585
- mountedRef.current = true;
10586
- if (enabled) {
10587
- fetchData();
10588
- }
10589
- return () => {
10590
- mountedRef.current = false;
10591
- };
10592
- }, [enabled, fetchData]);
10593
- useEffect(() => {
10594
- if (!enabled || !refetchOnReconnect) return;
10595
- const unsub = client.onReconnected(() => {
10596
- fetchData();
10597
- });
10598
- return unsub;
10599
- }, [client, enabled, refetchOnReconnect, fetchData]);
10600
- useEffect(() => {
10601
- if (!enabled || !realtime || !repo) return;
10602
- const db = client.database(repo);
10603
- let unsubscribe;
10604
- db.events().subscribe(
10605
- {
10606
- workspace: realtime.workspace,
10607
- event_types: realtime.eventTypes,
10608
- path: realtime.path,
10609
- node_type: realtime.nodeType
10610
- },
10611
- () => {
10612
- fetchData();
10613
- }
10614
- ).then((sub) => {
10615
- unsubscribe = () => sub.unsubscribe();
10616
- });
10617
- return () => {
10618
- unsubscribe?.();
10619
- };
10620
- }, [client, enabled, repo, realtime?.workspace, realtime?.path, realtime?.nodeType, fetchData]);
10621
- return {
10622
- data,
10623
- isLoading,
10624
- error,
10625
- refetch: fetchData
10626
- };
10627
- };
10628
- }
10629
-
10630
- // src/integrations/react/use-subscription.ts
10631
- function createUseSubscription(react, context) {
10632
- return function useSubscription(options, callback) {
10633
- const { useEffect, useRef, useContext } = react;
10634
- const ctx = useContext(context);
10635
- if (!ctx) {
10636
- throw new Error("useSubscription must be used within a <RaisinProvider>");
10637
- }
10638
- const { client, repository } = ctx;
10639
- const callbackRef = useRef(callback);
10640
- callbackRef.current = callback;
10641
- const enabled = options.enabled !== false;
10642
- useEffect(() => {
10643
- if (!enabled || !repository || !options.workspace) return;
10644
- const db = client.database(repository);
10645
- let unsubscribe;
10646
- let cancelled = false;
10647
- db.events().subscribe(
10648
- {
10649
- workspace: options.workspace,
10650
- event_types: options.event_types,
10651
- path: options.path,
10652
- node_type: options.node_type,
10653
- include_node: options.include_node
10654
- },
10655
- (event) => {
10656
- if (!cancelled) {
10657
- callbackRef.current(event);
10658
- }
10659
- }
10660
- ).then((sub) => {
10661
- if (cancelled) {
10662
- sub.unsubscribe();
10663
- } else {
10664
- unsubscribe = () => sub.unsubscribe();
10665
- }
10666
- });
10667
- return () => {
10668
- cancelled = true;
10669
- unsubscribe?.();
10670
- };
10671
- }, [client, repository, enabled, options.workspace, options.path, options.node_type, options.include_node]);
10672
- };
10673
- }
10674
-
10675
- // src/integrations/react/index.ts
10676
- function createRaisinReact(react) {
10677
- const RaisinContext = react.createContext(null);
10678
- function RaisinProvider(props) {
10679
- const { client, repository, children } = props;
10680
- const value = react.useMemo(
10681
- () => ({ client, repository }),
10682
- [client, repository]
10683
- );
10684
- return react.createElement(
10685
- RaisinContext.Provider,
10686
- { value },
10687
- children
10688
- );
10689
- }
10690
- function useRaisinClient() {
10691
- const ctx = react.useContext(RaisinContext);
10692
- if (!ctx) {
10693
- throw new Error("useRaisinClient must be used within a <RaisinProvider>");
10694
- }
10695
- return ctx.client;
10696
- }
10697
- function useDatabase(repository) {
10698
- const ctx = react.useContext(RaisinContext);
10699
- if (!ctx) {
10700
- throw new Error("useDatabase must be used within a <RaisinProvider>");
10701
- }
10702
- const repo = repository ?? ctx.repository;
10703
- if (!repo) {
10704
- throw new Error("useDatabase requires a repository \u2014 pass it as an argument or set it on <RaisinProvider>");
10705
- }
10706
- return react.useMemo(() => ctx.client.database(repo), [ctx.client, repo]);
10707
- }
10708
- const useAuth = createUseAuth(react, RaisinContext);
10709
- const useConnection = createUseConnection(react, RaisinContext);
10710
- const useSql = createUseSql(react, RaisinContext);
10711
- const useSubscription = createUseSubscription(react, RaisinContext);
10712
- function useConversation2(options) {
10713
- return useConversation(react, options);
10714
- }
10715
- function useConversationList2(options) {
10716
- return useConversationList(react, options);
10717
- }
10718
- function useFlow2(options) {
10719
- return useFlow(react, options);
10720
- }
10721
- return {
10722
- RaisinProvider,
10723
- useRaisinClient,
10724
- useDatabase,
10725
- useAuth,
10726
- useConnection,
10727
- useSql,
10728
- useSubscription,
10729
- useConversation: useConversation2,
10730
- useConversationList: useConversationList2,
10731
- useFlow: useFlow2
10732
- };
8869
+ function isFlowDefinitionContainer(node) {
8870
+ return node.node_type === "raisin:FlowContainer";
10733
8871
  }
10734
8872
  export {
10735
8873
  AllNodeEventTypes,
@@ -10751,6 +8889,7 @@ export {
10751
8889
  DEFAULT_RETRY_BASE_DELAY,
10752
8890
  DEFAULT_RETRY_MAX_DELAY,
10753
8891
  DEFAULT_RETRY_OPTIONS,
8892
+ DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS,
10754
8893
  Database,
10755
8894
  ElementTypes,
10756
8895
  EventHandler,
@@ -10758,11 +8897,16 @@ export {
10758
8897
  FlowClient,
10759
8898
  FlowsApi,
10760
8899
  FunctionsApi,
8900
+ HttpArchetypes,
10761
8901
  HttpDatabase,
8902
+ HttpElementTypes,
10762
8903
  HttpFunctionsApi,
8904
+ HttpNodeTypes,
10763
8905
  HttpWorkspaceClient,
8906
+ InboxApi,
10764
8907
  LocalStorageTokenStorage,
10765
8908
  LogLevel,
8909
+ McpClient,
10766
8910
  MemoryTokenStorage,
10767
8911
  NodeBuilder,
10768
8912
  NodeEventType,
@@ -10771,6 +8915,7 @@ export {
10771
8915
  NodeOperations,
10772
8916
  NodeTypes,
10773
8917
  RAISIN_CONTEXT_KEY,
8918
+ RETRY_STRATEGIES,
10774
8919
  RaisinAbortError,
10775
8920
  RaisinAuthError,
10776
8921
  RaisinClient,
@@ -10785,6 +8930,7 @@ export {
10785
8930
  ResponseStatus,
10786
8931
  RetryBuilder,
10787
8932
  SSEClient,
8933
+ SchedulerApi,
10788
8934
  SqlQuery,
10789
8935
  Tags,
10790
8936
  Transaction,
@@ -10806,6 +8952,7 @@ export {
10806
8952
  createFlowAdapter,
10807
8953
  createLoader,
10808
8954
  createRaisinReact,
8955
+ createRaisinVue,
10809
8956
  createRequestScopedClient,
10810
8957
  createSSRClient,
10811
8958
  createSqlAdapter,
@@ -10818,13 +8965,18 @@ export {
10818
8965
  getErrorMessage,
10819
8966
  getLogLevel,
10820
8967
  getLoggerConfig,
8968
+ getRefDisplayName,
8969
+ getRefPath,
10821
8970
  isEventMessage,
8971
+ isFlowDefinitionContainer,
8972
+ isFlowDefinitionStep,
10822
8973
  isHttpClient,
10823
8974
  isResponseEnvelope,
10824
8975
  isRetryableError,
10825
8976
  isTerminalEvent,
10826
8977
  isWebSocketClient,
10827
8978
  logger,
8979
+ normalizeHomePath,
10828
8980
  projectPlansFromMessages,
10829
8981
  rowsToObjects,
10830
8982
  setLogLevel,
@@ -10834,3 +8986,4 @@ export {
10834
8986
  useFlow,
10835
8987
  withRetry
10836
8988
  };
8989
+ //# sourceMappingURL=index.mjs.map