pgserve 1.1.6 → 1.1.7
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/README.md +3 -3
- package/package.json +1 -1
- package/src/postgres.js +131 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<p>
|
|
6
6
|
<a href="https://www.npmjs.com/package/pgserve"><img src="https://img.shields.io/npm/v/pgserve?style=flat-square&color=00D9FF" alt="npm version"></a>
|
|
7
7
|
<img src="https://img.shields.io/badge/node-%3E%3D18-green?style=flat-square" alt="Node.js">
|
|
8
|
-
<img src="https://img.shields.io/badge/PostgreSQL-
|
|
8
|
+
<img src="https://img.shields.io/badge/PostgreSQL-18-blue?style=flat-square" alt="PostgreSQL">
|
|
9
9
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License"></a>
|
|
10
10
|
<a href="https://discord.gg/xcW8c7fF3R"><img src="https://img.shields.io/discord/1095114867012292758?style=flat-square&color=00D9FF&label=discord" alt="Discord"></a>
|
|
11
11
|
</p>
|
|
@@ -41,7 +41,7 @@ psql postgresql://localhost:8432/myapp
|
|
|
41
41
|
|
|
42
42
|
<table>
|
|
43
43
|
<tr>
|
|
44
|
-
<td><b>Real PostgreSQL
|
|
44
|
+
<td><b>Real PostgreSQL 18</b></td>
|
|
45
45
|
<td>Native binaries, not WASM — full compatibility, extensions support</td>
|
|
46
46
|
</tr>
|
|
47
47
|
<tr>
|
|
@@ -450,7 +450,7 @@ CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
450
450
|
</tr>
|
|
451
451
|
</table>
|
|
452
452
|
|
|
453
|
-
> <b>Methodology:</b> Recall@k measured against brute-force ground truth (industry standard). PostgreSQL baseline is Docker <code>pgvector/pgvector:
|
|
453
|
+
> <b>Methodology:</b> Recall@k measured against brute-force ground truth (industry standard). PostgreSQL baseline is Docker <code>pgvector/pgvector:pg18</code>. RAM mode available on Linux and WSL2.
|
|
454
454
|
>
|
|
455
455
|
> Run benchmarks yourself: <code>bun tests/benchmarks/runner.js --include-vector</code>
|
|
456
456
|
|
package/package.json
CHANGED
package/src/postgres.js
CHANGED
|
@@ -509,6 +509,12 @@ export class PostgresManager {
|
|
|
509
509
|
persistent: this.persistent
|
|
510
510
|
}, 'PostgreSQL started successfully');
|
|
511
511
|
|
|
512
|
+
// Pre-install pgvector extension files if enabled
|
|
513
|
+
// This ensures vector.so + vector.control are ready before any CREATE EXTENSION call
|
|
514
|
+
if (this.enablePgvector) {
|
|
515
|
+
await this.ensurePgvectorFiles();
|
|
516
|
+
}
|
|
517
|
+
|
|
512
518
|
return this;
|
|
513
519
|
}
|
|
514
520
|
|
|
@@ -917,12 +923,137 @@ export class PostgresManager {
|
|
|
917
923
|
}
|
|
918
924
|
}
|
|
919
925
|
|
|
926
|
+
/**
|
|
927
|
+
* Ensure pgvector extension files are installed in the PG binary dirs.
|
|
928
|
+
* Downloads prebuilt vector.so from apt.postgresql.org on first use (cached).
|
|
929
|
+
* Patches vector.control to use absolute module_pathname.
|
|
930
|
+
*
|
|
931
|
+
* Linux only — .deb extraction requires dpkg-deb or ar+tar.
|
|
932
|
+
* Serialized via _pgvectorInstallPromise to prevent concurrent races.
|
|
933
|
+
*/
|
|
934
|
+
async ensurePgvectorFiles() {
|
|
935
|
+
// Serialize: only one install runs at a time
|
|
936
|
+
if (this._pgvectorInstallPromise) {
|
|
937
|
+
return this._pgvectorInstallPromise;
|
|
938
|
+
}
|
|
939
|
+
this._pgvectorInstallPromise = this._doEnsurePgvectorFiles();
|
|
940
|
+
try {
|
|
941
|
+
await this._pgvectorInstallPromise;
|
|
942
|
+
} finally {
|
|
943
|
+
this._pgvectorInstallPromise = null;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async _doEnsurePgvectorFiles() {
|
|
948
|
+
if (!this.binaries?.libDir) return;
|
|
949
|
+
|
|
950
|
+
// Linux only — .deb packages are Linux-specific
|
|
951
|
+
if (os.platform() !== 'linux') {
|
|
952
|
+
this.logger.info('pgvector auto-install is Linux-only. On macOS, install via: brew install pgvector');
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const libDir = this.binaries.libDir;
|
|
957
|
+
const binDir = this.binaries.binDir;
|
|
958
|
+
const extDir = path.join(path.dirname(binDir), 'share', 'postgresql', 'extension');
|
|
959
|
+
const vectorSo = path.join(libDir, 'vector.so');
|
|
960
|
+
const vectorControl = path.join(extDir, 'vector.control');
|
|
961
|
+
|
|
962
|
+
// Already installed
|
|
963
|
+
if (fs.existsSync(vectorSo) && fs.existsSync(vectorControl)) return;
|
|
964
|
+
|
|
965
|
+
this.logger.info('pgvector extension files not found — downloading prebuilt binary...');
|
|
966
|
+
|
|
967
|
+
try {
|
|
968
|
+
// Detect PG major version from the postgres binary
|
|
969
|
+
const { execSync } = await import('node:child_process');
|
|
970
|
+
const pgVersion = execSync(`${this.binaries.postgres} --version`, { encoding: 'utf-8' }).trim();
|
|
971
|
+
const majorMatch = pgVersion.match(/PostgreSQL (\d+)/);
|
|
972
|
+
const pgMajor = majorMatch ? majorMatch[1] : '17';
|
|
973
|
+
|
|
974
|
+
// Detect architecture — fail explicitly on unsupported platforms
|
|
975
|
+
const nodeArch = os.arch();
|
|
976
|
+
let arch;
|
|
977
|
+
if (nodeArch === 'x64') arch = 'amd64';
|
|
978
|
+
else if (nodeArch === 'arm64') arch = 'arm64';
|
|
979
|
+
else {
|
|
980
|
+
this.logger.warn({ arch: nodeArch }, 'Unsupported architecture for pgvector auto-install. Supported: x64, arm64');
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Download prebuilt pgvector .deb from apt.postgresql.org (HTTPS)
|
|
985
|
+
// Version 0.8.1-2 — update when new releases ship
|
|
986
|
+
const debUrl = `https://apt.postgresql.org/pub/repos/apt/pool/main/p/pgvector/postgresql-${pgMajor}-pgvector_0.8.1-2.pgdg%2B1_${arch}.deb`;
|
|
987
|
+
this.logger.info({ url: debUrl }, 'Downloading pgvector...');
|
|
988
|
+
|
|
989
|
+
const res = await fetch(debUrl);
|
|
990
|
+
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
991
|
+
|
|
992
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
993
|
+
|
|
994
|
+
// Extract .deb (it's an ar archive containing data.tar.xz)
|
|
995
|
+
const tmpDir = path.join(os.tmpdir(), `pgserve-pgvector-${process.pid}-${Date.now()}`);
|
|
996
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
997
|
+
const debPath = path.join(tmpDir, 'pgvector.deb');
|
|
998
|
+
fs.writeFileSync(debPath, buffer);
|
|
999
|
+
|
|
1000
|
+
// Use dpkg-deb or ar to extract
|
|
1001
|
+
try {
|
|
1002
|
+
execSync(`dpkg-deb -x ${debPath} ${tmpDir}/extracted`, { stdio: 'pipe' });
|
|
1003
|
+
} catch {
|
|
1004
|
+
// Fallback: try ar + tar
|
|
1005
|
+
fs.mkdirSync(path.join(tmpDir, 'extracted'), { recursive: true });
|
|
1006
|
+
execSync(`cd ${tmpDir} && ar x pgvector.deb && tar xf data.tar.* -C ${tmpDir}/extracted 2>/dev/null || tar xf data.tar.xz -C ${tmpDir}/extracted`, { stdio: 'pipe' });
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Copy .so file
|
|
1010
|
+
const soSrc = path.join(tmpDir, 'extracted', 'usr', 'lib', 'postgresql', pgMajor, 'lib', 'vector.so');
|
|
1011
|
+
if (fs.existsSync(soSrc)) {
|
|
1012
|
+
fs.copyFileSync(soSrc, vectorSo);
|
|
1013
|
+
this.logger.info({ path: vectorSo }, 'Installed vector.so');
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Copy extension SQL + control files
|
|
1017
|
+
const extSrc = path.join(tmpDir, 'extracted', 'usr', 'share', 'postgresql', pgMajor, 'extension');
|
|
1018
|
+
if (fs.existsSync(extSrc)) {
|
|
1019
|
+
fs.mkdirSync(extDir, { recursive: true });
|
|
1020
|
+
for (const f of fs.readdirSync(extSrc)) {
|
|
1021
|
+
if (f.startsWith('vector')) {
|
|
1022
|
+
fs.copyFileSync(path.join(extSrc, f), path.join(extDir, f));
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
this.logger.info({ path: extDir }, 'Installed vector extension SQL files');
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Patch vector.control to use absolute module_pathname
|
|
1029
|
+
// (embedded PG's $libdir doesn't match the compiled-in path)
|
|
1030
|
+
if (fs.existsSync(vectorControl)) {
|
|
1031
|
+
let control = fs.readFileSync(vectorControl, 'utf-8');
|
|
1032
|
+
control = control.replace(
|
|
1033
|
+
/module_pathname\s*=\s*'\$libdir\/vector'/,
|
|
1034
|
+
`module_pathname = '${vectorSo.replace('.so', '')}'`
|
|
1035
|
+
);
|
|
1036
|
+
fs.writeFileSync(vectorControl, control);
|
|
1037
|
+
this.logger.info('Patched vector.control with absolute module path');
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Cleanup
|
|
1041
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1042
|
+
this.logger.info('pgvector extension installed successfully');
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
this.logger.warn({ err: error.message }, 'Failed to install pgvector extension files (non-fatal)');
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
920
1048
|
/**
|
|
921
1049
|
* Enable pgvector extension on a database
|
|
922
1050
|
* Creates a temporary connection to the specific database to run CREATE EXTENSION
|
|
923
1051
|
* @param {string} dbName - Database name to enable pgvector on
|
|
924
1052
|
*/
|
|
925
1053
|
async enablePgvectorExtension(dbName) {
|
|
1054
|
+
// Ensure extension files are installed first
|
|
1055
|
+
await this.ensurePgvectorFiles();
|
|
1056
|
+
|
|
926
1057
|
const { SQL } = await import('bun');
|
|
927
1058
|
let dbPool = null;
|
|
928
1059
|
|