@raisindb/client 0.1.5 → 0.1.17
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-DJDP5CX6.mjs +866 -0
- package/dist/chunk-DJDP5CX6.mjs.map +1 -0
- package/dist/chunk-JRFBP4WK.mjs +170 -0
- package/dist/chunk-JRFBP4WK.mjs.map +1 -0
- package/dist/chunk-OLJOZIIU.mjs +1348 -0
- package/dist/chunk-OLJOZIIU.mjs.map +1 -0
- package/dist/chunk-T2ZSNYFG.mjs +248 -0
- package/dist/chunk-T2ZSNYFG.mjs.map +1 -0
- package/dist/chunk-VZX3ACSO.mjs +190 -0
- package/dist/chunk-VZX3ACSO.mjs.map +1 -0
- package/dist/chunk-WFBAWOVM.mjs +532 -0
- package/dist/chunk-WFBAWOVM.mjs.map +1 -0
- package/dist/conversation-list-store-CNcVagmw.d.mts +6120 -0
- package/dist/conversation-list-store-CNcVagmw.d.ts +6120 -0
- package/dist/index-DQjE-4Oj.d.ts +301 -0
- package/dist/index-FkFH4HHQ.d.mts +301 -0
- package/dist/index.d.mts +66 -6108
- package/dist/index.d.ts +66 -6108
- package/dist/index.js +859 -109
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +639 -3108
- 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 +2500 -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-62EaYoeM.d.ts +52 -0
- package/dist/svelte-sql-CgT-Ij-P.d.mts +52 -0
- package/dist/svelte.d.mts +288 -0
- package/dist/svelte.d.ts +288 -0
- package/dist/svelte.js +2413 -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 +1441 -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,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
createRaisinReact,
|
|
3
|
+
useConversation,
|
|
4
|
+
useConversationList,
|
|
5
|
+
useFlow
|
|
6
|
+
} from "./chunk-WFBAWOVM.mjs";
|
|
7
|
+
import {
|
|
8
|
+
RAISIN_CONTEXT_KEY,
|
|
9
|
+
createConversationAdapter,
|
|
10
|
+
createConversationListAdapter,
|
|
11
|
+
createFlowAdapter
|
|
12
|
+
} from "./chunk-VZX3ACSO.mjs";
|
|
13
|
+
import {
|
|
14
|
+
FlowClient,
|
|
15
|
+
RaisinAbortError,
|
|
16
|
+
RaisinAuthError,
|
|
17
|
+
RaisinConnectionError,
|
|
18
|
+
RaisinError,
|
|
19
|
+
RaisinFlowError,
|
|
20
|
+
RaisinTimeoutError,
|
|
21
|
+
SSEClient,
|
|
22
|
+
classifyHttpError
|
|
23
|
+
} from "./chunk-DJDP5CX6.mjs";
|
|
24
|
+
import {
|
|
25
|
+
createRaisinVue
|
|
26
|
+
} from "./chunk-JRFBP4WK.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
|
+
configureLogger,
|
|
41
|
+
getLogLevel,
|
|
42
|
+
getLoggerConfig,
|
|
43
|
+
isRecoveredDoneEvent,
|
|
44
|
+
logger,
|
|
45
|
+
normalizeHomePath,
|
|
46
|
+
projectPlansFromMessages,
|
|
47
|
+
setLogLevel
|
|
48
|
+
} from "./chunk-OLJOZIIU.mjs";
|
|
7
49
|
|
|
8
50
|
// 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
|
-
};
|
|
51
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
364
52
|
|
|
365
53
|
// src/utils/request-tracker.ts
|
|
366
54
|
import { v4 as uuidv4 } from "uuid";
|
|
@@ -881,6 +569,14 @@ var EventHandler = class {
|
|
|
881
569
|
* If a subscription with identical filters already exists, the callback
|
|
882
570
|
* is added to the existing subscription instead of creating a new one.
|
|
883
571
|
*
|
|
572
|
+
* Path filter semantics (matched server-side):
|
|
573
|
+
* - A plain path matches **only that exact node** — there is no implicit
|
|
574
|
+
* prefix matching.
|
|
575
|
+
* - `*` matches exactly one path segment (e.g. `/inbox/*` matches
|
|
576
|
+
* `/inbox/a` but not `/inbox/a/b`).
|
|
577
|
+
* - `**` matches recursively (e.g. `/inbox/**` matches all descendants
|
|
578
|
+
* of `/inbox`, but not `/inbox` itself).
|
|
579
|
+
*
|
|
884
580
|
* @param filters - Event filters
|
|
885
581
|
* @param callback - Callback function for events
|
|
886
582
|
* @returns Subscription object
|
|
@@ -1071,14 +767,29 @@ var EventHandler = class {
|
|
|
1071
767
|
* Restore subscriptions after reconnect
|
|
1072
768
|
*
|
|
1073
769
|
* Re-subscribes to the server with the original filters for all active
|
|
1074
|
-
* subscription entries.
|
|
770
|
+
* subscription entries. Failed entries are kept (with their callbacks)
|
|
771
|
+
* and marked pending so a retry call can re-attempt them without
|
|
772
|
+
* duplicating already-restored subscriptions.
|
|
773
|
+
*
|
|
774
|
+
* @param options.retry - When true, only re-attempts entries that failed
|
|
775
|
+
* in a previous call (instead of re-subscribing everything)
|
|
776
|
+
* @throws Error if any subscription could not be restored
|
|
1075
777
|
*/
|
|
1076
|
-
async restoreSubscriptions() {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
778
|
+
async restoreSubscriptions(options) {
|
|
779
|
+
if (!options?.retry) {
|
|
780
|
+
for (const entry of this.filterSubscriptions.values()) {
|
|
781
|
+
this.subscriptions.delete(entry.serverId);
|
|
782
|
+
entry.restorePending = true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
const entriesToRestore = Array.from(this.filterSubscriptions.entries()).filter(([, entry]) => entry.restorePending);
|
|
786
|
+
let failureCount = 0;
|
|
787
|
+
let lastError;
|
|
1080
788
|
for (const [hash, entry] of entriesToRestore) {
|
|
1081
|
-
if (entry.callbacks.size === 0)
|
|
789
|
+
if (entry.callbacks.size === 0) {
|
|
790
|
+
entry.restorePending = false;
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
1082
793
|
try {
|
|
1083
794
|
const payload = { filters: entry.filters };
|
|
1084
795
|
const response = await this.sendRequest(
|
|
@@ -1086,22 +797,24 @@ var EventHandler = class {
|
|
|
1086
797
|
"subscribe" /* Subscribe */
|
|
1087
798
|
);
|
|
1088
799
|
const newServerId = response.subscription_id;
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
filters: entry.filters,
|
|
1092
|
-
callbacks: entry.callbacks
|
|
1093
|
-
});
|
|
800
|
+
entry.serverId = newServerId;
|
|
801
|
+
entry.restorePending = false;
|
|
1094
802
|
this.subscriptions.set(newServerId, hash);
|
|
1095
803
|
for (const callback of entry.callbacks) {
|
|
1096
804
|
this.callbackToHash.set(callback, hash);
|
|
1097
805
|
}
|
|
1098
806
|
} catch (error) {
|
|
1099
807
|
logger.error("Failed to restore subscription:", error);
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
808
|
+
failureCount++;
|
|
809
|
+
lastError = error;
|
|
1103
810
|
}
|
|
1104
811
|
}
|
|
812
|
+
if (failureCount > 0) {
|
|
813
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
814
|
+
throw new Error(
|
|
815
|
+
`Failed to restore ${failureCount} subscription(s) after reconnect: ${message}`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
1105
818
|
}
|
|
1106
819
|
};
|
|
1107
820
|
var EventSubscriptions = class {
|
|
@@ -1136,7 +849,15 @@ var EventSubscriptions = class {
|
|
|
1136
849
|
/**
|
|
1137
850
|
* Subscribe to events for a specific path
|
|
1138
851
|
*
|
|
1139
|
-
*
|
|
852
|
+
* Path pattern semantics (matched server-side):
|
|
853
|
+
* - A plain path matches **only that exact node** — there is no implicit
|
|
854
|
+
* prefix matching, so `/folder` does NOT match `/folder/child`.
|
|
855
|
+
* - `*` matches exactly one path segment: `/folder/*` matches
|
|
856
|
+
* `/folder/child` but not `/folder/child/grandchild`.
|
|
857
|
+
* - `**` matches recursively: `/folder/**` matches every descendant of
|
|
858
|
+
* `/folder` (but not `/folder` itself).
|
|
859
|
+
*
|
|
860
|
+
* @param path - Path pattern (e.g. "/folder", "/folder/*", "/folder/**")
|
|
1140
861
|
* @param callback - Callback function for events
|
|
1141
862
|
* @param options - Optional subscription options
|
|
1142
863
|
* @param options.includeNode - Include full node data in event payload (default: false)
|
|
@@ -1768,7 +1489,17 @@ var NodeFileSource = class {
|
|
|
1768
1489
|
return;
|
|
1769
1490
|
}
|
|
1770
1491
|
if (!this._fsPromises) {
|
|
1771
|
-
|
|
1492
|
+
if (!isNodeEnvironment()) {
|
|
1493
|
+
throw new Error(
|
|
1494
|
+
"NodeFileSource requires Node.js (fs/promises is not available in browsers). In the browser, use a File or Blob object instead."
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
const specifier = "fs/promises";
|
|
1498
|
+
this._fsPromises = await import(
|
|
1499
|
+
/* @vite-ignore */
|
|
1500
|
+
/* webpackIgnore: true */
|
|
1501
|
+
specifier
|
|
1502
|
+
);
|
|
1772
1503
|
}
|
|
1773
1504
|
const stats = await this._fsPromises.stat(this._filePath);
|
|
1774
1505
|
this._size = stats.size;
|
|
@@ -3024,398 +2755,40 @@ var HttpFunctionsApi = class {
|
|
|
3024
2755
|
}
|
|
3025
2756
|
};
|
|
3026
2757
|
|
|
3027
|
-
// src/
|
|
3028
|
-
var
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
this.
|
|
3032
|
-
this.
|
|
3033
|
-
this.
|
|
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;
|
|
2758
|
+
// src/conversations.ts
|
|
2759
|
+
var DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS = 12e4;
|
|
2760
|
+
var ConversationManager = class {
|
|
2761
|
+
constructor(baseUrl, repository, authManager, options = {}, executeSql) {
|
|
2762
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
2763
|
+
this.repository = repository;
|
|
2764
|
+
this.authManager = authManager;
|
|
3111
2765
|
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3112
|
-
this.
|
|
3113
|
-
this.
|
|
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;
|
|
2766
|
+
this.requestTimeout = options.requestTimeout ?? 6e4;
|
|
2767
|
+
this.executeSql = executeSql;
|
|
3153
2768
|
}
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
2769
|
+
// ==========================================================================
|
|
2770
|
+
// User context
|
|
2771
|
+
// ==========================================================================
|
|
2772
|
+
async ensureCurrentUser() {
|
|
2773
|
+
if (this.cachedUserId && this.cachedUserHome) {
|
|
2774
|
+
return { userId: this.cachedUserId, userHome: this.cachedUserHome };
|
|
3158
2775
|
}
|
|
2776
|
+
const result = await this.sqlQuery(
|
|
2777
|
+
`SELECT RAISIN_CURRENT_USER()->>'path'::String as home, RAISIN_CURRENT_USER()->>'id'::String as user_id`
|
|
2778
|
+
);
|
|
2779
|
+
if (!result?.rows?.[0]) throw new Error("User not authenticated");
|
|
2780
|
+
const row = result.rows[0];
|
|
2781
|
+
const userHome = normalizeHomePath(row.home);
|
|
2782
|
+
if (!userHome) throw new Error("User home path not found");
|
|
2783
|
+
this.cachedUserHome = userHome;
|
|
2784
|
+
this.cachedUserId = row.user_id || userHome;
|
|
2785
|
+
return { userId: this.cachedUserId, userHome };
|
|
3159
2786
|
}
|
|
2787
|
+
// ==========================================================================
|
|
2788
|
+
// Conversation listing
|
|
2789
|
+
// ==========================================================================
|
|
3160
2790
|
/**
|
|
3161
|
-
*
|
|
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
|
-
// src/conversations.ts
|
|
3388
|
-
var ConversationManager = class {
|
|
3389
|
-
constructor(baseUrl, repository, authManager, options = {}, executeSql) {
|
|
3390
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
3391
|
-
this.repository = repository;
|
|
3392
|
-
this.authManager = authManager;
|
|
3393
|
-
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
3394
|
-
this.requestTimeout = options.requestTimeout ?? 6e4;
|
|
3395
|
-
this.executeSql = executeSql;
|
|
3396
|
-
}
|
|
3397
|
-
// ==========================================================================
|
|
3398
|
-
// User context
|
|
3399
|
-
// ==========================================================================
|
|
3400
|
-
async ensureCurrentUser() {
|
|
3401
|
-
if (this.cachedUserId && this.cachedUserHome) {
|
|
3402
|
-
return { userId: this.cachedUserId, userHome: this.cachedUserHome };
|
|
3403
|
-
}
|
|
3404
|
-
const result = await this.sqlQuery(
|
|
3405
|
-
`SELECT RAISIN_CURRENT_USER()->>'path'::String as home, RAISIN_CURRENT_USER()->>'id'::String as user_id`
|
|
3406
|
-
);
|
|
3407
|
-
if (!result?.rows?.[0]) throw new Error("User not authenticated");
|
|
3408
|
-
const row = result.rows[0];
|
|
3409
|
-
this.cachedUserHome = row.home;
|
|
3410
|
-
this.cachedUserId = row.user_id || this.cachedUserHome;
|
|
3411
|
-
if (!this.cachedUserHome) throw new Error("User home path not found");
|
|
3412
|
-
return { userId: this.cachedUserId, userHome: this.cachedUserHome };
|
|
3413
|
-
}
|
|
3414
|
-
// ==========================================================================
|
|
3415
|
-
// Conversation listing
|
|
3416
|
-
// ==========================================================================
|
|
3417
|
-
/**
|
|
3418
|
-
* List conversations for the current user.
|
|
2791
|
+
* List conversations for the current user.
|
|
3419
2792
|
*/
|
|
3420
2793
|
async list(options) {
|
|
3421
2794
|
const workspace = "raisin:access_control";
|
|
@@ -3604,13 +2977,15 @@ var ConversationManager = class {
|
|
|
3604
2977
|
fetch: this.fetchImpl,
|
|
3605
2978
|
signal: options?.signal,
|
|
3606
2979
|
method: "POST",
|
|
3607
|
-
body: { channel: streamTarget.channel, path: streamTarget.path }
|
|
2980
|
+
body: { channel: streamTarget.channel, path: streamTarget.path },
|
|
2981
|
+
inactivityTimeoutMs: options?.inactivityTimeoutMs ?? DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS
|
|
3608
2982
|
});
|
|
3609
2983
|
const iterator = sse[Symbol.asyncIterator]();
|
|
3610
2984
|
let pendingFirst = iterator.next();
|
|
3611
2985
|
try {
|
|
3612
2986
|
await sse.waitUntilConnected();
|
|
3613
2987
|
} catch {
|
|
2988
|
+
pendingFirst?.catch(() => void 0);
|
|
3614
2989
|
pendingFirst = null;
|
|
3615
2990
|
sse.close();
|
|
3616
2991
|
logger.debug("SSE connection failed, falling back to create-only");
|
|
@@ -3647,6 +3022,15 @@ var ConversationManager = class {
|
|
|
3647
3022
|
return;
|
|
3648
3023
|
}
|
|
3649
3024
|
}
|
|
3025
|
+
} catch (error) {
|
|
3026
|
+
if (error instanceof RaisinTimeoutError && error.code === "SSE_INACTIVITY_TIMEOUT") {
|
|
3027
|
+
logger.warn(
|
|
3028
|
+
`[ConversationManager] SSE stream inactive for ${error.timeoutMs}ms, ending turn with synthetic waiting event`
|
|
3029
|
+
);
|
|
3030
|
+
yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
throw error;
|
|
3650
3034
|
} finally {
|
|
3651
3035
|
sse.close();
|
|
3652
3036
|
}
|
|
@@ -4132,471 +3516,159 @@ var ConversationManager = class {
|
|
|
4132
3516
|
}
|
|
4133
3517
|
};
|
|
4134
3518
|
|
|
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) {
|
|
3519
|
+
// src/inbox.ts
|
|
3520
|
+
var InboxApi = class {
|
|
3521
|
+
constructor(baseUrl, repository, authManager, options = {}) {
|
|
4147
3522
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
4148
3523
|
this.repository = repository;
|
|
4149
3524
|
this.authManager = authManager;
|
|
4150
3525
|
this.fetchImpl = options.fetch ?? fetch;
|
|
4151
|
-
this.requestTimeout = options.requestTimeout ??
|
|
4152
|
-
this.flowsApi = flowsApi;
|
|
3526
|
+
this.requestTimeout = options.requestTimeout ?? 3e4;
|
|
4153
3527
|
}
|
|
4154
3528
|
/**
|
|
4155
|
-
*
|
|
3529
|
+
* List the caller's inbox tasks.
|
|
4156
3530
|
*
|
|
4157
|
-
*
|
|
4158
|
-
*
|
|
4159
|
-
* @param repository - Repository name
|
|
4160
|
-
* @param options - Additional options
|
|
3531
|
+
* Tasks are sorted pending-first, then by priority (highest first) and
|
|
3532
|
+
* due time (earliest first).
|
|
4161
3533
|
*/
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
3534
|
+
async listTasks(options = {}) {
|
|
3535
|
+
const params = new URLSearchParams();
|
|
3536
|
+
if (options.status) params.set("status", options.status);
|
|
3537
|
+
if (options.assignee) params.set("assignee", options.assignee);
|
|
3538
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
3539
|
+
return this.request({
|
|
3540
|
+
method: "GET",
|
|
3541
|
+
path: `/api/inbox/${this.repository}${query}`,
|
|
3542
|
+
signal: options.signal
|
|
3543
|
+
});
|
|
4170
3544
|
}
|
|
4171
|
-
// ==========================================================================
|
|
4172
|
-
// Flow Execution
|
|
4173
|
-
// ==========================================================================
|
|
4174
3545
|
/**
|
|
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
|
-
* ```
|
|
3546
|
+
* Get a single inbox task by ID (or path).
|
|
4192
3547
|
*/
|
|
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 },
|
|
3548
|
+
async getTask(taskId, options) {
|
|
3549
|
+
return this.request({
|
|
3550
|
+
method: "GET",
|
|
3551
|
+
path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}`,
|
|
4201
3552
|
signal: options?.signal
|
|
4202
3553
|
});
|
|
4203
|
-
return response;
|
|
4204
3554
|
}
|
|
4205
|
-
// ==========================================================================
|
|
4206
|
-
// Instance Status
|
|
4207
|
-
// ==========================================================================
|
|
4208
3555
|
/**
|
|
4209
|
-
*
|
|
3556
|
+
* Complete an inbox task.
|
|
4210
3557
|
*
|
|
4211
|
-
*
|
|
4212
|
-
*
|
|
4213
|
-
* flow execution where events may be missed).
|
|
3558
|
+
* The caller must be the task's assignee (or an admin). Completion marks
|
|
3559
|
+
* the task done, records who decided, and resumes the owning flow.
|
|
4214
3560
|
*
|
|
4215
|
-
* @param
|
|
4216
|
-
* @
|
|
3561
|
+
* @param taskId - Task node ID (or path)
|
|
3562
|
+
* @param response - The response payload:
|
|
3563
|
+
* - approval tasks: `{ action: '<option value>', comment?: string }`
|
|
3564
|
+
* - input tasks: the input value(s) matching the task's input_schema
|
|
3565
|
+
* - review/action tasks: any acknowledgement payload
|
|
4217
3566
|
*
|
|
4218
3567
|
* @example
|
|
4219
3568
|
* ```typescript
|
|
4220
|
-
*
|
|
4221
|
-
* console.log('Status:', status.status); // 'waiting', 'completed', etc.
|
|
3569
|
+
* await inbox.completeTask('task-123', { action: 'approve' });
|
|
4222
3570
|
* ```
|
|
4223
3571
|
*/
|
|
4224
|
-
async
|
|
4225
|
-
if (this.flowsApi) {
|
|
4226
|
-
return this.flowsApi.getInstanceStatus(instanceId);
|
|
4227
|
-
}
|
|
3572
|
+
async completeTask(taskId, response, options) {
|
|
4228
3573
|
return this.request({
|
|
4229
|
-
method: "
|
|
4230
|
-
path: `/api/
|
|
3574
|
+
method: "POST",
|
|
3575
|
+
path: `/api/inbox/${this.repository}/tasks/${encodeURIComponent(taskId)}/complete`,
|
|
3576
|
+
body: { response },
|
|
4231
3577
|
signal: options?.signal
|
|
4232
3578
|
});
|
|
4233
3579
|
}
|
|
4234
3580
|
// ==========================================================================
|
|
4235
|
-
//
|
|
3581
|
+
// Internal HTTP request helper
|
|
4236
3582
|
// ==========================================================================
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
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 = {};
|
|
3583
|
+
async request(options) {
|
|
3584
|
+
const url = `${this.baseUrl}${options.path}`;
|
|
3585
|
+
const headers = {
|
|
3586
|
+
"Content-Type": "application/json"
|
|
3587
|
+
};
|
|
4290
3588
|
const token = this.authManager.getAccessToken();
|
|
4291
3589
|
if (token) {
|
|
4292
3590
|
headers["Authorization"] = `Bearer ${token}`;
|
|
4293
3591
|
}
|
|
4294
|
-
const
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
3592
|
+
const controller = new AbortController();
|
|
3593
|
+
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
3594
|
+
if (options.signal) {
|
|
3595
|
+
if (options.signal.aborted) {
|
|
3596
|
+
clearTimeout(timeoutId);
|
|
3597
|
+
throw new RaisinAbortError();
|
|
3598
|
+
}
|
|
3599
|
+
options.signal.addEventListener("abort", () => controller.abort(), {
|
|
3600
|
+
once: true
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
4301
3603
|
try {
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
3604
|
+
const response = await this.fetchImpl(url, {
|
|
3605
|
+
method: options.method,
|
|
3606
|
+
headers,
|
|
3607
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
3608
|
+
signal: controller.signal
|
|
3609
|
+
});
|
|
3610
|
+
clearTimeout(timeoutId);
|
|
3611
|
+
if (!response.ok) {
|
|
3612
|
+
const errorText = await response.text();
|
|
3613
|
+
let errorMessage;
|
|
3614
|
+
try {
|
|
3615
|
+
const errorJson = JSON.parse(errorText);
|
|
3616
|
+
errorMessage = errorJson.message || errorJson.error || errorText;
|
|
3617
|
+
} catch {
|
|
3618
|
+
errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
|
|
4307
3619
|
}
|
|
3620
|
+
throw classifyHttpError(response.status, errorMessage);
|
|
4308
3621
|
}
|
|
4309
|
-
|
|
4310
|
-
|
|
3622
|
+
const contentType = response.headers.get("content-type");
|
|
3623
|
+
if (contentType?.includes("application/json")) {
|
|
3624
|
+
return await response.json();
|
|
3625
|
+
}
|
|
3626
|
+
return void 0;
|
|
3627
|
+
} catch (error) {
|
|
3628
|
+
clearTimeout(timeoutId);
|
|
3629
|
+
if (error instanceof RaisinAbortError || error instanceof RaisinTimeoutError) {
|
|
3630
|
+
throw error;
|
|
3631
|
+
}
|
|
3632
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3633
|
+
if (options.signal?.aborted) {
|
|
3634
|
+
throw new RaisinAbortError();
|
|
3635
|
+
}
|
|
3636
|
+
throw new RaisinTimeoutError(
|
|
3637
|
+
`Request timeout after ${this.requestTimeout}ms`,
|
|
3638
|
+
"REQUEST_TIMEOUT",
|
|
3639
|
+
this.requestTimeout
|
|
3640
|
+
);
|
|
3641
|
+
}
|
|
3642
|
+
throw error;
|
|
4311
3643
|
}
|
|
4312
3644
|
}
|
|
3645
|
+
};
|
|
3646
|
+
|
|
3647
|
+
// src/database.ts
|
|
3648
|
+
var Database = class _Database {
|
|
3649
|
+
constructor(repository, context, sendRequest, eventHandler, branch, revision, getUploadManager, getSignAssetUrl, httpOptions) {
|
|
3650
|
+
this.repository = repository;
|
|
3651
|
+
this.branch = branch;
|
|
3652
|
+
this.revision = revision;
|
|
3653
|
+
this._context = {
|
|
3654
|
+
...context,
|
|
3655
|
+
repository,
|
|
3656
|
+
branch: branch || context.branch,
|
|
3657
|
+
revision: revision || context.revision
|
|
3658
|
+
};
|
|
3659
|
+
this.sendRequest = (payload, requestType, contextOverride, requestOptions) => sendRequest(
|
|
3660
|
+
payload,
|
|
3661
|
+
requestType || "node_create",
|
|
3662
|
+
contextOverride || this._context,
|
|
3663
|
+
requestOptions
|
|
3664
|
+
);
|
|
3665
|
+
this.eventHandler = eventHandler;
|
|
3666
|
+
this.getUploadManager = getUploadManager;
|
|
3667
|
+
this.getSignAssetUrl = getSignAssetUrl;
|
|
3668
|
+
this.httpOptions = httpOptions;
|
|
3669
|
+
}
|
|
4313
3670
|
/**
|
|
4314
|
-
*
|
|
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
|
|
4512
|
-
// ==========================================================================
|
|
4513
|
-
async request(options) {
|
|
4514
|
-
const url = `${this.baseUrl}${options.path}`;
|
|
4515
|
-
const headers = {
|
|
4516
|
-
"Content-Type": "application/json"
|
|
4517
|
-
};
|
|
4518
|
-
const token = this.authManager.getAccessToken();
|
|
4519
|
-
if (token) {
|
|
4520
|
-
headers["Authorization"] = `Bearer ${token}`;
|
|
4521
|
-
}
|
|
4522
|
-
const controller = new AbortController();
|
|
4523
|
-
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
4524
|
-
if (options.signal) {
|
|
4525
|
-
if (options.signal.aborted) {
|
|
4526
|
-
clearTimeout(timeoutId);
|
|
4527
|
-
throw new RaisinAbortError();
|
|
4528
|
-
}
|
|
4529
|
-
options.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
4530
|
-
}
|
|
4531
|
-
try {
|
|
4532
|
-
const response = await this.fetchImpl(url, {
|
|
4533
|
-
method: options.method,
|
|
4534
|
-
headers,
|
|
4535
|
-
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
4536
|
-
signal: controller.signal
|
|
4537
|
-
});
|
|
4538
|
-
clearTimeout(timeoutId);
|
|
4539
|
-
if (!response.ok) {
|
|
4540
|
-
const errorText = await response.text();
|
|
4541
|
-
let errorMessage;
|
|
4542
|
-
try {
|
|
4543
|
-
const errorJson = JSON.parse(errorText);
|
|
4544
|
-
errorMessage = errorJson.message || errorJson.error || errorText;
|
|
4545
|
-
} catch {
|
|
4546
|
-
errorMessage = errorText || `HTTP ${response.status}: ${response.statusText}`;
|
|
4547
|
-
}
|
|
4548
|
-
throw classifyHttpError(response.status, errorMessage);
|
|
4549
|
-
}
|
|
4550
|
-
const contentType = response.headers.get("content-type");
|
|
4551
|
-
if (contentType?.includes("application/json")) {
|
|
4552
|
-
return await response.json();
|
|
4553
|
-
}
|
|
4554
|
-
return void 0;
|
|
4555
|
-
} catch (error) {
|
|
4556
|
-
clearTimeout(timeoutId);
|
|
4557
|
-
if (error instanceof RaisinAbortError || error instanceof RaisinTimeoutError) {
|
|
4558
|
-
throw error;
|
|
4559
|
-
}
|
|
4560
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
4561
|
-
if (options.signal?.aborted) {
|
|
4562
|
-
throw new RaisinAbortError();
|
|
4563
|
-
}
|
|
4564
|
-
throw new RaisinTimeoutError(
|
|
4565
|
-
`Request timeout after ${this.requestTimeout}ms`,
|
|
4566
|
-
"REQUEST_TIMEOUT",
|
|
4567
|
-
this.requestTimeout
|
|
4568
|
-
);
|
|
4569
|
-
}
|
|
4570
|
-
throw error;
|
|
4571
|
-
}
|
|
4572
|
-
}
|
|
4573
|
-
};
|
|
4574
|
-
|
|
4575
|
-
// src/database.ts
|
|
4576
|
-
var Database = class _Database {
|
|
4577
|
-
constructor(repository, context, sendRequest, eventHandler, branch, revision, getUploadManager, getSignAssetUrl, httpOptions) {
|
|
4578
|
-
this.repository = repository;
|
|
4579
|
-
this.branch = branch;
|
|
4580
|
-
this.revision = revision;
|
|
4581
|
-
this._context = {
|
|
4582
|
-
...context,
|
|
4583
|
-
repository,
|
|
4584
|
-
branch: branch || context.branch,
|
|
4585
|
-
revision: revision || context.revision
|
|
4586
|
-
};
|
|
4587
|
-
this.sendRequest = (payload, requestType, contextOverride, requestOptions) => sendRequest(
|
|
4588
|
-
payload,
|
|
4589
|
-
requestType || "node_create",
|
|
4590
|
-
contextOverride || this._context,
|
|
4591
|
-
requestOptions
|
|
4592
|
-
);
|
|
4593
|
-
this.eventHandler = eventHandler;
|
|
4594
|
-
this.getUploadManager = getUploadManager;
|
|
4595
|
-
this.getSignAssetUrl = getSignAssetUrl;
|
|
4596
|
-
this.httpOptions = httpOptions;
|
|
4597
|
-
}
|
|
4598
|
-
/**
|
|
4599
|
-
* Get the repository name
|
|
3671
|
+
* Get the repository name
|
|
4600
3672
|
*/
|
|
4601
3673
|
getRepository() {
|
|
4602
3674
|
return this.repository;
|
|
@@ -4848,8 +3920,44 @@ var Database = class _Database {
|
|
|
4848
3920
|
}
|
|
4849
3921
|
return this._flowClient;
|
|
4850
3922
|
}
|
|
3923
|
+
/**
|
|
3924
|
+
* Get a pre-configured InboxApi for human-in-the-loop tasks.
|
|
3925
|
+
*
|
|
3926
|
+
* Requires the Database to have been created via `RaisinClient.database()`
|
|
3927
|
+
* (which automatically provides HTTP context).
|
|
3928
|
+
*
|
|
3929
|
+
* @example
|
|
3930
|
+
* ```typescript
|
|
3931
|
+
* const db = client.database('my-repo');
|
|
3932
|
+
* const { tasks } = await db.inbox.listTasks({ status: 'pending' });
|
|
3933
|
+
* await db.inbox.completeTask(tasks[0].id, { action: 'approve' });
|
|
3934
|
+
* ```
|
|
3935
|
+
*/
|
|
3936
|
+
get inbox() {
|
|
3937
|
+
if (!this._inboxApi) {
|
|
3938
|
+
if (!this.httpOptions?.httpBaseUrl || !this.httpOptions?.authManager) {
|
|
3939
|
+
throw new Error(
|
|
3940
|
+
"db.inbox requires HTTP context. Use client.database() to get a Database with inbox support."
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3943
|
+
this._inboxApi = new InboxApi(
|
|
3944
|
+
this.httpOptions.httpBaseUrl,
|
|
3945
|
+
this.repository,
|
|
3946
|
+
this.httpOptions.authManager
|
|
3947
|
+
);
|
|
3948
|
+
}
|
|
3949
|
+
return this._inboxApi;
|
|
3950
|
+
}
|
|
4851
3951
|
/**
|
|
4852
3952
|
* Get EventHandler for subscribing to real-time events
|
|
3953
|
+
*
|
|
3954
|
+
* Path filter semantics for subscriptions (matched server-side):
|
|
3955
|
+
* - A plain path matches **only that exact node** — there is no implicit
|
|
3956
|
+
* prefix matching.
|
|
3957
|
+
* - `*` matches exactly one path segment (`/inbox/*` matches `/inbox/a`,
|
|
3958
|
+
* not `/inbox/a/b`).
|
|
3959
|
+
* - `**` matches recursively (`/inbox/**` matches all descendants of
|
|
3960
|
+
* `/inbox`, but not `/inbox` itself).
|
|
4853
3961
|
*/
|
|
4854
3962
|
events() {
|
|
4855
3963
|
return this.eventHandler;
|
|
@@ -4857,7 +3965,7 @@ var Database = class _Database {
|
|
|
4857
3965
|
};
|
|
4858
3966
|
|
|
4859
3967
|
// src/http-client.ts
|
|
4860
|
-
import { EventEmitter
|
|
3968
|
+
import { EventEmitter } from "events";
|
|
4861
3969
|
|
|
4862
3970
|
// src/upload/types.ts
|
|
4863
3971
|
var UploadErrorCode = /* @__PURE__ */ ((UploadErrorCode2) => {
|
|
@@ -5220,7 +4328,7 @@ var BatchUploader = class {
|
|
|
5220
4328
|
/**
|
|
5221
4329
|
* Create a batch uploader
|
|
5222
4330
|
*/
|
|
5223
|
-
constructor(
|
|
4331
|
+
constructor(config, files, options) {
|
|
5224
4332
|
this.fileStates = /* @__PURE__ */ new Map();
|
|
5225
4333
|
this.queue = [];
|
|
5226
4334
|
this.speedTracker = new BatchSpeedTracker();
|
|
@@ -5230,7 +4338,7 @@ var BatchUploader = class {
|
|
|
5230
4338
|
this.started = false;
|
|
5231
4339
|
this.resolveWhenResumed = null;
|
|
5232
4340
|
this.lastBytesUploaded = 0;
|
|
5233
|
-
this.config =
|
|
4341
|
+
this.config = config;
|
|
5234
4342
|
this.files = files;
|
|
5235
4343
|
this.options = {
|
|
5236
4344
|
repository: options.repository,
|
|
@@ -5684,7 +4792,7 @@ var Uploader = class {
|
|
|
5684
4792
|
/**
|
|
5685
4793
|
* Create a new Uploader
|
|
5686
4794
|
*/
|
|
5687
|
-
constructor(
|
|
4795
|
+
constructor(config, fileSource, options, uploadId) {
|
|
5688
4796
|
this.session = null;
|
|
5689
4797
|
this.status = "pending";
|
|
5690
4798
|
this.bytesUploaded = 0;
|
|
@@ -5693,10 +4801,10 @@ var Uploader = class {
|
|
|
5693
4801
|
this.speedTracker = new SpeedTracker();
|
|
5694
4802
|
this.pauseRequested = false;
|
|
5695
4803
|
this.abortController = null;
|
|
5696
|
-
this.baseUrl =
|
|
5697
|
-
this.authManager =
|
|
5698
|
-
this.fetchImpl =
|
|
5699
|
-
this.requestTimeout =
|
|
4804
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
4805
|
+
this.authManager = config.authManager;
|
|
4806
|
+
this.fetchImpl = config.fetchImpl ?? fetch;
|
|
4807
|
+
this.requestTimeout = config.requestTimeout ?? 3e4;
|
|
5700
4808
|
this.fileSource = fileSource;
|
|
5701
4809
|
this.id = uploadId ?? generateUploadId();
|
|
5702
4810
|
this.options = {
|
|
@@ -6062,9 +5170,9 @@ var UploadManager = class {
|
|
|
6062
5170
|
/**
|
|
6063
5171
|
* Create an upload manager
|
|
6064
5172
|
*/
|
|
6065
|
-
constructor(
|
|
5173
|
+
constructor(config) {
|
|
6066
5174
|
this.uploads = /* @__PURE__ */ new Map();
|
|
6067
|
-
this.config =
|
|
5175
|
+
this.config = config;
|
|
6068
5176
|
}
|
|
6069
5177
|
/**
|
|
6070
5178
|
* Create a new upload
|
|
@@ -6129,7 +5237,7 @@ var UploadManager = class {
|
|
|
6129
5237
|
};
|
|
6130
5238
|
|
|
6131
5239
|
// src/http-client.ts
|
|
6132
|
-
var RaisinHttpClient = class extends
|
|
5240
|
+
var RaisinHttpClient = class extends EventEmitter {
|
|
6133
5241
|
constructor(baseUrl, options = {}) {
|
|
6134
5242
|
super();
|
|
6135
5243
|
this._currentUser = null;
|
|
@@ -6177,9 +5285,13 @@ var RaisinHttpClient = class extends EventEmitter2 {
|
|
|
6177
5285
|
body: payload,
|
|
6178
5286
|
skipAuth: true
|
|
6179
5287
|
});
|
|
6180
|
-
|
|
5288
|
+
if (response.data.token) {
|
|
5289
|
+
this.authManager.storage.setAccessToken(response.data.token);
|
|
5290
|
+
} else {
|
|
5291
|
+
this.authManager.setTokens(response.data);
|
|
5292
|
+
}
|
|
6181
5293
|
this._currentUser = {
|
|
6182
|
-
userId: "admin",
|
|
5294
|
+
userId: response.data.user_id ?? "admin",
|
|
6183
5295
|
anonymous: false
|
|
6184
5296
|
};
|
|
6185
5297
|
this.emit("authenticated", response.data);
|
|
@@ -6369,14 +5481,14 @@ var RaisinHttpClient = class extends EventEmitter2 {
|
|
|
6369
5481
|
* @param config - Repository configuration
|
|
6370
5482
|
* @returns Repository creation result
|
|
6371
5483
|
*/
|
|
6372
|
-
async createRepository(repositoryId, description,
|
|
5484
|
+
async createRepository(repositoryId, description, config) {
|
|
6373
5485
|
const response = await this.request({
|
|
6374
5486
|
method: "POST",
|
|
6375
5487
|
path: "/api/repositories",
|
|
6376
5488
|
body: {
|
|
6377
5489
|
repository_id: repositoryId,
|
|
6378
5490
|
description,
|
|
6379
|
-
config
|
|
5491
|
+
config
|
|
6380
5492
|
}
|
|
6381
5493
|
});
|
|
6382
5494
|
return response.data;
|
|
@@ -6414,13 +5526,13 @@ var RaisinHttpClient = class extends EventEmitter2 {
|
|
|
6414
5526
|
* @param config - New repository configuration
|
|
6415
5527
|
* @returns Repository update result
|
|
6416
5528
|
*/
|
|
6417
|
-
async updateRepository(repositoryId, description,
|
|
5529
|
+
async updateRepository(repositoryId, description, config) {
|
|
6418
5530
|
const response = await this.request({
|
|
6419
5531
|
method: "PUT",
|
|
6420
5532
|
path: `/api/repositories/${repositoryId}`,
|
|
6421
5533
|
body: {
|
|
6422
5534
|
description,
|
|
6423
|
-
config
|
|
5535
|
+
config
|
|
6424
5536
|
}
|
|
6425
5537
|
});
|
|
6426
5538
|
return response.data;
|
|
@@ -7067,7 +6179,7 @@ var HttpWorkspaceClient = class {
|
|
|
7067
6179
|
};
|
|
7068
6180
|
|
|
7069
6181
|
// src/client.ts
|
|
7070
|
-
var
|
|
6182
|
+
var _RaisinClient = class _RaisinClient extends EventEmitter2 {
|
|
7071
6183
|
constructor(url, options = {}) {
|
|
7072
6184
|
super();
|
|
7073
6185
|
this._currentUser = null;
|
|
@@ -7081,23 +6193,41 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7081
6193
|
// Reconnection tracking
|
|
7082
6194
|
this._previousConnectionState = "disconnected" /* Disconnected */;
|
|
7083
6195
|
this._reconnectedListeners = /* @__PURE__ */ new Set();
|
|
6196
|
+
this._everConnected = false;
|
|
6197
|
+
// Requests queued while the connection is re-establishing.
|
|
6198
|
+
// Flushed after reconnect + re-auth; bounded by MAX_QUEUED_REQUESTS.
|
|
6199
|
+
this._queuedRequests = [];
|
|
6200
|
+
/**
|
|
6201
|
+
* Backoff delays (ms) between subscription-restore retries after reconnect.
|
|
6202
|
+
* @internal - overridable in tests
|
|
6203
|
+
*/
|
|
6204
|
+
this._restoreRetryDelaysMs = [1e3, 3e3, 9e3];
|
|
7084
6205
|
// Upload manager (uses HTTP under the hood)
|
|
7085
6206
|
this._uploadManager = null;
|
|
7086
6207
|
if (options.logLevel !== void 0) {
|
|
7087
6208
|
setLogLevel(options.logLevel);
|
|
7088
6209
|
}
|
|
6210
|
+
const connectionUrl = this.buildConnectionUrl(url, options.repository);
|
|
7089
6211
|
this.options = {
|
|
7090
6212
|
requestTimeout: options.requestTimeout ?? 3e4,
|
|
7091
|
-
tenantId: options.tenantId ?? this.extractTenantFromUrl(
|
|
6213
|
+
tenantId: options.tenantId ?? this.extractTenantFromUrl(connectionUrl),
|
|
7092
6214
|
defaultBranch: options.defaultBranch ?? "main"
|
|
7093
6215
|
};
|
|
7094
|
-
this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(
|
|
7095
|
-
this._repository = this.extractRepositoryFromUrl(
|
|
6216
|
+
this._httpBaseUrl = options.httpBaseUrl ?? this.deriveHttpUrl(connectionUrl);
|
|
6217
|
+
this._repository = options.repository ?? this.extractRepositoryFromUrl(connectionUrl);
|
|
7096
6218
|
this._context = {
|
|
7097
6219
|
tenant_id: this.options.tenantId,
|
|
7098
6220
|
branch: this.options.defaultBranch
|
|
7099
6221
|
};
|
|
7100
|
-
|
|
6222
|
+
const explicitTenant = options.tenantId ?? (this.extractTenantFromUrl(url) !== "default" ? this.options.tenantId : void 0);
|
|
6223
|
+
const connectionOptions = {
|
|
6224
|
+
...options.connection,
|
|
6225
|
+
headers: {
|
|
6226
|
+
...explicitTenant ? { "x-tenant-id": explicitTenant } : {},
|
|
6227
|
+
...options.connection?.headers
|
|
6228
|
+
}
|
|
6229
|
+
};
|
|
6230
|
+
this.connection = this.createConnection(connectionUrl, connectionOptions);
|
|
7101
6231
|
this.requestTracker = new RequestTracker({
|
|
7102
6232
|
defaultTimeout: this.options.requestTimeout
|
|
7103
6233
|
});
|
|
@@ -7107,6 +6237,14 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7107
6237
|
);
|
|
7108
6238
|
this.setupConnectionHandlers();
|
|
7109
6239
|
}
|
|
6240
|
+
/**
|
|
6241
|
+
* Create the underlying WebSocket connection.
|
|
6242
|
+
* Seam for tests to inject a fake connection.
|
|
6243
|
+
* @internal
|
|
6244
|
+
*/
|
|
6245
|
+
createConnection(url, options) {
|
|
6246
|
+
return new Connection(url, options);
|
|
6247
|
+
}
|
|
7110
6248
|
/**
|
|
7111
6249
|
* Derive HTTP URL from WebSocket URL
|
|
7112
6250
|
* ws://host:port/path -> http://host:port
|
|
@@ -7122,15 +6260,45 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7122
6260
|
return "http://localhost:8081";
|
|
7123
6261
|
}
|
|
7124
6262
|
}
|
|
6263
|
+
/**
|
|
6264
|
+
* Build the WebSocket connection URL.
|
|
6265
|
+
*
|
|
6266
|
+
* When a repository option is provided and the URL carries no path
|
|
6267
|
+
* (e.g. `ws://host:8081`), build the tenant-less `/ws/{repository}`
|
|
6268
|
+
* route internally. URLs that already carry a path are used as-is.
|
|
6269
|
+
*/
|
|
6270
|
+
buildConnectionUrl(url, repository) {
|
|
6271
|
+
if (!repository) {
|
|
6272
|
+
return url;
|
|
6273
|
+
}
|
|
6274
|
+
try {
|
|
6275
|
+
const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
|
|
6276
|
+
const parsed = new URL(wsUrl);
|
|
6277
|
+
const hasPath = parsed.pathname.split("/").some((p) => p.length > 0);
|
|
6278
|
+
if (hasPath) {
|
|
6279
|
+
return url;
|
|
6280
|
+
}
|
|
6281
|
+
return `${url.replace(/\/+$/, "")}/ws/${repository}`;
|
|
6282
|
+
} catch {
|
|
6283
|
+
return url;
|
|
6284
|
+
}
|
|
6285
|
+
}
|
|
7125
6286
|
/**
|
|
7126
6287
|
* Extract repository from URL
|
|
7127
|
-
* URL
|
|
6288
|
+
* URL forms: /ws/{repo} (tenant-less, preferred), /sys/{tenant}/{repo}
|
|
6289
|
+
* (operator/multi-tenant), /{tenant}/{repo} (legacy).
|
|
7128
6290
|
*/
|
|
7129
6291
|
extractRepositoryFromUrl(url) {
|
|
7130
6292
|
try {
|
|
7131
6293
|
const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
|
|
7132
6294
|
const parsed = new URL(wsUrl);
|
|
7133
6295
|
const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
|
|
6296
|
+
if (parts[0] === "ws") {
|
|
6297
|
+
return parts.length > 1 ? parts[1] : "";
|
|
6298
|
+
}
|
|
6299
|
+
if (parts[0] === "sys") {
|
|
6300
|
+
return parts.length > 2 ? parts[2] : "";
|
|
6301
|
+
}
|
|
7134
6302
|
return parts.length > 1 ? parts[1] : "";
|
|
7135
6303
|
} catch {
|
|
7136
6304
|
return "";
|
|
@@ -7138,14 +6306,24 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7138
6306
|
}
|
|
7139
6307
|
/**
|
|
7140
6308
|
* Extract tenant ID from URL
|
|
7141
|
-
* URL
|
|
6309
|
+
* URL forms: /ws/{repo} (tenant-less — no tenant in URL),
|
|
6310
|
+
* /sys/{tenant}/{repo} (canonical), /{tenant}/{repo} (legacy).
|
|
6311
|
+
* When the URL doesn't carry a tenant, fall back to 'default' — the
|
|
6312
|
+
* server's tenant middleware resolves it anyway (production derives the
|
|
6313
|
+
* tenant from the request itself), so an explicit tenantId is optional.
|
|
7142
6314
|
*/
|
|
7143
6315
|
extractTenantFromUrl(url) {
|
|
7144
6316
|
try {
|
|
7145
6317
|
const wsUrl = url.replace(/^raisin:\/\//, "ws://").replace(/^raisins:\/\//, "wss://");
|
|
7146
6318
|
const parsed = new URL(wsUrl);
|
|
7147
6319
|
const parts = parsed.pathname.split("/").filter((p) => p.length > 0);
|
|
7148
|
-
|
|
6320
|
+
if (parts[0] === "ws") {
|
|
6321
|
+
return "default";
|
|
6322
|
+
}
|
|
6323
|
+
if (parts[0] === "sys") {
|
|
6324
|
+
return parts.length > 1 ? parts[1] : "default";
|
|
6325
|
+
}
|
|
6326
|
+
return parts.length >= 2 ? parts[0] : "default";
|
|
7149
6327
|
} catch (error) {
|
|
7150
6328
|
return "default";
|
|
7151
6329
|
}
|
|
@@ -7159,7 +6337,11 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7159
6337
|
});
|
|
7160
6338
|
this.connection.on("stateChange", (state) => {
|
|
7161
6339
|
const wasDisconnected = this._previousConnectionState === "disconnected" /* Disconnected */ || this._previousConnectionState === "reconnecting" /* Reconnecting */;
|
|
6340
|
+
if (state === "connected" /* Connected */) {
|
|
6341
|
+
this._everConnected = true;
|
|
6342
|
+
}
|
|
7162
6343
|
if (state === "disconnected" /* Disconnected */ || state === "closed" /* Closed */) {
|
|
6344
|
+
this._queuedRequests = [];
|
|
7163
6345
|
this.requestTracker.cancelAll();
|
|
7164
6346
|
}
|
|
7165
6347
|
if (state === "connected" /* Connected */ && wasDisconnected && this._previousConnectionState !== "disconnected" /* Disconnected */) {
|
|
@@ -7224,19 +6406,36 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7224
6406
|
};
|
|
7225
6407
|
logger.debug(`Anonymous user info stored: ${message.user_id}`);
|
|
7226
6408
|
this._updateReadyState();
|
|
6409
|
+
this._flushQueuedRequests();
|
|
7227
6410
|
if (hasActiveSubscriptions) {
|
|
7228
6411
|
this._restoreSubscriptionsAndNotify();
|
|
7229
6412
|
}
|
|
7230
6413
|
} else {
|
|
7231
6414
|
logger.info(`[handleConnectedMessage] Stored token found, auto-authenticating...`);
|
|
7232
|
-
this.autoReauthenticate().then(async () => {
|
|
6415
|
+
this.autoReauthenticate().then(async (authenticated) => {
|
|
6416
|
+
if (authenticated) {
|
|
6417
|
+
this._flushQueuedRequests();
|
|
6418
|
+
} else {
|
|
6419
|
+
this._rejectQueuedRequests(
|
|
6420
|
+
new RaisinAuthError(
|
|
6421
|
+
"Re-authentication failed after reconnect",
|
|
6422
|
+
"AUTH_TOKEN_EXPIRED",
|
|
6423
|
+
401
|
|
6424
|
+
)
|
|
6425
|
+
);
|
|
6426
|
+
}
|
|
7233
6427
|
if (hasActiveSubscriptions) {
|
|
7234
6428
|
await this._restoreSubscriptionsAndNotify();
|
|
7235
6429
|
}
|
|
7236
6430
|
}).catch((err) => {
|
|
7237
6431
|
logger.error(`[handleConnectedMessage] Auto-authenticate failed:`, err);
|
|
6432
|
+
this._rejectQueuedRequests(
|
|
6433
|
+
err instanceof Error ? err : new Error("Re-authentication failed after reconnect")
|
|
6434
|
+
);
|
|
7238
6435
|
});
|
|
7239
6436
|
}
|
|
6437
|
+
} else {
|
|
6438
|
+
this._flushQueuedRequests();
|
|
7240
6439
|
}
|
|
7241
6440
|
this.emit("connected", {
|
|
7242
6441
|
connectionId: message.connection_id,
|
|
@@ -7245,18 +6444,41 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7245
6444
|
});
|
|
7246
6445
|
}
|
|
7247
6446
|
/**
|
|
7248
|
-
* Restore subscriptions after reconnection and notify listeners
|
|
6447
|
+
* Restore subscriptions after reconnection and notify listeners.
|
|
6448
|
+
*
|
|
6449
|
+
* Retries with backoff (see `_restoreRetryDelaysMs`); on final failure a
|
|
6450
|
+
* `subscription_restore_failed` event is emitted so applications can
|
|
6451
|
+
* resync their state (e.g. reload lists and re-subscribe manually).
|
|
7249
6452
|
* @internal
|
|
7250
6453
|
*/
|
|
7251
6454
|
async _restoreSubscriptionsAndNotify() {
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
6455
|
+
const delays = this._restoreRetryDelaysMs;
|
|
6456
|
+
let lastError;
|
|
6457
|
+
for (let attempt = 0; attempt <= delays.length; attempt++) {
|
|
6458
|
+
try {
|
|
6459
|
+
logger.info(
|
|
6460
|
+
`[_restoreSubscriptionsAndNotify] Restoring subscriptions (attempt ${attempt + 1}/${delays.length + 1})...`
|
|
6461
|
+
);
|
|
6462
|
+
await this.eventHandler.restoreSubscriptions({ retry: attempt > 0 });
|
|
6463
|
+
logger.info("[_restoreSubscriptionsAndNotify] Subscriptions restored successfully");
|
|
6464
|
+
this._emitReconnected();
|
|
6465
|
+
return;
|
|
6466
|
+
} catch (error) {
|
|
6467
|
+
lastError = error;
|
|
6468
|
+
logger.error(
|
|
6469
|
+
`[_restoreSubscriptionsAndNotify] Restore attempt ${attempt + 1} failed:`,
|
|
6470
|
+
error
|
|
6471
|
+
);
|
|
6472
|
+
if (attempt < delays.length) {
|
|
6473
|
+
await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
7259
6476
|
}
|
|
6477
|
+
logger.error("[_restoreSubscriptionsAndNotify] Giving up after retries; events may be stale");
|
|
6478
|
+
this.emit(
|
|
6479
|
+
"subscription_restore_failed",
|
|
6480
|
+
lastError instanceof Error ? lastError : new Error(String(lastError))
|
|
6481
|
+
);
|
|
7260
6482
|
}
|
|
7261
6483
|
/**
|
|
7262
6484
|
* Emit reconnected event to all listeners
|
|
@@ -7275,13 +6497,15 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7275
6497
|
}
|
|
7276
6498
|
/**
|
|
7277
6499
|
* Auto-reauthenticate with stored JWT on reconnection
|
|
6500
|
+
*
|
|
6501
|
+
* @returns true if the session was re-authenticated, false otherwise
|
|
7278
6502
|
* @internal
|
|
7279
6503
|
*/
|
|
7280
6504
|
async autoReauthenticate() {
|
|
7281
6505
|
const token = this.authManager.storage.getAccessToken();
|
|
7282
6506
|
if (!token) {
|
|
7283
6507
|
logger.warn("[autoReauthenticate] No stored token");
|
|
7284
|
-
return;
|
|
6508
|
+
return false;
|
|
7285
6509
|
}
|
|
7286
6510
|
const payload = AuthManager.parseToken(token);
|
|
7287
6511
|
if (payload?.exp && typeof payload.exp === "number") {
|
|
@@ -7293,18 +6517,20 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7293
6517
|
this.authManager.clear();
|
|
7294
6518
|
this._currentUser = null;
|
|
7295
6519
|
this._emitAuthEvent("SESSION_EXPIRED");
|
|
7296
|
-
return;
|
|
6520
|
+
return false;
|
|
7297
6521
|
}
|
|
7298
6522
|
const newToken = this.authManager.storage.getAccessToken();
|
|
7299
6523
|
if (newToken) {
|
|
7300
6524
|
await this.authenticate({ type: "jwt", token: newToken });
|
|
6525
|
+
return true;
|
|
7301
6526
|
}
|
|
7302
|
-
return;
|
|
6527
|
+
return false;
|
|
7303
6528
|
}
|
|
7304
6529
|
}
|
|
7305
6530
|
try {
|
|
7306
6531
|
await this.authenticate({ type: "jwt", token });
|
|
7307
6532
|
logger.info("[autoReauthenticate] Successfully re-authenticated");
|
|
6533
|
+
return true;
|
|
7308
6534
|
} catch (err) {
|
|
7309
6535
|
logger.error("[autoReauthenticate] Failed to authenticate:", err);
|
|
7310
6536
|
const refreshed = await this.refreshToken();
|
|
@@ -7312,11 +6538,14 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7312
6538
|
const newToken = this.authManager.storage.getAccessToken();
|
|
7313
6539
|
if (newToken) {
|
|
7314
6540
|
await this.authenticate({ type: "jwt", token: newToken });
|
|
6541
|
+
return true;
|
|
7315
6542
|
}
|
|
6543
|
+
return false;
|
|
7316
6544
|
} else {
|
|
7317
6545
|
this.authManager.clear();
|
|
7318
6546
|
this._currentUser = null;
|
|
7319
6547
|
this._emitAuthEvent("SESSION_EXPIRED");
|
|
6548
|
+
return false;
|
|
7320
6549
|
}
|
|
7321
6550
|
}
|
|
7322
6551
|
}
|
|
@@ -7432,18 +6661,18 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7432
6661
|
}
|
|
7433
6662
|
const url = `${this._httpBaseUrl}/auth/${repository}/login`;
|
|
7434
6663
|
logger.debug("[loginWithEmail] POST", url);
|
|
7435
|
-
const response = await
|
|
6664
|
+
const response = await this._fetchWithTimeout(url, {
|
|
7436
6665
|
method: "POST",
|
|
7437
6666
|
headers: { "Content-Type": "application/json" },
|
|
7438
6667
|
body: JSON.stringify({ email, password })
|
|
7439
6668
|
});
|
|
7440
6669
|
if (!response.ok) {
|
|
7441
6670
|
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
6671
|
+
throw new RaisinAuthError(
|
|
6672
|
+
error.message || "Login failed",
|
|
6673
|
+
error.code || "LOGIN_FAILED",
|
|
6674
|
+
response.status
|
|
6675
|
+
);
|
|
7447
6676
|
}
|
|
7448
6677
|
const tokens = await response.json();
|
|
7449
6678
|
logger.info("[loginWithEmail] HTTP login successful, user_id:", tokens.identity.id);
|
|
@@ -7476,7 +6705,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7476
6705
|
anonymous: false,
|
|
7477
6706
|
node: userNode ?? void 0
|
|
7478
6707
|
};
|
|
7479
|
-
const expiresInMs = tokens.expires_at
|
|
6708
|
+
const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
|
|
7480
6709
|
if (expiresInMs > 0) {
|
|
7481
6710
|
this.scheduleAutoRefresh(expiresInMs);
|
|
7482
6711
|
}
|
|
@@ -7500,18 +6729,18 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7500
6729
|
async registerWithEmail(email, password, repository, displayName) {
|
|
7501
6730
|
logger.info("[registerWithEmail] Starting registration...");
|
|
7502
6731
|
const url = `${this._httpBaseUrl}/auth/${repository}/register`;
|
|
7503
|
-
const response = await
|
|
6732
|
+
const response = await this._fetchWithTimeout(url, {
|
|
7504
6733
|
method: "POST",
|
|
7505
6734
|
headers: { "Content-Type": "application/json" },
|
|
7506
6735
|
body: JSON.stringify({ email, password, display_name: displayName })
|
|
7507
6736
|
});
|
|
7508
6737
|
if (!response.ok) {
|
|
7509
6738
|
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
6739
|
+
throw new RaisinAuthError(
|
|
6740
|
+
error.message || "Registration failed",
|
|
6741
|
+
error.code || "REGISTRATION_FAILED",
|
|
6742
|
+
response.status
|
|
6743
|
+
);
|
|
7515
6744
|
}
|
|
7516
6745
|
const tokens = await response.json();
|
|
7517
6746
|
logger.info("[registerWithEmail] Registration successful, user_id:", tokens.identity.id);
|
|
@@ -7542,7 +6771,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7542
6771
|
anonymous: false,
|
|
7543
6772
|
node: userNode ?? void 0
|
|
7544
6773
|
};
|
|
7545
|
-
const expiresInMs = tokens.expires_at
|
|
6774
|
+
const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
|
|
7546
6775
|
if (expiresInMs > 0) {
|
|
7547
6776
|
this.scheduleAutoRefresh(expiresInMs);
|
|
7548
6777
|
}
|
|
@@ -7638,7 +6867,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7638
6867
|
if (refreshToken) {
|
|
7639
6868
|
try {
|
|
7640
6869
|
const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
|
|
7641
|
-
const response = await
|
|
6870
|
+
const response = await this._fetchWithTimeout(url, {
|
|
7642
6871
|
method: "POST",
|
|
7643
6872
|
headers: { "Content-Type": "application/json" },
|
|
7644
6873
|
body: JSON.stringify({ refresh_token: refreshToken })
|
|
@@ -7647,7 +6876,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7647
6876
|
const tokens = await response.json();
|
|
7648
6877
|
this.authManager.storage.setAccessToken(tokens.access_token);
|
|
7649
6878
|
this.authManager.storage.setRefreshToken(tokens.refresh_token);
|
|
7650
|
-
const expiresInMs = tokens.expires_at
|
|
6879
|
+
const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
|
|
7651
6880
|
if (expiresInMs > 0) {
|
|
7652
6881
|
this.scheduleAutoRefresh(expiresInMs);
|
|
7653
6882
|
}
|
|
@@ -7764,7 +6993,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7764
6993
|
try {
|
|
7765
6994
|
logger.info("[refreshToken] Refreshing access token...");
|
|
7766
6995
|
const url = this._repository ? `${this._httpBaseUrl}/auth/${this._repository}/refresh` : `${this._httpBaseUrl}/auth/refresh`;
|
|
7767
|
-
const response = await
|
|
6996
|
+
const response = await this._fetchWithTimeout(url, {
|
|
7768
6997
|
method: "POST",
|
|
7769
6998
|
headers: { "Content-Type": "application/json" },
|
|
7770
6999
|
body: JSON.stringify({ refresh_token: refreshToken })
|
|
@@ -7794,7 +7023,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7794
7023
|
if (this.authManager.storage instanceof LocalStorageTokenStorage) {
|
|
7795
7024
|
this.authManager.storage.setUser(user);
|
|
7796
7025
|
}
|
|
7797
|
-
const expiresInMs = tokens.expires_at
|
|
7026
|
+
const expiresInMs = _RaisinClient.expiresAtToMs(tokens.expires_at) - Date.now() - 5 * 60 * 1e3;
|
|
7798
7027
|
if (expiresInMs > 0) {
|
|
7799
7028
|
this.scheduleAutoRefresh(expiresInMs);
|
|
7800
7029
|
}
|
|
@@ -7808,18 +7037,66 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7808
7037
|
}
|
|
7809
7038
|
}
|
|
7810
7039
|
/**
|
|
7811
|
-
*
|
|
7040
|
+
* Fetch with a timeout derived from `requestTimeout`.
|
|
7041
|
+
*
|
|
7042
|
+
* Aborts the request after `requestTimeout` ms and rejects with a
|
|
7043
|
+
* `RaisinTimeoutError`. If the caller provides its own AbortSignal in
|
|
7044
|
+
* `init.signal`, it is respected: an external abort rejects with the
|
|
7045
|
+
* original AbortError instead of a timeout error.
|
|
7812
7046
|
* @internal
|
|
7813
7047
|
*/
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7048
|
+
async _fetchWithTimeout(url, init = {}) {
|
|
7049
|
+
const timeoutMs = this.options.requestTimeout;
|
|
7050
|
+
const controller = new AbortController();
|
|
7051
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7052
|
+
const externalSignal = init.signal ?? void 0;
|
|
7053
|
+
if (externalSignal) {
|
|
7054
|
+
if (externalSignal.aborted) {
|
|
7055
|
+
controller.abort();
|
|
7056
|
+
} else {
|
|
7057
|
+
externalSignal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
7058
|
+
}
|
|
7817
7059
|
}
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7822
|
-
|
|
7060
|
+
try {
|
|
7061
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
7062
|
+
} catch (error) {
|
|
7063
|
+
if (error instanceof Error && error.name === "AbortError" && !externalSignal?.aborted) {
|
|
7064
|
+
throw new RaisinTimeoutError(
|
|
7065
|
+
`Request to ${url} timed out after ${timeoutMs}ms`,
|
|
7066
|
+
"REQUEST_TIMEOUT",
|
|
7067
|
+
timeoutMs
|
|
7068
|
+
);
|
|
7069
|
+
}
|
|
7070
|
+
throw error;
|
|
7071
|
+
} finally {
|
|
7072
|
+
clearTimeout(timer);
|
|
7073
|
+
}
|
|
7074
|
+
}
|
|
7075
|
+
/**
|
|
7076
|
+
* Normalize a token `expires_at` value to epoch milliseconds.
|
|
7077
|
+
*
|
|
7078
|
+
* Server endpoints are inconsistent: some return seconds, others
|
|
7079
|
+
* milliseconds. Anything above 1e11 can't be a seconds-epoch (year 5138),
|
|
7080
|
+
* so treat it as milliseconds.
|
|
7081
|
+
* @internal
|
|
7082
|
+
*/
|
|
7083
|
+
static expiresAtToMs(expiresAt) {
|
|
7084
|
+
return expiresAt > 1e11 ? expiresAt : expiresAt * 1e3;
|
|
7085
|
+
}
|
|
7086
|
+
/**
|
|
7087
|
+
* Schedule automatic token refresh
|
|
7088
|
+
* @internal
|
|
7089
|
+
*/
|
|
7090
|
+
scheduleAutoRefresh(delayMs) {
|
|
7091
|
+
if (this._refreshTimer) {
|
|
7092
|
+
clearTimeout(this._refreshTimer);
|
|
7093
|
+
}
|
|
7094
|
+
const clamped = Math.min(Math.max(delayMs, 3e4), 2147483647);
|
|
7095
|
+
logger.debug(`[scheduleAutoRefresh] Scheduling refresh in ${Math.round(clamped / 1e3 / 60)} minutes`);
|
|
7096
|
+
this._refreshTimer = setTimeout(async () => {
|
|
7097
|
+
logger.info("[autoRefresh] Auto-refreshing token...");
|
|
7098
|
+
await this.refreshToken();
|
|
7099
|
+
}, clamped);
|
|
7823
7100
|
}
|
|
7824
7101
|
/**
|
|
7825
7102
|
* Get a database/repository interface
|
|
@@ -7843,10 +7120,26 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7843
7120
|
}
|
|
7844
7121
|
/**
|
|
7845
7122
|
* Send a request to the server
|
|
7123
|
+
*
|
|
7124
|
+
* If the connection is temporarily down (connecting/reconnecting, or
|
|
7125
|
+
* disconnected with auto-reconnect enabled), the request is queued and
|
|
7126
|
+
* flushed once the connection is re-established and re-authenticated.
|
|
7127
|
+
* Queued requests are still subject to their own request timeout, and the
|
|
7128
|
+
* queue is bounded (overflow rejects immediately).
|
|
7846
7129
|
*/
|
|
7847
7130
|
async sendRequestInternal(payload, requestType, contextOverride, requestOptions) {
|
|
7848
|
-
|
|
7849
|
-
|
|
7131
|
+
const connected = this.connection.isConnected();
|
|
7132
|
+
if (!connected) {
|
|
7133
|
+
const state = this.connection.getState();
|
|
7134
|
+
const willReconnect = state === "connecting" /* Connecting */ || state === "reconnecting" /* Reconnecting */ || state === "disconnected" /* Disconnected */ && this._everConnected && this.connection.isAutoReconnectEnabled();
|
|
7135
|
+
if (!willReconnect) {
|
|
7136
|
+
throw new Error("Not connected to server");
|
|
7137
|
+
}
|
|
7138
|
+
if (this._queuedRequests.length >= _RaisinClient.MAX_QUEUED_REQUESTS) {
|
|
7139
|
+
throw new Error(
|
|
7140
|
+
`Request queue is full (${_RaisinClient.MAX_QUEUED_REQUESTS} requests pending while reconnecting)`
|
|
7141
|
+
);
|
|
7142
|
+
}
|
|
7850
7143
|
}
|
|
7851
7144
|
const requestId = this.requestTracker.generateRequestId();
|
|
7852
7145
|
const context = contextOverride || this._context;
|
|
@@ -7865,8 +7158,15 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7865
7158
|
try {
|
|
7866
7159
|
const encoded = encodeMessage(request);
|
|
7867
7160
|
logger.debug(`Encoded request - ID: ${requestId}, type: ${requestType}, size: ${encoded.length} bytes`);
|
|
7868
|
-
|
|
7869
|
-
|
|
7161
|
+
if (connected) {
|
|
7162
|
+
this.connection.send(encoded);
|
|
7163
|
+
logger.debug(`Request sent - ID: ${requestId}`);
|
|
7164
|
+
} else {
|
|
7165
|
+
this._queuedRequests.push({ requestId, encoded });
|
|
7166
|
+
logger.debug(
|
|
7167
|
+
`Request queued while reconnecting - ID: ${requestId}, queue size: ${this._queuedRequests.length}`
|
|
7168
|
+
);
|
|
7169
|
+
}
|
|
7870
7170
|
} catch (error) {
|
|
7871
7171
|
logger.error(`Failed to send request - ID: ${requestId}:`, error);
|
|
7872
7172
|
this.requestTracker.rejectRequest(
|
|
@@ -7877,6 +7177,42 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
7877
7177
|
}
|
|
7878
7178
|
return responsePromise;
|
|
7879
7179
|
}
|
|
7180
|
+
/**
|
|
7181
|
+
* Flush requests queued while the connection was re-establishing.
|
|
7182
|
+
* Called after the connection is connected and (re-)authenticated.
|
|
7183
|
+
* @internal
|
|
7184
|
+
*/
|
|
7185
|
+
_flushQueuedRequests() {
|
|
7186
|
+
if (this._queuedRequests.length === 0) return;
|
|
7187
|
+
const queued = this._queuedRequests;
|
|
7188
|
+
this._queuedRequests = [];
|
|
7189
|
+
logger.info(`[_flushQueuedRequests] Flushing ${queued.length} queued request(s)`);
|
|
7190
|
+
for (const { requestId, encoded } of queued) {
|
|
7191
|
+
if (!this.requestTracker.hasPendingRequest(requestId)) continue;
|
|
7192
|
+
try {
|
|
7193
|
+
this.connection.send(encoded);
|
|
7194
|
+
} catch (error) {
|
|
7195
|
+
this.requestTracker.rejectRequest(
|
|
7196
|
+
requestId,
|
|
7197
|
+
error instanceof Error ? error : new Error("Failed to send queued request")
|
|
7198
|
+
);
|
|
7199
|
+
}
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
/**
|
|
7203
|
+
* Reject all requests queued while the connection was re-establishing
|
|
7204
|
+
* (e.g. re-authentication failed after reconnect).
|
|
7205
|
+
* @internal
|
|
7206
|
+
*/
|
|
7207
|
+
_rejectQueuedRequests(error) {
|
|
7208
|
+
if (this._queuedRequests.length === 0) return;
|
|
7209
|
+
const queued = this._queuedRequests;
|
|
7210
|
+
this._queuedRequests = [];
|
|
7211
|
+
logger.warn(`[_rejectQueuedRequests] Rejecting ${queued.length} queued request(s): ${error.message}`);
|
|
7212
|
+
for (const { requestId } of queued) {
|
|
7213
|
+
this.requestTracker.rejectRequest(requestId, error);
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
7880
7216
|
/**
|
|
7881
7217
|
* Get the HTTP base URL derived from the WebSocket URL
|
|
7882
7218
|
*/
|
|
@@ -8202,6 +7538,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
8202
7538
|
*/
|
|
8203
7539
|
async _subscribeToUserHome(repository, userHome) {
|
|
8204
7540
|
await this._unsubscribeFromUserHome();
|
|
7541
|
+
userHome = normalizeHomePath(userHome) ?? userHome;
|
|
8205
7542
|
try {
|
|
8206
7543
|
const db = this.database(repository);
|
|
8207
7544
|
const pathParts = userHome.split("/").filter((p) => p.length > 0);
|
|
@@ -8254,12 +7591,12 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
8254
7591
|
* @param config - Repository configuration
|
|
8255
7592
|
* @returns Repository creation result
|
|
8256
7593
|
*/
|
|
8257
|
-
async createRepository(repositoryId, description,
|
|
7594
|
+
async createRepository(repositoryId, description, config) {
|
|
8258
7595
|
return this.sendRequestInternal(
|
|
8259
7596
|
{
|
|
8260
7597
|
repository_id: repositoryId,
|
|
8261
7598
|
description,
|
|
8262
|
-
config
|
|
7599
|
+
config
|
|
8263
7600
|
},
|
|
8264
7601
|
"repository_create" /* RepositoryCreate */
|
|
8265
7602
|
);
|
|
@@ -8298,12 +7635,12 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
8298
7635
|
* @param config - New repository configuration
|
|
8299
7636
|
* @returns Repository update result
|
|
8300
7637
|
*/
|
|
8301
|
-
async updateRepository(repositoryId, description,
|
|
7638
|
+
async updateRepository(repositoryId, description, config) {
|
|
8302
7639
|
return this.sendRequestInternal(
|
|
8303
7640
|
{
|
|
8304
7641
|
repository_id: repositoryId,
|
|
8305
7642
|
description,
|
|
8306
|
-
config
|
|
7643
|
+
config
|
|
8307
7644
|
},
|
|
8308
7645
|
"repository_update" /* RepositoryUpdate */
|
|
8309
7646
|
);
|
|
@@ -8501,7 +7838,7 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
8501
7838
|
const branch = options.branch ?? this._context.branch ?? "main";
|
|
8502
7839
|
const pathWithProperty = options.propertyPath ? `${options.path}@${options.propertyPath}` : options.path;
|
|
8503
7840
|
const endpoint = `/api/repository/${options.repository}/${branch}/head/${options.workspace}${pathWithProperty}/raisin:sign`;
|
|
8504
|
-
const response = await
|
|
7841
|
+
const response = await this._fetchWithTimeout(`${this._httpBaseUrl}${endpoint}`, {
|
|
8505
7842
|
method: "POST",
|
|
8506
7843
|
headers: {
|
|
8507
7844
|
"Content-Type": "application/json",
|
|
@@ -8556,6 +7893,9 @@ var RaisinClient = class _RaisinClient extends EventEmitter3 {
|
|
|
8556
7893
|
return _RaisinClient.forSSR(baseUrl, options);
|
|
8557
7894
|
}
|
|
8558
7895
|
};
|
|
7896
|
+
/** Hard cap on requests held while reconnecting; overflow rejects immediately. */
|
|
7897
|
+
_RaisinClient.MAX_QUEUED_REQUESTS = 100;
|
|
7898
|
+
var RaisinClient = _RaisinClient;
|
|
8559
7899
|
|
|
8560
7900
|
// src/node-builder.ts
|
|
8561
7901
|
var NodeBuilder = class _NodeBuilder {
|
|
@@ -8840,17 +8180,17 @@ var NodeEventType = {
|
|
|
8840
8180
|
var AllNodeEventTypes = Object.values(NodeEventType);
|
|
8841
8181
|
|
|
8842
8182
|
// src/integrations/react-router.ts
|
|
8843
|
-
function createSSRClient(
|
|
8844
|
-
const client = RaisinClient.forSSR(
|
|
8845
|
-
if (
|
|
8183
|
+
function createSSRClient(config) {
|
|
8184
|
+
const client = RaisinClient.forSSR(config.httpBaseUrl, config.httpOptions);
|
|
8185
|
+
if (config.credentials) {
|
|
8846
8186
|
}
|
|
8847
8187
|
return client;
|
|
8848
8188
|
}
|
|
8849
|
-
function createLoader(
|
|
8189
|
+
function createLoader(config, loaderFn) {
|
|
8850
8190
|
return async (args) => {
|
|
8851
|
-
const client = createSSRClient(
|
|
8852
|
-
if (
|
|
8853
|
-
await client.authenticate(
|
|
8191
|
+
const client = createSSRClient(config);
|
|
8192
|
+
if (config.credentials) {
|
|
8193
|
+
await client.authenticate(config.credentials);
|
|
8854
8194
|
}
|
|
8855
8195
|
try {
|
|
8856
8196
|
return await loaderFn(client, args);
|
|
@@ -8890,1846 +8230,27 @@ function isTerminalEvent(event) {
|
|
|
8890
8230
|
return event.type === "flow_completed" || event.type === "flow_failed";
|
|
8891
8231
|
}
|
|
8892
8232
|
|
|
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
|
-
};
|
|
8233
|
+
// src/types/flow-definition.ts
|
|
8234
|
+
function getRefDisplayName(ref) {
|
|
8235
|
+
const path = ref["raisin:path"] || ref["raisin:ref"];
|
|
8236
|
+
const segments = path.split("/").filter(Boolean);
|
|
8237
|
+
return segments[segments.length - 1] || path;
|
|
8933
8238
|
}
|
|
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;
|
|
8950
|
-
}
|
|
8951
|
-
function projectPlansFromMessages(messages) {
|
|
8952
|
-
const ordered = [...messages].sort((a, b) => {
|
|
8953
|
-
const aTime = Date.parse(a.timestamp || "");
|
|
8954
|
-
const bTime = Date.parse(b.timestamp || "");
|
|
8955
|
-
return (Number.isNaN(aTime) ? 0 : aTime) - (Number.isNaN(bTime) ? 0 : bTime);
|
|
8956
|
-
});
|
|
8957
|
-
const plans = /* @__PURE__ */ new Map();
|
|
8958
|
-
for (const message of ordered) {
|
|
8959
|
-
const timestamp = message.timestamp;
|
|
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
|
-
});
|
|
8239
|
+
function getRefPath(ref) {
|
|
8240
|
+
return ref["raisin:path"] || ref["raisin:ref"];
|
|
9022
8241
|
}
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
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
|
-
}
|
|
8242
|
+
var RETRY_STRATEGIES = {
|
|
8243
|
+
none: { max_retries: 0, base_delay_ms: 0, max_delay_ms: 0 },
|
|
8244
|
+
quick: { max_retries: 3, base_delay_ms: 1e3, max_delay_ms: 1e4 },
|
|
8245
|
+
standard: { max_retries: 5, base_delay_ms: 2e3, max_delay_ms: 6e4 },
|
|
8246
|
+
aggressive: { max_retries: 10, base_delay_ms: 5e3, max_delay_ms: 12e4 },
|
|
8247
|
+
llm: { max_retries: 5, base_delay_ms: 1e4, max_delay_ms: 12e4 }
|
|
9596
8248
|
};
|
|
9597
|
-
|
|
9598
|
-
|
|
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
|
-
};
|
|
8249
|
+
function isFlowDefinitionStep(node) {
|
|
8250
|
+
return node.node_type === "raisin:FlowStep";
|
|
10541
8251
|
}
|
|
10542
|
-
|
|
10543
|
-
|
|
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
|
-
};
|
|
8252
|
+
function isFlowDefinitionContainer(node) {
|
|
8253
|
+
return node.node_type === "raisin:FlowContainer";
|
|
10733
8254
|
}
|
|
10734
8255
|
export {
|
|
10735
8256
|
AllNodeEventTypes,
|
|
@@ -10751,6 +8272,7 @@ export {
|
|
|
10751
8272
|
DEFAULT_RETRY_BASE_DELAY,
|
|
10752
8273
|
DEFAULT_RETRY_MAX_DELAY,
|
|
10753
8274
|
DEFAULT_RETRY_OPTIONS,
|
|
8275
|
+
DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS,
|
|
10754
8276
|
Database,
|
|
10755
8277
|
ElementTypes,
|
|
10756
8278
|
EventHandler,
|
|
@@ -10761,6 +8283,7 @@ export {
|
|
|
10761
8283
|
HttpDatabase,
|
|
10762
8284
|
HttpFunctionsApi,
|
|
10763
8285
|
HttpWorkspaceClient,
|
|
8286
|
+
InboxApi,
|
|
10764
8287
|
LocalStorageTokenStorage,
|
|
10765
8288
|
LogLevel,
|
|
10766
8289
|
MemoryTokenStorage,
|
|
@@ -10771,6 +8294,7 @@ export {
|
|
|
10771
8294
|
NodeOperations,
|
|
10772
8295
|
NodeTypes,
|
|
10773
8296
|
RAISIN_CONTEXT_KEY,
|
|
8297
|
+
RETRY_STRATEGIES,
|
|
10774
8298
|
RaisinAbortError,
|
|
10775
8299
|
RaisinAuthError,
|
|
10776
8300
|
RaisinClient,
|
|
@@ -10806,6 +8330,7 @@ export {
|
|
|
10806
8330
|
createFlowAdapter,
|
|
10807
8331
|
createLoader,
|
|
10808
8332
|
createRaisinReact,
|
|
8333
|
+
createRaisinVue,
|
|
10809
8334
|
createRequestScopedClient,
|
|
10810
8335
|
createSSRClient,
|
|
10811
8336
|
createSqlAdapter,
|
|
@@ -10818,13 +8343,18 @@ export {
|
|
|
10818
8343
|
getErrorMessage,
|
|
10819
8344
|
getLogLevel,
|
|
10820
8345
|
getLoggerConfig,
|
|
8346
|
+
getRefDisplayName,
|
|
8347
|
+
getRefPath,
|
|
10821
8348
|
isEventMessage,
|
|
8349
|
+
isFlowDefinitionContainer,
|
|
8350
|
+
isFlowDefinitionStep,
|
|
10822
8351
|
isHttpClient,
|
|
10823
8352
|
isResponseEnvelope,
|
|
10824
8353
|
isRetryableError,
|
|
10825
8354
|
isTerminalEvent,
|
|
10826
8355
|
isWebSocketClient,
|
|
10827
8356
|
logger,
|
|
8357
|
+
normalizeHomePath,
|
|
10828
8358
|
projectPlansFromMessages,
|
|
10829
8359
|
rowsToObjects,
|
|
10830
8360
|
setLogLevel,
|
|
@@ -10834,3 +8364,4 @@ export {
|
|
|
10834
8364
|
useFlow,
|
|
10835
8365
|
withRetry
|
|
10836
8366
|
};
|
|
8367
|
+
//# sourceMappingURL=index.mjs.map
|