pgserve 2.1.3 → 2.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.
Files changed (235) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +105 -1
  3. package/bin/autopg-wrapper.cjs +16 -0
  4. package/bin/pgserve-wrapper.cjs +32 -6
  5. package/bin/postgres-server.js +56 -0
  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 +14 -2
  27. package/scripts/postinstall.cjs +60 -0
  28. package/src/cli-config.cjs +310 -0
  29. package/src/cli-install.cjs +112 -11
  30. package/src/cli-restart.cjs +228 -0
  31. package/src/cli-ui.cjs +580 -0
  32. package/src/cluster.js +43 -38
  33. package/src/postgres.js +141 -19
  34. package/src/settings-loader.cjs +235 -0
  35. package/src/settings-migrate.cjs +212 -0
  36. package/src/settings-pg-args.cjs +146 -0
  37. package/src/settings-schema.cjs +422 -0
  38. package/src/settings-validator.cjs +416 -0
  39. package/src/settings-writer.cjs +288 -0
  40. package/src/upgrade/index.js +65 -0
  41. package/src/upgrade/runner.js +23 -0
  42. package/src/upgrade/steps/binary-cache-flush.js +67 -0
  43. package/src/upgrade/steps/consumer-signal.js +40 -0
  44. package/src/upgrade/steps/env-refresh.js +89 -0
  45. package/src/upgrade/steps/health-validate.js +53 -0
  46. package/src/upgrade/steps/plpgsql-resolve.js +66 -0
  47. package/src/upgrade/steps/port-reconcile.js +52 -0
  48. package/.claude/context/windows-debug.md +0 -119
  49. package/.genie/AGENTS.md +0 -15
  50. package/.genie/agents/README.md +0 -110
  51. package/.genie/agents/analyze.md +0 -176
  52. package/.genie/agents/forge.md +0 -290
  53. package/.genie/agents/garbage-cleaner.md +0 -324
  54. package/.genie/agents/garbage-collector.md +0 -596
  55. package/.genie/agents/github-issue-gc.md +0 -618
  56. package/.genie/agents/review.md +0 -380
  57. package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
  58. package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
  59. package/.genie/agents/semantic-analyzer.md +0 -101
  60. package/.genie/agents/update.md +0 -182
  61. package/.genie/agents/wish.md +0 -357
  62. package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
  63. package/.genie/code/AGENTS.md +0 -694
  64. package/.genie/code/agents/audit/risk.md +0 -173
  65. package/.genie/code/agents/audit/security.md +0 -189
  66. package/.genie/code/agents/audit.md +0 -145
  67. package/.genie/code/agents/challenge.md +0 -230
  68. package/.genie/code/agents/change-reviewer.md +0 -295
  69. package/.genie/code/agents/code-garbage-collector.md +0 -425
  70. package/.genie/code/agents/code-quality.md +0 -410
  71. package/.genie/code/agents/commit-suggester.md +0 -255
  72. package/.genie/code/agents/commit.md +0 -124
  73. package/.genie/code/agents/consensus.md +0 -204
  74. package/.genie/code/agents/daily-standup.md +0 -722
  75. package/.genie/code/agents/docgen.md +0 -48
  76. package/.genie/code/agents/explore.md +0 -79
  77. package/.genie/code/agents/fix.md +0 -100
  78. package/.genie/code/agents/git/commit-advisory.md +0 -219
  79. package/.genie/code/agents/git/workflows/issue.md +0 -244
  80. package/.genie/code/agents/git/workflows/pr.md +0 -179
  81. package/.genie/code/agents/git/workflows/release.md +0 -460
  82. package/.genie/code/agents/git/workflows/report.md +0 -342
  83. package/.genie/code/agents/git.md +0 -432
  84. package/.genie/code/agents/implementor.md +0 -161
  85. package/.genie/code/agents/install.md +0 -515
  86. package/.genie/code/agents/issue-creator.md +0 -344
  87. package/.genie/code/agents/polish.md +0 -116
  88. package/.genie/code/agents/qa.md +0 -653
  89. package/.genie/code/agents/refactor.md +0 -294
  90. package/.genie/code/agents/release.md +0 -1129
  91. package/.genie/code/agents/roadmap.md +0 -885
  92. package/.genie/code/agents/tests.md +0 -557
  93. package/.genie/code/agents/tracer.md +0 -50
  94. package/.genie/code/agents/update/upstream-update.md +0 -85
  95. package/.genie/code/agents/update/versions/generic-update.md +0 -305
  96. package/.genie/code/agents/vibe.md +0 -1317
  97. package/.genie/code/spells/agent-configuration.md +0 -58
  98. package/.genie/code/spells/automated-rc-publishing.md +0 -106
  99. package/.genie/code/spells/branch-tracker-guidance.md +0 -28
  100. package/.genie/code/spells/debug.md +0 -320
  101. package/.genie/code/spells/emoji-naming-convention.md +0 -303
  102. package/.genie/code/spells/evidence-storage.md +0 -26
  103. package/.genie/code/spells/file-naming-rules.md +0 -35
  104. package/.genie/code/spells/forge-code-blueprints.md +0 -195
  105. package/.genie/code/spells/genie-integration.md +0 -153
  106. package/.genie/code/spells/publishing-protocol.md +0 -61
  107. package/.genie/code/spells/team-consultation-protocol.md +0 -284
  108. package/.genie/code/spells/tool-requirements.md +0 -20
  109. package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
  110. package/.genie/code/teams/tech-council/council.md +0 -328
  111. package/.genie/code/teams/tech-council/jt.md +0 -352
  112. package/.genie/code/teams/tech-council/nayr.md +0 -305
  113. package/.genie/code/teams/tech-council/oettam.md +0 -375
  114. package/.genie/neurons/README.md +0 -193
  115. package/.genie/neurons/forge.md +0 -106
  116. package/.genie/neurons/genie.md +0 -63
  117. package/.genie/neurons/review.md +0 -106
  118. package/.genie/neurons/wish.md +0 -104
  119. package/.genie/product/README.md +0 -20
  120. package/.genie/product/cli-automation.md +0 -359
  121. package/.genie/product/environment.md +0 -60
  122. package/.genie/product/mission.md +0 -60
  123. package/.genie/product/roadmap.md +0 -44
  124. package/.genie/product/tech-stack.md +0 -34
  125. package/.genie/product/templates/context-template.md +0 -218
  126. package/.genie/product/templates/qa-done-report-template.md +0 -68
  127. package/.genie/product/templates/review-report-template.md +0 -89
  128. package/.genie/product/templates/wish-template.md +0 -120
  129. package/.genie/scripts/helpers/analyze-commit.js +0 -195
  130. package/.genie/scripts/helpers/bullet-counter.js +0 -194
  131. package/.genie/scripts/helpers/bullet-find.js +0 -289
  132. package/.genie/scripts/helpers/bullet-id.js +0 -244
  133. package/.genie/scripts/helpers/check-secrets.js +0 -237
  134. package/.genie/scripts/helpers/count-tokens.js +0 -200
  135. package/.genie/scripts/helpers/create-frontmatter.js +0 -456
  136. package/.genie/scripts/helpers/detect-markers.js +0 -293
  137. package/.genie/scripts/helpers/detect-todos.js +0 -267
  138. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
  139. package/.genie/scripts/helpers/embeddings.js +0 -344
  140. package/.genie/scripts/helpers/find-empty-sections.js +0 -158
  141. package/.genie/scripts/helpers/index.js +0 -319
  142. package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
  143. package/.genie/scripts/helpers/validate-links.js +0 -207
  144. package/.genie/scripts/helpers/validate-paths.js +0 -373
  145. package/.genie/spells/README.md +0 -9
  146. package/.genie/spells/ace-protocol.md +0 -118
  147. package/.genie/spells/ask-one-at-a-time.md +0 -175
  148. package/.genie/spells/backup-analyzer.md +0 -542
  149. package/.genie/spells/blocker.md +0 -12
  150. package/.genie/spells/break-things-move-fast.md +0 -56
  151. package/.genie/spells/context-candidates.md +0 -72
  152. package/.genie/spells/context-critic.md +0 -51
  153. package/.genie/spells/defer-to-expertise.md +0 -278
  154. package/.genie/spells/delegate-dont-do.md +0 -292
  155. package/.genie/spells/error-investigation-protocol.md +0 -328
  156. package/.genie/spells/evidence-based-completion.md +0 -273
  157. package/.genie/spells/experiment.md +0 -65
  158. package/.genie/spells/file-creation-protocol.md +0 -229
  159. package/.genie/spells/forge-integration.md +0 -281
  160. package/.genie/spells/forge-orchestration.md +0 -514
  161. package/.genie/spells/gather-context.md +0 -18
  162. package/.genie/spells/global-health-check.md +0 -34
  163. package/.genie/spells/global-noop-roundtrip.md +0 -25
  164. package/.genie/spells/install-genie.md +0 -1232
  165. package/.genie/spells/install.md +0 -82
  166. package/.genie/spells/investigate-before-commit.md +0 -112
  167. package/.genie/spells/know-yourself.md +0 -288
  168. package/.genie/spells/learn.md +0 -828
  169. package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
  170. package/.genie/spells/mcp-first.md +0 -124
  171. package/.genie/spells/multi-step-execution.md +0 -67
  172. package/.genie/spells/orchestration-boundary-protocol.md +0 -256
  173. package/.genie/spells/orchestrator-not-implementor.md +0 -189
  174. package/.genie/spells/prompt.md +0 -746
  175. package/.genie/spells/reflect.md +0 -404
  176. package/.genie/spells/routing-decision-matrix.md +0 -368
  177. package/.genie/spells/run-in-parallel.md +0 -12
  178. package/.genie/spells/session-state-updater-example.md +0 -196
  179. package/.genie/spells/session-state-updater.md +0 -220
  180. package/.genie/spells/track-long-running-tasks.md +0 -133
  181. package/.genie/spells/troubleshoot-infrastructure.md +0 -176
  182. package/.genie/spells/upgrade-genie.md +0 -415
  183. package/.genie/spells/url-presentation-protocol.md +0 -301
  184. package/.genie/spells/wish-initiation.md +0 -158
  185. package/.genie/spells/wish-issue-linkage.md +0 -410
  186. package/.genie/spells/wish-lifecycle.md +0 -100
  187. package/.genie/state/provider-status.json +0 -3
  188. package/.genie/state/version.json +0 -16
  189. package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
  190. package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
  191. package/.genie/wishes/pgserve-v2/WISH.md +0 -442
  192. package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
  193. package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
  194. package/.gitguardian.yaml +0 -29
  195. package/.gitguardianignore +0 -16
  196. package/.github/workflows/ci.yml +0 -122
  197. package/.github/workflows/release.yml +0 -289
  198. package/.github/workflows/version.yml +0 -228
  199. package/.husky/pre-commit +0 -2
  200. package/AGENTS.md +0 -433
  201. package/CLAUDE.md +0 -1
  202. package/Makefile +0 -285
  203. package/assets/icon.ico +0 -0
  204. package/bun.lock +0 -435
  205. package/bunfig.toml +0 -28
  206. package/ecosystem.config.cjs +0 -23
  207. package/eslint.config.js +0 -63
  208. package/examples/multi-tenant-demo.js +0 -104
  209. package/install.sh +0 -123
  210. package/knip.json +0 -9
  211. package/tests/audit.test.js +0 -189
  212. package/tests/backpressure.test.js +0 -167
  213. package/tests/benchmarks/runner.js +0 -1197
  214. package/tests/benchmarks/vector-generator.js +0 -368
  215. package/tests/cli-install.test.js +0 -322
  216. package/tests/control-db.test.js +0 -285
  217. package/tests/daemon-args.test.js +0 -86
  218. package/tests/daemon-control.test.js +0 -171
  219. package/tests/daemon-fingerprint-integration.test.js +0 -111
  220. package/tests/daemon-pr24-regression.test.js +0 -198
  221. package/tests/fingerprint.test.js +0 -263
  222. package/tests/fixtures/240-orphan-seed.sql +0 -30
  223. package/tests/multi-tenant.test.js +0 -374
  224. package/tests/orphan-cleanup.test.js +0 -390
  225. package/tests/pg-version-regex.test.js +0 -129
  226. package/tests/quick-bench.js +0 -135
  227. package/tests/router-handshake-retry.test.js +0 -119
  228. package/tests/router-handshake-watchdog.test.js +0 -110
  229. package/tests/sdk.test.js +0 -71
  230. package/tests/stale-postmaster-pid.test.js +0 -85
  231. package/tests/stress-test.js +0 -439
  232. package/tests/sync-perf-test.js +0 -150
  233. package/tests/tcp-listen.test.js +0 -368
  234. package/tests/tenancy.test.js +0 -403
  235. package/tests/wrapper-supervision.test.js +0 -107
@@ -1,374 +0,0 @@
1
- /**
2
- * Multi-Tenant Router Test
3
- *
4
- * Tests the new multi-tenant architecture:
5
- * - Single port server
6
- * - Multiple databases auto-provisioned
7
- * - Database isolation
8
- */
9
-
10
- import { startMultiTenantServer } from '../src/index.js';
11
- import pg from 'pg';
12
- import { test, expect } from 'bun:test';
13
- import fs from 'fs';
14
- import path from 'path';
15
-
16
- const { Client } = pg;
17
-
18
- // Test data directory
19
- const testDataDir = './test-data-multitenant';
20
-
21
- // Cleanup helper
22
- function cleanup() {
23
- if (fs.existsSync(testDataDir)) {
24
- fs.rmSync(testDataDir, { recursive: true, force: true });
25
- }
26
- }
27
-
28
- test('Multi-tenant router - basic setup', async () => {
29
- cleanup();
30
-
31
- const router = await startMultiTenantServer({
32
- port: 15432, // Use non-standard port for testing
33
- baseDir: testDataDir,
34
- logLevel: 'info'
35
- });
36
-
37
- // Verify router started
38
- const stats = router.getStats();
39
- expect(stats.port).toBe(15432);
40
- expect(stats.postgres.databases.length).toBe(0); // No databases yet
41
-
42
- await router.stop();
43
- cleanup();
44
- });
45
-
46
- test('Multi-tenant router - auto-provision database', async () => {
47
- cleanup();
48
-
49
- const router = await startMultiTenantServer({
50
- port: 15432,
51
- baseDir: testDataDir,
52
- logLevel: 'info'
53
- });
54
-
55
- // Connect to database "testdb1" (should auto-create)
56
- const client = new Client({
57
- host: '127.0.0.1',
58
- port: 15432,
59
- database: 'testdb1',
60
- user: 'postgres',
61
- password: 'postgres'
62
- });
63
-
64
- await client.connect();
65
-
66
- // Verify instance was created
67
- const stats = router.getStats();
68
- expect(stats.postgres.databases.length).toBe(1);
69
-
70
- const databases = router.listDatabases();
71
- expect(databases.length).toBe(1);
72
- expect(databases[0]).toBe('testdb1');
73
-
74
- // Create table
75
- await client.query('CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)');
76
- await client.query("INSERT INTO users (name) VALUES ('Alice')");
77
-
78
- // Query
79
- const result = await client.query('SELECT * FROM users');
80
- expect(result.rows.length).toBe(1);
81
- expect(result.rows[0].name).toBe('Alice');
82
-
83
- await client.end();
84
- await router.stop();
85
- cleanup();
86
- });
87
-
88
- test('Multi-tenant router - multiple databases isolated', async () => {
89
- cleanup();
90
-
91
- const router = await startMultiTenantServer({
92
- port: 15432,
93
- baseDir: testDataDir,
94
- logLevel: 'info'
95
- });
96
-
97
- // Connect to database 1
98
- const client1 = new Client({
99
- host: '127.0.0.1',
100
- port: 15432,
101
- database: 'db1',
102
- user: 'postgres',
103
- password: 'postgres'
104
- });
105
-
106
- await client1.connect();
107
- await client1.query('CREATE TABLE users (id INT, name TEXT)');
108
- await client1.query("INSERT INTO users VALUES (1, 'Alice')");
109
-
110
- // Verify db1 exists
111
- let stats = router.getStats();
112
- expect(stats.postgres.databases.length).toBe(1);
113
-
114
- await client1.end();
115
-
116
- // Connect to database 2
117
- const client2 = new Client({
118
- host: '127.0.0.1',
119
- port: 15432,
120
- database: 'db2',
121
- user: 'postgres',
122
- password: 'postgres'
123
- });
124
-
125
- await client2.connect();
126
- await client2.query('CREATE TABLE posts (id INT, title TEXT)');
127
- await client2.query("INSERT INTO posts VALUES (1, 'Hello World')");
128
-
129
- // Verify db2 exists
130
- stats = router.getStats();
131
- expect(stats.postgres.databases.length).toBe(2);
132
-
133
- await client2.end();
134
-
135
- // Reconnect to db1 - verify data is isolated
136
- const client1Again = new Client({
137
- host: '127.0.0.1',
138
- port: 15432,
139
- database: 'db1',
140
- user: 'postgres',
141
- password: 'postgres'
142
- });
143
-
144
- await client1Again.connect();
145
-
146
- // Should have users table, NOT posts table
147
- const usersResult = await client1Again.query('SELECT * FROM users');
148
- expect(usersResult.rows.length).toBe(1);
149
- expect(usersResult.rows[0].name).toBe('Alice');
150
-
151
- // Posts table should NOT exist
152
- try {
153
- await client1Again.query('SELECT * FROM posts');
154
- throw new Error('Should throw error - posts table does not exist in db1');
155
- } catch (error) {
156
- expect(error.message).toContain('does not exist');
157
- }
158
-
159
- await client1Again.end();
160
- await router.stop();
161
- cleanup();
162
- });
163
-
164
- test('Router - pre-handshake buffer is bounded (issue #18 root cause #2)', async () => {
165
- // Regression test for issue #18: without a bound on state.buffer, a
166
- // client that sends garbage and never completes the PG startup would
167
- // grow router memory unbounded (traced to the production 74 GiB VmSize).
168
- // After fix, the router must close the connection once the buffer
169
- // exceeds MAX_STARTUP_BUFFER_SIZE (1 MiB).
170
- cleanup();
171
-
172
- const router = await startMultiTenantServer({
173
- port: 15546,
174
- baseDir: testDataDir,
175
- logLevel: 'warn',
176
- });
177
-
178
- const net = await import('net');
179
- const sock = net.connect(15546, '127.0.0.1');
180
- await new Promise((resolve) => sock.once('connect', resolve));
181
-
182
- const garbage = Buffer.alloc(256 * 1024, 0x41); // 256 KiB of 'A'
183
- let closed = false;
184
- sock.on('close', () => { closed = true; });
185
-
186
- // Send 5 × 256 KiB = 1.25 MiB, exceeding the 1 MiB cap.
187
- for (let i = 0; i < 5 && !closed; i++) {
188
- await new Promise((resolve) => sock.write(garbage, resolve));
189
- }
190
-
191
- // Wait up to 2s for the proxy to close the connection.
192
- const deadline = Date.now() + 2000;
193
- while (!closed && Date.now() < deadline) {
194
- await new Promise((r) => setTimeout(r, 50));
195
- }
196
-
197
- expect(closed).toBe(true);
198
- sock.destroy();
199
-
200
- await router.stop();
201
- cleanup();
202
- });
203
-
204
- test('Router - socket state has startupInProgress flag (issue #18 root cause #1)', async () => {
205
- // White-box regression test for the reentrancy guard. Without
206
- // state.startupInProgress, two data events arriving during the first
207
- // processStartupMessage() await would launch concurrent async tasks
208
- // that race to assign state.pgSocket, leaking the loser. This test
209
- // verifies the flag is wired into the state object.
210
- cleanup();
211
-
212
- const router = await startMultiTenantServer({
213
- port: 15547,
214
- baseDir: testDataDir,
215
- logLevel: 'warn',
216
- });
217
-
218
- const net = await import('net');
219
- const sock = net.connect(15547, '127.0.0.1');
220
- await new Promise((resolve) => sock.once('connect', resolve));
221
-
222
- // Router tracks client sockets in this.connections; introspect to pull
223
- // the state object and confirm the flag exists and defaults to false.
224
- // On some platforms (macOS in particular) the client-side 'connect'
225
- // event fires slightly before the server-side handleSocketOpen runs,
226
- // so poll until the router has registered the connection rather than
227
- // asserting synchronously.
228
- const deadline = Date.now() + 2000;
229
- while (router.connections.size === 0 && Date.now() < deadline) {
230
- await new Promise((r) => setTimeout(r, 20));
231
- }
232
- expect(router.connections.size).toBeGreaterThan(0);
233
- const bunSocket = [...router.connections][0];
234
- const state = router.socketState.get(bunSocket);
235
- expect(state).toBeDefined();
236
- expect(state.startupInProgress).toBe(false);
237
-
238
- sock.destroy();
239
- await router.stop();
240
- cleanup();
241
- });
242
-
243
- test('PostgresManager - stop() nulls socketDir/databaseDir (issue #24)', async () => {
244
- // Regression test for issue #24: router used to cache stale socketPath
245
- // pointing to a directory that stop() had already rmSync'd. After fix,
246
- // stop() nulls socketDir/databaseDir UNCONDITIONALLY so subsequent
247
- // getSocketPath() returns null (forcing TCP fallback in the router).
248
- cleanup();
249
-
250
- const { PostgresManager } = await import('../src/postgres.js');
251
- const { createLogger } = await import('../src/logger.js');
252
- const pg = new PostgresManager({
253
- port: 15543,
254
- logger: createLogger({ level: 'warn' }),
255
- });
256
-
257
- await pg.start();
258
- const socketPathBeforeStop = pg.getSocketPath();
259
- expect(socketPathBeforeStop).not.toBeNull();
260
- expect(fs.existsSync(pg.socketDir)).toBe(true);
261
-
262
- await pg.stop();
263
-
264
- // CORE ASSERTION: socketDir must be nulled after stop
265
- expect(pg.socketDir).toBeNull();
266
- expect(pg.getSocketPath()).toBeNull();
267
- // databaseDir nulled only in memory mode (persistent mode keeps user-owned path)
268
- expect(pg.databaseDir).toBeNull();
269
- // And the dir on disk must actually be gone
270
- // (socketPathBeforeStop points inside the deleted socketDir)
271
- const staleSocketDir = path.dirname(socketPathBeforeStop);
272
- expect(fs.existsSync(staleSocketDir)).toBe(false);
273
- });
274
-
275
- test('PostgresManager - start()+stop()+start() yields fresh socketDir (issue #24)', async () => {
276
- // Regression test for issue #24: pgManager.start() called after stop()
277
- // must produce a FRESH socketDir (different path). Without the fix, a
278
- // re-entry guard was missing and socketDir could leak across restarts.
279
- cleanup();
280
-
281
- const { PostgresManager } = await import('../src/postgres.js');
282
- const { createLogger } = await import('../src/logger.js');
283
- const pg = new PostgresManager({
284
- port: 15544,
285
- logger: createLogger({ level: 'warn' }),
286
- });
287
-
288
- await pg.start();
289
- const socketDir1 = pg.socketDir;
290
- expect(socketDir1).not.toBeNull();
291
-
292
- await pg.stop();
293
- expect(pg.socketDir).toBeNull();
294
-
295
- await pg.start();
296
- const socketDir2 = pg.socketDir;
297
- expect(socketDir2).not.toBeNull();
298
- expect(socketDir2).not.toBe(socketDir1);
299
- expect(fs.existsSync(socketDir2)).toBe(true);
300
-
301
- await pg.stop();
302
- });
303
-
304
- test('PostgresManager - double start() is a no-op (issue #24 re-entry guard)', async () => {
305
- // Without the guard, a second start() would overwrite socketDir/databaseDir
306
- // and leak the previous tmp dir (the "1,457 stale sock dirs" symptom).
307
- cleanup();
308
-
309
- const { PostgresManager } = await import('../src/postgres.js');
310
- const { createLogger } = await import('../src/logger.js');
311
- const pg = new PostgresManager({
312
- port: 15545,
313
- logger: createLogger({ level: 'warn' }),
314
- });
315
-
316
- await pg.start();
317
- const socketDir1 = pg.socketDir;
318
-
319
- // Second start() should silently return the same instance without
320
- // reassigning socketDir/databaseDir.
321
- const result = await pg.start();
322
- expect(result).toBe(pg);
323
- expect(pg.socketDir).toBe(socketDir1);
324
-
325
- await pg.stop();
326
- });
327
-
328
- test('Multi-tenant router - instance reuse', async () => {
329
- cleanup();
330
-
331
- const router = await startMultiTenantServer({
332
- port: 15432,
333
- baseDir: testDataDir,
334
- logLevel: 'info'
335
- });
336
-
337
- // First connection to "reuse-test"
338
- const client1 = new Client({
339
- host: '127.0.0.1',
340
- port: 15432,
341
- database: 'reuse-test',
342
- user: 'postgres',
343
- password: 'postgres'
344
- });
345
-
346
- await client1.connect();
347
- await client1.query('CREATE TABLE test (value INT)');
348
- await client1.query('INSERT INTO test VALUES (42)');
349
- await client1.end();
350
-
351
- // Second connection to same database (should reuse instance)
352
- const client2 = new Client({
353
- host: '127.0.0.1',
354
- port: 15432,
355
- database: 'reuse-test',
356
- user: 'postgres',
357
- password: 'postgres'
358
- });
359
-
360
- await client2.connect();
361
-
362
- // Should still have the table from client1
363
- const result = await client2.query('SELECT * FROM test');
364
- expect(result.rows.length).toBe(1);
365
- expect(result.rows[0].value).toBe(42);
366
-
367
- // Still only 1 database
368
- const stats = router.getStats();
369
- expect(stats.postgres.databases.length).toBe(1);
370
-
371
- await client2.end();
372
- await router.stop();
373
- cleanup();
374
- });