@radishbot/sdk 0.7.1 → 0.8.0
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/dist/index.d.ts +2 -2
- package/dist/index.js +37 -105
- package/package.json +1 -1
- package/src/connection.ts +2 -78
- package/src/index.ts +55 -78
- package/src/module_bindings/add_log_reducer.ts +1 -1
- package/src/module_bindings/add_logs_batch_reducer.ts +1 -1
- package/src/module_bindings/create_sub_flow_reducer.ts +1 -1
- package/src/module_bindings/finish_flow_reducer.ts +1 -1
- package/src/module_bindings/start_action_reducer.ts +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ export declare class Flow {
|
|
|
12
12
|
a<T>(name: string, fn: (action: Flow) => T | Promise<T>, timeoutSeconds?: number): Promise<T>;
|
|
13
13
|
finish(): Promise<void>;
|
|
14
14
|
finishWithError(err?: Error | string): Promise<void>;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
readonly token: string;
|
|
16
|
+
exportID(): string;
|
|
17
17
|
disconnect(): void;
|
|
18
18
|
finishAndDisconnect(): Promise<void>;
|
|
19
19
|
}
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "spacetimedb";
|
|
19
19
|
var add_log_reducer_default = {
|
|
20
20
|
keyHash: __t.string(),
|
|
21
|
-
|
|
21
|
+
exportToken: __t.string(),
|
|
22
22
|
level: __t.string(),
|
|
23
23
|
message: __t.string(),
|
|
24
24
|
data: __t.string()
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from "spacetimedb";
|
|
31
31
|
var add_logs_batch_reducer_default = {
|
|
32
32
|
keyHash: __t2.string(),
|
|
33
|
-
|
|
33
|
+
exportToken: __t2.string(),
|
|
34
34
|
entries: __t2.string()
|
|
35
35
|
};
|
|
36
36
|
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
} from "spacetimedb";
|
|
53
53
|
var create_sub_flow_reducer_default = {
|
|
54
54
|
keyHash: __t4.string(),
|
|
55
|
-
|
|
55
|
+
parentExportToken: __t4.string(),
|
|
56
56
|
name: __t4.string(),
|
|
57
57
|
timeoutSeconds: __t4.u64(),
|
|
58
58
|
exportToken: __t4.string()
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
} from "spacetimedb";
|
|
75
75
|
var finish_flow_reducer_default = {
|
|
76
76
|
keyHash: __t6.string(),
|
|
77
|
-
|
|
77
|
+
exportToken: __t6.string(),
|
|
78
78
|
status: __t6.string(),
|
|
79
79
|
errorMessage: __t6.string()
|
|
80
80
|
};
|
|
@@ -95,7 +95,7 @@ import {
|
|
|
95
95
|
} from "spacetimedb";
|
|
96
96
|
var start_action_reducer_default = {
|
|
97
97
|
keyHash: __t8.string(),
|
|
98
|
-
|
|
98
|
+
exportToken: __t8.string(),
|
|
99
99
|
name: __t8.string()
|
|
100
100
|
};
|
|
101
101
|
|
|
@@ -283,15 +283,10 @@ class SdkConnection {
|
|
|
283
283
|
_dbName;
|
|
284
284
|
_conn = null;
|
|
285
285
|
_connectPromise = null;
|
|
286
|
-
_keyHash = null;
|
|
287
|
-
_flowWaiters = new Map;
|
|
288
286
|
constructor(host, dbName) {
|
|
289
287
|
this._host = host;
|
|
290
288
|
this._dbName = dbName;
|
|
291
289
|
}
|
|
292
|
-
setKeyHash(keyHash) {
|
|
293
|
-
this._keyHash = keyHash;
|
|
294
|
-
}
|
|
295
290
|
async connect() {
|
|
296
291
|
if (this._conn)
|
|
297
292
|
return this._conn;
|
|
@@ -300,14 +295,7 @@ class SdkConnection {
|
|
|
300
295
|
this._connectPromise = new Promise((resolve, reject) => {
|
|
301
296
|
DbConnection.builder().withUri(this._host).withDatabaseName(this._dbName).onConnect((c, _identity, _token) => {
|
|
302
297
|
this._conn = c;
|
|
303
|
-
c
|
|
304
|
-
const waiter = this._flowWaiters.get(row.exportToken);
|
|
305
|
-
if (waiter) {
|
|
306
|
-
this._flowWaiters.delete(row.exportToken);
|
|
307
|
-
waiter(row.id);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
c.subscriptionBuilder().onApplied(() => resolve(c)).subscribeToAllTables();
|
|
298
|
+
resolve(c);
|
|
311
299
|
}).onConnectError((_ctx, err) => {
|
|
312
300
|
reject(new Error(`SpacetimeDB connection failed: ${err}`));
|
|
313
301
|
}).build();
|
|
@@ -319,47 +307,6 @@ class SdkConnection {
|
|
|
319
307
|
throw new Error("Not connected");
|
|
320
308
|
return this._conn;
|
|
321
309
|
}
|
|
322
|
-
async createFlowAndResolveId(reducerCall, exportToken) {
|
|
323
|
-
const conn = this.conn;
|
|
324
|
-
for (const f of conn.db.flow.iter()) {
|
|
325
|
-
if (f.exportToken === exportToken)
|
|
326
|
-
return f.id;
|
|
327
|
-
}
|
|
328
|
-
return new Promise((resolve, reject) => {
|
|
329
|
-
let resolved = false;
|
|
330
|
-
this._flowWaiters.set(exportToken, (id) => {
|
|
331
|
-
if (!resolved) {
|
|
332
|
-
resolved = true;
|
|
333
|
-
clearInterval(interval);
|
|
334
|
-
resolve(id);
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
reducerCall();
|
|
338
|
-
const interval = setInterval(() => {
|
|
339
|
-
if (resolved) {
|
|
340
|
-
clearInterval(interval);
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
for (const f of conn.db.flow.iter()) {
|
|
344
|
-
if (f.exportToken === exportToken) {
|
|
345
|
-
resolved = true;
|
|
346
|
-
clearInterval(interval);
|
|
347
|
-
this._flowWaiters.delete(exportToken);
|
|
348
|
-
resolve(f.id);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}, 100);
|
|
353
|
-
setTimeout(() => {
|
|
354
|
-
if (!resolved) {
|
|
355
|
-
resolved = true;
|
|
356
|
-
clearInterval(interval);
|
|
357
|
-
this._flowWaiters.delete(exportToken);
|
|
358
|
-
reject(new Error(`Flow creation timed out for token ${exportToken}`));
|
|
359
|
-
}
|
|
360
|
-
}, 15000);
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
310
|
disconnect() {
|
|
364
311
|
if (this._conn) {
|
|
365
312
|
this._conn.disconnect();
|
|
@@ -441,7 +388,6 @@ function generateToken() {
|
|
|
441
388
|
class Flow {
|
|
442
389
|
_sdk;
|
|
443
390
|
_keyHash;
|
|
444
|
-
_id = null;
|
|
445
391
|
_exportToken;
|
|
446
392
|
_ready;
|
|
447
393
|
_resolveReady;
|
|
@@ -449,15 +395,15 @@ class Flow {
|
|
|
449
395
|
_flushScheduled = false;
|
|
450
396
|
_isReady = false;
|
|
451
397
|
_finished = false;
|
|
452
|
-
|
|
398
|
+
_parentExportToken;
|
|
453
399
|
_name;
|
|
454
400
|
_timeoutSeconds;
|
|
455
401
|
_release;
|
|
456
402
|
_runId;
|
|
457
|
-
constructor(sdk, keyHash,
|
|
403
|
+
constructor(sdk, keyHash, parentExportToken, name, timeoutSeconds, release = "", runId = "") {
|
|
458
404
|
this._sdk = sdk;
|
|
459
405
|
this._keyHash = keyHash;
|
|
460
|
-
this.
|
|
406
|
+
this._parentExportToken = parentExportToken;
|
|
461
407
|
this._name = name;
|
|
462
408
|
this._timeoutSeconds = !timeoutSeconds || timeoutSeconds === Infinity ? 0n : BigInt(timeoutSeconds);
|
|
463
409
|
this._release = release;
|
|
@@ -467,31 +413,24 @@ class Flow {
|
|
|
467
413
|
this._resolveReady = resolve;
|
|
468
414
|
});
|
|
469
415
|
}
|
|
470
|
-
|
|
416
|
+
_create() {
|
|
471
417
|
const conn = this._sdk.conn;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (this._parentId === 0n) {
|
|
476
|
-
const id = await this._sdk.createFlowAndResolveId(() => conn.reducers.createRootFlow({
|
|
477
|
-
keyHash,
|
|
418
|
+
if (this._parentExportToken === null) {
|
|
419
|
+
conn.reducers.createRootFlow({
|
|
420
|
+
keyHash: this._keyHash,
|
|
478
421
|
runId: this._runId,
|
|
479
|
-
timeoutSeconds,
|
|
480
|
-
exportToken,
|
|
422
|
+
timeoutSeconds: this._timeoutSeconds,
|
|
423
|
+
exportToken: this._exportToken,
|
|
481
424
|
release: this._release
|
|
482
|
-
})
|
|
483
|
-
this._id = id;
|
|
425
|
+
});
|
|
484
426
|
} else {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
exportToken
|
|
493
|
-
}), exportToken);
|
|
494
|
-
this._id = id;
|
|
427
|
+
conn.reducers.createSubFlow({
|
|
428
|
+
keyHash: this._keyHash,
|
|
429
|
+
parentExportToken: this._parentExportToken,
|
|
430
|
+
name: this._name,
|
|
431
|
+
timeoutSeconds: this._timeoutSeconds,
|
|
432
|
+
exportToken: this._exportToken
|
|
433
|
+
});
|
|
495
434
|
}
|
|
496
435
|
this._isReady = true;
|
|
497
436
|
this._resolveReady();
|
|
@@ -500,7 +439,7 @@ class Flow {
|
|
|
500
439
|
static MAX_PENDING = 1e4;
|
|
501
440
|
log(message, data, level = "info") {
|
|
502
441
|
if (this._finished) {
|
|
503
|
-
|
|
442
|
+
_origConsole.warn(`[radish] Cannot log to finished flow`);
|
|
504
443
|
return this;
|
|
505
444
|
}
|
|
506
445
|
if (this._pendingLogs.length >= Flow.MAX_PENDING) {
|
|
@@ -528,9 +467,8 @@ class Flow {
|
|
|
528
467
|
return this.log(message, data, "debug");
|
|
529
468
|
}
|
|
530
469
|
action(name, timeoutSeconds = 100) {
|
|
531
|
-
const child = new Flow(this._sdk, this._keyHash,
|
|
470
|
+
const child = new Flow(this._sdk, this._keyHash, this._exportToken, name, timeoutSeconds, this._release, this._runId);
|
|
532
471
|
this._ready.then(() => {
|
|
533
|
-
child._parentId = this._id;
|
|
534
472
|
child._create();
|
|
535
473
|
});
|
|
536
474
|
return child;
|
|
@@ -561,7 +499,7 @@ class Flow {
|
|
|
561
499
|
this._finished = true;
|
|
562
500
|
this._sdk.conn.reducers.finishFlow({
|
|
563
501
|
keyHash: this._keyHash,
|
|
564
|
-
|
|
502
|
+
exportToken: this._exportToken,
|
|
565
503
|
status: "finished",
|
|
566
504
|
errorMessage: ""
|
|
567
505
|
});
|
|
@@ -576,7 +514,7 @@ class Flow {
|
|
|
576
514
|
this._finished = true;
|
|
577
515
|
this._sdk.conn.reducers.finishFlow({
|
|
578
516
|
keyHash: this._keyHash,
|
|
579
|
-
|
|
517
|
+
exportToken: this._exportToken,
|
|
580
518
|
status: "error",
|
|
581
519
|
errorMessage
|
|
582
520
|
});
|
|
@@ -584,19 +522,16 @@ class Flow {
|
|
|
584
522
|
get runId() {
|
|
585
523
|
return this._runId;
|
|
586
524
|
}
|
|
587
|
-
|
|
588
|
-
|
|
525
|
+
get token() {
|
|
526
|
+
return this._exportToken;
|
|
527
|
+
}
|
|
528
|
+
exportID() {
|
|
589
529
|
return JSON.stringify({
|
|
590
|
-
flowId: this._id.toString(),
|
|
591
530
|
exportToken: this._exportToken,
|
|
592
531
|
keyHash: this._keyHash,
|
|
593
532
|
runId: this._runId
|
|
594
533
|
});
|
|
595
534
|
}
|
|
596
|
-
async getId() {
|
|
597
|
-
await this._ready;
|
|
598
|
-
return this._id;
|
|
599
|
-
}
|
|
600
535
|
disconnect() {
|
|
601
536
|
this._sdk.disconnect();
|
|
602
537
|
}
|
|
@@ -623,7 +558,7 @@ class Flow {
|
|
|
623
558
|
const e = batch[0];
|
|
624
559
|
conn.reducers.addLog({
|
|
625
560
|
keyHash: this._keyHash,
|
|
626
|
-
|
|
561
|
+
exportToken: this._exportToken,
|
|
627
562
|
level: e.level,
|
|
628
563
|
message: e.message,
|
|
629
564
|
data: e.data
|
|
@@ -631,7 +566,7 @@ class Flow {
|
|
|
631
566
|
} else {
|
|
632
567
|
conn.reducers.addLogsBatch({
|
|
633
568
|
keyHash: this._keyHash,
|
|
634
|
-
|
|
569
|
+
exportToken: this._exportToken,
|
|
635
570
|
entries: JSON.stringify(batch)
|
|
636
571
|
});
|
|
637
572
|
}
|
|
@@ -656,16 +591,13 @@ async function RL(secretKey, options = {}) {
|
|
|
656
591
|
const retentionDays = parseRetention(retention);
|
|
657
592
|
const keyHash = await hashKey(secretKey);
|
|
658
593
|
const sdk = new SdkConnection(host, dbName);
|
|
659
|
-
sdk.setKeyHash(keyHash);
|
|
660
594
|
try {
|
|
661
595
|
await sdk.connect();
|
|
662
596
|
} catch (e) {
|
|
663
597
|
throw new Error(`Failed to connect to Radish (${host}/${dbName}): ${e instanceof Error ? e.message : e}`);
|
|
664
598
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
} catch {}
|
|
668
|
-
const root = new Flow(sdk, keyHash, 0n, "/", 0, release, runId);
|
|
599
|
+
sdk.conn.reducers.registerKey({ keyHash, label, retentionDays: BigInt(retentionDays) });
|
|
600
|
+
const root = new Flow(sdk, keyHash, null, "/", 0, release, runId);
|
|
669
601
|
root._create();
|
|
670
602
|
return root;
|
|
671
603
|
}
|
|
@@ -678,9 +610,9 @@ async function restoreFlow(secretKey, exportedId, options = {}) {
|
|
|
678
610
|
}
|
|
679
611
|
const sdk = new SdkConnection(host, dbName);
|
|
680
612
|
await sdk.connect();
|
|
681
|
-
const flow = new Flow(sdk, keyHash,
|
|
682
|
-
flow._id = BigInt(parsed.flowId);
|
|
613
|
+
const flow = new Flow(sdk, keyHash, null, "restored", 100, "", parsed.runId || "");
|
|
683
614
|
flow._exportToken = parsed.exportToken;
|
|
615
|
+
flow._isReady = true;
|
|
684
616
|
flow._resolveReady();
|
|
685
617
|
return flow;
|
|
686
618
|
}
|
package/package.json
CHANGED
package/src/connection.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SpacetimeDB connection wrapper for the Radish SDK.
|
|
3
|
-
*
|
|
3
|
+
* No subscription needed — all operations use exportToken (fire-and-forget reducers).
|
|
4
4
|
*/
|
|
5
5
|
import { DbConnection as StdbConnection } from "./module_bindings";
|
|
6
6
|
|
|
@@ -9,20 +9,12 @@ export class SdkConnection {
|
|
|
9
9
|
private _dbName: string;
|
|
10
10
|
private _conn: StdbConnection | null = null;
|
|
11
11
|
private _connectPromise: Promise<StdbConnection> | null = null;
|
|
12
|
-
private _keyHash: string | null = null;
|
|
13
|
-
|
|
14
|
-
// Callbacks waiting for flow creation by exportToken
|
|
15
|
-
private _flowWaiters = new Map<string, (id: bigint) => void>();
|
|
16
12
|
|
|
17
13
|
constructor(host: string, dbName: string) {
|
|
18
14
|
this._host = host;
|
|
19
15
|
this._dbName = dbName;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
setKeyHash(keyHash: string): void {
|
|
23
|
-
this._keyHash = keyHash;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
18
|
async connect(): Promise<StdbConnection> {
|
|
27
19
|
if (this._conn) return this._conn;
|
|
28
20
|
if (this._connectPromise) return this._connectPromise;
|
|
@@ -33,19 +25,7 @@ export class SdkConnection {
|
|
|
33
25
|
.withDatabaseName(this._dbName)
|
|
34
26
|
.onConnect((c, _identity, _token) => {
|
|
35
27
|
this._conn = c;
|
|
36
|
-
|
|
37
|
-
// Resolve flow waiters immediately when new flows arrive
|
|
38
|
-
c.db.flow.onInsert((_ctx, row) => {
|
|
39
|
-
const waiter = this._flowWaiters.get(row.exportToken);
|
|
40
|
-
if (waiter) {
|
|
41
|
-
this._flowWaiters.delete(row.exportToken);
|
|
42
|
-
waiter(row.id);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
c.subscriptionBuilder()
|
|
47
|
-
.onApplied(() => resolve(c))
|
|
48
|
-
.subscribeToAllTables();
|
|
28
|
+
resolve(c);
|
|
49
29
|
})
|
|
50
30
|
.onConnectError((_ctx, err) => {
|
|
51
31
|
reject(new Error(`SpacetimeDB connection failed: ${err}`));
|
|
@@ -61,62 +41,6 @@ export class SdkConnection {
|
|
|
61
41
|
return this._conn;
|
|
62
42
|
}
|
|
63
43
|
|
|
64
|
-
/**
|
|
65
|
-
* Call a reducer and wait for the flow to appear in the subscription.
|
|
66
|
-
* Returns the server-assigned flow ID.
|
|
67
|
-
*/
|
|
68
|
-
async createFlowAndResolveId(reducerCall: () => void, exportToken: string): Promise<bigint> {
|
|
69
|
-
const conn = this.conn;
|
|
70
|
-
|
|
71
|
-
// Check if flow already exists (from a previous subscription update)
|
|
72
|
-
for (const f of conn.db.flow.iter()) {
|
|
73
|
-
if (f.exportToken === exportToken) return f.id;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return new Promise<bigint>((resolve, reject) => {
|
|
77
|
-
let resolved = false;
|
|
78
|
-
|
|
79
|
-
// Register waiter — triggered by onInsert handler in connect()
|
|
80
|
-
this._flowWaiters.set(exportToken, (id) => {
|
|
81
|
-
if (!resolved) {
|
|
82
|
-
resolved = true;
|
|
83
|
-
clearInterval(interval);
|
|
84
|
-
resolve(id);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Call the reducer
|
|
89
|
-
reducerCall();
|
|
90
|
-
|
|
91
|
-
// Poll as fallback in case onInsert fires before waiter is registered
|
|
92
|
-
const interval = setInterval(() => {
|
|
93
|
-
if (resolved) {
|
|
94
|
-
clearInterval(interval);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
for (const f of conn.db.flow.iter()) {
|
|
98
|
-
if (f.exportToken === exportToken) {
|
|
99
|
-
resolved = true;
|
|
100
|
-
clearInterval(interval);
|
|
101
|
-
this._flowWaiters.delete(exportToken);
|
|
102
|
-
resolve(f.id);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}, 100);
|
|
107
|
-
|
|
108
|
-
// Timeout after 15 seconds (properly rejects the promise)
|
|
109
|
-
setTimeout(() => {
|
|
110
|
-
if (!resolved) {
|
|
111
|
-
resolved = true;
|
|
112
|
-
clearInterval(interval);
|
|
113
|
-
this._flowWaiters.delete(exportToken);
|
|
114
|
-
reject(new Error(`Flow creation timed out for token ${exportToken}`));
|
|
115
|
-
}
|
|
116
|
-
}, 15_000);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
44
|
disconnect(): void {
|
|
121
45
|
if (this._conn) {
|
|
122
46
|
this._conn.disconnect();
|
package/src/index.ts
CHANGED
|
@@ -85,7 +85,6 @@ function generateToken(): string {
|
|
|
85
85
|
export class Flow {
|
|
86
86
|
private _sdk: SdkConnection;
|
|
87
87
|
private _keyHash: string;
|
|
88
|
-
private _id: bigint | null = null;
|
|
89
88
|
private _exportToken: string;
|
|
90
89
|
private _ready: Promise<void>;
|
|
91
90
|
private _resolveReady!: () => void;
|
|
@@ -93,7 +92,7 @@ export class Flow {
|
|
|
93
92
|
private _flushScheduled = false;
|
|
94
93
|
private _isReady = false;
|
|
95
94
|
private _finished = false;
|
|
96
|
-
private
|
|
95
|
+
private _parentExportToken: string | null;
|
|
97
96
|
private _name: string;
|
|
98
97
|
private _timeoutSeconds: bigint;
|
|
99
98
|
private _release: string;
|
|
@@ -103,7 +102,7 @@ export class Flow {
|
|
|
103
102
|
constructor(
|
|
104
103
|
sdk: SdkConnection,
|
|
105
104
|
keyHash: string,
|
|
106
|
-
|
|
105
|
+
parentExportToken: string | null,
|
|
107
106
|
name: string,
|
|
108
107
|
timeoutSeconds: number,
|
|
109
108
|
release: string = "",
|
|
@@ -111,7 +110,7 @@ export class Flow {
|
|
|
111
110
|
) {
|
|
112
111
|
this._sdk = sdk;
|
|
113
112
|
this._keyHash = keyHash;
|
|
114
|
-
this.
|
|
113
|
+
this._parentExportToken = parentExportToken;
|
|
115
114
|
this._name = name;
|
|
116
115
|
this._timeoutSeconds = !timeoutSeconds || timeoutSeconds === Infinity ? 0n : BigInt(timeoutSeconds);
|
|
117
116
|
this._release = release;
|
|
@@ -122,45 +121,33 @@ export class Flow {
|
|
|
122
121
|
});
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
/** @internal */
|
|
126
|
-
|
|
124
|
+
/** @internal — fire-and-forget: sends creation reducer, resolves immediately */
|
|
125
|
+
_create(): void {
|
|
127
126
|
const conn = this._sdk.conn;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
runId: this._runId,
|
|
138
|
-
timeoutSeconds,
|
|
139
|
-
exportToken,
|
|
140
|
-
release: this._release,
|
|
141
|
-
}),
|
|
142
|
-
exportToken,
|
|
143
|
-
);
|
|
144
|
-
this._id = id;
|
|
127
|
+
|
|
128
|
+
if (this._parentExportToken === null) {
|
|
129
|
+
conn.reducers.createRootFlow({
|
|
130
|
+
keyHash: this._keyHash,
|
|
131
|
+
runId: this._runId,
|
|
132
|
+
timeoutSeconds: this._timeoutSeconds,
|
|
133
|
+
exportToken: this._exportToken,
|
|
134
|
+
release: this._release,
|
|
135
|
+
});
|
|
145
136
|
} else {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
name,
|
|
154
|
-
timeoutSeconds,
|
|
155
|
-
exportToken,
|
|
156
|
-
}),
|
|
157
|
-
exportToken,
|
|
158
|
-
);
|
|
159
|
-
this._id = id;
|
|
137
|
+
conn.reducers.createSubFlow({
|
|
138
|
+
keyHash: this._keyHash,
|
|
139
|
+
parentExportToken: this._parentExportToken,
|
|
140
|
+
name: this._name,
|
|
141
|
+
timeoutSeconds: this._timeoutSeconds,
|
|
142
|
+
exportToken: this._exportToken,
|
|
143
|
+
});
|
|
160
144
|
}
|
|
145
|
+
|
|
146
|
+
// Ready immediately — server processes messages in order,
|
|
147
|
+
// so any subsequent reducers run after the creation above
|
|
161
148
|
this._isReady = true;
|
|
162
149
|
this._resolveReady();
|
|
163
|
-
this._drain();
|
|
150
|
+
this._drain();
|
|
164
151
|
}
|
|
165
152
|
|
|
166
153
|
/** Max pending logs before oldest are dropped (prevents memory issues at high volume) */
|
|
@@ -169,10 +156,9 @@ export class Flow {
|
|
|
169
156
|
/** Log a message with optional data */
|
|
170
157
|
log(message: string, data?: unknown, level: LogLevel = "info"): this {
|
|
171
158
|
if (this._finished) {
|
|
172
|
-
|
|
159
|
+
_origConsole.warn(`[radish] Cannot log to finished flow`);
|
|
173
160
|
return this;
|
|
174
161
|
}
|
|
175
|
-
// Drop oldest logs if buffer is full (backpressure)
|
|
176
162
|
if (this._pendingLogs.length >= Flow.MAX_PENDING) {
|
|
177
163
|
const dropped = this._pendingLogs.length - Flow.MAX_PENDING + 1;
|
|
178
164
|
this._pendingLogs.splice(0, dropped);
|
|
@@ -199,11 +185,18 @@ export class Flow {
|
|
|
199
185
|
return this.log(message, data, "debug");
|
|
200
186
|
}
|
|
201
187
|
|
|
202
|
-
/** Create a sub-
|
|
188
|
+
/** Create a sub-flow. Returns immediately — creation runs in background. */
|
|
203
189
|
action(name: string, timeoutSeconds = 100): Flow {
|
|
204
|
-
const child = new Flow(
|
|
190
|
+
const child = new Flow(
|
|
191
|
+
this._sdk,
|
|
192
|
+
this._keyHash,
|
|
193
|
+
this._exportToken,
|
|
194
|
+
name,
|
|
195
|
+
timeoutSeconds,
|
|
196
|
+
this._release,
|
|
197
|
+
this._runId,
|
|
198
|
+
);
|
|
205
199
|
this._ready.then(() => {
|
|
206
|
-
(child as any)._parentId = this._id!;
|
|
207
200
|
child._create();
|
|
208
201
|
});
|
|
209
202
|
return child;
|
|
@@ -235,11 +228,11 @@ export class Flow {
|
|
|
235
228
|
/** Finish this flow successfully */
|
|
236
229
|
async finish(): Promise<void> {
|
|
237
230
|
await this._ready;
|
|
238
|
-
this._drain();
|
|
231
|
+
this._drain();
|
|
239
232
|
this._finished = true;
|
|
240
233
|
this._sdk.conn.reducers.finishFlow({
|
|
241
234
|
keyHash: this._keyHash,
|
|
242
|
-
|
|
235
|
+
exportToken: this._exportToken,
|
|
243
236
|
status: "finished",
|
|
244
237
|
errorMessage: "",
|
|
245
238
|
});
|
|
@@ -256,7 +249,7 @@ export class Flow {
|
|
|
256
249
|
this._finished = true;
|
|
257
250
|
this._sdk.conn.reducers.finishFlow({
|
|
258
251
|
keyHash: this._keyHash,
|
|
259
|
-
|
|
252
|
+
exportToken: this._exportToken,
|
|
260
253
|
status: "error",
|
|
261
254
|
errorMessage,
|
|
262
255
|
});
|
|
@@ -267,23 +260,20 @@ export class Flow {
|
|
|
267
260
|
return this._runId;
|
|
268
261
|
}
|
|
269
262
|
|
|
263
|
+
/** Get the export token for this flow */
|
|
264
|
+
get token(): string {
|
|
265
|
+
return this._exportToken;
|
|
266
|
+
}
|
|
267
|
+
|
|
270
268
|
/** Export this flow's handle for restoration in another context */
|
|
271
|
-
|
|
272
|
-
await this._ready;
|
|
269
|
+
exportID(): string {
|
|
273
270
|
return JSON.stringify({
|
|
274
|
-
flowId: this._id!.toString(),
|
|
275
271
|
exportToken: this._exportToken,
|
|
276
272
|
keyHash: this._keyHash,
|
|
277
273
|
runId: this._runId,
|
|
278
274
|
});
|
|
279
275
|
}
|
|
280
276
|
|
|
281
|
-
/** Get the resolved server-assigned flow ID */
|
|
282
|
-
async getId(): Promise<bigint> {
|
|
283
|
-
await this._ready;
|
|
284
|
-
return this._id!;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
277
|
/** Disconnect the underlying SpacetimeDB connection */
|
|
288
278
|
disconnect(): void {
|
|
289
279
|
this._sdk.disconnect();
|
|
@@ -292,7 +282,6 @@ export class Flow {
|
|
|
292
282
|
/** Finish the flow and disconnect */
|
|
293
283
|
async finishAndDisconnect(): Promise<void> {
|
|
294
284
|
await this.finish();
|
|
295
|
-
// Small delay to allow the finish reducer to be sent
|
|
296
285
|
await new Promise((r) => setTimeout(r, 100));
|
|
297
286
|
this.disconnect();
|
|
298
287
|
}
|
|
@@ -317,7 +306,7 @@ export class Flow {
|
|
|
317
306
|
const e = batch[0];
|
|
318
307
|
conn.reducers.addLog({
|
|
319
308
|
keyHash: this._keyHash,
|
|
320
|
-
|
|
309
|
+
exportToken: this._exportToken,
|
|
321
310
|
level: e.level,
|
|
322
311
|
message: e.message,
|
|
323
312
|
data: e.data,
|
|
@@ -325,7 +314,7 @@ export class Flow {
|
|
|
325
314
|
} else {
|
|
326
315
|
conn.reducers.addLogsBatch({
|
|
327
316
|
keyHash: this._keyHash,
|
|
328
|
-
|
|
317
|
+
exportToken: this._exportToken,
|
|
329
318
|
entries: JSON.stringify(batch),
|
|
330
319
|
});
|
|
331
320
|
}
|
|
@@ -339,7 +328,6 @@ export interface RLOptions {
|
|
|
339
328
|
defaultTimeout?: number;
|
|
340
329
|
release?: string;
|
|
341
330
|
retention?: string;
|
|
342
|
-
/** Run ID — groups flows across processes. Auto-generated if not provided. */
|
|
343
331
|
runId?: string;
|
|
344
332
|
}
|
|
345
333
|
|
|
@@ -350,24 +338,18 @@ function parseRetention(retention: string): number {
|
|
|
350
338
|
}
|
|
351
339
|
|
|
352
340
|
/**
|
|
353
|
-
* Create a Radish Logger instance. Returns the root
|
|
341
|
+
* Create a Radish Logger instance. Returns the root flow.
|
|
354
342
|
*
|
|
355
343
|
* ```ts
|
|
356
344
|
* const root = await RL('my-secret-key');
|
|
357
345
|
*
|
|
358
|
-
* // Auto-managed — auto-finish on return, auto-error on throw
|
|
359
346
|
* await root.a('request', async (req) => {
|
|
360
347
|
* console.log('GET /api/users');
|
|
361
348
|
* const rows = await req.a('db_query', () => db.query('SELECT ...'));
|
|
362
349
|
* console.log('Done', { count: rows.length });
|
|
363
350
|
* });
|
|
364
351
|
*
|
|
365
|
-
*
|
|
366
|
-
* const ws = root.action('websocket');
|
|
367
|
-
* ws.info('Connected');
|
|
368
|
-
* await ws.finish();
|
|
369
|
-
*
|
|
370
|
-
* await root.finish();
|
|
352
|
+
* await root.finishAndDisconnect();
|
|
371
353
|
* ```
|
|
372
354
|
*/
|
|
373
355
|
export async function RL(secretKey: string, options: RLOptions = {}): Promise<Flow> {
|
|
@@ -384,7 +366,6 @@ export async function RL(secretKey: string, options: RLOptions = {}): Promise<Fl
|
|
|
384
366
|
const retentionDays = parseRetention(retention);
|
|
385
367
|
const keyHash = await hashKey(secretKey);
|
|
386
368
|
const sdk = new SdkConnection(host, dbName);
|
|
387
|
-
sdk.setKeyHash(keyHash);
|
|
388
369
|
try {
|
|
389
370
|
await sdk.connect();
|
|
390
371
|
} catch (e) {
|
|
@@ -392,15 +373,11 @@ export async function RL(secretKey: string, options: RLOptions = {}): Promise<Fl
|
|
|
392
373
|
}
|
|
393
374
|
|
|
394
375
|
// Register key (idempotent — updates retention if changed)
|
|
395
|
-
|
|
396
|
-
sdk.conn.reducers.registerKey({ keyHash, label, retentionDays: BigInt(retentionDays) });
|
|
397
|
-
} catch {
|
|
398
|
-
// Already registered
|
|
399
|
-
}
|
|
376
|
+
sdk.conn.reducers.registerKey({ keyHash, label, retentionDays: BigInt(retentionDays) });
|
|
400
377
|
|
|
401
|
-
// Root flow —
|
|
402
|
-
const root = new Flow(sdk, keyHash,
|
|
403
|
-
root._create();
|
|
378
|
+
// Root flow — fire-and-forget, ready immediately
|
|
379
|
+
const root = new Flow(sdk, keyHash, null, "/", 0, release, runId);
|
|
380
|
+
root._create();
|
|
404
381
|
return root;
|
|
405
382
|
}
|
|
406
383
|
|
|
@@ -417,9 +394,9 @@ export async function restoreFlow(secretKey: string, exportedId: string, options
|
|
|
417
394
|
const sdk = new SdkConnection(host, dbName);
|
|
418
395
|
await sdk.connect();
|
|
419
396
|
|
|
420
|
-
const flow = new Flow(sdk, keyHash,
|
|
421
|
-
(flow as any)._id = BigInt(parsed.flowId);
|
|
397
|
+
const flow = new Flow(sdk, keyHash, null, "restored", 100, "", parsed.runId || "");
|
|
422
398
|
(flow as any)._exportToken = parsed.exportToken;
|
|
399
|
+
(flow as any)._isReady = true;
|
|
423
400
|
(flow as any)._resolveReady();
|
|
424
401
|
return flow;
|
|
425
402
|
}
|