@radishbot/sdk 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radishbot/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
package/src/cli.ts CHANGED
File without changes
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { SdkConnection } from './connection';
1
+ import { SdkConnection } from "./connection";
2
2
 
3
- export type LogLevel = 'info' | 'warn' | 'error' | 'debug';
3
+ export type LogLevel = "info" | "warn" | "error" | "debug";
4
4
 
5
5
  // ── Console capture ──────────────────────────────────────────
6
6
 
@@ -15,26 +15,32 @@ const _origConsole = {
15
15
  const _flowStack: Flow[] = [];
16
16
 
17
17
  function _patchConsole() {
18
- const capture = (level: LogLevel) => (...args: unknown[]) => {
19
- const flow = _flowStack[_flowStack.length - 1];
20
- if (flow) {
21
- const msg = args.map(a => {
22
- if (typeof a === 'string') return a;
23
- if (a instanceof Error) return a.message;
24
- try { return JSON.stringify(a); } catch { return String(a); }
25
- }).join(' ');
26
- const data = args.length <= 1 ? undefined
27
- : args.length === 2 ? args[1]
28
- : args.slice(1);
29
- flow.log(msg, data, level);
30
- }
31
- _origConsole[level === 'info' ? 'log' : level](...args);
32
- };
33
- console.log = capture('info');
34
- console.info = capture('info');
35
- console.warn = capture('warn');
36
- console.error = capture('error');
37
- console.debug = capture('debug');
18
+ const capture =
19
+ (level: LogLevel) =>
20
+ (...args: unknown[]) => {
21
+ const flow = _flowStack[_flowStack.length - 1];
22
+ if (flow) {
23
+ const msg = args
24
+ .map((a) => {
25
+ if (typeof a === "string") return a;
26
+ if (a instanceof Error) return a.message;
27
+ try {
28
+ return JSON.stringify(a);
29
+ } catch {
30
+ return String(a);
31
+ }
32
+ })
33
+ .join(" ");
34
+ const data = args.length <= 1 ? undefined : args.length === 2 ? args[1] : args.slice(1);
35
+ flow.log(msg, data, level);
36
+ }
37
+ _origConsole[level === "info" ? "log" : level](...args);
38
+ };
39
+ console.log = capture("info");
40
+ console.info = capture("info");
41
+ console.warn = capture("warn");
42
+ console.error = capture("error");
43
+ console.debug = capture("debug");
38
44
  }
39
45
 
40
46
  function _restoreConsole() {
@@ -46,7 +52,7 @@ function _restoreConsole() {
46
52
  }
47
53
 
48
54
  function serialize(value: unknown): string {
49
- if (value === undefined || value === null) return '{}';
55
+ if (value === undefined || value === null) return "{}";
50
56
  if (value instanceof Error)
51
57
  return JSON.stringify({
52
58
  name: value.name,
@@ -54,9 +60,7 @@ function serialize(value: unknown): string {
54
60
  stack: value.stack,
55
61
  });
56
62
  try {
57
- return JSON.stringify(value, (_key, v) =>
58
- typeof v === 'bigint' ? v.toString() : v
59
- );
63
+ return JSON.stringify(value, (_key, v) => (typeof v === "bigint" ? v.toString() : v));
60
64
  } catch {
61
65
  return JSON.stringify({ value: String(value) });
62
66
  }
@@ -65,17 +69,17 @@ function serialize(value: unknown): string {
65
69
  async function hashKey(secretKey: string): Promise<string> {
66
70
  const encoder = new TextEncoder();
67
71
  const data = encoder.encode(secretKey);
68
- const hashBuffer = await crypto.subtle.digest('SHA-256', data);
72
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
69
73
  const hashArray = Array.from(new Uint8Array(hashBuffer));
70
- return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
74
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
71
75
  }
72
76
 
73
77
  function generateToken(): string {
74
78
  const bytes = new Uint8Array(16);
75
79
  crypto.getRandomValues(bytes);
76
80
  return Array.from(bytes)
77
- .map((b) => b.toString(16).padStart(2, '0'))
78
- .join('');
81
+ .map((b) => b.toString(16).padStart(2, "0"))
82
+ .join("");
79
83
  }
80
84
 
81
85
  export class Flow {
@@ -92,6 +96,7 @@ export class Flow {
92
96
  private _parentId: bigint;
93
97
  private _name: string;
94
98
  private _timeoutSeconds: bigint;
99
+ private _release: string;
95
100
 
96
101
  /** @internal */
97
102
  constructor(
@@ -99,13 +104,15 @@ export class Flow {
99
104
  keyHash: string,
100
105
  parentId: bigint,
101
106
  name: string,
102
- timeoutSeconds: number
107
+ timeoutSeconds: number,
108
+ release: string = "",
103
109
  ) {
104
110
  this._sdk = sdk;
105
111
  this._keyHash = keyHash;
106
112
  this._parentId = parentId;
107
113
  this._name = name;
108
- this._timeoutSeconds = (!timeoutSeconds || timeoutSeconds === Infinity) ? 0n : BigInt(timeoutSeconds);
114
+ this._timeoutSeconds = !timeoutSeconds || timeoutSeconds === Infinity ? 0n : BigInt(timeoutSeconds);
115
+ this._release = release;
109
116
  this._exportToken = generateToken();
110
117
  this._ready = new Promise<void>((resolve) => {
111
118
  this._resolveReady = resolve;
@@ -121,8 +128,8 @@ export class Flow {
121
128
 
122
129
  if (this._parentId === 0n) {
123
130
  const id = await this._sdk.createFlowAndResolveId(
124
- () => conn.reducers.createRootFlow({ keyHash, timeoutSeconds, exportToken }),
125
- exportToken
131
+ () => conn.reducers.createRootFlow({ keyHash, timeoutSeconds, exportToken, release: this._release }),
132
+ exportToken,
126
133
  );
127
134
  this._id = id;
128
135
  } else {
@@ -137,7 +144,7 @@ export class Flow {
137
144
  timeoutSeconds,
138
145
  exportToken,
139
146
  }),
140
- exportToken
147
+ exportToken,
141
148
  );
142
149
  this._id = id;
143
150
  }
@@ -147,7 +154,7 @@ export class Flow {
147
154
  }
148
155
 
149
156
  /** Log a message with optional data */
150
- log(message: string, data?: unknown, level: LogLevel = 'info'): this {
157
+ log(message: string, data?: unknown, level: LogLevel = "info"): this {
151
158
  if (this._finished) {
152
159
  console.warn(`[radish] Cannot log to finished flow`);
153
160
  return this;
@@ -155,28 +162,28 @@ export class Flow {
155
162
  this._pendingLogs.push({
156
163
  level,
157
164
  message,
158
- data: data !== undefined ? serialize(data) : '{}',
165
+ data: data !== undefined ? serialize(data) : "{}",
159
166
  });
160
167
  this._scheduleFlush();
161
168
  return this;
162
169
  }
163
170
 
164
171
  info(message: string, data?: unknown): this {
165
- return this.log(message, data, 'info');
172
+ return this.log(message, data, "info");
166
173
  }
167
174
  warn(message: string, data?: unknown): this {
168
- return this.log(message, data, 'warn');
175
+ return this.log(message, data, "warn");
169
176
  }
170
177
  error(message: string, data?: unknown): this {
171
- return this.log(message, data, 'error');
178
+ return this.log(message, data, "error");
172
179
  }
173
180
  debug(message: string, data?: unknown): this {
174
- return this.log(message, data, 'debug');
181
+ return this.log(message, data, "debug");
175
182
  }
176
183
 
177
184
  /** Create a sub-action. Returns immediately — creation runs in background. */
178
185
  action(name: string, timeoutSeconds = 100): Flow {
179
- const child = new Flow(this._sdk, this._keyHash, 0n, name, timeoutSeconds);
186
+ const child = new Flow(this._sdk, this._keyHash, 0n, name, timeoutSeconds, this._release);
180
187
  this._ready.then(() => {
181
188
  (child as any)._parentId = this._id!;
182
189
  child._create();
@@ -215,14 +222,16 @@ export class Flow {
215
222
  this._sdk.conn.reducers.finishFlow({
216
223
  keyHash: this._keyHash,
217
224
  flowId: this._id!,
218
- status: 'finished',
225
+ status: "finished",
226
+ errorMessage: "",
219
227
  });
220
228
  }
221
229
 
222
230
  /** Finish this flow with error status */
223
231
  async finishWithError(err?: Error | string): Promise<void> {
232
+ const errorMessage = err ? (typeof err === "string" ? err : err.message) : "";
224
233
  if (err) {
225
- this.error(typeof err === 'string' ? err : err.message, err);
234
+ this.error(errorMessage, err);
226
235
  }
227
236
  await this._ready;
228
237
  this._drain();
@@ -230,7 +239,8 @@ export class Flow {
230
239
  this._sdk.conn.reducers.finishFlow({
231
240
  keyHash: this._keyHash,
232
241
  flowId: this._id!,
233
- status: 'error',
242
+ status: "error",
243
+ errorMessage,
234
244
  });
235
245
  }
236
246
 
@@ -290,6 +300,14 @@ export interface RLOptions {
290
300
  dbName?: string;
291
301
  label?: string;
292
302
  defaultTimeout?: number;
303
+ release?: string;
304
+ retention?: string;
305
+ }
306
+
307
+ function parseRetention(retention: string): number {
308
+ const match = retention.match(/^(\d+)d$/);
309
+ if (!match) throw new Error('Invalid retention format. Use e.g. "30d"');
310
+ return parseInt(match[1], 10);
293
311
  }
294
312
 
295
313
  /**
@@ -315,20 +333,23 @@ export interface RLOptions {
315
333
  */
316
334
  export async function RL(secretKey: string, options: RLOptions = {}): Promise<Flow> {
317
335
  const {
318
- host = 'wss://maincloud.spacetimedb.com',
319
- dbName = 'radish-log',
320
- label = '',
336
+ host = "wss://maincloud.spacetimedb.com",
337
+ dbName = "radish-log",
338
+ label = "",
321
339
  defaultTimeout = 100,
340
+ release = "",
341
+ retention = "30d",
322
342
  } = options;
323
343
 
344
+ const retentionDays = parseRetention(retention);
324
345
  const keyHash = await hashKey(secretKey);
325
346
  const sdk = new SdkConnection(host, dbName);
326
347
  sdk.setKeyHash(keyHash);
327
348
  await sdk.connect();
328
349
 
329
- // Register key (idempotent — server rejects dupes)
350
+ // Register key (idempotent — updates retention if changed)
330
351
  try {
331
- sdk.conn.reducers.registerKey({ keyHash, label });
352
+ sdk.conn.reducers.registerKey({ keyHash, label, retentionDays: BigInt(retentionDays) });
332
353
  } catch {
333
354
  // Already registered
334
355
  }
@@ -341,29 +362,25 @@ export async function RL(secretKey: string, options: RLOptions = {}): Promise<Fl
341
362
  }
342
363
 
343
364
  // Root flow — creation runs in background, logs queue until ready
344
- const root = new Flow(sdk, keyHash, 0n, '/', 0);
365
+ const root = new Flow(sdk, keyHash, 0n, "/", 0, release);
345
366
  root._create(); // fire-and-forget — resolves _ready when server assigns ID
346
367
  return root;
347
368
  }
348
369
 
349
370
  /** Restore a flow from an exported ID string */
350
- export async function restoreFlow(
351
- secretKey: string,
352
- exportedId: string,
353
- options: RLOptions = {}
354
- ): Promise<Flow> {
355
- const { host = 'wss://maincloud.spacetimedb.com', dbName = 'radish-log' } = options;
371
+ export async function restoreFlow(secretKey: string, exportedId: string, options: RLOptions = {}): Promise<Flow> {
372
+ const { host = "wss://maincloud.spacetimedb.com", dbName = "radish-log" } = options;
356
373
 
357
374
  const parsed = JSON.parse(exportedId);
358
375
  const keyHash = await hashKey(secretKey);
359
376
  if (keyHash !== parsed.keyHash) {
360
- throw new Error('Secret key does not match the flow owner');
377
+ throw new Error("Secret key does not match the flow owner");
361
378
  }
362
379
 
363
380
  const sdk = new SdkConnection(host, dbName);
364
381
  await sdk.connect();
365
382
 
366
- const flow = new Flow(sdk, keyHash, 0n, 'restored', 100);
383
+ const flow = new Flow(sdk, keyHash, 0n, "restored", 100);
367
384
  (flow as any)._id = BigInt(parsed.flowId);
368
385
  (flow as any)._exportToken = parsed.exportToken;
369
386
  (flow as any)._resolveReady();
@@ -375,9 +392,9 @@ export function generateKey(): string {
375
392
  const bytes = new Uint8Array(32);
376
393
  crypto.getRandomValues(bytes);
377
394
  return (
378
- 'rl_' +
395
+ "rl_" +
379
396
  Array.from(bytes)
380
- .map((b) => b.toString(16).padStart(2, '0'))
381
- .join('')
397
+ .map((b) => b.toString(16).padStart(2, "0"))
398
+ .join("")
382
399
  );
383
400
  }
@@ -13,5 +13,6 @@ import {
13
13
  export default __t.row({
14
14
  keyHash: __t.string().primaryKey().name("key_hash"),
15
15
  label: __t.string(),
16
+ retentionDays: __t.u64().name("retention_days"),
16
17
  createdAt: __t.timestamp().name("created_at"),
17
18
  });
@@ -14,4 +14,5 @@ export default {
14
14
  keyHash: __t.string(),
15
15
  timeoutSeconds: __t.u64(),
16
16
  exportToken: __t.string(),
17
+ release: __t.string(),
17
18
  };
@@ -0,0 +1,25 @@
1
+ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
2
+ // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
3
+
4
+ /* eslint-disable */
5
+ /* tslint:disable */
6
+ import {
7
+ TypeBuilder as __TypeBuilder,
8
+ t as __t,
9
+ type AlgebraicTypeType as __AlgebraicTypeType,
10
+ type Infer as __Infer,
11
+ } from "spacetimedb";
12
+
13
+ export default __t.row({
14
+ id: __t.u64().primaryKey(),
15
+ keyHash: __t.string().name("key_hash"),
16
+ fingerprint: __t.string(),
17
+ message: __t.string(),
18
+ path: __t.string(),
19
+ release: __t.string(),
20
+ count: __t.u64(),
21
+ status: __t.string(),
22
+ firstSeenAt: __t.timestamp().name("first_seen_at"),
23
+ lastSeenAt: __t.u64().name("last_seen_at"),
24
+ lastFlowId: __t.u64().name("last_flow_id"),
25
+ });
@@ -14,4 +14,5 @@ export default {
14
14
  keyHash: __t.string(),
15
15
  flowId: __t.u64(),
16
16
  status: __t.string(),
17
+ errorMessage: __t.string(),
17
18
  };
@@ -17,6 +17,7 @@ export default __t.row({
17
17
  name: __t.string(),
18
18
  path: __t.string(),
19
19
  status: __t.string(),
20
+ release: __t.string(),
20
21
  timeoutSeconds: __t.u64().name("timeout_seconds"),
21
22
  createdAt: __t.timestamp().name("created_at"),
22
23
  finishedAt: __t.u64().name("finished_at"),
@@ -43,12 +43,14 @@ import FinishActionReducer from "./finish_action_reducer";
43
43
  import FinishFlowReducer from "./finish_flow_reducer";
44
44
  import RegisterKeyReducer from "./register_key_reducer";
45
45
  import StartActionReducer from "./start_action_reducer";
46
+ import UpdateErrorGroupStatusReducer from "./update_error_group_status_reducer";
46
47
 
47
48
  // Import all procedure arg schemas
48
49
 
49
50
  // Import all table schema definitions
50
51
  import ActionRow from "./action_table";
51
52
  import ApiKeyRow from "./api_key_table";
53
+ import ErrorGroupRow from "./error_group_table";
52
54
  import FlowRow from "./flow_table";
53
55
  import LogEntryRow from "./log_entry_table";
54
56
 
@@ -78,6 +80,21 @@ const tablesSchema = __schema({
78
80
  { name: 'api_key_key_hash_key', constraint: 'unique', columns: ['keyHash'] },
79
81
  ],
80
82
  }, ApiKeyRow),
83
+ errorGroup: __table({
84
+ name: 'error_group',
85
+ indexes: [
86
+ { name: 'fingerprint', algorithm: 'btree', columns: [
87
+ 'fingerprint',
88
+ ] },
89
+ { name: 'id', algorithm: 'btree', columns: [
90
+ 'id',
91
+ ] },
92
+ ],
93
+ constraints: [
94
+ { name: 'error_group_fingerprint_key', constraint: 'unique', columns: ['fingerprint'] },
95
+ { name: 'error_group_id_key', constraint: 'unique', columns: ['id'] },
96
+ ],
97
+ }, ErrorGroupRow),
81
98
  flow: __table({
82
99
  name: 'flow',
83
100
  indexes: [
@@ -117,6 +134,7 @@ const reducersSchema = __reducers(
117
134
  __reducerSchema("finish_flow", FinishFlowReducer),
118
135
  __reducerSchema("register_key", RegisterKeyReducer),
119
136
  __reducerSchema("start_action", StartActionReducer),
137
+ __reducerSchema("update_error_group_status", UpdateErrorGroupStatusReducer),
120
138
  );
121
139
 
122
140
  /** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */
@@ -13,4 +13,5 @@ import {
13
13
  export default {
14
14
  keyHash: __t.string(),
15
15
  label: __t.string(),
16
+ retentionDays: __t.u64(),
16
17
  };
@@ -15,6 +15,7 @@ import FinishActionReducer from "../finish_action_reducer";
15
15
  import FinishFlowReducer from "../finish_flow_reducer";
16
16
  import RegisterKeyReducer from "../register_key_reducer";
17
17
  import StartActionReducer from "../start_action_reducer";
18
+ import UpdateErrorGroupStatusReducer from "../update_error_group_status_reducer";
18
19
 
19
20
  export type AddLogParams = __Infer<typeof AddLogReducer>;
20
21
  export type AddLogsBatchParams = __Infer<typeof AddLogsBatchReducer>;
@@ -25,4 +26,5 @@ export type FinishActionParams = __Infer<typeof FinishActionReducer>;
25
26
  export type FinishFlowParams = __Infer<typeof FinishFlowReducer>;
26
27
  export type RegisterKeyParams = __Infer<typeof RegisterKeyReducer>;
27
28
  export type StartActionParams = __Infer<typeof StartActionReducer>;
29
+ export type UpdateErrorGroupStatusParams = __Infer<typeof UpdateErrorGroupStatusReducer>;
28
30
 
@@ -23,10 +23,26 @@ export type Action = __Infer<typeof Action>;
23
23
  export const ApiKey = __t.object("ApiKey", {
24
24
  keyHash: __t.string(),
25
25
  label: __t.string(),
26
+ retentionDays: __t.u64(),
26
27
  createdAt: __t.timestamp(),
27
28
  });
28
29
  export type ApiKey = __Infer<typeof ApiKey>;
29
30
 
31
+ export const ErrorGroup = __t.object("ErrorGroup", {
32
+ id: __t.u64(),
33
+ keyHash: __t.string(),
34
+ fingerprint: __t.string(),
35
+ message: __t.string(),
36
+ path: __t.string(),
37
+ release: __t.string(),
38
+ count: __t.u64(),
39
+ status: __t.string(),
40
+ firstSeenAt: __t.timestamp(),
41
+ lastSeenAt: __t.u64(),
42
+ lastFlowId: __t.u64(),
43
+ });
44
+ export type ErrorGroup = __Infer<typeof ErrorGroup>;
45
+
30
46
  export const Flow = __t.object("Flow", {
31
47
  id: __t.u64(),
32
48
  keyHash: __t.string(),
@@ -34,6 +50,7 @@ export const Flow = __t.object("Flow", {
34
50
  name: __t.string(),
35
51
  path: __t.string(),
36
52
  status: __t.string(),
53
+ release: __t.string(),
37
54
  timeoutSeconds: __t.u64(),
38
55
  createdAt: __t.timestamp(),
39
56
  finishedAt: __t.u64(),
@@ -41,6 +58,12 @@ export const Flow = __t.object("Flow", {
41
58
  });
42
59
  export type Flow = __Infer<typeof Flow>;
43
60
 
61
+ export const GcJob = __t.object("GcJob", {
62
+ scheduledId: __t.u64(),
63
+ scheduledAt: __t.scheduleAt(),
64
+ });
65
+ export type GcJob = __Infer<typeof GcJob>;
66
+
44
67
  export const LogEntry = __t.object("LogEntry", {
45
68
  id: __t.u64(),
46
69
  flowId: __t.u64(),
@@ -0,0 +1,17 @@
1
+ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
2
+ // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
3
+
4
+ /* eslint-disable */
5
+ /* tslint:disable */
6
+ import {
7
+ TypeBuilder as __TypeBuilder,
8
+ t as __t,
9
+ type AlgebraicTypeType as __AlgebraicTypeType,
10
+ type Infer as __Infer,
11
+ } from "spacetimedb";
12
+
13
+ export default {
14
+ keyHash: __t.string(),
15
+ errorGroupId: __t.u64(),
16
+ status: __t.string(),
17
+ };