@donkeylabs/server 1.1.17 → 1.1.19

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/CLAUDE.md CHANGED
@@ -130,7 +130,7 @@ await api.users.create({ email, name });
130
130
  | `.html()` | htmx partials |
131
131
 
132
132
  ## Core Services (ctx.core)
133
- `ctx.core.logger`, `ctx.core.cache`, `ctx.core.jobs`, `ctx.core.events`, `ctx.core.rateLimiter`, `ctx.core.sse`
133
+ `ctx.core.logger`, `ctx.core.cache`, `ctx.core.jobs`, `ctx.core.events`, `ctx.core.rateLimiter`, `ctx.core.sse`, `ctx.core.processes`, `ctx.core.workflows`
134
134
 
135
135
  ## Error Handling
136
136
  ```ts
@@ -150,4 +150,8 @@ bun --bun tsc --noEmit # Type check
150
150
  `get_project_info`, `create_plugin`, `add_migration`, `add_service_method`, `create_router`, `add_route`, `generate_types`, `list_plugins`, `scaffold_feature`
151
151
 
152
152
  ## Detailed Docs
153
- See `docs/` for: handlers, middleware, database, plugins, testing, jobs, cron, sse, workflows, router, errors, sveltekit-adapter.
153
+ **For comprehensive documentation, see the `docs/` directory.** Each service has its own detailed guide:
154
+ - Core: logger, cache, events, cron, jobs, external-jobs, processes, workflows, sse, rate-limiter, errors
155
+ - API: router, handlers, middleware
156
+ - Server: lifecycle-hooks, services (custom services)
157
+ - Infrastructure: database, plugins, testing, sveltekit-adapter, api-client
@@ -11,6 +11,9 @@ Core services are foundational utilities automatically available to all plugins
11
11
  | [Events](events.md) | Pub/sub event system | In-memory |
12
12
  | [Cron](cron.md) | Scheduled recurring tasks | In-memory |
13
13
  | [Jobs](jobs.md) | Background job queue | In-memory |
14
+ | [External Jobs](external-jobs.md) | Jobs in any language | SQLite |
15
+ | [Processes](processes.md) | Long-running daemons | SQLite |
16
+ | [Workflows](workflows.md) | Multi-step orchestration | In-memory |
14
17
  | [SSE](sse.md) | Server-Sent Events | In-memory |
15
18
  | [RateLimiter](rate-limiter.md) | Request throttling | In-memory |
16
19
  | [Errors](errors.md) | HTTP error factories | - |
@@ -336,3 +339,10 @@ See individual service documentation for adapter interfaces:
336
339
  - [Events Adapters](events.md#custom-adapters)
337
340
  - [Jobs Adapters](jobs.md#custom-adapters)
338
341
  - [Rate Limiter Adapters](rate-limiter.md#custom-adapters)
342
+
343
+ ---
344
+
345
+ ## Related Documentation
346
+
347
+ - [Custom Services](services.md) - Register app-specific services with `defineService()`
348
+ - [Lifecycle Hooks](lifecycle-hooks.md) - `onReady`, `onShutdown`, `onError` hooks
@@ -0,0 +1,348 @@
1
+ # Server Lifecycle Hooks
2
+
3
+ Lifecycle hooks allow you to execute code at specific points in the server's lifecycle: after initialization, during shutdown, and when errors occur.
4
+
5
+ ## Overview
6
+
7
+ | Hook | When Called | Use Case |
8
+ |------|-------------|----------|
9
+ | `onReady` | After server starts, plugins initialized | Initialize app-specific services, warm caches |
10
+ | `onShutdown` | Before server stops | Cleanup connections, flush buffers |
11
+ | `onError` | On unhandled errors | Error reporting, alerts |
12
+
13
+ ## onReady Hook
14
+
15
+ Called after the server is fully initialized, plugins are ready, and the server is accepting requests.
16
+
17
+ ```typescript
18
+ import { AppServer } from "@donkeylabs/server";
19
+
20
+ const server = new AppServer({ db, port: 3000 });
21
+
22
+ server.onReady(async (ctx) => {
23
+ console.log("Server is ready!");
24
+
25
+ // Access all services
26
+ ctx.core.logger.info("Server started", { port: 3000 });
27
+
28
+ // Initialize app-specific classes
29
+ const dashboard = new AdminDashboard(ctx.plugins.auth);
30
+ await dashboard.initialize();
31
+
32
+ // Register as a service for use in routes
33
+ ctx.setService("dashboard", dashboard);
34
+
35
+ // Warm caches
36
+ await ctx.core.cache.set("config", await loadConfig());
37
+
38
+ // Start background tasks
39
+ ctx.core.cron.schedule("0 * * * *", async () => {
40
+ await ctx.plugins.reports.generateHourly();
41
+ });
42
+ });
43
+
44
+ await server.start();
45
+ ```
46
+
47
+ ### HookContext
48
+
49
+ The `onReady` callback receives a `HookContext` with:
50
+
51
+ ```typescript
52
+ interface HookContext {
53
+ /** Database instance (Kysely) */
54
+ db: Kysely<any>;
55
+
56
+ /** Core services */
57
+ core: {
58
+ logger: Logger;
59
+ cache: Cache;
60
+ events: Events;
61
+ cron: Cron;
62
+ jobs: Jobs;
63
+ sse: SSE;
64
+ rateLimiter: RateLimiter;
65
+ errors: Errors;
66
+ workflows: Workflows;
67
+ processes: Processes;
68
+ };
69
+
70
+ /** Plugin services */
71
+ plugins: Record<string, any>;
72
+
73
+ /** Server configuration */
74
+ config: Record<string, any>;
75
+
76
+ /** Custom registered services */
77
+ services: Record<string, any>;
78
+
79
+ /** Register a service at runtime */
80
+ setService: <T>(name: string, service: T) => void;
81
+ }
82
+ ```
83
+
84
+ ### Multiple onReady Handlers
85
+
86
+ You can register multiple handlers - they execute in registration order:
87
+
88
+ ```typescript
89
+ server.onReady(async (ctx) => {
90
+ // First: Initialize core dependencies
91
+ await initializeDatabase(ctx.db);
92
+ });
93
+
94
+ server.onReady(async (ctx) => {
95
+ // Second: Warm caches
96
+ await warmCaches(ctx);
97
+ });
98
+
99
+ server.onReady(async (ctx) => {
100
+ // Third: Start background jobs
101
+ ctx.core.jobs.start();
102
+ });
103
+ ```
104
+
105
+ ## onShutdown Hook
106
+
107
+ Called when the server is shutting down. Use for cleanup operations.
108
+
109
+ ```typescript
110
+ server.onShutdown(async () => {
111
+ console.log("Server shutting down...");
112
+
113
+ // Close external connections
114
+ await externalApi.disconnect();
115
+
116
+ // Flush pending data
117
+ await analytics.flush();
118
+
119
+ // Save state
120
+ await saveCheckpoint();
121
+ });
122
+ ```
123
+
124
+ ### Graceful Shutdown
125
+
126
+ Enable automatic graceful shutdown on SIGTERM/SIGINT:
127
+
128
+ ```typescript
129
+ server
130
+ .onShutdown(async () => {
131
+ await cleanup();
132
+ })
133
+ .enableGracefulShutdown(); // Handles SIGTERM and SIGINT
134
+
135
+ await server.start();
136
+ ```
137
+
138
+ With `enableGracefulShutdown()`:
139
+ 1. SIGTERM/SIGINT triggers shutdown
140
+ 2. Server stops accepting new requests
141
+ 3. Running requests complete (with timeout)
142
+ 4. `onShutdown` handlers execute
143
+ 5. Core services shut down (jobs, cron, SSE, processes)
144
+ 6. Process exits
145
+
146
+ ### Manual Shutdown
147
+
148
+ You can also trigger shutdown programmatically:
149
+
150
+ ```typescript
151
+ // Somewhere in your code
152
+ await server.shutdown();
153
+ ```
154
+
155
+ ## onError Hook
156
+
157
+ Called when an unhandled error occurs during request handling or in background tasks.
158
+
159
+ ```typescript
160
+ server.onError(async (error, ctx) => {
161
+ // Log the error
162
+ ctx?.core.logger.error("Unhandled error", {
163
+ message: error.message,
164
+ stack: error.stack,
165
+ });
166
+
167
+ // Send to error tracking service
168
+ await errorTracker.capture(error, {
169
+ tags: { environment: process.env.NODE_ENV },
170
+ });
171
+
172
+ // Alert on critical errors
173
+ if (isCritical(error)) {
174
+ await ctx?.plugins.notifications.sendAlert({
175
+ channel: "ops",
176
+ message: `Critical error: ${error.message}`,
177
+ });
178
+ }
179
+ });
180
+ ```
181
+
182
+ **Note:** `ctx` may be undefined if the error occurs outside of a request context.
183
+
184
+ ## SvelteKit Adapter Usage
185
+
186
+ Lifecycle hooks are especially useful with the SvelteKit adapter where you don't call `server.start()` directly:
187
+
188
+ ```typescript
189
+ // src/server/index.ts
190
+ import { AppServer } from "@donkeylabs/server";
191
+ import { db } from "./db";
192
+
193
+ export const server = new AppServer({ db })
194
+ .use(authPlugin)
195
+ .use(usersPlugin)
196
+ .router(usersRouter)
197
+
198
+ // Initialize app-specific services after plugins are ready
199
+ .onReady(async (ctx) => {
200
+ // This runs when SvelteKit starts
201
+ const nvr = new NVR(ctx.plugins.auth);
202
+ await nvr.connect();
203
+ ctx.setService("nvr", nvr);
204
+ })
205
+
206
+ // Cleanup when SvelteKit stops
207
+ .onShutdown(async () => {
208
+ await cleanup();
209
+ })
210
+
211
+ // Handle errors
212
+ .onError(async (error, ctx) => {
213
+ await reportError(error);
214
+ });
215
+
216
+ // Export for SvelteKit adapter
217
+ export type AppContext = typeof server extends AppServer<infer C> ? C : never;
218
+ ```
219
+
220
+ ## Complete Example
221
+
222
+ ```typescript
223
+ import { AppServer, defineService } from "@donkeylabs/server";
224
+
225
+ // Define services
226
+ const cacheWarmerService = defineService("cacheWarmer", (ctx) => ({
227
+ warm: async () => {
228
+ const users = await ctx.db.selectFrom("users").selectAll().execute();
229
+ for (const user of users) {
230
+ await ctx.core.cache.set(`user:${user.id}`, user, 3600000);
231
+ }
232
+ },
233
+ }));
234
+
235
+ // Create server
236
+ const server = new AppServer({
237
+ db,
238
+ port: 3000,
239
+ config: {
240
+ environment: process.env.NODE_ENV,
241
+ },
242
+ });
243
+
244
+ // Register plugins and services
245
+ server
246
+ .use(authPlugin)
247
+ .use(usersPlugin)
248
+ .registerService(cacheWarmerService);
249
+
250
+ // Lifecycle hooks
251
+ server.onReady(async (ctx) => {
252
+ ctx.core.logger.info("Server ready", {
253
+ port: 3000,
254
+ environment: ctx.config.environment,
255
+ });
256
+
257
+ // Warm caches on startup
258
+ await ctx.services.cacheWarmer.warm();
259
+
260
+ // Schedule periodic cache warming
261
+ ctx.core.cron.schedule("*/30 * * * *", async () => {
262
+ await ctx.services.cacheWarmer.warm();
263
+ });
264
+ });
265
+
266
+ server.onShutdown(async () => {
267
+ console.log("Graceful shutdown initiated");
268
+ // Cleanup happens automatically for core services
269
+ });
270
+
271
+ server.onError(async (error, ctx) => {
272
+ console.error("Unhandled error:", error);
273
+ // Report to monitoring service
274
+ });
275
+
276
+ // Enable graceful shutdown and start
277
+ server.enableGracefulShutdown();
278
+ await server.start();
279
+
280
+ console.log("Server running on http://localhost:3000");
281
+ ```
282
+
283
+ ## Best Practices
284
+
285
+ ### 1. Keep onReady Fast
286
+ Don't block startup with heavy operations:
287
+
288
+ ```typescript
289
+ // Good - async initialization
290
+ server.onReady(async (ctx) => {
291
+ // Fire and forget for non-critical warmup
292
+ ctx.services.cacheWarmer.warm().catch(console.error);
293
+ });
294
+
295
+ // Avoid - blocking startup
296
+ server.onReady(async (ctx) => {
297
+ // This delays server readiness
298
+ await heavyInitialization(); // 30 seconds...
299
+ });
300
+ ```
301
+
302
+ ### 2. Handle Shutdown Timeouts
303
+ Don't let shutdown hang indefinitely:
304
+
305
+ ```typescript
306
+ server.onShutdown(async () => {
307
+ const timeout = setTimeout(() => {
308
+ console.error("Shutdown timeout - forcing exit");
309
+ process.exit(1);
310
+ }, 30000);
311
+
312
+ try {
313
+ await gracefulCleanup();
314
+ } finally {
315
+ clearTimeout(timeout);
316
+ }
317
+ });
318
+ ```
319
+
320
+ ### 3. Order Matters
321
+ Hooks execute in registration order. Register dependencies first:
322
+
323
+ ```typescript
324
+ // First: Initialize the connection
325
+ server.onReady(async (ctx) => {
326
+ const conn = await createConnection();
327
+ ctx.setService("conn", conn);
328
+ });
329
+
330
+ // Second: Use the connection
331
+ server.onReady(async (ctx) => {
332
+ await ctx.services.conn.ping(); // conn is available
333
+ });
334
+ ```
335
+
336
+ ### 4. Error Handling in Hooks
337
+ Errors in hooks can prevent startup or cleanup:
338
+
339
+ ```typescript
340
+ server.onReady(async (ctx) => {
341
+ try {
342
+ await riskyOperation();
343
+ } catch (error) {
344
+ ctx.core.logger.error("Non-critical init failed", { error });
345
+ // Don't rethrow - allow server to start
346
+ }
347
+ });
348
+ ```