@donkeylabs/server 2.0.6 → 2.0.10
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/docs/lifecycle-hooks.md +16 -16
- package/docs/processes.md +93 -0
- package/package.json +13 -3
- package/src/admin/dashboard.ts +717 -0
- package/src/admin/index.ts +85 -0
- package/src/admin/routes.ts +573 -0
- package/src/admin/styles.ts +422 -0
- package/src/core/index.ts +25 -0
- package/src/core/job-adapter-kysely.ts +22 -1
- package/src/core/job-adapter-sqlite.ts +22 -1
- package/src/core/jobs.ts +37 -0
- package/src/core/process-client.ts +121 -0
- package/src/core/processes.ts +67 -0
- package/src/core/storage-adapter-local.ts +403 -0
- package/src/core/storage-adapter-s3.ts +409 -0
- package/src/core/storage.ts +543 -0
- package/src/core/websocket.ts +13 -3
- package/src/core/workflow-adapter-kysely.ts +22 -1
- package/src/core/workflows.ts +37 -0
- package/src/core.ts +10 -1
- package/src/harness.ts +3 -0
- package/src/index.ts +19 -0
- package/src/process-client.ts +7 -0
- package/src/server.ts +72 -31
package/docs/lifecycle-hooks.md
CHANGED
|
@@ -104,20 +104,20 @@ server.onReady(async (ctx) => {
|
|
|
104
104
|
|
|
105
105
|
## onShutdown Hook
|
|
106
106
|
|
|
107
|
-
Called when the server is shutting down.
|
|
107
|
+
Called when the server is shutting down. Receives the same `HookContext` as `onReady` for cleanup operations.
|
|
108
108
|
|
|
109
109
|
```typescript
|
|
110
|
-
server.onShutdown(async () => {
|
|
111
|
-
|
|
110
|
+
server.onShutdown(async (ctx) => {
|
|
111
|
+
ctx.core.logger.info("Server shutting down...");
|
|
112
112
|
|
|
113
113
|
// Close external connections
|
|
114
|
-
await externalApi.disconnect();
|
|
114
|
+
await ctx.services.externalApi.disconnect();
|
|
115
115
|
|
|
116
116
|
// Flush pending data
|
|
117
|
-
await analytics.flush();
|
|
117
|
+
await ctx.services.analytics.flush();
|
|
118
118
|
|
|
119
|
-
//
|
|
120
|
-
await
|
|
119
|
+
// Access plugins for cleanup
|
|
120
|
+
await ctx.plugins.cache.flushAll();
|
|
121
121
|
});
|
|
122
122
|
```
|
|
123
123
|
|
|
@@ -127,8 +127,8 @@ Enable automatic graceful shutdown on SIGTERM/SIGINT:
|
|
|
127
127
|
|
|
128
128
|
```typescript
|
|
129
129
|
server
|
|
130
|
-
.onShutdown(async () => {
|
|
131
|
-
await cleanup();
|
|
130
|
+
.onShutdown(async (ctx) => {
|
|
131
|
+
await cleanup(ctx);
|
|
132
132
|
})
|
|
133
133
|
.enableGracefulShutdown(); // Handles SIGTERM and SIGINT
|
|
134
134
|
|
|
@@ -204,8 +204,8 @@ export const server = new AppServer({ db })
|
|
|
204
204
|
})
|
|
205
205
|
|
|
206
206
|
// Cleanup when SvelteKit stops
|
|
207
|
-
.onShutdown(async () => {
|
|
208
|
-
await
|
|
207
|
+
.onShutdown(async (ctx) => {
|
|
208
|
+
await ctx.services.nvr?.disconnect();
|
|
209
209
|
})
|
|
210
210
|
|
|
211
211
|
// Handle errors
|
|
@@ -263,8 +263,8 @@ server.onReady(async (ctx) => {
|
|
|
263
263
|
});
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
-
server.onShutdown(async () => {
|
|
267
|
-
|
|
266
|
+
server.onShutdown(async (ctx) => {
|
|
267
|
+
ctx.core.logger.info("Graceful shutdown initiated");
|
|
268
268
|
// Cleanup happens automatically for core services
|
|
269
269
|
});
|
|
270
270
|
|
|
@@ -303,14 +303,14 @@ server.onReady(async (ctx) => {
|
|
|
303
303
|
Don't let shutdown hang indefinitely:
|
|
304
304
|
|
|
305
305
|
```typescript
|
|
306
|
-
server.onShutdown(async () => {
|
|
306
|
+
server.onShutdown(async (ctx) => {
|
|
307
307
|
const timeout = setTimeout(() => {
|
|
308
|
-
|
|
308
|
+
ctx.core.logger.error("Shutdown timeout - forcing exit");
|
|
309
309
|
process.exit(1);
|
|
310
310
|
}, 30000);
|
|
311
311
|
|
|
312
312
|
try {
|
|
313
|
-
await gracefulCleanup();
|
|
313
|
+
await gracefulCleanup(ctx);
|
|
314
314
|
} finally {
|
|
315
315
|
clearTimeout(timeout);
|
|
316
316
|
}
|
package/docs/processes.md
CHANGED
|
@@ -214,6 +214,10 @@ const client = await ProcessClient.connect({
|
|
|
214
214
|
heartbeatInterval: 5000, // Send heartbeat every 5s (default)
|
|
215
215
|
reconnectInterval: 2000, // Retry connection every 2s
|
|
216
216
|
maxReconnectAttempts: 30, // Max reconnection attempts
|
|
217
|
+
stats: {
|
|
218
|
+
enabled: true, // Enable CPU/memory stats emission
|
|
219
|
+
interval: 2000, // Emit stats every 2s (default: 5000)
|
|
220
|
+
},
|
|
217
221
|
});
|
|
218
222
|
```
|
|
219
223
|
|
|
@@ -296,6 +300,13 @@ interface ProcessDefinition {
|
|
|
296
300
|
|
|
297
301
|
/** Max restart attempts (default: 3) */
|
|
298
302
|
maxRestarts?: number;
|
|
303
|
+
|
|
304
|
+
/** Callbacks */
|
|
305
|
+
onMessage?: (process, message) => void;
|
|
306
|
+
onCrash?: (process, exitCode) => void;
|
|
307
|
+
onUnhealthy?: (process) => void;
|
|
308
|
+
onRestart?: (oldProcess, newProcess, attempt) => void;
|
|
309
|
+
onStats?: (process, stats: ProcessStats) => void;
|
|
299
310
|
}
|
|
300
311
|
```
|
|
301
312
|
|
|
@@ -380,6 +391,8 @@ The server emits these events for process lifecycle:
|
|
|
380
391
|
| `process.connected` | `{ processId, name }` | Client connected |
|
|
381
392
|
| `process.{name}.{event}` | Event data | Custom process event |
|
|
382
393
|
| `process.heartbeat` | `{ processId, name }` | Heartbeat received |
|
|
394
|
+
| `process.stats` | `{ processId, name, stats }` | CPU/memory stats (if enabled) |
|
|
395
|
+
| `process.{name}.stats` | `{ processId, name, stats }` | Stats for specific process type |
|
|
383
396
|
| `process.stale` | `{ processId, name, timeSince }` | No heartbeat |
|
|
384
397
|
| `process.stopped` | `{ processId, name, exitCode }` | Process stopped |
|
|
385
398
|
| `process.crashed` | `{ processId, name, error }` | Process crashed |
|
|
@@ -441,6 +454,86 @@ router.route("subscribe").sse({
|
|
|
441
454
|
<progress value={progress} max="100">{progress}%</progress>
|
|
442
455
|
```
|
|
443
456
|
|
|
457
|
+
## Real-time Stats Monitoring
|
|
458
|
+
|
|
459
|
+
ProcessClient can emit CPU and memory stats at regular intervals for monitoring.
|
|
460
|
+
|
|
461
|
+
### Enable Stats in Wrapper
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
// workers/my-worker.ts
|
|
465
|
+
import { ProcessClient } from "@donkeylabs/server/process-client";
|
|
466
|
+
|
|
467
|
+
const client = await ProcessClient.connect({
|
|
468
|
+
stats: {
|
|
469
|
+
enabled: true, // Enable stats emission
|
|
470
|
+
interval: 2000, // Emit every 2 seconds (default: 5000)
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// Your process logic...
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Stats Shape
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
interface ProcessStats {
|
|
481
|
+
cpu: {
|
|
482
|
+
user: number; // User CPU time in microseconds
|
|
483
|
+
system: number; // System CPU time in microseconds
|
|
484
|
+
percent: number; // CPU usage percentage (0-100, can exceed on multi-core)
|
|
485
|
+
};
|
|
486
|
+
memory: {
|
|
487
|
+
rss: number; // Resident set size in bytes
|
|
488
|
+
heapTotal: number; // V8 heap total in bytes
|
|
489
|
+
heapUsed: number; // V8 heap used in bytes
|
|
490
|
+
external: number; // External memory (C++ objects) in bytes
|
|
491
|
+
};
|
|
492
|
+
uptime: number; // Process uptime in seconds
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Listen for Stats on Server
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
// Option 1: Use onStats callback in process definition
|
|
500
|
+
processes.define("video-encoder", {
|
|
501
|
+
command: "bun",
|
|
502
|
+
args: ["./workers/encoder.ts"],
|
|
503
|
+
onStats: (proc, stats) => {
|
|
504
|
+
console.log(`[${proc.name}] CPU: ${stats.cpu.percent.toFixed(1)}%`);
|
|
505
|
+
console.log(`[${proc.name}] Memory: ${(stats.memory.rss / 1e6).toFixed(1)}MB`);
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Option 2: Listen via events
|
|
510
|
+
ctx.core.events.on("process.video-encoder.stats", ({ processId, stats }) => {
|
|
511
|
+
// Store in time-series DB, send to monitoring dashboard, etc.
|
|
512
|
+
metrics.gauge("process.cpu", stats.cpu.percent, { processId });
|
|
513
|
+
metrics.gauge("process.memory", stats.memory.rss, { processId });
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Option 3: Listen to all process stats
|
|
517
|
+
ctx.core.events.on("process.stats", ({ processId, name, stats }) => {
|
|
518
|
+
console.log(`${name} (${processId}): ${stats.cpu.percent}% CPU`);
|
|
519
|
+
});
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Broadcast Stats to Dashboard
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
// Forward stats to SSE for real-time dashboard
|
|
526
|
+
ctx.core.events.on("process.stats", ({ processId, name, stats }) => {
|
|
527
|
+
ctx.core.sse.broadcast("admin:processes", "stats", {
|
|
528
|
+
processId,
|
|
529
|
+
name,
|
|
530
|
+
cpu: stats.cpu.percent,
|
|
531
|
+
memory: stats.memory.rss,
|
|
532
|
+
uptime: stats.uptime,
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
```
|
|
536
|
+
|
|
444
537
|
## Heartbeat Monitoring
|
|
445
538
|
|
|
446
539
|
The ProcessClient automatically sends heartbeats. If heartbeats stop:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@donkeylabs/server",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Type-safe plugin system for building RPC-style APIs with Bun",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -66,7 +66,17 @@
|
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"typescript": "^5",
|
|
68
68
|
"kysely": "^0.27.0 || ^0.28.0",
|
|
69
|
-
"zod": "^3.20.0"
|
|
69
|
+
"zod": "^3.20.0",
|
|
70
|
+
"@aws-sdk/client-s3": "^3.0.0",
|
|
71
|
+
"@aws-sdk/s3-request-presigner": "^3.0.0"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"@aws-sdk/client-s3": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@aws-sdk/s3-request-presigner": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
70
80
|
},
|
|
71
81
|
"dependencies": {
|
|
72
82
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
@@ -86,4 +96,4 @@
|
|
|
86
96
|
"url": "https://github.com/donkeylabs-io/donkeylabs"
|
|
87
97
|
},
|
|
88
98
|
"license": "MIT"
|
|
89
|
-
}
|
|
99
|
+
}
|