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,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
- });