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,439 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * pgserve Stress Test Suite
5
- *
6
- * Like PassMark but for PostgreSQL - progressive load testing with multiple scenarios.
7
- * Perfect for filming terminal under stress.
8
- *
9
- * Usage: bun tests/stress-test.js [port]
10
- */
11
-
12
- import pg from 'pg';
13
- const { Pool } = pg;
14
-
15
- const PORT = parseInt(process.argv[2]) || 8433;
16
-
17
- // ANSI colors
18
- const C = {
19
- reset: '\x1b[0m',
20
- bold: '\x1b[1m',
21
- dim: '\x1b[2m',
22
- green: '\x1b[32m',
23
- yellow: '\x1b[33m',
24
- cyan: '\x1b[36m',
25
- red: '\x1b[31m',
26
- magenta: '\x1b[35m',
27
- };
28
-
29
- const results = [];
30
- let globalPool = null;
31
-
32
- /**
33
- * Create a connection pool
34
- */
35
- function createPool(maxConnections) {
36
- return new Pool({
37
- host: '127.0.0.1',
38
- port: PORT,
39
- database: 'stresstest',
40
- user: 'postgres',
41
- password: 'postgres',
42
- max: maxConnections,
43
- idleTimeoutMillis: 30000,
44
- connectionTimeoutMillis: 10000,
45
- });
46
- }
47
-
48
- /**
49
- * Setup test tables
50
- */
51
- async function setup(pool) {
52
- const client = await pool.connect();
53
- try {
54
- await client.query(`
55
- DROP TABLE IF EXISTS stress_users CASCADE;
56
- DROP TABLE IF EXISTS stress_orders CASCADE;
57
- DROP TABLE IF EXISTS stress_logs CASCADE;
58
-
59
- CREATE TABLE stress_users (
60
- id SERIAL PRIMARY KEY,
61
- name TEXT NOT NULL,
62
- email TEXT,
63
- created_at TIMESTAMP DEFAULT NOW()
64
- );
65
-
66
- CREATE TABLE stress_orders (
67
- id SERIAL PRIMARY KEY,
68
- user_id INTEGER,
69
- amount DECIMAL(10,2),
70
- status TEXT DEFAULT 'pending',
71
- created_at TIMESTAMP DEFAULT NOW()
72
- );
73
-
74
- CREATE TABLE stress_logs (
75
- id SERIAL PRIMARY KEY,
76
- level TEXT,
77
- message TEXT,
78
- data JSONB,
79
- created_at TIMESTAMP DEFAULT NOW()
80
- );
81
-
82
- CREATE INDEX idx_orders_user ON stress_orders(user_id);
83
- CREATE INDEX idx_orders_status ON stress_orders(status);
84
- CREATE INDEX idx_logs_level ON stress_logs(level);
85
- `);
86
- } finally {
87
- client.release();
88
- }
89
- }
90
-
91
- /**
92
- * Print banner
93
- */
94
- function banner() {
95
- console.log(`
96
- ${C.cyan}${C.bold}╔════════════════════════════════════════════════════════════════╗
97
- ║ pgserve STRESS TEST SUITE ║
98
- ║ ║
99
- ║ Progressive load testing with multiple scenarios ║
100
- ╚════════════════════════════════════════════════════════════════╝${C.reset}
101
-
102
- ${C.dim}Target: postgresql://127.0.0.1:${PORT}/stresstest${C.reset}
103
- `);
104
- }
105
-
106
- /**
107
- * Print section header
108
- */
109
- function section(name, description) {
110
- console.log(`
111
- ${C.yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
112
- ${C.bold}${C.cyan}▶ ${name}${C.reset}
113
- ${C.dim}${description}${C.reset}
114
- ${C.yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
115
- `);
116
- }
117
-
118
- /**
119
- * Progress bar
120
- */
121
- function progressBar(current, total, width = 30) {
122
- const pct = Math.min(1, Math.max(0, current / total || 0));
123
- // filled is clamped to [0, width], so (width - filled) is always non-negative
124
- const filled = Math.max(0, Math.min(width, Math.round(pct * width)));
125
- const empty = width - filled;
126
- return `[${'█'.repeat(filled)}${'░'.repeat(empty)}] ${(pct * 100).toFixed(0)}%`;
127
- }
128
-
129
- /**
130
- * Run a test phase
131
- */
132
- async function runPhase(name, config) {
133
- const { connections, duration, workload } = config;
134
-
135
- const pool = createPool(connections);
136
- const latencies = [];
137
- let queries = 0;
138
- let errors = 0;
139
- let running = true;
140
-
141
- const startTime = performance.now();
142
-
143
- // Worker function
144
- async function worker(id) {
145
- while (running) {
146
- const start = performance.now();
147
- try {
148
- await workload(pool, id);
149
- latencies.push(performance.now() - start);
150
- queries++;
151
- } catch (err) {
152
- // Only count errors while test is running (not pool shutdown errors)
153
- if (running) {
154
- errors++;
155
- }
156
- }
157
- }
158
- }
159
-
160
- // Start workers
161
- const workers = Array.from({ length: connections }, (_, i) => worker(i));
162
-
163
- // Progress display
164
- const progressInterval = setInterval(() => {
165
- const elapsed = (performance.now() - startTime) / 1000;
166
- const qps = queries / elapsed;
167
- const progress = progressBar(elapsed, duration);
168
- process.stdout.write(`\r ${progress} | ${queries.toLocaleString()} queries | ${qps.toFixed(0)} QPS | ${errors} errors `);
169
- }, 200);
170
-
171
- // Wait for duration
172
- await new Promise(r => setTimeout(r, duration * 1000));
173
- running = false;
174
-
175
- // End pool immediately to unblock workers waiting for connections
176
- // This is necessary because workers may be stuck in pool.connect() or pool.query()
177
- await pool.end().catch(() => {});
178
-
179
- // Wait for workers to finish (with timeout in case any are still stuck)
180
- await Promise.race([
181
- Promise.allSettled(workers),
182
- new Promise(r => setTimeout(r, 2000)) // 2 second grace period
183
- ]);
184
- clearInterval(progressInterval);
185
-
186
- const totalTime = (performance.now() - startTime) / 1000;
187
-
188
- // Calculate stats
189
- latencies.sort((a, b) => a - b);
190
- const avg = latencies.length > 0 ? latencies.reduce((a, b) => a + b, 0) / latencies.length : 0;
191
- const p50 = latencies[Math.floor(latencies.length * 0.5)] || 0;
192
- const p95 = latencies[Math.floor(latencies.length * 0.95)] || 0;
193
- const p99 = latencies[Math.floor(latencies.length * 0.99)] || 0;
194
- const qps = queries / totalTime;
195
-
196
- const result = {
197
- name,
198
- connections,
199
- duration: totalTime,
200
- queries,
201
- errors,
202
- qps,
203
- latency: { avg, p50, p95, p99 }
204
- };
205
-
206
- results.push(result);
207
-
208
- console.log(`\r ${C.green}✓${C.reset} Complete: ${queries.toLocaleString()} queries in ${totalTime.toFixed(1)}s = ${C.bold}${qps.toFixed(0)} QPS${C.reset} `);
209
- console.log(` ${C.dim}Latency: avg=${avg.toFixed(1)}ms p50=${p50.toFixed(1)}ms p95=${p95.toFixed(1)}ms p99=${p99.toFixed(1)}ms${C.reset}`);
210
-
211
- return result;
212
- }
213
-
214
- // ============================================================================
215
- // WORKLOADS
216
- // ============================================================================
217
-
218
- const workloads = {
219
- // Pure inserts
220
- writeHeavy: async (pool, workerId) => {
221
- await pool.query(
222
- 'INSERT INTO stress_logs (level, message, data) VALUES ($1, $2, $3)',
223
- ['info', `Worker ${workerId} log entry`, JSON.stringify({ ts: Date.now(), worker: workerId })]
224
- );
225
- },
226
-
227
- // Pure reads
228
- readHeavy: async (pool) => {
229
- await pool.query('SELECT * FROM stress_logs ORDER BY id DESC LIMIT 50');
230
- },
231
-
232
- // Mixed CRUD
233
- mixed: async (pool, workerId) => {
234
- const op = Math.random();
235
- if (op < 0.3) {
236
- // 30% writes
237
- await pool.query(
238
- 'INSERT INTO stress_orders (user_id, amount, status) VALUES ($1, $2, $3)',
239
- [Math.floor(Math.random() * 1000), Math.random() * 1000, 'pending']
240
- );
241
- } else if (op < 0.5) {
242
- // 20% updates
243
- await pool.query(
244
- "UPDATE stress_orders SET status = $1 WHERE id = (SELECT id FROM stress_orders WHERE status = 'pending' LIMIT 1)",
245
- ['completed']
246
- );
247
- } else {
248
- // 50% reads
249
- await pool.query('SELECT * FROM stress_orders WHERE status = $1 LIMIT 20', ['pending']);
250
- }
251
- },
252
-
253
- // Complex queries with joins
254
- complex: async (pool) => {
255
- await pool.query(`
256
- SELECT o.*, COUNT(*) OVER() as total
257
- FROM stress_orders o
258
- WHERE o.created_at > NOW() - INTERVAL '1 hour'
259
- ORDER BY o.created_at DESC
260
- LIMIT 10
261
- `);
262
- },
263
-
264
- // Transaction heavy
265
- transactions: async (pool) => {
266
- const client = await pool.connect();
267
- try {
268
- await client.query('BEGIN');
269
- await client.query(
270
- 'INSERT INTO stress_users (name, email) VALUES ($1, $2) RETURNING id',
271
- [`User-${Date.now()}`, `user-${Date.now()}@test.com`]
272
- );
273
- await client.query(
274
- 'INSERT INTO stress_orders (user_id, amount) VALUES (currval(pg_get_serial_sequence(\'stress_users\', \'id\')), $1)',
275
- [Math.random() * 500]
276
- );
277
- await client.query('COMMIT');
278
- } catch (err) {
279
- await client.query('ROLLBACK');
280
- throw err;
281
- } finally {
282
- client.release();
283
- }
284
- },
285
- };
286
-
287
- // ============================================================================
288
- // MAIN TEST SUITE
289
- // ============================================================================
290
-
291
- async function runSuite() {
292
- banner();
293
-
294
- console.log(`${C.dim}Connecting and setting up test database...${C.reset}`);
295
- globalPool = createPool(5);
296
- await setup(globalPool);
297
- console.log(`${C.green}✓${C.reset} Setup complete\n`);
298
-
299
- // -------------------------------------------------------------------------
300
- // TEST 1: Connection Ramp-Up
301
- // -------------------------------------------------------------------------
302
- section('TEST 1: Connection Ramp-Up', 'Gradually increasing concurrent connections');
303
-
304
- for (const conns of [10, 50, 100, 250, 500]) {
305
- console.log(`\n ${C.cyan}→ ${conns} connections${C.reset}`);
306
- await runPhase(`ramp-${conns}`, {
307
- connections: conns,
308
- duration: 10,
309
- workload: workloads.mixed
310
- });
311
- await new Promise(r => setTimeout(r, 1000)); // Brief pause between phases
312
- }
313
-
314
- // -------------------------------------------------------------------------
315
- // TEST 2: Write Stress
316
- // -------------------------------------------------------------------------
317
- section('TEST 2: Write Stress', 'Heavy INSERT workload - 200 connections, 15 seconds');
318
-
319
- await runPhase('write-stress', {
320
- connections: 200,
321
- duration: 15,
322
- workload: workloads.writeHeavy
323
- });
324
-
325
- // -------------------------------------------------------------------------
326
- // TEST 3: Read Stress
327
- // -------------------------------------------------------------------------
328
- section('TEST 3: Read Stress', 'Heavy SELECT workload - 200 connections, 15 seconds');
329
-
330
- await runPhase('read-stress', {
331
- connections: 200,
332
- duration: 15,
333
- workload: workloads.readHeavy
334
- });
335
-
336
- // -------------------------------------------------------------------------
337
- // TEST 4: Mixed Workload
338
- // -------------------------------------------------------------------------
339
- section('TEST 4: Mixed Workload', 'Real-world CRUD simulation - 300 connections, 20 seconds');
340
-
341
- await runPhase('mixed-heavy', {
342
- connections: 300,
343
- duration: 20,
344
- workload: workloads.mixed
345
- });
346
-
347
- // -------------------------------------------------------------------------
348
- // TEST 5: Transaction Stress
349
- // -------------------------------------------------------------------------
350
- section('TEST 5: Transaction Stress', 'Multi-statement transactions - 150 connections, 15 seconds');
351
-
352
- await runPhase('transactions', {
353
- connections: 150,
354
- duration: 15,
355
- workload: workloads.transactions
356
- });
357
-
358
- // -------------------------------------------------------------------------
359
- // TEST 6: Peak Load
360
- // -------------------------------------------------------------------------
361
- section('TEST 6: Peak Load', 'Maximum stress - 500 connections, 20 seconds');
362
-
363
- await runPhase('peak-load', {
364
- connections: 500,
365
- duration: 20,
366
- workload: workloads.mixed
367
- });
368
-
369
- // -------------------------------------------------------------------------
370
- // TEST 7: Extreme Load
371
- // -------------------------------------------------------------------------
372
- section('TEST 7: Extreme Load', 'Near-limit stress - 750 connections, 15 seconds');
373
-
374
- await runPhase('extreme-load', {
375
- connections: 750,
376
- duration: 15,
377
- workload: workloads.mixed
378
- });
379
-
380
- // -------------------------------------------------------------------------
381
- // FINAL REPORT
382
- // -------------------------------------------------------------------------
383
- await globalPool.end();
384
-
385
- console.log(`
386
- ${C.cyan}${C.bold}
387
- ╔════════════════════════════════════════════════════════════════╗
388
- ║ FINAL RESULTS ║
389
- ╚════════════════════════════════════════════════════════════════╝${C.reset}
390
- `);
391
-
392
- // Summary table
393
- console.log(`${C.bold}Test Name Conn Queries QPS Avg P95 P99 Errors${C.reset}`);
394
- console.log(`${'─'.repeat(85)}`);
395
-
396
- let totalQueries = 0;
397
- let totalErrors = 0;
398
- let peakQps = 0;
399
-
400
- for (const r of results) {
401
- const name = r.name.padEnd(20);
402
- const conn = String(r.connections).padStart(4);
403
- const queries = r.queries.toLocaleString().padStart(10);
404
- const qps = r.qps.toFixed(0).padStart(7);
405
- const avg = r.latency.avg.toFixed(1).padStart(6) + 'ms';
406
- const p95 = r.latency.p95.toFixed(1).padStart(6) + 'ms';
407
- const p99 = r.latency.p99.toFixed(1).padStart(6) + 'ms';
408
- const errors = String(r.errors).padStart(6);
409
-
410
- const color = r.errors > 0 ? C.yellow : C.green;
411
- console.log(`${color}${name} ${conn} ${queries} ${qps} ${avg} ${p95} ${p99} ${errors}${C.reset}`);
412
-
413
- totalQueries += r.queries;
414
- totalErrors += r.errors;
415
- if (r.qps > peakQps) peakQps = r.qps;
416
- }
417
-
418
- console.log(`${'─'.repeat(85)}`);
419
-
420
- // Score calculation (arbitrary but fun)
421
- const score = Math.round((peakQps * 0.5) + (totalQueries / 1000) - (totalErrors * 10));
422
-
423
- console.log(`
424
- ${C.bold}Summary:${C.reset}
425
- Total Queries: ${totalQueries.toLocaleString()}
426
- Total Errors: ${totalErrors}
427
- Peak QPS: ${peakQps.toFixed(0)}
428
-
429
- ${C.magenta}${C.bold}╔═══════════════════════════════════╗
430
- ║ PGSERVE SCORE: ${String(score).padStart(6)} ║
431
- ╚═══════════════════════════════════╝${C.reset}
432
- `);
433
- }
434
-
435
- // Run
436
- runSuite().catch(err => {
437
- console.error(`${C.red}Error:${C.reset}`, err);
438
- process.exit(1);
439
- });
@@ -1,150 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Sync Performance Impact Test
4
- *
5
- * Verifies that enabling sync has ZERO impact on hot path performance.
6
- * Runs identical workloads with and without sync, compares results.
7
- */
8
-
9
- import { startMultiTenantServer } from '../src/index.js';
10
- import pg from 'pg';
11
-
12
- const ITERATIONS = 1000;
13
- const WARMUP = 100;
14
-
15
- async function runWorkload(port, label) {
16
- const client = new pg.Client({
17
- host: 'localhost',
18
- port,
19
- database: 'perftest',
20
- user: 'postgres',
21
- password: 'postgres'
22
- });
23
-
24
- await client.connect();
25
-
26
- // Create table
27
- await client.query('CREATE TABLE IF NOT EXISTS bench (id SERIAL PRIMARY KEY, data TEXT)');
28
- await client.query('TRUNCATE bench');
29
-
30
- // Warmup
31
- for (let i = 0; i < WARMUP; i++) {
32
- await client.query('INSERT INTO bench (data) VALUES ($1)', [`warmup-${i}`]);
33
- }
34
- await client.query('TRUNCATE bench');
35
-
36
- // Benchmark
37
- const start = process.hrtime.bigint();
38
-
39
- for (let i = 0; i < ITERATIONS; i++) {
40
- await client.query('INSERT INTO bench (data) VALUES ($1)', [`row-${i}`]);
41
- }
42
-
43
- const insertEnd = process.hrtime.bigint();
44
- const insertMs = Number(insertEnd - start) / 1_000_000;
45
-
46
- // Read benchmark
47
- const readStart = process.hrtime.bigint();
48
- for (let i = 0; i < ITERATIONS; i++) {
49
- await client.query('SELECT * FROM bench WHERE id = $1', [i % ITERATIONS + 1]);
50
- }
51
- const readEnd = process.hrtime.bigint();
52
- const readMs = Number(readEnd - readStart) / 1_000_000;
53
-
54
- await client.end();
55
-
56
- return {
57
- label,
58
- inserts: {
59
- total: insertMs,
60
- perOp: insertMs / ITERATIONS,
61
- opsPerSec: Math.round(ITERATIONS / (insertMs / 1000))
62
- },
63
- reads: {
64
- total: readMs,
65
- perOp: readMs / ITERATIONS,
66
- opsPerSec: Math.round(ITERATIONS / (readMs / 1000))
67
- }
68
- };
69
- }
70
-
71
- async function main() {
72
- console.log('='.repeat(60));
73
- console.log('SYNC PERFORMANCE IMPACT TEST');
74
- console.log('='.repeat(60));
75
- console.log(`Iterations: ${ITERATIONS} | Warmup: ${WARMUP}`);
76
- console.log();
77
-
78
- // Test 1: Without sync
79
- console.log('[1/2] Starting pgserve WITHOUT sync...');
80
- const serverNoSync = await startMultiTenantServer({
81
- port: 18001,
82
- logLevel: 'error'
83
- });
84
-
85
- await new Promise(r => setTimeout(r, 2000)); // Wait for server
86
- const resultNoSync = await runWorkload(18001, 'NO SYNC');
87
- await serverNoSync.stop();
88
-
89
- console.log(' Done.');
90
- console.log();
91
-
92
- // Test 2: With sync enabled (failing target - simulates sync overhead)
93
- console.log('[2/2] Starting pgserve WITH sync (failing target)...');
94
- const serverWithSync = await startMultiTenantServer({
95
- port: 18002,
96
- logLevel: 'error',
97
- syncTo: 'postgresql://dummy:dummy@localhost:59999/fake', // Intentionally failing
98
- syncDatabases: 'perftest'
99
- });
100
-
101
- await new Promise(r => setTimeout(r, 2000)); // Wait for server
102
- const resultWithSync = await runWorkload(18002, 'WITH SYNC');
103
- await serverWithSync.stop();
104
-
105
- console.log(' Done.');
106
- console.log();
107
-
108
- // Results
109
- console.log('='.repeat(60));
110
- console.log('RESULTS');
111
- console.log('='.repeat(60));
112
- console.log();
113
-
114
- console.log('INSERT PERFORMANCE:');
115
- console.log(` Without Sync: ${resultNoSync.inserts.opsPerSec.toLocaleString()} ops/sec (${resultNoSync.inserts.perOp.toFixed(2)} ms/op)`);
116
- console.log(` With Sync: ${resultWithSync.inserts.opsPerSec.toLocaleString()} ops/sec (${resultWithSync.inserts.perOp.toFixed(2)} ms/op)`);
117
-
118
- const insertDiff = ((resultWithSync.inserts.opsPerSec - resultNoSync.inserts.opsPerSec) / resultNoSync.inserts.opsPerSec * 100).toFixed(2);
119
- console.log(` Difference: ${insertDiff > 0 ? '+' : ''}${insertDiff}%`);
120
- console.log();
121
-
122
- console.log('READ PERFORMANCE:');
123
- console.log(` Without Sync: ${resultNoSync.reads.opsPerSec.toLocaleString()} ops/sec (${resultNoSync.reads.perOp.toFixed(2)} ms/op)`);
124
- console.log(` With Sync: ${resultWithSync.reads.opsPerSec.toLocaleString()} ops/sec (${resultWithSync.reads.perOp.toFixed(2)} ms/op)`);
125
-
126
- const readDiff = ((resultWithSync.reads.opsPerSec - resultNoSync.reads.opsPerSec) / resultNoSync.reads.opsPerSec * 100).toFixed(2);
127
- console.log(` Difference: ${readDiff > 0 ? '+' : ''}${readDiff}%`);
128
- console.log();
129
-
130
- // Verdict
131
- console.log('='.repeat(60));
132
- const threshold = 5; // 5% tolerance
133
- const insertPass = Math.abs(parseFloat(insertDiff)) < threshold;
134
- const readPass = Math.abs(parseFloat(readDiff)) < threshold;
135
-
136
- if (insertPass && readPass) {
137
- console.log('VERDICT: ✅ PASS - ZERO PERFORMANCE IMPACT');
138
- console.log(` (within ${threshold}% tolerance)`);
139
- } else {
140
- console.log('VERDICT: ❌ FAIL - PERFORMANCE REGRESSION DETECTED');
141
- }
142
- console.log('='.repeat(60));
143
-
144
- process.exit(insertPass && readPass ? 0 : 1);
145
- }
146
-
147
- main().catch(e => {
148
- console.error('Test failed:', e);
149
- process.exit(1);
150
- });