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