@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.
- package/README.md +281 -70
- package/dist/chunk-3KMOXRTK.mjs +1377 -0
- package/dist/chunk-3KMOXRTK.mjs.map +1 -0
- package/dist/chunk-B6EFDYFS.mjs +170 -0
- package/dist/chunk-B6EFDYFS.mjs.map +1 -0
- package/dist/chunk-P44XWTPA.mjs +190 -0
- package/dist/chunk-P44XWTPA.mjs.map +1 -0
- package/dist/chunk-Q4IBPW2W.mjs +866 -0
- package/dist/chunk-Q4IBPW2W.mjs.map +1 -0
- package/dist/chunk-QJKYNLT4.mjs +532 -0
- package/dist/chunk-QJKYNLT4.mjs.map +1 -0
- package/dist/chunk-T2ZSNYFG.mjs +248 -0
- package/dist/chunk-T2ZSNYFG.mjs.map +1 -0
- package/dist/conversation-list-store-CU5mi_PE.d.mts +6842 -0
- package/dist/conversation-list-store-CU5mi_PE.d.ts +6842 -0
- package/dist/index-DDus5a7P.d.mts +301 -0
- package/dist/index-sFvMihQA.d.ts +301 -0
- package/dist/index.d.mts +72 -6109
- package/dist/index.d.ts +72 -6109
- package/dist/index.js +1640 -211
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1280 -3098
- package/dist/index.mjs.map +1 -0
- package/dist/react.d.mts +4 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +2519 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +9 -0
- package/dist/react.mjs.map +1 -0
- package/dist/svelte-sql-B2G94Q9p.d.ts +52 -0
- package/dist/svelte-sql-CpXHXg34.d.mts +52 -0
- package/dist/svelte.d.mts +288 -0
- package/dist/svelte.d.ts +288 -0
- package/dist/svelte.js +2432 -0
- package/dist/svelte.js.map +1 -0
- package/dist/svelte.mjs +25 -0
- package/dist/svelte.mjs.map +1 -0
- package/dist/vue.d.mts +197 -0
- package/dist/vue.d.ts +197 -0
- package/dist/vue.js +1460 -0
- package/dist/vue.js.map +1 -0
- package/dist/vue.mjs +9 -0
- package/dist/vue.mjs.map +1 -0
- 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
|
|
32
|
-
__export(
|
|
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(
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
this.options.
|
|
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
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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)
|
|
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
|
-
|
|
1219
|
-
|
|
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
|
-
|
|
1230
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1548
|
-
|
|
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
|
-
|
|
1568
|
-
|
|
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
|
-
*
|
|
1766
|
+
* Move a child to a specific position among its siblings.
|
|
1579
1767
|
*
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
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(
|
|
1776
|
+
async reorder(parentPath, childName, position) {
|
|
1585
1777
|
const payload = {
|
|
1586
|
-
|
|
1587
|
-
|
|
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
|
|
1786
|
+
* Move a child so it sits immediately before one of its siblings.
|
|
1594
1787
|
*
|
|
1595
1788
|
* @param parentPath - Parent node path
|
|
1596
|
-
* @param
|
|
1597
|
-
* @param
|
|
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,
|
|
1792
|
+
async moveChildBefore(parentPath, childName, beforeChildName) {
|
|
1601
1793
|
const payload = {
|
|
1602
1794
|
parent_path: parentPath,
|
|
1603
|
-
|
|
1604
|
-
|
|
1795
|
+
child_name: childName,
|
|
1796
|
+
before_child_name: beforeChildName
|
|
1605
1797
|
};
|
|
1606
|
-
|
|
1607
|
-
return result;
|
|
1798
|
+
await this.sendRequest(payload, "node_move_child_before");
|
|
1608
1799
|
}
|
|
1609
1800
|
/**
|
|
1610
|
-
* Move a child
|
|
1801
|
+
* Move a child so it sits immediately after one of its siblings.
|
|
1611
1802
|
*
|
|
1612
1803
|
* @param parentPath - Parent node path
|
|
1613
|
-
* @param
|
|
1614
|
-
* @param
|
|
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,
|
|
1807
|
+
async moveChildAfter(parentPath, childName, afterChildName) {
|
|
1618
1808
|
const payload = {
|
|
1619
1809
|
parent_path: parentPath,
|
|
1620
|
-
|
|
1621
|
-
|
|
1810
|
+
child_name: childName,
|
|
1811
|
+
after_child_name: afterChildName
|
|
1622
1812
|
};
|
|
1623
|
-
|
|
1624
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
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
|
|
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
|
-
*
|
|
4544
|
-
*
|
|
4545
|
-
*
|
|
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', '
|
|
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(
|
|
4983
|
+
async respondToHumanTask(_instanceId, taskId, response, options) {
|
|
4563
4984
|
await this.request({
|
|
4564
4985
|
method: "POST",
|
|
4565
|
-
path: `/api/
|
|
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/
|
|
4705
|
-
var
|
|
4706
|
-
constructor(
|
|
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.
|
|
4709
|
-
this.
|
|
4710
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
|
|
4731
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
4737
|
-
*
|
|
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
|
-
|
|
4740
|
-
return
|
|
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
|
-
|
|
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
|
|
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(
|
|
8146
|
+
tenantId: options.tenantId ?? this.extractTenantFromUrl(connectionUrl),
|
|
7221
8147
|
defaultBranch: options.defaultBranch ?? "main"
|
|
7222
8148
|
};
|
|
7223
|
-
this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(
|
|
7224
|
-
this._repository = this.extractRepositoryFromUrl(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
7323
|
-
|
|
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
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
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
|
|
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
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
7978
|
-
|
|
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
|
-
|
|
7998
|
-
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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:
|
|
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
|