pgserve 2.1.2 → 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.
Files changed (227) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +105 -1
  3. package/bin/autopg-wrapper.cjs +16 -0
  4. package/bin/pgserve-wrapper.cjs +31 -6
  5. package/bin/postgres-server.js +80 -7
  6. package/console/README.md +131 -0
  7. package/console/api.js +173 -0
  8. package/console/app.jsx +483 -0
  9. package/console/colors_and_type.css +227 -0
  10. package/console/components.jsx +167 -0
  11. package/console/console.css +1666 -0
  12. package/console/data.jsx +350 -0
  13. package/console/index.html +31 -0
  14. package/console/screens/databases.jsx +5 -0
  15. package/console/screens/health.jsx +5 -0
  16. package/console/screens/ingress.jsx +5 -0
  17. package/console/screens/optimizer.jsx +5 -0
  18. package/console/screens/rlm-sim.jsx +5 -0
  19. package/console/screens/rlm-trace.jsx +5 -0
  20. package/console/screens/security.jsx +5 -0
  21. package/console/screens/settings.jsx +611 -0
  22. package/console/screens/sql.jsx +5 -0
  23. package/console/screens/sync.jsx +5 -0
  24. package/console/screens/tables.jsx +5 -0
  25. package/console/tweaks-panel.jsx +425 -0
  26. package/package.json +11 -1
  27. package/src/cli-config.cjs +310 -0
  28. package/src/cli-install.cjs +98 -11
  29. package/src/cli-restart.cjs +228 -0
  30. package/src/cli-ui.cjs +580 -0
  31. package/src/cluster.js +43 -38
  32. package/src/postgres.js +141 -19
  33. package/src/settings-loader.cjs +235 -0
  34. package/src/settings-migrate.cjs +212 -0
  35. package/src/settings-pg-args.cjs +146 -0
  36. package/src/settings-schema.cjs +422 -0
  37. package/src/settings-validator.cjs +416 -0
  38. package/src/settings-writer.cjs +288 -0
  39. package/.claude/context/windows-debug.md +0 -119
  40. package/.genie/AGENTS.md +0 -15
  41. package/.genie/agents/README.md +0 -110
  42. package/.genie/agents/analyze.md +0 -176
  43. package/.genie/agents/forge.md +0 -290
  44. package/.genie/agents/garbage-cleaner.md +0 -324
  45. package/.genie/agents/garbage-collector.md +0 -596
  46. package/.genie/agents/github-issue-gc.md +0 -618
  47. package/.genie/agents/review.md +0 -380
  48. package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
  49. package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
  50. package/.genie/agents/semantic-analyzer.md +0 -101
  51. package/.genie/agents/update.md +0 -182
  52. package/.genie/agents/wish.md +0 -357
  53. package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
  54. package/.genie/code/AGENTS.md +0 -694
  55. package/.genie/code/agents/audit/risk.md +0 -173
  56. package/.genie/code/agents/audit/security.md +0 -189
  57. package/.genie/code/agents/audit.md +0 -145
  58. package/.genie/code/agents/challenge.md +0 -230
  59. package/.genie/code/agents/change-reviewer.md +0 -295
  60. package/.genie/code/agents/code-garbage-collector.md +0 -425
  61. package/.genie/code/agents/code-quality.md +0 -410
  62. package/.genie/code/agents/commit-suggester.md +0 -255
  63. package/.genie/code/agents/commit.md +0 -124
  64. package/.genie/code/agents/consensus.md +0 -204
  65. package/.genie/code/agents/daily-standup.md +0 -722
  66. package/.genie/code/agents/docgen.md +0 -48
  67. package/.genie/code/agents/explore.md +0 -79
  68. package/.genie/code/agents/fix.md +0 -100
  69. package/.genie/code/agents/git/commit-advisory.md +0 -219
  70. package/.genie/code/agents/git/workflows/issue.md +0 -244
  71. package/.genie/code/agents/git/workflows/pr.md +0 -179
  72. package/.genie/code/agents/git/workflows/release.md +0 -460
  73. package/.genie/code/agents/git/workflows/report.md +0 -342
  74. package/.genie/code/agents/git.md +0 -432
  75. package/.genie/code/agents/implementor.md +0 -161
  76. package/.genie/code/agents/install.md +0 -515
  77. package/.genie/code/agents/issue-creator.md +0 -344
  78. package/.genie/code/agents/polish.md +0 -116
  79. package/.genie/code/agents/qa.md +0 -653
  80. package/.genie/code/agents/refactor.md +0 -294
  81. package/.genie/code/agents/release.md +0 -1129
  82. package/.genie/code/agents/roadmap.md +0 -885
  83. package/.genie/code/agents/tests.md +0 -557
  84. package/.genie/code/agents/tracer.md +0 -50
  85. package/.genie/code/agents/update/upstream-update.md +0 -85
  86. package/.genie/code/agents/update/versions/generic-update.md +0 -305
  87. package/.genie/code/agents/vibe.md +0 -1317
  88. package/.genie/code/spells/agent-configuration.md +0 -58
  89. package/.genie/code/spells/automated-rc-publishing.md +0 -106
  90. package/.genie/code/spells/branch-tracker-guidance.md +0 -28
  91. package/.genie/code/spells/debug.md +0 -320
  92. package/.genie/code/spells/emoji-naming-convention.md +0 -303
  93. package/.genie/code/spells/evidence-storage.md +0 -26
  94. package/.genie/code/spells/file-naming-rules.md +0 -35
  95. package/.genie/code/spells/forge-code-blueprints.md +0 -195
  96. package/.genie/code/spells/genie-integration.md +0 -153
  97. package/.genie/code/spells/publishing-protocol.md +0 -61
  98. package/.genie/code/spells/team-consultation-protocol.md +0 -284
  99. package/.genie/code/spells/tool-requirements.md +0 -20
  100. package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
  101. package/.genie/code/teams/tech-council/council.md +0 -328
  102. package/.genie/code/teams/tech-council/jt.md +0 -352
  103. package/.genie/code/teams/tech-council/nayr.md +0 -305
  104. package/.genie/code/teams/tech-council/oettam.md +0 -375
  105. package/.genie/neurons/README.md +0 -193
  106. package/.genie/neurons/forge.md +0 -106
  107. package/.genie/neurons/genie.md +0 -63
  108. package/.genie/neurons/review.md +0 -106
  109. package/.genie/neurons/wish.md +0 -104
  110. package/.genie/product/README.md +0 -20
  111. package/.genie/product/cli-automation.md +0 -359
  112. package/.genie/product/environment.md +0 -60
  113. package/.genie/product/mission.md +0 -60
  114. package/.genie/product/roadmap.md +0 -44
  115. package/.genie/product/tech-stack.md +0 -34
  116. package/.genie/product/templates/context-template.md +0 -218
  117. package/.genie/product/templates/qa-done-report-template.md +0 -68
  118. package/.genie/product/templates/review-report-template.md +0 -89
  119. package/.genie/product/templates/wish-template.md +0 -120
  120. package/.genie/scripts/helpers/analyze-commit.js +0 -195
  121. package/.genie/scripts/helpers/bullet-counter.js +0 -194
  122. package/.genie/scripts/helpers/bullet-find.js +0 -289
  123. package/.genie/scripts/helpers/bullet-id.js +0 -244
  124. package/.genie/scripts/helpers/check-secrets.js +0 -237
  125. package/.genie/scripts/helpers/count-tokens.js +0 -200
  126. package/.genie/scripts/helpers/create-frontmatter.js +0 -456
  127. package/.genie/scripts/helpers/detect-markers.js +0 -293
  128. package/.genie/scripts/helpers/detect-todos.js +0 -267
  129. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
  130. package/.genie/scripts/helpers/embeddings.js +0 -344
  131. package/.genie/scripts/helpers/find-empty-sections.js +0 -158
  132. package/.genie/scripts/helpers/index.js +0 -319
  133. package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
  134. package/.genie/scripts/helpers/validate-links.js +0 -207
  135. package/.genie/scripts/helpers/validate-paths.js +0 -373
  136. package/.genie/spells/README.md +0 -9
  137. package/.genie/spells/ace-protocol.md +0 -118
  138. package/.genie/spells/ask-one-at-a-time.md +0 -175
  139. package/.genie/spells/backup-analyzer.md +0 -542
  140. package/.genie/spells/blocker.md +0 -12
  141. package/.genie/spells/break-things-move-fast.md +0 -56
  142. package/.genie/spells/context-candidates.md +0 -72
  143. package/.genie/spells/context-critic.md +0 -51
  144. package/.genie/spells/defer-to-expertise.md +0 -278
  145. package/.genie/spells/delegate-dont-do.md +0 -292
  146. package/.genie/spells/error-investigation-protocol.md +0 -328
  147. package/.genie/spells/evidence-based-completion.md +0 -273
  148. package/.genie/spells/experiment.md +0 -65
  149. package/.genie/spells/file-creation-protocol.md +0 -229
  150. package/.genie/spells/forge-integration.md +0 -281
  151. package/.genie/spells/forge-orchestration.md +0 -514
  152. package/.genie/spells/gather-context.md +0 -18
  153. package/.genie/spells/global-health-check.md +0 -34
  154. package/.genie/spells/global-noop-roundtrip.md +0 -25
  155. package/.genie/spells/install-genie.md +0 -1232
  156. package/.genie/spells/install.md +0 -82
  157. package/.genie/spells/investigate-before-commit.md +0 -112
  158. package/.genie/spells/know-yourself.md +0 -288
  159. package/.genie/spells/learn.md +0 -828
  160. package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
  161. package/.genie/spells/mcp-first.md +0 -124
  162. package/.genie/spells/multi-step-execution.md +0 -67
  163. package/.genie/spells/orchestration-boundary-protocol.md +0 -256
  164. package/.genie/spells/orchestrator-not-implementor.md +0 -189
  165. package/.genie/spells/prompt.md +0 -746
  166. package/.genie/spells/reflect.md +0 -404
  167. package/.genie/spells/routing-decision-matrix.md +0 -368
  168. package/.genie/spells/run-in-parallel.md +0 -12
  169. package/.genie/spells/session-state-updater-example.md +0 -196
  170. package/.genie/spells/session-state-updater.md +0 -220
  171. package/.genie/spells/track-long-running-tasks.md +0 -133
  172. package/.genie/spells/troubleshoot-infrastructure.md +0 -176
  173. package/.genie/spells/upgrade-genie.md +0 -415
  174. package/.genie/spells/url-presentation-protocol.md +0 -301
  175. package/.genie/spells/wish-initiation.md +0 -158
  176. package/.genie/spells/wish-issue-linkage.md +0 -410
  177. package/.genie/spells/wish-lifecycle.md +0 -100
  178. package/.genie/state/provider-status.json +0 -3
  179. package/.genie/state/version.json +0 -16
  180. package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
  181. package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
  182. package/.genie/wishes/pgserve-v2/WISH.md +0 -442
  183. package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
  184. package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
  185. package/.gitguardian.yaml +0 -29
  186. package/.gitguardianignore +0 -16
  187. package/.github/workflows/ci.yml +0 -122
  188. package/.github/workflows/release.yml +0 -289
  189. package/.github/workflows/version.yml +0 -228
  190. package/.husky/pre-commit +0 -2
  191. package/AGENTS.md +0 -433
  192. package/CLAUDE.md +0 -1
  193. package/Makefile +0 -285
  194. package/assets/icon.ico +0 -0
  195. package/bun.lock +0 -435
  196. package/bunfig.toml +0 -28
  197. package/ecosystem.config.cjs +0 -23
  198. package/eslint.config.js +0 -63
  199. package/examples/multi-tenant-demo.js +0 -104
  200. package/install.sh +0 -123
  201. package/knip.json +0 -9
  202. package/scripts/test-bun-self-heal.sh +0 -163
  203. package/scripts/test-npx.sh +0 -60
  204. package/tests/audit.test.js +0 -189
  205. package/tests/backpressure.test.js +0 -167
  206. package/tests/benchmarks/runner.js +0 -1197
  207. package/tests/benchmarks/vector-generator.js +0 -368
  208. package/tests/cli-install.test.js +0 -322
  209. package/tests/control-db.test.js +0 -285
  210. package/tests/daemon-control.test.js +0 -171
  211. package/tests/daemon-fingerprint-integration.test.js +0 -111
  212. package/tests/daemon-pr24-regression.test.js +0 -198
  213. package/tests/fingerprint.test.js +0 -263
  214. package/tests/fixtures/240-orphan-seed.sql +0 -30
  215. package/tests/multi-tenant.test.js +0 -374
  216. package/tests/orphan-cleanup.test.js +0 -390
  217. package/tests/pg-version-regex.test.js +0 -129
  218. package/tests/quick-bench.js +0 -135
  219. package/tests/router-handshake-retry.test.js +0 -119
  220. package/tests/router-handshake-watchdog.test.js +0 -110
  221. package/tests/sdk.test.js +0 -71
  222. package/tests/stale-postmaster-pid.test.js +0 -85
  223. package/tests/stress-test.js +0 -439
  224. package/tests/sync-perf-test.js +0 -150
  225. package/tests/tcp-listen.test.js +0 -368
  226. package/tests/tenancy.test.js +0 -403
  227. 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
- });