@donkeylabs/server 2.4.0 → 2.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/server",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "type": "module",
5
5
  "description": "Type-safe plugin system for building RPC-style APIs with Bun",
6
6
  "main": "./src/index.ts",
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Core Event Map
3
+ *
4
+ * Single source of truth for all core service event types.
5
+ * These events are emitted by workflows, jobs, processes, cron, and logs.
6
+ * They are always available in EventRegistry without code generation.
7
+ */
8
+
9
+ import type { ProcessStats } from "./processes";
10
+ import type { PersistentLogEntry } from "./logs";
11
+
12
+ export interface CoreEventMap {
13
+ // Workflow events
14
+ "workflow.started": { instanceId: string; workflowName: string; input: any };
15
+ "workflow.completed": { instanceId: string; output: any };
16
+ "workflow.failed": { instanceId: string; workflowName: string; error: string };
17
+ "workflow.cancelled": { instanceId: string; workflowName: string };
18
+ "workflow.progress": { instanceId: string; progress: number; currentStep: string; completedSteps: number; totalSteps: number };
19
+ "workflow.event": { instanceId: string; workflowName: string; event: string; data: any };
20
+ "workflow.step.started": { instanceId: string; stepName: string; stepType: string };
21
+ "workflow.step.completed": { instanceId: string; stepName: string; output: any };
22
+ "workflow.step.failed": { instanceId: string; stepName: string; error: string; attempts: number };
23
+ "workflow.step.poll": { instanceId: string; stepName: string; pollCount: number; done: boolean; result: any };
24
+ "workflow.step.loop": { instanceId: string; stepName: string; loopCount: number; target: string };
25
+ "workflow.step.retry": { instanceId: string; stepName: string; attempt: number; maxAttempts: number; delay: number };
26
+ "workflow.watchdog.stale": { instanceId: string; reason: string; timeoutMs: number };
27
+ "workflow.watchdog.killed": { instanceId: string; reason: string; timeoutMs: number };
28
+
29
+ // Job events
30
+ "job.completed": { jobId: string; name: string; result: any };
31
+ "job.failed": { jobId: string; name: string; error: string; attempts?: number; stack?: string };
32
+ "job.stale": { jobId: string; name: string; timeSinceHeartbeat: number };
33
+ "job.reconnected": { jobId: string; name: string };
34
+ "job.lost": { jobId: string; name: string };
35
+ "job.event": { jobId: string; name: string; event: string; data?: any };
36
+ "job.external.spawned": { jobId: string; name: string };
37
+ "job.external.progress": { jobId: string; name: string; percent: number; message: string; data: any };
38
+ "job.external.log": { jobId: string; name: string; level: string; message: string; data?: any };
39
+ "job.watchdog.stale": { jobId: string; name: string; timeSinceHeartbeat: number };
40
+ "job.watchdog.killed": { jobId: string; name: string; reason: string };
41
+
42
+ // Process events
43
+ "process.spawned": { processId: string; name: string; pid: number };
44
+ "process.stopped": { processId: string; name: string };
45
+ "process.crashed": { processId: string; name: string; exitCode: number | null };
46
+ "process.restarted": { oldProcessId: string; newProcessId: string; name: string; attempt: number };
47
+ "process.reconnected": { processId: string; name: string; pid: number };
48
+ "process.stats": { processId: string; name: string; stats: ProcessStats };
49
+ "process.limits_exceeded": { processId: string; name: string; reason: string; limit: number; value?: number };
50
+ "process.heartbeat_missed": { processId: string; name: string };
51
+ "process.event": { processId: string; name: string; event: string; data: any };
52
+ "process.message": { processId: string; name: string; message: any };
53
+ "process.watchdog.stale": { processId: string; name: string; reason: string; timeoutMs: number };
54
+ "process.watchdog.killed": { processId: string; name: string; reason: string; value?: number };
55
+
56
+ // Cron events
57
+ "cron.event": { taskId: string; name: string; event: string; data?: any };
58
+
59
+ // Log events
60
+ "log.created": PersistentLogEntry;
61
+ }
62
+
63
+ /**
64
+ * Serializable core event definitions for CLI type generation.
65
+ * Maps event name to TypeScript type string (used by `donkeylabs generate`).
66
+ */
67
+ export const CORE_EVENT_DEFINITIONS: Record<string, string> = {
68
+ // Workflow events
69
+ "workflow.started": "{ instanceId: string; workflowName: string; input: any }",
70
+ "workflow.completed": "{ instanceId: string; output: any }",
71
+ "workflow.failed": "{ instanceId: string; workflowName: string; error: string }",
72
+ "workflow.cancelled": "{ instanceId: string; workflowName: string }",
73
+ "workflow.progress": "{ instanceId: string; progress: number; currentStep: string; completedSteps: number; totalSteps: number }",
74
+ "workflow.event": "{ instanceId: string; workflowName: string; event: string; data: any }",
75
+ "workflow.step.started": "{ instanceId: string; stepName: string; stepType: string }",
76
+ "workflow.step.completed": "{ instanceId: string; stepName: string; output: any }",
77
+ "workflow.step.failed": "{ instanceId: string; stepName: string; error: string; attempts: number }",
78
+ "workflow.step.poll": "{ instanceId: string; stepName: string; pollCount: number; done: boolean; result: any }",
79
+ "workflow.step.loop": "{ instanceId: string; stepName: string; loopCount: number; target: string }",
80
+ "workflow.step.retry": "{ instanceId: string; stepName: string; attempt: number; maxAttempts: number; delay: number }",
81
+ "workflow.watchdog.stale": "{ instanceId: string; reason: string; timeoutMs: number }",
82
+ "workflow.watchdog.killed": "{ instanceId: string; reason: string; timeoutMs: number }",
83
+
84
+ // Job events
85
+ "job.completed": "{ jobId: string; name: string; result: any }",
86
+ "job.failed": "{ jobId: string; name: string; error: string; attempts?: number; stack?: string }",
87
+ "job.stale": "{ jobId: string; name: string; timeSinceHeartbeat: number }",
88
+ "job.reconnected": "{ jobId: string; name: string }",
89
+ "job.lost": "{ jobId: string; name: string }",
90
+ "job.event": "{ jobId: string; name: string; event: string; data?: any }",
91
+ "job.external.spawned": "{ jobId: string; name: string }",
92
+ "job.external.progress": "{ jobId: string; name: string; percent: number; message: string; data: any }",
93
+ "job.external.log": "{ jobId: string; name: string; level: string; message: string; data?: any }",
94
+ "job.watchdog.stale": "{ jobId: string; name: string; timeSinceHeartbeat: number }",
95
+ "job.watchdog.killed": "{ jobId: string; name: string; reason: string }",
96
+
97
+ // Process events
98
+ "process.spawned": "{ processId: string; name: string; pid: number }",
99
+ "process.stopped": "{ processId: string; name: string }",
100
+ "process.crashed": "{ processId: string; name: string; exitCode: number | null }",
101
+ "process.restarted": "{ oldProcessId: string; newProcessId: string; name: string; attempt: number }",
102
+ "process.reconnected": "{ processId: string; name: string; pid: number }",
103
+ "process.stats": "{ processId: string; name: string; stats: { cpu: { user: number; system: number; percent: number }; memory: { rss: number; heapTotal: number; heapUsed: number; external: number }; uptime: number } }",
104
+ "process.limits_exceeded": "{ processId: string; name: string; reason: string; limit: number; value?: number }",
105
+ "process.heartbeat_missed": "{ processId: string; name: string }",
106
+ "process.event": "{ processId: string; name: string; event: string; data: any }",
107
+ "process.message": "{ processId: string; name: string; message: any }",
108
+ "process.watchdog.stale": "{ processId: string; name: string; reason: string; timeoutMs: number }",
109
+ "process.watchdog.killed": "{ processId: string; name: string; reason: string; value?: number }",
110
+
111
+ // Cron events
112
+ "cron.event": "{ taskId: string; name: string; event: string; data?: any }",
113
+
114
+ // Log events
115
+ "log.created": "{ id: string; timestamp: Date; level: string; message: string; source: string; sourceId?: string; tags?: string[]; data?: Record<string, any>; context?: Record<string, any> }",
116
+ };
package/src/core/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  // Core Services - Re-export all services
2
2
 
3
+ export { type CoreEventMap, CORE_EVENT_DEFINITIONS } from "./core-events";
4
+
3
5
  export {
4
6
  type Logger,
5
7
  type LogLevel,
@@ -49,12 +49,12 @@ export class KyselyProcessAdapter implements ProcessAdapter {
49
49
  this.db = db as Kysely<Database>;
50
50
  this.cleanupDays = config.cleanupDays ?? 7;
51
51
 
52
- // Start cleanup timer
52
+ // Start cleanup timer - delay initial cleanup to allow migrations to run first
53
53
  if (this.cleanupDays > 0) {
54
54
  const interval = config.cleanupInterval ?? 3600000; // 1 hour
55
55
  this.cleanupTimer = setInterval(() => this.cleanup(), interval);
56
- // Run cleanup on startup
57
- this.cleanup();
56
+ // Delay initial cleanup to ensure table exists after migrations
57
+ setTimeout(() => this.cleanup(), 5000);
58
58
  }
59
59
  }
60
60
 
@@ -226,19 +226,28 @@ export class KyselyProcessAdapter implements ProcessAdapter {
226
226
  async getOrphaned(): Promise<ManagedProcess[]> {
227
227
  if (this.checkStopped()) return [];
228
228
 
229
- const rows = await this.db
230
- .selectFrom("__donkeylabs_processes__")
231
- .selectAll()
232
- .where((eb) =>
233
- eb.or([
234
- eb("status", "=", "running"),
235
- eb("status", "=", "spawning"),
236
- eb("status", "=", "orphaned"),
237
- ])
238
- )
239
- .execute();
229
+ try {
230
+ const rows = await this.db
231
+ .selectFrom("__donkeylabs_processes__")
232
+ .selectAll()
233
+ .where((eb) =>
234
+ eb.or([
235
+ eb("status", "=", "running"),
236
+ eb("status", "=", "spawning"),
237
+ eb("status", "=", "orphaned"),
238
+ ])
239
+ )
240
+ .execute();
240
241
 
241
- return rows.map((r) => this.rowToProcess(r));
242
+ return rows.map((r) => this.rowToProcess(r));
243
+ } catch (err: any) {
244
+ // Silently ignore if table doesn't exist yet (migrations not run)
245
+ const message = err?.message?.toLowerCase() || "";
246
+ if (message.includes("does not exist") || message.includes("no such table")) {
247
+ return [];
248
+ }
249
+ throw err;
250
+ }
242
251
  }
243
252
 
244
253
  private rowToProcess(row: ProcessesTable): ManagedProcess {
package/src/core.ts CHANGED
@@ -18,6 +18,7 @@ import type { WebSocketService } from "./core/websocket";
18
18
  import type { Storage } from "./core/storage";
19
19
  import type { Logs } from "./core/logs";
20
20
  import type { Health } from "./core/health";
21
+ import type { CoreEventMap } from "./core/core-events";
21
22
 
22
23
  // ============================================
23
24
  // Auto-detect caller module for plugin define()
@@ -71,7 +72,7 @@ export type EventSchemas = Record<string, z.ZodType<any>>;
71
72
  * ```
72
73
  */
73
74
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
74
- export interface EventRegistry {}
75
+ export interface EventRegistry extends CoreEventMap {}
75
76
 
76
77
  /**
77
78
  * Registry for process type definitions.
package/src/index.ts CHANGED
@@ -112,6 +112,9 @@ export {
112
112
  type EventMetadata,
113
113
  } from "./core/index";
114
114
 
115
+ // Core event types
116
+ export { type CoreEventMap, CORE_EVENT_DEFINITIONS } from "./core/core-events";
117
+
115
118
  // Health checks
116
119
  export {
117
120
  type Health,
package/src/server.ts CHANGED
@@ -56,6 +56,7 @@ import {
56
56
  import { createHealth, createDbHealthCheck, type HealthConfig } from "./core/health";
57
57
  import type { AdminConfig } from "./admin";
58
58
  import { zodSchemaToTs } from "./generator/zod-to-ts";
59
+ import { CORE_EVENT_DEFINITIONS } from "./core/core-events";
59
60
 
60
61
  export interface TypeGenerationConfig {
61
62
  /** Output path for generated client types (e.g., "./src/lib/api.ts") */
@@ -878,7 +879,7 @@ export class AppServer {
878
879
  });
879
880
  }
880
881
 
881
- console.log(JSON.stringify({ routes, processes }));
882
+ console.log(JSON.stringify({ routes, processes, coreEvents: CORE_EVENT_DEFINITIONS }));
882
883
  }
883
884
 
884
885
  /**