@raisindb/client 0.2.4 → 0.2.6

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-CU5mi_PE.d.mts +6842 -0
  15. package/dist/conversation-list-store-CU5mi_PE.d.ts +6842 -0
  16. package/dist/index-DDus5a7P.d.mts +301 -0
  17. package/dist/index-sFvMihQA.d.ts +301 -0
  18. package/dist/index.d.mts +72 -6109
  19. package/dist/index.d.ts +72 -6109
  20. package/dist/index.js +1640 -211
  21. package/dist/index.js.map +1 -0
  22. package/dist/index.mjs +1280 -3098
  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-B2G94Q9p.d.ts +52 -0
  31. package/dist/svelte-sql-CpXHXg34.d.mts +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
  *
@@ -1455,17 +1622,40 @@ var NodeOperations = class {
1455
1622
  // Tree Operations
1456
1623
  // ========================================================================
1457
1624
  /**
1458
- * List children of a parent node
1625
+ * List children of a parent node, in editorial (drag-and-drop) order.
1459
1626
  *
1460
1627
  * @param parentPath - Path of the parent node
1461
1628
  * @returns Array of child nodes
1462
1629
  */
1463
1630
  async listChildren(parentPath) {
1631
+ const { items } = await this.listChildrenPage(parentPath);
1632
+ return items;
1633
+ }
1634
+ /**
1635
+ * List one page of a parent's children, in editorial order.
1636
+ *
1637
+ * Pagination is keyset-based: pass the previous response's `nextCursor` back as
1638
+ * `cursor` to get the following page. `nextCursor` is `null` on the last page.
1639
+ * Treat the cursor as opaque — do not parse or construct it.
1640
+ *
1641
+ * @param parentPath - Path of the parent node
1642
+ * @param options - `cursor` from a previous page, and page `limit`
1643
+ */
1644
+ async listChildrenPage(parentPath, options = {}) {
1464
1645
  const payload = {
1465
1646
  parent_path: parentPath
1466
1647
  };
1648
+ if (options.cursor !== void 0) payload.cursor = options.cursor;
1649
+ if (options.limit !== void 0) payload.limit = options.limit;
1467
1650
  const result = await this.sendRequest(payload, "node_list_children");
1468
- return result;
1651
+ if (Array.isArray(result)) {
1652
+ return { items: result, nextCursor: null };
1653
+ }
1654
+ const page = result;
1655
+ return {
1656
+ items: page.nodes ?? [],
1657
+ nextCursor: page.next_cursor == null ? null : JSON.stringify(page.next_cursor)
1658
+ };
1469
1659
  }
1470
1660
  /**
1471
1661
  * Get a node tree starting from a root node
@@ -1528,7 +1718,7 @@ var NodeOperations = class {
1528
1718
  */
1529
1719
  async rename(nodePath, newName) {
1530
1720
  const payload = {
1531
- node_path: nodePath,
1721
+ old_path: nodePath,
1532
1722
  new_name: newName
1533
1723
  };
1534
1724
  const result = await this.sendRequest(payload, "node_rename");
@@ -1544,9 +1734,8 @@ var NodeOperations = class {
1544
1734
  */
1545
1735
  async copy(fromPath, toParentPath, newName) {
1546
1736
  const payload = {
1547
- from_path: fromPath,
1548
- to_parent_path: toParentPath,
1549
- deep: false
1737
+ source_path: fromPath,
1738
+ target_parent: toParentPath
1550
1739
  };
1551
1740
  if (newName !== void 0) {
1552
1741
  payload.new_name = newName;
@@ -1564,9 +1753,8 @@ var NodeOperations = class {
1564
1753
  */
1565
1754
  async copyTree(fromPath, toParentPath, newName) {
1566
1755
  const payload = {
1567
- from_path: fromPath,
1568
- to_parent_path: toParentPath,
1569
- deep: true
1756
+ source_path: fromPath,
1757
+ target_parent: toParentPath
1570
1758
  };
1571
1759
  if (newName !== void 0) {
1572
1760
  payload.new_name = newName;
@@ -1575,53 +1763,72 @@ var NodeOperations = class {
1575
1763
  return result;
1576
1764
  }
1577
1765
  /**
1578
- * Reorder a node by setting a new order key
1766
+ * Move a child to a specific position among its siblings.
1579
1767
  *
1580
- * @param nodePath - Node path
1581
- * @param orderKey - New order key (base62-encoded fractional index)
1582
- * @returns Reordered node
1768
+ * Positions are 0-based; a position past the end appends. The server owns the
1769
+ * fractional order key — you name a position, it mints the key.
1770
+ *
1771
+ * @param parentPath - Parent node path
1772
+ * @param childName - Name of the child to move (not a full path)
1773
+ * @param position - Target 0-based position among the parent's children
1774
+ * @returns The reordered node, carrying its newly assigned `order_key`
1583
1775
  */
1584
- async reorder(nodePath, orderKey) {
1776
+ async reorder(parentPath, childName, position) {
1585
1777
  const payload = {
1586
- node_path: nodePath,
1587
- order_key: orderKey
1778
+ parent_path: parentPath,
1779
+ child_name: childName,
1780
+ position
1588
1781
  };
1589
1782
  const result = await this.sendRequest(payload, "node_reorder");
1590
1783
  return result;
1591
1784
  }
1592
1785
  /**
1593
- * Move a child node before a reference sibling
1786
+ * Move a child so it sits immediately before one of its siblings.
1594
1787
  *
1595
1788
  * @param parentPath - Parent node path
1596
- * @param childPath - Child node path to move
1597
- * @param referencePath - Reference sibling path to position before
1598
- * @returns Moved node
1789
+ * @param childName - Name of the child to move (not a full path)
1790
+ * @param beforeChildName - Name of the sibling to sit before
1599
1791
  */
1600
- async moveChildBefore(parentPath, childPath, referencePath) {
1792
+ async moveChildBefore(parentPath, childName, beforeChildName) {
1601
1793
  const payload = {
1602
1794
  parent_path: parentPath,
1603
- child_path: childPath,
1604
- reference_path: referencePath
1795
+ child_name: childName,
1796
+ before_child_name: beforeChildName
1605
1797
  };
1606
- const result = await this.sendRequest(payload, "node_move_child_before");
1607
- return result;
1798
+ await this.sendRequest(payload, "node_move_child_before");
1608
1799
  }
1609
1800
  /**
1610
- * Move a child node after a reference sibling
1801
+ * Move a child so it sits immediately after one of its siblings.
1611
1802
  *
1612
1803
  * @param parentPath - Parent node path
1613
- * @param childPath - Child node path to move
1614
- * @param referencePath - Reference sibling path to position after
1615
- * @returns Moved node
1804
+ * @param childName - Name of the child to move (not a full path)
1805
+ * @param afterChildName - Name of the sibling to sit after
1616
1806
  */
1617
- async moveChildAfter(parentPath, childPath, referencePath) {
1807
+ async moveChildAfter(parentPath, childName, afterChildName) {
1618
1808
  const payload = {
1619
1809
  parent_path: parentPath,
1620
- child_path: childPath,
1621
- reference_path: referencePath
1810
+ child_name: childName,
1811
+ after_child_name: afterChildName
1622
1812
  };
1623
- const result = await this.sendRequest(payload, "node_move_child_after");
1624
- return result;
1813
+ await this.sendRequest(payload, "node_move_child_after");
1814
+ }
1815
+ /**
1816
+ * Replay a parent's child order from another branch onto the current branch.
1817
+ *
1818
+ * Carries node sibling-order across branches — useful for selective publish
1819
+ * flows, where copying node content (e.g. via SQL) does not move the ordering
1820
+ * index. Only children that exist under the parent on both branches are
1821
+ * reordered. The target branch is the connection's current branch.
1822
+ *
1823
+ * @param parentPath - Parent node path
1824
+ * @param sourceBranch - Branch to copy the child order FROM
1825
+ */
1826
+ async applyChildOrder(parentPath, sourceBranch) {
1827
+ const payload = {
1828
+ parent_path: parentPath,
1829
+ source_branch: sourceBranch
1830
+ };
1831
+ await this.sendRequest(payload, "node_apply_child_order");
1625
1832
  }
1626
1833
  // ========================================================================
1627
1834
  // Relationship Operations
@@ -1897,7 +2104,17 @@ var NodeFileSource = class {
1897
2104
  return;
1898
2105
  }
1899
2106
  if (!this._fsPromises) {
1900
- this._fsPromises = await import("fs/promises");
2107
+ if (!isNodeEnvironment()) {
2108
+ throw new Error(
2109
+ "NodeFileSource requires Node.js (fs/promises is not available in browsers). In the browser, use a File or Blob object instead."
2110
+ );
2111
+ }
2112
+ const specifier = "fs/promises";
2113
+ this._fsPromises = await import(
2114
+ /* @vite-ignore */
2115
+ /* webpackIgnore: true */
2116
+ specifier
2117
+ );
1901
2118
  }
1902
2119
  const stats = await this._fsPromises.stat(this._filePath);
1903
2120
  this._size = stats.size;
@@ -2698,6 +2915,17 @@ var Archetypes = class {
2698
2915
  "archetype_unpublish" /* ArchetypeUnpublish */
2699
2916
  );
2700
2917
  }
2918
+ /**
2919
+ * Get resolved Archetype with full inheritance applied
2920
+ */
2921
+ async getResolved(name) {
2922
+ return this.sendRequest(
2923
+ {
2924
+ name
2925
+ },
2926
+ "archetype_get_resolved" /* ArchetypeGetResolved */
2927
+ );
2928
+ }
2701
2929
  };
2702
2930
 
2703
2931
  // src/element-types.ts
@@ -2785,6 +3013,17 @@ var ElementTypes = class {
2785
3013
  "element_type_unpublish" /* ElementTypeUnpublish */
2786
3014
  );
2787
3015
  }
3016
+ /**
3017
+ * Get resolved ElementType with full inheritance applied
3018
+ */
3019
+ async getResolved(name) {
3020
+ return this.sendRequest(
3021
+ {
3022
+ name
3023
+ },
3024
+ "element_type_get_resolved" /* ElementTypeGetResolved */
3025
+ );
3026
+ }
2788
3027
  };
2789
3028
 
2790
3029
  // src/branches.ts
@@ -2886,6 +3125,102 @@ var Branches = class {
2886
3125
  "branch_compare" /* BranchCompare */
2887
3126
  );
2888
3127
  }
3128
+ /**
3129
+ * Per-node diff of `branch` relative to `baseBranch` (added/modified/deleted).
3130
+ *
3131
+ * Cheaper than reading both branch listings: cost scales with the number of
3132
+ * changed nodes since the branches diverged, not the total content.
3133
+ */
3134
+ async diff(branch, baseBranch) {
3135
+ return this.sendRequest(
3136
+ {
3137
+ branch,
3138
+ base_branch: baseBranch
3139
+ },
3140
+ "branch_diff" /* BranchDiff */
3141
+ );
3142
+ }
3143
+ /**
3144
+ * Copy a node set from `sourceBranch` onto `targetBranch` (branch
3145
+ * promotion): node ids are preserved, so repeated copies update the same
3146
+ * target nodes; all changes land in ONE atomic commit on the target branch.
3147
+ *
3148
+ * Each root's parent path must already exist on the target branch. With
3149
+ * `deleteMissing`, target nodes under the roots that are absent from the
3150
+ * copied source set are pruned (one-way sync).
3151
+ */
3152
+ async copyNodes(sourceBranch, targetBranch, opts) {
3153
+ return this.sendRequest(
3154
+ {
3155
+ source_branch: sourceBranch,
3156
+ target_branch: targetBranch,
3157
+ workspace: opts.workspace,
3158
+ roots: opts.roots,
3159
+ recursive: opts.recursive ?? true,
3160
+ delete_missing: opts.deleteMissing ?? false
3161
+ },
3162
+ "branch_copy_nodes" /* BranchCopyNodes */
3163
+ );
3164
+ }
3165
+ };
3166
+
3167
+ // src/scheduler.ts
3168
+ var SchedulerApi = class {
3169
+ constructor(_context, sendRequest) {
3170
+ this.sendRequest = sendRequest;
3171
+ }
3172
+ /**
3173
+ * Schedule a one-shot invocation of a function or flow at a fixed time.
3174
+ */
3175
+ async schedule(options) {
3176
+ const runAt = options.runAt instanceof Date ? options.runAt.toISOString() : options.runAt;
3177
+ return this.sendRequest(
3178
+ {
3179
+ target_kind: options.targetKind,
3180
+ target_path: options.targetPath,
3181
+ input: options.input ?? {},
3182
+ run_at: runAt,
3183
+ external_key: options.externalKey,
3184
+ branch: options.branch,
3185
+ workspace: options.workspace,
3186
+ max_retries: options.maxRetries
3187
+ },
3188
+ "scheduled_invocation_create" /* ScheduledInvocationCreate */
3189
+ );
3190
+ }
3191
+ /**
3192
+ * Cancel a pending invocation before it fires, by job id or external key.
3193
+ */
3194
+ async cancel(ref) {
3195
+ const payload = typeof ref === "string" ? { job_id: ref } : { job_id: ref.jobId, external_key: ref.externalKey };
3196
+ return this.sendRequest(
3197
+ payload,
3198
+ "scheduled_invocation_cancel" /* ScheduledInvocationCancel */
3199
+ );
3200
+ }
3201
+ /**
3202
+ * List this repository's scheduled invocations.
3203
+ */
3204
+ async list(filter) {
3205
+ const result = await this.sendRequest(
3206
+ {
3207
+ external_key: filter?.externalKey,
3208
+ status: filter?.status
3209
+ },
3210
+ "scheduled_invocation_list" /* ScheduledInvocationList */
3211
+ );
3212
+ return result?.invocations ?? [];
3213
+ }
3214
+ /**
3215
+ * Fetch a single scheduled invocation, by job id or external key.
3216
+ */
3217
+ async get(ref) {
3218
+ const payload = typeof ref === "string" ? { job_id: ref } : { job_id: ref.jobId, external_key: ref.externalKey };
3219
+ return this.sendRequest(
3220
+ payload,
3221
+ "scheduled_invocation_get" /* ScheduledInvocationGet */
3222
+ );
3223
+ }
2889
3224
  };
2890
3225
 
2891
3226
  // src/tags.ts
@@ -3241,6 +3576,7 @@ var SSEClient = class {
3241
3576
  this._lastEventId = options.lastEventId;
3242
3577
  this.autoReconnect = options.reconnect?.enabled ?? true;
3243
3578
  this.externalSignal = options.signal;
3579
+ this.inactivityTimeoutMs = options.inactivityTimeoutMs;
3244
3580
  this.eventTypeSet = options.eventTypes?.length ? new Set(options.eventTypes) : null;
3245
3581
  this.reconnectManager = new ReconnectManager({
3246
3582
  initialDelay: options.reconnect?.initialDelay ?? 1e3,
@@ -3384,6 +3720,23 @@ var SSEClient = class {
3384
3720
  if (err.name === "AbortError" || err instanceof RaisinAbortError) {
3385
3721
  return;
3386
3722
  }
3723
+ if (err instanceof RaisinTimeoutError && err.code === "SSE_INACTIVITY_TIMEOUT") {
3724
+ logger.warn("SSE inactivity timeout:", err.message);
3725
+ if (!this.autoReconnect) {
3726
+ this.setState("disconnected");
3727
+ onError?.(err);
3728
+ throw err;
3729
+ }
3730
+ this.setState("reconnecting");
3731
+ const scheduled2 = await this.waitForReconnect();
3732
+ if (!scheduled2) {
3733
+ logger.warn("SSE max reconnection attempts reached");
3734
+ this.setState("disconnected");
3735
+ onError?.(err);
3736
+ throw err;
3737
+ }
3738
+ continue;
3739
+ }
3387
3740
  logger.error("SSE connection error:", err.message);
3388
3741
  onError?.(err);
3389
3742
  if (!this.autoReconnect) {
@@ -3432,7 +3785,9 @@ var SSEClient = class {
3432
3785
  fetchOptions.headers["Content-Type"] = "application/json";
3433
3786
  fetchOptions.body = JSON.stringify(this.options.body);
3434
3787
  }
3435
- const response = await this.fetchImpl(this.url, fetchOptions);
3788
+ const response = await this.withInactivityTimeout(
3789
+ this.fetchImpl(this.url, fetchOptions)
3790
+ );
3436
3791
  if (!response.ok) {
3437
3792
  const text = await response.text().catch(() => "");
3438
3793
  const msg = `SSE connection failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`;
@@ -3455,7 +3810,7 @@ var SSEClient = class {
3455
3810
  let buffer = "";
3456
3811
  try {
3457
3812
  while (true) {
3458
- const { done, value } = await reader.read();
3813
+ const { done, value } = await this.withInactivityTimeout(reader.read());
3459
3814
  if (done) break;
3460
3815
  buffer += decoder.decode(value, { stream: true });
3461
3816
  const blocks = buffer.split("\n\n");
@@ -3486,9 +3841,48 @@ var SSEClient = class {
3486
3841
  }
3487
3842
  }
3488
3843
  } finally {
3489
- reader.releaseLock();
3844
+ try {
3845
+ reader.releaseLock();
3846
+ } catch {
3847
+ }
3490
3848
  }
3491
3849
  }
3850
+ /**
3851
+ * Race a stream operation against the inactivity timeout.
3852
+ *
3853
+ * If no bytes (or response) arrive within `inactivityTimeoutMs`, the
3854
+ * internal connection is aborted and the operation rejects with a
3855
+ * `RaisinTimeoutError` (code `SSE_INACTIVITY_TIMEOUT`). The timer is
3856
+ * effectively reset on every chunk because each `reader.read()` is
3857
+ * wrapped individually.
3858
+ */
3859
+ withInactivityTimeout(promise) {
3860
+ const timeoutMs = this.inactivityTimeoutMs;
3861
+ if (!timeoutMs || timeoutMs <= 0) return promise;
3862
+ return new Promise((resolve, reject) => {
3863
+ const timer = setTimeout(() => {
3864
+ promise.catch(() => void 0);
3865
+ this.abortController?.abort();
3866
+ reject(
3867
+ new RaisinTimeoutError(
3868
+ `SSE stream inactive for ${timeoutMs}ms`,
3869
+ "SSE_INACTIVITY_TIMEOUT",
3870
+ timeoutMs
3871
+ )
3872
+ );
3873
+ }, timeoutMs);
3874
+ promise.then(
3875
+ (value) => {
3876
+ clearTimeout(timer);
3877
+ resolve(value);
3878
+ },
3879
+ (error) => {
3880
+ clearTimeout(timer);
3881
+ reject(error);
3882
+ }
3883
+ );
3884
+ });
3885
+ }
3492
3886
  /**
3493
3887
  * Wait for the reconnect manager to schedule a reconnection attempt.
3494
3888
  * Returns true if reconnection was scheduled, false if max attempts reached.
@@ -3513,7 +3907,24 @@ function isRecoveredDoneEvent(event) {
3513
3907
  return typeof done.content !== "string" || done.content.trim().length === 0;
3514
3908
  }
3515
3909
 
3910
+ // src/utils/home-path.ts
3911
+ var ACCESS_CONTROL_PREFIX = "/raisin:access_control";
3912
+ function normalizeHomePath(home) {
3913
+ if (home === null || home === void 0) return null;
3914
+ let path = home.trim();
3915
+ if (path.length === 0) return null;
3916
+ if (path === ACCESS_CONTROL_PREFIX) return "/";
3917
+ if (path.startsWith(`${ACCESS_CONTROL_PREFIX}/`)) {
3918
+ path = path.slice(ACCESS_CONTROL_PREFIX.length);
3919
+ }
3920
+ while (path.length > 1 && path.endsWith("/")) {
3921
+ path = path.slice(0, -1);
3922
+ }
3923
+ return path;
3924
+ }
3925
+
3516
3926
  // src/conversations.ts
3927
+ var DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS = 12e4;
3517
3928
  var ConversationManager = class {
3518
3929
  constructor(baseUrl, repository, authManager, options = {}, executeSql) {
3519
3930
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -3535,10 +3946,11 @@ var ConversationManager = class {
3535
3946
  );
3536
3947
  if (!result?.rows?.[0]) throw new Error("User not authenticated");
3537
3948
  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 };
3949
+ const userHome = normalizeHomePath(row.home);
3950
+ if (!userHome) throw new Error("User home path not found");
3951
+ this.cachedUserHome = userHome;
3952
+ this.cachedUserId = row.user_id || userHome;
3953
+ return { userId: this.cachedUserId, userHome };
3542
3954
  }
3543
3955
  // ==========================================================================
3544
3956
  // Conversation listing
@@ -3733,13 +4145,15 @@ var ConversationManager = class {
3733
4145
  fetch: this.fetchImpl,
3734
4146
  signal: options?.signal,
3735
4147
  method: "POST",
3736
- body: { channel: streamTarget.channel, path: streamTarget.path }
4148
+ body: { channel: streamTarget.channel, path: streamTarget.path },
4149
+ inactivityTimeoutMs: options?.inactivityTimeoutMs ?? DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS
3737
4150
  });
3738
4151
  const iterator = sse[Symbol.asyncIterator]();
3739
4152
  let pendingFirst = iterator.next();
3740
4153
  try {
3741
4154
  await sse.waitUntilConnected();
3742
4155
  } catch {
4156
+ pendingFirst?.catch(() => void 0);
3743
4157
  pendingFirst = null;
3744
4158
  sse.close();
3745
4159
  logger.debug("SSE connection failed, falling back to create-only");
@@ -3776,6 +4190,15 @@ var ConversationManager = class {
3776
4190
  return;
3777
4191
  }
3778
4192
  }
4193
+ } catch (error) {
4194
+ if (error instanceof RaisinTimeoutError && error.code === "SSE_INACTIVITY_TIMEOUT") {
4195
+ logger.warn(
4196
+ `[ConversationManager] SSE stream inactive for ${error.timeoutMs}ms, ending turn with synthetic waiting event`
4197
+ );
4198
+ yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
4199
+ return;
4200
+ }
4201
+ throw error;
3779
4202
  } finally {
3780
4203
  sse.close();
3781
4204
  }
@@ -4538,31 +4961,29 @@ var FlowClient = class _FlowClient {
4538
4961
  * Respond to a human task within a flow.
4539
4962
  *
4540
4963
  * 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.
4964
+ * use this method to submit the response. Completion is validated
4965
+ * (caller must be the task's assignee or an admin) and resumes the flow.
4542
4966
  *
4543
- * @param instanceId - Flow instance ID
4544
- * @param taskId - The human task node ID
4545
- * @param response - The human's response data
4967
+ * Note: prefer `InboxApi` for full inbox functionality (listing tasks,
4968
+ * task details); this is a convenience passthrough to the same endpoint.
4969
+ *
4970
+ * @param instanceId - Flow instance ID (unused; kept for API stability)
4971
+ * @param taskId - The inbox task node ID (or path)
4972
+ * @param response - The response data
4546
4973
  *
4547
4974
  * @example
4548
4975
  * ```typescript
4549
4976
  * // Approve a task
4550
- * await flows.respondToHumanTask('instance-123', 'step-5', {
4977
+ * await flows.respondToHumanTask('instance-123', 'task-abc', {
4551
4978
  * action: 'approve',
4552
4979
  * comment: 'Looks good!',
4553
4980
  * });
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
4981
  * ```
4561
4982
  */
4562
- async respondToHumanTask(instanceId, taskId, response, options) {
4983
+ async respondToHumanTask(_instanceId, taskId, response, options) {
4563
4984
  await this.request({
4564
4985
  method: "POST",
4565
- path: `/api/flows/${this.repository}/instances/${instanceId}/tasks/${taskId}/respond`,
4986
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
4566
4987
  body: { response },
4567
4988
  signal: options?.signal
4568
4989
  });
@@ -4701,43 +5122,171 @@ var FlowClient = class _FlowClient {
4701
5122
  }
4702
5123
  };
4703
5124
 
4704
- // src/database.ts
4705
- var Database = class _Database {
4706
- constructor(repository, context, sendRequest, eventHandler, branch, revision, getUploadManager, getSignAssetUrl, httpOptions) {
5125
+ // src/inbox.ts
5126
+ var InboxApi = class {
5127
+ constructor(baseUrl, repository, authManager, options = {}) {
5128
+ this.baseUrl = baseUrl.replace(/\/$/, "");
4707
5129
  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;
5130
+ this.authManager = authManager;
5131
+ this.fetchImpl = options.fetch ?? fetch;
5132
+ this.requestTimeout = options.requestTimeout ?? 3e4;
4726
5133
  }
4727
5134
  /**
4728
- * Get the repository name
5135
+ * List the caller's inbox tasks.
5136
+ *
5137
+ * Tasks are sorted pending-first, then by priority (highest first) and
5138
+ * due time (earliest first).
4729
5139
  */
4730
- getRepository() {
4731
- return this.repository;
5140
+ async listTasks(options = {}) {
5141
+ const params = new URLSearchParams();
5142
+ if (options.status) params.set("status", options.status);
5143
+ if (options.assignee) params.set("assignee", options.assignee);
5144
+ const query = params.size > 0 ? `?${params.toString()}` : "";
5145
+ return this.request({
5146
+ method: "GET",
5147
+ path: `/api/inbox/${this.repository}${query}`,
5148
+ signal: options.signal
5149
+ });
4732
5150
  }
4733
5151
  /**
4734
- * Create a new Database instance scoped to a specific branch.
5152
+ * Get a single inbox task by ID (or path).
5153
+ */
5154
+ async getTask(taskId, options) {
5155
+ return this.request({
5156
+ method: "GET",
5157
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}`,
5158
+ signal: options?.signal
5159
+ });
5160
+ }
5161
+ /**
5162
+ * Complete an inbox task.
4735
5163
  *
4736
- * @param branch - Branch name
4737
- * @returns New Database instance with branch context
5164
+ * The caller must be the task's assignee (or an admin). Completion marks
5165
+ * the task done, records who decided, and resumes the owning flow.
5166
+ *
5167
+ * @param taskId - Task node ID (or path)
5168
+ * @param response - The response payload:
5169
+ * - approval tasks: `{ action: '<option value>', comment?: string }`
5170
+ * - input tasks: the input value(s) matching the task's input_schema
5171
+ * - review/action tasks: any acknowledgement payload
5172
+ *
5173
+ * @example
5174
+ * ```typescript
5175
+ * await inbox.completeTask('task-123', { action: 'approve' });
5176
+ * ```
4738
5177
  */
4739
- onBranch(branch) {
4740
- return new _Database(
5178
+ async completeTask(taskId, response, options) {
5179
+ return this.request({
5180
+ method: "POST",
5181
+ path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
5182
+ body: { response },
5183
+ signal: options?.signal
5184
+ });
5185
+ }
5186
+ // ==========================================================================
5187
+ // Internal HTTP request helper
5188
+ // ==========================================================================
5189
+ async request(options) {
5190
+ const url = `${this.baseUrl}${options.path}`;
5191
+ const headers = {
5192
+ "Content-Type": "application/json"
5193
+ };
5194
+ const token = this.authManager.getAccessToken();
5195
+ if (token) {
5196
+ headers["Authorization"] = `Bearer ${token}`;
5197
+ }
5198
+ const controller = new AbortController();
5199
+ const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
5200
+ if (options.signal) {
5201
+ if (options.signal.aborted) {
5202
+ clearTimeout(timeoutId);
5203
+ throw new RaisinAbortError();
5204
+ }
5205
+ options.signal.addEventListener("abort", () => controller.abort(), {
5206
+ once: true
5207
+ });
5208
+ }
5209
+ try {
5210
+ const response = await this.fetchImpl(url, {
5211
+ method: options.method,
5212
+ headers,
5213
+ body: options.body ? JSON.stringify(options.body) : void 0,
5214
+ signal: controller.signal
5215
+ });
5216
+ clearTimeout(timeoutId);
5217
+ if (!response.ok) {
5218
+ const errorText = await response.text();
5219
+ let errorMessage;
5220
+ try {
5221
+ const errorJson = JSON.parse(errorText);
5222
+ errorMessage = errorJson.message || errorJson.error || errorText;
5223
+ } catch {
5224
+ errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
5225
+ }
5226
+ throw classifyHttpError(response.status, errorMessage);
5227
+ }
5228
+ const contentType = response.headers.get("content-type");
5229
+ if (contentType?.includes("application/json")) {
5230
+ return await response.json();
5231
+ }
5232
+ return void 0;
5233
+ } catch (error) {
5234
+ clearTimeout(timeoutId);
5235
+ if (error instanceof RaisinAbortError || error instanceof RaisinTimeoutError) {
5236
+ throw error;
5237
+ }
5238
+ if (error instanceof Error && error.name === "AbortError") {
5239
+ if (options.signal?.aborted) {
5240
+ throw new RaisinAbortError();
5241
+ }
5242
+ throw new RaisinTimeoutError(
5243
+ `Request timeout after ${this.requestTimeout}ms`,
5244
+ "REQUEST_TIMEOUT",
5245
+ this.requestTimeout
5246
+ );
5247
+ }
5248
+ throw error;
5249
+ }
5250
+ }
5251
+ };
5252
+
5253
+ // src/database.ts
5254
+ var Database = class _Database {
5255
+ constructor(repository, context, sendRequest, eventHandler, branch, revision, getUploadManager, getSignAssetUrl, httpOptions) {
5256
+ this.repository = repository;
5257
+ this.branch = branch;
5258
+ this.revision = revision;
5259
+ this._context = {
5260
+ ...context,
5261
+ repository,
5262
+ branch: branch || context.branch,
5263
+ revision: revision || context.revision
5264
+ };
5265
+ this.sendRequest = (payload, requestType, contextOverride, requestOptions) => sendRequest(
5266
+ payload,
5267
+ requestType || "node_create",
5268
+ contextOverride || this._context,
5269
+ requestOptions
5270
+ );
5271
+ this.eventHandler = eventHandler;
5272
+ this.getUploadManager = getUploadManager;
5273
+ this.getSignAssetUrl = getSignAssetUrl;
5274
+ this.httpOptions = httpOptions;
5275
+ }
5276
+ /**
5277
+ * Get the repository name
5278
+ */
5279
+ getRepository() {
5280
+ return this.repository;
5281
+ }
5282
+ /**
5283
+ * Create a new Database instance scoped to a specific branch.
5284
+ *
5285
+ * @param branch - Branch name
5286
+ * @returns New Database instance with branch context
5287
+ */
5288
+ onBranch(branch) {
5289
+ return new _Database(
4741
5290
  this.repository,
4742
5291
  this._context,
4743
5292
  this.sendRequest,
@@ -4890,6 +5439,13 @@ var Database = class _Database {
4890
5439
  tags() {
4891
5440
  return new Tags(this._context, this.sendRequest);
4892
5441
  }
5442
+ /**
5443
+ * Get SchedulerApi for one-shot scheduled invocations (time-based
5444
+ * function/flow runs).
5445
+ */
5446
+ scheduler() {
5447
+ return new SchedulerApi(this._context, this.sendRequest);
5448
+ }
4893
5449
  /**
4894
5450
  * Get FlowsApi for running flows via WebSocket
4895
5451
  */
@@ -4977,8 +5533,44 @@ var Database = class _Database {
4977
5533
  }
4978
5534
  return this._flowClient;
4979
5535
  }
5536
+ /**
5537
+ * Get a pre-configured InboxApi for human-in-the-loop tasks.
5538
+ *
5539
+ * Requires the Database to have been created via `RaisinClient.database()`
5540
+ * (which automatically provides HTTP context).
5541
+ *
5542
+ * @example
5543
+ * ```typescript
5544
+ * const db = client.database('my-repo');
5545
+ * const { tasks } = await db.inbox.listTasks({ status: 'pending' });
5546
+ * await db.inbox.completeTask(tasks[0].id, { action: 'approve' });
5547
+ * ```
5548
+ */
5549
+ get inbox() {
5550
+ if (!this._inboxApi) {
5551
+ if (!this.httpOptions?.httpBaseUrl || !this.httpOptions?.authManager) {
5552
+ throw new Error(
5553
+ "db.inbox requires HTTP context. Use client.database() to get a Database with inbox support."
5554
+ );
5555
+ }
5556
+ this._inboxApi = new InboxApi(
5557
+ this.httpOptions.httpBaseUrl,
5558
+ this.repository,
5559
+ this.httpOptions.authManager
5560
+ );
5561
+ }
5562
+ return this._inboxApi;
5563
+ }
4980
5564
  /**
4981
5565
  * Get EventHandler for subscribing to real-time events
5566
+ *
5567
+ * Path filter semantics for subscriptions (matched server-side):
5568
+ * - A plain path matches **only that exact node** — there is no implicit
5569
+ * prefix matching.
5570
+ * - `*` matches exactly one path segment (`/inbox/*` matches `/inbox/a`,
5571
+ * not `/inbox/a/b`).
5572
+ * - `**` matches recursively (`/inbox/**` matches all descendants of
5573
+ * `/inbox`, but not `/inbox` itself).
4982
5574
  */
4983
5575
  events() {
4984
5576
  return this.eventHandler;
@@ -6257,10 +6849,194 @@ var UploadManager = class {
6257
6849
  }
6258
6850
  };
6259
6851
 
6852
+ // src/http-schema.ts
6853
+ var HttpSchemaBase = class {
6854
+ constructor(repository, branch, client) {
6855
+ this.repository = repository;
6856
+ this.branch = branch;
6857
+ this.client = client;
6858
+ }
6859
+ /** Base management URL for this schema kind. */
6860
+ base() {
6861
+ return `/api/management/${this.repository}/${this.branch}/${this.kind}`;
6862
+ }
6863
+ /** List all schemas (or only published ones). */
6864
+ async list(publishedOnly = false) {
6865
+ const path = publishedOnly ? `${this.base()}/published` : this.base();
6866
+ const result = await this.client.managementRequest("GET", path);
6867
+ return Array.isArray(result) ? result : [];
6868
+ }
6869
+ /** Get a single schema by name. */
6870
+ async get(name) {
6871
+ return this.client.managementRequest("GET", `${this.base()}/${encodeURIComponent(name)}`);
6872
+ }
6873
+ /** Get the schema with full inheritance (`extends`) applied. */
6874
+ async getResolved(name) {
6875
+ return this.client.managementRequest(
6876
+ "GET",
6877
+ `${this.base()}/${encodeURIComponent(name)}/resolved`
6878
+ );
6879
+ }
6880
+ /** Create a schema. `name` is folded into the definition body if absent. */
6881
+ async create(name, definition, commit) {
6882
+ return this.client.managementRequest("POST", this.base(), {
6883
+ [this.defField]: { name, ...definition },
6884
+ commit
6885
+ });
6886
+ }
6887
+ /** Update an existing schema. */
6888
+ async update(name, definition, commit) {
6889
+ return this.client.managementRequest(
6890
+ "PUT",
6891
+ `${this.base()}/${encodeURIComponent(name)}`,
6892
+ { [this.defField]: { name, ...definition }, commit }
6893
+ );
6894
+ }
6895
+ /** Delete a schema. */
6896
+ async delete(name, commit) {
6897
+ return this.client.managementRequest(
6898
+ "DELETE",
6899
+ `${this.base()}/${encodeURIComponent(name)}`,
6900
+ commit
6901
+ );
6902
+ }
6903
+ /** Publish a schema. */
6904
+ async publish(name, commit) {
6905
+ return this.client.managementRequest(
6906
+ "POST",
6907
+ `${this.base()}/${encodeURIComponent(name)}/publish`,
6908
+ commit
6909
+ );
6910
+ }
6911
+ /** Unpublish a schema. */
6912
+ async unpublish(name, commit) {
6913
+ return this.client.managementRequest(
6914
+ "POST",
6915
+ `${this.base()}/${encodeURIComponent(name)}/unpublish`,
6916
+ commit
6917
+ );
6918
+ }
6919
+ };
6920
+ var HttpNodeTypes = class extends HttpSchemaBase {
6921
+ constructor() {
6922
+ super(...arguments);
6923
+ this.kind = "nodetypes";
6924
+ this.defField = "node_type";
6925
+ }
6926
+ /**
6927
+ * Get the resolved NodeType. Unlike archetypes/element types, NodeType
6928
+ * resolution can be workspace-aware (honors workspace NodeType pins).
6929
+ */
6930
+ async getResolved(name, opts) {
6931
+ const query = opts?.workspace ? `?workspace=${encodeURIComponent(opts.workspace)}` : "";
6932
+ return this.client.managementRequest(
6933
+ "GET",
6934
+ `${this.base()}/${encodeURIComponent(name)}/resolved${query}`
6935
+ );
6936
+ }
6937
+ };
6938
+ var HttpArchetypes = class extends HttpSchemaBase {
6939
+ constructor() {
6940
+ super(...arguments);
6941
+ this.kind = "archetypes";
6942
+ this.defField = "archetype";
6943
+ }
6944
+ };
6945
+ var HttpElementTypes = class extends HttpSchemaBase {
6946
+ constructor() {
6947
+ super(...arguments);
6948
+ this.kind = "elementtypes";
6949
+ this.defField = "element_type";
6950
+ }
6951
+ };
6952
+
6953
+ // src/mcp.ts
6954
+ var McpClient = class {
6955
+ constructor(transport) {
6956
+ this.transport = transport;
6957
+ }
6958
+ /**
6959
+ * List the tools this MCP server advertises to the calling identity.
6960
+ *
6961
+ * Wraps the `tools/list` JSON-RPC method. The visible set is RLS-scoped to
6962
+ * the authenticated caller (or the anonymous role).
6963
+ *
6964
+ * @returns The advertised tool descriptors.
6965
+ */
6966
+ async listTools() {
6967
+ const result = await this.transport.rpc("tools/list");
6968
+ return { tools: result?.tools ?? [] };
6969
+ }
6970
+ /**
6971
+ * Invoke a tool by name.
6972
+ *
6973
+ * Wraps the `tools/call` JSON-RPC method. The underlying `raisin:Function`
6974
+ * runs RLS-scoped to the calling identity. A domain-level tool failure is
6975
+ * reported via {@link McpCallToolResult.isError} (not thrown); transport and
6976
+ * protocol errors are thrown.
6977
+ *
6978
+ * @param name - Tool name from {@link listTools}.
6979
+ * @param args - Arguments object matching the tool's `inputSchema`.
6980
+ * @returns The tool's content blocks and optional structured content.
6981
+ */
6982
+ async callTool(name, args) {
6983
+ const result = await this.transport.rpc("tools/call", {
6984
+ name,
6985
+ arguments: args ?? {}
6986
+ });
6987
+ return result;
6988
+ }
6989
+ /**
6990
+ * Read a resource by its `raisin://` URI.
6991
+ *
6992
+ * Wraps the `resources/read` JSON-RPC method. JSON-only resources return
6993
+ * their properties as a `text` entry; byte-serving resources return base64
6994
+ * in `blob`.
6995
+ *
6996
+ * @param uri - `raisin://{workspace}/{path}` URI to read.
6997
+ * @returns One content entry per URI read.
6998
+ */
6999
+ async readResource(uri) {
7000
+ const result = await this.transport.rpc("resources/read", {
7001
+ uri
7002
+ });
7003
+ return { contents: result?.contents ?? [] };
7004
+ }
7005
+ /**
7006
+ * Subscribe to change notifications for a resource URI (or URI prefix).
7007
+ *
7008
+ * Wraps the `resources/subscribe` JSON-RPC method, which upgrades to an SSE
7009
+ * stream. The returned {@link McpResourceSubscription} is an async iterable of
7010
+ * {@link McpResourceUpdate}; the server's initial subscription ack is consumed
7011
+ * internally and not yielded. Call `close()` to end the stream.
7012
+ *
7013
+ * @param uri - `raisin://` URI (or prefix) to watch.
7014
+ * @returns A live, closeable subscription.
7015
+ */
7016
+ subscribeResource(uri) {
7017
+ const stream = this.transport.subscribe("resources/subscribe", { uri });
7018
+ return {
7019
+ close: () => stream.close(),
7020
+ async *[Symbol.asyncIterator]() {
7021
+ for await (const frame of stream) {
7022
+ if (frame.method === "notifications/resources/updated") {
7023
+ const params = frame.params ?? {};
7024
+ if (typeof params.uri === "string") {
7025
+ yield { uri: params.uri };
7026
+ }
7027
+ }
7028
+ }
7029
+ }
7030
+ };
7031
+ }
7032
+ };
7033
+
6260
7034
  // src/http-client.ts
6261
7035
  var RaisinHttpClient = class extends import_events3.EventEmitter {
6262
7036
  constructor(baseUrl, options = {}) {
6263
7037
  super();
7038
+ /** Monotonic JSON-RPC id counter for MCP requests. */
7039
+ this._mcpRequestId = 0;
6264
7040
  this._currentUser = null;
6265
7041
  this._uploadManager = null;
6266
7042
  this.baseUrl = baseUrl.replace(/\/$/, "");
@@ -6306,9 +7082,13 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
6306
7082
  body: payload,
6307
7083
  skipAuth: true
6308
7084
  });
6309
- this.authManager.setTokens(response.data);
7085
+ if (response.data.token) {
7086
+ this.authManager.storage.setAccessToken(response.data.token);
7087
+ } else {
7088
+ this.authManager.setTokens(response.data);
7089
+ }
6310
7090
  this._currentUser = {
6311
- userId: "admin",
7091
+ userId: response.data.user_id ?? "admin",
6312
7092
  anonymous: false
6313
7093
  };
6314
7094
  this.emit("authenticated", response.data);
@@ -6952,6 +7732,107 @@ var RaisinHttpClient = class extends import_events3.EventEmitter {
6952
7732
  });
6953
7733
  return response.data;
6954
7734
  }
7735
+ /**
7736
+ * Send one JSON-RPC request to an MCP server endpoint and return its
7737
+ * unwrapped `result`.
7738
+ *
7739
+ * Calls `POST /mcp/{repo}/{branch}/{slug}` carrying a single JSON-RPC 2.0
7740
+ * message. MCP errors are returned in the JSON-RPC envelope (HTTP `200`), so
7741
+ * this method inspects the body's `error` member and throws on failure.
7742
+ *
7743
+ * @internal Used by {@link McpClient} via the transport built in `mcp()`.
7744
+ */
7745
+ async mcpRpc(repository, branch, slug, method, params) {
7746
+ const id = ++this._mcpRequestId;
7747
+ const response = await this.request({
7748
+ method: "POST",
7749
+ path: `/mcp/${repository}/${branch}/${slug}`,
7750
+ body: {
7751
+ jsonrpc: "2.0",
7752
+ id,
7753
+ method,
7754
+ ...params !== void 0 ? { params } : {}
7755
+ }
7756
+ });
7757
+ const frame = response.data;
7758
+ if (frame?.error) {
7759
+ const err = new Error(frame.error.message || `MCP error ${frame.error.code}`);
7760
+ err.code = frame.error.code;
7761
+ err.data = frame.error.data;
7762
+ throw err;
7763
+ }
7764
+ return frame?.result;
7765
+ }
7766
+ /**
7767
+ * Open the `resources/subscribe` SSE stream for an MCP server endpoint.
7768
+ *
7769
+ * Streams JSON-RPC frames (the subscription ack followed by
7770
+ * `notifications/resources/updated` frames) from
7771
+ * `POST /mcp/{repo}/{branch}/{slug}`.
7772
+ *
7773
+ * @internal Used by {@link McpClient} via the transport built in `mcp()`.
7774
+ */
7775
+ mcpSubscribe(repository, branch, slug, method, params) {
7776
+ const headers = {};
7777
+ if (this.authManager.isAuthenticated()) {
7778
+ const token = this.authManager.getAccessToken();
7779
+ if (token) {
7780
+ headers["Authorization"] = `Bearer ${token}`;
7781
+ }
7782
+ }
7783
+ const sse = new SSEClient(
7784
+ `${this.baseUrl}/mcp/${repository}/${branch}/${slug}`,
7785
+ {
7786
+ method: "POST",
7787
+ body: {
7788
+ jsonrpc: "2.0",
7789
+ id: ++this._mcpRequestId,
7790
+ method,
7791
+ params
7792
+ },
7793
+ headers,
7794
+ fetch: this.fetchImpl,
7795
+ reconnect: { enabled: false }
7796
+ }
7797
+ );
7798
+ return {
7799
+ close: () => sse.close(),
7800
+ async *[Symbol.asyncIterator]() {
7801
+ for await (const event of sse) {
7802
+ yield event.data;
7803
+ }
7804
+ }
7805
+ };
7806
+ }
7807
+ /**
7808
+ * Get an {@link McpClient} for a content-declared MCP server.
7809
+ *
7810
+ * @param slug - The `raisin:McpServer` slug (resolved in the `mcp` workspace).
7811
+ * @param branch - Branch to address; defaults to the client's current branch.
7812
+ */
7813
+ mcp(slug, branch) {
7814
+ const repository = this._context.repository;
7815
+ if (!repository) {
7816
+ throw new Error("mcp() requires a repository; call database(name) first");
7817
+ }
7818
+ const effectiveBranch = branch ?? this.getBranch();
7819
+ const transport = {
7820
+ rpc: (m, p) => this.mcpRpc(repository, effectiveBranch, slug, m, p),
7821
+ subscribe: (m, p) => this.mcpSubscribe(repository, effectiveBranch, slug, m, p)
7822
+ };
7823
+ return new McpClient(transport);
7824
+ }
7825
+ /**
7826
+ * Make a management REST request and return the parsed body.
7827
+ *
7828
+ * @internal Used by the HTTP schema-management helpers
7829
+ * ({@link HttpNodeTypes}/{@link HttpArchetypes}/{@link HttpElementTypes}) to
7830
+ * reuse the client's centralized auth, timeout, and base-URL handling.
7831
+ */
7832
+ async managementRequest(method, path, body) {
7833
+ const response = await this.request({ method, path, body });
7834
+ return response.data;
7835
+ }
6955
7836
  /**
6956
7837
  * Get a simplified database interface for SSR
6957
7838
  * This returns an HTTP-based database client
@@ -7039,6 +7920,36 @@ var HttpDatabase = class _HttpDatabase {
7039
7920
  (repo, name, input) => this.client.invokeFunctionSync(repo, name, input)
7040
7921
  );
7041
7922
  }
7923
+ /**
7924
+ * Get an {@link McpClient} for a content-declared MCP server on this database.
7925
+ *
7926
+ * @param slug - The `raisin:McpServer` slug (resolved in the `mcp` workspace).
7927
+ * @returns A client scoped to `{repo}/{branch}/{slug}`, using this database's
7928
+ * branch (falling back to the HTTP client's current branch).
7929
+ */
7930
+ mcp(slug) {
7931
+ const transport = {
7932
+ rpc: (m, p) => this.client.mcpRpc(this.repository, this.schemaBranch(), slug, m, p),
7933
+ subscribe: (m, p) => this.client.mcpSubscribe(this.repository, this.schemaBranch(), slug, m, p)
7934
+ };
7935
+ return new McpClient(transport);
7936
+ }
7937
+ /** Effective branch for management calls. */
7938
+ schemaBranch() {
7939
+ return this.branch ?? this.client.getBranch();
7940
+ }
7941
+ /** NodeType management (get/list/getResolved/create/update/delete/publish/unpublish). */
7942
+ nodeTypes() {
7943
+ return new HttpNodeTypes(this.repository, this.schemaBranch(), this.client);
7944
+ }
7945
+ /** Archetype management (get/list/getResolved/create/update/delete/publish/unpublish). */
7946
+ archetypes() {
7947
+ return new HttpArchetypes(this.repository, this.schemaBranch(), this.client);
7948
+ }
7949
+ /** ElementType management (get/list/getResolved/create/update/delete/publish/unpublish). */
7950
+ elementTypes() {
7951
+ return new HttpElementTypes(this.repository, this.schemaBranch(), this.client);
7952
+ }
7042
7953
  };
7043
7954
  var HttpWorkspaceClient = class {
7044
7955
  constructor(repository, workspace, client) {
@@ -7196,10 +8107,13 @@ var HttpWorkspaceClient = class {
7196
8107
  };
7197
8108
 
7198
8109
  // src/client.ts
7199
- var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8110
+ var _refreshingUser;
8111
+ var _RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7200
8112
  constructor(url, options = {}) {
7201
8113
  super();
7202
8114
  this._currentUser = null;
8115
+ /** In-flight role re-resolve, used to coalesce concurrent refreshCurrentUser() calls. */
8116
+ __privateAdd(this, _refreshingUser, null);
7203
8117
  // Auth state change listeners (Firebase/Supabase pattern)
7204
8118
  this._authListeners = /* @__PURE__ */ new Set();
7205
8119
  this._userChangeListeners = /* @__PURE__ */ new Set();
@@ -7210,23 +8124,43 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7210
8124
  // Reconnection tracking
7211
8125
  this._previousConnectionState = "disconnected" /* Disconnected */;
7212
8126
  this._reconnectedListeners = /* @__PURE__ */ new Set();
8127
+ this._everConnected = false;
8128
+ // Requests queued while the connection is re-establishing.
8129
+ // Flushed after reconnect + re-auth; bounded by MAX_QUEUED_REQUESTS.
8130
+ this._queuedRequests = [];
8131
+ /**
8132
+ * Backoff delays (ms) between subscription-restore retries after reconnect.
8133
+ * @internal - overridable in tests
8134
+ */
8135
+ this._restoreRetryDelaysMs = [1e3, 3e3, 9e3];
7213
8136
  // Upload manager (uses HTTP under the hood)
7214
8137
  this._uploadManager = null;
8138
+ /** In-flight refresh shared by concurrent refreshToken() callers. */
8139
+ this._refreshInFlight = null;
7215
8140
  if (options.logLevel !== void 0) {
7216
8141
  setLogLevel(options.logLevel);
7217
8142
  }
8143
+ const connectionUrl = this.buildConnectionUrl(url, options.repository);
7218
8144
  this.options = {
7219
8145
  requestTimeout: options.requestTimeout ?? 3e4,
7220
- tenantId: options.tenantId ?? this.extractTenantFromUrl(url),
8146
+ tenantId: options.tenantId ?? this.extractTenantFromUrl(connectionUrl),
7221
8147
  defaultBranch: options.defaultBranch ?? "main"
7222
8148
  };
7223
- this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(url);
7224
- this._repository = this.extractRepositoryFromUrl(url);
8149
+ this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(connectionUrl);
8150
+ this._repository = options.repository ?? this.extractRepositoryFromUrl(connectionUrl);
7225
8151
  this._context = {
7226
8152
  tenant_id: this.options.tenantId,
7227
8153
  branch: this.options.defaultBranch
7228
8154
  };
7229
- this.connection = new Connection(url, options.connection);
8155
+ const explicitTenant = options.tenantId ?? (this.extractTenantFromUrl(url) !== "default" ? this.options.tenantId : void 0);
8156
+ const connectionOptions = {
8157
+ ...options.connection,
8158
+ headers: {
8159
+ ...explicitTenant ? { "x-tenant-id": explicitTenant } : {},
8160
+ ...options.connection?.headers
8161
+ }
8162
+ };
8163
+ this.connection = this.createConnection(connectionUrl, connectionOptions);
7230
8164
  this.requestTracker = new RequestTracker({
7231
8165
  defaultTimeout: this.options.requestTimeout
7232
8166
  });
@@ -7236,6 +8170,14 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7236
8170
  );
7237
8171
  this.setupConnectionHandlers();
7238
8172
  }
8173
+ /**
8174
+ * Create the underlying WebSocket connection.
8175
+ * Seam for tests to inject a fake connection.
8176
+ * @internal
8177
+ */
8178
+ createConnection(url, options) {
8179
+ return new Connection(url, options);
8180
+ }
7239
8181
  /**
7240
8182
  * Derive HTTP URL from WebSocket URL
7241
8183
  * ws://host:port/path -> http://host:port
@@ -7251,15 +8193,45 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7251
8193
  return "http://localhost:8081";
7252
8194
  }
7253
8195
  }
8196
+ /**
8197
+ * Build the WebSocket connection URL.
8198
+ *
8199
+ * When a repository option is provided and the URL carries no path
8200
+ * (e.g. `ws://host:8081`), build the tenant-less `/ws/{repository}`
8201
+ * route internally. URLs that already carry a path are used as-is.
8202
+ */
8203
+ buildConnectionUrl(url, repository) {
8204
+ if (!repository) {
8205
+ return url;
8206
+ }
8207
+ try {
8208
+ const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
8209
+ const parsed = new URL(wsUrl);
8210
+ const hasPath = parsed.pathname.split("/").some((p) => p.length > 0);
8211
+ if (hasPath) {
8212
+ return url;
8213
+ }
8214
+ return `${url.replace(/\/+$/, "")}/ws/${repository}`;
8215
+ } catch {
8216
+ return url;
8217
+ }
8218
+ }
7254
8219
  /**
7255
8220
  * Extract repository from URL
7256
- * URL format: raisin://host:port/tenant/repository or ws://host:port/tenant/repository
8221
+ * URL forms: /ws/{repo} (tenant-less, preferred), /sys/{tenant}/{repo}
8222
+ * (operator/multi-tenant), /{tenant}/{repo} (legacy).
7257
8223
  */
7258
8224
  extractRepositoryFromUrl(url) {
7259
8225
  try {
7260
8226
  const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
7261
8227
  const parsed = new URL(wsUrl);
7262
8228
  const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
8229
+ if (parts[0] === "ws") {
8230
+ return parts.length > 1 ? parts[1] : "";
8231
+ }
8232
+ if (parts[0] === "sys") {
8233
+ return parts.length > 2 ? parts[2] : "";
8234
+ }
7263
8235
  return parts.length > 1 ? parts[1] : "";
7264
8236
  } catch {
7265
8237
  return "";
@@ -7267,14 +8239,24 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7267
8239
  }
7268
8240
  /**
7269
8241
  * Extract tenant ID from URL
7270
- * URL format: raisin://host:port/tenant/repository or ws://host:port/tenant/repository
8242
+ * URL forms: /ws/{repo} (tenant-less — no tenant in URL),
8243
+ * /sys/{tenant}/{repo} (canonical), /{tenant}/{repo} (legacy).
8244
+ * When the URL doesn't carry a tenant, fall back to 'default' — the
8245
+ * server's tenant middleware resolves it anyway (production derives the
8246
+ * tenant from the request itself), so an explicit tenantId is optional.
7271
8247
  */
7272
8248
  extractTenantFromUrl(url) {
7273
8249
  try {
7274
8250
  const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
7275
8251
  const parsed = new URL(wsUrl);
7276
8252
  const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
7277
- return parts.length > 0 ? parts[0] : "default";
8253
+ if (parts[0] === "ws") {
8254
+ return "default";
8255
+ }
8256
+ if (parts[0] === "sys") {
8257
+ return parts.length > 1 ? parts[1] : "default";
8258
+ }
8259
+ return parts.length >= 2 ? parts[0] : "default";
7278
8260
  } catch (error) {
7279
8261
  return "default";
7280
8262
  }
@@ -7288,7 +8270,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7288
8270
  });
7289
8271
  this.connection.on("stateChange", (state) => {
7290
8272
  const wasDisconnected = this._previousConnectionState === "disconnected" /* Disconnected */ || this._previousConnectionState === "reconnecting" /* Reconnecting */;
8273
+ if (state === "connected" /* Connected */) {
8274
+ this._everConnected = true;
8275
+ }
7291
8276
  if (state === "disconnected" /* Disconnected */ || state === "closed" /* Closed */) {
8277
+ this._queuedRequests = [];
7292
8278
  this.requestTracker.cancelAll();
7293
8279
  }
7294
8280
  if (state === "connected" /* Connected */ && wasDisconnected && this._previousConnectionState !== "disconnected" /* Disconnected */) {
@@ -7319,8 +8305,13 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7319
8305
  if (isConnectedMessage(message)) {
7320
8306
  this.handleConnectedMessage(message);
7321
8307
  } else if (isEventMessage(message)) {
7322
- logger.debug(`Routing to event handler`);
7323
- this.eventHandler.handleEvent(message);
8308
+ if (message.event_type === "permissions:changed") {
8309
+ logger.info("[permissions:changed] server signaled a role/permission change; re-resolving");
8310
+ void this.refreshCurrentUser();
8311
+ } else {
8312
+ logger.debug(`Routing to event handler`);
8313
+ this.eventHandler.handleEvent(message);
8314
+ }
7324
8315
  } else if (isResponseEnvelope(message)) {
7325
8316
  logger.debug(`Routing to response handler - request_id: ${message.request_id}, status: ${message.status}`);
7326
8317
  this.handleResponse(message);
@@ -7353,19 +8344,36 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7353
8344
  };
7354
8345
  logger.debug(`Anonymous user info stored: ${message.user_id}`);
7355
8346
  this._updateReadyState();
8347
+ this._flushQueuedRequests();
7356
8348
  if (hasActiveSubscriptions) {
7357
8349
  this._restoreSubscriptionsAndNotify();
7358
8350
  }
7359
8351
  } else {
7360
8352
  logger.info(`[handleConnectedMessage] Stored token found, auto-authenticating...`);
7361
- this.autoReauthenticate().then(async () => {
8353
+ this.autoReauthenticate().then(async (authenticated) => {
8354
+ if (authenticated) {
8355
+ this._flushQueuedRequests();
8356
+ } else {
8357
+ this._rejectQueuedRequests(
8358
+ new RaisinAuthError(
8359
+ "Re-authentication failed after reconnect",
8360
+ "AUTH_TOKEN_EXPIRED",
8361
+ 401
8362
+ )
8363
+ );
8364
+ }
7362
8365
  if (hasActiveSubscriptions) {
7363
8366
  await this._restoreSubscriptionsAndNotify();
7364
8367
  }
7365
8368
  }).catch((err) => {
7366
8369
  logger.error(`[handleConnectedMessage] Auto-authenticate failed:`, err);
8370
+ this._rejectQueuedRequests(
8371
+ err instanceof Error ? err : new Error("Re-authentication failed after reconnect")
8372
+ );
7367
8373
  });
7368
8374
  }
8375
+ } else {
8376
+ this._flushQueuedRequests();
7369
8377
  }
7370
8378
  this.emit("connected", {
7371
8379
  connectionId: message.connection_id,
@@ -7374,18 +8382,41 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7374
8382
  });
7375
8383
  }
7376
8384
  /**
7377
- * Restore subscriptions after reconnection and notify listeners
8385
+ * Restore subscriptions after reconnection and notify listeners.
8386
+ *
8387
+ * Retries with backoff (see `_restoreRetryDelaysMs`); on final failure a
8388
+ * `subscription_restore_failed` event is emitted so applications can
8389
+ * resync their state (e.g. reload lists and re-subscribe manually).
7378
8390
  * @internal
7379
8391
  */
7380
8392
  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);
8393
+ const delays = this._restoreRetryDelaysMs;
8394
+ let lastError;
8395
+ for (let attempt = 0; attempt <= delays.length; attempt++) {
8396
+ try {
8397
+ logger.info(
8398
+ `[_restoreSubscriptionsAndNotify] Restoring subscriptions (attempt ${attempt + 1}/${delays.length + 1})...`
8399
+ );
8400
+ await this.eventHandler.restoreSubscriptions({ retry: attempt > 0 });
8401
+ logger.info("[_restoreSubscriptionsAndNotify] Subscriptions restored successfully");
8402
+ this._emitReconnected();
8403
+ return;
8404
+ } catch (error) {
8405
+ lastError = error;
8406
+ logger.error(
8407
+ `[_restoreSubscriptionsAndNotify] Restore attempt ${attempt + 1} failed:`,
8408
+ error
8409
+ );
8410
+ if (attempt < delays.length) {
8411
+ await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
8412
+ }
8413
+ }
7388
8414
  }
8415
+ logger.error("[_restoreSubscriptionsAndNotify] Giving up after retries; events may be stale");
8416
+ this.emit(
8417
+ "subscription_restore_failed",
8418
+ lastError instanceof Error ? lastError : new Error(String(lastError))
8419
+ );
7389
8420
  }
7390
8421
  /**
7391
8422
  * Emit reconnected event to all listeners
@@ -7404,13 +8435,15 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7404
8435
  }
7405
8436
  /**
7406
8437
  * Auto-reauthenticate with stored JWT on reconnection
8438
+ *
8439
+ * @returns true if the session was re-authenticated, false otherwise
7407
8440
  * @internal
7408
8441
  */
7409
8442
  async autoReauthenticate() {
7410
8443
  const token = this.authManager.storage.getAccessToken();
7411
8444
  if (!token) {
7412
8445
  logger.warn("[autoReauthenticate] No stored token");
7413
- return;
8446
+ return false;
7414
8447
  }
7415
8448
  const payload = AuthManager.parseToken(token);
7416
8449
  if (payload?.exp && typeof payload.exp === "number") {
@@ -7422,18 +8455,20 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7422
8455
  this.authManager.clear();
7423
8456
  this._currentUser = null;
7424
8457
  this._emitAuthEvent("SESSION_EXPIRED");
7425
- return;
8458
+ return false;
7426
8459
  }
7427
8460
  const newToken = this.authManager.storage.getAccessToken();
7428
8461
  if (newToken) {
7429
8462
  await this.authenticate({ type: "jwt", token: newToken });
8463
+ return true;
7430
8464
  }
7431
- return;
8465
+ return false;
7432
8466
  }
7433
8467
  }
7434
8468
  try {
7435
8469
  await this.authenticate({ type: "jwt", token });
7436
8470
  logger.info("[autoReauthenticate] Successfully re-authenticated");
8471
+ return true;
7437
8472
  } catch (err) {
7438
8473
  logger.error("[autoReauthenticate] Failed to authenticate:", err);
7439
8474
  const refreshed = await this.refreshToken();
@@ -7441,11 +8476,14 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7441
8476
  const newToken = this.authManager.storage.getAccessToken();
7442
8477
  if (newToken) {
7443
8478
  await this.authenticate({ type: "jwt", token: newToken });
8479
+ return true;
7444
8480
  }
8481
+ return false;
7445
8482
  } else {
7446
8483
  this.authManager.clear();
7447
8484
  this._currentUser = null;
7448
8485
  this._emitAuthEvent("SESSION_EXPIRED");
8486
+ return false;
7449
8487
  }
7450
8488
  }
7451
8489
  }
@@ -7561,18 +8599,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7561
8599
  }
7562
8600
  const url = `${this._httpBaseUrl}/auth/${repository}/login`;
7563
8601
  logger.debug("[loginWithEmail] POST", url);
7564
- const response = await fetch(url, {
8602
+ const response = await this._fetchWithTimeout(url, {
7565
8603
  method: "POST",
7566
8604
  headers: { "Content-Type": "application/json" },
7567
8605
  body: JSON.stringify({ email, password })
7568
8606
  });
7569
8607
  if (!response.ok) {
7570
8608
  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;
8609
+ throw new RaisinAuthError(
8610
+ error.message || "Login failed",
8611
+ error.code || "LOGIN_FAILED",
8612
+ response.status
8613
+ );
7576
8614
  }
7577
8615
  const tokens = await response.json();
7578
8616
  logger.info("[loginWithEmail] HTTP login successful, user_id:", tokens.identity.id);
@@ -7601,11 +8639,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7601
8639
  }
7602
8640
  this._currentUser = {
7603
8641
  userId: user.id,
7604
- roles: [],
8642
+ roles: this._currentUser?.roles ?? [],
7605
8643
  anonymous: false,
7606
8644
  node: userNode ?? void 0
7607
8645
  };
7608
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
8646
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7609
8647
  if (expiresInMs > 0) {
7610
8648
  this.scheduleAutoRefresh(expiresInMs);
7611
8649
  }
@@ -7629,18 +8667,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7629
8667
  async registerWithEmail(email, password, repository, displayName) {
7630
8668
  logger.info("[registerWithEmail] Starting registration...");
7631
8669
  const url = `${this._httpBaseUrl}/auth/${repository}/register`;
7632
- const response = await fetch(url, {
8670
+ const response = await this._fetchWithTimeout(url, {
7633
8671
  method: "POST",
7634
8672
  headers: { "Content-Type": "application/json" },
7635
8673
  body: JSON.stringify({ email, password, display_name: displayName })
7636
8674
  });
7637
8675
  if (!response.ok) {
7638
8676
  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;
8677
+ throw new RaisinAuthError(
8678
+ error.message || "Registration failed",
8679
+ error.code || "REGISTRATION_FAILED",
8680
+ response.status
8681
+ );
7644
8682
  }
7645
8683
  const tokens = await response.json();
7646
8684
  logger.info("[registerWithEmail] Registration successful, user_id:", tokens.identity.id);
@@ -7667,11 +8705,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7667
8705
  }
7668
8706
  this._currentUser = {
7669
8707
  userId: user.id,
7670
- roles: [],
8708
+ roles: this._currentUser?.roles ?? [],
7671
8709
  anonymous: false,
7672
8710
  node: userNode ?? void 0
7673
8711
  };
7674
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
8712
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7675
8713
  if (expiresInMs > 0) {
7676
8714
  this.scheduleAutoRefresh(expiresInMs);
7677
8715
  }
@@ -7763,39 +8801,18 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7763
8801
  if (payload && payload.exp && typeof payload.exp === "number") {
7764
8802
  if (payload.exp * 1e3 < Date.now()) {
7765
8803
  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 {
8804
+ if (!this.authManager.storage.getRefreshToken()) {
7795
8805
  logger.warn("[initSession] No refresh token, clearing session");
7796
8806
  this.authManager.clear();
7797
8807
  return null;
7798
8808
  }
8809
+ const refreshed = await this.refreshToken();
8810
+ if (!refreshed) {
8811
+ logger.warn("[initSession] Token refresh failed, clearing session");
8812
+ this.authManager.clear();
8813
+ return null;
8814
+ }
8815
+ logger.info("[initSession] Token refreshed successfully");
7799
8816
  }
7800
8817
  }
7801
8818
  try {
@@ -7829,7 +8846,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7829
8846
  }
7830
8847
  this._currentUser = {
7831
8848
  userId: user.id,
7832
- roles: [],
8849
+ roles: this._currentUser?.roles ?? [],
7833
8850
  anonymous: false,
7834
8851
  node: userNode ?? void 0
7835
8852
  };
@@ -7885,6 +8902,16 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7885
8902
  * @throws Error if no refresh token is stored or refresh fails
7886
8903
  */
7887
8904
  async refreshToken() {
8905
+ if (this._refreshInFlight) {
8906
+ logger.debug("[refreshToken] Refresh already in flight, awaiting it");
8907
+ return this._refreshInFlight;
8908
+ }
8909
+ this._refreshInFlight = this._refreshToken().finally(() => {
8910
+ this._refreshInFlight = null;
8911
+ });
8912
+ return this._refreshInFlight;
8913
+ }
8914
+ async _refreshToken() {
7888
8915
  const refreshToken = this.authManager.storage.getRefreshToken();
7889
8916
  if (!refreshToken) {
7890
8917
  logger.warn("[refreshToken] No refresh token available");
@@ -7893,7 +8920,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7893
8920
  try {
7894
8921
  logger.info("[refreshToken] Refreshing access token...");
7895
8922
  const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
7896
- const response = await fetch(url, {
8923
+ const response = await this._fetchWithTimeout(url, {
7897
8924
  method: "POST",
7898
8925
  headers: { "Content-Type": "application/json" },
7899
8926
  body: JSON.stringify({ refresh_token: refreshToken })
@@ -7901,6 +8928,11 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7901
8928
  if (!response.ok) {
7902
8929
  const errorText = await response.text();
7903
8930
  logger.error("[refreshToken] Refresh failed:", response.status, errorText);
8931
+ if (/TOKEN_REUSE_DETECTED|TOKEN_FAMILY_MISMATCH/.test(errorText)) {
8932
+ logger.error(
8933
+ "[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."
8934
+ );
8935
+ }
7904
8936
  if (response.status === 401 || response.status === 403) {
7905
8937
  this.authManager.clear();
7906
8938
  this._currentUser = null;
@@ -7923,7 +8955,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7923
8955
  if (this.authManager.storage instanceof LocalStorageTokenStorage) {
7924
8956
  this.authManager.storage.setUser(user);
7925
8957
  }
7926
- const expiresInMs = tokens.expires_at * 1e3 - Date.now() - 5 * 60 * 1e3;
8958
+ const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
7927
8959
  if (expiresInMs > 0) {
7928
8960
  this.scheduleAutoRefresh(expiresInMs);
7929
8961
  }
@@ -7936,6 +8968,53 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7936
8968
  return null;
7937
8969
  }
7938
8970
  }
8971
+ /**
8972
+ * Fetch with a timeout derived from `requestTimeout`.
8973
+ *
8974
+ * Aborts the request after `requestTimeout` ms and rejects with a
8975
+ * `RaisinTimeoutError`. If the caller provides its own AbortSignal in
8976
+ * `init.signal`, it is respected: an external abort rejects with the
8977
+ * original AbortError instead of a timeout error.
8978
+ * @internal
8979
+ */
8980
+ async _fetchWithTimeout(url, init = {}) {
8981
+ const timeoutMs = this.options.requestTimeout;
8982
+ const controller = new AbortController();
8983
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
8984
+ const externalSignal = init.signal ?? void 0;
8985
+ if (externalSignal) {
8986
+ if (externalSignal.aborted) {
8987
+ controller.abort();
8988
+ } else {
8989
+ externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
8990
+ }
8991
+ }
8992
+ try {
8993
+ return await fetch(url, { ...init, signal: controller.signal });
8994
+ } catch (error) {
8995
+ if (error instanceof Error && error.name === "AbortError" && !externalSignal?.aborted) {
8996
+ throw new RaisinTimeoutError(
8997
+ `Request to ${url} timed out after ${timeoutMs}ms`,
8998
+ "REQUEST_TIMEOUT",
8999
+ timeoutMs
9000
+ );
9001
+ }
9002
+ throw error;
9003
+ } finally {
9004
+ clearTimeout(timer);
9005
+ }
9006
+ }
9007
+ /**
9008
+ * Normalize a token `expires_at` value to epoch milliseconds.
9009
+ *
9010
+ * Server endpoints are inconsistent: some return seconds, others
9011
+ * milliseconds. Anything above 1e11 can't be a seconds-epoch (year 5138),
9012
+ * so treat it as milliseconds.
9013
+ * @internal
9014
+ */
9015
+ static expiresAtToMs(expiresAt) {
9016
+ return expiresAt > 1e11 ? expiresAt : expiresAt * 1e3;
9017
+ }
7939
9018
  /**
7940
9019
  * Schedule automatic token refresh
7941
9020
  * @internal
@@ -7944,11 +9023,12 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7944
9023
  if (this._refreshTimer) {
7945
9024
  clearTimeout(this._refreshTimer);
7946
9025
  }
7947
- logger.debug(`[scheduleAutoRefresh] Scheduling refresh in ${Math.round(delayMs / 1e3 / 60)} minutes`);
9026
+ const clamped = Math.min(Math.max(delayMs, 3e4), 2147483647);
9027
+ logger.debug(`[scheduleAutoRefresh] Scheduling refresh in ${Math.round(clamped / 1e3 / 60)} minutes`);
7948
9028
  this._refreshTimer = setTimeout(async () => {
7949
9029
  logger.info("[autoRefresh] Auto-refreshing token...");
7950
9030
  await this.refreshToken();
7951
- }, delayMs);
9031
+ }, clamped);
7952
9032
  }
7953
9033
  /**
7954
9034
  * Get a database/repository interface
@@ -7972,10 +9052,26 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7972
9052
  }
7973
9053
  /**
7974
9054
  * Send a request to the server
9055
+ *
9056
+ * If the connection is temporarily down (connecting/reconnecting, or
9057
+ * disconnected with auto-reconnect enabled), the request is queued and
9058
+ * flushed once the connection is re-established and re-authenticated.
9059
+ * Queued requests are still subject to their own request timeout, and the
9060
+ * queue is bounded (overflow rejects immediately).
7975
9061
  */
7976
9062
  async sendRequestInternal(payload, requestType, contextOverride, requestOptions) {
7977
- if (!this.connection.isConnected()) {
7978
- throw new Error("Not connected to server");
9063
+ const connected = this.connection.isConnected();
9064
+ if (!connected) {
9065
+ const state = this.connection.getState();
9066
+ const willReconnect = state === "connecting" /* Connecting */ || state === "reconnecting" /* Reconnecting */ || state === "disconnected" /* Disconnected */ && this._everConnected && this.connection.isAutoReconnectEnabled();
9067
+ if (!willReconnect) {
9068
+ throw new Error("Not connected to server");
9069
+ }
9070
+ if (this._queuedRequests.length >= _RaisinClient.MAX_QUEUED_REQUESTS) {
9071
+ throw new Error(
9072
+ `Request queue is full (${_RaisinClient.MAX_QUEUED_REQUESTS} requests pending while reconnecting)`
9073
+ );
9074
+ }
7979
9075
  }
7980
9076
  const requestId = this.requestTracker.generateRequestId();
7981
9077
  const context = contextOverride || this._context;
@@ -7994,8 +9090,15 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
7994
9090
  try {
7995
9091
  const encoded = encodeMessage(request);
7996
9092
  logger.debug(`Encoded request - ID: ${requestId}, type: ${requestType}, size: ${encoded.length} bytes`);
7997
- this.connection.send(encoded);
7998
- logger.debug(`Request sent - ID: ${requestId}`);
9093
+ if (connected) {
9094
+ this.connection.send(encoded);
9095
+ logger.debug(`Request sent - ID: ${requestId}`);
9096
+ } else {
9097
+ this._queuedRequests.push({ requestId, encoded });
9098
+ logger.debug(
9099
+ `Request queued while reconnecting - ID: ${requestId}, queue size: ${this._queuedRequests.length}`
9100
+ );
9101
+ }
7999
9102
  } catch (error) {
8000
9103
  logger.error(`Failed to send request - ID: ${requestId}:`, error);
8001
9104
  this.requestTracker.rejectRequest(
@@ -8006,6 +9109,42 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8006
9109
  }
8007
9110
  return responsePromise;
8008
9111
  }
9112
+ /**
9113
+ * Flush requests queued while the connection was re-establishing.
9114
+ * Called after the connection is connected and (re-)authenticated.
9115
+ * @internal
9116
+ */
9117
+ _flushQueuedRequests() {
9118
+ if (this._queuedRequests.length === 0) return;
9119
+ const queued = this._queuedRequests;
9120
+ this._queuedRequests = [];
9121
+ logger.info(`[_flushQueuedRequests] Flushing ${queued.length} queued request(s)`);
9122
+ for (const { requestId, encoded } of queued) {
9123
+ if (!this.requestTracker.hasPendingRequest(requestId)) continue;
9124
+ try {
9125
+ this.connection.send(encoded);
9126
+ } catch (error) {
9127
+ this.requestTracker.rejectRequest(
9128
+ requestId,
9129
+ error instanceof Error ? error : new Error("Failed to send queued request")
9130
+ );
9131
+ }
9132
+ }
9133
+ }
9134
+ /**
9135
+ * Reject all requests queued while the connection was re-establishing
9136
+ * (e.g. re-authentication failed after reconnect).
9137
+ * @internal
9138
+ */
9139
+ _rejectQueuedRequests(error) {
9140
+ if (this._queuedRequests.length === 0) return;
9141
+ const queued = this._queuedRequests;
9142
+ this._queuedRequests = [];
9143
+ logger.warn(`[_rejectQueuedRequests] Rejecting ${queued.length} queued request(s): ${error.message}`);
9144
+ for (const { requestId } of queued) {
9145
+ this.requestTracker.rejectRequest(requestId, error);
9146
+ }
9147
+ }
8009
9148
  /**
8010
9149
  * Get the HTTP base URL derived from the WebSocket URL
8011
9150
  */
@@ -8083,6 +9222,44 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8083
9222
  getCurrentUser() {
8084
9223
  return this._currentUser;
8085
9224
  }
9225
+ /**
9226
+ * Re-resolve the current user's EFFECTIVE roles from the server and refresh
9227
+ * `getCurrentUser()`.
9228
+ *
9229
+ * Roles are deliberately not embedded in the JWT (they are resolved live
9230
+ * server-side), so when an admin changes a user's roles/groups the token does
9231
+ * not change — the client must re-ask. This re-runs the WS JWT auth handshake
9232
+ * (which returns freshly resolved effective roles), updates `_currentUser`, and
9233
+ * emits `USER_UPDATED` so role-gated UI can react without a reload.
9234
+ *
9235
+ * Called automatically when the server pushes `permissions:changed` after an
9236
+ * access-control write, and exposed for on-demand refresh. Safe no-op when not
9237
+ * authenticated.
9238
+ */
9239
+ async refreshCurrentUser() {
9240
+ if (__privateGet(this, _refreshingUser)) return __privateGet(this, _refreshingUser);
9241
+ const token = this.authManager.storage.getAccessToken();
9242
+ if (!token) return;
9243
+ __privateSet(this, _refreshingUser, (async () => {
9244
+ const before = JSON.stringify(this._currentUser?.roles ?? null);
9245
+ const prevNode = this._currentUser?.node;
9246
+ try {
9247
+ await this.authenticate({ type: "jwt", token });
9248
+ if (this._currentUser && prevNode && !this._currentUser.node) {
9249
+ this._currentUser.node = prevNode;
9250
+ }
9251
+ const after = JSON.stringify(this._currentUser?.roles ?? null);
9252
+ if (after !== before) {
9253
+ this._emitAuthEvent("USER_UPDATED");
9254
+ }
9255
+ } catch (err) {
9256
+ logger.warn("[refreshCurrentUser] role re-resolve failed:", err);
9257
+ } finally {
9258
+ __privateSet(this, _refreshingUser, null);
9259
+ }
9260
+ })());
9261
+ return __privateGet(this, _refreshingUser);
9262
+ }
8086
9263
  /**
8087
9264
  * Get current user ID
8088
9265
  *
@@ -8291,7 +9468,10 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8291
9468
  _emitAuthEvent(event) {
8292
9469
  const session = {
8293
9470
  user: this.getStoredUser(),
8294
- accessToken: this.authManager.storage.getAccessToken()
9471
+ accessToken: this.authManager.storage.getAccessToken(),
9472
+ // Surface effective roles so role-gated consumers can react to
9473
+ // SIGNED_IN / USER_UPDATED without calling getCurrentUser() themselves.
9474
+ roles: this._currentUser?.roles ?? null
8295
9475
  };
8296
9476
  const change = { event, session };
8297
9477
  for (const callback of this._authListeners) {
@@ -8331,6 +9511,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8331
9511
  */
8332
9512
  async _subscribeToUserHome(repository, userHome) {
8333
9513
  await this._unsubscribeFromUserHome();
9514
+ userHome = normalizeHomePath(userHome) ?? userHome;
8334
9515
  try {
8335
9516
  const db = this.database(repository);
8336
9517
  const pathParts = userHome.split("/").filter((p) => p.length > 0);
@@ -8630,7 +9811,7 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8630
9811
  const branch = options.branch ?? this._context.branch ?? "main";
8631
9812
  const pathWithProperty = options.propertyPath ? `${options.path}@${options.propertyPath}` : options.path;
8632
9813
  const endpoint = `/api/repository/${options.repository}/${branch}/head/${options.workspace}${pathWithProperty}/raisin:sign`;
8633
- const response = await fetch(`${this._httpBaseUrl}${endpoint}`, {
9814
+ const response = await this._fetchWithTimeout(`${this._httpBaseUrl}${endpoint}`, {
8634
9815
  method: "POST",
8635
9816
  headers: {
8636
9817
  "Content-Type": "application/json",
@@ -8685,6 +9866,10 @@ var RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
8685
9866
  return _RaisinClient.forSSR(baseUrl, options);
8686
9867
  }
8687
9868
  };
9869
+ _refreshingUser = new WeakMap();
9870
+ /** Hard cap on requests held while reconnecting; overflow rejects immediately. */
9871
+ _RaisinClient.MAX_QUEUED_REQUESTS = 100;
9872
+ var RaisinClient = _RaisinClient;
8688
9873
 
8689
9874
  // src/node-builder.ts
8690
9875
  var NodeBuilder = class _NodeBuilder {
@@ -8772,7 +9957,12 @@ var NodeBuilder = class _NodeBuilder {
8772
9957
  return this;
8773
9958
  }
8774
9959
  /**
8775
- * Set the order key
9960
+ * Set the order key.
9961
+ *
9962
+ * @deprecated The server owns editorial ordering: the order key is assigned by
9963
+ * the ordering index on write, so any value set here is overwritten. To place a
9964
+ * node, create it (it appends to the end of its parent) then call
9965
+ * `nodes.reorder()`, `nodes.moveChildBefore()` or `nodes.moveChildAfter()`.
8776
9966
  */
8777
9967
  orderKey(orderKey) {
8778
9968
  this.node.order_key = orderKey;
@@ -8852,10 +10042,10 @@ var NodeBuilder = class _NodeBuilder {
8852
10042
  if (!this.node.node_type) {
8853
10043
  throw new Error("Node type is required");
8854
10044
  }
10045
+ if (this.node.order_key === void 0) this.node.order_key = "";
8855
10046
  if (!this.node.children) this.node.children = [];
8856
10047
  if (!this.node.relations) this.node.relations = [];
8857
10048
  if (!this.node.properties) this.node.properties = {};
8858
- if (!this.node.order_key) this.node.order_key = "a";
8859
10049
  if (typeof this.node.version !== "number") this.node.version = 1;
8860
10050
  return this.node;
8861
10051
  }
@@ -8937,8 +10127,8 @@ var NodeHelpers = class {
8937
10127
  node_type: "MockType",
8938
10128
  properties: {},
8939
10129
  children: [],
10130
+ order_key: "",
8940
10131
  relations: [],
8941
- order_key: "a",
8942
10132
  version: 1,
8943
10133
  ...overrides
8944
10134
  };
@@ -9019,6 +10209,29 @@ function isTerminalEvent(event) {
9019
10209
  return event.type === "flow_completed" || event.type === "flow_failed";
9020
10210
  }
9021
10211
 
10212
+ // src/types/flow-definition.ts
10213
+ function getRefDisplayName(ref) {
10214
+ const path = ref["raisin:path"] || ref["raisin:ref"];
10215
+ const segments = path.split("/").filter(Boolean);
10216
+ return segments[segments.length - 1] || path;
10217
+ }
10218
+ function getRefPath(ref) {
10219
+ return ref["raisin:path"] || ref["raisin:ref"];
10220
+ }
10221
+ var RETRY_STRATEGIES = {
10222
+ none: { max_retries: 0, base_delay_ms: 0, max_delay_ms: 0 },
10223
+ quick: { max_retries: 3, base_delay_ms: 1e3, max_delay_ms: 1e4 },
10224
+ standard: { max_retries: 5, base_delay_ms: 2e3, max_delay_ms: 6e4 },
10225
+ aggressive: { max_retries: 10, base_delay_ms: 5e3, max_delay_ms: 12e4 },
10226
+ llm: { max_retries: 5, base_delay_ms: 1e4, max_delay_ms: 12e4 }
10227
+ };
10228
+ function isFlowDefinitionStep(node) {
10229
+ return node.node_type === "raisin:FlowStep";
10230
+ }
10231
+ function isFlowDefinitionContainer(node) {
10232
+ return node.node_type === "raisin:FlowContainer";
10233
+ }
10234
+
9022
10235
  // src/utils/plan-projection.ts
9023
10236
  function asRecord(value) {
9024
10237
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
@@ -9047,6 +10260,15 @@ function parsePlanTasks(rawTasks) {
9047
10260
  };
9048
10261
  }).filter((task) => task !== null);
9049
10262
  }
10263
+ var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled"]);
10264
+ var TERMINAL_PLAN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
10265
+ function setPlanStatus(plan, nextStatus) {
10266
+ if (!nextStatus) return;
10267
+ if (TERMINAL_PLAN_STATUSES.has(plan.status) && !TERMINAL_PLAN_STATUSES.has(nextStatus)) {
10268
+ return;
10269
+ }
10270
+ plan.status = nextStatus;
10271
+ }
9050
10272
  function upsertTask(plan, nextTask) {
9051
10273
  const existingIndex = plan.tasks.findIndex(
9052
10274
  (task) => nextTask.taskId && task.taskId === nextTask.taskId || !nextTask.taskId && task.title === nextTask.title
@@ -9055,8 +10277,12 @@ function upsertTask(plan, nextTask) {
9055
10277
  plan.tasks.push(nextTask);
9056
10278
  return;
9057
10279
  }
10280
+ const existing = plan.tasks[existingIndex];
10281
+ if (TERMINAL_TASK_STATUSES.has(existing.status) && !TERMINAL_TASK_STATUSES.has(nextTask.status)) {
10282
+ return;
10283
+ }
9058
10284
  plan.tasks[existingIndex] = {
9059
- ...plan.tasks[existingIndex],
10285
+ ...existing,
9060
10286
  ...nextTask
9061
10287
  };
9062
10288
  }
@@ -9100,7 +10326,7 @@ function projectPlansFromMessages(messages) {
9100
10326
  plan.planPath = planPath ?? plan.planPath;
9101
10327
  plan.planId = planId ?? plan.planId;
9102
10328
  plan.title = asString(data.title) || plan.title;
9103
- plan.status = asString(data.status) || plan.status;
10329
+ setPlanStatus(plan, asString(data.status));
9104
10330
  plan.requiresApproval = Boolean(data.requires_approval) || plan.status === "pending_approval";
9105
10331
  plan.sourceMessagePath = message.path ?? plan.sourceMessagePath;
9106
10332
  const parsedTasks = parsePlanTasks(data.tasks);
@@ -9116,7 +10342,7 @@ function projectPlansFromMessages(messages) {
9116
10342
  const plan = ensurePlan(plans, key, "Plan", timestamp);
9117
10343
  plan.planPath = planPath ?? plan.planPath;
9118
10344
  plan.planId = planId ?? plan.planId;
9119
- plan.status = asString(data.plan_status) || plan.status;
10345
+ setPlanStatus(plan, asString(data.plan_status));
9120
10346
  plan.requiresApproval = plan.status === "pending_approval";
9121
10347
  const taskUpdate = {
9122
10348
  taskId: asString(data.task_id),
@@ -9143,6 +10369,12 @@ function projectPlansFromMessages(messages) {
9143
10369
  }
9144
10370
  }
9145
10371
  }
10372
+ for (const plan of plans.values()) {
10373
+ if (!TERMINAL_PLAN_STATUSES.has(plan.status) && plan.tasks.length > 0 && plan.tasks.every((task) => TERMINAL_TASK_STATUSES.has(task.status))) {
10374
+ plan.status = "completed";
10375
+ plan.requiresApproval = false;
10376
+ }
10377
+ }
9146
10378
  return [...plans.values()].sort((a, b) => {
9147
10379
  const aTime = Date.parse(a.updatedAt || "");
9148
10380
  const bTime = Date.parse(b.updatedAt || "");
@@ -9357,10 +10589,12 @@ var ConversationStore = class {
9357
10589
  }
9358
10590
  switch (event.type) {
9359
10591
  case "text_chunk":
10592
+ this.markStreamingActivity();
9360
10593
  this._streamingText += event.text;
9361
10594
  this.notify();
9362
10595
  break;
9363
10596
  case "thought_chunk":
10597
+ this.markStreamingActivity();
9364
10598
  this.notify();
9365
10599
  break;
9366
10600
  case "assistant_message":
@@ -9369,6 +10603,7 @@ var ConversationStore = class {
9369
10603
  this.notify();
9370
10604
  break;
9371
10605
  case "tool_call_started": {
10606
+ this.markStreamingActivity();
9372
10607
  logger.debug("[ConversationStore] Tool started:", event.functionName);
9373
10608
  const info = {
9374
10609
  id: event.toolCallId,
@@ -9381,6 +10616,7 @@ var ConversationStore = class {
9381
10616
  break;
9382
10617
  }
9383
10618
  case "tool_call_completed": {
10619
+ this.markStreamingActivity();
9384
10620
  logger.debug("[ConversationStore] Tool completed:", event.toolCallId);
9385
10621
  const idx = this._activeToolCalls.findIndex((tc) => tc.id === event.toolCallId);
9386
10622
  if (idx >= 0) {
@@ -9455,6 +10691,7 @@ var ConversationStore = class {
9455
10691
  logger.error("[ConversationStore] Turn failed", event.error);
9456
10692
  this._error = event.error;
9457
10693
  this._isStreaming = false;
10694
+ this._isWaiting = false;
9458
10695
  this._streamingText = "";
9459
10696
  this._activeToolCalls = [];
9460
10697
  this.clearStreamingTimer();
@@ -9492,6 +10729,26 @@ var ConversationStore = class {
9492
10729
  }
9493
10730
  }
9494
10731
  }
10732
+ /**
10733
+ * Agent activity observed while the store thought the turn was over.
10734
+ *
10735
+ * This happens when execution continues WITHOUT a user message — plan
10736
+ * approval in approve_then_auto / step_by_step (approvePlan() returns an
10737
+ * enqueue receipt and the agent starts executing), or agent-initiated
10738
+ * follow-ups. The events arrive over the persistent live subscription
10739
+ * (no history replay), so activity is always "now": re-enter streaming
10740
+ * state so UIs show progress and the end-of-turn handling (done/waiting
10741
+ * → deferred reload) runs again for this continuation.
10742
+ */
10743
+ markStreamingActivity() {
10744
+ if (this._isStreaming) return;
10745
+ logger.debug("[ConversationStore] Agent activity while idle \u2014 re-entering streaming state");
10746
+ this._isStreaming = true;
10747
+ this._isWaiting = false;
10748
+ this._streamingText = "";
10749
+ this.resetStreamingTimer();
10750
+ this.startWatchdog();
10751
+ }
9495
10752
  /**
9496
10753
  * If accumulated streaming text hasn't been captured in an assistant_message,
9497
10754
  * synthesize one.
@@ -9725,6 +10982,14 @@ var ConversationStore = class {
9725
10982
  };
9726
10983
 
9727
10984
  // src/stores/conversation-list-store.ts
10985
+ function buildChatsSubscriptionFilters(userHome) {
10986
+ const home = normalizeHomePath(userHome) ?? userHome;
10987
+ return {
10988
+ workspace: "raisin:access_control",
10989
+ path: `${home}/inbox/chats/**`,
10990
+ event_types: ["node:created", "node:updated"]
10991
+ };
10992
+ }
9728
10993
  var ConversationListStore = class {
9729
10994
  constructor(options) {
9730
10995
  this.subscribers = /* @__PURE__ */ new Set();
@@ -9870,13 +11135,8 @@ var ConversationListStore = class {
9870
11135
  const rows = result.rows;
9871
11136
  const userHome = rows?.[0]?.home;
9872
11137
  if (!userHome) return;
9873
- const chatsPath = `${userHome}/inbox/chats`;
9874
11138
  this._realtimeSubscription = await events.subscribe(
9875
- {
9876
- workspace: "raisin:access_control",
9877
- path: chatsPath,
9878
- event_types: ["node:created", "node:updated"]
9879
- },
11139
+ buildChatsSubscriptionFilters(userHome),
9880
11140
  async (event) => {
9881
11141
  const eventData = event;
9882
11142
  const nodeId = eventData.payload?.node_id;
@@ -9982,7 +11242,7 @@ function useConversation(react, options) {
9982
11242
  var INITIAL_LIST_SNAPSHOT = {
9983
11243
  conversations: [],
9984
11244
  totalUnreadCount: 0,
9985
- isLoading: false,
11245
+ isLoading: true,
9986
11246
  error: null
9987
11247
  };
9988
11248
  function useConversationList(react, options) {
@@ -10860,6 +12120,160 @@ function createRaisinReact(react) {
10860
12120
  useFlow: useFlow2
10861
12121
  };
10862
12122
  }
12123
+
12124
+ // src/integrations/vue/vue-auth.ts
12125
+ function createUseAuth2(vue) {
12126
+ return function useAuth(client) {
12127
+ const adapter = createAuthAdapter(client);
12128
+ const snapshot = vue.ref(adapter.getSnapshot());
12129
+ const unsubscribe = adapter.subscribe((s) => {
12130
+ snapshot.value = s;
12131
+ });
12132
+ vue.onUnmounted(() => {
12133
+ unsubscribe();
12134
+ adapter.destroy();
12135
+ });
12136
+ return {
12137
+ user: vue.computed(() => snapshot.value.user),
12138
+ isAuthenticated: vue.computed(() => snapshot.value.isAuthenticated),
12139
+ isLoading: vue.computed(() => snapshot.value.isLoading),
12140
+ login: adapter.login,
12141
+ register: adapter.register,
12142
+ logout: adapter.logout,
12143
+ initSession: adapter.initSession
12144
+ };
12145
+ };
12146
+ }
12147
+
12148
+ // src/integrations/vue/vue-connection.ts
12149
+ function createUseConnection2(vue) {
12150
+ return function useConnection(client) {
12151
+ const adapter = createConnectionAdapter(client);
12152
+ const snapshot = vue.ref(adapter.getSnapshot());
12153
+ const unsubscribe = adapter.subscribe((s) => {
12154
+ snapshot.value = s;
12155
+ });
12156
+ vue.onUnmounted(() => {
12157
+ unsubscribe();
12158
+ adapter.destroy();
12159
+ });
12160
+ return {
12161
+ state: vue.computed(() => snapshot.value.state),
12162
+ isConnected: vue.computed(() => snapshot.value.isConnected),
12163
+ isReady: vue.computed(() => snapshot.value.isReady),
12164
+ connect: adapter.connect,
12165
+ disconnect: adapter.disconnect
12166
+ };
12167
+ };
12168
+ }
12169
+
12170
+ // src/integrations/vue/vue-sql.ts
12171
+ function createUseSql2(vue) {
12172
+ return function useSql(database, sql, params, options) {
12173
+ const adapter = createSqlAdapter(database, sql, params, options);
12174
+ const snapshot = vue.ref(adapter.getSnapshot());
12175
+ const unsubscribe = adapter.subscribe((s) => {
12176
+ snapshot.value = s;
12177
+ });
12178
+ vue.onUnmounted(() => {
12179
+ unsubscribe();
12180
+ adapter.destroy();
12181
+ });
12182
+ return {
12183
+ data: vue.computed(() => snapshot.value.data),
12184
+ isLoading: vue.computed(() => snapshot.value.isLoading),
12185
+ error: vue.computed(() => snapshot.value.error),
12186
+ refetch: adapter.refetch
12187
+ };
12188
+ };
12189
+ }
12190
+
12191
+ // src/integrations/vue/vue-subscription.ts
12192
+ function createUseSubscription2(vue) {
12193
+ return function useSubscription(database, filters, callback) {
12194
+ const adapter = createSubscriptionAdapter(database, filters, callback);
12195
+ vue.onUnmounted(() => adapter.destroy());
12196
+ return {
12197
+ unsubscribe: () => adapter.destroy()
12198
+ };
12199
+ };
12200
+ }
12201
+
12202
+ // src/integrations/vue/vue-conversation.ts
12203
+ function createUseConversation(vue) {
12204
+ return function useConversation2(options) {
12205
+ const store = new ConversationStore(options);
12206
+ const snapshot = vue.ref(store.getSnapshot());
12207
+ const unsubscribe = store.subscribe((s) => {
12208
+ snapshot.value = s;
12209
+ });
12210
+ vue.onUnmounted(() => {
12211
+ unsubscribe();
12212
+ store.destroy();
12213
+ });
12214
+ return {
12215
+ conversation: vue.computed(() => snapshot.value.conversation),
12216
+ messages: vue.computed(() => snapshot.value.messages),
12217
+ isStreaming: vue.computed(() => snapshot.value.isStreaming),
12218
+ isWaiting: vue.computed(() => snapshot.value.isWaiting),
12219
+ streamingText: vue.computed(() => snapshot.value.streamingText),
12220
+ error: vue.computed(() => snapshot.value.error),
12221
+ activeToolCalls: vue.computed(() => snapshot.value.activeToolCalls),
12222
+ plans: vue.computed(() => snapshot.value.plans),
12223
+ isLoading: vue.computed(() => snapshot.value.isLoading),
12224
+ conversationPath: vue.computed(() => snapshot.value.conversationPath),
12225
+ sendMessage: (content) => store.sendMessage(content),
12226
+ approvePlan: async (planPath) => {
12227
+ await store.approvePlan(planPath);
12228
+ },
12229
+ rejectPlan: async (planPath, feedback) => {
12230
+ await store.rejectPlan(planPath, feedback);
12231
+ },
12232
+ stop: () => store.stop(),
12233
+ loadMessages: () => store.loadMessages(),
12234
+ store
12235
+ };
12236
+ };
12237
+ }
12238
+ function createUseConversationList(vue) {
12239
+ return function useConversationList2(options) {
12240
+ const store = new ConversationListStore(options);
12241
+ const snapshot = vue.ref(store.getSnapshot());
12242
+ const unsubscribe = store.subscribe((s) => {
12243
+ snapshot.value = s;
12244
+ });
12245
+ store.load().catch((err) => {
12246
+ logger.error("[useConversationList] Initial load failed:", err);
12247
+ });
12248
+ vue.onUnmounted(() => {
12249
+ unsubscribe();
12250
+ store.destroy();
12251
+ });
12252
+ return {
12253
+ conversations: vue.computed(() => snapshot.value.conversations),
12254
+ totalUnreadCount: vue.computed(() => snapshot.value.totalUnreadCount),
12255
+ isLoading: vue.computed(() => snapshot.value.isLoading),
12256
+ error: vue.computed(() => snapshot.value.error),
12257
+ createConversation: (opts) => store.createConversation(opts),
12258
+ deleteConversation: (conversationPath) => store.deleteConversation(conversationPath),
12259
+ markAsRead: (conversationPath) => store.markAsRead(conversationPath),
12260
+ refresh: () => store.load(),
12261
+ store
12262
+ };
12263
+ };
12264
+ }
12265
+
12266
+ // src/integrations/vue/index.ts
12267
+ function createRaisinVue(vue) {
12268
+ return {
12269
+ useAuth: createUseAuth2(vue),
12270
+ useConnection: createUseConnection2(vue),
12271
+ useSql: createUseSql2(vue),
12272
+ useSubscription: createUseSubscription2(vue),
12273
+ useConversation: createUseConversation(vue),
12274
+ useConversationList: createUseConversationList(vue)
12275
+ };
12276
+ }
10863
12277
  // Annotate the CommonJS export names for ESM import in node:
10864
12278
  0 && (module.exports = {
10865
12279
  AllNodeEventTypes,
@@ -10881,6 +12295,7 @@ function createRaisinReact(react) {
10881
12295
  DEFAULT_RETRY_BASE_DELAY,
10882
12296
  DEFAULT_RETRY_MAX_DELAY,
10883
12297
  DEFAULT_RETRY_OPTIONS,
12298
+ DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS,
10884
12299
  Database,
10885
12300
  ElementTypes,
10886
12301
  EventHandler,
@@ -10888,11 +12303,16 @@ function createRaisinReact(react) {
10888
12303
  FlowClient,
10889
12304
  FlowsApi,
10890
12305
  FunctionsApi,
12306
+ HttpArchetypes,
10891
12307
  HttpDatabase,
12308
+ HttpElementTypes,
10892
12309
  HttpFunctionsApi,
12310
+ HttpNodeTypes,
10893
12311
  HttpWorkspaceClient,
12312
+ InboxApi,
10894
12313
  LocalStorageTokenStorage,
10895
12314
  LogLevel,
12315
+ McpClient,
10896
12316
  MemoryTokenStorage,
10897
12317
  NodeBuilder,
10898
12318
  NodeEventType,
@@ -10901,6 +12321,7 @@ function createRaisinReact(react) {
10901
12321
  NodeOperations,
10902
12322
  NodeTypes,
10903
12323
  RAISIN_CONTEXT_KEY,
12324
+ RETRY_STRATEGIES,
10904
12325
  RaisinAbortError,
10905
12326
  RaisinAuthError,
10906
12327
  RaisinClient,
@@ -10915,6 +12336,7 @@ function createRaisinReact(react) {
10915
12336
  ResponseStatus,
10916
12337
  RetryBuilder,
10917
12338
  SSEClient,
12339
+ SchedulerApi,
10918
12340
  SqlQuery,
10919
12341
  Tags,
10920
12342
  Transaction,
@@ -10936,6 +12358,7 @@ function createRaisinReact(react) {
10936
12358
  createFlowAdapter,
10937
12359
  createLoader,
10938
12360
  createRaisinReact,
12361
+ createRaisinVue,
10939
12362
  createRequestScopedClient,
10940
12363
  createSSRClient,
10941
12364
  createSqlAdapter,
@@ -10948,13 +12371,18 @@ function createRaisinReact(react) {
10948
12371
  getErrorMessage,
10949
12372
  getLogLevel,
10950
12373
  getLoggerConfig,
12374
+ getRefDisplayName,
12375
+ getRefPath,
10951
12376
  isEventMessage,
12377
+ isFlowDefinitionContainer,
12378
+ isFlowDefinitionStep,
10952
12379
  isHttpClient,
10953
12380
  isResponseEnvelope,
10954
12381
  isRetryableError,
10955
12382
  isTerminalEvent,
10956
12383
  isWebSocketClient,
10957
12384
  logger,
12385
+ normalizeHomePath,
10958
12386
  projectPlansFromMessages,
10959
12387
  rowsToObjects,
10960
12388
  setLogLevel,
@@ -10964,3 +12392,4 @@ function createRaisinReact(react) {
10964
12392
  useFlow,
10965
12393
  withRetry
10966
12394
  });
12395
+ //# sourceMappingURL=index.js.map