@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 +696 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +37 -105
- package/package.json +3 -3
- package/src/cli.ts +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/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
|
-
|
|
16
|
-
|
|
15
|
+
readonly token: string;
|
|
16
|
+
exportID(): string;
|
|
17
17
|
disconnect(): void;
|
|
18
18
|
finishAndDisconnect(): Promise<void>;
|
|
19
19
|
}
|