@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.js CHANGED
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __typeError = (msg) => {
7
+ throw TypeError(msg);
8
+ };
8
9
  var __export = (target, all) => {
9
10
  for (var name in all)
10
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,19 +18,15 @@ var __copyProps = (to, from, except, desc) => {
17
18
  }
18
19
  return to;
19
20
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
23
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
24
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
25
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
29
26
 
30
27
  // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
28
+ var src_exports = {};
29
+ __export(src_exports, {
33
30
  AllNodeEventTypes: () => AllNodeEventTypes,
34
31
  Archetypes: () => Archetypes,
35
32
  AuthManager: () => AuthManager,
@@ -49,6 +46,7 @@ __export(index_exports, {
49
46
  DEFAULT_RETRY_BASE_DELAY: () => DEFAULT_RETRY_BASE_DELAY,
50
47
  DEFAULT_RETRY_MAX_DELAY: () => DEFAULT_RETRY_MAX_DELAY,
51
48
  DEFAULT_RETRY_OPTIONS: () => DEFAULT_RETRY_OPTIONS,
49
+ DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS: () => DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS,
52
50
  Database: () => Database,
53
51
  ElementTypes: () => ElementTypes,
54
52
  EventHandler: () => EventHandler,
@@ -56,11 +54,16 @@ __export(index_exports, {
56
54
  FlowClient: () => FlowClient,
57
55
  FlowsApi: () => FlowsApi,
58
56
  FunctionsApi: () => FunctionsApi,
57
+ HttpArchetypes: () => HttpArchetypes,
59
58
  HttpDatabase: () => HttpDatabase,
59
+ HttpElementTypes: () => HttpElementTypes,
60
60
  HttpFunctionsApi: () => HttpFunctionsApi,
61
+ HttpNodeTypes: () => HttpNodeTypes,
61
62
  HttpWorkspaceClient: () => HttpWorkspaceClient,
63
+ InboxApi: () => InboxApi,
62
64
  LocalStorageTokenStorage: () => LocalStorageTokenStorage,
63
65
  LogLevel: () => LogLevel,
66
+ McpClient: () => McpClient,
64
67
  MemoryTokenStorage: () => MemoryTokenStorage,
65
68
  NodeBuilder: () => NodeBuilder,
66
69
  NodeEventType: () => NodeEventType,
@@ -69,6 +72,7 @@ __export(index_exports, {
69
72
  NodeOperations: () => NodeOperations,
70
73
  NodeTypes: () => NodeTypes,
71
74
  RAISIN_CONTEXT_KEY: () => RAISIN_CONTEXT_KEY,
75
+ RETRY_STRATEGIES: () => RETRY_STRATEGIES,
72
76
  RaisinAbortError: () => RaisinAbortError,
73
77
  RaisinAuthError: () => RaisinAuthError,
74
78
  RaisinClient: () => RaisinClient,
@@ -83,6 +87,7 @@ __export(index_exports, {
83
87
  ResponseStatus: () => ResponseStatus,
84
88
  RetryBuilder: () => RetryBuilder,
85
89
  SSEClient: () => SSEClient,
90
+ SchedulerApi: () => SchedulerApi,
86
91
  SqlQuery: () => SqlQuery,
87
92
  Tags: () => Tags,
88
93
  Transaction: () => Transaction,
@@ -104,6 +109,7 @@ __export(index_exports, {
104
109
  createFlowAdapter: () => createFlowAdapter,
105
110
  createLoader: () => createLoader,
106
111
  createRaisinReact: () => createRaisinReact,
112
+ createRaisinVue: () => createRaisinVue,
107
113
  createRequestScopedClient: () => createRequestScopedClient,
108
114
  createSSRClient: () => createSSRClient,
109
115
  createSqlAdapter: () => createSqlAdapter,
@@ -116,13 +122,18 @@ __export(index_exports, {
116
122
  getErrorMessage: () => getErrorMessage,
117
123
  getLogLevel: () => getLogLevel,
118
124
  getLoggerConfig: () => getLoggerConfig,
125
+ getRefDisplayName: () => getRefDisplayName,
126
+ getRefPath: () => getRefPath,
119
127
  isEventMessage: () => isEventMessage,
128
+ isFlowDefinitionContainer: () => isFlowDefinitionContainer,
129
+ isFlowDefinitionStep: () => isFlowDefinitionStep,
120
130
  isHttpClient: () => isHttpClient,
121
131
  isResponseEnvelope: () => isResponseEnvelope,
122
132
  isRetryableError: () => isRetryableError,
123
133
  isTerminalEvent: () => isTerminalEvent,
124
134
  isWebSocketClient: () => isWebSocketClient,
125
135
  logger: () => logger,
136
+ normalizeHomePath: () => normalizeHomePath,
126
137
  projectPlansFromMessages: () => projectPlansFromMessages,
127
138
  rowsToObjects: () => rowsToObjects,
128
139
  setLogLevel: () => setLogLevel,
@@ -132,7 +143,7 @@ __export(index_exports, {
132
143
  useFlow: () => useFlow,
133
144
  withRetry: () => withRetry
134
145
  });
135
- module.exports = __toCommonJS(index_exports);
146
+ module.exports = __toCommonJS(src_exports);
136
147
 
137
148
  // src/client.ts
138
149
  var import_events4 = require("events");
@@ -326,7 +337,8 @@ var Connection = class extends import_events.EventEmitter {
326
337
  autoReconnect: options.autoReconnect ?? true,
327
338
  heartbeatInterval: options.heartbeatInterval ?? 3e4,
328
339
  heartbeatTimeout: options.heartbeatTimeout ?? 5e3,
329
- protocols: options.protocols
340
+ protocols: options.protocols,
341
+ headers: options.headers
330
342
  };
331
343
  this.reconnectManager = new ReconnectManager(options.reconnectOptions);
332
344
  this.WebSocketImpl = getWebSocketImpl();
@@ -342,10 +354,10 @@ var Connection = class extends import_events.EventEmitter {
342
354
  this.setState("connecting" /* Connecting */);
343
355
  return new Promise((resolve, reject) => {
344
356
  try {
345
- this.ws = new this.WebSocketImpl(
346
- this.url,
347
- this.options.protocols
348
- );
357
+ const isBrowserWebSocket = typeof WebSocket !== "undefined" && this.WebSocketImpl === WebSocket;
358
+ this.ws = isBrowserWebSocket || !this.options.headers ? new this.WebSocketImpl(this.url, this.options.protocols) : new this.WebSocketImpl(this.url, this.options.protocols, {
359
+ headers: this.options.headers
360
+ });
349
361
  this.ws.binaryType = "arraybuffer";
350
362
  this.ws.onopen = () => {
351
363
  this.setState("connected" /* Connected */);
@@ -434,6 +446,16 @@ var Connection = class extends import_events.EventEmitter {
434
446
  isConnected() {
435
447
  return this.state === "connected" /* Connected */;
436
448
  }
449
+ /**
450
+ * Whether auto-reconnect is enabled for this connection.
451
+ *
452
+ * Used by the client to decide whether requests issued while the
453
+ * connection is down can be queued (the connection will come back) or
454
+ * should fail immediately.
455
+ */
456
+ isAutoReconnectEnabled() {
457
+ return this.options.autoReconnect;
458
+ }
437
459
  /**
438
460
  * Reconnect to the server
439
461
  */
@@ -831,6 +853,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
831
853
  RequestType2["NodeReorder"] = "node_reorder";
832
854
  RequestType2["NodeMoveChildBefore"] = "node_move_child_before";
833
855
  RequestType2["NodeMoveChildAfter"] = "node_move_child_after";
856
+ RequestType2["NodeApplyChildOrder"] = "node_apply_child_order";
834
857
  RequestType2["PropertyGet"] = "property_get";
835
858
  RequestType2["PropertyUpdate"] = "property_update";
836
859
  RequestType2["RelationAdd"] = "relation_add";
@@ -850,6 +873,8 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
850
873
  RequestType2["BranchUpdateHead"] = "branch_update_head";
851
874
  RequestType2["BranchMerge"] = "branch_merge";
852
875
  RequestType2["BranchCompare"] = "branch_compare";
876
+ RequestType2["BranchDiff"] = "branch_diff";
877
+ RequestType2["BranchCopyNodes"] = "branch_copy_nodes";
853
878
  RequestType2["TagCreate"] = "tag_create";
854
879
  RequestType2["TagGet"] = "tag_get";
855
880
  RequestType2["TagList"] = "tag_list";
@@ -870,6 +895,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
870
895
  RequestType2["ArchetypeDelete"] = "archetype_delete";
871
896
  RequestType2["ArchetypePublish"] = "archetype_publish";
872
897
  RequestType2["ArchetypeUnpublish"] = "archetype_unpublish";
898
+ RequestType2["ArchetypeGetResolved"] = "archetype_get_resolved";
873
899
  RequestType2["ElementTypeCreate"] = "element_type_create";
874
900
  RequestType2["ElementTypeGet"] = "element_type_get";
875
901
  RequestType2["ElementTypeList"] = "element_type_list";
@@ -877,6 +903,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
877
903
  RequestType2["ElementTypeDelete"] = "element_type_delete";
878
904
  RequestType2["ElementTypePublish"] = "element_type_publish";
879
905
  RequestType2["ElementTypeUnpublish"] = "element_type_unpublish";
906
+ RequestType2["ElementTypeGetResolved"] = "element_type_get_resolved";
880
907
  RequestType2["FullTextSearch"] = "full_text_search";
881
908
  RequestType2["VectorSearch"] = "vector_search";
882
909
  RequestType2["Subscribe"] = "subscribe";
@@ -901,6 +928,10 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
901
928
  RequestType2["FlowUnsubscribeEvents"] = "flow_unsubscribe_events";
902
929
  RequestType2["FunctionInvoke"] = "function_invoke";
903
930
  RequestType2["FunctionInvokeSync"] = "function_invoke_sync";
931
+ RequestType2["ScheduledInvocationCreate"] = "scheduled_invocation_create";
932
+ RequestType2["ScheduledInvocationCancel"] = "scheduled_invocation_cancel";
933
+ RequestType2["ScheduledInvocationList"] = "scheduled_invocation_list";
934
+ RequestType2["ScheduledInvocationGet"] = "scheduled_invocation_get";
904
935
  return RequestType2;
905
936
  })(RequestType || {});
906
937
  var ResponseStatus = /* @__PURE__ */ ((ResponseStatus2) => {
@@ -1010,6 +1041,14 @@ var EventHandler = class {
1010
1041
  * If a subscription with identical filters already exists, the callback
1011
1042
  * is added to the existing subscription instead of creating a new one.
1012
1043
  *
1044
+ * Path filter semantics (matched server-side):
1045
+ * - A plain path matches **only that exact node** — there is no implicit
1046
+ * prefix matching.
1047
+ * - `*` matches exactly one path segment (e.g. `/inbox/*` matches
1048
+ * `/inbox/a` but not `/inbox/a/b`).
1049
+ * - `**` matches recursively (e.g. `/inbox/**` matches all descendants
1050
+ * of `/inbox`, but not `/inbox` itself).
1051
+ *
1013
1052
  * @param filters - Event filters
1014
1053
  * @param callback - Callback function for events
1015
1054
  * @returns Subscription object
@@ -1200,14 +1239,29 @@ var EventHandler = class {
1200
1239
  * Restore subscriptions after reconnect
1201
1240
  *
1202
1241
  * Re-subscribes to the server with the original filters for all active
1203
- * subscription entries.
1242
+ * subscription entries. Failed entries are kept (with their callbacks)
1243
+ * and marked pending so a retry call can re-attempt them without
1244
+ * duplicating already-restored subscriptions.
1245
+ *
1246
+ * @param options.retry - When true, only re-attempts entries that failed
1247
+ * in a previous call (instead of re-subscribing everything)
1248
+ * @throws Error if any subscription could not be restored
1204
1249
  */
1205
- async restoreSubscriptions() {
1206
- const entriesToRestore = Array.from(this.filterSubscriptions.entries());
1207
- this.subscriptions.clear();
1208
- this.filterSubscriptions.clear();
1250
+ async restoreSubscriptions(options) {
1251
+ if (!options?.retry) {
1252
+ for (const entry of this.filterSubscriptions.values()) {
1253
+ this.subscriptions.delete(entry.serverId);
1254
+ entry.restorePending = true;
1255
+ }
1256
+ }
1257
+ const entriesToRestore = Array.from(this.filterSubscriptions.entries()).filter(([, entry]) => entry.restorePending);
1258
+ let failureCount = 0;
1259
+ let lastError;
1209
1260
  for (const [hash, entry] of entriesToRestore) {
1210
- if (entry.callbacks.size === 0) continue;
1261
+ if (entry.callbacks.size === 0) {
1262
+ entry.restorePending = false;
1263
+ continue;
1264
+ }
1211
1265
  try {
1212
1266
  const payload = { filters: entry.filters };
1213
1267
  const response = await this.sendRequest(
@@ -1215,22 +1269,24 @@ var EventHandler = class {
1215
1269
  "subscribe" /* Subscribe */
1216
1270
  );
1217
1271
  const newServerId = response.subscription_id;
1218
- this.filterSubscriptions.set(hash, {
1219
- serverId: newServerId,
1220
- filters: entry.filters,
1221
- callbacks: entry.callbacks
1222
- });
1272
+ entry.serverId = newServerId;
1273
+ entry.restorePending = false;
1223
1274
  this.subscriptions.set(newServerId, hash);
1224
1275
  for (const callback of entry.callbacks) {
1225
1276
  this.callbackToHash.set(callback, hash);
1226
1277
  }
1227
1278
  } catch (error) {
1228
1279
  logger.error("Failed to restore subscription:", error);
1229
- for (const callback of entry.callbacks) {
1230
- this.callbackToHash.delete(callback);
1231
- }
1280
+ failureCount++;
1281
+ lastError = error;
1232
1282
  }
1233
1283
  }
1284
+ if (failureCount > 0) {
1285
+ const message = lastError instanceof Error ? lastError.message : String(lastError);
1286
+ throw new Error(
1287
+ `Failed to restore ${failureCount} subscription(s) after reconnect: ${message}`
1288
+ );
1289
+ }
1234
1290
  }
1235
1291
  };
1236
1292
  var EventSubscriptions = class {
@@ -1265,7 +1321,15 @@ var EventSubscriptions = class {
1265
1321
  /**
1266
1322
  * Subscribe to events for a specific path
1267
1323
  *
1268
- * @param path - Path pattern (supports wildcards like "/folder/*")
1324
+ * Path pattern semantics (matched server-side):
1325
+ * - A plain path matches **only that exact node** — there is no implicit
1326
+ * prefix matching, so `/folder` does NOT match `/folder/child`.
1327
+ * - `*` matches exactly one path segment: `/folder/*` matches
1328
+ * `/folder/child` but not `/folder/child/grandchild`.
1329
+ * - `**` matches recursively: `/folder/**` matches every descendant of
1330
+ * `/folder` (but not `/folder` itself).
1331
+ *
1332
+ * @param path - Path pattern (e.g. "/folder", "/folder/*", "/folder/**")
1269
1333
  * @param callback - Callback function for events
1270
1334
  * @param options - Optional subscription options
1271
1335
  * @param options.includeNode - Include full node data in event payload (default: false)
@@ -1312,6 +1376,40 @@ var NodeOperations = class {
1312
1376
  const result = await this.sendRequest(payload);
1313
1377
  return result;
1314
1378
  }
1379
+ /**
1380
+ * Create a node, auto-creating any missing ancestor folders.
1381
+ *
1382
+ * @param options - Node creation options (with optional `parentNodeType`)
1383
+ * @returns Created node
1384
+ */
1385
+ async createDeep(options) {
1386
+ const payload = {
1387
+ node_type: options.type,
1388
+ path: options.path,
1389
+ properties: options.properties ?? {},
1390
+ content: options.content,
1391
+ parent_node_type: options.parentNodeType
1392
+ };
1393
+ const result = await this.sendRequest(payload, "node_create_deep");
1394
+ return result;
1395
+ }
1396
+ /**
1397
+ * Upsert a node by path (create-or-update), auto-creating any missing ancestor folders.
1398
+ *
1399
+ * @param options - Node creation options (with optional `parentNodeType`)
1400
+ * @returns Upserted node
1401
+ */
1402
+ async upsertDeep(options) {
1403
+ const payload = {
1404
+ node_type: options.type,
1405
+ path: options.path,
1406
+ properties: options.properties ?? {},
1407
+ content: options.content,
1408
+ parent_node_type: options.parentNodeType
1409
+ };
1410
+ const result = await this.sendRequest(payload, "node_upsert_deep");
1411
+ return result;
1412
+ }
1315
1413
  /**
1316
1414
  * Update an existing node
1317
1415
  *
@@ -1361,6 +1459,75 @@ var NodeOperations = class {
1361
1459
  throw error;
1362
1460
  }
1363
1461
  }
1462
+ /**
1463
+ * List a node's revision history (git-style "file history"), newest first.
1464
+ *
1465
+ * Each entry carries the `revision` (usable with `atRevision()` reads to
1466
+ * fetch the full snapshot), plus when/who and whether the node was deleted
1467
+ * at that revision. Always available regardless of the NodeType `auditable`
1468
+ * flag — it reflects the structural MVCC version history.
1469
+ *
1470
+ * @param id - Node ID
1471
+ * @param options - Optional `{ limit }` to cap the number of revisions
1472
+ * @returns Array of revision entries (newest first)
1473
+ *
1474
+ * @example
1475
+ * ```typescript
1476
+ * const history = await ws.nodes().history(nodeId, { limit: 50 });
1477
+ * for (const rev of history) {
1478
+ * const snapshot = await ws.atRevision(rev.revision).nodes().get(nodeId);
1479
+ * }
1480
+ * ```
1481
+ */
1482
+ async history(id, options) {
1483
+ const payload = {
1484
+ node_id: id,
1485
+ limit: options?.limit
1486
+ };
1487
+ const result = await this.sendRequest(payload, "node_history");
1488
+ return result ?? [];
1489
+ }
1490
+ /**
1491
+ * List a node's revision history by path (newest first).
1492
+ *
1493
+ * @param path - Node path
1494
+ * @param options - Optional `{ limit }` to cap the number of revisions
1495
+ * @returns Array of revision entries (newest first)
1496
+ */
1497
+ async historyByPath(path, options) {
1498
+ const payload = {
1499
+ path,
1500
+ limit: options?.limit
1501
+ };
1502
+ const result = await this.sendRequest(payload, "node_history");
1503
+ return result ?? [];
1504
+ }
1505
+ /**
1506
+ * Query a node's audit-log entries by ID.
1507
+ *
1508
+ * Audit logs are only produced for NodeTypes marked `auditable`. This is
1509
+ * distinct from {@link history}, which is the always-on structural revision
1510
+ * history available for every node.
1511
+ *
1512
+ * @param id - Node ID
1513
+ * @returns Array of audit-log entries
1514
+ */
1515
+ async auditLog(id) {
1516
+ const payload = { node_id: id };
1517
+ const result = await this.sendRequest(payload, "audit_query");
1518
+ return result ?? [];
1519
+ }
1520
+ /**
1521
+ * Query a node's audit-log entries by path.
1522
+ *
1523
+ * @param path - Node path
1524
+ * @returns Array of audit-log entries
1525
+ */
1526
+ async auditLogByPath(path) {
1527
+ const payload = { path };
1528
+ const result = await this.sendRequest(payload, "audit_query");
1529
+ return result ?? [];
1530
+ }
1364
1531
  /**
1365
1532
  * Query nodes
1366
1533
  *
@@ -1528,7 +1695,7 @@ var NodeOperations = class {
1528
1695
  */
1529
1696
  async rename(nodePath, newName) {
1530
1697
  const payload = {
1531
- node_path: nodePath,
1698
+ old_path: nodePath,
1532
1699
  new_name: newName
1533
1700
  };
1534
1701
  const result = await this.sendRequest(payload, "node_rename");
@@ -1544,9 +1711,8 @@ var NodeOperations = class {
1544
1711
  */
1545
1712
  async copy(fromPath, toParentPath, newName) {
1546
1713
  const payload = {
1547
- from_path: fromPath,
1548
- to_parent_path: toParentPath,
1549
- deep: false
1714
+ source_path: fromPath,
1715
+ target_parent: toParentPath
1550
1716
  };
1551
1717
  if (newName !== void 0) {
1552
1718
  payload.new_name = newName;
@@ -1564,9 +1730,8 @@ var NodeOperations = class {
1564
1730
  */
1565
1731
  async copyTree(fromPath, toParentPath, newName) {
1566
1732
  const payload = {
1567
- from_path: fromPath,
1568
- to_parent_path: toParentPath,
1569
- deep: true
1733
+ source_path: fromPath,
1734
+ target_parent: toParentPath
1570
1735
  };
1571
1736
  if (newName !== void 0) {
1572
1737
  payload.new_name = newName;
@@ -1623,6 +1788,24 @@ var NodeOperations = class {
1623
1788
  const result = await this.sendRequest(payload, "node_move_child_after");
1624
1789
  return result;
1625
1790
  }
1791
+ /**
1792
+ * Replay a parent's child order from another branch onto the current branch.
1793
+ *
1794
+ * Carries node sibling-order across branches — useful for selective publish
1795
+ * flows, where copying node content (e.g. via SQL) does not move the ordering
1796
+ * index. Only children that exist under the parent on both branches are
1797
+ * reordered. The target branch is the connection's current branch.
1798
+ *
1799
+ * @param parentPath - Parent node path
1800
+ * @param sourceBranch - Branch to copy the child order FROM
1801
+ */
1802
+ async applyChildOrder(parentPath, sourceBranch) {
1803
+ const payload = {
1804
+ parent_path: parentPath,
1805
+ source_branch: sourceBranch
1806
+ };
1807
+ await this.sendRequest(payload, "node_apply_child_order");
1808
+ }
1626
1809
  // ========================================================================
1627
1810
  // Relationship Operations
1628
1811
  // ========================================================================
@@ -1897,7 +2080,17 @@ var NodeFileSource = class {
1897
2080
  return;
1898
2081
  }
1899
2082
  if (!this._fsPromises) {
1900
- this._fsPromises = await import("fs/promises");
2083
+ if (!isNodeEnvironment()) {
2084
+ throw new Error(
2085
+ "NodeFileSource requires Node.js (fs/promises is not available in browsers). In the browser, use a File or Blob object instead."
2086
+ );
2087
+ }
2088
+ const specifier = "fs/promises";
2089
+ this._fsPromises = await import(
2090
+ /* @vite-ignore */
2091
+ /* webpackIgnore: true */
2092
+ specifier
2093
+ );
1901
2094
  }
1902
2095
  const stats = await this._fsPromises.stat(this._filePath);
1903
2096
  this._size = stats.size;
@@ -2698,6 +2891,17 @@ var Archetypes = class {
2698
2891
  "archetype_unpublish" /* ArchetypeUnpublish */
2699
2892
  );
2700
2893
  }
2894
+ /**
2895
+ * Get resolved Archetype with full inheritance applied
2896
+ */
2897
+ async getResolved(name) {
2898
+ return this.sendRequest(
2899
+ {
2900
+ name
2901
+ },
2902
+ "archetype_get_resolved" /* ArchetypeGetResolved */
2903
+ );
2904
+ }
2701
2905
  };
2702
2906
 
2703
2907
  // src/element-types.ts
@@ -2785,6 +2989,17 @@ var ElementTypes = class {
2785
2989
  "element_type_unpublish" /* ElementTypeUnpublish */
2786
2990
  );
2787
2991
  }
2992
+ /**
2993
+ * Get resolved ElementType with full inheritance applied
2994
+ */
2995
+ async getResolved(name) {
2996
+ return this.sendRequest(
2997
+ {
2998
+ name
2999
+ },
3000
+ "element_type_get_resolved" /* ElementTypeGetResolved */
3001
+ );
3002
+ }
2788
3003
  };
2789
3004
 
2790
3005
  // src/branches.ts
@@ -2886,6 +3101,102 @@ var Branches = class {
2886
3101
  "branch_compare" /* BranchCompare */
2887
3102
  );
2888
3103
  }
3104
+ /**
3105
+ * Per-node diff of `branch` relative to `baseBranch` (added/modified/deleted).
3106
+ *
3107
+ * Cheaper than reading both branch listings: cost scales with the number of
3108
+ * changed nodes since the branches diverged, not the total content.
3109
+ */
3110
+ async diff(branch, baseBranch) {
3111
+ return this.sendRequest(
3112
+ {
3113
+ branch,
3114
+ base_branch: baseBranch
3115
+ },
3116
+ "branch_diff" /* BranchDiff */
3117
+ );
3118
+ }
3119
+ /**
3120
+ * Copy a node set from `sourceBranch` onto `targetBranch` (branch
3121
+ * promotion): node ids are preserved, so repeated copies update the same
3122
+ * target nodes; all changes land in ONE atomic commit on the target branch.
3123
+ *
3124
+ * Each root's parent path must already exist on the target branch. With
3125
+ * `deleteMissing`, target nodes under the roots that are absent from the
3126
+ * copied source set are pruned (one-way sync).
3127
+ */
3128
+ async copyNodes(sourceBranch, targetBranch, opts) {
3129
+ return this.sendRequest(
3130
+ {
3131
+ source_branch: sourceBranch,
3132
+ target_branch: targetBranch,
3133
+ workspace: opts.workspace,
3134
+ roots: opts.roots,
3135
+ recursive: opts.recursive ?? true,
3136
+ delete_missing: opts.deleteMissing ?? false
3137
+ },
3138
+ "branch_copy_nodes" /* BranchCopyNodes */
3139
+ );
3140
+ }
3141
+ };
3142
+
3143
+ // src/scheduler.ts
3144
+ var SchedulerApi = class {
3145
+ constructor(_context, sendRequest) {
3146
+ this.sendRequest = sendRequest;
3147
+ }
3148
+ /**
3149
+ * Schedule a one-shot invocation of a function or flow at a fixed time.
3150
+ */
3151
+ async schedule(options) {
3152
+ const runAt = options.runAt instanceof Date ? options.runAt.toISOString() : options.runAt;
3153
+ return this.sendRequest(
3154
+ {
3155
+ target_kind: options.targetKind,
3156
+ target_path: options.targetPath,
3157
+ input: options.input ?? {},
3158
+ run_at: runAt,
3159
+ external_key: options.externalKey,
3160
+ branch: options.branch,
3161
+ workspace: options.workspace,
3162
+ max_retries: options.maxRetries
3163
+ },
3164
+ "scheduled_invocation_create" /* ScheduledInvocationCreate */
3165
+ );
3166
+ }
3167
+ /**
3168
+ * Cancel a pending invocation before it fires, by job id or external key.
3169
+ */
3170
+ async cancel(ref) {
3171
+ const payload = typeof ref === "string" ? { job_id: ref } : { job_id: ref.jobId, external_key: ref.externalKey };
3172
+ return this.sendRequest(
3173
+ payload,
3174
+ "scheduled_invocation_cancel" /* ScheduledInvocationCancel */
3175
+ );
3176
+ }
3177
+ /**
3178
+ * List this repository's scheduled invocations.
3179
+ */
3180
+ async list(filter) {
3181
+ const result = await this.sendRequest(
3182
+ {
3183
+ external_key: filter?.externalKey,
3184
+ status: filter?.status
3185
+ },
3186
+ "scheduled_invocation_list" /* ScheduledInvocationList */
3187
+ );
3188
+ return result?.invocations ?? [];
3189
+ }
3190
+ /**
3191
+ * Fetch a single scheduled invocation, by job id or external key.
3192
+ */
3193
+ async get(ref) {
3194
+ const payload = typeof ref === "string" ? { job_id: ref } : { job_id: ref.jobId, external_key: ref.externalKey };
3195
+ return this.sendRequest(
3196
+ payload,
3197
+ "scheduled_invocation_get" /* ScheduledInvocationGet */
3198
+ );
3199
+ }
2889
3200
  };
2890
3201
 
2891
3202
  // src/tags.ts
@@ -3241,6 +3552,7 @@ var SSEClient = class {
3241
3552
  this._lastEventId = options.lastEventId;
3242
3553
  this.autoReconnect = options.reconnect?.enabled ?? true;
3243
3554
  this.externalSignal = options.signal;
3555
+ this.inactivityTimeoutMs = options.inactivityTimeoutMs;
3244
3556
  this.eventTypeSet = options.eventTypes?.length ? new Set(options.eventTypes) : null;
3245
3557
  this.reconnectManager = new ReconnectManager({
3246
3558
  initialDelay: options.reconnect?.initialDelay ?? 1e3,
@@ -3384,6 +3696,23 @@ var SSEClient = class {
3384
3696
  if (err.name === "AbortError" || err instanceof RaisinAbortError) {
3385
3697
  return;
3386
3698
  }
3699
+ if (err instanceof RaisinTimeoutError && err.code === "SSE_INACTIVITY_TIMEOUT") {
3700
+ logger.warn("SSE inactivity timeout:", err.message);
3701
+ if (!this.autoReconnect) {
3702
+ this.setState("disconnected");
3703
+ onError?.(err);
3704
+ throw err;
3705
+ }
3706
+ this.setState("reconnecting");
3707
+ const scheduled2 = await this.waitForReconnect();
3708
+ if (!scheduled2) {
3709
+ logger.warn("SSE max reconnection attempts reached");
3710
+ this.setState("disconnected");
3711
+ onError?.(err);
3712
+ throw err;
3713
+ }
3714
+ continue;
3715
+ }
3387
3716
  logger.error("SSE connection error:", err.message);
3388
3717
  onError?.(err);
3389
3718
  if (!this.autoReconnect) {
@@ -3432,7 +3761,9 @@ var SSEClient = class {
3432
3761
  fetchOptions.headers["Content-Type"] = "application/json";
3433
3762
  fetchOptions.body = JSON.stringify(this.options.body);
3434
3763
  }
3435
- const response = await this.fetchImpl(this.url, fetchOptions);
3764
+ const response = await this.withInactivityTimeout(
3765
+ this.fetchImpl(this.url, fetchOptions)
3766
+ );
3436
3767
  if (!response.ok) {
3437
3768
  const text = await response.text().catch(() => "");
3438
3769
  const msg = `SSE connection failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`;
@@ -3455,7 +3786,7 @@ var SSEClient = class {
3455
3786
  let buffer = "";
3456
3787
  try {
3457
3788
  while (true) {
3458
- const { done, value } = await reader.read();
3789
+ const { done, value } = await this.withInactivityTimeout(reader.read());
3459
3790
  if (done) break;
3460
3791
  buffer += decoder.decode(value, { stream: true });
3461
3792
  const blocks = buffer.split("\n\n");
@@ -3486,9 +3817,48 @@ var SSEClient = class {
3486
3817
  }
3487
3818
  }
3488
3819
  } finally {
3489
- reader.releaseLock();
3820
+ try {
3821
+ reader.releaseLock();
3822
+ } catch {
3823
+ }
3490
3824
  }
3491
3825
  }
3826
+ /**
3827
+ * Race a stream operation against the inactivity timeout.
3828
+ *
3829
+ * If no bytes (or response) arrive within `inactivityTimeoutMs`, the
3830
+ * internal connection is aborted and the operation rejects with a
3831
+ * `RaisinTimeoutError` (code `SSE_INACTIVITY_TIMEOUT`). The timer is
3832
+ * effectively reset on every chunk because each `reader.read()` is
3833
+ * wrapped individually.
3834
+ */
3835
+ withInactivityTimeout(promise) {
3836
+ const timeoutMs = this.inactivityTimeoutMs;
3837
+ if (!timeoutMs || timeoutMs <= 0) return promise;
3838
+ return new Promise((resolve, reject) => {
3839
+ const timer = setTimeout(() => {
3840
+ promise.catch(() => void 0);
3841
+ this.abortController?.abort();
3842
+ reject(
3843
+ new RaisinTimeoutError(
3844
+ `SSE stream inactive for ${timeoutMs}ms`,
3845
+ "SSE_INACTIVITY_TIMEOUT",
3846
+ timeoutMs
3847
+ )
3848
+ );
3849
+ }, timeoutMs);
3850
+ promise.then(
3851
+ (value) => {
3852
+ clearTimeout(timer);
3853
+ resolve(value);
3854
+ },
3855
+ (error) => {
3856
+ clearTimeout(timer);
3857
+ reject(error);
3858
+ }
3859
+ );
3860
+ });
3861
+ }
3492
3862
  /**
3493
3863
  * Wait for the reconnect manager to schedule a reconnection attempt.
3494
3864
  * Returns true if reconnection was scheduled, false if max attempts reached.
@@ -3513,7 +3883,24 @@ function isRecoveredDoneEvent(event) {
3513
3883
  return typeof done.content !== "string" || done.content.trim().length === 0;
3514
3884
  }
3515
3885
 
3886
+ // src/utils/home-path.ts
3887
+ var ACCESS_CONTROL_PREFIX = "/raisin:access_control";
3888
+ function normalizeHomePath(home) {
3889
+ if (home === null || home === void 0) return null;
3890
+ let path = home.trim();
3891
+ if (path.length === 0) return null;
3892
+ if (path === ACCESS_CONTROL_PREFIX) return "/";
3893
+ if (path.startsWith(`${ACCESS_CONTROL_PREFIX}/`)) {
3894
+ path = path.slice(ACCESS_CONTROL_PREFIX.length);
3895
+ }
3896
+ while (path.length > 1 && path.endsWith("/")) {
3897
+ path = path.slice(0, -1);
3898
+ }
3899
+ return path;
3900
+ }
3901
+
3516
3902
  // src/conversations.ts
3903
+ var DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS = 12e4;
3517
3904
  var ConversationManager = class {
3518
3905
  constructor(baseUrl, repository, authManager, options = {}, executeSql) {
3519
3906
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -3535,10 +3922,11 @@ var ConversationManager = class {
3535
3922
  );
3536
3923
  if (!result?.rows?.[0]) throw new Error("User not authenticated");
3537
3924
  const row = result.rows[0];
3538
- this.cachedUserHome = row.home;
3539
- this.cachedUserId = row.user_id || this.cachedUserHome;
3540
- if (!this.cachedUserHome) throw new Error("User home path not found");
3541
- return { userId: this.cachedUserId, userHome: this.cachedUserHome };
3925
+ const userHome = normalizeHomePath(row.home);
3926
+ if (!userHome) throw new Error("User home path not found");
3927
+ this.cachedUserHome = userHome;
3928
+ this.cachedUserId = row.user_id || userHome;
3929
+ return { userId: this.cachedUserId, userHome };
3542
3930
  }
3543
3931
  // ==========================================================================
3544
3932
  // Conversation listing
@@ -3733,13 +4121,15 @@ var ConversationManager = class {
3733
4121
  fetch: this.fetchImpl,
3734
4122
  signal: options?.signal,
3735
4123
  method: "POST",
3736
- body: { channel: streamTarget.channel, path: streamTarget.path }
4124
+ body: { channel: streamTarget.channel, path: streamTarget.path },
4125
+ inactivityTimeoutMs: options?.inactivityTimeoutMs ?? DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS
3737
4126
  });
3738
4127
  const iterator = sse[Symbol.asyncIterator]();
3739
4128
  let pendingFirst = iterator.next();
3740
4129
  try {
3741
4130
  await sse.waitUntilConnected();
3742
4131
  } catch {
4132
+ pendingFirst?.catch(() => void 0);
3743
4133
  pendingFirst = null;
3744
4134
  sse.close();
3745
4135
  logger.debug("SSE connection failed, falling back to create-only");
@@ -3776,6 +4166,15 @@ var ConversationManager = class {
3776
4166
  return;
3777
4167
  }
3778
4168
  }
4169
+ } catch (error) {
4170
+ if (error instanceof RaisinTimeoutError && error.code === "SSE_INACTIVITY_TIMEOUT") {
4171
+ logger.warn(
4172
+ `[ConversationManager] SSE stream inactive for ${error.timeoutMs}ms, ending turn with synthetic waiting event`
4173
+ );
4174
+ yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
4175
+ return;
4176
+ }
4177
+ throw error;
3779
4178
  } finally {
3780
4179
  sse.close();
3781
4180
  }
@@ -4538,31 +4937,29 @@ var FlowClient = class _FlowClient {
4538
4937
  * Respond to a human task within a flow.
4539
4938
  *
4540
4939
  * When a flow is waiting on a human_task step (approval, input, review),
4541
- * use this method to submit the response and resume the flow.
4940
+ * use this method to submit the response. Completion is validated
4941
+ * (caller must be the task's assignee or an admin) and resumes the flow.
4542
4942
  *
4543
- * @param instanceId - Flow instance ID
4544
- * @param taskId - The human task node ID
4545
- * @param response - The human's response data
4943
+ * Note: prefer `InboxApi` for full inbox functionality (listing tasks,
4944
+ * task details); this is a convenience passthrough to the same endpoint.
4945
+ *
4946
+ * @param instanceId - Flow instance ID (unused; kept for API stability)
4947
+ * @param taskId - The inbox task node ID (or path)
4948
+ * @param response - The response data
4546
4949
  *
4547
4950
  * @example
4548
4951
  * ```typescript
4549
4952
  * // Approve a task
4550
- * await flows.respondToHumanTask('instance-123', 'step-5', {
4953
+ * await flows.respondToHumanTask('instance-123', 'task-abc', {
4551
4954
  * action: 'approve',
4552
4955
  * comment: 'Looks good!',
4553
4956
  * });
4554
- *
4555
- * // Provide input for an input task
4556
- * await flows.respondToHumanTask('instance-123', 'step-3', {
4557
- * name: 'John Doe',
4558
- * email: 'john@example.com',
4559
- * });
4560
4957
  * ```
4561
4958
  */
4562
- async respondToHumanTask(instanceId, taskId, response, options) {
4959
+ async respondToHumanTask(_instanceId, taskId, response, options) {
4563
4960
  await this.request({
4564
4961
  method: "POST",
4565
- path: `/api/flows/${this.repository}/instances/${instanceId}/tasks/${taskId}/respond`,
4962
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
4566
4963
  body: { response },
4567
4964
  signal: options?.signal
4568
4965
  });
@@ -4701,66 +5098,194 @@ var FlowClient = class _FlowClient {
4701
5098
  }
4702
5099
  };
4703
5100
 
4704
- // src/database.ts
4705
- var Database = class _Database {
4706
- constructor(repository, context, sendRequest, eventHandler, branch, revision, getUploadManager, getSignAssetUrl, httpOptions) {
5101
+ // src/inbox.ts
5102
+ var InboxApi = class {
5103
+ constructor(baseUrl, repository, authManager, options = {}) {
5104
+ this.baseUrl = baseUrl.replace(/\/$/, "");
4707
5105
  this.repository = repository;
4708
- this.branch = branch;
4709
- this.revision = revision;
4710
- this._context = {
4711
- ...context,
4712
- repository,
4713
- branch: branch || context.branch,
4714
- revision: revision || context.revision
4715
- };
4716
- this.sendRequest = (payload, requestType, contextOverride, requestOptions) => sendRequest(
4717
- payload,
4718
- requestType || "node_create",
4719
- contextOverride || this._context,
4720
- requestOptions
4721
- );
4722
- this.eventHandler = eventHandler;
4723
- this.getUploadManager = getUploadManager;
4724
- this.getSignAssetUrl = getSignAssetUrl;
4725
- this.httpOptions = httpOptions;
5106
+ this.authManager = authManager;
5107
+ this.fetchImpl = options.fetch ?? fetch;
5108
+ this.requestTimeout = options.requestTimeout ?? 3e4;
4726
5109
  }
4727
5110
  /**
4728
- * Get the repository name
5111
+ * List the caller's inbox tasks.
5112
+ *
5113
+ * Tasks are sorted pending-first, then by priority (highest first) and
5114
+ * due time (earliest first).
4729
5115
  */
4730
- getRepository() {
4731
- return this.repository;
5116
+ async listTasks(options = {}) {
5117
+ const params = new URLSearchParams();
5118
+ if (options.status) params.set("status", options.status);
5119
+ if (options.assignee) params.set("assignee", options.assignee);
5120
+ const query = params.size > 0 ? `?${params.toString()}` : "";
5121
+ return this.request({
5122
+ method: "GET",
5123
+ path: `/api/inbox/${this.repository}${query}`,
5124
+ signal: options.signal
5125
+ });
4732
5126
  }
4733
5127
  /**
4734
- * Create a new Database instance scoped to a specific branch.
4735
- *
4736
- * @param branch - Branch name
4737
- * @returns New Database instance with branch context
5128
+ * Get a single inbox task by ID (or path).
4738
5129
  */
4739
- onBranch(branch) {
4740
- return new _Database(
4741
- this.repository,
4742
- this._context,
4743
- this.sendRequest,
4744
- this.eventHandler,
4745
- branch,
4746
- this.revision,
4747
- this.getUploadManager,
4748
- this.getSignAssetUrl,
4749
- this.httpOptions
4750
- );
5130
+ async getTask(taskId, options) {
5131
+ return this.request({
5132
+ method: "GET",
5133
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}`,
5134
+ signal: options?.signal
5135
+ });
4751
5136
  }
4752
5137
  /**
4753
- * Create a new Database instance scoped to a specific revision/commit.
5138
+ * Complete an inbox task.
4754
5139
  *
4755
- * @param revision - Revision/commit ID
4756
- * @returns New Database instance with revision context
5140
+ * The caller must be the task's assignee (or an admin). Completion marks
5141
+ * the task done, records who decided, and resumes the owning flow.
5142
+ *
5143
+ * @param taskId - Task node ID (or path)
5144
+ * @param response - The response payload:
5145
+ * - approval tasks: `{ action: '<option value>', comment?: string }`
5146
+ * - input tasks: the input value(s) matching the task's input_schema
5147
+ * - review/action tasks: any acknowledgement payload
5148
+ *
5149
+ * @example
5150
+ * ```typescript
5151
+ * await inbox.completeTask('task-123', { action: 'approve' });
5152
+ * ```
4757
5153
  */
4758
- atRevision(revision) {
4759
- return new _Database(
4760
- this.repository,
4761
- this._context,
4762
- this.sendRequest,
4763
- this.eventHandler,
5154
+ async completeTask(taskId, response, options) {
5155
+ return this.request({
5156
+ method: "POST",
5157
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
5158
+ body: { response },
5159
+ signal: options?.signal
5160
+ });
5161
+ }
5162
+ // ==========================================================================
5163
+ // Internal HTTP request helper
5164
+ // ==========================================================================
5165
+ async request(options) {
5166
+ const url = `${this.baseUrl}${options.path}`;
5167
+ const headers = {
5168
+ "Content-Type": "application/json"
5169
+ };
5170
+ const token = this.authManager.getAccessToken();
5171
+ if (token) {
5172
+ headers["Authorization"] = `Bearer ${token}`;
5173
+ }
5174
+ const controller = new AbortController();
5175
+ const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
5176
+ if (options.signal) {
5177
+ if (options.signal.aborted) {
5178
+ clearTimeout(timeoutId);
5179
+ throw new RaisinAbortError();
5180
+ }
5181
+ options.signal.addEventListener("abort", () => controller.abort(), {
5182
+ once: true
5183
+ });
5184
+ }
5185
+ try {
5186
+ const response = await this.fetchImpl(url, {
5187
+ method: options.method,
5188
+ headers,
5189
+ body: options.body ? JSON.stringify(options.body) : void 0,
5190
+ signal: controller.signal
5191
+ });
5192
+ clearTimeout(timeoutId);
5193
+ if (!response.ok) {
5194
+ const errorText = await response.text();
5195
+ let errorMessage;
5196
+ try {
5197
+ const errorJson = JSON.parse(errorText);
5198
+ errorMessage = errorJson.message || errorJson.error || errorText;
5199
+ } catch {
5200
+ errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
5201
+ }
5202
+ throw classifyHttpError(response.status, errorMessage);
5203
+ }
5204
+ const contentType = response.headers.get("content-type");
5205
+ if (contentType?.includes("application/json")) {
5206
+ return await response.json();
5207
+ }
5208
+ return void 0;
5209
+ } catch (error) {
5210
+ clearTimeout(timeoutId);
5211
+ if (error instanceof RaisinAbortError || error instanceof RaisinTimeoutError) {
5212
+ throw error;
5213
+ }
5214
+ if (error instanceof Error && error.name === "AbortError") {
5215
+ if (options.signal?.aborted) {
5216
+ throw new RaisinAbortError();
5217
+ }
5218
+ throw new RaisinTimeoutError(
5219
+ `Request timeout after ${this.requestTimeout}ms`,
5220
+ "REQUEST_TIMEOUT",
5221
+ this.requestTimeout
5222
+ );
5223
+ }
5224
+ throw error;
5225
+ }
5226
+ }
5227
+ };
5228
+
5229
+ // src/database.ts
5230
+ var Database = class _Database {
5231
+ constructor(repository, context, sendRequest, eventHandler, branch, revision, getUploadManager, getSignAssetUrl, httpOptions) {
5232
+ this.repository = repository;
5233
+ this.branch = branch;
5234
+ this.revision = revision;
5235
+ this._context = {
5236
+ ...context,
5237
+ repository,
5238
+ branch: branch || context.branch,
5239
+ revision: revision || context.revision
5240
+ };
5241
+ this.sendRequest = (payload, requestType, contextOverride, requestOptions) => sendRequest(
5242
+ payload,
5243
+ requestType || "node_create",
5244
+ contextOverride || this._context,
5245
+ requestOptions
5246
+ );
5247
+ this.eventHandler = eventHandler;
5248
+ this.getUploadManager = getUploadManager;
5249
+ this.getSignAssetUrl = getSignAssetUrl;
5250
+ this.httpOptions = httpOptions;
5251
+ }
5252
+ /**
5253
+ * Get the repository name
5254
+ */
5255
+ getRepository() {
5256
+ return this.repository;
5257
+ }
5258
+ /**
5259
+ * Create a new Database instance scoped to a specific branch.
5260
+ *
5261
+ * @param branch - Branch name
5262
+ * @returns New Database instance with branch context
5263
+ */
5264
+ onBranch(branch) {
5265
+ return new _Database(
5266
+ this.repository,
5267
+ this._context,
5268
+ this.sendRequest,
5269
+ this.eventHandler,
5270
+ branch,
5271
+ this.revision,
5272
+ this.getUploadManager,
5273
+ this.getSignAssetUrl,
5274
+ this.httpOptions
5275
+ );
5276
+ }
5277
+ /**
5278
+ * Create a new Database instance scoped to a specific revision/commit.
5279
+ *
5280
+ * @param revision - Revision/commit ID
5281
+ * @returns New Database instance with revision context
5282
+ */
5283
+ atRevision(revision) {
5284
+ return new _Database(
5285
+ this.repository,
5286
+ this._context,
5287
+ this.sendRequest,
5288
+ this.eventHandler,
4764
5289
  this.branch,
4765
5290
  revision,
4766
5291
  this.getUploadManager,
@@ -4890,6 +5415,13 @@ var Database = class _Database {
4890
5415
  tags() {
4891
5416
  return new Tags(this._context, this.sendRequest);
4892
5417
  }
5418
+ /**
5419
+ * Get SchedulerApi for one-shot scheduled invocations (time-based
5420
+ * function/flow runs).
5421
+ */
5422
+ scheduler() {
5423
+ return new SchedulerApi(this._context, this.sendRequest);
5424
+ }
4893
5425
  /**
4894
5426
  * Get FlowsApi for running flows via WebSocket
4895
5427
  */
@@ -4977,8 +5509,44 @@ var Database = class _Database {
4977
5509
  }
4978
5510
  return this._flowClient;
4979
5511
  }
5512
+ /**
5513
+ * Get a pre-configured InboxApi for human-in-the-loop tasks.
5514
+ *
5515
+ * Requires the Database to have been created via `RaisinClient.database()`
5516
+ * (which automatically provides HTTP context).
5517
+ *
5518
+ * @example
5519
+ * ```typescript
5520
+ * const db = client.database('my-repo');
5521
+ * const { tasks } = await db.inbox.listTasks({ status: 'pending' });
5522
+ * await db.inbox.completeTask(tasks[0].id, { action: 'approve' });
5523
+ * ```
5524
+ */
5525
+ get inbox() {
5526
+ if (!this._inboxApi) {
5527
+ if (!this.httpOptions?.httpBaseUrl || !this.httpOptions?.authManager) {
5528
+ throw new Error(
5529
+ "db.inbox requires HTTP context. Use client.database() to get a Database with inbox support."
5530
+ );
5531
+ }
5532
+ this._inboxApi = new InboxApi(
5533
+ this.httpOptions.httpBaseUrl,
5534
+ this.repository,
5535
+ this.httpOptions.authManager
5536
+ );
5537
+ }
5538
+ return this._inboxApi;
5539
+ }
4980
5540
  /**
4981
5541
  * Get EventHandler for subscribing to real-time events
5542
+ *
5543
+ * Path filter semantics for subscriptions (matched server-side):
5544
+ * - A plain path matches **only that exact node** — there is no implicit
5545
+ * prefix matching.
5546
+ * - `*` matches exactly one path segment (`/inbox/*` matches `/inbox/a`,
5547
+ * not `/inbox/a/b`).
5548
+ * - `**` matches recursively (`/inbox/**` matches all descendants of
5549
+ * `/inbox`, but not `/inbox` itself).
4982
5550
  */
4983
5551
  events() {
4984
5552
  return this.eventHandler;
@@ -6257,10 +6825,194 @@ var UploadManager = class {
6257
6825
  }
6258
6826
  };
6259
6827
 
6828
+ // src/http-schema.ts
6829
+ var HttpSchemaBase = class {
6830
+ constructor(repository, branch, client) {
6831
+ this.repository = repository;
6832
+ this.branch = branch;
6833
+ this.client = client;
6834
+ }
6835
+ /** Base management URL for this schema kind. */
6836
+ base() {
6837
+ return `/api/management/${this.repository}/${this.branch}/${this.kind}`;
6838
+ }
6839
+ /** List all schemas (or only published ones). */
6840
+ async list(publishedOnly = false) {
6841
+ const path = publishedOnly ? `${this.base()}/published` : this.base();
6842
+ const result = await this.client.managementRequest("GET", path);
6843
+ return Array.isArray(result) ? result : [];
6844
+ }
6845
+ /** Get a single schema by name. */
6846
+ async get(name) {
6847
+ return this.client.managementRequest("GET", `${this.base()}/${encodeURIComponent(name)}`);
6848
+ }
6849
+ /** Get the schema with full inheritance (`extends`) applied. */
6850
+ async getResolved(name) {
6851
+ return this.client.managementRequest(
6852
+ "GET",
6853
+ `${this.base()}/${encodeURIComponent(name)}/resolved`
6854
+ );
6855
+ }
6856
+ /** Create a schema. `name` is folded into the definition body if absent. */
6857
+ async create(name, definition, commit) {
6858
+ return this.client.managementRequest("POST", this.base(), {
6859
+ [this.defField]: { name, ...definition },
6860
+ commit
6861
+ });
6862
+ }
6863
+ /** Update an existing schema. */
6864
+ async update(name, definition, commit) {
6865
+ return this.client.managementRequest(
6866
+ "PUT",
6867
+ `${this.base()}/${encodeURIComponent(name)}`,
6868
+ { [this.defField]: { name, ...definition }, commit }
6869
+ );
6870
+ }
6871
+ /** Delete a schema. */
6872
+ async delete(name, commit) {
6873
+ return this.client.managementRequest(
6874
+ "DELETE",
6875
+ `${this.base()}/${encodeURIComponent(name)}`,
6876
+ commit
6877
+ );
6878
+ }
6879
+ /** Publish a schema. */
6880
+ async publish(name, commit) {
6881
+ return this.client.managementRequest(
6882
+ "POST",
6883
+ `${this.base()}/${encodeURIComponent(name)}/publish`,
6884
+ commit
6885
+ );
6886
+ }
6887
+ /** Unpublish a schema. */
6888
+ async unpublish(name, commit) {
6889
+ return this.client.managementRequest(
6890
+ "POST",
6891
+ `${this.base()}/${encodeURIComponent(name)}/unpublish`,
6892
+ commit
6893
+ );
6894
+ }
6895
+ };
6896
+ var HttpNodeTypes = class extends HttpSchemaBase {
6897
+ constructor() {
6898
+ super(...arguments);
6899
+ this.kind = "nodetypes";
6900
+ this.defField = "node_type";
6901
+ }
6902
+ /**
6903
+ * Get the resolved NodeType. Unlike archetypes/element types, NodeType
6904
+ * resolution can be workspace-aware (honors workspace NodeType pins).
6905
+ */
6906
+ async getResolved(name, opts) {
6907
+ const query = opts?.workspace ? `?workspace=${encodeURIComponent(opts.workspace)}` : "";
6908
+ return this.client.managementRequest(
6909
+ "GET",
6910
+ `${this.base()}/${encodeURIComponent(name)}/resolved${query}`
6911
+ );
6912
+ }
6913
+ };
6914
+ var HttpArchetypes = class extends HttpSchemaBase {
6915
+ constructor() {
6916
+ super(...arguments);
6917
+ this.kind = "archetypes";
6918
+ this.defField = "archetype";
6919
+ }
6920
+ };
6921
+ var HttpElementTypes = class extends HttpSchemaBase {
6922
+ constructor() {
6923
+ super(...arguments);
6924
+ this.kind = "elementtypes";
6925
+ this.defField = "element_type";
6926
+ }
6927
+ };
6928
+
6929
+ // src/mcp.ts
6930
+ var McpClient = class {
6931
+ constructor(transport) {
6932
+ this.transport = transport;
6933
+ }
6934
+ /**
6935
+ * List the tools this MCP server advertises to the calling identity.
6936
+ *
6937
+ * Wraps the `tools/list` JSON-RPC method. The visible set is RLS-scoped to
6938
+ * the authenticated caller (or the anonymous role).
6939
+ *
6940
+ * @returns The advertised tool descriptors.
6941
+ */
6942
+ async listTools() {
6943
+ const result = await this.transport.rpc("tools/list");
6944
+ return { tools: result?.tools ?? [] };
6945
+ }
6946
+ /**
6947
+ * Invoke a tool by name.
6948
+ *
6949
+ * Wraps the `tools/call` JSON-RPC method. The underlying `raisin:Function`
6950
+ * runs RLS-scoped to the calling identity. A domain-level tool failure is
6951
+ * reported via {@link McpCallToolResult.isError} (not thrown); transport and
6952
+ * protocol errors are thrown.
6953
+ *
6954
+ * @param name - Tool name from {@link listTools}.
6955
+ * @param args - Arguments object matching the tool's `inputSchema`.
6956
+ * @returns The tool's content blocks and optional structured content.
6957
+ */
6958
+ async callTool(name, args) {
6959
+ const result = await this.transport.rpc("tools/call", {
6960
+ name,
6961
+ arguments: args ?? {}
6962
+ });
6963
+ return result;
6964
+ }
6965
+ /**
6966
+ * Read a resource by its `raisin://` URI.
6967
+ *
6968
+ * Wraps the `resources/read` JSON-RPC method. JSON-only resources return
6969
+ * their properties as a `text` entry; byte-serving resources return base64
6970
+ * in `blob`.
6971
+ *
6972
+ * @param uri - `raisin://{workspace}/{path}` URI to read.
6973
+ * @returns One content entry per URI read.
6974
+ */
6975
+ async readResource(uri) {
6976
+ const result = await this.transport.rpc("resources/read", {
6977
+ uri
6978
+ });
6979
+ return { contents: result?.contents ?? [] };
6980
+ }
6981
+ /**
6982
+ * Subscribe to change notifications for a resource URI (or URI prefix).
6983
+ *
6984
+ * Wraps the `resources/subscribe` JSON-RPC method, which upgrades to an SSE
6985
+ * stream. The returned {@link McpResourceSubscription} is an async iterable of
6986
+ * {@link McpResourceUpdate}; the server's initial subscription ack is consumed
6987
+ * internally and not yielded. Call `close()` to end the stream.
6988
+ *
6989
+ * @param uri - `raisin://` URI (or prefix) to watch.
6990
+ * @returns A live, closeable subscription.
6991
+ */
6992
+ subscribeResource(uri) {
6993
+ const stream = this.transport.subscribe("resources/subscribe", { uri });
6994
+ return {
6995
+ close: () => stream.close(),
6996
+ async *[Symbol.asyncIterator]() {
6997
+ for await (const frame of stream) {
6998
+ if (frame.method === "notifications/resources/updated") {
6999
+ const params = frame.params ?? {};
7000
+ if (typeof params.uri === "string") {
7001
+ yield { uri: params.uri };
7002
+ }
7003
+ }
7004
+ }
7005
+ }
7006
+ };
7007
+ }
7008
+ };
7009
+
6260
7010
  // src/http-client.ts
6261
7011
  var RaisinHttpClient = class extends import_events3.EventEmitter {
6262
7012
  constructor(baseUrl, options = {}) {
6263
7013
  super();
7014
+ /** Monotonic JSON-RPC id counter for MCP requests. */
7015
+ this._mcpRequestId = 0;
6264
7016
  this._currentUser = null;
6265
7017
  this._uploadManager = null;
6266
7018
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -6306,9 +7058,13 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
6306
7058
  body: payload,
6307
7059
  skipAuth: true
6308
7060
  });
6309
- this.authManager.setTokens(response.data);
7061
+ if (response.data.token) {
7062
+ this.authManager.storage.setAccessToken(response.data.token);
7063
+ } else {
7064
+ this.authManager.setTokens(response.data);
7065
+ }
6310
7066
  this._currentUser = {
6311
- userId: "admin",
7067
+ userId: response.data.user_id ?? "admin",
6312
7068
  anonymous: false
6313
7069
  };
6314
7070
  this.emit("authenticated", response.data);
@@ -6952,6 +7708,107 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
6952
7708
  });
6953
7709
  return response.data;
6954
7710
  }
7711
+ /**
7712
+ * Send one JSON-RPC request to an MCP server endpoint and return its
7713
+ * unwrapped `result`.
7714
+ *
7715
+ * Calls `POST /mcp/{repo}/{branch}/{slug}` carrying a single JSON-RPC 2.0
7716
+ * message. MCP errors are returned in the JSON-RPC envelope (HTTP `200`), so
7717
+ * this method inspects the body's `error` member and throws on failure.
7718
+ *
7719
+ * @internal Used by {@link McpClient} via the transport built in `mcp()`.
7720
+ */
7721
+ async mcpRpc(repository, branch, slug, method, params) {
7722
+ const id = ++this._mcpRequestId;
7723
+ const response = await this.request({
7724
+ method: "POST",
7725
+ path: `/mcp/${repository}/${branch}/${slug}`,
7726
+ body: {
7727
+ jsonrpc: "2.0",
7728
+ id,
7729
+ method,
7730
+ ...params !== void 0 ? { params } : {}
7731
+ }
7732
+ });
7733
+ const frame = response.data;
7734
+ if (frame?.error) {
7735
+ const err = new Error(frame.error.message || `MCP error ${frame.error.code}`);
7736
+ err.code = frame.error.code;
7737
+ err.data = frame.error.data;
7738
+ throw err;
7739
+ }
7740
+ return frame?.result;
7741
+ }
7742
+ /**
7743
+ * Open the `resources/subscribe` SSE stream for an MCP server endpoint.
7744
+ *
7745
+ * Streams JSON-RPC frames (the subscription ack followed by
7746
+ * `notifications/resources/updated` frames) from
7747
+ * `POST /mcp/{repo}/{branch}/{slug}`.
7748
+ *
7749
+ * @internal Used by {@link McpClient} via the transport built in `mcp()`.
7750
+ */
7751
+ mcpSubscribe(repository, branch, slug, method, params) {
7752
+ const headers = {};
7753
+ if (this.authManager.isAuthenticated()) {
7754
+ const token = this.authManager.getAccessToken();
7755
+ if (token) {
7756
+ headers["Authorization"] = `Bearer ${token}`;
7757
+ }
7758
+ }
7759
+ const sse = new SSEClient(
7760
+ `${this.baseUrl}/mcp/${repository}/${branch}/${slug}`,
7761
+ {
7762
+ method: "POST",
7763
+ body: {
7764
+ jsonrpc: "2.0",
7765
+ id: ++this._mcpRequestId,
7766
+ method,
7767
+ params
7768
+ },
7769
+ headers,
7770
+ fetch: this.fetchImpl,
7771
+ reconnect: { enabled: false }
7772
+ }
7773
+ );
7774
+ return {
7775
+ close: () => sse.close(),
7776
+ async *[Symbol.asyncIterator]() {
7777
+ for await (const event of sse) {
7778
+ yield event.data;
7779
+ }
7780
+ }
7781
+ };
7782
+ }
7783
+ /**
7784
+ * Get an {@link McpClient} for a content-declared MCP server.
7785
+ *
7786
+ * @param slug - The `raisin:McpServer` slug (resolved in the `mcp` workspace).
7787
+ * @param branch - Branch to address; defaults to the client's current branch.
7788
+ */
7789
+ mcp(slug, branch) {
7790
+ const repository = this._context.repository;
7791
+ if (!repository) {
7792
+ throw new Error("mcp() requires a repository; call database(name) first");
7793
+ }
7794
+ const effectiveBranch = branch ?? this.getBranch();
7795
+ const transport = {
7796
+ rpc: (m, p) => this.mcpRpc(repository, effectiveBranch, slug, m, p),
7797
+ subscribe: (m, p) => this.mcpSubscribe(repository, effectiveBranch, slug, m, p)
7798
+ };
7799
+ return new McpClient(transport);
7800
+ }
7801
+ /**
7802
+ * Make a management REST request and return the parsed body.
7803
+ *
7804
+ * @internal Used by the HTTP schema-management helpers
7805
+ * ({@link HttpNodeTypes}/{@link HttpArchetypes}/{@link HttpElementTypes}) to
7806
+ * reuse the client's centralized auth, timeout, and base-URL handling.
7807
+ */
7808
+ async managementRequest(method, path, body) {
7809
+ const response = await this.request({ method, path, body });
7810
+ return response.data;
7811
+ }
6955
7812
  /**
6956
7813
  * Get a simplified database interface for SSR
6957
7814
  * This returns an HTTP-based database client
@@ -7039,6 +7896,36 @@ var HttpDatabase = class _HttpDatabase {
7039
7896
  (repo, name, input) => this.client.invokeFunctionSync(repo, name, input)
7040
7897
  );
7041
7898
  }
7899
+ /**
7900
+ * Get an {@link McpClient} for a content-declared MCP server on this database.
7901
+ *
7902
+ * @param slug - The `raisin:McpServer` slug (resolved in the `mcp` workspace).
7903
+ * @returns A client scoped to `{repo}/{branch}/{slug}`, using this database's
7904
+ * branch (falling back to the HTTP client's current branch).
7905
+ */
7906
+ mcp(slug) {
7907
+ const transport = {
7908
+ rpc: (m, p) => this.client.mcpRpc(this.repository, this.schemaBranch(), slug, m, p),
7909
+ subscribe: (m, p) => this.client.mcpSubscribe(this.repository, this.schemaBranch(), slug, m, p)
7910
+ };
7911
+ return new McpClient(transport);
7912
+ }
7913
+ /** Effective branch for management calls. */
7914
+ schemaBranch() {
7915
+ return this.branch ?? this.client.getBranch();
7916
+ }
7917
+ /** NodeType management (get/list/getResolved/create/update/delete/publish/unpublish). */
7918
+ nodeTypes() {
7919
+ return new HttpNodeTypes(this.repository, this.schemaBranch(), this.client);
7920
+ }
7921
+ /** Archetype management (get/list/getResolved/create/update/delete/publish/unpublish). */
7922
+ archetypes() {
7923
+ return new HttpArchetypes(this.repository, this.schemaBranch(), this.client);
7924
+ }
7925
+ /** ElementType management (get/list/getResolved/create/update/delete/publish/unpublish). */
7926
+ elementTypes() {
7927
+ return new HttpElementTypes(this.repository, this.schemaBranch(), this.client);
7928
+ }
7042
7929
  };
7043
7930
  var HttpWorkspaceClient = class {
7044
7931
  constructor(repository, workspace, client) {
@@ -7196,10 +8083,13 @@ var HttpWorkspaceClient = class {
7196
8083
  };
7197
8084
 
7198
8085
  // src/client.ts
7199
- var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8086
+ var _refreshingUser;
8087
+ var _RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7200
8088
  constructor(url, options = {}) {
7201
8089
  super();
7202
8090
  this._currentUser = null;
8091
+ /** In-flight role re-resolve, used to coalesce concurrent refreshCurrentUser() calls. */
8092
+ __privateAdd(this, _refreshingUser, null);
7203
8093
  // Auth state change listeners (Firebase/Supabase pattern)
7204
8094
  this._authListeners = /* @__PURE__ */ new Set();
7205
8095
  this._userChangeListeners = /* @__PURE__ */ new Set();
@@ -7210,23 +8100,43 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7210
8100
  // Reconnection tracking
7211
8101
  this._previousConnectionState = "disconnected" /* Disconnected */;
7212
8102
  this._reconnectedListeners = /* @__PURE__ */ new Set();
8103
+ this._everConnected = false;
8104
+ // Requests queued while the connection is re-establishing.
8105
+ // Flushed after reconnect + re-auth; bounded by MAX_QUEUED_REQUESTS.
8106
+ this._queuedRequests = [];
8107
+ /**
8108
+ * Backoff delays (ms) between subscription-restore retries after reconnect.
8109
+ * @internal - overridable in tests
8110
+ */
8111
+ this._restoreRetryDelaysMs = [1e3, 3e3, 9e3];
7213
8112
  // Upload manager (uses HTTP under the hood)
7214
8113
  this._uploadManager = null;
8114
+ /** In-flight refresh shared by concurrent refreshToken() callers. */
8115
+ this._refreshInFlight = null;
7215
8116
  if (options.logLevel !== void 0) {
7216
8117
  setLogLevel(options.logLevel);
7217
8118
  }
8119
+ const connectionUrl = this.buildConnectionUrl(url, options.repository);
7218
8120
  this.options = {
7219
8121
  requestTimeout: options.requestTimeout ?? 3e4,
7220
- tenantId: options.tenantId ?? this.extractTenantFromUrl(url),
8122
+ tenantId: options.tenantId ?? this.extractTenantFromUrl(connectionUrl),
7221
8123
  defaultBranch: options.defaultBranch ?? "main"
7222
8124
  };
7223
- this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(url);
7224
- this._repository = this.extractRepositoryFromUrl(url);
8125
+ this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(connectionUrl);
8126
+ this._repository = options.repository ?? this.extractRepositoryFromUrl(connectionUrl);
7225
8127
  this._context = {
7226
8128
  tenant_id: this.options.tenantId,
7227
8129
  branch: this.options.defaultBranch
7228
8130
  };
7229
- this.connection = new Connection(url, options.connection);
8131
+ const explicitTenant = options.tenantId ?? (this.extractTenantFromUrl(url) !== "default" ? this.options.tenantId : void 0);
8132
+ const connectionOptions = {
8133
+ ...options.connection,
8134
+ headers: {
8135
+ ...explicitTenant ? { "x-tenant-id": explicitTenant } : {},
8136
+ ...options.connection?.headers
8137
+ }
8138
+ };
8139
+ this.connection = this.createConnection(connectionUrl, connectionOptions);
7230
8140
  this.requestTracker = new RequestTracker({
7231
8141
  defaultTimeout: this.options.requestTimeout
7232
8142
  });
@@ -7236,6 +8146,14 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7236
8146
  );
7237
8147
  this.setupConnectionHandlers();
7238
8148
  }
8149
+ /**
8150
+ * Create the underlying WebSocket connection.
8151
+ * Seam for tests to inject a fake connection.
8152
+ * @internal
8153
+ */
8154
+ createConnection(url, options) {
8155
+ return new Connection(url, options);
8156
+ }
7239
8157
  /**
7240
8158
  * Derive HTTP URL from WebSocket URL
7241
8159
  * ws://host:port/path -> http://host:port
@@ -7251,15 +8169,45 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7251
8169
  return "http://localhost:8081";
7252
8170
  }
7253
8171
  }
8172
+ /**
8173
+ * Build the WebSocket connection URL.
8174
+ *
8175
+ * When a repository option is provided and the URL carries no path
8176
+ * (e.g. `ws://host:8081`), build the tenant-less `/ws/{repository}`
8177
+ * route internally. URLs that already carry a path are used as-is.
8178
+ */
8179
+ buildConnectionUrl(url, repository) {
8180
+ if (!repository) {
8181
+ return url;
8182
+ }
8183
+ try {
8184
+ const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
8185
+ const parsed = new URL(wsUrl);
8186
+ const hasPath = parsed.pathname.split("/").some((p) => p.length > 0);
8187
+ if (hasPath) {
8188
+ return url;
8189
+ }
8190
+ return `${url.replace(/\/+$/, "")}/ws/${repository}`;
8191
+ } catch {
8192
+ return url;
8193
+ }
8194
+ }
7254
8195
  /**
7255
8196
  * Extract repository from URL
7256
- * URL format: raisin://host:port/tenant/repository or ws://host:port/tenant/repository
8197
+ * URL forms: /ws/{repo} (tenant-less, preferred), /sys/{tenant}/{repo}
8198
+ * (operator/multi-tenant), /{tenant}/{repo} (legacy).
7257
8199
  */
7258
8200
  extractRepositoryFromUrl(url) {
7259
8201
  try {
7260
8202
  const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
7261
8203
  const parsed = new URL(wsUrl);
7262
8204
  const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
8205
+ if (parts[0] === "ws") {
8206
+ return parts.length > 1 ? parts[1] : "";
8207
+ }
8208
+ if (parts[0] === "sys") {
8209
+ return parts.length > 2 ? parts[2] : "";
8210
+ }
7263
8211
  return parts.length > 1 ? parts[1] : "";
7264
8212
  } catch {
7265
8213
  return "";
@@ -7267,14 +8215,24 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7267
8215
  }
7268
8216
  /**
7269
8217
  * Extract tenant ID from URL
7270
- * URL format: raisin://host:port/tenant/repository or ws://host:port/tenant/repository
8218
+ * URL forms: /ws/{repo} (tenant-less — no tenant in URL),
8219
+ * /sys/{tenant}/{repo} (canonical), /{tenant}/{repo} (legacy).
8220
+ * When the URL doesn't carry a tenant, fall back to 'default' — the
8221
+ * server's tenant middleware resolves it anyway (production derives the
8222
+ * tenant from the request itself), so an explicit tenantId is optional.
7271
8223
  */
7272
8224
  extractTenantFromUrl(url) {
7273
8225
  try {
7274
8226
  const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
7275
8227
  const parsed = new URL(wsUrl);
7276
8228
  const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
7277
- return parts.length > 0 ? parts[0] : "default";
8229
+ if (parts[0] === "ws") {
8230
+ return "default";
8231
+ }
8232
+ if (parts[0] === "sys") {
8233
+ return parts.length > 1 ? parts[1] : "default";
8234
+ }
8235
+ return parts.length >= 2 ? parts[0] : "default";
7278
8236
  } catch (error) {
7279
8237
  return "default";
7280
8238
  }
@@ -7288,7 +8246,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7288
8246
  });
7289
8247
  this.connection.on("stateChange", (state) => {
7290
8248
  const wasDisconnected = this._previousConnectionState === "disconnected" /* Disconnected */ || this._previousConnectionState === "reconnecting" /* Reconnecting */;
8249
+ if (state === "connected" /* Connected */) {
8250
+ this._everConnected = true;
8251
+ }
7291
8252
  if (state === "disconnected" /* Disconnected */ || state === "closed" /* Closed */) {
8253
+ this._queuedRequests = [];
7292
8254
  this.requestTracker.cancelAll();
7293
8255
  }
7294
8256
  if (state === "connected" /* Connected */ && wasDisconnected && this._previousConnectionState !== "disconnected" /* Disconnected */) {
@@ -7319,8 +8281,13 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7319
8281
  if (isConnectedMessage(message)) {
7320
8282
  this.handleConnectedMessage(message);
7321
8283
  } else if (isEventMessage(message)) {
7322
- logger.debug(`Routing to event handler`);
7323
- this.eventHandler.handleEvent(message);
8284
+ if (message.event_type === "permissions:changed") {
8285
+ logger.info("[permissions:changed] server signaled a role/permission change; re-resolving");
8286
+ void this.refreshCurrentUser();
8287
+ } else {
8288
+ logger.debug(`Routing to event handler`);
8289
+ this.eventHandler.handleEvent(message);
8290
+ }
7324
8291
  } else if (isResponseEnvelope(message)) {
7325
8292
  logger.debug(`Routing to response handler - request_id: ${message.request_id}, status: ${message.status}`);
7326
8293
  this.handleResponse(message);
@@ -7353,19 +8320,36 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7353
8320
  };
7354
8321
  logger.debug(`Anonymous user info stored: ${message.user_id}`);
7355
8322
  this._updateReadyState();
8323
+ this._flushQueuedRequests();
7356
8324
  if (hasActiveSubscriptions) {
7357
8325
  this._restoreSubscriptionsAndNotify();
7358
8326
  }
7359
8327
  } else {
7360
8328
  logger.info(`[handleConnectedMessage] Stored token found, auto-authenticating...`);
7361
- this.autoReauthenticate().then(async () => {
8329
+ this.autoReauthenticate().then(async (authenticated) => {
8330
+ if (authenticated) {
8331
+ this._flushQueuedRequests();
8332
+ } else {
8333
+ this._rejectQueuedRequests(
8334
+ new RaisinAuthError(
8335
+ "Re-authentication failed after reconnect",
8336
+ "AUTH_TOKEN_EXPIRED",
8337
+ 401
8338
+ )
8339
+ );
8340
+ }
7362
8341
  if (hasActiveSubscriptions) {
7363
8342
  await this._restoreSubscriptionsAndNotify();
7364
8343
  }
7365
8344
  }).catch((err) => {
7366
8345
  logger.error(`[handleConnectedMessage] Auto-authenticate failed:`, err);
8346
+ this._rejectQueuedRequests(
8347
+ err instanceof Error ? err : new Error("Re-authentication failed after reconnect")
8348
+ );
7367
8349
  });
7368
8350
  }
8351
+ } else {
8352
+ this._flushQueuedRequests();
7369
8353
  }
7370
8354
  this.emit("connected", {
7371
8355
  connectionId: message.connection_id,
@@ -7374,18 +8358,41 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7374
8358
  });
7375
8359
  }
7376
8360
  /**
7377
- * Restore subscriptions after reconnection and notify listeners
8361
+ * Restore subscriptions after reconnection and notify listeners.
8362
+ *
8363
+ * Retries with backoff (see `_restoreRetryDelaysMs`); on final failure a
8364
+ * `subscription_restore_failed` event is emitted so applications can
8365
+ * resync their state (e.g. reload lists and re-subscribe manually).
7378
8366
  * @internal
7379
8367
  */
7380
8368
  async _restoreSubscriptionsAndNotify() {
7381
- try {
7382
- logger.info("[_restoreSubscriptionsAndNotify] Restoring subscriptions...");
7383
- await this.eventHandler.restoreSubscriptions();
7384
- logger.info("[_restoreSubscriptionsAndNotify] Subscriptions restored successfully");
7385
- this._emitReconnected();
7386
- } catch (error) {
7387
- logger.error("[_restoreSubscriptionsAndNotify] Failed to restore subscriptions:", error);
8369
+ const delays = this._restoreRetryDelaysMs;
8370
+ let lastError;
8371
+ for (let attempt = 0; attempt <= delays.length; attempt++) {
8372
+ try {
8373
+ logger.info(
8374
+ `[_restoreSubscriptionsAndNotify] Restoring subscriptions (attempt ${attempt + 1}/${delays.length + 1})...`
8375
+ );
8376
+ await this.eventHandler.restoreSubscriptions({ retry: attempt > 0 });
8377
+ logger.info("[_restoreSubscriptionsAndNotify] Subscriptions restored successfully");
8378
+ this._emitReconnected();
8379
+ return;
8380
+ } catch (error) {
8381
+ lastError = error;
8382
+ logger.error(
8383
+ `[_restoreSubscriptionsAndNotify] Restore attempt ${attempt + 1} failed:`,
8384
+ error
8385
+ );
8386
+ if (attempt < delays.length) {
8387
+ await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
8388
+ }
8389
+ }
7388
8390
  }
8391
+ logger.error("[_restoreSubscriptionsAndNotify] Giving up after retries; events may be stale");
8392
+ this.emit(
8393
+ "subscription_restore_failed",
8394
+ lastError instanceof Error ? lastError : new Error(String(lastError))
8395
+ );
7389
8396
  }
7390
8397
  /**
7391
8398
  * Emit reconnected event to all listeners
@@ -7404,13 +8411,15 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7404
8411
  }
7405
8412
  /**
7406
8413
  * Auto-reauthenticate with stored JWT on reconnection
8414
+ *
8415
+ * @returns true if the session was re-authenticated, false otherwise
7407
8416
  * @internal
7408
8417
  */
7409
8418
  async autoReauthenticate() {
7410
8419
  const token = this.authManager.storage.getAccessToken();
7411
8420
  if (!token) {
7412
8421
  logger.warn("[autoReauthenticate] No stored token");
7413
- return;
8422
+ return false;
7414
8423
  }
7415
8424
  const payload = AuthManager.parseToken(token);
7416
8425
  if (payload?.exp && typeof payload.exp === "number") {
@@ -7422,18 +8431,20 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7422
8431
  this.authManager.clear();
7423
8432
  this._currentUser = null;
7424
8433
  this._emitAuthEvent("SESSION_EXPIRED");
7425
- return;
8434
+ return false;
7426
8435
  }
7427
8436
  const newToken = this.authManager.storage.getAccessToken();
7428
8437
  if (newToken) {
7429
8438
  await this.authenticate({ type: "jwt", token: newToken });
8439
+ return true;
7430
8440
  }
7431
- return;
8441
+ return false;
7432
8442
  }
7433
8443
  }
7434
8444
  try {
7435
8445
  await this.authenticate({ type: "jwt", token });
7436
8446
  logger.info("[autoReauthenticate] Successfully re-authenticated");
8447
+ return true;
7437
8448
  } catch (err) {
7438
8449
  logger.error("[autoReauthenticate] Failed to authenticate:", err);
7439
8450
  const refreshed = await this.refreshToken();
@@ -7441,11 +8452,14 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7441
8452
  const newToken = this.authManager.storage.getAccessToken();
7442
8453
  if (newToken) {
7443
8454
  await this.authenticate({ type: "jwt", token: newToken });
8455
+ return true;
7444
8456
  }
8457
+ return false;
7445
8458
  } else {
7446
8459
  this.authManager.clear();
7447
8460
  this._currentUser = null;
7448
8461
  this._emitAuthEvent("SESSION_EXPIRED");
8462
+ return false;
7449
8463
  }
7450
8464
  }
7451
8465
  }
@@ -7561,18 +8575,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7561
8575
  }
7562
8576
  const url = `${this._httpBaseUrl}/auth/${repository}/login`;
7563
8577
  logger.debug("[loginWithEmail] POST", url);
7564
- const response = await fetch(url, {
8578
+ const response = await this._fetchWithTimeout(url, {
7565
8579
  method: "POST",
7566
8580
  headers: { "Content-Type": "application/json" },
7567
8581
  body: JSON.stringify({ email, password })
7568
8582
  });
7569
8583
  if (!response.ok) {
7570
8584
  const error = await response.json().catch(() => ({ message: response.statusText }));
7571
- const authError = {
7572
- code: error.code || "LOGIN_FAILED",
7573
- message: error.message || "Login failed"
7574
- };
7575
- throw authError;
8585
+ throw new RaisinAuthError(
8586
+ error.message || "Login failed",
8587
+ error.code || "LOGIN_FAILED",
8588
+ response.status
8589
+ );
7576
8590
  }
7577
8591
  const tokens = await response.json();
7578
8592
  logger.info("[loginWithEmail] HTTP login successful, user_id:", tokens.identity.id);
@@ -7601,11 +8615,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7601
8615
  }
7602
8616
  this._currentUser = {
7603
8617
  userId: user.id,
7604
- roles: [],
8618
+ roles: this._currentUser?.roles ?? [],
7605
8619
  anonymous: false,
7606
8620
  node: userNode ?? void 0
7607
8621
  };
7608
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
8622
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7609
8623
  if (expiresInMs > 0) {
7610
8624
  this.scheduleAutoRefresh(expiresInMs);
7611
8625
  }
@@ -7629,18 +8643,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7629
8643
  async registerWithEmail(email, password, repository, displayName) {
7630
8644
  logger.info("[registerWithEmail] Starting registration...");
7631
8645
  const url = `${this._httpBaseUrl}/auth/${repository}/register`;
7632
- const response = await fetch(url, {
8646
+ const response = await this._fetchWithTimeout(url, {
7633
8647
  method: "POST",
7634
8648
  headers: { "Content-Type": "application/json" },
7635
8649
  body: JSON.stringify({ email, password, display_name: displayName })
7636
8650
  });
7637
8651
  if (!response.ok) {
7638
8652
  const error = await response.json().catch(() => ({ message: response.statusText }));
7639
- const authError = {
7640
- code: error.code || "REGISTRATION_FAILED",
7641
- message: error.message || "Registration failed"
7642
- };
7643
- throw authError;
8653
+ throw new RaisinAuthError(
8654
+ error.message || "Registration failed",
8655
+ error.code || "REGISTRATION_FAILED",
8656
+ response.status
8657
+ );
7644
8658
  }
7645
8659
  const tokens = await response.json();
7646
8660
  logger.info("[registerWithEmail] Registration successful, user_id:", tokens.identity.id);
@@ -7667,11 +8681,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7667
8681
  }
7668
8682
  this._currentUser = {
7669
8683
  userId: user.id,
7670
- roles: [],
8684
+ roles: this._currentUser?.roles ?? [],
7671
8685
  anonymous: false,
7672
8686
  node: userNode ?? void 0
7673
8687
  };
7674
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
8688
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7675
8689
  if (expiresInMs > 0) {
7676
8690
  this.scheduleAutoRefresh(expiresInMs);
7677
8691
  }
@@ -7763,39 +8777,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7763
8777
  if (payload && payload.exp && typeof payload.exp === "number") {
7764
8778
  if (payload.exp * 1e3 < Date.now()) {
7765
8779
  logger.info("[initSession] Token expired, attempting refresh...");
7766
- const refreshToken = this.authManager.storage.getRefreshToken();
7767
- if (refreshToken) {
7768
- try {
7769
- const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
7770
- const response = await fetch(url, {
7771
- method: "POST",
7772
- headers: { "Content-Type": "application/json" },
7773
- body: JSON.stringify({ refresh_token: refreshToken })
7774
- });
7775
- if (response.ok) {
7776
- const tokens = await response.json();
7777
- this.authManager.storage.setAccessToken(tokens.access_token);
7778
- this.authManager.storage.setRefreshToken(tokens.refresh_token);
7779
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
7780
- if (expiresInMs > 0) {
7781
- this.scheduleAutoRefresh(expiresInMs);
7782
- }
7783
- logger.info("[initSession] Token refreshed successfully");
7784
- } else {
7785
- logger.warn("[initSession] Token refresh failed, clearing session");
7786
- this.authManager.clear();
7787
- return null;
7788
- }
7789
- } catch (err) {
7790
- logger.warn("[initSession] Token refresh error:", err);
7791
- this.authManager.clear();
7792
- return null;
7793
- }
7794
- } else {
8780
+ if (!this.authManager.storage.getRefreshToken()) {
7795
8781
  logger.warn("[initSession] No refresh token, clearing session");
7796
8782
  this.authManager.clear();
7797
8783
  return null;
7798
8784
  }
8785
+ const refreshed = await this.refreshToken();
8786
+ if (!refreshed) {
8787
+ logger.warn("[initSession] Token refresh failed, clearing session");
8788
+ this.authManager.clear();
8789
+ return null;
8790
+ }
8791
+ logger.info("[initSession] Token refreshed successfully");
7799
8792
  }
7800
8793
  }
7801
8794
  try {
@@ -7829,7 +8822,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7829
8822
  }
7830
8823
  this._currentUser = {
7831
8824
  userId: user.id,
7832
- roles: [],
8825
+ roles: this._currentUser?.roles ?? [],
7833
8826
  anonymous: false,
7834
8827
  node: userNode ?? void 0
7835
8828
  };
@@ -7885,6 +8878,16 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7885
8878
  * @throws Error if no refresh token is stored or refresh fails
7886
8879
  */
7887
8880
  async refreshToken() {
8881
+ if (this._refreshInFlight) {
8882
+ logger.debug("[refreshToken] Refresh already in flight, awaiting it");
8883
+ return this._refreshInFlight;
8884
+ }
8885
+ this._refreshInFlight = this._refreshToken().finally(() => {
8886
+ this._refreshInFlight = null;
8887
+ });
8888
+ return this._refreshInFlight;
8889
+ }
8890
+ async _refreshToken() {
7888
8891
  const refreshToken = this.authManager.storage.getRefreshToken();
7889
8892
  if (!refreshToken) {
7890
8893
  logger.warn("[refreshToken] No refresh token available");
@@ -7893,7 +8896,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7893
8896
  try {
7894
8897
  logger.info("[refreshToken] Refreshing access token...");
7895
8898
  const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
7896
- const response = await fetch(url, {
8899
+ const response = await this._fetchWithTimeout(url, {
7897
8900
  method: "POST",
7898
8901
  headers: { "Content-Type": "application/json" },
7899
8902
  body: JSON.stringify({ refresh_token: refreshToken })
@@ -7901,6 +8904,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7901
8904
  if (!response.ok) {
7902
8905
  const errorText = await response.text();
7903
8906
  logger.error("[refreshToken] Refresh failed:", response.status, errorText);
8907
+ if (/TOKEN_REUSE_DETECTED|TOKEN_FAMILY_MISMATCH/.test(errorText)) {
8908
+ logger.error(
8909
+ "[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."
8910
+ );
8911
+ }
7904
8912
  if (response.status === 401 || response.status === 403) {
7905
8913
  this.authManager.clear();
7906
8914
  this._currentUser = null;
@@ -7923,7 +8931,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7923
8931
  if (this.authManager.storage instanceof LocalStorageTokenStorage) {
7924
8932
  this.authManager.storage.setUser(user);
7925
8933
  }
7926
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
8934
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7927
8935
  if (expiresInMs > 0) {
7928
8936
  this.scheduleAutoRefresh(expiresInMs);
7929
8937
  }
@@ -7936,6 +8944,53 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7936
8944
  return null;
7937
8945
  }
7938
8946
  }
8947
+ /**
8948
+ * Fetch with a timeout derived from `requestTimeout`.
8949
+ *
8950
+ * Aborts the request after `requestTimeout` ms and rejects with a
8951
+ * `RaisinTimeoutError`. If the caller provides its own AbortSignal in
8952
+ * `init.signal`, it is respected: an external abort rejects with the
8953
+ * original AbortError instead of a timeout error.
8954
+ * @internal
8955
+ */
8956
+ async _fetchWithTimeout(url, init = {}) {
8957
+ const timeoutMs = this.options.requestTimeout;
8958
+ const controller = new AbortController();
8959
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
8960
+ const externalSignal = init.signal ?? void 0;
8961
+ if (externalSignal) {
8962
+ if (externalSignal.aborted) {
8963
+ controller.abort();
8964
+ } else {
8965
+ externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
8966
+ }
8967
+ }
8968
+ try {
8969
+ return await fetch(url, { ...init, signal: controller.signal });
8970
+ } catch (error) {
8971
+ if (error instanceof Error && error.name === "AbortError" && !externalSignal?.aborted) {
8972
+ throw new RaisinTimeoutError(
8973
+ `Request to ${url} timed out after ${timeoutMs}ms`,
8974
+ "REQUEST_TIMEOUT",
8975
+ timeoutMs
8976
+ );
8977
+ }
8978
+ throw error;
8979
+ } finally {
8980
+ clearTimeout(timer);
8981
+ }
8982
+ }
8983
+ /**
8984
+ * Normalize a token `expires_at` value to epoch milliseconds.
8985
+ *
8986
+ * Server endpoints are inconsistent: some return seconds, others
8987
+ * milliseconds. Anything above 1e11 can't be a seconds-epoch (year 5138),
8988
+ * so treat it as milliseconds.
8989
+ * @internal
8990
+ */
8991
+ static expiresAtToMs(expiresAt) {
8992
+ return expiresAt > 1e11 ? expiresAt : expiresAt * 1e3;
8993
+ }
7939
8994
  /**
7940
8995
  * Schedule automatic token refresh
7941
8996
  * @internal
@@ -7944,11 +8999,12 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7944
8999
  if (this._refreshTimer) {
7945
9000
  clearTimeout(this._refreshTimer);
7946
9001
  }
7947
- logger.debug(`[scheduleAutoRefresh] Scheduling refresh in ${Math.round(delayMs / 1e3 / 60)} minutes`);
9002
+ const clamped = Math.min(Math.max(delayMs, 3e4), 2147483647);
9003
+ logger.debug(`[scheduleAutoRefresh] Scheduling refresh in ${Math.round(clamped / 1e3 / 60)} minutes`);
7948
9004
  this._refreshTimer = setTimeout(async () => {
7949
9005
  logger.info("[autoRefresh] Auto-refreshing token...");
7950
9006
  await this.refreshToken();
7951
- }, delayMs);
9007
+ }, clamped);
7952
9008
  }
7953
9009
  /**
7954
9010
  * Get a database/repository interface
@@ -7972,10 +9028,26 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7972
9028
  }
7973
9029
  /**
7974
9030
  * Send a request to the server
9031
+ *
9032
+ * If the connection is temporarily down (connecting/reconnecting, or
9033
+ * disconnected with auto-reconnect enabled), the request is queued and
9034
+ * flushed once the connection is re-established and re-authenticated.
9035
+ * Queued requests are still subject to their own request timeout, and the
9036
+ * queue is bounded (overflow rejects immediately).
7975
9037
  */
7976
9038
  async sendRequestInternal(payload, requestType, contextOverride, requestOptions) {
7977
- if (!this.connection.isConnected()) {
7978
- throw new Error("Not connected to server");
9039
+ const connected = this.connection.isConnected();
9040
+ if (!connected) {
9041
+ const state = this.connection.getState();
9042
+ const willReconnect = state === "connecting" /* Connecting */ || state === "reconnecting" /* Reconnecting */ || state === "disconnected" /* Disconnected */ && this._everConnected && this.connection.isAutoReconnectEnabled();
9043
+ if (!willReconnect) {
9044
+ throw new Error("Not connected to server");
9045
+ }
9046
+ if (this._queuedRequests.length >= _RaisinClient.MAX_QUEUED_REQUESTS) {
9047
+ throw new Error(
9048
+ `Request queue is full (${_RaisinClient.MAX_QUEUED_REQUESTS} requests pending while reconnecting)`
9049
+ );
9050
+ }
7979
9051
  }
7980
9052
  const requestId = this.requestTracker.generateRequestId();
7981
9053
  const context = contextOverride || this._context;
@@ -7994,8 +9066,15 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7994
9066
  try {
7995
9067
  const encoded = encodeMessage(request);
7996
9068
  logger.debug(`Encoded request - ID: ${requestId}, type: ${requestType}, size: ${encoded.length} bytes`);
7997
- this.connection.send(encoded);
7998
- logger.debug(`Request sent - ID: ${requestId}`);
9069
+ if (connected) {
9070
+ this.connection.send(encoded);
9071
+ logger.debug(`Request sent - ID: ${requestId}`);
9072
+ } else {
9073
+ this._queuedRequests.push({ requestId, encoded });
9074
+ logger.debug(
9075
+ `Request queued while reconnecting - ID: ${requestId}, queue size: ${this._queuedRequests.length}`
9076
+ );
9077
+ }
7999
9078
  } catch (error) {
8000
9079
  logger.error(`Failed to send request - ID: ${requestId}:`, error);
8001
9080
  this.requestTracker.rejectRequest(
@@ -8006,6 +9085,42 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8006
9085
  }
8007
9086
  return responsePromise;
8008
9087
  }
9088
+ /**
9089
+ * Flush requests queued while the connection was re-establishing.
9090
+ * Called after the connection is connected and (re-)authenticated.
9091
+ * @internal
9092
+ */
9093
+ _flushQueuedRequests() {
9094
+ if (this._queuedRequests.length === 0) return;
9095
+ const queued = this._queuedRequests;
9096
+ this._queuedRequests = [];
9097
+ logger.info(`[_flushQueuedRequests] Flushing ${queued.length} queued request(s)`);
9098
+ for (const { requestId, encoded } of queued) {
9099
+ if (!this.requestTracker.hasPendingRequest(requestId)) continue;
9100
+ try {
9101
+ this.connection.send(encoded);
9102
+ } catch (error) {
9103
+ this.requestTracker.rejectRequest(
9104
+ requestId,
9105
+ error instanceof Error ? error : new Error("Failed to send queued request")
9106
+ );
9107
+ }
9108
+ }
9109
+ }
9110
+ /**
9111
+ * Reject all requests queued while the connection was re-establishing
9112
+ * (e.g. re-authentication failed after reconnect).
9113
+ * @internal
9114
+ */
9115
+ _rejectQueuedRequests(error) {
9116
+ if (this._queuedRequests.length === 0) return;
9117
+ const queued = this._queuedRequests;
9118
+ this._queuedRequests = [];
9119
+ logger.warn(`[_rejectQueuedRequests] Rejecting ${queued.length} queued request(s): ${error.message}`);
9120
+ for (const { requestId } of queued) {
9121
+ this.requestTracker.rejectRequest(requestId, error);
9122
+ }
9123
+ }
8009
9124
  /**
8010
9125
  * Get the HTTP base URL derived from the WebSocket URL
8011
9126
  */
@@ -8083,6 +9198,44 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8083
9198
  getCurrentUser() {
8084
9199
  return this._currentUser;
8085
9200
  }
9201
+ /**
9202
+ * Re-resolve the current user's EFFECTIVE roles from the server and refresh
9203
+ * `getCurrentUser()`.
9204
+ *
9205
+ * Roles are deliberately not embedded in the JWT (they are resolved live
9206
+ * server-side), so when an admin changes a user's roles/groups the token does
9207
+ * not change — the client must re-ask. This re-runs the WS JWT auth handshake
9208
+ * (which returns freshly resolved effective roles), updates `_currentUser`, and
9209
+ * emits `USER_UPDATED` so role-gated UI can react without a reload.
9210
+ *
9211
+ * Called automatically when the server pushes `permissions:changed` after an
9212
+ * access-control write, and exposed for on-demand refresh. Safe no-op when not
9213
+ * authenticated.
9214
+ */
9215
+ async refreshCurrentUser() {
9216
+ if (__privateGet(this, _refreshingUser)) return __privateGet(this, _refreshingUser);
9217
+ const token = this.authManager.storage.getAccessToken();
9218
+ if (!token) return;
9219
+ __privateSet(this, _refreshingUser, (async () => {
9220
+ const before = JSON.stringify(this._currentUser?.roles ?? null);
9221
+ const prevNode = this._currentUser?.node;
9222
+ try {
9223
+ await this.authenticate({ type: "jwt", token });
9224
+ if (this._currentUser && prevNode && !this._currentUser.node) {
9225
+ this._currentUser.node = prevNode;
9226
+ }
9227
+ const after = JSON.stringify(this._currentUser?.roles ?? null);
9228
+ if (after !== before) {
9229
+ this._emitAuthEvent("USER_UPDATED");
9230
+ }
9231
+ } catch (err) {
9232
+ logger.warn("[refreshCurrentUser] role re-resolve failed:", err);
9233
+ } finally {
9234
+ __privateSet(this, _refreshingUser, null);
9235
+ }
9236
+ })());
9237
+ return __privateGet(this, _refreshingUser);
9238
+ }
8086
9239
  /**
8087
9240
  * Get current user ID
8088
9241
  *
@@ -8291,7 +9444,10 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8291
9444
  _emitAuthEvent(event) {
8292
9445
  const session = {
8293
9446
  user: this.getStoredUser(),
8294
- accessToken: this.authManager.storage.getAccessToken()
9447
+ accessToken: this.authManager.storage.getAccessToken(),
9448
+ // Surface effective roles so role-gated consumers can react to
9449
+ // SIGNED_IN / USER_UPDATED without calling getCurrentUser() themselves.
9450
+ roles: this._currentUser?.roles ?? null
8295
9451
  };
8296
9452
  const change = { event, session };
8297
9453
  for (const callback of this._authListeners) {
@@ -8331,6 +9487,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8331
9487
  */
8332
9488
  async _subscribeToUserHome(repository, userHome) {
8333
9489
  await this._unsubscribeFromUserHome();
9490
+ userHome = normalizeHomePath(userHome) ?? userHome;
8334
9491
  try {
8335
9492
  const db = this.database(repository);
8336
9493
  const pathParts = userHome.split("/").filter((p) => p.length > 0);
@@ -8630,7 +9787,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8630
9787
  const branch = options.branch ?? this._context.branch ?? "main";
8631
9788
  const pathWithProperty = options.propertyPath ? `${options.path}@${options.propertyPath}` : options.path;
8632
9789
  const endpoint = `/api/repository/${options.repository}/${branch}/head/${options.workspace}${pathWithProperty}/raisin:sign`;
8633
- const response = await fetch(`${this._httpBaseUrl}${endpoint}`, {
9790
+ const response = await this._fetchWithTimeout(`${this._httpBaseUrl}${endpoint}`, {
8634
9791
  method: "POST",
8635
9792
  headers: {
8636
9793
  "Content-Type": "application/json",
@@ -8685,6 +9842,10 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8685
9842
  return _RaisinClient.forSSR(baseUrl, options);
8686
9843
  }
8687
9844
  };
9845
+ _refreshingUser = new WeakMap();
9846
+ /** Hard cap on requests held while reconnecting; overflow rejects immediately. */
9847
+ _RaisinClient.MAX_QUEUED_REQUESTS = 100;
9848
+ var RaisinClient = _RaisinClient;
8688
9849
 
8689
9850
  // src/node-builder.ts
8690
9851
  var NodeBuilder = class _NodeBuilder {
@@ -9019,6 +10180,29 @@ function isTerminalEvent(event) {
9019
10180
  return event.type === "flow_completed" || event.type === "flow_failed";
9020
10181
  }
9021
10182
 
10183
+ // src/types/flow-definition.ts
10184
+ function getRefDisplayName(ref) {
10185
+ const path = ref["raisin:path"] || ref["raisin:ref"];
10186
+ const segments = path.split("/").filter(Boolean);
10187
+ return segments[segments.length - 1] || path;
10188
+ }
10189
+ function getRefPath(ref) {
10190
+ return ref["raisin:path"] || ref["raisin:ref"];
10191
+ }
10192
+ var RETRY_STRATEGIES = {
10193
+ none: { max_retries: 0, base_delay_ms: 0, max_delay_ms: 0 },
10194
+ quick: { max_retries: 3, base_delay_ms: 1e3, max_delay_ms: 1e4 },
10195
+ standard: { max_retries: 5, base_delay_ms: 2e3, max_delay_ms: 6e4 },
10196
+ aggressive: { max_retries: 10, base_delay_ms: 5e3, max_delay_ms: 12e4 },
10197
+ llm: { max_retries: 5, base_delay_ms: 1e4, max_delay_ms: 12e4 }
10198
+ };
10199
+ function isFlowDefinitionStep(node) {
10200
+ return node.node_type === "raisin:FlowStep";
10201
+ }
10202
+ function isFlowDefinitionContainer(node) {
10203
+ return node.node_type === "raisin:FlowContainer";
10204
+ }
10205
+
9022
10206
  // src/utils/plan-projection.ts
9023
10207
  function asRecord(value) {
9024
10208
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
@@ -9047,6 +10231,15 @@ function parsePlanTasks(rawTasks) {
9047
10231
  };
9048
10232
  }).filter((task) => task !== null);
9049
10233
  }
10234
+ var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled"]);
10235
+ var TERMINAL_PLAN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
10236
+ function setPlanStatus(plan, nextStatus) {
10237
+ if (!nextStatus) return;
10238
+ if (TERMINAL_PLAN_STATUSES.has(plan.status) && !TERMINAL_PLAN_STATUSES.has(nextStatus)) {
10239
+ return;
10240
+ }
10241
+ plan.status = nextStatus;
10242
+ }
9050
10243
  function upsertTask(plan, nextTask) {
9051
10244
  const existingIndex = plan.tasks.findIndex(
9052
10245
  (task) => nextTask.taskId && task.taskId === nextTask.taskId || !nextTask.taskId && task.title === nextTask.title
@@ -9055,8 +10248,12 @@ function upsertTask(plan, nextTask) {
9055
10248
  plan.tasks.push(nextTask);
9056
10249
  return;
9057
10250
  }
10251
+ const existing = plan.tasks[existingIndex];
10252
+ if (TERMINAL_TASK_STATUSES.has(existing.status) && !TERMINAL_TASK_STATUSES.has(nextTask.status)) {
10253
+ return;
10254
+ }
9058
10255
  plan.tasks[existingIndex] = {
9059
- ...plan.tasks[existingIndex],
10256
+ ...existing,
9060
10257
  ...nextTask
9061
10258
  };
9062
10259
  }
@@ -9100,7 +10297,7 @@ function projectPlansFromMessages(messages) {
9100
10297
  plan.planPath = planPath ?? plan.planPath;
9101
10298
  plan.planId = planId ?? plan.planId;
9102
10299
  plan.title = asString(data.title) || plan.title;
9103
- plan.status = asString(data.status) || plan.status;
10300
+ setPlanStatus(plan, asString(data.status));
9104
10301
  plan.requiresApproval = Boolean(data.requires_approval) || plan.status === "pending_approval";
9105
10302
  plan.sourceMessagePath = message.path ?? plan.sourceMessagePath;
9106
10303
  const parsedTasks = parsePlanTasks(data.tasks);
@@ -9116,7 +10313,7 @@ function projectPlansFromMessages(messages) {
9116
10313
  const plan = ensurePlan(plans, key, "Plan", timestamp);
9117
10314
  plan.planPath = planPath ?? plan.planPath;
9118
10315
  plan.planId = planId ?? plan.planId;
9119
- plan.status = asString(data.plan_status) || plan.status;
10316
+ setPlanStatus(plan, asString(data.plan_status));
9120
10317
  plan.requiresApproval = plan.status === "pending_approval";
9121
10318
  const taskUpdate = {
9122
10319
  taskId: asString(data.task_id),
@@ -9143,6 +10340,12 @@ function projectPlansFromMessages(messages) {
9143
10340
  }
9144
10341
  }
9145
10342
  }
10343
+ for (const plan of plans.values()) {
10344
+ if (!TERMINAL_PLAN_STATUSES.has(plan.status) && plan.tasks.length > 0 && plan.tasks.every((task) => TERMINAL_TASK_STATUSES.has(task.status))) {
10345
+ plan.status = "completed";
10346
+ plan.requiresApproval = false;
10347
+ }
10348
+ }
9146
10349
  return [...plans.values()].sort((a, b) => {
9147
10350
  const aTime = Date.parse(a.updatedAt || "");
9148
10351
  const bTime = Date.parse(b.updatedAt || "");
@@ -9357,10 +10560,12 @@ var ConversationStore = class {
9357
10560
  }
9358
10561
  switch (event.type) {
9359
10562
  case "text_chunk":
10563
+ this.markStreamingActivity();
9360
10564
  this._streamingText += event.text;
9361
10565
  this.notify();
9362
10566
  break;
9363
10567
  case "thought_chunk":
10568
+ this.markStreamingActivity();
9364
10569
  this.notify();
9365
10570
  break;
9366
10571
  case "assistant_message":
@@ -9369,6 +10574,7 @@ var ConversationStore = class {
9369
10574
  this.notify();
9370
10575
  break;
9371
10576
  case "tool_call_started": {
10577
+ this.markStreamingActivity();
9372
10578
  logger.debug("[ConversationStore] Tool started:", event.functionName);
9373
10579
  const info = {
9374
10580
  id: event.toolCallId,
@@ -9381,6 +10587,7 @@ var ConversationStore = class {
9381
10587
  break;
9382
10588
  }
9383
10589
  case "tool_call_completed": {
10590
+ this.markStreamingActivity();
9384
10591
  logger.debug("[ConversationStore] Tool completed:", event.toolCallId);
9385
10592
  const idx = this._activeToolCalls.findIndex((tc) => tc.id === event.toolCallId);
9386
10593
  if (idx >= 0) {
@@ -9455,6 +10662,7 @@ var ConversationStore = class {
9455
10662
  logger.error("[ConversationStore] Turn failed", event.error);
9456
10663
  this._error = event.error;
9457
10664
  this._isStreaming = false;
10665
+ this._isWaiting = false;
9458
10666
  this._streamingText = "";
9459
10667
  this._activeToolCalls = [];
9460
10668
  this.clearStreamingTimer();
@@ -9492,6 +10700,26 @@ var ConversationStore = class {
9492
10700
  }
9493
10701
  }
9494
10702
  }
10703
+ /**
10704
+ * Agent activity observed while the store thought the turn was over.
10705
+ *
10706
+ * This happens when execution continues WITHOUT a user message — plan
10707
+ * approval in approve_then_auto / step_by_step (approvePlan() returns an
10708
+ * enqueue receipt and the agent starts executing), or agent-initiated
10709
+ * follow-ups. The events arrive over the persistent live subscription
10710
+ * (no history replay), so activity is always "now": re-enter streaming
10711
+ * state so UIs show progress and the end-of-turn handling (done/waiting
10712
+ * → deferred reload) runs again for this continuation.
10713
+ */
10714
+ markStreamingActivity() {
10715
+ if (this._isStreaming) return;
10716
+ logger.debug("[ConversationStore] Agent activity while idle \u2014 re-entering streaming state");
10717
+ this._isStreaming = true;
10718
+ this._isWaiting = false;
10719
+ this._streamingText = "";
10720
+ this.resetStreamingTimer();
10721
+ this.startWatchdog();
10722
+ }
9495
10723
  /**
9496
10724
  * If accumulated streaming text hasn't been captured in an assistant_message,
9497
10725
  * synthesize one.
@@ -9725,6 +10953,14 @@ var ConversationStore = class {
9725
10953
  };
9726
10954
 
9727
10955
  // src/stores/conversation-list-store.ts
10956
+ function buildChatsSubscriptionFilters(userHome) {
10957
+ const home = normalizeHomePath(userHome) ?? userHome;
10958
+ return {
10959
+ workspace: "raisin:access_control",
10960
+ path: `${home}/inbox/chats/**`,
10961
+ event_types: ["node:created", "node:updated"]
10962
+ };
10963
+ }
9728
10964
  var ConversationListStore = class {
9729
10965
  constructor(options) {
9730
10966
  this.subscribers = /* @__PURE__ */ new Set();
@@ -9870,13 +11106,8 @@ var ConversationListStore = class {
9870
11106
  const rows = result.rows;
9871
11107
  const userHome = rows?.[0]?.home;
9872
11108
  if (!userHome) return;
9873
- const chatsPath = `${userHome}/inbox/chats`;
9874
11109
  this._realtimeSubscription = await events.subscribe(
9875
- {
9876
- workspace: "raisin:access_control",
9877
- path: chatsPath,
9878
- event_types: ["node:created", "node:updated"]
9879
- },
11110
+ buildChatsSubscriptionFilters(userHome),
9880
11111
  async (event) => {
9881
11112
  const eventData = event;
9882
11113
  const nodeId = eventData.payload?.node_id;
@@ -9982,7 +11213,7 @@ function useConversation(react, options) {
9982
11213
  var INITIAL_LIST_SNAPSHOT = {
9983
11214
  conversations: [],
9984
11215
  totalUnreadCount: 0,
9985
- isLoading: false,
11216
+ isLoading: true,
9986
11217
  error: null
9987
11218
  };
9988
11219
  function useConversationList(react, options) {
@@ -10860,6 +12091,160 @@ function createRaisinReact(react) {
10860
12091
  useFlow: useFlow2
10861
12092
  };
10862
12093
  }
12094
+
12095
+ // src/integrations/vue/vue-auth.ts
12096
+ function createUseAuth2(vue) {
12097
+ return function useAuth(client) {
12098
+ const adapter = createAuthAdapter(client);
12099
+ const snapshot = vue.ref(adapter.getSnapshot());
12100
+ const unsubscribe = adapter.subscribe((s) => {
12101
+ snapshot.value = s;
12102
+ });
12103
+ vue.onUnmounted(() => {
12104
+ unsubscribe();
12105
+ adapter.destroy();
12106
+ });
12107
+ return {
12108
+ user: vue.computed(() => snapshot.value.user),
12109
+ isAuthenticated: vue.computed(() => snapshot.value.isAuthenticated),
12110
+ isLoading: vue.computed(() => snapshot.value.isLoading),
12111
+ login: adapter.login,
12112
+ register: adapter.register,
12113
+ logout: adapter.logout,
12114
+ initSession: adapter.initSession
12115
+ };
12116
+ };
12117
+ }
12118
+
12119
+ // src/integrations/vue/vue-connection.ts
12120
+ function createUseConnection2(vue) {
12121
+ return function useConnection(client) {
12122
+ const adapter = createConnectionAdapter(client);
12123
+ const snapshot = vue.ref(adapter.getSnapshot());
12124
+ const unsubscribe = adapter.subscribe((s) => {
12125
+ snapshot.value = s;
12126
+ });
12127
+ vue.onUnmounted(() => {
12128
+ unsubscribe();
12129
+ adapter.destroy();
12130
+ });
12131
+ return {
12132
+ state: vue.computed(() => snapshot.value.state),
12133
+ isConnected: vue.computed(() => snapshot.value.isConnected),
12134
+ isReady: vue.computed(() => snapshot.value.isReady),
12135
+ connect: adapter.connect,
12136
+ disconnect: adapter.disconnect
12137
+ };
12138
+ };
12139
+ }
12140
+
12141
+ // src/integrations/vue/vue-sql.ts
12142
+ function createUseSql2(vue) {
12143
+ return function useSql(database, sql, params, options) {
12144
+ const adapter = createSqlAdapter(database, sql, params, options);
12145
+ const snapshot = vue.ref(adapter.getSnapshot());
12146
+ const unsubscribe = adapter.subscribe((s) => {
12147
+ snapshot.value = s;
12148
+ });
12149
+ vue.onUnmounted(() => {
12150
+ unsubscribe();
12151
+ adapter.destroy();
12152
+ });
12153
+ return {
12154
+ data: vue.computed(() => snapshot.value.data),
12155
+ isLoading: vue.computed(() => snapshot.value.isLoading),
12156
+ error: vue.computed(() => snapshot.value.error),
12157
+ refetch: adapter.refetch
12158
+ };
12159
+ };
12160
+ }
12161
+
12162
+ // src/integrations/vue/vue-subscription.ts
12163
+ function createUseSubscription2(vue) {
12164
+ return function useSubscription(database, filters, callback) {
12165
+ const adapter = createSubscriptionAdapter(database, filters, callback);
12166
+ vue.onUnmounted(() => adapter.destroy());
12167
+ return {
12168
+ unsubscribe: () => adapter.destroy()
12169
+ };
12170
+ };
12171
+ }
12172
+
12173
+ // src/integrations/vue/vue-conversation.ts
12174
+ function createUseConversation(vue) {
12175
+ return function useConversation2(options) {
12176
+ const store = new ConversationStore(options);
12177
+ const snapshot = vue.ref(store.getSnapshot());
12178
+ const unsubscribe = store.subscribe((s) => {
12179
+ snapshot.value = s;
12180
+ });
12181
+ vue.onUnmounted(() => {
12182
+ unsubscribe();
12183
+ store.destroy();
12184
+ });
12185
+ return {
12186
+ conversation: vue.computed(() => snapshot.value.conversation),
12187
+ messages: vue.computed(() => snapshot.value.messages),
12188
+ isStreaming: vue.computed(() => snapshot.value.isStreaming),
12189
+ isWaiting: vue.computed(() => snapshot.value.isWaiting),
12190
+ streamingText: vue.computed(() => snapshot.value.streamingText),
12191
+ error: vue.computed(() => snapshot.value.error),
12192
+ activeToolCalls: vue.computed(() => snapshot.value.activeToolCalls),
12193
+ plans: vue.computed(() => snapshot.value.plans),
12194
+ isLoading: vue.computed(() => snapshot.value.isLoading),
12195
+ conversationPath: vue.computed(() => snapshot.value.conversationPath),
12196
+ sendMessage: (content) => store.sendMessage(content),
12197
+ approvePlan: async (planPath) => {
12198
+ await store.approvePlan(planPath);
12199
+ },
12200
+ rejectPlan: async (planPath, feedback) => {
12201
+ await store.rejectPlan(planPath, feedback);
12202
+ },
12203
+ stop: () => store.stop(),
12204
+ loadMessages: () => store.loadMessages(),
12205
+ store
12206
+ };
12207
+ };
12208
+ }
12209
+ function createUseConversationList(vue) {
12210
+ return function useConversationList2(options) {
12211
+ const store = new ConversationListStore(options);
12212
+ const snapshot = vue.ref(store.getSnapshot());
12213
+ const unsubscribe = store.subscribe((s) => {
12214
+ snapshot.value = s;
12215
+ });
12216
+ store.load().catch((err) => {
12217
+ logger.error("[useConversationList] Initial load failed:", err);
12218
+ });
12219
+ vue.onUnmounted(() => {
12220
+ unsubscribe();
12221
+ store.destroy();
12222
+ });
12223
+ return {
12224
+ conversations: vue.computed(() => snapshot.value.conversations),
12225
+ totalUnreadCount: vue.computed(() => snapshot.value.totalUnreadCount),
12226
+ isLoading: vue.computed(() => snapshot.value.isLoading),
12227
+ error: vue.computed(() => snapshot.value.error),
12228
+ createConversation: (opts) => store.createConversation(opts),
12229
+ deleteConversation: (conversationPath) => store.deleteConversation(conversationPath),
12230
+ markAsRead: (conversationPath) => store.markAsRead(conversationPath),
12231
+ refresh: () => store.load(),
12232
+ store
12233
+ };
12234
+ };
12235
+ }
12236
+
12237
+ // src/integrations/vue/index.ts
12238
+ function createRaisinVue(vue) {
12239
+ return {
12240
+ useAuth: createUseAuth2(vue),
12241
+ useConnection: createUseConnection2(vue),
12242
+ useSql: createUseSql2(vue),
12243
+ useSubscription: createUseSubscription2(vue),
12244
+ useConversation: createUseConversation(vue),
12245
+ useConversationList: createUseConversationList(vue)
12246
+ };
12247
+ }
10863
12248
  // Annotate the CommonJS export names for ESM import in node:
10864
12249
  0 && (module.exports = {
10865
12250
  AllNodeEventTypes,
@@ -10881,6 +12266,7 @@ function createRaisinReact(react) {
10881
12266
  DEFAULT_RETRY_BASE_DELAY,
10882
12267
  DEFAULT_RETRY_MAX_DELAY,
10883
12268
  DEFAULT_RETRY_OPTIONS,
12269
+ DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS,
10884
12270
  Database,
10885
12271
  ElementTypes,
10886
12272
  EventHandler,
@@ -10888,11 +12274,16 @@ function createRaisinReact(react) {
10888
12274
  FlowClient,
10889
12275
  FlowsApi,
10890
12276
  FunctionsApi,
12277
+ HttpArchetypes,
10891
12278
  HttpDatabase,
12279
+ HttpElementTypes,
10892
12280
  HttpFunctionsApi,
12281
+ HttpNodeTypes,
10893
12282
  HttpWorkspaceClient,
12283
+ InboxApi,
10894
12284
  LocalStorageTokenStorage,
10895
12285
  LogLevel,
12286
+ McpClient,
10896
12287
  MemoryTokenStorage,
10897
12288
  NodeBuilder,
10898
12289
  NodeEventType,
@@ -10901,6 +12292,7 @@ function createRaisinReact(react) {
10901
12292
  NodeOperations,
10902
12293
  NodeTypes,
10903
12294
  RAISIN_CONTEXT_KEY,
12295
+ RETRY_STRATEGIES,
10904
12296
  RaisinAbortError,
10905
12297
  RaisinAuthError,
10906
12298
  RaisinClient,
@@ -10915,6 +12307,7 @@ function createRaisinReact(react) {
10915
12307
  ResponseStatus,
10916
12308
  RetryBuilder,
10917
12309
  SSEClient,
12310
+ SchedulerApi,
10918
12311
  SqlQuery,
10919
12312
  Tags,
10920
12313
  Transaction,
@@ -10936,6 +12329,7 @@ function createRaisinReact(react) {
10936
12329
  createFlowAdapter,
10937
12330
  createLoader,
10938
12331
  createRaisinReact,
12332
+ createRaisinVue,
10939
12333
  createRequestScopedClient,
10940
12334
  createSSRClient,
10941
12335
  createSqlAdapter,
@@ -10948,13 +12342,18 @@ function createRaisinReact(react) {
10948
12342
  getErrorMessage,
10949
12343
  getLogLevel,
10950
12344
  getLoggerConfig,
12345
+ getRefDisplayName,
12346
+ getRefPath,
10951
12347
  isEventMessage,
12348
+ isFlowDefinitionContainer,
12349
+ isFlowDefinitionStep,
10952
12350
  isHttpClient,
10953
12351
  isResponseEnvelope,
10954
12352
  isRetryableError,
10955
12353
  isTerminalEvent,
10956
12354
  isWebSocketClient,
10957
12355
  logger,
12356
+ normalizeHomePath,
10958
12357
  projectPlansFromMessages,
10959
12358
  rowsToObjects,
10960
12359
  setLogLevel,
@@ -10964,3 +12363,4 @@ function createRaisinReact(react) {
10964
12363
  useFlow,
10965
12364
  withRetry
10966
12365
  });
12366
+ //# sourceMappingURL=index.js.map