pgserve 2.0.1 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgserve",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Embedded PostgreSQL server with true concurrent connections - zero config, auto-provision databases",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/daemon.js CHANGED
@@ -251,7 +251,7 @@ export class PgserveDaemon extends EventEmitter {
251
251
 
252
252
  this.pgManager = options.pgManager || new PostgresManager({
253
253
  dataDir: this.baseDir,
254
- port: options.pgPort || 5433,
254
+ port: options.pgPort ?? 0,
255
255
  logger: this.logger.child ? this.logger.child({ component: 'postgres' }) : this.logger,
256
256
  useRam: this.useRam,
257
257
  enablePgvector: options.enablePgvector || false,
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * 1. SO_PEERCRED (Linux) / getpeereid + LOCAL_PEERPID (macOS)
8
8
  * → kernel-attested {pid, uid, gid}
9
- * 2. /proc/$pid/cwd peer's current working directory (Linux only)
9
+ * 2. peer cwd lookup → /proc/$pid/cwd on Linux, lsof on macOS
10
10
  * 3. walk upward to the nearest package.json
11
11
  * 4. if found: fingerprint = sha256(realpath \0 name \0 uid)[:12] mode='package'
12
12
  * else: fingerprint = sha256(uid \0 cwd \0 cmdline[1])[:12] mode='script'
@@ -29,6 +29,7 @@
29
29
  */
30
30
 
31
31
  import crypto from 'crypto';
32
+ import { execFileSync } from 'child_process';
32
33
  import fs from 'fs';
33
34
  import path from 'path';
34
35
  import { audit, AUDIT_EVENTS } from './audit.js';
@@ -175,17 +176,21 @@ function makeDarwinReader(symbols, ptr) {
175
176
  }
176
177
 
177
178
  // ---------------------------------------------------------------------------
178
- // /proc reads Linux-only; macOS daemon support is best-effort
179
+ // Peer process metadata reads
179
180
  // ---------------------------------------------------------------------------
180
181
 
181
182
  /**
182
- * Resolve the cwd of a peer process via /proc/$pid/cwd. Linux-only.
183
- * Returns null if the symlink cannot be read (process gone, EACCES, etc).
183
+ * Resolve the cwd of a peer process. Linux uses /proc/$pid/cwd; macOS has no
184
+ * /proc, so it shells out to the platform lsof binary and parses the cwd row.
185
+ * Returns null if the process disappeared, permissions deny the lookup, or the
186
+ * host does not expose a cwd for the peer.
184
187
  *
185
188
  * @param {number} pid
186
189
  * @returns {string | null}
187
190
  */
188
191
  export function readProcCwd(pid) {
192
+ if (!Number.isInteger(pid) || pid <= 0) return null;
193
+ if (process.platform === 'darwin') return readDarwinCwd(pid);
189
194
  if (process.platform !== 'linux') return null;
190
195
  try {
191
196
  return fs.readlinkSync(`/proc/${pid}/cwd`);
@@ -194,6 +199,27 @@ export function readProcCwd(pid) {
194
199
  }
195
200
  }
196
201
 
202
+ function readDarwinCwd(pid) {
203
+ const lsof = process.env.PGSERVE_LSOF_BIN || '/usr/sbin/lsof';
204
+ try {
205
+ const output = execFileSync(lsof, ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], {
206
+ encoding: 'utf8',
207
+ timeout: 1000,
208
+ stdio: ['ignore', 'pipe', 'ignore'],
209
+ });
210
+ return parseDarwinLsofCwd(output);
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+
216
+ export function parseDarwinLsofCwd(output) {
217
+ for (const line of String(output || '').split(/\r?\n/)) {
218
+ if (line.startsWith('n') && line.length > 1) return line.slice(1);
219
+ }
220
+ return null;
221
+ }
222
+
197
223
  /**
198
224
  * Read the peer's argv via /proc/$pid/cmdline (NUL-separated).
199
225
  * argv[0] is the exe; argv[1] is typically the script.
package/src/postgres.js CHANGED
@@ -406,10 +406,23 @@ export function pgvectorMetaMatches(meta, runtime) {
406
406
  return true;
407
407
  }
408
408
 
409
+ function findAvailableTcpPort() {
410
+ const server = Bun.listen({
411
+ hostname: '127.0.0.1',
412
+ port: 0,
413
+ socket: {
414
+ data() {},
415
+ },
416
+ });
417
+ const port = server.port;
418
+ server.stop(true);
419
+ return port;
420
+ }
421
+
409
422
  export class PostgresManager {
410
423
  constructor(options = {}) {
411
424
  this.dataDir = options.dataDir || null; // null = memory mode (temp dir)
412
- this.port = options.port || 5433; // Internal PG port (router listens on different port)
425
+ this.port = options.port ?? 5433; // Internal PG port (router listens on different port)
413
426
  this.user = options.user || 'postgres';
414
427
  this.password = options.password || 'postgres';
415
428
  this.logger = options.logger;
@@ -465,6 +478,10 @@ export class PostgresManager {
465
478
  await fs.promises.chmod(this.binaries.initdb, '755');
466
479
  await fs.promises.chmod(this.binaries.postgres, '755');
467
480
 
481
+ if (this.port === 0) {
482
+ this.port = findAvailableTcpPort();
483
+ }
484
+
468
485
  // Determine data directory
469
486
  if (this.persistent) {
470
487
  this.databaseDir = this.dataDir;
@@ -568,7 +585,8 @@ export class PostgresManager {
568
585
  const initdbCmd = [
569
586
  this.binaries.initdb,
570
587
  `--pgdata=${this.databaseDir}`,
571
- '--auth=password',
588
+ '--auth-local=trust',
589
+ '--auth-host=password',
572
590
  `--username=${this.user}`,
573
591
  `--pwfile=${passwordFile}`,
574
592
  ];
@@ -744,11 +762,13 @@ export class PostgresManager {
744
762
  // Whichever succeeds first wins
745
763
 
746
764
  const markReady = (method) => {
747
- if (!started) {
748
- started = true;
749
- this.logger.info({ port: this.port, method }, 'PostgreSQL ready');
750
- resolve();
751
- }
765
+ if (started || processExited) return true;
766
+ const socketPath = this.getSocketPath();
767
+ if (socketPath && !fs.existsSync(socketPath)) return false;
768
+ started = true;
769
+ this.logger.info({ port: this.port, method }, 'PostgreSQL ready');
770
+ resolve();
771
+ return true;
752
772
  };
753
773
 
754
774
  // Read stderr - detect port binding in logs (locale-independent: just look for port number)
@@ -873,14 +893,19 @@ export class PostgresManager {
873
893
  if (processExited) return;
874
894
 
875
895
  try {
896
+ const socketPath = this.getSocketPath();
897
+ if (socketPath && fs.existsSync(socketPath)) {
898
+ markReady('unix-socket');
899
+ return;
900
+ }
876
901
  await tryConnect();
877
902
  // On Windows, TCP port opens before PostgreSQL is fully ready for protocol handshakes
878
903
  // Add delay to let PostgreSQL complete its startup sequence
879
904
  if (isWindows) {
880
905
  await Bun.sleep(2000); // 2 second delay for Windows
881
906
  }
882
- markReady('tcp');
883
- return;
907
+ if (processExited) return;
908
+ if (markReady('tcp')) return;
884
909
  } catch {
885
910
  await Bun.sleep(200);
886
911
  }
@@ -20,6 +20,8 @@ import {
20
20
  initFingerprintFfi,
21
21
  getPeerCred,
22
22
  findNearestPackageJson,
23
+ parseDarwinLsofCwd,
24
+ readProcCwd,
23
25
  readPackageName,
24
26
  derivePackageFingerprint,
25
27
  deriveScriptFingerprint,
@@ -80,6 +82,18 @@ test('getPeerCred reads kernel-attested pid/uid/gid via Unix socket pair', async
80
82
  expect(cred.gid).toBe(expectedGid);
81
83
  });
82
84
 
85
+ test('macOS lsof parser extracts cwd field output', () => {
86
+ const cwd = path.join(scratch, 'project');
87
+ const output = `p12345\nn${cwd}\n`;
88
+ expect(parseDarwinLsofCwd(output)).toBe(cwd);
89
+ expect(parseDarwinLsofCwd('p12345\n')).toBeNull();
90
+ });
91
+
92
+ test('readProcCwd resolves the current process cwd on supported platforms', () => {
93
+ if (process.platform !== 'linux' && process.platform !== 'darwin') return;
94
+ expect(readProcCwd(process.pid)).toBe(process.cwd());
95
+ });
96
+
83
97
  // ---------------------------------------------------------------------------
84
98
  // Pure-function tests on derivation surface
85
99
  // ---------------------------------------------------------------------------