@donkeylabs/server 2.0.7 → 2.0.11

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.
@@ -104,20 +104,20 @@ server.onReady(async (ctx) => {
104
104
 
105
105
  ## onShutdown Hook
106
106
 
107
- Called when the server is shutting down. Use for cleanup operations.
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
- console.log("Server shutting down...");
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
- // Save state
120
- await saveCheckpoint();
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 cleanup();
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
- console.log("Graceful shutdown initiated");
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
- console.error("Shutdown timeout - forcing exit");
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.7",
3
+ "version": "2.0.11",
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
+ }