@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 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
- exportID(): Promise<string>;
16
- getId(): Promise<bigint>;
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
- flowId: __t.u64(),
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
- flowId: __t2.u64(),
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
- parentFlowId: __t4.u64(),
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
- flowId: __t6.u64(),
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
- flowId: __t8.u64(),
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.db.flow.onInsert((_ctx, row) => {
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
- _parentId;
398
+ _parentExportToken;
453
399
  _name;
454
400
  _timeoutSeconds;
455
401
  _release;
456
402
  _runId;
457
- constructor(sdk, keyHash, parentId, name, timeoutSeconds, release = "", runId = "") {
403
+ constructor(sdk, keyHash, parentExportToken, name, timeoutSeconds, release = "", runId = "") {
458
404
  this._sdk = sdk;
459
405
  this._keyHash = keyHash;
460
- this._parentId = parentId;
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
- async _create() {
416
+ _create() {
471
417
  const conn = this._sdk.conn;
472
- const exportToken = this._exportToken;
473
- const keyHash = this._keyHash;
474
- const timeoutSeconds = this._timeoutSeconds;
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
- }), exportToken);
483
- this._id = id;
425
+ });
484
426
  } else {
485
- const parentFlowId = this._parentId;
486
- const name = this._name;
487
- const id = await this._sdk.createFlowAndResolveId(() => conn.reducers.createSubFlow({
488
- keyHash,
489
- parentFlowId,
490
- name,
491
- timeoutSeconds,
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
- console.warn(`[radish] Cannot log to finished flow`);
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, 0n, name, timeoutSeconds, this._release, this._runId);
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
- flowId: this._id,
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
- flowId: this._id,
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
- async exportID() {
588
- await this._ready;
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
- flowId: this._id,
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
- flowId: this._id,
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
- try {
666
- sdk.conn.reducers.registerKey({ keyHash, label, retentionDays: BigInt(retentionDays) });
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, 0n, "restored", 100, "", parsed.runId || "");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radishbot/sdk",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/connection.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * SpacetimeDB connection wrapper for the Radish SDK.
3
- * Uses the generated bindings + SpacetimeDB WebSocket client.
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 _parentId: bigint;
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
- parentId: bigint,
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._parentId = parentId;
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
- async _create(): Promise<void> {
124
+ /** @internal — fire-and-forget: sends creation reducer, resolves immediately */
125
+ _create(): void {
127
126
  const conn = this._sdk.conn;
128
- const exportToken = this._exportToken;
129
- const keyHash = this._keyHash;
130
- const timeoutSeconds = this._timeoutSeconds;
131
-
132
- if (this._parentId === 0n) {
133
- const id = await this._sdk.createFlowAndResolveId(
134
- () =>
135
- conn.reducers.createRootFlow({
136
- keyHash,
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
- const parentFlowId = this._parentId;
147
- const name = this._name;
148
- const id = await this._sdk.createFlowAndResolveId(
149
- () =>
150
- conn.reducers.createSubFlow({
151
- keyHash,
152
- parentFlowId,
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(); // flush any logs queued before ready
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
- console.warn(`[radish] Cannot log to finished flow`);
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-action. Returns immediately — creation runs in background. */
188
+ /** Create a sub-flow. Returns immediately — creation runs in background. */
203
189
  action(name: string, timeoutSeconds = 100): Flow {
204
- const child = new Flow(this._sdk, this._keyHash, 0n, name, timeoutSeconds, this._release, this._runId);
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(); // sync flush — sends over same WS, ordering preserved
231
+ this._drain();
239
232
  this._finished = true;
240
233
  this._sdk.conn.reducers.finishFlow({
241
234
  keyHash: this._keyHash,
242
- flowId: this._id!,
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
- flowId: this._id!,
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
- async exportID(): Promise<string> {
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
- flowId: this._id!,
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
- flowId: this._id!,
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 action.
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
- * // Manual — when you need more control
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
- try {
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 — creation runs in background, logs queue until ready
402
- const root = new Flow(sdk, keyHash, 0n, "/", 0, release, runId);
403
- root._create(); // fire-and-forget — resolves _ready when server assigns ID
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, 0n, "restored", 100, "", parsed.runId || "");
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
  }
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  export default {
14
14
  keyHash: __t.string(),
15
- flowId: __t.u64(),
15
+ exportToken: __t.string(),
16
16
  level: __t.string(),
17
17
  message: __t.string(),
18
18
  data: __t.string(),
@@ -12,6 +12,6 @@ import {
12
12
 
13
13
  export default {
14
14
  keyHash: __t.string(),
15
- flowId: __t.u64(),
15
+ exportToken: __t.string(),
16
16
  entries: __t.string(),
17
17
  };
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  export default {
14
14
  keyHash: __t.string(),
15
- parentFlowId: __t.u64(),
15
+ parentExportToken: __t.string(),
16
16
  name: __t.string(),
17
17
  timeoutSeconds: __t.u64(),
18
18
  exportToken: __t.string(),
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  export default {
14
14
  keyHash: __t.string(),
15
- flowId: __t.u64(),
15
+ exportToken: __t.string(),
16
16
  status: __t.string(),
17
17
  errorMessage: __t.string(),
18
18
  };
@@ -12,6 +12,6 @@ import {
12
12
 
13
13
  export default {
14
14
  keyHash: __t.string(),
15
- flowId: __t.u64(),
15
+ exportToken: __t.string(),
16
16
  name: __t.string(),
17
17
  };