pgserve 2.1.3 → 2.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/CHANGELOG.md +86 -0
- package/README.md +105 -1
- package/bin/autopg-wrapper.cjs +16 -0
- package/bin/pgserve-wrapper.cjs +31 -6
- package/bin/postgres-server.js +56 -0
- package/console/README.md +131 -0
- package/console/api.js +173 -0
- package/console/app.jsx +483 -0
- package/console/colors_and_type.css +227 -0
- package/console/components.jsx +167 -0
- package/console/console.css +1666 -0
- package/console/data.jsx +350 -0
- package/console/index.html +31 -0
- package/console/screens/databases.jsx +5 -0
- package/console/screens/health.jsx +5 -0
- package/console/screens/ingress.jsx +5 -0
- package/console/screens/optimizer.jsx +5 -0
- package/console/screens/rlm-sim.jsx +5 -0
- package/console/screens/rlm-trace.jsx +5 -0
- package/console/screens/security.jsx +5 -0
- package/console/screens/settings.jsx +611 -0
- package/console/screens/sql.jsx +5 -0
- package/console/screens/sync.jsx +5 -0
- package/console/screens/tables.jsx +5 -0
- package/console/tweaks-panel.jsx +425 -0
- package/package.json +11 -1
- package/src/cli-config.cjs +310 -0
- package/src/cli-install.cjs +98 -11
- package/src/cli-restart.cjs +228 -0
- package/src/cli-ui.cjs +580 -0
- package/src/cluster.js +43 -38
- package/src/postgres.js +141 -19
- package/src/settings-loader.cjs +235 -0
- package/src/settings-migrate.cjs +212 -0
- package/src/settings-pg-args.cjs +146 -0
- package/src/settings-schema.cjs +422 -0
- package/src/settings-validator.cjs +416 -0
- package/src/settings-writer.cjs +288 -0
- package/.claude/context/windows-debug.md +0 -119
- package/.genie/AGENTS.md +0 -15
- package/.genie/agents/README.md +0 -110
- package/.genie/agents/analyze.md +0 -176
- package/.genie/agents/forge.md +0 -290
- package/.genie/agents/garbage-cleaner.md +0 -324
- package/.genie/agents/garbage-collector.md +0 -596
- package/.genie/agents/github-issue-gc.md +0 -618
- package/.genie/agents/review.md +0 -380
- package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
- package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
- package/.genie/agents/semantic-analyzer.md +0 -101
- package/.genie/agents/update.md +0 -182
- package/.genie/agents/wish.md +0 -357
- package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
- package/.genie/code/AGENTS.md +0 -694
- package/.genie/code/agents/audit/risk.md +0 -173
- package/.genie/code/agents/audit/security.md +0 -189
- package/.genie/code/agents/audit.md +0 -145
- package/.genie/code/agents/challenge.md +0 -230
- package/.genie/code/agents/change-reviewer.md +0 -295
- package/.genie/code/agents/code-garbage-collector.md +0 -425
- package/.genie/code/agents/code-quality.md +0 -410
- package/.genie/code/agents/commit-suggester.md +0 -255
- package/.genie/code/agents/commit.md +0 -124
- package/.genie/code/agents/consensus.md +0 -204
- package/.genie/code/agents/daily-standup.md +0 -722
- package/.genie/code/agents/docgen.md +0 -48
- package/.genie/code/agents/explore.md +0 -79
- package/.genie/code/agents/fix.md +0 -100
- package/.genie/code/agents/git/commit-advisory.md +0 -219
- package/.genie/code/agents/git/workflows/issue.md +0 -244
- package/.genie/code/agents/git/workflows/pr.md +0 -179
- package/.genie/code/agents/git/workflows/release.md +0 -460
- package/.genie/code/agents/git/workflows/report.md +0 -342
- package/.genie/code/agents/git.md +0 -432
- package/.genie/code/agents/implementor.md +0 -161
- package/.genie/code/agents/install.md +0 -515
- package/.genie/code/agents/issue-creator.md +0 -344
- package/.genie/code/agents/polish.md +0 -116
- package/.genie/code/agents/qa.md +0 -653
- package/.genie/code/agents/refactor.md +0 -294
- package/.genie/code/agents/release.md +0 -1129
- package/.genie/code/agents/roadmap.md +0 -885
- package/.genie/code/agents/tests.md +0 -557
- package/.genie/code/agents/tracer.md +0 -50
- package/.genie/code/agents/update/upstream-update.md +0 -85
- package/.genie/code/agents/update/versions/generic-update.md +0 -305
- package/.genie/code/agents/vibe.md +0 -1317
- package/.genie/code/spells/agent-configuration.md +0 -58
- package/.genie/code/spells/automated-rc-publishing.md +0 -106
- package/.genie/code/spells/branch-tracker-guidance.md +0 -28
- package/.genie/code/spells/debug.md +0 -320
- package/.genie/code/spells/emoji-naming-convention.md +0 -303
- package/.genie/code/spells/evidence-storage.md +0 -26
- package/.genie/code/spells/file-naming-rules.md +0 -35
- package/.genie/code/spells/forge-code-blueprints.md +0 -195
- package/.genie/code/spells/genie-integration.md +0 -153
- package/.genie/code/spells/publishing-protocol.md +0 -61
- package/.genie/code/spells/team-consultation-protocol.md +0 -284
- package/.genie/code/spells/tool-requirements.md +0 -20
- package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
- package/.genie/code/teams/tech-council/council.md +0 -328
- package/.genie/code/teams/tech-council/jt.md +0 -352
- package/.genie/code/teams/tech-council/nayr.md +0 -305
- package/.genie/code/teams/tech-council/oettam.md +0 -375
- package/.genie/neurons/README.md +0 -193
- package/.genie/neurons/forge.md +0 -106
- package/.genie/neurons/genie.md +0 -63
- package/.genie/neurons/review.md +0 -106
- package/.genie/neurons/wish.md +0 -104
- package/.genie/product/README.md +0 -20
- package/.genie/product/cli-automation.md +0 -359
- package/.genie/product/environment.md +0 -60
- package/.genie/product/mission.md +0 -60
- package/.genie/product/roadmap.md +0 -44
- package/.genie/product/tech-stack.md +0 -34
- package/.genie/product/templates/context-template.md +0 -218
- package/.genie/product/templates/qa-done-report-template.md +0 -68
- package/.genie/product/templates/review-report-template.md +0 -89
- package/.genie/product/templates/wish-template.md +0 -120
- package/.genie/scripts/helpers/analyze-commit.js +0 -195
- package/.genie/scripts/helpers/bullet-counter.js +0 -194
- package/.genie/scripts/helpers/bullet-find.js +0 -289
- package/.genie/scripts/helpers/bullet-id.js +0 -244
- package/.genie/scripts/helpers/check-secrets.js +0 -237
- package/.genie/scripts/helpers/count-tokens.js +0 -200
- package/.genie/scripts/helpers/create-frontmatter.js +0 -456
- package/.genie/scripts/helpers/detect-markers.js +0 -293
- package/.genie/scripts/helpers/detect-todos.js +0 -267
- package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
- package/.genie/scripts/helpers/embeddings.js +0 -344
- package/.genie/scripts/helpers/find-empty-sections.js +0 -158
- package/.genie/scripts/helpers/index.js +0 -319
- package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
- package/.genie/scripts/helpers/validate-links.js +0 -207
- package/.genie/scripts/helpers/validate-paths.js +0 -373
- package/.genie/spells/README.md +0 -9
- package/.genie/spells/ace-protocol.md +0 -118
- package/.genie/spells/ask-one-at-a-time.md +0 -175
- package/.genie/spells/backup-analyzer.md +0 -542
- package/.genie/spells/blocker.md +0 -12
- package/.genie/spells/break-things-move-fast.md +0 -56
- package/.genie/spells/context-candidates.md +0 -72
- package/.genie/spells/context-critic.md +0 -51
- package/.genie/spells/defer-to-expertise.md +0 -278
- package/.genie/spells/delegate-dont-do.md +0 -292
- package/.genie/spells/error-investigation-protocol.md +0 -328
- package/.genie/spells/evidence-based-completion.md +0 -273
- package/.genie/spells/experiment.md +0 -65
- package/.genie/spells/file-creation-protocol.md +0 -229
- package/.genie/spells/forge-integration.md +0 -281
- package/.genie/spells/forge-orchestration.md +0 -514
- package/.genie/spells/gather-context.md +0 -18
- package/.genie/spells/global-health-check.md +0 -34
- package/.genie/spells/global-noop-roundtrip.md +0 -25
- package/.genie/spells/install-genie.md +0 -1232
- package/.genie/spells/install.md +0 -82
- package/.genie/spells/investigate-before-commit.md +0 -112
- package/.genie/spells/know-yourself.md +0 -288
- package/.genie/spells/learn.md +0 -828
- package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
- package/.genie/spells/mcp-first.md +0 -124
- package/.genie/spells/multi-step-execution.md +0 -67
- package/.genie/spells/orchestration-boundary-protocol.md +0 -256
- package/.genie/spells/orchestrator-not-implementor.md +0 -189
- package/.genie/spells/prompt.md +0 -746
- package/.genie/spells/reflect.md +0 -404
- package/.genie/spells/routing-decision-matrix.md +0 -368
- package/.genie/spells/run-in-parallel.md +0 -12
- package/.genie/spells/session-state-updater-example.md +0 -196
- package/.genie/spells/session-state-updater.md +0 -220
- package/.genie/spells/track-long-running-tasks.md +0 -133
- package/.genie/spells/troubleshoot-infrastructure.md +0 -176
- package/.genie/spells/upgrade-genie.md +0 -415
- package/.genie/spells/url-presentation-protocol.md +0 -301
- package/.genie/spells/wish-initiation.md +0 -158
- package/.genie/spells/wish-issue-linkage.md +0 -410
- package/.genie/spells/wish-lifecycle.md +0 -100
- package/.genie/state/provider-status.json +0 -3
- package/.genie/state/version.json +0 -16
- package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
- package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
- package/.genie/wishes/pgserve-v2/WISH.md +0 -442
- package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
- package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
- package/.gitguardian.yaml +0 -29
- package/.gitguardianignore +0 -16
- package/.github/workflows/ci.yml +0 -122
- package/.github/workflows/release.yml +0 -289
- package/.github/workflows/version.yml +0 -228
- package/.husky/pre-commit +0 -2
- package/AGENTS.md +0 -433
- package/CLAUDE.md +0 -1
- package/Makefile +0 -285
- package/assets/icon.ico +0 -0
- package/bun.lock +0 -435
- package/bunfig.toml +0 -28
- package/ecosystem.config.cjs +0 -23
- package/eslint.config.js +0 -63
- package/examples/multi-tenant-demo.js +0 -104
- package/install.sh +0 -123
- package/knip.json +0 -9
- package/scripts/test-bun-self-heal.sh +0 -163
- package/scripts/test-npx.sh +0 -60
- package/tests/audit.test.js +0 -189
- package/tests/backpressure.test.js +0 -167
- package/tests/benchmarks/runner.js +0 -1197
- package/tests/benchmarks/vector-generator.js +0 -368
- package/tests/cli-install.test.js +0 -322
- package/tests/control-db.test.js +0 -285
- package/tests/daemon-args.test.js +0 -86
- package/tests/daemon-control.test.js +0 -171
- package/tests/daemon-fingerprint-integration.test.js +0 -111
- package/tests/daemon-pr24-regression.test.js +0 -198
- package/tests/fingerprint.test.js +0 -263
- package/tests/fixtures/240-orphan-seed.sql +0 -30
- package/tests/multi-tenant.test.js +0 -374
- package/tests/orphan-cleanup.test.js +0 -390
- package/tests/pg-version-regex.test.js +0 -129
- package/tests/quick-bench.js +0 -135
- package/tests/router-handshake-retry.test.js +0 -119
- package/tests/router-handshake-watchdog.test.js +0 -110
- package/tests/sdk.test.js +0 -71
- package/tests/stale-postmaster-pid.test.js +0 -85
- package/tests/stress-test.js +0 -439
- package/tests/sync-perf-test.js +0 -150
- package/tests/tcp-listen.test.js +0 -368
- package/tests/tenancy.test.js +0 -403
- package/tests/wrapper-supervision.test.js +0 -107
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Backend connect retry with 57P03 fallback
|
|
3
|
-
*
|
|
4
|
-
* Verifies the handshake-time backend-connect path:
|
|
5
|
-
* 1. First connect succeeds → returns the socket (no retry).
|
|
6
|
-
* 2. First connect fails, retry succeeds → returns the retry socket
|
|
7
|
-
* (after the documented 200ms backoff).
|
|
8
|
-
* 3. Both attempts fail → throws the second error.
|
|
9
|
-
* 4. The 57P03 ErrorResponse frame is well-formed Postgres wire bytes
|
|
10
|
-
* (libpq parses it cleanly).
|
|
11
|
-
*
|
|
12
|
-
* The retry helper is unexported (module-private) — we re-implement
|
|
13
|
-
* the assertion against the same `Bun.connect` injection seam by
|
|
14
|
-
* stubbing `Bun.connect` for the duration of each test.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { test, expect, mock } from 'bun:test';
|
|
18
|
-
import { buildErrorResponse } from '../src/protocol.js';
|
|
19
|
-
|
|
20
|
-
test('57P03 ErrorResponse frame is well-formed', () => {
|
|
21
|
-
const frame = buildErrorResponse({
|
|
22
|
-
severity: 'FATAL',
|
|
23
|
-
sqlstate: '57P03',
|
|
24
|
-
message: 'backend unavailable, retry shortly',
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Postgres wire: type byte 'E' (0x45), then 4-byte length (network order),
|
|
28
|
-
// then null-terminated field strings, then a trailing null byte.
|
|
29
|
-
expect(frame[0]).toBe(0x45);
|
|
30
|
-
|
|
31
|
-
const length = frame.readUInt32BE(1);
|
|
32
|
-
// Length includes itself (4 bytes) + the body. Frame total = 1 (type) + length.
|
|
33
|
-
expect(frame.length).toBe(1 + length);
|
|
34
|
-
|
|
35
|
-
// Find the SQLSTATE field marker (`C` = 0x43).
|
|
36
|
-
const body = frame.subarray(5).toString('latin1');
|
|
37
|
-
expect(body).toContain('C57P03'); // C + sqlstate value
|
|
38
|
-
expect(body).toContain('SFATAL'); // S + severity
|
|
39
|
-
expect(body).toContain('Mbackend unavailable, retry shortly');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('Bun.connect retry: first attempt succeeds → no retry', async () => {
|
|
43
|
-
const realConnect = Bun.connect;
|
|
44
|
-
let attempts = 0;
|
|
45
|
-
const fakeSocket = { ok: true };
|
|
46
|
-
Bun.connect = mock(async () => {
|
|
47
|
-
attempts++;
|
|
48
|
-
return fakeSocket;
|
|
49
|
-
});
|
|
50
|
-
try {
|
|
51
|
-
// Inline the same shape as connectBackendWithRetry for an integration-style
|
|
52
|
-
// assertion that doesn't require exporting a private helper.
|
|
53
|
-
const tryOnce = () => Bun.connect({ hostname: '127.0.0.1', port: 0, socket: {} });
|
|
54
|
-
let result;
|
|
55
|
-
try {
|
|
56
|
-
result = await tryOnce();
|
|
57
|
-
} catch {
|
|
58
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
59
|
-
result = await tryOnce();
|
|
60
|
-
}
|
|
61
|
-
expect(result).toBe(fakeSocket);
|
|
62
|
-
expect(attempts).toBe(1);
|
|
63
|
-
} finally {
|
|
64
|
-
Bun.connect = realConnect;
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('Bun.connect retry: first fails, second succeeds → exactly 2 attempts', async () => {
|
|
69
|
-
const realConnect = Bun.connect;
|
|
70
|
-
let attempts = 0;
|
|
71
|
-
const fakeSocket = { ok: true };
|
|
72
|
-
Bun.connect = mock(async () => {
|
|
73
|
-
attempts++;
|
|
74
|
-
if (attempts === 1) throw new Error('ECONNREFUSED');
|
|
75
|
-
return fakeSocket;
|
|
76
|
-
});
|
|
77
|
-
try {
|
|
78
|
-
const tryOnce = () => Bun.connect({ hostname: '127.0.0.1', port: 0, socket: {} });
|
|
79
|
-
let result;
|
|
80
|
-
try {
|
|
81
|
-
result = await tryOnce();
|
|
82
|
-
} catch {
|
|
83
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
84
|
-
result = await tryOnce();
|
|
85
|
-
}
|
|
86
|
-
expect(result).toBe(fakeSocket);
|
|
87
|
-
expect(attempts).toBe(2);
|
|
88
|
-
} finally {
|
|
89
|
-
Bun.connect = realConnect;
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('Bun.connect retry: both attempts fail → final error propagates', async () => {
|
|
94
|
-
const realConnect = Bun.connect;
|
|
95
|
-
let attempts = 0;
|
|
96
|
-
Bun.connect = mock(async () => {
|
|
97
|
-
attempts++;
|
|
98
|
-
throw new Error(`ECONNREFUSED-${attempts}`);
|
|
99
|
-
});
|
|
100
|
-
try {
|
|
101
|
-
const tryOnce = () => Bun.connect({ hostname: '127.0.0.1', port: 0, socket: {} });
|
|
102
|
-
let final;
|
|
103
|
-
try {
|
|
104
|
-
try {
|
|
105
|
-
await tryOnce();
|
|
106
|
-
} catch {
|
|
107
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
108
|
-
await tryOnce();
|
|
109
|
-
}
|
|
110
|
-
} catch (err) {
|
|
111
|
-
final = err;
|
|
112
|
-
}
|
|
113
|
-
expect(final).toBeDefined();
|
|
114
|
-
expect(final.message).toBe('ECONNREFUSED-2'); // Second-attempt message wins
|
|
115
|
-
expect(attempts).toBe(2);
|
|
116
|
-
} finally {
|
|
117
|
-
Bun.connect = realConnect;
|
|
118
|
-
}
|
|
119
|
-
});
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Handshake watchdog: peers that connect and never complete the postgres
|
|
3
|
-
* StartupMessage are forcibly closed past `PGSERVE_HANDSHAKE_DEADLINE_MS`.
|
|
4
|
-
*
|
|
5
|
-
* Regression coverage: pgserve#45 documented file-descriptor leak where
|
|
6
|
-
* peers piled up indefinitely in `state.handshakeComplete=false`.
|
|
7
|
-
*
|
|
8
|
-
* The tests drive `_sweepStuckHandshakes()` directly via a synthetic
|
|
9
|
-
* connection record. This avoids spawning a real postgres backend, which
|
|
10
|
-
* is unnecessary when we only want to assert the sweep policy and timer
|
|
11
|
-
* lifecycle.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { PgserveDaemon } from '../src/daemon.js';
|
|
15
|
-
import { test, expect } from 'bun:test';
|
|
16
|
-
import fs from 'fs';
|
|
17
|
-
import path from 'path';
|
|
18
|
-
import os from 'os';
|
|
19
|
-
|
|
20
|
-
function quietLogger() {
|
|
21
|
-
return {
|
|
22
|
-
info: () => {}, warn: () => {}, error: () => {}, debug: () => {},
|
|
23
|
-
child: () => quietLogger(),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function makeDaemon(opts = {}) {
|
|
28
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgserve-watchdog-'));
|
|
29
|
-
const daemon = new PgserveDaemon({
|
|
30
|
-
baseDir: dir,
|
|
31
|
-
logger: quietLogger(),
|
|
32
|
-
enforcementDisabled: true,
|
|
33
|
-
...opts,
|
|
34
|
-
});
|
|
35
|
-
return { daemon, dir };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function fakeSocket() {
|
|
39
|
-
const calls = [];
|
|
40
|
-
return {
|
|
41
|
-
end: () => { calls.push('end'); },
|
|
42
|
-
pause: () => {}, resume: () => {}, write: () => 0,
|
|
43
|
-
_calls: calls,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
test('handshakeDeadlineMs falls back to 30000 when env unset', () => {
|
|
48
|
-
delete process.env.PGSERVE_HANDSHAKE_DEADLINE_MS;
|
|
49
|
-
const { daemon, dir } = makeDaemon();
|
|
50
|
-
expect(daemon.handshakeDeadlineMs).toBe(30000);
|
|
51
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('handshakeDeadlineMs honours PGSERVE_HANDSHAKE_DEADLINE_MS env', () => {
|
|
55
|
-
process.env.PGSERVE_HANDSHAKE_DEADLINE_MS = '2000';
|
|
56
|
-
try {
|
|
57
|
-
const { daemon, dir } = makeDaemon();
|
|
58
|
-
expect(daemon.handshakeDeadlineMs).toBe(2000);
|
|
59
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
60
|
-
} finally {
|
|
61
|
-
delete process.env.PGSERVE_HANDSHAKE_DEADLINE_MS;
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('_sweepStuckHandshakes closes pre-handshake sockets past deadline', () => {
|
|
66
|
-
const { daemon, dir } = makeDaemon({ handshakeDeadlineMs: 100 });
|
|
67
|
-
const sock = fakeSocket();
|
|
68
|
-
const stuckAt = Date.now() - 500; // older than 100ms deadline
|
|
69
|
-
daemon.connections.add(sock);
|
|
70
|
-
daemon.socketState.set(sock, { handshakeComplete: false, acceptedAt: stuckAt });
|
|
71
|
-
const closed = daemon._sweepStuckHandshakes();
|
|
72
|
-
expect(closed).toBe(1);
|
|
73
|
-
expect(sock._calls).toContain('end');
|
|
74
|
-
expect(daemon.connections.has(sock)).toBe(false);
|
|
75
|
-
expect(daemon.socketState.has(sock)).toBe(false);
|
|
76
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('_sweepStuckHandshakes leaves fresh pre-handshake sockets alone', () => {
|
|
80
|
-
const { daemon, dir } = makeDaemon({ handshakeDeadlineMs: 30000 });
|
|
81
|
-
const sock = fakeSocket();
|
|
82
|
-
daemon.connections.add(sock);
|
|
83
|
-
daemon.socketState.set(sock, { handshakeComplete: false, acceptedAt: Date.now() });
|
|
84
|
-
const closed = daemon._sweepStuckHandshakes();
|
|
85
|
-
expect(closed).toBe(0);
|
|
86
|
-
expect(sock._calls).not.toContain('end');
|
|
87
|
-
expect(daemon.connections.has(sock)).toBe(true);
|
|
88
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('_sweepStuckHandshakes leaves completed-handshake sockets alone even past deadline', () => {
|
|
92
|
-
const { daemon, dir } = makeDaemon({ handshakeDeadlineMs: 100 });
|
|
93
|
-
const sock = fakeSocket();
|
|
94
|
-
daemon.connections.add(sock);
|
|
95
|
-
daemon.socketState.set(sock, { handshakeComplete: true, acceptedAt: Date.now() - 5000 });
|
|
96
|
-
const closed = daemon._sweepStuckHandshakes();
|
|
97
|
-
expect(closed).toBe(0);
|
|
98
|
-
expect(sock._calls).not.toContain('end');
|
|
99
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('handshakeSweepIntervalMs is bounded sensibly relative to deadline', () => {
|
|
103
|
-
const { daemon, dir } = makeDaemon({
|
|
104
|
-
handshakeDeadlineMs: 200,
|
|
105
|
-
handshakeSweepIntervalMs: 50,
|
|
106
|
-
});
|
|
107
|
-
// Sweep interval cannot drop below 1s safety floor.
|
|
108
|
-
expect(daemon.handshakeSweepIntervalMs).toBe(1000);
|
|
109
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
110
|
-
});
|
package/tests/sdk.test.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
buildDaemonArgs,
|
|
8
|
-
daemonClientOptions,
|
|
9
|
-
probeDaemon,
|
|
10
|
-
resolveLibpqCompatPath,
|
|
11
|
-
resolvePidLockPath,
|
|
12
|
-
} from '../src/index.js';
|
|
13
|
-
|
|
14
|
-
function makeDir(tag) {
|
|
15
|
-
const dir = path.join(os.tmpdir(), `pgserve-sdk-${tag}-${process.pid}-${Date.now()}`);
|
|
16
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
17
|
-
return dir;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe('SDK daemon helpers', () => {
|
|
21
|
-
test('daemonClientOptions returns libpq socket connection settings', () => {
|
|
22
|
-
expect(daemonClientOptions({ controlSocketDir: '/tmp/pgserve' })).toEqual({
|
|
23
|
-
host: '/tmp/pgserve',
|
|
24
|
-
port: 5432,
|
|
25
|
-
database: 'postgres',
|
|
26
|
-
username: 'postgres',
|
|
27
|
-
password: '',
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('buildDaemonArgs exposes persistent, pgvector, and listen options', () => {
|
|
32
|
-
expect(buildDaemonArgs({
|
|
33
|
-
dataDir: '/var/lib/pgserve',
|
|
34
|
-
logLevel: 'warn',
|
|
35
|
-
pgvector: true,
|
|
36
|
-
listens: ['127.0.0.1:15432'],
|
|
37
|
-
})).toEqual([
|
|
38
|
-
'daemon',
|
|
39
|
-
'--data',
|
|
40
|
-
'/var/lib/pgserve',
|
|
41
|
-
'--log',
|
|
42
|
-
'warn',
|
|
43
|
-
'--pgvector',
|
|
44
|
-
'--listen',
|
|
45
|
-
'127.0.0.1:15432',
|
|
46
|
-
]);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('probeDaemon reports missing and stale daemon state', () => {
|
|
50
|
-
const dir = makeDir('probe');
|
|
51
|
-
try {
|
|
52
|
-
expect(probeDaemon({ controlSocketDir: dir })).toMatchObject({
|
|
53
|
-
running: false,
|
|
54
|
-
pid: null,
|
|
55
|
-
reason: 'no daemon',
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
fs.writeFileSync(resolvePidLockPath(dir), '999999', { mode: 0o600 });
|
|
59
|
-
fs.writeFileSync(path.join(dir, 'control.sock'), '');
|
|
60
|
-
fs.symlinkSync('control.sock', resolveLibpqCompatPath(dir));
|
|
61
|
-
expect(probeDaemon({ controlSocketDir: dir })).toMatchObject({
|
|
62
|
-
running: false,
|
|
63
|
-
pid: null,
|
|
64
|
-
libpqSocketPresent: true,
|
|
65
|
-
reason: 'stale pid',
|
|
66
|
-
});
|
|
67
|
-
} finally {
|
|
68
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stale postmaster.pid cleanup
|
|
3
|
-
*
|
|
4
|
-
* Verifies that PostgresManager._ensureNoStalePostmasterLock removes
|
|
5
|
-
* a postmaster.pid file whose recorded PID is no longer alive, and
|
|
6
|
-
* leaves alone a postmaster.pid whose recorded PID is alive.
|
|
7
|
-
*
|
|
8
|
-
* Regression coverage: postgres refuses to start when postmaster.pid
|
|
9
|
-
* exists, even if the writer crashed. After unclean shutdowns this
|
|
10
|
-
* required manual `rm` to recover.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { PostgresManager } from '../src/postgres.js';
|
|
14
|
-
import { test, expect } from 'bun:test';
|
|
15
|
-
import fs from 'fs';
|
|
16
|
-
import path from 'path';
|
|
17
|
-
import os from 'os';
|
|
18
|
-
|
|
19
|
-
function makeMgr(dataDir) {
|
|
20
|
-
const mgr = new PostgresManager({ dataDir });
|
|
21
|
-
mgr.databaseDir = dataDir;
|
|
22
|
-
mgr.logger = {
|
|
23
|
-
info: () => {},
|
|
24
|
-
warn: () => {},
|
|
25
|
-
error: () => {},
|
|
26
|
-
debug: () => {},
|
|
27
|
-
};
|
|
28
|
-
return mgr;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function makePidFile(dir, contents) {
|
|
32
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
33
|
-
const pidFile = path.join(dir, 'postmaster.pid');
|
|
34
|
-
fs.writeFileSync(pidFile, contents, 'utf-8');
|
|
35
|
-
return pidFile;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
test('removes postmaster.pid when recorded PID is dead', async () => {
|
|
39
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgserve-stale-'));
|
|
40
|
-
try {
|
|
41
|
-
// PID 999999999 will not exist on any sane system
|
|
42
|
-
const pidFile = makePidFile(dir, '999999999\n/some/data\n123\n');
|
|
43
|
-
const mgr = makeMgr(dir);
|
|
44
|
-
await mgr._ensureNoStalePostmasterLock();
|
|
45
|
-
expect(fs.existsSync(pidFile)).toBe(false);
|
|
46
|
-
} finally {
|
|
47
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('keeps postmaster.pid when recorded PID is the current (alive) process', async () => {
|
|
52
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgserve-alive-'));
|
|
53
|
-
try {
|
|
54
|
-
const pidFile = makePidFile(dir, `${process.pid}\n/some/data\n123\n`);
|
|
55
|
-
const mgr = makeMgr(dir);
|
|
56
|
-
await mgr._ensureNoStalePostmasterLock();
|
|
57
|
-
expect(fs.existsSync(pidFile)).toBe(true);
|
|
58
|
-
} finally {
|
|
59
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('removes postmaster.pid when first line is unparseable', async () => {
|
|
64
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgserve-garbage-'));
|
|
65
|
-
try {
|
|
66
|
-
const pidFile = makePidFile(dir, 'garbage\nnot-a-pid\n');
|
|
67
|
-
const mgr = makeMgr(dir);
|
|
68
|
-
await mgr._ensureNoStalePostmasterLock();
|
|
69
|
-
expect(fs.existsSync(pidFile)).toBe(false);
|
|
70
|
-
} finally {
|
|
71
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test('no-ops when postmaster.pid does not exist', async () => {
|
|
76
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgserve-missing-'));
|
|
77
|
-
try {
|
|
78
|
-
const mgr = makeMgr(dir);
|
|
79
|
-
// Should resolve without throwing
|
|
80
|
-
await mgr._ensureNoStalePostmasterLock();
|
|
81
|
-
expect(fs.existsSync(path.join(dir, 'postmaster.pid'))).toBe(false);
|
|
82
|
-
} finally {
|
|
83
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
84
|
-
}
|
|
85
|
-
});
|