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