swarm-mail 1.1.1 → 1.2.0
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/daemon.d.ts +28 -5
- package/dist/daemon.d.ts.map +1 -1
- package/dist/index.js +59 -12
- package/dist/pglite.d.ts +2 -2
- package/dist/pglite.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/daemon.d.ts
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* import { startDaemon, stopDaemon, isDaemonRunning, healthCheck } from 'swarm-mail/daemon';
|
|
10
10
|
*
|
|
11
11
|
* // Start daemon
|
|
12
|
-
* const { pid, port } = await startDaemon({ port:
|
|
12
|
+
* const { pid, port } = await startDaemon({ port: 15433 });
|
|
13
13
|
*
|
|
14
14
|
* // Check health
|
|
15
|
-
* const healthy = await healthCheck({ port:
|
|
15
|
+
* const healthy = await healthCheck({ port: 15433 });
|
|
16
16
|
*
|
|
17
17
|
* // Stop daemon
|
|
18
18
|
* await stopDaemon('/path/to/project');
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* Daemon start options
|
|
23
23
|
*/
|
|
24
24
|
export interface DaemonOptions {
|
|
25
|
-
/** TCP port to bind (default:
|
|
25
|
+
/** TCP port to bind (default: 15433) */
|
|
26
26
|
port?: number;
|
|
27
27
|
/** Host to bind (default: 127.0.0.1) */
|
|
28
28
|
host?: string;
|
|
@@ -55,6 +55,29 @@ export interface DaemonInfo {
|
|
|
55
55
|
* @returns Absolute path to PID file
|
|
56
56
|
*/
|
|
57
57
|
export declare function getPidFilePath(projectPath?: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Read PID from PID file
|
|
60
|
+
*
|
|
61
|
+
* @param projectPath - Optional project root path
|
|
62
|
+
* @returns Process ID, or null if file doesn't exist or is invalid
|
|
63
|
+
*/
|
|
64
|
+
export declare function readPidFile(projectPath?: string): Promise<number | null>;
|
|
65
|
+
/**
|
|
66
|
+
* Clean up stale PID file
|
|
67
|
+
*
|
|
68
|
+
* Removes PID file if it points to a dead process or doesn't exist.
|
|
69
|
+
* Used for self-healing when daemon startup fails due to stale state.
|
|
70
|
+
*
|
|
71
|
+
* @param projectPath - Optional project root path
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // Clean up before starting daemon
|
|
76
|
+
* await cleanupPidFile('/path/to/project');
|
|
77
|
+
* await startDaemon({ port: 15433, projectPath: '/path/to/project' });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare function cleanupPidFile(projectPath?: string): Promise<void>;
|
|
58
81
|
/**
|
|
59
82
|
* Check if daemon is running
|
|
60
83
|
*
|
|
@@ -104,7 +127,7 @@ export declare function healthCheck(options: Pick<DaemonOptions, "port" | "host"
|
|
|
104
127
|
* @example
|
|
105
128
|
* ```typescript
|
|
106
129
|
* // Start with TCP port
|
|
107
|
-
* const { pid, port } = await startDaemon({ port:
|
|
130
|
+
* const { pid, port } = await startDaemon({ port: 15433 });
|
|
108
131
|
*
|
|
109
132
|
* // Start with Unix socket
|
|
110
133
|
* const { pid, socketPath } = await startDaemon({
|
|
@@ -113,7 +136,7 @@ export declare function healthCheck(options: Pick<DaemonOptions, "port" | "host"
|
|
|
113
136
|
*
|
|
114
137
|
* // Start with custom database path
|
|
115
138
|
* const { pid, port } = await startDaemon({
|
|
116
|
-
* port:
|
|
139
|
+
* port: 15433,
|
|
117
140
|
* dbPath: '/custom/path/to/db'
|
|
118
141
|
* });
|
|
119
142
|
* ```
|
package/dist/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoCH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoCH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB3D;AA4BD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe9E;AA2BD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxE;AA2BD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAM5E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,GACrD,OAAO,CAAC,OAAO,CAAC,CA2BlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAsB,WAAW,CAC/B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,UAAU,CAAC,CAsFrB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDpE"}
|
package/dist/index.js
CHANGED
|
@@ -19004,6 +19004,12 @@ async function deletePidFile(projectPath) {
|
|
|
19004
19004
|
await unlink(pidFilePath);
|
|
19005
19005
|
} catch {}
|
|
19006
19006
|
}
|
|
19007
|
+
async function cleanupPidFile(projectPath) {
|
|
19008
|
+
const pid = await readPidFile(projectPath);
|
|
19009
|
+
if (!pid || !isProcessAlive(pid)) {
|
|
19010
|
+
await deletePidFile(projectPath);
|
|
19011
|
+
}
|
|
19012
|
+
}
|
|
19007
19013
|
async function waitFor(condition, timeoutMs, intervalMs = 100) {
|
|
19008
19014
|
const start = Date.now();
|
|
19009
19015
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -19026,7 +19032,7 @@ async function healthCheck(options) {
|
|
|
19026
19032
|
const postgres = await import("postgres").then((m) => m.default);
|
|
19027
19033
|
const sql = options.path ? postgres({ path: options.path }) : postgres({
|
|
19028
19034
|
host: options.host || "127.0.0.1",
|
|
19029
|
-
port: options.port ||
|
|
19035
|
+
port: options.port || 15433,
|
|
19030
19036
|
max: 1
|
|
19031
19037
|
});
|
|
19032
19038
|
try {
|
|
@@ -19043,7 +19049,7 @@ async function healthCheck(options) {
|
|
|
19043
19049
|
}
|
|
19044
19050
|
}
|
|
19045
19051
|
async function startDaemon(options = {}) {
|
|
19046
|
-
const { port =
|
|
19052
|
+
const { port = 15433, host = "127.0.0.1", path: path2, dbPath, projectPath } = options;
|
|
19047
19053
|
if (activeServer && activeProjectPath === projectPath) {
|
|
19048
19054
|
return {
|
|
19049
19055
|
pid: process.pid,
|
|
@@ -19062,6 +19068,15 @@ async function startDaemon(options = {}) {
|
|
|
19062
19068
|
socketPath: path2
|
|
19063
19069
|
};
|
|
19064
19070
|
}
|
|
19071
|
+
const healthOptions = path2 ? { path: path2 } : { port, host };
|
|
19072
|
+
if (await healthCheck(healthOptions)) {
|
|
19073
|
+
console.log(`[daemon] Port/socket already in use and healthy, assuming external daemon`);
|
|
19074
|
+
return {
|
|
19075
|
+
pid: process.pid,
|
|
19076
|
+
port: path2 ? undefined : port,
|
|
19077
|
+
socketPath: path2
|
|
19078
|
+
};
|
|
19079
|
+
}
|
|
19065
19080
|
const finalDbPath = dbPath || getDatabasePath2(projectPath);
|
|
19066
19081
|
const db = await PGlite2.create({
|
|
19067
19082
|
dataDir: finalDbPath,
|
|
@@ -19073,7 +19088,6 @@ async function startDaemon(options = {}) {
|
|
|
19073
19088
|
activeDb = db;
|
|
19074
19089
|
activeProjectPath = projectPath;
|
|
19075
19090
|
await writePidFile(process.pid, projectPath);
|
|
19076
|
-
const healthOptions = path2 ? { path: path2 } : { port, host };
|
|
19077
19091
|
const ready = await waitFor(() => healthCheck(healthOptions), 1e4);
|
|
19078
19092
|
if (!ready) {
|
|
19079
19093
|
await server.stop();
|
|
@@ -19357,17 +19371,50 @@ async function getSwarmMailSocketInternal(projectPath) {
|
|
|
19357
19371
|
const projectKey = projectPath || getDatabasePath2(projectPath);
|
|
19358
19372
|
const dbPath = getDatabasePath2(projectPath);
|
|
19359
19373
|
const socketPath = process.env.SWARM_MAIL_SOCKET_PATH;
|
|
19360
|
-
const port = process.env.SWARM_MAIL_SOCKET_PORT ? Number.parseInt(process.env.SWARM_MAIL_SOCKET_PORT, 10) :
|
|
19374
|
+
const port = process.env.SWARM_MAIL_SOCKET_PORT ? Number.parseInt(process.env.SWARM_MAIL_SOCKET_PORT, 10) : 15433;
|
|
19361
19375
|
const host = process.env.SWARM_MAIL_SOCKET_HOST || "127.0.0.1";
|
|
19362
|
-
const running = await isDaemonRunning(projectPath);
|
|
19363
|
-
if (!running) {
|
|
19364
|
-
const daemonOptions = socketPath ? { path: socketPath, dbPath, projectPath } : { port, host, dbPath, projectPath };
|
|
19365
|
-
await startDaemon(daemonOptions);
|
|
19366
|
-
}
|
|
19367
19376
|
const healthOptions = socketPath ? { path: socketPath } : { port, host };
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19377
|
+
if (await healthCheck(healthOptions)) {
|
|
19378
|
+
console.log("[swarm-mail] Daemon already healthy, connecting...");
|
|
19379
|
+
const adapterOptions2 = socketPath ? { path: socketPath } : { host, port };
|
|
19380
|
+
const db2 = await createSocketAdapter(adapterOptions2);
|
|
19381
|
+
const adapter2 = createSwarmMailAdapter(db2, projectKey);
|
|
19382
|
+
await adapter2.runMigrations();
|
|
19383
|
+
return adapter2;
|
|
19384
|
+
}
|
|
19385
|
+
const pid = await readPidFile(projectPath);
|
|
19386
|
+
if (pid) {
|
|
19387
|
+
try {
|
|
19388
|
+
process.kill(pid, 0);
|
|
19389
|
+
} catch (e) {
|
|
19390
|
+
const err = e;
|
|
19391
|
+
if (err.code === "ESRCH") {
|
|
19392
|
+
console.log(`[swarm-mail] Cleaning up stale PID file (dead process ${pid})`);
|
|
19393
|
+
await cleanupPidFile(projectPath);
|
|
19394
|
+
}
|
|
19395
|
+
}
|
|
19396
|
+
}
|
|
19397
|
+
const daemonOptions = socketPath ? { path: socketPath, dbPath, projectPath } : { port, host, dbPath, projectPath };
|
|
19398
|
+
for (let attempt = 0;attempt < 3; attempt++) {
|
|
19399
|
+
try {
|
|
19400
|
+
await startDaemon(daemonOptions);
|
|
19401
|
+
break;
|
|
19402
|
+
} catch (error45) {
|
|
19403
|
+
const err = error45;
|
|
19404
|
+
if (err.message?.includes("EADDRINUSE") || err.message?.includes("Failed to listen") || err.code === "EADDRINUSE") {
|
|
19405
|
+
console.log(`[swarm-mail] Port busy on attempt ${attempt + 1}, checking if daemon is healthy...`);
|
|
19406
|
+
await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
|
|
19407
|
+
if (await healthCheck(healthOptions)) {
|
|
19408
|
+
console.log("[swarm-mail] Daemon started by another process, connecting...");
|
|
19409
|
+
break;
|
|
19410
|
+
}
|
|
19411
|
+
} else {
|
|
19412
|
+
throw error45;
|
|
19413
|
+
}
|
|
19414
|
+
}
|
|
19415
|
+
}
|
|
19416
|
+
if (!await healthCheck(healthOptions)) {
|
|
19417
|
+
throw new Error(`Failed to start or connect to daemon after retries (${socketPath ? `socket: ${socketPath}` : `TCP: ${host}:${port}`})`);
|
|
19371
19418
|
}
|
|
19372
19419
|
const adapterOptions = socketPath ? { path: socketPath } : { host, port };
|
|
19373
19420
|
const db = await createSocketAdapter(adapterOptions);
|
package/dist/pglite.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ export declare function getSwarmMail(projectPath?: string): Promise<SwarmMailAda
|
|
|
109
109
|
*
|
|
110
110
|
* **Port/Path Resolution:**
|
|
111
111
|
* - Checks SWARM_MAIL_SOCKET_PATH env var for Unix socket
|
|
112
|
-
* - Falls back to TCP on SWARM_MAIL_SOCKET_PORT (default:
|
|
112
|
+
* - Falls back to TCP on SWARM_MAIL_SOCKET_PORT (default: 15433)
|
|
113
113
|
* - Host defaults to 127.0.0.1
|
|
114
114
|
*
|
|
115
115
|
* @param projectPath - Optional project root path
|
|
@@ -123,7 +123,7 @@ export declare function getSwarmMail(projectPath?: string): Promise<SwarmMailAda
|
|
|
123
123
|
* const swarmMail = await getSwarmMailSocket('/path/to/project');
|
|
124
124
|
*
|
|
125
125
|
* // TCP socket
|
|
126
|
-
* process.env.SWARM_MAIL_SOCKET_PORT = '
|
|
126
|
+
* process.env.SWARM_MAIL_SOCKET_PORT = '15433';
|
|
127
127
|
* const swarmMail = await getSwarmMailSocket('/path/to/project');
|
|
128
128
|
* ```
|
|
129
129
|
*/
|
package/dist/pglite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pglite.d.ts","sourceRoot":"","sources":["../src/pglite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAS9C,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAqDjE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAkC1D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAQjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB5D;AA0HD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,YAAY,CAChC,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAqD3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAS3B;
|
|
1
|
+
{"version":3,"file":"pglite.d.ts","sourceRoot":"","sources":["../src/pglite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAS9C,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAqDjE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAkC1D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAQjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB5D;AA0HD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,YAAY,CAChC,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAqD3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAS3B;AAuGD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,uBAAuB,CAC3C,UAAU,SAAS,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAQ3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYxE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUvD;AAGD,OAAO,EAAE,MAAM,EAAE,CAAC"}
|