@radishbot/sdk 0.7.2 → 0.8.1

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/cli.js ADDED
@@ -0,0 +1,696 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/module_bindings/index.ts
4
+ import {
5
+ DbConnectionBuilder as __DbConnectionBuilder,
6
+ DbConnectionImpl as __DbConnectionImpl,
7
+ SubscriptionBuilderImpl as __SubscriptionBuilderImpl,
8
+ convertToAccessorMap as __convertToAccessorMap,
9
+ makeQueryBuilder as __makeQueryBuilder,
10
+ procedures as __procedures,
11
+ reducerSchema as __reducerSchema,
12
+ reducers as __reducers,
13
+ schema as __schema,
14
+ table as __table
15
+ } from "spacetimedb";
16
+
17
+ // src/module_bindings/add_log_reducer.ts
18
+ import {
19
+ t as __t
20
+ } from "spacetimedb";
21
+ var add_log_reducer_default = {
22
+ keyHash: __t.string(),
23
+ exportToken: __t.string(),
24
+ level: __t.string(),
25
+ message: __t.string(),
26
+ data: __t.string()
27
+ };
28
+
29
+ // src/module_bindings/add_logs_batch_reducer.ts
30
+ import {
31
+ t as __t2
32
+ } from "spacetimedb";
33
+ var add_logs_batch_reducer_default = {
34
+ keyHash: __t2.string(),
35
+ exportToken: __t2.string(),
36
+ entries: __t2.string()
37
+ };
38
+
39
+ // src/module_bindings/create_root_flow_reducer.ts
40
+ import {
41
+ t as __t3
42
+ } from "spacetimedb";
43
+ var create_root_flow_reducer_default = {
44
+ keyHash: __t3.string(),
45
+ runId: __t3.string(),
46
+ timeoutSeconds: __t3.u64(),
47
+ exportToken: __t3.string(),
48
+ release: __t3.string()
49
+ };
50
+
51
+ // src/module_bindings/create_sub_flow_reducer.ts
52
+ import {
53
+ t as __t4
54
+ } from "spacetimedb";
55
+ var create_sub_flow_reducer_default = {
56
+ keyHash: __t4.string(),
57
+ parentExportToken: __t4.string(),
58
+ name: __t4.string(),
59
+ timeoutSeconds: __t4.u64(),
60
+ exportToken: __t4.string()
61
+ };
62
+
63
+ // src/module_bindings/finish_action_reducer.ts
64
+ import {
65
+ t as __t5
66
+ } from "spacetimedb";
67
+ var finish_action_reducer_default = {
68
+ keyHash: __t5.string(),
69
+ actionId: __t5.u64(),
70
+ status: __t5.string()
71
+ };
72
+
73
+ // src/module_bindings/finish_flow_reducer.ts
74
+ import {
75
+ t as __t6
76
+ } from "spacetimedb";
77
+ var finish_flow_reducer_default = {
78
+ keyHash: __t6.string(),
79
+ exportToken: __t6.string(),
80
+ status: __t6.string(),
81
+ errorMessage: __t6.string()
82
+ };
83
+
84
+ // src/module_bindings/register_key_reducer.ts
85
+ import {
86
+ t as __t7
87
+ } from "spacetimedb";
88
+ var register_key_reducer_default = {
89
+ keyHash: __t7.string(),
90
+ label: __t7.string(),
91
+ retentionDays: __t7.u64()
92
+ };
93
+
94
+ // src/module_bindings/start_action_reducer.ts
95
+ import {
96
+ t as __t8
97
+ } from "spacetimedb";
98
+ var start_action_reducer_default = {
99
+ keyHash: __t8.string(),
100
+ exportToken: __t8.string(),
101
+ name: __t8.string()
102
+ };
103
+
104
+ // src/module_bindings/update_error_group_status_reducer.ts
105
+ import {
106
+ t as __t9
107
+ } from "spacetimedb";
108
+ var update_error_group_status_reducer_default = {
109
+ keyHash: __t9.string(),
110
+ errorGroupId: __t9.u64(),
111
+ status: __t9.string()
112
+ };
113
+
114
+ // src/module_bindings/action_table.ts
115
+ import {
116
+ t as __t10
117
+ } from "spacetimedb";
118
+ var action_table_default = __t10.row({
119
+ id: __t10.u64().primaryKey(),
120
+ flowId: __t10.u64().name("flow_id"),
121
+ name: __t10.string(),
122
+ status: __t10.string(),
123
+ createdAt: __t10.timestamp().name("created_at"),
124
+ finishedAt: __t10.u64().name("finished_at")
125
+ });
126
+
127
+ // src/module_bindings/api_key_table.ts
128
+ import {
129
+ t as __t11
130
+ } from "spacetimedb";
131
+ var api_key_table_default = __t11.row({
132
+ keyHash: __t11.string().primaryKey().name("key_hash"),
133
+ label: __t11.string(),
134
+ retentionDays: __t11.u64().name("retention_days"),
135
+ createdAt: __t11.timestamp().name("created_at")
136
+ });
137
+
138
+ // src/module_bindings/error_group_table.ts
139
+ import {
140
+ t as __t12
141
+ } from "spacetimedb";
142
+ var error_group_table_default = __t12.row({
143
+ id: __t12.u64().primaryKey(),
144
+ keyHash: __t12.string().name("key_hash"),
145
+ fingerprint: __t12.string(),
146
+ message: __t12.string(),
147
+ path: __t12.string(),
148
+ release: __t12.string(),
149
+ count: __t12.u64(),
150
+ status: __t12.string(),
151
+ firstSeenAt: __t12.timestamp().name("first_seen_at"),
152
+ lastSeenAt: __t12.u64().name("last_seen_at"),
153
+ lastFlowId: __t12.u64().name("last_flow_id")
154
+ });
155
+
156
+ // src/module_bindings/flow_table.ts
157
+ import {
158
+ t as __t13
159
+ } from "spacetimedb";
160
+ var flow_table_default = __t13.row({
161
+ id: __t13.u64().primaryKey(),
162
+ keyHash: __t13.string().name("key_hash"),
163
+ runId: __t13.string().name("run_id"),
164
+ parentFlowId: __t13.u64().name("parent_flow_id"),
165
+ name: __t13.string(),
166
+ path: __t13.string(),
167
+ status: __t13.string(),
168
+ release: __t13.string(),
169
+ timeoutSeconds: __t13.u64().name("timeout_seconds"),
170
+ createdAt: __t13.timestamp().name("created_at"),
171
+ finishedAt: __t13.u64().name("finished_at"),
172
+ exportToken: __t13.string().name("export_token")
173
+ });
174
+
175
+ // src/module_bindings/log_entry_table.ts
176
+ import {
177
+ t as __t14
178
+ } from "spacetimedb";
179
+ var log_entry_table_default = __t14.row({
180
+ id: __t14.u64().primaryKey(),
181
+ flowId: __t14.u64().name("flow_id"),
182
+ level: __t14.string(),
183
+ message: __t14.string(),
184
+ data: __t14.string(),
185
+ createdAt: __t14.timestamp().name("created_at")
186
+ });
187
+
188
+ // src/module_bindings/index.ts
189
+ var tablesSchema = __schema({
190
+ action: __table({
191
+ name: "action",
192
+ indexes: [
193
+ { name: "id", algorithm: "btree", columns: [
194
+ "id"
195
+ ] }
196
+ ],
197
+ constraints: [
198
+ { name: "action_id_key", constraint: "unique", columns: ["id"] }
199
+ ]
200
+ }, action_table_default),
201
+ apiKey: __table({
202
+ name: "api_key",
203
+ indexes: [
204
+ { name: "keyHash", algorithm: "btree", columns: [
205
+ "keyHash"
206
+ ] }
207
+ ],
208
+ constraints: [
209
+ { name: "api_key_key_hash_key", constraint: "unique", columns: ["keyHash"] }
210
+ ]
211
+ }, api_key_table_default),
212
+ errorGroup: __table({
213
+ name: "error_group",
214
+ indexes: [
215
+ { name: "fingerprint", algorithm: "btree", columns: [
216
+ "fingerprint"
217
+ ] },
218
+ { name: "id", algorithm: "btree", columns: [
219
+ "id"
220
+ ] }
221
+ ],
222
+ constraints: [
223
+ { name: "error_group_fingerprint_key", constraint: "unique", columns: ["fingerprint"] },
224
+ { name: "error_group_id_key", constraint: "unique", columns: ["id"] }
225
+ ]
226
+ }, error_group_table_default),
227
+ flow: __table({
228
+ name: "flow",
229
+ indexes: [
230
+ { name: "exportToken", algorithm: "btree", columns: [
231
+ "exportToken"
232
+ ] },
233
+ { name: "id", algorithm: "btree", columns: [
234
+ "id"
235
+ ] }
236
+ ],
237
+ constraints: [
238
+ { name: "flow_export_token_key", constraint: "unique", columns: ["exportToken"] },
239
+ { name: "flow_id_key", constraint: "unique", columns: ["id"] }
240
+ ]
241
+ }, flow_table_default),
242
+ logEntry: __table({
243
+ name: "log_entry",
244
+ indexes: [
245
+ { name: "id", algorithm: "btree", columns: [
246
+ "id"
247
+ ] }
248
+ ],
249
+ constraints: [
250
+ { name: "log_entry_id_key", constraint: "unique", columns: ["id"] }
251
+ ]
252
+ }, log_entry_table_default)
253
+ });
254
+ var reducersSchema = __reducers(__reducerSchema("add_log", add_log_reducer_default), __reducerSchema("add_logs_batch", add_logs_batch_reducer_default), __reducerSchema("create_root_flow", create_root_flow_reducer_default), __reducerSchema("create_sub_flow", create_sub_flow_reducer_default), __reducerSchema("finish_action", finish_action_reducer_default), __reducerSchema("finish_flow", finish_flow_reducer_default), __reducerSchema("register_key", register_key_reducer_default), __reducerSchema("start_action", start_action_reducer_default), __reducerSchema("update_error_group_status", update_error_group_status_reducer_default));
255
+ var proceduresSchema = __procedures();
256
+ var REMOTE_MODULE = {
257
+ versionInfo: {
258
+ cliVersion: "2.0.3"
259
+ },
260
+ tables: tablesSchema.schemaType.tables,
261
+ reducers: reducersSchema.reducersType.reducers,
262
+ ...proceduresSchema
263
+ };
264
+ var tables = __makeQueryBuilder(tablesSchema.schemaType);
265
+ var reducers = __convertToAccessorMap(reducersSchema.reducersType.reducers);
266
+
267
+ class SubscriptionBuilder extends __SubscriptionBuilderImpl {
268
+ }
269
+
270
+ class DbConnectionBuilder extends __DbConnectionBuilder {
271
+ }
272
+
273
+ class DbConnection extends __DbConnectionImpl {
274
+ static builder = () => {
275
+ return new DbConnectionBuilder(REMOTE_MODULE, (config) => new DbConnection(config));
276
+ };
277
+ subscriptionBuilder = () => {
278
+ return new SubscriptionBuilder(this);
279
+ };
280
+ }
281
+
282
+ // src/cli.ts
283
+ var HOST = "wss://maincloud.spacetimedb.com";
284
+ var DB_NAME = "radish-log";
285
+ var c = {
286
+ reset: "\x1B[0m",
287
+ dim: "\x1B[2m",
288
+ bold: "\x1B[1m",
289
+ red: "\x1B[31m",
290
+ green: "\x1B[32m",
291
+ yellow: "\x1B[33m",
292
+ blue: "\x1B[34m",
293
+ magenta: "\x1B[35m",
294
+ cyan: "\x1B[36m",
295
+ gray: "\x1B[90m"
296
+ };
297
+ function levelColor(level) {
298
+ switch (level) {
299
+ case "info":
300
+ return c.blue;
301
+ case "warn":
302
+ return c.yellow;
303
+ case "error":
304
+ return c.red;
305
+ case "debug":
306
+ return c.gray;
307
+ default:
308
+ return c.dim;
309
+ }
310
+ }
311
+ function statusColor(status) {
312
+ switch (status) {
313
+ case "open":
314
+ return c.blue;
315
+ case "finished":
316
+ return c.green;
317
+ case "error":
318
+ return c.red;
319
+ case "timeout":
320
+ return c.yellow;
321
+ default:
322
+ return c.dim;
323
+ }
324
+ }
325
+ function errorStatusColor(status) {
326
+ switch (status) {
327
+ case "open":
328
+ return c.red;
329
+ case "regressed":
330
+ return c.yellow;
331
+ case "resolved":
332
+ return c.green;
333
+ case "ignored":
334
+ return c.gray;
335
+ default:
336
+ return c.dim;
337
+ }
338
+ }
339
+ async function hashKey(key) {
340
+ const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key));
341
+ return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
342
+ }
343
+ function tsMicros(ts) {
344
+ return ts.microsSinceUnixEpoch;
345
+ }
346
+ function fmtTime(ts) {
347
+ return new Date(Number(tsMicros(ts) / 1000n)).toLocaleTimeString();
348
+ }
349
+ function fmtDateTime(ts) {
350
+ const d = new Date(Number(tsMicros(ts) / 1000n));
351
+ return d.toLocaleString();
352
+ }
353
+ function fmtMs(us) {
354
+ const ms = us / 1000;
355
+ if (ms < 1)
356
+ return "<1ms";
357
+ if (ms < 1000)
358
+ return `${Math.round(ms)}ms`;
359
+ if (ms < 60000)
360
+ return `${(ms / 1000).toFixed(1)}s`;
361
+ return `${(ms / 60000).toFixed(1)}m`;
362
+ }
363
+ function fmtDuration(startTs, endMicros) {
364
+ return fmtMs(Number(endMicros - tsMicros(startTs)));
365
+ }
366
+ function fmtData(data) {
367
+ if (!data || data === "{}")
368
+ return "";
369
+ try {
370
+ const parsed = JSON.parse(data);
371
+ const str = JSON.stringify(parsed, null, 2);
372
+ return str.split(`
373
+ `).map((line, i) => i === 0 ? " " + c.dim + line + c.reset : " " + c.dim + line + c.reset).join(`
374
+ `);
375
+ } catch {
376
+ return " " + c.dim + data + c.reset;
377
+ }
378
+ }
379
+ function padRight(s, n) {
380
+ return s.length >= n ? s.slice(0, n) : s + " ".repeat(n - s.length);
381
+ }
382
+ function connect(keyHash) {
383
+ return new Promise((resolve, reject) => {
384
+ const conn = DbConnection.builder().withUri(HOST).withDatabaseName(DB_NAME).onConnect((c2, _identity, _token) => {
385
+ c2.subscriptionBuilder().onApplied(() => {
386
+ const flows = () => [...c2.db.flow.iter()];
387
+ const logs = () => [...c2.db.logEntry.iter()];
388
+ const errorGroups = () => [...c2.db.errorGroup.iter()];
389
+ resolve({ conn: c2, flows, logs, errorGroups });
390
+ }).subscribe([
391
+ `SELECT * FROM flow WHERE key_hash = '${keyHash}'`,
392
+ `SELECT * FROM log_entry WHERE flow_id IN (SELECT id FROM flow WHERE key_hash = '${keyHash}')`,
393
+ `SELECT * FROM error_group WHERE key_hash = '${keyHash}'`
394
+ ]);
395
+ }).onConnectError((_ctx, err) => reject(new Error(`Connection failed: ${err}`))).build();
396
+ });
397
+ }
398
+ function cmdList(flows) {
399
+ const roots = flows.filter((f) => f.parentFlowId === 0n).sort((a, b) => {
400
+ const am = tsMicros(a.createdAt), bm = tsMicros(b.createdAt);
401
+ return am > bm ? -1 : am < bm ? 1 : 0;
402
+ });
403
+ if (roots.length === 0) {
404
+ console.log(`${c.dim}No flows found for this key.${c.reset}`);
405
+ return;
406
+ }
407
+ const hasRelease = roots.some((f) => f.release && f.release !== "");
408
+ const hasRunId = roots.some((f) => f.runId && f.runId !== "");
409
+ console.log(`${c.bold}Flows${c.reset} ${c.dim}(${roots.length} total)${c.reset}
410
+ `);
411
+ const cols = [
412
+ "ID".padEnd(8),
413
+ "STATUS".padEnd(10),
414
+ ...hasRunId ? ["RUN".padEnd(18)] : [],
415
+ ...hasRelease ? ["RELEASE".padEnd(10)] : [],
416
+ "DURATION".padEnd(10),
417
+ "CREATED"
418
+ ];
419
+ const divs = [
420
+ "─".repeat(8),
421
+ "─".repeat(10),
422
+ ...hasRunId ? ["─".repeat(18)] : [],
423
+ ...hasRelease ? ["─".repeat(10)] : [],
424
+ "─".repeat(10),
425
+ "─".repeat(24)
426
+ ];
427
+ console.log(`${c.dim} ${cols.join(" ")}${c.reset}`);
428
+ console.log(`${c.dim} ${divs.join(" ")}${c.reset}`);
429
+ for (const f of roots.slice(0, 30)) {
430
+ const id = f.id.toString().padEnd(8);
431
+ const sc = statusColor(f.status);
432
+ const status = padRight(f.status, 10);
433
+ const dur = f.finishedAt !== 0n ? padRight(fmtDuration(f.createdAt, f.finishedAt), 10) : padRight("—", 10);
434
+ const time = fmtDateTime(f.createdAt);
435
+ const runIdCol = hasRunId ? padRight(f.runId ? f.runId.slice(0, 16) : "—", 18) : "";
436
+ const release = hasRelease ? padRight(f.release || "—", 10) + " " : "";
437
+ console.log(` ${c.dim}${id}${c.reset} ${sc}${status}${c.reset} ${hasRunId ? runIdCol + " " : ""}${release}${dur} ${c.dim}${time}${c.reset}`);
438
+ }
439
+ if (roots.length > 30) {
440
+ console.log(`${c.dim} ... and ${roots.length - 30} more${c.reset}`);
441
+ }
442
+ }
443
+ function cmdShow(flowId, allFlows, allLogs) {
444
+ const flow = allFlows.find((f) => f.id === flowId);
445
+ if (!flow) {
446
+ console.error(`${c.red}Flow ${flowId} not found.${c.reset}`);
447
+ process.exit(1);
448
+ }
449
+ const sc = statusColor(flow.status);
450
+ const releaseTag = flow.release ? ` ${c.dim}(${flow.release})${c.reset}` : "";
451
+ console.log(`${c.bold}Flow #${flow.id}${c.reset} ${sc}${flow.status}${c.reset}${releaseTag}`);
452
+ if (flow.runId) {
453
+ console.log(`${c.dim}Run: ${flow.runId}${c.reset}`);
454
+ }
455
+ console.log(`${c.dim}Created: ${fmtDateTime(flow.createdAt)}${c.reset}`);
456
+ if (flow.finishedAt !== 0n) {
457
+ console.log(`${c.dim}Duration: ${fmtDuration(flow.createdAt, flow.finishedAt)}${c.reset}`);
458
+ }
459
+ if (flow.release) {
460
+ console.log(`${c.dim}Release: ${flow.release}${c.reset}`);
461
+ }
462
+ console.log();
463
+ const children = allFlows.filter((f) => f.parentFlowId === flowId).sort((a, b) => {
464
+ const am = tsMicros(a.createdAt), bm = tsMicros(b.createdAt);
465
+ return am < bm ? -1 : am > bm ? 1 : 0;
466
+ });
467
+ if (children.length > 0) {
468
+ console.log(`${c.bold}Actions${c.reset}
469
+ `);
470
+ for (const ch of children) {
471
+ const sc2 = statusColor(ch.status);
472
+ const dur = ch.finishedAt !== 0n ? fmtDuration(ch.createdAt, ch.finishedAt) : "…";
473
+ console.log(` ${sc2}●${c.reset} ${c.bold}${ch.name}${c.reset} ${c.dim}#${ch.id}${c.reset} ${sc2}${ch.status}${c.reset} ${c.dim}${dur}${c.reset}`);
474
+ }
475
+ console.log();
476
+ }
477
+ const flowIds = new Set;
478
+ function collectIds(parentId) {
479
+ flowIds.add(parentId);
480
+ for (const f of allFlows) {
481
+ if (f.parentFlowId === parentId)
482
+ collectIds(f.id);
483
+ }
484
+ }
485
+ collectIds(flowId);
486
+ const logs = allLogs.filter((l) => flowIds.has(l.flowId) && l.level !== "flow" && l.level !== "action").sort((a, b) => {
487
+ const am = tsMicros(a.createdAt), bm = tsMicros(b.createdAt);
488
+ return am < bm ? -1 : am > bm ? 1 : 0;
489
+ });
490
+ if (logs.length === 0) {
491
+ console.log(`${c.dim}No logs.${c.reset}`);
492
+ return;
493
+ }
494
+ const flowMap = new Map;
495
+ for (const f of allFlows)
496
+ flowMap.set(f.id, f.name === "/" ? "root" : f.name);
497
+ console.log(`${c.bold}Logs${c.reset}
498
+ `);
499
+ for (const l of logs) {
500
+ const time = fmtTime(l.createdAt);
501
+ const lc = levelColor(l.level);
502
+ const lvl = padRight(l.level, 5);
503
+ const source = l.flowId !== flowId ? ` ${c.cyan}[${flowMap.get(l.flowId) || "?"}]${c.reset}` : "";
504
+ const data = fmtData(l.data);
505
+ console.log(` ${c.dim}${time}${c.reset} ${lc}${lvl}${c.reset}${source} ${l.message}${data}`);
506
+ }
507
+ }
508
+ function cmdTree(flowId, allFlows) {
509
+ const flow = allFlows.find((f) => f.id === flowId);
510
+ if (!flow) {
511
+ console.error(`${c.red}Flow ${flowId} not found.${c.reset}`);
512
+ process.exit(1);
513
+ }
514
+ console.log(`${c.bold}Flow Tree #${flow.id}${c.reset}
515
+ `);
516
+ function printNode(f, prefix, connector) {
517
+ const sc = statusColor(f.status);
518
+ const name = f.name === "/" ? "root" : f.name;
519
+ const dur = f.finishedAt !== 0n ? ` ${c.dim}${fmtDuration(f.createdAt, f.finishedAt)}${c.reset}` : "";
520
+ console.log(`${prefix}${connector}${sc}●${c.reset} ${c.bold}${name}${c.reset} ${c.dim}#${f.id}${c.reset} ${sc}${f.status}${c.reset}${dur}`);
521
+ }
522
+ function printChildren(parentId, prefix) {
523
+ const children = allFlows.filter((ch) => ch.parentFlowId === parentId).sort((a, b) => {
524
+ const am = tsMicros(a.createdAt), bm = tsMicros(b.createdAt);
525
+ return am < bm ? -1 : am > bm ? 1 : 0;
526
+ });
527
+ for (let i = 0;i < children.length; i++) {
528
+ const isLast = i === children.length - 1;
529
+ printNode(children[i], prefix, isLast ? "└─ " : "├─ ");
530
+ printChildren(children[i].id, prefix + (isLast ? " " : "│ "));
531
+ }
532
+ }
533
+ printNode(flow, "", "");
534
+ printChildren(flowId, "");
535
+ }
536
+ async function cmdTail(keyHash) {
537
+ console.log(`${c.bold}Tailing logs...${c.reset} ${c.dim}(Ctrl+C to stop)${c.reset}
538
+ `);
539
+ const seen = new Set;
540
+ const flowNames = new Map;
541
+ const conn = await new Promise((resolve, reject) => {
542
+ DbConnection.builder().withUri(HOST).withDatabaseName(DB_NAME).onConnect((c2, _identity, _token) => {
543
+ c2.subscriptionBuilder().onApplied(() => {
544
+ for (const f of c2.db.flow.iter()) {
545
+ flowNames.set(f.id, f.name === "/" ? "root" : f.name);
546
+ }
547
+ for (const l of c2.db.logEntry.iter())
548
+ seen.add(l.id);
549
+ resolve(c2);
550
+ }).subscribe([
551
+ `SELECT * FROM flow WHERE key_hash = '${keyHash}'`,
552
+ `SELECT * FROM log_entry WHERE flow_id IN (SELECT id FROM flow WHERE key_hash = '${keyHash}')`
553
+ ]);
554
+ }).onConnectError((_ctx, err) => reject(new Error(`Connection failed: ${err}`))).build();
555
+ });
556
+ setInterval(() => {
557
+ for (const f of conn.db.flow.iter()) {
558
+ if (!flowNames.has(f.id)) {
559
+ flowNames.set(f.id, f.name === "/" ? "root" : f.name);
560
+ }
561
+ }
562
+ const newLogs = [];
563
+ for (const l of conn.db.logEntry.iter()) {
564
+ if (!seen.has(l.id) && l.level !== "flow" && l.level !== "action") {
565
+ seen.add(l.id);
566
+ newLogs.push(l);
567
+ }
568
+ }
569
+ newLogs.sort((a, b) => {
570
+ const am = tsMicros(a.createdAt), bm = tsMicros(b.createdAt);
571
+ return am < bm ? -1 : am > bm ? 1 : 0;
572
+ });
573
+ for (const l of newLogs) {
574
+ const time = fmtTime(l.createdAt);
575
+ const lc = levelColor(l.level);
576
+ const lvl = padRight(l.level, 5);
577
+ const source = `${c.cyan}[${flowNames.get(l.flowId) || "?"}]${c.reset}`;
578
+ const data = fmtData(l.data);
579
+ console.log(`${c.dim}${time}${c.reset} ${lc}${lvl}${c.reset} ${source} ${l.message}${data}`);
580
+ }
581
+ }, 500);
582
+ }
583
+ function cmdErrors(errorGroups) {
584
+ const statusOrder = { open: 0, regressed: 1, resolved: 2, ignored: 3 };
585
+ const sorted = [...errorGroups].sort((a, b) => {
586
+ const ao = statusOrder[a.status] ?? 99, bo = statusOrder[b.status] ?? 99;
587
+ if (ao !== bo)
588
+ return ao - bo;
589
+ return a.count > b.count ? -1 : a.count < b.count ? 1 : 0;
590
+ });
591
+ if (sorted.length === 0) {
592
+ console.log(`${c.dim}No error groups found for this key.${c.reset}`);
593
+ return;
594
+ }
595
+ console.log(`${c.bold}Errors${c.reset} ${c.dim}(${sorted.length} groups)${c.reset}
596
+ `);
597
+ console.log(`${c.dim} ${"ID".padEnd(8)} ${"STATUS".padEnd(10)} ${"COUNT".padEnd(6)} ${"RELEASE".padEnd(10)} ${"PATH".padEnd(23)} MESSAGE${c.reset}`);
598
+ console.log(`${c.dim} ${"─".repeat(8)} ${"─".repeat(10)} ${"─".repeat(6)} ${"─".repeat(10)} ${"─".repeat(23)} ${"─".repeat(25)}${c.reset}`);
599
+ for (const g of sorted) {
600
+ const id = g.id.toString().padEnd(8);
601
+ const esc = errorStatusColor(g.status);
602
+ const status = padRight(g.status, 10);
603
+ const count = g.count.toString().padEnd(6);
604
+ const release = padRight(g.release || "—", 10);
605
+ const path = padRight(g.path || "—", 23);
606
+ const message = g.message.length > 50 ? g.message.slice(0, 47) + "..." : g.message;
607
+ console.log(` ${c.dim}${id}${c.reset} ${esc}${status}${c.reset} ${count} ${c.dim}${release}${c.reset} ${c.dim}${path}${c.reset} ${message}`);
608
+ }
609
+ }
610
+ async function cmdUpdateErrorGroupStatus(conn, keyHash, errorGroupId, status) {
611
+ conn.reducers.updateErrorGroupStatus({
612
+ keyHash,
613
+ errorGroupId: BigInt(errorGroupId),
614
+ status
615
+ });
616
+ const label = status === "resolved" ? "Resolved" : "Ignored";
617
+ console.log(`${c.green}${label} error group #${errorGroupId}.${c.reset}`);
618
+ }
619
+ async function main() {
620
+ const args = process.argv.slice(2);
621
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
622
+ console.log(`
623
+ ${c.bold}radish${c.reset} — inspect flows and logs
624
+
625
+ ${c.bold}Usage:${c.reset}
626
+ radish keygen Generate a new secret key
627
+ radish <key> List recent flows
628
+ radish <key> <flowId> Show flow detail + logs
629
+ radish <key> tree <flowId> Show flow tree
630
+ radish <key> tail Live-tail all logs
631
+ radish <key> errors Show error groups
632
+ radish <key> resolve <errorGroupId> Resolve an error group
633
+ radish <key> ignore <errorGroupId> Ignore an error group
634
+ `);
635
+ process.exit(0);
636
+ }
637
+ if (args[0] === "keygen") {
638
+ const bytes = new Uint8Array(32);
639
+ crypto.getRandomValues(bytes);
640
+ const key = "rl_" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
641
+ console.log(`
642
+ ${c.bold}Generated key:${c.reset} ${key}
643
+ `);
644
+ console.log(`Save this key — it's your dashboard login.`);
645
+ console.log(`Dashboard: ${c.cyan}https://radish-log.spacetimedb.com${c.reset}
646
+ `);
647
+ process.exit(0);
648
+ }
649
+ const secretKey = args[0];
650
+ const keyHash = await hashKey(secretKey);
651
+ const subcmd = args[1];
652
+ if (subcmd === "tail") {
653
+ await cmdTail(keyHash);
654
+ return;
655
+ }
656
+ process.stderr.write(`${c.dim}Connecting...${c.reset}`);
657
+ const { conn, flows, logs, errorGroups } = await connect(keyHash);
658
+ process.stderr.write(`\r${" ".repeat(20)}\r`);
659
+ const allFlows = flows();
660
+ const allLogs = logs();
661
+ if (!subcmd) {
662
+ cmdList(allFlows);
663
+ } else if (subcmd === "tree") {
664
+ const flowId = BigInt(args[2] || "0");
665
+ if (flowId === 0n) {
666
+ console.error(`${c.red}Usage: radish <key> tree <flowId>${c.reset}`);
667
+ process.exit(1);
668
+ }
669
+ cmdTree(flowId, allFlows);
670
+ } else if (subcmd === "errors") {
671
+ cmdErrors(errorGroups());
672
+ } else if (subcmd === "resolve") {
673
+ const errorGroupId = args[2];
674
+ if (!errorGroupId) {
675
+ console.error(`${c.red}Usage: radish <key> resolve <errorGroupId>${c.reset}`);
676
+ process.exit(1);
677
+ }
678
+ await cmdUpdateErrorGroupStatus(conn, keyHash, errorGroupId, "resolved");
679
+ } else if (subcmd === "ignore") {
680
+ const errorGroupId = args[2];
681
+ if (!errorGroupId) {
682
+ console.error(`${c.red}Usage: radish <key> ignore <errorGroupId>${c.reset}`);
683
+ process.exit(1);
684
+ }
685
+ await cmdUpdateErrorGroupStatus(conn, keyHash, errorGroupId, "ignored");
686
+ } else {
687
+ const flowId = BigInt(subcmd);
688
+ cmdShow(flowId, allFlows, allLogs);
689
+ }
690
+ conn.disconnect();
691
+ process.exit(0);
692
+ }
693
+ main().catch((err) => {
694
+ console.error(`${c.red}${err.message}${c.reset}`);
695
+ process.exit(1);
696
+ });
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
  }