@outfitter/daemon 0.1.0-rc.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.
@@ -0,0 +1,771 @@
1
+ import { Result, TaggedErrorClass } from "@outfitter/contracts";
2
+ import { LoggerInstance } from "@outfitter/logging";
3
+ declare const DaemonErrorBase: TaggedErrorClass<"DaemonError", {
4
+ code: DaemonErrorCode;
5
+ message: string;
6
+ }>;
7
+ /**
8
+ * Error codes for daemon operations.
9
+ *
10
+ * - `ALREADY_RUNNING`: Daemon start requested but already running
11
+ * - `NOT_RUNNING`: Daemon stop requested but not running
12
+ * - `SHUTDOWN_TIMEOUT`: Graceful shutdown exceeded timeout
13
+ * - `PID_ERROR`: PID file operations failed
14
+ * - `START_FAILED`: Daemon failed to start
15
+ */
16
+ type DaemonErrorCode = "ALREADY_RUNNING" | "NOT_RUNNING" | "SHUTDOWN_TIMEOUT" | "PID_ERROR" | "START_FAILED";
17
+ /**
18
+ * Error type for daemon operations.
19
+ *
20
+ * Uses the TaggedError pattern for type-safe error handling with Result types.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const error = new DaemonError({
25
+ * code: "ALREADY_RUNNING",
26
+ * message: "Daemon is already running with PID 1234",
27
+ * });
28
+ *
29
+ * if (error.code === "ALREADY_RUNNING") {
30
+ * console.log("Stop the existing daemon first");
31
+ * }
32
+ * ```
33
+ */
34
+ declare class DaemonError extends DaemonErrorBase {}
35
+ /**
36
+ * Daemon lifecycle states.
37
+ *
38
+ * State machine transitions:
39
+ * - `stopped` -> `starting` (via start())
40
+ * - `starting` -> `running` (when initialization complete)
41
+ * - `starting` -> `stopped` (if start fails)
42
+ * - `running` -> `stopping` (via stop() or signal)
43
+ * - `stopping` -> `stopped` (when shutdown complete)
44
+ */
45
+ type DaemonState = "stopped" | "starting" | "running" | "stopping";
46
+ /**
47
+ * Configuration options for creating a daemon.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const options: DaemonOptions = {
52
+ * name: "my-daemon",
53
+ * pidFile: "/var/run/my-daemon.pid",
54
+ * logger: myLogger,
55
+ * shutdownTimeout: 10000, // 10 seconds
56
+ * };
57
+ *
58
+ * const daemon = createDaemon(options);
59
+ * ```
60
+ */
61
+ interface DaemonOptions {
62
+ /**
63
+ * Unique name identifying this daemon.
64
+ * Used in log messages and error context.
65
+ */
66
+ name: string;
67
+ /**
68
+ * Absolute path to the PID file.
69
+ * The daemon writes its process ID here on start and removes it on stop.
70
+ * Used to prevent multiple instances and for external process management.
71
+ */
72
+ pidFile: string;
73
+ /**
74
+ * Optional logger instance for daemon messages.
75
+ * If not provided, logging is disabled.
76
+ */
77
+ logger?: LoggerInstance;
78
+ /**
79
+ * Maximum time in milliseconds to wait for graceful shutdown.
80
+ * After this timeout, the daemon will force stop.
81
+ * @defaultValue 5000
82
+ */
83
+ shutdownTimeout?: number;
84
+ }
85
+ /**
86
+ * Shutdown handler function type.
87
+ *
88
+ * Called during graceful shutdown to allow cleanup of resources.
89
+ * Must complete within the shutdown timeout.
90
+ */
91
+ type ShutdownHandler = () => Promise<void>;
92
+ /**
93
+ * Daemon instance interface.
94
+ *
95
+ * Provides lifecycle management for a background process including
96
+ * start/stop operations, signal handling, and shutdown hooks.
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const daemon = createDaemon({
101
+ * name: "my-service",
102
+ * pidFile: "/var/run/my-service.pid",
103
+ * });
104
+ *
105
+ * // Register cleanup handlers
106
+ * daemon.onShutdown(async () => {
107
+ * await database.close();
108
+ * });
109
+ *
110
+ * // Start the daemon
111
+ * const result = await daemon.start();
112
+ * if (result.isErr()) {
113
+ * console.error("Failed to start:", result.error.message);
114
+ * process.exit(1);
115
+ * }
116
+ *
117
+ * // Daemon is now running...
118
+ * // Stop gracefully when needed
119
+ * await daemon.stop();
120
+ * ```
121
+ */
122
+ interface Daemon {
123
+ /**
124
+ * Current lifecycle state of the daemon.
125
+ */
126
+ readonly state: DaemonState;
127
+ /**
128
+ * Start the daemon.
129
+ *
130
+ * Creates PID file and registers signal handlers.
131
+ * Transitions from `stopped` to `starting` then `running`.
132
+ *
133
+ * @returns Result with void on success, or DaemonError on failure
134
+ */
135
+ start(): Promise<Result<void, DaemonError>>;
136
+ /**
137
+ * Stop the daemon gracefully.
138
+ *
139
+ * Runs shutdown handlers, removes PID file, and cleans up signal handlers.
140
+ * Transitions from `running` to `stopping` then `stopped`.
141
+ *
142
+ * @returns Result with void on success, or DaemonError on failure
143
+ */
144
+ stop(): Promise<Result<void, DaemonError>>;
145
+ /**
146
+ * Check if the daemon is currently running.
147
+ *
148
+ * @returns true if state is "running", false otherwise
149
+ */
150
+ isRunning(): boolean;
151
+ /**
152
+ * Register a shutdown handler to be called during graceful shutdown.
153
+ *
154
+ * Multiple handlers can be registered and will be called in registration order.
155
+ * Handlers must complete within the shutdown timeout.
156
+ *
157
+ * @param handler - Async function to execute during shutdown
158
+ */
159
+ onShutdown(handler: ShutdownHandler): void;
160
+ }
161
+ import { TaggedErrorClass as TaggedErrorClass2 } from "@outfitter/contracts";
162
+ declare const StaleSocketErrorBase: TaggedErrorClass2<"StaleSocketError", {
163
+ message: string;
164
+ socketPath: string;
165
+ pid?: number;
166
+ }>;
167
+ declare const ConnectionRefusedErrorBase: TaggedErrorClass2<"ConnectionRefusedError", {
168
+ message: string;
169
+ socketPath: string;
170
+ }>;
171
+ declare const ConnectionTimeoutErrorBase: TaggedErrorClass2<"ConnectionTimeoutError", {
172
+ message: string;
173
+ socketPath: string;
174
+ timeoutMs: number;
175
+ }>;
176
+ declare const ProtocolErrorBase: TaggedErrorClass2<"ProtocolError", {
177
+ message: string;
178
+ socketPath: string;
179
+ details?: string;
180
+ }>;
181
+ declare const LockErrorBase: TaggedErrorClass2<"LockError", {
182
+ message: string;
183
+ lockPath: string;
184
+ pid?: number;
185
+ }>;
186
+ /**
187
+ * Socket exists but daemon process is dead.
188
+ *
189
+ * Indicates a stale socket from a crashed daemon that needs cleanup
190
+ * before a new daemon can start.
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const error = new StaleSocketError({
195
+ * message: "Daemon socket is stale",
196
+ * socketPath: "/run/user/1000/waymark/daemon.sock",
197
+ * pid: 12345,
198
+ * });
199
+ * ```
200
+ */
201
+ declare class StaleSocketError extends StaleSocketErrorBase {}
202
+ /**
203
+ * Daemon is not running (connection refused).
204
+ *
205
+ * Socket does not exist or connection was actively refused,
206
+ * indicating no daemon is listening.
207
+ *
208
+ * @example
209
+ * ```typescript
210
+ * const error = new ConnectionRefusedError({
211
+ * message: "Connection refused",
212
+ * socketPath: "/run/user/1000/waymark/daemon.sock",
213
+ * });
214
+ * ```
215
+ */
216
+ declare class ConnectionRefusedError extends ConnectionRefusedErrorBase {}
217
+ /**
218
+ * Daemon did not respond within timeout.
219
+ *
220
+ * Connection was established but daemon failed to respond
221
+ * to ping or request within the configured timeout.
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * const error = new ConnectionTimeoutError({
226
+ * message: "Connection timed out after 5000ms",
227
+ * socketPath: "/run/user/1000/waymark/daemon.sock",
228
+ * timeoutMs: 5000,
229
+ * });
230
+ * ```
231
+ */
232
+ declare class ConnectionTimeoutError extends ConnectionTimeoutErrorBase {}
233
+ /**
234
+ * Invalid response format from daemon.
235
+ *
236
+ * Daemon responded but the response could not be parsed
237
+ * or did not match the expected protocol format.
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * const error = new ProtocolError({
242
+ * message: "Invalid JSON response",
243
+ * socketPath: "/run/user/1000/waymark/daemon.sock",
244
+ * details: "Unexpected token at position 42",
245
+ * });
246
+ * ```
247
+ */
248
+ declare class ProtocolError extends ProtocolErrorBase {}
249
+ /**
250
+ * Failed to acquire or release daemon lock.
251
+ *
252
+ * Used when PID file operations fail due to permissions,
253
+ * concurrent access, or file system errors.
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * const error = new LockError({
258
+ * message: "Daemon already running",
259
+ * lockPath: "/run/user/1000/waymark/daemon.lock",
260
+ * pid: 12345,
261
+ * });
262
+ * ```
263
+ */
264
+ declare class LockError extends LockErrorBase {}
265
+ /**
266
+ * Union of all daemon connection error types.
267
+ *
268
+ * Use for exhaustive matching on connection failures:
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * function handleError(error: DaemonConnectionError): string {
273
+ * switch (error._tag) {
274
+ * case "StaleSocketError":
275
+ * return `Stale socket at ${error.socketPath}`;
276
+ * case "ConnectionRefusedError":
277
+ * return "Daemon not running";
278
+ * case "ConnectionTimeoutError":
279
+ * return `Timeout after ${error.timeoutMs}ms`;
280
+ * case "ProtocolError":
281
+ * return `Protocol error: ${error.details}`;
282
+ * }
283
+ * }
284
+ * ```
285
+ */
286
+ type DaemonConnectionError = StaleSocketError | ConnectionRefusedError | ConnectionTimeoutError | ProtocolError;
287
+ /**
288
+ * Check if running on a Unix-like platform (macOS or Linux).
289
+ *
290
+ * @returns true on macOS/Linux, false on Windows
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * if (isUnixPlatform()) {
295
+ * // Use Unix domain sockets
296
+ * } else {
297
+ * // Use named pipes
298
+ * }
299
+ * ```
300
+ */
301
+ declare function isUnixPlatform(): boolean;
302
+ /**
303
+ * Get the Unix domain socket path for a tool's daemon.
304
+ *
305
+ * @param toolName - Name of the tool (e.g., "waymark", "firewatch")
306
+ * @returns Absolute path to the daemon socket
307
+ *
308
+ * @example
309
+ * ```typescript
310
+ * const socketPath = getSocketPath("waymark");
311
+ * // "/run/user/1000/waymark/daemon.sock" on Linux
312
+ * // "/var/folders/.../waymark/daemon.sock" on macOS
313
+ * ```
314
+ */
315
+ declare function getSocketPath(toolName: string): string;
316
+ /**
317
+ * Get the lock file path for a tool's daemon.
318
+ *
319
+ * @param toolName - Name of the tool (e.g., "waymark", "firewatch")
320
+ * @returns Absolute path to the daemon lock file
321
+ *
322
+ * @example
323
+ * ```typescript
324
+ * const lockPath = getLockPath("waymark");
325
+ * // "/run/user/1000/waymark/daemon.lock" on Linux
326
+ * ```
327
+ */
328
+ declare function getLockPath(toolName: string): string;
329
+ /**
330
+ * Get the PID file path for a tool's daemon.
331
+ *
332
+ * @param toolName - Name of the tool (e.g., "waymark", "firewatch")
333
+ * @returns Absolute path to the daemon PID file
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * const pidPath = getPidPath("waymark");
338
+ * // "/run/user/1000/waymark/daemon.pid" on Linux
339
+ * ```
340
+ */
341
+ declare function getPidPath(toolName: string): string;
342
+ /**
343
+ * Get the directory containing daemon files for a tool.
344
+ *
345
+ * @param toolName - Name of the tool (e.g., "waymark", "firewatch")
346
+ * @returns Absolute path to the daemon directory
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * const daemonDir = getDaemonDir("waymark");
351
+ * // "/run/user/1000/waymark" on Linux
352
+ * ```
353
+ */
354
+ declare function getDaemonDir(toolName: string): string;
355
+ import { Result as Result2 } from "@outfitter/contracts";
356
+ /**
357
+ * Handle returned when a lock is successfully acquired.
358
+ *
359
+ * Must be passed to `releaseDaemonLock` to properly release the lock.
360
+ */
361
+ interface LockHandle {
362
+ /** Path to the lock file */
363
+ readonly lockPath: string;
364
+ /** PID that owns the lock */
365
+ readonly pid: number;
366
+ }
367
+ /**
368
+ * Check if a process with the given PID is alive.
369
+ *
370
+ * Uses `process.kill(pid, 0)` which sends no signal but checks
371
+ * if the process exists and is accessible.
372
+ *
373
+ * @param pid - Process ID to check
374
+ * @returns true if process exists and is accessible, false otherwise
375
+ *
376
+ * @example
377
+ * ```typescript
378
+ * if (isProcessAlive(12345)) {
379
+ * console.log("Process is still running");
380
+ * } else {
381
+ * console.log("Process has exited");
382
+ * }
383
+ * ```
384
+ */
385
+ declare function isProcessAlive(pid: number): boolean;
386
+ /**
387
+ * Check if a daemon is alive by reading its lock file.
388
+ *
389
+ * @param lockPath - Path to the daemon lock file
390
+ * @returns true if lock file exists and process is alive, false otherwise
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * const alive = await isDaemonAlive("/run/user/1000/waymark/daemon.lock");
395
+ * if (!alive) {
396
+ * // Safe to start a new daemon
397
+ * }
398
+ * ```
399
+ */
400
+ declare function isDaemonAlive(lockPath: string): Promise<boolean>;
401
+ /**
402
+ * Acquire an exclusive lock for a daemon.
403
+ *
404
+ * Uses a PID file approach compatible with Bun:
405
+ * 1. Check if lock file exists with a valid PID
406
+ * 2. If PID is alive, refuse (daemon already running)
407
+ * 3. If PID is stale or no lock, write our PID atomically
408
+ *
409
+ * @param lockPath - Path to create the lock file
410
+ * @returns Result with LockHandle on success, LockError on failure
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const result = await acquireDaemonLock("/run/user/1000/waymark/daemon.lock");
415
+ *
416
+ * if (result.isOk()) {
417
+ * console.log(`Lock acquired for PID ${result.value.pid}`);
418
+ * // ... run daemon ...
419
+ * await releaseDaemonLock(result.value);
420
+ * } else {
421
+ * console.error(`Failed to acquire lock: ${result.error.message}`);
422
+ * }
423
+ * ```
424
+ */
425
+ declare function acquireDaemonLock(lockPath: string): Promise<Result2<LockHandle, LockError>>;
426
+ /**
427
+ * Release a daemon lock.
428
+ *
429
+ * Removes the lock file only if the PID inside matches the handle.
430
+ * This prevents accidentally removing a lock acquired by another process.
431
+ *
432
+ * @param handle - Lock handle returned from acquireDaemonLock
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * const result = await acquireDaemonLock(lockPath);
437
+ * if (result.isOk()) {
438
+ * try {
439
+ * // ... run daemon ...
440
+ * } finally {
441
+ * await releaseDaemonLock(result.value);
442
+ * }
443
+ * }
444
+ * ```
445
+ */
446
+ declare function releaseDaemonLock(handle: LockHandle): Promise<void>;
447
+ /**
448
+ * Read the PID from a lock file.
449
+ *
450
+ * @param lockPath - Path to the lock file
451
+ * @returns The PID if valid, undefined otherwise
452
+ *
453
+ * @internal
454
+ */
455
+ declare function readLockPid(lockPath: string): Promise<number | undefined>;
456
+ /**
457
+ * Create a new daemon instance.
458
+ *
459
+ * The daemon manages its own lifecycle including PID file creation/removal,
460
+ * signal handling for graceful shutdown, and execution of registered
461
+ * shutdown handlers.
462
+ *
463
+ * @param options - Daemon configuration options
464
+ * @returns Daemon instance
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * const daemon = createDaemon({
469
+ * name: "my-service",
470
+ * pidFile: "/var/run/my-service.pid",
471
+ * shutdownTimeout: 10000,
472
+ * });
473
+ *
474
+ * daemon.onShutdown(async () => {
475
+ * await database.close();
476
+ * });
477
+ *
478
+ * const result = await daemon.start();
479
+ * if (result.isErr()) {
480
+ * console.error("Failed to start:", result.error.message);
481
+ * process.exit(1);
482
+ * }
483
+ * ```
484
+ */
485
+ declare function createDaemon(options: DaemonOptions): Daemon;
486
+ /**
487
+ * Message handler type for processing incoming IPC messages.
488
+ *
489
+ * Receives a parsed message and returns a response to send back to the client.
490
+ * Throwing an error will result in an error response to the client.
491
+ */
492
+ type IpcMessageHandler = (message: unknown) => Promise<unknown>;
493
+ /**
494
+ * IPC server interface for receiving messages from clients.
495
+ *
496
+ * The server listens on a Unix socket and processes incoming messages
497
+ * using the registered message handler.
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * const server = createIpcServer("/var/run/my-daemon.sock");
502
+ *
503
+ * server.onMessage(async (msg) => {
504
+ * if (msg.type === "status") {
505
+ * return { status: "ok", uptime: process.uptime() };
506
+ * }
507
+ * return { error: "Unknown command" };
508
+ * });
509
+ *
510
+ * await server.listen();
511
+ * ```
512
+ */
513
+ interface IpcServer {
514
+ /**
515
+ * Start listening for connections on the Unix socket.
516
+ *
517
+ * Creates the socket file and begins accepting client connections.
518
+ * Messages are processed using the handler registered via onMessage.
519
+ */
520
+ listen(): Promise<void>;
521
+ /**
522
+ * Stop listening and close all connections.
523
+ *
524
+ * Removes the socket file and cleans up resources.
525
+ */
526
+ close(): Promise<void>;
527
+ /**
528
+ * Register a message handler for incoming messages.
529
+ *
530
+ * Only one handler can be registered. Calling this multiple times
531
+ * replaces the previous handler.
532
+ *
533
+ * @param handler - Function to process incoming messages
534
+ */
535
+ onMessage(handler: IpcMessageHandler): void;
536
+ }
537
+ /**
538
+ * IPC client interface for sending messages to a server.
539
+ *
540
+ * The client connects to a Unix socket and can send messages,
541
+ * receiving responses asynchronously.
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * const client = createIpcClient("/var/run/my-daemon.sock");
546
+ *
547
+ * await client.connect();
548
+ *
549
+ * const response = await client.send<StatusResponse>({ type: "status" });
550
+ * console.log("Daemon uptime:", response.uptime);
551
+ *
552
+ * client.close();
553
+ * ```
554
+ */
555
+ interface IpcClient {
556
+ /**
557
+ * Connect to the IPC server.
558
+ *
559
+ * Establishes a connection to the Unix socket. Throws if the
560
+ * server is not available.
561
+ */
562
+ connect(): Promise<void>;
563
+ /**
564
+ * Send a message and wait for a response.
565
+ *
566
+ * Serializes the message to JSON, sends it to the server, and
567
+ * waits for a response.
568
+ *
569
+ * @typeParam T - Expected response type
570
+ * @param message - Message to send (must be JSON-serializable)
571
+ * @returns Promise resolving to the server's response
572
+ * @throws Error if not connected or communication fails
573
+ */
574
+ send<T>(message: unknown): Promise<T>;
575
+ /**
576
+ * Close the connection to the server.
577
+ *
578
+ * Can be called multiple times safely.
579
+ */
580
+ close(): void;
581
+ }
582
+ /**
583
+ * Create an IPC server listening on a Unix socket.
584
+ *
585
+ * @param socketPath - Path to the Unix socket file
586
+ * @returns IpcServer instance
587
+ *
588
+ * @example
589
+ * ```typescript
590
+ * const server = createIpcServer("/var/run/my-daemon.sock");
591
+ *
592
+ * server.onMessage(async (msg) => {
593
+ * return { echo: msg };
594
+ * });
595
+ *
596
+ * await server.listen();
597
+ * // Server is now accepting connections
598
+ * ```
599
+ */
600
+ declare function createIpcServer(socketPath: string): IpcServer;
601
+ /**
602
+ * Create an IPC client for connecting to a server.
603
+ *
604
+ * @param socketPath - Path to the Unix socket file
605
+ * @returns IpcClient instance
606
+ *
607
+ * @example
608
+ * ```typescript
609
+ * const client = createIpcClient("/var/run/my-daemon.sock");
610
+ *
611
+ * await client.connect();
612
+ * const response = await client.send({ command: "status" });
613
+ * console.log(response);
614
+ * client.close();
615
+ * ```
616
+ */
617
+ declare function createIpcClient(socketPath: string): IpcClient;
618
+ import { Result as Result3 } from "@outfitter/contracts";
619
+ /**
620
+ * A single health check definition.
621
+ *
622
+ * Health checks are used to verify that a service or resource is functioning
623
+ * correctly. Each check has a name for identification and a check function
624
+ * that returns a Result indicating success or failure.
625
+ *
626
+ * @example
627
+ * ```typescript
628
+ * const databaseCheck: HealthCheck = {
629
+ * name: "database",
630
+ * check: async () => {
631
+ * try {
632
+ * await db.ping();
633
+ * return Result.ok(undefined);
634
+ * } catch (error) {
635
+ * return Result.err(error);
636
+ * }
637
+ * },
638
+ * };
639
+ * ```
640
+ */
641
+ interface HealthCheck {
642
+ /**
643
+ * Unique name identifying this health check.
644
+ * Used as the key in the HealthStatus.checks record.
645
+ */
646
+ name: string;
647
+ /**
648
+ * Function that performs the health check.
649
+ *
650
+ * Should return Result.ok(undefined) if healthy, or Result.err(error)
651
+ * with details about the failure.
652
+ */
653
+ check(): Promise<Result3<void, Error>>;
654
+ }
655
+ /**
656
+ * Result of an individual health check.
657
+ *
658
+ * Contains the healthy/unhealthy status and an optional message
659
+ * providing more details (typically the error message on failure).
660
+ */
661
+ interface HealthCheckResult {
662
+ /** Whether this check passed (true) or failed (false) */
663
+ healthy: boolean;
664
+ /** Optional message, typically the error message on failure */
665
+ message?: string;
666
+ }
667
+ /**
668
+ * Aggregated health status from all registered checks.
669
+ *
670
+ * The overall healthy status is true only if ALL individual checks pass.
671
+ * Includes the uptime of the health checker in seconds.
672
+ *
673
+ * @example
674
+ * ```typescript
675
+ * const status: HealthStatus = {
676
+ * healthy: false,
677
+ * checks: {
678
+ * database: { healthy: true },
679
+ * cache: { healthy: false, message: "Connection refused" },
680
+ * },
681
+ * uptime: 3600,
682
+ * };
683
+ * ```
684
+ */
685
+ interface HealthStatus {
686
+ /** Overall health status - true only if ALL checks pass */
687
+ healthy: boolean;
688
+ /** Individual check results keyed by check name */
689
+ checks: Record<string, HealthCheckResult>;
690
+ /** Uptime in seconds since the health checker was created */
691
+ uptime: number;
692
+ }
693
+ /**
694
+ * Health checker interface for managing and running health checks.
695
+ *
696
+ * Provides methods to run all registered checks and get the aggregated
697
+ * health status, as well as dynamically registering new checks at runtime.
698
+ *
699
+ * @example
700
+ * ```typescript
701
+ * const checker = createHealthChecker([
702
+ * { name: "db", check: checkDatabase },
703
+ * { name: "cache", check: checkCache },
704
+ * ]);
705
+ *
706
+ * // Later, add more checks
707
+ * checker.register({ name: "queue", check: checkQueue });
708
+ *
709
+ * // Get health status
710
+ * const status = await checker.check();
711
+ * console.log("Healthy:", status.healthy);
712
+ * ```
713
+ */
714
+ interface HealthChecker {
715
+ /**
716
+ * Run all registered health checks and return aggregated status.
717
+ *
718
+ * Checks are run in parallel for efficiency. The overall healthy
719
+ * status is true only if all individual checks pass.
720
+ *
721
+ * @returns Aggregated health status
722
+ */
723
+ check(): Promise<HealthStatus>;
724
+ /**
725
+ * Register a new health check at runtime.
726
+ *
727
+ * The check will be included in all subsequent calls to check().
728
+ *
729
+ * @param check - Health check to register
730
+ */
731
+ register(check: HealthCheck): void;
732
+ }
733
+ /**
734
+ * Create a health checker with initial checks.
735
+ *
736
+ * The health checker runs all registered checks in parallel and aggregates
737
+ * their results. Individual check failures are isolated and don't prevent
738
+ * other checks from running.
739
+ *
740
+ * @param checks - Initial health checks to register
741
+ * @returns HealthChecker instance
742
+ *
743
+ * @example
744
+ * ```typescript
745
+ * const checker = createHealthChecker([
746
+ * {
747
+ * name: "database",
748
+ * check: async () => {
749
+ * await db.ping();
750
+ * return Result.ok(undefined);
751
+ * },
752
+ * },
753
+ * {
754
+ * name: "cache",
755
+ * check: async () => {
756
+ * await redis.ping();
757
+ * return Result.ok(undefined);
758
+ * },
759
+ * },
760
+ * ]);
761
+ *
762
+ * // Run health checks
763
+ * const status = await checker.check();
764
+ *
765
+ * if (!status.healthy) {
766
+ * console.error("Service unhealthy:", status.checks);
767
+ * }
768
+ * ```
769
+ */
770
+ declare function createHealthChecker(checks: HealthCheck[]): HealthChecker;
771
+ export { releaseDaemonLock, readLockPid, isUnixPlatform, isProcessAlive, isDaemonAlive, getSocketPath, getPidPath, getLockPath, getDaemonDir, createIpcServer, createIpcClient, createHealthChecker, createDaemon, acquireDaemonLock, StaleSocketError, ShutdownHandler, ProtocolError, LockHandle, LockError, IpcServer, IpcMessageHandler, IpcClient, HealthStatus, HealthChecker, HealthCheckResult, HealthCheck, DaemonState, DaemonOptions, DaemonErrorCode, DaemonError, DaemonConnectionError, Daemon, ConnectionTimeoutError, ConnectionRefusedError };