pgserve 2.0.0 โ 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/.github/workflows/ci.yml +2 -2
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/version.yml +2 -2
- package/Makefile +12 -12
- package/README.md +30 -9
- package/bin/pgserve-wrapper.cjs +3 -3
- package/bin/{pglite-server.js โ postgres-server.js} +5 -0
- package/bun.lock +0 -3
- package/ecosystem.config.cjs +3 -3
- package/knip.json +1 -1
- package/package.json +4 -5
- package/scripts/test-bun-self-heal.sh +10 -10
- package/src/daemon.js +1 -1
- package/src/fingerprint.js +30 -4
- package/src/index.js +20 -0
- package/src/postgres.js +34 -9
- package/src/sdk.js +137 -0
- package/tests/benchmarks/runner.js +430 -754
- package/tests/daemon-fingerprint-integration.test.js +5 -3
- package/tests/daemon-pr24-regression.test.js +1 -4
- package/tests/fingerprint.test.js +14 -0
- package/tests/sdk.test.js +71 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -80,7 +80,7 @@ jobs:
|
|
|
80
80
|
- name: Build executable
|
|
81
81
|
run: |
|
|
82
82
|
mkdir -p dist
|
|
83
|
-
bun build --compile bin/
|
|
83
|
+
bun build --compile bin/postgres-server.js --outfile dist/pgserve
|
|
84
84
|
|
|
85
85
|
- name: Verify executable
|
|
86
86
|
run: |
|
|
@@ -119,4 +119,4 @@ jobs:
|
|
|
119
119
|
npm init -y
|
|
120
120
|
npm install /tmp/pgserve-*.tgz
|
|
121
121
|
timeout 30 npx pgserve --help
|
|
122
|
-
echo "npx test passed!"
|
|
122
|
+
echo "npx test passed!"
|
|
@@ -86,7 +86,7 @@ jobs:
|
|
|
86
86
|
TAG="${{ steps.bump.outputs.tag }}"
|
|
87
87
|
git add package.json
|
|
88
88
|
git commit -m "[skip ci] release ${TAG}"
|
|
89
|
-
git tag "${TAG}"
|
|
89
|
+
git tag -a "${TAG}" -m "release ${TAG}"
|
|
90
90
|
git push origin HEAD --follow-tags
|
|
91
91
|
|
|
92
92
|
# ---------------------------------------------------------------------------
|
|
@@ -87,7 +87,7 @@ jobs:
|
|
|
87
87
|
--windows-description="Embedded PostgreSQL Server - Zero config, auto-provision, unlimited connections" `
|
|
88
88
|
--windows-version="$WIN_VERSION" `
|
|
89
89
|
--windows-copyright="Copyright (c) 2025 Namastex Labs" `
|
|
90
|
-
bin/
|
|
90
|
+
bin/postgres-server.js --outfile dist/${{ matrix.output }}
|
|
91
91
|
Get-ChildItem dist/
|
|
92
92
|
shell: pwsh
|
|
93
93
|
|
|
@@ -97,7 +97,7 @@ jobs:
|
|
|
97
97
|
run: |
|
|
98
98
|
mkdir -p dist
|
|
99
99
|
VERSION=$(node -p "require('./package.json').version")
|
|
100
|
-
bun build --compile --define BUILD_VERSION="'$VERSION'" bin/
|
|
100
|
+
bun build --compile --define BUILD_VERSION="'$VERSION'" bin/postgres-server.js --outfile dist/${{ matrix.output }}
|
|
101
101
|
ls -lh dist/
|
|
102
102
|
|
|
103
103
|
- name: Upload artifact
|
package/Makefile
CHANGED
|
@@ -79,13 +79,13 @@ bench: ## Run benchmarks
|
|
|
79
79
|
.PHONY: test-local
|
|
80
80
|
test-local: ## Test server locally
|
|
81
81
|
@echo "$(CYAN)๐งช Testing server...$(RESET)"
|
|
82
|
-
@./bin/
|
|
82
|
+
@./bin/postgres-server.js start ./data/test-local --port 12050 --log info &
|
|
83
83
|
@TESTPID=$$!; \
|
|
84
84
|
sleep 3; \
|
|
85
|
-
./bin/
|
|
86
|
-
./bin/
|
|
85
|
+
./bin/postgres-server.js list; \
|
|
86
|
+
./bin/postgres-server.js health --port 12050; \
|
|
87
87
|
kill $$TESTPID 2>/dev/null || true; \
|
|
88
|
-
./bin/
|
|
88
|
+
./bin/postgres-server.js cleanup
|
|
89
89
|
@echo "$(GREEN)โ
Server test passed!$(RESET)"
|
|
90
90
|
|
|
91
91
|
# ==========================================
|
|
@@ -152,7 +152,7 @@ check-version: ## Check if version tag exists
|
|
|
152
152
|
|
|
153
153
|
check-files: ## Check required files exist
|
|
154
154
|
@echo "$(CYAN)๐ Checking required files...$(RESET)"
|
|
155
|
-
@for file in package.json README.md LICENSE src/index.js bin/
|
|
155
|
+
@for file in package.json README.md LICENSE src/index.js bin/postgres-server.js; do \
|
|
156
156
|
if [ ! -f "$$file" ]; then \
|
|
157
157
|
echo "$(RED)โ Missing required file: $$file$(RESET)"; \
|
|
158
158
|
exit 1; \
|
|
@@ -172,26 +172,26 @@ $(DIST_DIR):
|
|
|
172
172
|
|
|
173
173
|
build: $(DIST_DIR) ## Build standalone executable for current platform
|
|
174
174
|
@echo "$(CYAN)๐จ Building standalone executable...$(RESET)"
|
|
175
|
-
@bun build --compile bin/
|
|
175
|
+
@bun build --compile bin/postgres-server.js --outfile $(DIST_DIR)/pgserve
|
|
176
176
|
@echo "$(GREEN)โ
Built: $(DIST_DIR)/pgserve$(RESET)"
|
|
177
177
|
|
|
178
178
|
build-linux: $(DIST_DIR) ## Build for Linux (x64 + arm64)
|
|
179
179
|
@echo "$(CYAN)๐ง Building for Linux...$(RESET)"
|
|
180
|
-
@bun build --compile --target=bun-linux-x64 bin/
|
|
181
|
-
@bun build --compile --target=bun-linux-arm64 bin/
|
|
180
|
+
@bun build --compile --target=bun-linux-x64 bin/postgres-server.js --outfile $(DIST_DIR)/pgserve-linux-x64
|
|
181
|
+
@bun build --compile --target=bun-linux-arm64 bin/postgres-server.js --outfile $(DIST_DIR)/pgserve-linux-arm64
|
|
182
182
|
@echo "$(GREEN)โ
Built: $(DIST_DIR)/pgserve-linux-x64$(RESET)"
|
|
183
183
|
@echo "$(GREEN)โ
Built: $(DIST_DIR)/pgserve-linux-arm64$(RESET)"
|
|
184
184
|
|
|
185
185
|
build-macos: $(DIST_DIR) ## Build for macOS (x64 + arm64)
|
|
186
186
|
@echo "$(CYAN)๐ Building for macOS...$(RESET)"
|
|
187
|
-
@bun build --compile --target=bun-darwin-x64 bin/
|
|
188
|
-
@bun build --compile --target=bun-darwin-arm64 bin/
|
|
187
|
+
@bun build --compile --target=bun-darwin-x64 bin/postgres-server.js --outfile $(DIST_DIR)/pgserve-darwin-x64
|
|
188
|
+
@bun build --compile --target=bun-darwin-arm64 bin/postgres-server.js --outfile $(DIST_DIR)/pgserve-darwin-arm64
|
|
189
189
|
@echo "$(GREEN)โ
Built: $(DIST_DIR)/pgserve-darwin-x64$(RESET)"
|
|
190
190
|
@echo "$(GREEN)โ
Built: $(DIST_DIR)/pgserve-darwin-arm64$(RESET)"
|
|
191
191
|
|
|
192
192
|
build-windows: $(DIST_DIR) ## Build for Windows (x64)
|
|
193
193
|
@echo "$(CYAN)๐ช Building for Windows...$(RESET)"
|
|
194
|
-
@bun build --compile --target=bun-windows-x64 bin/
|
|
194
|
+
@bun build --compile --target=bun-windows-x64 bin/postgres-server.js --outfile $(DIST_DIR)/pgserve-windows-x64.exe
|
|
195
195
|
@echo "$(GREEN)โ
Built: $(DIST_DIR)/pgserve-windows-x64.exe$(RESET)"
|
|
196
196
|
|
|
197
197
|
build-all: build-linux build-macos build-windows ## Build for all platforms
|
|
@@ -254,7 +254,7 @@ publish: ## โ ๏ธ [DEPRECATED] Releases run from CI on push to main
|
|
|
254
254
|
clean: ## Clean generated files
|
|
255
255
|
@echo "$(CYAN)๐งน Cleaning...$(RESET)"
|
|
256
256
|
@rm -rf data/test-* data/genieos-local
|
|
257
|
-
@./bin/
|
|
257
|
+
@./bin/postgres-server.js cleanup
|
|
258
258
|
@echo "$(GREEN)โ
Cleaned!$(RESET)"
|
|
259
259
|
|
|
260
260
|
clean-all: clean ## Deep clean (including node_modules)
|
package/README.md
CHANGED
|
@@ -355,6 +355,27 @@ ss -tlnp | grep pgserve # no rows expected
|
|
|
355
355
|
|
|
356
356
|
## API
|
|
357
357
|
|
|
358
|
+
Daemon-first apps can let the first caller install/start the singleton and
|
|
359
|
+
then connect through the Unix socket. The daemon derives the app identity
|
|
360
|
+
from kernel peer credentials and routes it to that app's signed fingerprint
|
|
361
|
+
database.
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
import { daemonClientOptions, ensureDaemon } from 'pgserve';
|
|
365
|
+
import postgres from 'postgres';
|
|
366
|
+
|
|
367
|
+
await ensureDaemon({
|
|
368
|
+
dataDir: `${process.env.HOME}/.pgserve/data`,
|
|
369
|
+
logLevel: 'warn',
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
const sql = postgres(daemonClientOptions());
|
|
373
|
+
await sql`SELECT current_database()`;
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The classic TCP router API remains available for explicit v1-compatible
|
|
377
|
+
embedded servers:
|
|
378
|
+
|
|
358
379
|
```javascript
|
|
359
380
|
import { startMultiTenantServer } from 'pgserve';
|
|
360
381
|
|
|
@@ -510,10 +531,10 @@ CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
510
531
|
<tr>
|
|
511
532
|
<th>Scenario</th>
|
|
512
533
|
<th>SQLite</th>
|
|
513
|
-
<th>PGlite</th>
|
|
514
534
|
<th>PostgreSQL</th>
|
|
515
|
-
<th>pgserve</th>
|
|
516
|
-
<th>pgserve
|
|
535
|
+
<th>pgserve 1.2.0</th>
|
|
536
|
+
<th>pgserve v2</th>
|
|
537
|
+
<th>pgserve v2 --ram</th>
|
|
517
538
|
</tr>
|
|
518
539
|
<tr>
|
|
519
540
|
<td><b>Concurrent Writes</b> (10 agents)</td>
|
|
@@ -546,10 +567,10 @@ CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
546
567
|
<table>
|
|
547
568
|
<tr>
|
|
548
569
|
<th>Metric</th>
|
|
549
|
-
<th>PGlite</th>
|
|
550
570
|
<th>PostgreSQL</th>
|
|
551
|
-
<th>pgserve</th>
|
|
552
|
-
<th>pgserve
|
|
571
|
+
<th>pgserve 1.2.0</th>
|
|
572
|
+
<th>pgserve v2</th>
|
|
573
|
+
<th>pgserve v2 --ram</th>
|
|
553
574
|
</tr>
|
|
554
575
|
<tr>
|
|
555
576
|
<td><b>Vector INSERT</b> (1000 ร 1536-dim)</td>
|
|
@@ -598,7 +619,7 @@ CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
598
619
|
<td>117</td>
|
|
599
620
|
</tr>
|
|
600
621
|
<tr>
|
|
601
|
-
<td>
|
|
622
|
+
<td>pgserve 1.2.0</td>
|
|
602
623
|
<td>305</td>
|
|
603
624
|
<td>65</td>
|
|
604
625
|
<td>100%</td>
|
|
@@ -616,7 +637,7 @@ CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
616
637
|
<td>1,067</td>
|
|
617
638
|
</tr>
|
|
618
639
|
<tr>
|
|
619
|
-
<td>pgserve</td>
|
|
640
|
+
<td>pgserve v2</td>
|
|
620
641
|
<td>2,145</td>
|
|
621
642
|
<td>149</td>
|
|
622
643
|
<td>100%</td>
|
|
@@ -625,7 +646,7 @@ CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
625
646
|
<td>1,347</td>
|
|
626
647
|
</tr>
|
|
627
648
|
<tr>
|
|
628
|
-
<td><b>pgserve --ram</b></td>
|
|
649
|
+
<td><b>pgserve v2 --ram</b></td>
|
|
629
650
|
<td><b>3,541</b></td>
|
|
630
651
|
<td><b>381</b></td>
|
|
631
652
|
<td><b>100%</b></td>
|
package/bin/pgserve-wrapper.cjs
CHANGED
|
@@ -61,19 +61,19 @@ if (!bunPath) {
|
|
|
61
61
|
// then exits instantly with:
|
|
62
62
|
// Error: Bun's postinstall script was not run.
|
|
63
63
|
//
|
|
64
|
-
//
|
|
64
|
+
// postgres-server.js's TCP readiness poll can't distinguish this from a slow
|
|
65
65
|
// startup, so users see a confusing 30s timeout. Detect the specific error
|
|
66
66
|
// here, attempt the documented self-heal once (`node install.js`), and retry.
|
|
67
67
|
// If self-heal also fails, surface the real error instead of hanging later.
|
|
68
68
|
ensureBunHealthy(bunPath);
|
|
69
69
|
|
|
70
|
-
const scriptPath = path.join(__dirname, '
|
|
70
|
+
const scriptPath = path.join(__dirname, 'postgres-server.js');
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Verify the selected bun binary can execute. If it fails with the known
|
|
74
74
|
* "postinstall script was not run" signature, attempt a one-shot repair via
|
|
75
75
|
* the bun npm package's install.js. Throws (with a useful message) rather
|
|
76
|
-
* than letting
|
|
76
|
+
* than letting postgres-server.js hang on the TCP readiness poll for 30s.
|
|
77
77
|
*/
|
|
78
78
|
function ensureBunHealthy(bunExe) {
|
|
79
79
|
const probe = probeBun(bunExe);
|
|
@@ -117,6 +117,7 @@ function parseDaemonArgs(daemonArgs) {
|
|
|
117
117
|
logLevel: 'info',
|
|
118
118
|
autoProvision: true,
|
|
119
119
|
tcpListens: [],
|
|
120
|
+
enablePgvector: false,
|
|
120
121
|
};
|
|
121
122
|
for (let i = 0; i < daemonArgs.length; i++) {
|
|
122
123
|
const arg = daemonArgs[i];
|
|
@@ -138,6 +139,9 @@ function parseDaemonArgs(daemonArgs) {
|
|
|
138
139
|
case '--listen':
|
|
139
140
|
opts.tcpListens.push(daemonArgs[++i]);
|
|
140
141
|
break;
|
|
142
|
+
case '--pgvector':
|
|
143
|
+
opts.enablePgvector = true;
|
|
144
|
+
break;
|
|
141
145
|
case '--help':
|
|
142
146
|
console.log(`
|
|
143
147
|
pgserve daemon โ singleton control-socket mode
|
|
@@ -154,6 +158,7 @@ OPTIONS:
|
|
|
154
158
|
--log <level> Log level: error|warn|info|debug (default: info)
|
|
155
159
|
--no-provision Disable auto-provisioning of databases
|
|
156
160
|
--listen [host:]port Bind opt-in TCP listener (repeatable)
|
|
161
|
+
--pgvector Auto-enable pgvector extension on new databases
|
|
157
162
|
--help Show this help
|
|
158
163
|
|
|
159
164
|
The daemon binds $XDG_RUNTIME_DIR/pgserve/control.sock (fallback /tmp/pgserve/control.sock).
|
package/bun.lock
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"bun": "^1.3.4",
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"@electric-sql/pglite": "^0.2.17",
|
|
12
11
|
"eslint": "^9.39.1",
|
|
13
12
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
14
13
|
"husky": "^9.1.7",
|
|
@@ -25,8 +24,6 @@
|
|
|
25
24
|
},
|
|
26
25
|
},
|
|
27
26
|
"packages": {
|
|
28
|
-
"@electric-sql/pglite": ["@electric-sql/pglite@0.2.17", "", {}, "sha512-qEpKRT2oUaWDH6tjRxLHjdzMqRUGYDnGZlKrnL4dJ77JVMcP2Hpo3NYnOSPKdZdeec57B6QPprCUFg0picx5Pw=="],
|
|
29
|
-
|
|
30
27
|
"@embedded-postgres/darwin-arm64": ["@embedded-postgres/darwin-arm64@18.2.0-beta.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wnswaF+uDvGeitqajJ8v8xOG4ttFrzixElwKNe2MIxBXSLWPV3xhi6tBY0Sjw8Lmiu6UG9vNLFZSjHPrIeokBg=="],
|
|
31
28
|
|
|
32
29
|
"@embedded-postgres/darwin-x64": ["@embedded-postgres/darwin-x64@18.2.0-beta.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-u9WtTPxRuO0uOny5IniXHSDaLmtOujwzDoExIV/jFT0Fu8SzpX7wdoPbsSPBLgyQWdr/nPA77K9QI4r6P1/fKA=="],
|
package/ecosystem.config.cjs
CHANGED
|
@@ -2,7 +2,7 @@ module.exports = {
|
|
|
2
2
|
apps: [
|
|
3
3
|
{
|
|
4
4
|
name: 'pgserve',
|
|
5
|
-
script: './bin/
|
|
5
|
+
script: './bin/postgres-server.js',
|
|
6
6
|
args: 'router --port 8432',
|
|
7
7
|
cwd: '/home/namastex/dev/pgserve',
|
|
8
8
|
interpreter: 'node',
|
|
@@ -13,8 +13,8 @@ module.exports = {
|
|
|
13
13
|
env: {
|
|
14
14
|
NODE_ENV: 'production'
|
|
15
15
|
},
|
|
16
|
-
error_file: '/home/namastex/logs/
|
|
17
|
-
out_file: '/home/namastex/logs/
|
|
16
|
+
error_file: '/home/namastex/logs/postgres-server-error.log',
|
|
17
|
+
out_file: '/home/namastex/logs/postgres-server-out.log',
|
|
18
18
|
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
|
19
19
|
merge_logs: true,
|
|
20
20
|
time: true
|
package/knip.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://unpkg.com/knip@5/schema.json",
|
|
3
|
-
"entry": ["src/index.js", "bin/
|
|
3
|
+
"entry": ["src/index.js", "bin/postgres-server.js", "bin/pgserve-wrapper.cjs"],
|
|
4
4
|
"project": ["src/**/*.js", "bin/**/*.js", "bin/**/*.cjs"],
|
|
5
5
|
"ignore": ["tests/**", "helpers/**", "scripts/**"],
|
|
6
6
|
"ignoreBinaries": ["scripts/test-npx.sh", "scripts/test-bun-self-heal.sh", "make"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgserve",
|
|
3
|
-
"version": "2.0.
|
|
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",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"bench": "bun tests/benchmarks/runner.js",
|
|
12
12
|
"test": "bun test tests/**/*.test.js",
|
|
13
13
|
"test:watch": "bun test --watch tests/**/*.test.js",
|
|
14
|
-
"dev": "bun --watch bin/
|
|
15
|
-
"start": "bun bin/
|
|
16
|
-
"build": "bun build --compile bin/
|
|
14
|
+
"dev": "bun --watch bin/postgres-server.js",
|
|
15
|
+
"start": "bun bin/postgres-server.js",
|
|
16
|
+
"build": "bun build --compile bin/postgres-server.js --outfile dist/pgserve",
|
|
17
17
|
"build:all": "make build-all",
|
|
18
18
|
"lint": "eslint src/ bin/",
|
|
19
19
|
"lint:fix": "eslint src/ bin/ --fix",
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
"@embedded-postgres/windows-x64": "18.2.0-beta.16"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@electric-sql/pglite": "^0.2.17",
|
|
52
51
|
"eslint": "^9.39.1",
|
|
53
52
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
54
53
|
"husky": "^9.1.7",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# pgserve-wrapper.cjs must detect this and self-heal via `node install.js`.
|
|
8
8
|
#
|
|
9
9
|
# This test stages a synthetic broken install tree, runs the wrapper, and
|
|
10
|
-
# asserts that it recovers and spawns
|
|
10
|
+
# asserts that it recovers and spawns postgres-server.
|
|
11
11
|
|
|
12
12
|
set -e
|
|
13
13
|
|
|
@@ -37,10 +37,10 @@ mkdir -p "$FIXTURE/node_modules/pgserve/bin"
|
|
|
37
37
|
|
|
38
38
|
cp "$WRAPPER" "$FIXTURE/node_modules/pgserve/bin/pgserve-wrapper.cjs"
|
|
39
39
|
|
|
40
|
-
# Stub
|
|
40
|
+
# Stub postgres-server so we can detect a successful spawn without needing
|
|
41
41
|
# postgres binaries in the fixture.
|
|
42
|
-
cat > "$FIXTURE/node_modules/pgserve/bin/
|
|
43
|
-
console.log("
|
|
42
|
+
cat > "$FIXTURE/node_modules/pgserve/bin/postgres-server.js" <<'EOF'
|
|
43
|
+
console.log("postgres-server-spawned");
|
|
44
44
|
process.exit(0);
|
|
45
45
|
EOF
|
|
46
46
|
|
|
@@ -97,13 +97,13 @@ if ! echo "$OUTPUT" | grep -q "bun runtime recovered"; then
|
|
|
97
97
|
exit 1
|
|
98
98
|
fi
|
|
99
99
|
|
|
100
|
-
if ! echo "$OUTPUT" | grep -q "
|
|
101
|
-
echo "โ
|
|
100
|
+
if ! echo "$OUTPUT" | grep -q "postgres-server-spawned"; then
|
|
101
|
+
echo "โ postgres-server was not spawned after self-heal"
|
|
102
102
|
echo "$OUTPUT"
|
|
103
103
|
exit 1
|
|
104
104
|
fi
|
|
105
105
|
|
|
106
|
-
echo "โ self-heal path: wrapper detected, repaired, and spawned
|
|
106
|
+
echo "โ self-heal path: wrapper detected, repaired, and spawned postgres-server"
|
|
107
107
|
|
|
108
108
|
echo ""
|
|
109
109
|
echo "=== Testing healthy path is unaffected ==="
|
|
@@ -122,13 +122,13 @@ if echo "$OUTPUT" | grep -q "self-heal\|recovered"; then
|
|
|
122
122
|
exit 1
|
|
123
123
|
fi
|
|
124
124
|
|
|
125
|
-
if ! echo "$OUTPUT" | grep -q "
|
|
126
|
-
echo "โ
|
|
125
|
+
if ! echo "$OUTPUT" | grep -q "postgres-server-spawned"; then
|
|
126
|
+
echo "โ postgres-server was not spawned on healthy path"
|
|
127
127
|
echo "$OUTPUT"
|
|
128
128
|
exit 1
|
|
129
129
|
fi
|
|
130
130
|
|
|
131
|
-
echo "โ healthy path: wrapper was silent and spawned
|
|
131
|
+
echo "โ healthy path: wrapper was silent and spawned postgres-server directly"
|
|
132
132
|
|
|
133
133
|
echo ""
|
|
134
134
|
echo "=== Testing non-postinstall errors surface raw ==="
|
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
|
|
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,
|
package/src/fingerprint.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
179
|
+
// Peer process metadata reads
|
|
179
180
|
// ---------------------------------------------------------------------------
|
|
180
181
|
|
|
181
182
|
/**
|
|
182
|
-
* Resolve the cwd of a peer process
|
|
183
|
-
*
|
|
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/index.js
CHANGED
|
@@ -24,6 +24,26 @@ export {
|
|
|
24
24
|
acquirePidLock,
|
|
25
25
|
isProcessAlive,
|
|
26
26
|
} from './daemon.js';
|
|
27
|
+
export {
|
|
28
|
+
buildDaemonArgs,
|
|
29
|
+
daemonClientOptions,
|
|
30
|
+
ensureDaemon,
|
|
31
|
+
probeDaemon,
|
|
32
|
+
resolveBundledCliBin,
|
|
33
|
+
} from './sdk.js';
|
|
34
|
+
export {
|
|
35
|
+
derivePackageFingerprint,
|
|
36
|
+
deriveScriptFingerprint,
|
|
37
|
+
fingerprintFromCred,
|
|
38
|
+
findNearestPackageJson,
|
|
39
|
+
readPackageName,
|
|
40
|
+
readPersistFlag,
|
|
41
|
+
} from './fingerprint.js';
|
|
42
|
+
export {
|
|
43
|
+
hashToken,
|
|
44
|
+
mintToken,
|
|
45
|
+
parseTcpAuth,
|
|
46
|
+
} from './tokens.js';
|
|
27
47
|
|
|
28
48
|
// Default export
|
|
29
49
|
export { startMultiTenantServer as default } from './router.js';
|
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
|
|
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=
|
|
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 (
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
883
|
-
return;
|
|
907
|
+
if (processExited) return;
|
|
908
|
+
if (markReady('tcp')) return;
|
|
884
909
|
} catch {
|
|
885
910
|
await Bun.sleep(200);
|
|
886
911
|
}
|