@outfitter/daemon 0.2.1 → 0.2.2

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,103 @@
1
+ import { LockError } from "./daemon-8tpctdgw";
2
+ import { Result } from "@outfitter/contracts";
3
+ /**
4
+ * Handle returned when a lock is successfully acquired.
5
+ *
6
+ * Must be passed to `releaseDaemonLock` to properly release the lock.
7
+ */
8
+ interface LockHandle {
9
+ /** Path to the lock file */
10
+ readonly lockPath: string;
11
+ /** PID that owns the lock */
12
+ readonly pid: number;
13
+ }
14
+ /**
15
+ * Check if a process with the given PID is alive.
16
+ *
17
+ * Uses `process.kill(pid, 0)` which sends no signal but checks
18
+ * if the process exists and is accessible.
19
+ *
20
+ * @param pid - Process ID to check
21
+ * @returns true if process exists and is accessible, false otherwise
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * if (isProcessAlive(12345)) {
26
+ * console.log("Process is still running");
27
+ * } else {
28
+ * console.log("Process has exited");
29
+ * }
30
+ * ```
31
+ */
32
+ declare function isProcessAlive(pid: number): boolean;
33
+ /**
34
+ * Check if a daemon is alive by reading its lock file.
35
+ *
36
+ * @param lockPath - Path to the daemon lock file
37
+ * @returns true if lock file exists and process is alive, false otherwise
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * const alive = await isDaemonAlive("/run/user/1000/waymark/daemon.lock");
42
+ * if (!alive) {
43
+ * // Safe to start a new daemon
44
+ * }
45
+ * ```
46
+ */
47
+ declare function isDaemonAlive(lockPath: string): Promise<boolean>;
48
+ /**
49
+ * Acquire an exclusive lock for a daemon.
50
+ *
51
+ * Uses a PID file approach compatible with Bun:
52
+ * 1. Check if lock file exists with a valid PID
53
+ * 2. If PID is alive, refuse (daemon already running)
54
+ * 3. If PID is stale or no lock, write our PID atomically
55
+ *
56
+ * @param lockPath - Path to create the lock file
57
+ * @returns Result with LockHandle on success, LockError on failure
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const result = await acquireDaemonLock("/run/user/1000/waymark/daemon.lock");
62
+ *
63
+ * if (result.isOk()) {
64
+ * console.log(`Lock acquired for PID ${result.value.pid}`);
65
+ * // ... run daemon ...
66
+ * await releaseDaemonLock(result.value);
67
+ * } else {
68
+ * console.error(`Failed to acquire lock: ${result.error.message}`);
69
+ * }
70
+ * ```
71
+ */
72
+ declare function acquireDaemonLock(lockPath: string): Promise<Result<LockHandle, LockError>>;
73
+ /**
74
+ * Release a daemon lock.
75
+ *
76
+ * Removes the lock file only if the PID inside matches the handle.
77
+ * This prevents accidentally removing a lock acquired by another process.
78
+ *
79
+ * @param handle - Lock handle returned from acquireDaemonLock
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const result = await acquireDaemonLock(lockPath);
84
+ * if (result.isOk()) {
85
+ * try {
86
+ * // ... run daemon ...
87
+ * } finally {
88
+ * await releaseDaemonLock(result.value);
89
+ * }
90
+ * }
91
+ * ```
92
+ */
93
+ declare function releaseDaemonLock(handle: LockHandle): Promise<void>;
94
+ /**
95
+ * Read the PID from a lock file.
96
+ *
97
+ * @param lockPath - Path to the lock file
98
+ * @returns The PID if valid, undefined otherwise
99
+ *
100
+ * @internal
101
+ */
102
+ declare function readLockPid(lockPath: string): Promise<number | undefined>;
103
+ export { LockHandle, isProcessAlive, isDaemonAlive, acquireDaemonLock, releaseDaemonLock, readLockPid };
@@ -0,0 +1,25 @@
1
+ // @bun
2
+ // packages/daemon/src/errors.ts
3
+ import { TaggedError } from "@outfitter/contracts";
4
+ var StaleSocketErrorBase = TaggedError("StaleSocketError")();
5
+ var ConnectionRefusedErrorBase = TaggedError("ConnectionRefusedError")();
6
+ var ConnectionTimeoutErrorBase = TaggedError("ConnectionTimeoutError")();
7
+ var ProtocolErrorBase = TaggedError("ProtocolError")();
8
+ var LockErrorBase = TaggedError("LockError")();
9
+
10
+ class StaleSocketError extends StaleSocketErrorBase {
11
+ }
12
+
13
+ class ConnectionRefusedError extends ConnectionRefusedErrorBase {
14
+ }
15
+
16
+ class ConnectionTimeoutError extends ConnectionTimeoutErrorBase {
17
+ }
18
+
19
+ class ProtocolError extends ProtocolErrorBase {
20
+ }
21
+
22
+ class LockError extends LockErrorBase {
23
+ }
24
+
25
+ export { StaleSocketError, ConnectionRefusedError, ConnectionTimeoutError, ProtocolError, LockError };
@@ -0,0 +1,48 @@
1
+ // @bun
2
+ // packages/daemon/src/platform.ts
3
+ import { platform as osPlatform, tmpdir, userInfo } from "os";
4
+ import { join } from "path";
5
+ function isUnixPlatform() {
6
+ const plat = osPlatform();
7
+ return plat === "darwin" || plat === "linux";
8
+ }
9
+ function getRuntimeDir() {
10
+ const xdgRuntime = process.env["XDG_RUNTIME_DIR"];
11
+ if (xdgRuntime) {
12
+ return xdgRuntime;
13
+ }
14
+ const plat = osPlatform();
15
+ if (plat === "darwin") {
16
+ return process.env["TMPDIR"] ?? tmpdir();
17
+ }
18
+ if (plat === "linux") {
19
+ return `/run/user/${userInfo().uid}`;
20
+ }
21
+ return process.env["TEMP"] ?? tmpdir();
22
+ }
23
+ function getSocketPath(toolName) {
24
+ if (!isUnixPlatform()) {
25
+ return `\\\\.\\pipe\\${toolName}-daemon`;
26
+ }
27
+ return join(getRuntimeDir(), toolName, "daemon.sock");
28
+ }
29
+ function getLockPath(toolName) {
30
+ if (!isUnixPlatform()) {
31
+ return join(tmpdir(), `${toolName}-daemon.lock`);
32
+ }
33
+ return join(getRuntimeDir(), toolName, "daemon.lock");
34
+ }
35
+ function getPidPath(toolName) {
36
+ if (!isUnixPlatform()) {
37
+ return join(tmpdir(), `${toolName}-daemon.pid`);
38
+ }
39
+ return join(getRuntimeDir(), toolName, "daemon.pid");
40
+ }
41
+ function getDaemonDir(toolName) {
42
+ if (!isUnixPlatform()) {
43
+ return tmpdir();
44
+ }
45
+ return join(getRuntimeDir(), toolName);
46
+ }
47
+
48
+ export { isUnixPlatform, getSocketPath, getLockPath, getPidPath, getDaemonDir };
@@ -0,0 +1,2 @@
1
+ import { Daemon, DaemonError, DaemonErrorCode, DaemonOptions, DaemonState, ShutdownHandler } from "./shared/@outfitter/daemon-9w2ey87r";
2
+ export { ShutdownHandler, DaemonState, DaemonOptions, DaemonErrorCode, DaemonError, Daemon };
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ import {
3
+ DaemonError
4
+ } from "./shared/@outfitter/daemon-dzt3fqvp.js";
5
+ export {
6
+ DaemonError
7
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@outfitter/daemon",
3
3
  "description": "Daemon lifecycle, IPC, and health checks for Outfitter",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
@@ -15,7 +15,49 @@
15
15
  "default": "./dist/index.js"
16
16
  }
17
17
  },
18
- "./package.json": "./package.json"
18
+ "./errors": {
19
+ "import": {
20
+ "types": "./dist/errors.d.ts",
21
+ "default": "./dist/errors.js"
22
+ }
23
+ },
24
+ "./health": {
25
+ "import": {
26
+ "types": "./dist/health.d.ts",
27
+ "default": "./dist/health.js"
28
+ }
29
+ },
30
+ "./ipc": {
31
+ "import": {
32
+ "types": "./dist/ipc.d.ts",
33
+ "default": "./dist/ipc.js"
34
+ }
35
+ },
36
+ "./lifecycle": {
37
+ "import": {
38
+ "types": "./dist/lifecycle.d.ts",
39
+ "default": "./dist/lifecycle.js"
40
+ }
41
+ },
42
+ "./locking": {
43
+ "import": {
44
+ "types": "./dist/locking.d.ts",
45
+ "default": "./dist/locking.js"
46
+ }
47
+ },
48
+ "./package.json": "./package.json",
49
+ "./platform": {
50
+ "import": {
51
+ "types": "./dist/platform.d.ts",
52
+ "default": "./dist/platform.js"
53
+ }
54
+ },
55
+ "./types": {
56
+ "import": {
57
+ "types": "./dist/types.d.ts",
58
+ "default": "./dist/types.js"
59
+ }
60
+ }
19
61
  },
20
62
  "sideEffects": false,
21
63
  "scripts": {
@@ -27,9 +69,9 @@
27
69
  "clean": "rm -rf dist"
28
70
  },
29
71
  "dependencies": {
30
- "@outfitter/contracts": "0.2.0",
31
- "@outfitter/file-ops": "0.2.0",
32
- "@outfitter/logging": "0.3.0"
72
+ "@outfitter/contracts": "0.3.0",
73
+ "@outfitter/file-ops": "0.2.1",
74
+ "@outfitter/logging": "0.4.0"
33
75
  },
34
76
  "devDependencies": {
35
77
  "@types/bun": "latest",