@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 +1 -1
- package/src/core/core-events.ts +116 -0
- package/src/core/index.ts +2 -0
- package/src/core/process-adapter-kysely.ts +24 -15
- package/src/core.ts +2 -1
- package/src/index.ts +3 -0
- package/src/server.ts +2 -1
package/package.json
CHANGED
|
@@ -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
|
@@ -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
|
-
//
|
|
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
|
-
|
|
230
|
-
.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
eb(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
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
|
/**
|