postgres-memory-server 0.2.0 → 0.2.1

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/cli.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import process2 from "process";
4
+ import process3 from "process";
5
5
 
6
6
  // src/PostgresMemoryServer.ts
7
- import { promises as fs2 } from "fs";
7
+ import { promises as fs2, rmSync } from "fs";
8
8
  import os2 from "os";
9
9
  import path2 from "path";
10
+ import process2 from "process";
10
11
  import EmbeddedPostgres from "embedded-postgres";
11
12
  import { Client } from "pg";
12
13
 
@@ -67,7 +68,8 @@ function getPgMajorVersion() {
67
68
  const content = readFileSync(candidate, "utf8");
68
69
  const pkg = JSON.parse(content);
69
70
  if (pkg.name === "embedded-postgres" && pkg.version) {
70
- return pkg.version.split(".")[0];
71
+ const major = pkg.version.split(".")[0];
72
+ if (major) return major;
71
73
  }
72
74
  } catch {
73
75
  }
@@ -213,7 +215,7 @@ function buildDownloadUrl(version, pgMajorVersion, platform, arch) {
213
215
  }
214
216
  function getMacOSCodename() {
215
217
  const release = os.release();
216
- const majorVersion = parseInt(release.split(".")[0], 10);
218
+ const majorVersion = parseInt(release.split(".")[0] ?? "0", 10);
217
219
  if (majorVersion >= 24) return "sequoia";
218
220
  if (majorVersion >= 23) return "sonoma";
219
221
  throw new Error(
@@ -378,7 +380,7 @@ async function installPgVectorExtension(nativeDir, pgMajorVersion) {
378
380
  function getHomebrewBottleTag(platform, arch) {
379
381
  if (platform === "darwin") {
380
382
  const release = os.release();
381
- const major = parseInt(release.split(".")[0], 10);
383
+ const major = parseInt(release.split(".")[0] ?? "0", 10);
382
384
  const prefix = arch === "arm64" ? "arm64_" : "";
383
385
  if (major >= 25) return `${prefix}tahoe`;
384
386
  if (major >= 24) return `${prefix}sequoia`;
@@ -390,9 +392,61 @@ function getHomebrewBottleTag(platform, arch) {
390
392
  }
391
393
  throw new Error(`No Homebrew bottles available for ${platform}-${arch}`);
392
394
  }
395
+ var ORPHAN_MIN_AGE_MS = 6e4;
396
+ async function sweepOrphanedDataDirs(minAgeMs = ORPHAN_MIN_AGE_MS) {
397
+ const tmpDir = os.tmpdir();
398
+ let entries;
399
+ try {
400
+ entries = await fs.readdir(tmpDir);
401
+ } catch {
402
+ return;
403
+ }
404
+ const cutoff = Date.now() - minAgeMs;
405
+ await Promise.all(
406
+ entries.filter((name) => name.startsWith("postgres-memory-server-")).map(async (name) => {
407
+ const fullPath = path.join(tmpDir, name);
408
+ let stat;
409
+ try {
410
+ stat = await fs.stat(fullPath);
411
+ if (!stat.isDirectory()) return;
412
+ } catch {
413
+ return;
414
+ }
415
+ const pidFile = path.join(fullPath, "postmaster.pid");
416
+ let pid = null;
417
+ let pidFileExists = false;
418
+ try {
419
+ const content = await fs.readFile(pidFile, "utf8");
420
+ pidFileExists = true;
421
+ const firstLine = content.split("\n")[0]?.trim();
422
+ const parsed = firstLine ? parseInt(firstLine, 10) : NaN;
423
+ if (!Number.isNaN(parsed) && parsed > 0) {
424
+ pid = parsed;
425
+ }
426
+ } catch {
427
+ }
428
+ if (pid !== null) {
429
+ try {
430
+ process.kill(pid, 0);
431
+ return;
432
+ } catch (err) {
433
+ const code = err.code;
434
+ if (code === "EPERM") {
435
+ return;
436
+ }
437
+ }
438
+ }
439
+ if (!pidFileExists && stat.mtimeMs > cutoff) {
440
+ return;
441
+ }
442
+ await fs.rm(fullPath, { recursive: true, force: true }).catch(() => {
443
+ });
444
+ })
445
+ );
446
+ }
393
447
  function parseParadeDBVersion(version) {
394
448
  const match = version.match(/^(\d+\.\d+\.\d+)(?:-pg(\d+))?$/);
395
- if (!match) {
449
+ if (!match || !match[1]) {
396
450
  return { extVersion: version };
397
451
  }
398
452
  return {
@@ -478,6 +532,30 @@ function quoteIdentifier(name) {
478
532
  }
479
533
 
480
534
  // src/PostgresMemoryServer.ts
535
+ var liveInstances = /* @__PURE__ */ new Set();
536
+ var exitHandlersRegistered = false;
537
+ var orphanSweepDone = false;
538
+ function registerExitHandlers() {
539
+ if (exitHandlersRegistered) return;
540
+ exitHandlersRegistered = true;
541
+ const cleanup = () => {
542
+ for (const instance of liveInstances) {
543
+ try {
544
+ instance._cleanupSync();
545
+ } catch {
546
+ }
547
+ }
548
+ };
549
+ process2.once("exit", cleanup);
550
+ const signalCleanup = (signal) => {
551
+ cleanup();
552
+ process2.removeListener(signal, signalCleanup);
553
+ process2.kill(process2.pid, signal);
554
+ };
555
+ process2.on("SIGINT", signalCleanup);
556
+ process2.on("SIGTERM", signalCleanup);
557
+ process2.on("SIGHUP", signalCleanup);
558
+ }
481
559
  var PostgresMemoryServer = class _PostgresMemoryServer {
482
560
  constructor(pg, port, dataDir, options) {
483
561
  this.pg = pg;
@@ -490,64 +568,81 @@ var PostgresMemoryServer = class _PostgresMemoryServer {
490
568
  snapshotSupported;
491
569
  hasSnapshot = false;
492
570
  static async create(options = {}) {
571
+ if (!orphanSweepDone) {
572
+ orphanSweepDone = true;
573
+ await sweepOrphanedDataDirs().catch(() => {
574
+ });
575
+ }
493
576
  const normalized = normalizeOptions(options);
494
577
  const port = await getFreePort();
495
578
  const dataDir = await fs2.mkdtemp(
496
579
  path2.join(os2.tmpdir(), "postgres-memory-server-")
497
580
  );
498
- const postgresFlags = [];
499
- if (normalized.preset === "paradedb") {
500
- const nativeDir = getNativeDir();
501
- const extVersion = resolveParadeDBVersion(normalized.version);
502
- const pgMajor = DEFAULT_POSTGRES_VERSION;
503
- try {
504
- await installParadeDBExtension(nativeDir, extVersion, pgMajor);
505
- } catch (error) {
506
- throw new ExtensionInstallError(
507
- "pg_search",
508
- error instanceof Error ? error : new Error(String(error))
509
- );
510
- }
511
- if (normalized.extensions.includes("vector")) {
581
+ let pg;
582
+ try {
583
+ const postgresFlags = [];
584
+ if (normalized.preset === "paradedb") {
585
+ const nativeDir = getNativeDir();
586
+ const extVersion = resolveParadeDBVersion(normalized.version);
587
+ const pgMajor = DEFAULT_POSTGRES_VERSION;
512
588
  try {
513
- await installPgVectorExtension(nativeDir, pgMajor);
589
+ await installParadeDBExtension(nativeDir, extVersion, pgMajor);
514
590
  } catch (error) {
515
591
  throw new ExtensionInstallError(
516
- "vector",
592
+ "pg_search",
517
593
  error instanceof Error ? error : new Error(String(error))
518
594
  );
519
595
  }
596
+ if (normalized.extensions.includes("vector")) {
597
+ try {
598
+ await installPgVectorExtension(nativeDir, pgMajor);
599
+ } catch (error) {
600
+ throw new ExtensionInstallError(
601
+ "vector",
602
+ error instanceof Error ? error : new Error(String(error))
603
+ );
604
+ }
605
+ }
606
+ if (normalized.extensions.includes("pg_search") || normalized.extensions.length === 0) {
607
+ postgresFlags.push("-c", "shared_preload_libraries=pg_search");
608
+ }
520
609
  }
521
- if (normalized.extensions.includes("pg_search") || normalized.extensions.length === 0) {
522
- postgresFlags.push(
523
- "-c",
524
- "shared_preload_libraries=pg_search"
525
- );
610
+ pg = new EmbeddedPostgres({
611
+ databaseDir: dataDir,
612
+ port,
613
+ user: normalized.username,
614
+ password: normalized.password,
615
+ persistent: false,
616
+ postgresFlags,
617
+ onLog: () => {
618
+ },
619
+ onError: () => {
620
+ }
621
+ });
622
+ await pg.initialise();
623
+ await pg.start();
624
+ if (normalized.database !== "postgres") {
625
+ await pg.createDatabase(normalized.database);
526
626
  }
527
- }
528
- const pg = new EmbeddedPostgres({
529
- databaseDir: dataDir,
530
- port,
531
- user: normalized.username,
532
- password: normalized.password,
533
- persistent: false,
534
- postgresFlags,
535
- onLog: () => {
536
- },
537
- onError: () => {
627
+ const server = new _PostgresMemoryServer(pg, port, dataDir, normalized);
628
+ liveInstances.add(server);
629
+ registerExitHandlers();
630
+ const initStatements = buildInitStatements(normalized);
631
+ if (initStatements.length > 0) {
632
+ await server.runSql(initStatements);
538
633
  }
539
- });
540
- await pg.initialise();
541
- await pg.start();
542
- if (normalized.database !== "postgres") {
543
- await pg.createDatabase(normalized.database);
544
- }
545
- const server = new _PostgresMemoryServer(pg, port, dataDir, normalized);
546
- const initStatements = buildInitStatements(normalized);
547
- if (initStatements.length > 0) {
548
- await server.runSql(initStatements);
634
+ return server;
635
+ } catch (error) {
636
+ if (pg) {
637
+ try {
638
+ await pg.stop();
639
+ } catch {
640
+ }
641
+ }
642
+ await fs2.rm(dataDir, { recursive: true, force: true }).catch(() => {
643
+ });
644
+ throw error;
549
645
  }
550
- return server;
551
646
  }
552
647
  static createPostgres(options = {}) {
553
648
  return _PostgresMemoryServer.create({ ...options, preset: "postgres" });
@@ -579,6 +674,14 @@ var PostgresMemoryServer = class _PostgresMemoryServer {
579
674
  getImage() {
580
675
  return this.options.image;
581
676
  }
677
+ /**
678
+ * Returns the absolute path to the temporary PostgreSQL data directory
679
+ * for this instance. Useful for debugging or backing up state. The
680
+ * directory is automatically removed by `stop()`.
681
+ */
682
+ getDataDir() {
683
+ return this.dataDir;
684
+ }
582
685
  getConnectionOptions() {
583
686
  return {
584
687
  host: this.getHost(),
@@ -684,7 +787,32 @@ var PostgresMemoryServer = class _PostgresMemoryServer {
684
787
  return;
685
788
  }
686
789
  this.stopped = true;
687
- await this.pg.stop();
790
+ liveInstances.delete(this);
791
+ try {
792
+ await this.pg.stop();
793
+ } catch {
794
+ }
795
+ await fs2.rm(this.dataDir, { recursive: true, force: true }).catch(() => {
796
+ });
797
+ }
798
+ /**
799
+ * Synchronous cleanup for use in process exit handlers. Cannot await,
800
+ * so we just remove the data directory and let the OS reap the postgres
801
+ * child process. embedded-postgres registers its own exit hook to kill
802
+ * the process; this method is a backup for the data directory only.
803
+ *
804
+ * @internal
805
+ */
806
+ _cleanupSync() {
807
+ if (this.stopped) {
808
+ return;
809
+ }
810
+ this.stopped = true;
811
+ liveInstances.delete(this);
812
+ try {
813
+ rmSync(this.dataDir, { recursive: true, force: true });
814
+ } catch {
815
+ }
688
816
  }
689
817
  /**
690
818
  * Connect to the "postgres" system database for admin operations
@@ -719,7 +847,7 @@ var PostgresMemoryServer = class _PostgresMemoryServer {
719
847
 
720
848
  // src/cli.ts
721
849
  async function main() {
722
- const { options, initFiles, json } = parseArgs(process2.argv.slice(2));
850
+ const { options, initFiles, json } = parseArgs(process3.argv.slice(2));
723
851
  const server = await PostgresMemoryServer.create(options);
724
852
  try {
725
853
  for (const file of initFiles) {
@@ -735,39 +863,39 @@ async function main() {
735
863
  image: server.getImage()
736
864
  };
737
865
  if (json) {
738
- process2.stdout.write(`${JSON.stringify(payload, null, 2)}
866
+ process3.stdout.write(`${JSON.stringify(payload, null, 2)}
739
867
  `);
740
868
  } else {
741
- process2.stdout.write(`POSTGRES_MEMORY_SERVER_URI=${payload.uri}
869
+ process3.stdout.write(`POSTGRES_MEMORY_SERVER_URI=${payload.uri}
742
870
  `);
743
- process2.stdout.write(`POSTGRES_MEMORY_SERVER_HOST=${payload.host}
871
+ process3.stdout.write(`POSTGRES_MEMORY_SERVER_HOST=${payload.host}
744
872
  `);
745
- process2.stdout.write(`POSTGRES_MEMORY_SERVER_PORT=${payload.port}
873
+ process3.stdout.write(`POSTGRES_MEMORY_SERVER_PORT=${payload.port}
746
874
  `);
747
- process2.stdout.write(
875
+ process3.stdout.write(
748
876
  `POSTGRES_MEMORY_SERVER_DATABASE=${payload.database}
749
877
  `
750
878
  );
751
- process2.stdout.write(
879
+ process3.stdout.write(
752
880
  `POSTGRES_MEMORY_SERVER_USERNAME=${payload.username}
753
881
  `
754
882
  );
755
- process2.stdout.write(
883
+ process3.stdout.write(
756
884
  `POSTGRES_MEMORY_SERVER_PASSWORD=${payload.password}
757
885
  `
758
886
  );
759
- process2.stdout.write(`POSTGRES_MEMORY_SERVER_IMAGE=${payload.image}
887
+ process3.stdout.write(`POSTGRES_MEMORY_SERVER_IMAGE=${payload.image}
760
888
  `);
761
- process2.stdout.write("\nPress Ctrl+C to stop the server.\n");
889
+ process3.stdout.write("\nPress Ctrl+C to stop the server.\n");
762
890
  }
763
891
  const stop = async () => {
764
892
  await server.stop();
765
- process2.exit(0);
893
+ process3.exit(0);
766
894
  };
767
- process2.on("SIGINT", () => {
895
+ process3.on("SIGINT", () => {
768
896
  void stop();
769
897
  });
770
- process2.on("SIGTERM", () => {
898
+ process3.on("SIGTERM", () => {
771
899
  void stop();
772
900
  });
773
901
  await new Promise(() => {
@@ -829,7 +957,7 @@ function parseArgs(argv) {
829
957
  }
830
958
  case "--help": {
831
959
  printHelp();
832
- process2.exit(0);
960
+ process3.exit(0);
833
961
  }
834
962
  default: {
835
963
  throw new Error(`Unknown argument: ${arg}`);
@@ -849,36 +977,36 @@ function readValue(argv, index, flag) {
849
977
  return value;
850
978
  }
851
979
  function printHelp() {
852
- process2.stdout.write(`postgres-memory-server
980
+ process3.stdout.write(`postgres-memory-server
853
981
 
854
982
  `);
855
- process2.stdout.write(`Options:
983
+ process3.stdout.write(`Options:
856
984
  `);
857
- process2.stdout.write(` --preset postgres|paradedb
985
+ process3.stdout.write(` --preset postgres|paradedb
858
986
  `);
859
- process2.stdout.write(` --version <tag>
987
+ process3.stdout.write(` --version <tag>
860
988
  `);
861
- process2.stdout.write(` --image <image> (deprecated, ignored)
989
+ process3.stdout.write(` --image <image> (deprecated, ignored)
862
990
  `);
863
- process2.stdout.write(` --database <name>
991
+ process3.stdout.write(` --database <name>
864
992
  `);
865
- process2.stdout.write(` --username <name>
993
+ process3.stdout.write(` --username <name>
866
994
  `);
867
- process2.stdout.write(` --password <password>
995
+ process3.stdout.write(` --password <password>
868
996
  `);
869
- process2.stdout.write(` --extension <name> repeatable
997
+ process3.stdout.write(` --extension <name> repeatable
870
998
  `);
871
- process2.stdout.write(` --init-file <path> repeatable
999
+ process3.stdout.write(` --init-file <path> repeatable
872
1000
  `);
873
- process2.stdout.write(` --json
1001
+ process3.stdout.write(` --json
874
1002
  `);
875
- process2.stdout.write(` --help
1003
+ process3.stdout.write(` --help
876
1004
  `);
877
1005
  }
878
1006
  void main().catch((error) => {
879
1007
  const message = error instanceof Error ? error.stack ?? error.message : String(error);
880
- process2.stderr.write(`${message}
1008
+ process3.stderr.write(`${message}
881
1009
  `);
882
- process2.exit(1);
1010
+ process3.exit(1);
883
1011
  });
884
1012
  //# sourceMappingURL=cli.js.map