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 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: 5433 });
12
+ * const { pid, port } = await startDaemon({ port: 15433 });
13
13
  *
14
14
  * // Check health
15
- * const healthy = await healthCheck({ port: 5433 });
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: 5433) */
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: 5433 });
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: 5433,
139
+ * port: 15433,
117
140
  * dbPath: '/custom/path/to/db'
118
141
  * });
119
142
  * ```
@@ -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,uCAAuC;IACvC,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;AAqGD;;;;;;;;;;;;;;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,CAyErB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmDpE"}
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 || 5433,
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 = 5433, host = "127.0.0.1", path: path2, dbPath, projectPath } = options;
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) : 5433;
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
- const healthy = await healthCheck(healthOptions);
19369
- if (!healthy) {
19370
- throw new Error(`Daemon health check failed after startup (${socketPath ? `socket: ${socketPath}` : `TCP: ${host}:${port}`})`);
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: 5433)
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 = '5433';
126
+ * process.env.SWARM_MAIL_SOCKET_PORT = '15433';
127
127
  * const swarmMail = await getSwarmMailSocket('/path/to/project');
128
128
  * ```
129
129
  */
@@ -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;AA0DD;;;;;;;;;;;;;;;;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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swarm-mail",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Event sourcing primitives for multi-agent coordination. Local-first, no external servers.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",