@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.
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +15 -0
- package/dist/health.d.ts +2 -0
- package/dist/health.js +7 -0
- package/dist/ipc.d.ts +2 -0
- package/dist/ipc.js +9 -0
- package/dist/lifecycle.d.ts +3 -0
- package/dist/lifecycle.js +8 -0
- package/dist/locking.d.ts +3 -0
- package/dist/locking.js +16 -0
- package/dist/platform.d.ts +2 -0
- package/dist/platform.js +15 -0
- package/dist/shared/@outfitter/daemon-3j14csts.js +180 -0
- package/dist/shared/@outfitter/daemon-3qezw7hc.d.ts +154 -0
- package/dist/shared/@outfitter/daemon-5as2p88e.d.ts +69 -0
- package/dist/shared/@outfitter/daemon-6efpehdg.d.ts +133 -0
- package/dist/shared/@outfitter/daemon-8tpctdgw.d.ts +127 -0
- package/dist/shared/@outfitter/daemon-906e24jc.js +106 -0
- package/dist/shared/@outfitter/daemon-9w2ey87r.d.ts +161 -0
- package/dist/shared/@outfitter/daemon-bd6kcdnj.d.ts +32 -0
- package/dist/shared/@outfitter/daemon-c1zbfqq5.js +50 -0
- package/dist/shared/@outfitter/daemon-cy5wntm2.js +207 -0
- package/dist/shared/@outfitter/daemon-dzt3fqvp.js +9 -0
- package/dist/shared/@outfitter/daemon-h536nv4k.d.ts +103 -0
- package/dist/shared/@outfitter/daemon-qqn2jpsg.js +25 -0
- package/dist/shared/@outfitter/daemon-wz4peqjh.js +48 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +7 -0
- package/package.json +47 -5
|
@@ -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 };
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
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.
|
|
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
|
-
"./
|
|
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.
|
|
31
|
-
"@outfitter/file-ops": "0.2.
|
|
32
|
-
"@outfitter/logging": "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",
|