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,368 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- /**
4
- * Vector Embedding Generator for Benchmarks
5
- *
6
- * Generates pre-computed random unit vectors for consistent benchmarking.
7
- * Excludes embedding API latency from database performance measurements.
8
- *
9
- * Usage:
10
- * bun tests/benchmarks/vector-generator.js [--count=10000] [--dim=1536]
11
- * bun tests/benchmarks/vector-generator.js --all # Generate all standard fixtures
12
- */
13
-
14
- import fs from 'fs';
15
- import path from 'path';
16
-
17
- const FIXTURES_DIR = path.join(import.meta.dirname, 'fixtures');
18
-
19
- /**
20
- * Generate a random unit vector (normalized L2)
21
- * @param {number} dimension - Vector dimension
22
- * @returns {number[]} Normalized vector
23
- */
24
- function generateUnitVector(dimension) {
25
- // Generate random values from normal distribution (Box-Muller transform)
26
- const vector = [];
27
- for (let i = 0; i < dimension; i++) {
28
- // Use uniform random and transform to approximate normal
29
- const u1 = Math.random();
30
- const u2 = Math.random();
31
- const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
32
- vector.push(z);
33
- }
34
-
35
- // Normalize to unit length (L2 norm = 1)
36
- const norm = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
37
- return vector.map(v => v / norm);
38
- }
39
-
40
- /**
41
- * Generate embeddings with metadata
42
- * @param {number} count - Number of embeddings
43
- * @param {number} dimension - Vector dimension
44
- * @param {number} seed - Random seed for reproducibility (resets Math.random)
45
- * @returns {{ vectors: number[][], metadata: object[] }}
46
- */
47
- function generateEmbeddings(count, dimension, seed = 42) {
48
- // Simple seeded random (LCG)
49
- let state = seed;
50
- const seededRandom = () => {
51
- state = (state * 1664525 + 1013904223) % 4294967296;
52
- return state / 4294967296;
53
- };
54
-
55
- // Override Math.random temporarily
56
- const originalRandom = Math.random;
57
- Math.random = seededRandom;
58
-
59
- const vectors = [];
60
- const metadata = [];
61
-
62
- const categories = ['technology', 'science', 'business', 'health', 'sports', 'entertainment'];
63
- const tenants = ['tenant_a', 'tenant_b', 'tenant_c', 'tenant_d', 'tenant_e'];
64
-
65
- console.log(`Generating ${count.toLocaleString()} vectors of dimension ${dimension}...`);
66
- const startTime = performance.now();
67
-
68
- for (let i = 0; i < count; i++) {
69
- vectors.push(generateUnitVector(dimension));
70
- metadata.push({
71
- id: i + 1,
72
- category: categories[Math.floor(Math.random() * categories.length)],
73
- tenant_id: tenants[Math.floor(Math.random() * tenants.length)],
74
- timestamp: new Date(Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000)).toISOString(),
75
- score: Math.random()
76
- });
77
-
78
- // Progress indicator
79
- if ((i + 1) % 1000 === 0) {
80
- process.stdout.write(`\r Generated ${(i + 1).toLocaleString()} / ${count.toLocaleString()} vectors`);
81
- }
82
- }
83
-
84
- // Restore original Math.random
85
- Math.random = originalRandom;
86
-
87
- const elapsed = ((performance.now() - startTime) / 1000).toFixed(2);
88
- console.log(`\n Completed in ${elapsed}s`);
89
-
90
- return { vectors, metadata };
91
- }
92
-
93
- /**
94
- * Save embeddings to JSON file
95
- * @param {string} filename - Output filename
96
- * @param {{ vectors: number[][], metadata: object[] }} data - Embeddings data
97
- */
98
- function saveEmbeddings(filename, data) {
99
- const filepath = path.join(FIXTURES_DIR, filename);
100
-
101
- // Ensure fixtures directory exists
102
- if (!fs.existsSync(FIXTURES_DIR)) {
103
- fs.mkdirSync(FIXTURES_DIR, { recursive: true });
104
- }
105
-
106
- // Save with minimal formatting for smaller files
107
- const json = JSON.stringify(data);
108
- fs.writeFileSync(filepath, json);
109
-
110
- const sizeMB = (Buffer.byteLength(json) / 1024 / 1024).toFixed(2);
111
- console.log(` Saved to ${filepath} (${sizeMB} MB)`);
112
-
113
- return filepath;
114
- }
115
-
116
- /**
117
- * Load embeddings from JSON file
118
- * @param {string} filename - Input filename
119
- * @returns {{ vectors: number[][], metadata: object[] }}
120
- */
121
- export function loadEmbeddings(filename) {
122
- const filepath = path.join(FIXTURES_DIR, filename);
123
-
124
- if (!fs.existsSync(filepath)) {
125
- throw new Error(`Embeddings file not found: ${filepath}. Run: bun tests/benchmarks/vector-generator.js --all`);
126
- }
127
-
128
- const data = JSON.parse(fs.readFileSync(filepath, 'utf-8'));
129
- console.log(`Loaded ${data.vectors.length.toLocaleString()} vectors from ${filename}`);
130
- return data;
131
- }
132
-
133
- /**
134
- * Get embeddings file path (generates if missing)
135
- * @param {number} count - Number of embeddings
136
- * @param {number} dimension - Vector dimension
137
- * @returns {string} Path to embeddings file
138
- */
139
- export function getEmbeddingsPath(count, dimension) {
140
- const filename = `embeddings-${count}-${dimension}.json`;
141
- const filepath = path.join(FIXTURES_DIR, filename);
142
-
143
- if (!fs.existsSync(filepath)) {
144
- console.log(`\nGenerating missing embeddings file: ${filename}`);
145
- const data = generateEmbeddings(count, dimension);
146
- saveEmbeddings(filename, data);
147
- }
148
-
149
- return filepath;
150
- }
151
-
152
- /**
153
- * Generate query vectors (separate from corpus)
154
- * @param {number} count - Number of query vectors
155
- * @param {number} dimension - Vector dimension
156
- * @returns {number[][]} Query vectors
157
- */
158
- export function generateQueryVectors(count, dimension, seed = 12345) {
159
- const data = generateEmbeddings(count, dimension, seed);
160
- return data.vectors;
161
- }
162
-
163
- /**
164
- * Format vector for PostgreSQL pgvector
165
- * @param {number[]} vector - Vector array
166
- * @returns {string} PostgreSQL vector literal
167
- */
168
- export function formatPgVector(vector) {
169
- return `[${vector.join(',')}]`;
170
- }
171
-
172
- // ============================================================================
173
- // RECALL MEASUREMENT (Industry-standard methodology)
174
- // Based on: ANN-Benchmarks, Qdrant, VectorDBBench
175
- // ============================================================================
176
-
177
- /**
178
- * Compute L2 (Euclidean) distance squared between two vectors
179
- * We use squared distance for efficiency (avoids sqrt, maintains ordering)
180
- * @param {number[]} a - First vector
181
- * @param {number[]} b - Second vector
182
- * @returns {number} Squared L2 distance
183
- */
184
- export function l2DistanceSquared(a, b) {
185
- let sum = 0;
186
- for (let i = 0; i < a.length; i++) {
187
- const diff = a[i] - b[i];
188
- sum += diff * diff;
189
- }
190
- return sum;
191
- }
192
-
193
- /**
194
- * Compute ground truth (exact k-NN via brute force)
195
- * This is the gold standard that approximate results are compared against.
196
- *
197
- * @param {number[][]} corpus - All vectors in the database
198
- * @param {number[][]} queries - Query vectors
199
- * @param {number} k - Number of nearest neighbors
200
- * @returns {number[][]} Ground truth: array of k neighbor IDs for each query
201
- */
202
- export function computeGroundTruth(corpus, queries, k) {
203
- console.log(` Computing ground truth (brute-force k=${k} for ${queries.length} queries)...`);
204
- const startTime = performance.now();
205
-
206
- const groundTruth = [];
207
-
208
- for (let q = 0; q < queries.length; q++) {
209
- const query = queries[q];
210
-
211
- // Compute distance to all corpus vectors
212
- const distances = [];
213
- for (let i = 0; i < corpus.length; i++) {
214
- distances.push({
215
- id: i + 1, // 1-indexed to match database IDs
216
- distance: l2DistanceSquared(query, corpus[i])
217
- });
218
- }
219
-
220
- // Sort by distance and take top-k
221
- distances.sort((a, b) => a.distance - b.distance);
222
- groundTruth.push(distances.slice(0, k).map(d => d.id));
223
-
224
- // Progress
225
- if ((q + 1) % 10 === 0 || q === queries.length - 1) {
226
- process.stdout.write(`\r Ground truth: ${q + 1}/${queries.length} queries`);
227
- }
228
- }
229
-
230
- const elapsed = ((performance.now() - startTime) / 1000).toFixed(2);
231
- console.log(`\n Ground truth computed in ${elapsed}s`);
232
-
233
- return groundTruth;
234
- }
235
-
236
- /**
237
- * Calculate Recall@k
238
- * Recall = (# of ground truth neighbors found) / k
239
- *
240
- * @param {number[][]} approximateResults - IDs returned by approximate search
241
- * @param {number[][]} groundTruth - IDs from exact brute-force search
242
- * @param {number} k - Number of neighbors (for normalization)
243
- * @returns {{ recall: number, perQuery: number[] }} Average recall and per-query recalls
244
- */
245
- export function calculateRecall(approximateResults, groundTruth, k) {
246
- if (approximateResults.length !== groundTruth.length) {
247
- throw new Error(`Result count mismatch: ${approximateResults.length} vs ${groundTruth.length}`);
248
- }
249
-
250
- const perQuery = [];
251
- let totalRecall = 0;
252
-
253
- for (let i = 0; i < approximateResults.length; i++) {
254
- const approxSet = new Set(approximateResults[i]);
255
- const truthSet = groundTruth[i];
256
-
257
- // Count how many ground truth neighbors were found
258
- let found = 0;
259
- for (const truthId of truthSet) {
260
- if (approxSet.has(truthId)) {
261
- found++;
262
- }
263
- }
264
-
265
- const queryRecall = found / k;
266
- perQuery.push(queryRecall);
267
- totalRecall += queryRecall;
268
- }
269
-
270
- return {
271
- recall: totalRecall / approximateResults.length,
272
- perQuery
273
- };
274
- }
275
-
276
- /**
277
- * Get or compute ground truth for a dataset
278
- * Caches the result to avoid recomputation
279
- *
280
- * @param {number[][]} corpus - Corpus vectors
281
- * @param {number[][]} queries - Query vectors
282
- * @param {number} k - Number of neighbors
283
- * @param {string} cacheKey - Unique key for caching
284
- * @returns {number[][]} Ground truth neighbor IDs
285
- */
286
- const groundTruthCache = new Map();
287
-
288
- export function getGroundTruth(corpus, queries, k, cacheKey) {
289
- const fullKey = `${cacheKey}-k${k}`;
290
-
291
- if (groundTruthCache.has(fullKey)) {
292
- console.log(` Using cached ground truth for ${fullKey}`);
293
- return groundTruthCache.get(fullKey);
294
- }
295
-
296
- const groundTruth = computeGroundTruth(corpus, queries, k);
297
- groundTruthCache.set(fullKey, groundTruth);
298
- return groundTruth;
299
- }
300
-
301
- // Standard fixture configurations
302
- const STANDARD_FIXTURES = [
303
- { count: 1000, dimension: 384, desc: 'Small corpus, small model (all-MiniLM)' },
304
- { count: 1000, dimension: 1536, desc: 'Small corpus, OpenAI embeddings' },
305
- { count: 10000, dimension: 384, desc: 'Medium corpus, small model' },
306
- { count: 10000, dimension: 1536, desc: 'Medium corpus, OpenAI embeddings' },
307
- ];
308
-
309
- // CLI interface
310
- if (import.meta.main) {
311
- const args = process.argv.slice(2);
312
-
313
- if (args.includes('--help') || args.includes('-h')) {
314
- console.log(`
315
- Vector Embedding Generator for Benchmarks
316
-
317
- Usage:
318
- bun tests/benchmarks/vector-generator.js [options]
319
-
320
- Options:
321
- --all Generate all standard fixtures
322
- --count=N Number of embeddings (default: 10000)
323
- --dim=N Vector dimension (default: 1536)
324
- --help, -h Show this help
325
-
326
- Standard Fixtures (--all):
327
- ${STANDARD_FIXTURES.map(f => ` - ${f.count} x ${f.dimension}-dim: ${f.desc}`).join('\n')}
328
-
329
- Examples:
330
- bun tests/benchmarks/vector-generator.js --all
331
- bun tests/benchmarks/vector-generator.js --count=5000 --dim=768
332
- `);
333
- process.exit(0);
334
- }
335
-
336
- if (args.includes('--all')) {
337
- console.log('\n=== Generating All Standard Fixtures ===\n');
338
-
339
- for (const { count, dimension, desc } of STANDARD_FIXTURES) {
340
- console.log(`\n[${count} x ${dimension}-dim] ${desc}`);
341
- const data = generateEmbeddings(count, dimension);
342
- saveEmbeddings(`embeddings-${count}-${dimension}.json`, data);
343
- }
344
-
345
- console.log('\n✓ All fixtures generated successfully\n');
346
- } else {
347
- // Parse individual options
348
- let count = 10000;
349
- let dimension = 1536;
350
-
351
- for (const arg of args) {
352
- if (arg.startsWith('--count=')) {
353
- count = parseInt(arg.split('=')[1], 10);
354
- } else if (arg.startsWith('--dim=')) {
355
- dimension = parseInt(arg.split('=')[1], 10);
356
- }
357
- }
358
-
359
- console.log(`\n=== Generating Embeddings ===\n`);
360
- console.log(`Count: ${count.toLocaleString()}`);
361
- console.log(`Dimension: ${dimension}`);
362
-
363
- const data = generateEmbeddings(count, dimension);
364
- saveEmbeddings(`embeddings-${count}-${dimension}.json`, data);
365
-
366
- console.log('\n✓ Done\n');
367
- }
368
- }
@@ -1,322 +0,0 @@
1
- /**
2
- * Tests for src/cli-install.cjs — pgserve install/uninstall/status/url/port.
3
- *
4
- * Wave 1 of the canonical-pgserve-pm2-supervision wish (PR #55, issue #56).
5
- *
6
- * Strategy: drive the pure paths (config read/write, arg parsing, pm2-args
7
- * builder) directly. The pm2-spawning paths (install / uninstall) are
8
- * exercised by spawning the real pgserve binary against a temp HOME so
9
- * `pm2` is invoked but with no real daemon side-effect when pm2 is
10
- * either absent OR the test stubs its calls via PATH.
11
- *
12
- * No test in this file actually starts pgserve. We only verify the CLI
13
- * surface — the daemon lifecycle is covered by daemon-control.test.js.
14
- */
15
-
16
- import { test, expect, beforeEach, afterEach, describe } from 'bun:test';
17
- import fs from 'node:fs';
18
- import os from 'node:os';
19
- import path from 'node:path';
20
- import { spawnSync } from 'node:child_process';
21
-
22
- const REPO_ROOT = path.resolve(__dirname, '..');
23
- const BIN = path.join(REPO_ROOT, 'bin', 'pgserve-wrapper.cjs');
24
-
25
- let tmpHome;
26
- let stubBin;
27
- let originalConfigDir;
28
- let originalPath;
29
-
30
- function makeStubPm2(mode = 'success') {
31
- // mode: 'success' | 'failure' | 'missing'
32
- // Writes a stub `pm2` script into a tempdir we prepend to PATH.
33
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgserve-stub-pm2-'));
34
- if (mode === 'missing') {
35
- // Don't create a stub; PATH still has our dir but no pm2 binary.
36
- return { dir, calls: [] };
37
- }
38
- const callLog = path.join(dir, 'calls.log');
39
- const exitCode = mode === 'failure' ? 1 : 0;
40
- // jlist returns either an empty list (so install proceeds) or a fake
41
- // process record (so subsequent install calls hit the idempotent
42
- // path). We toggle via a sentinel file the test owns.
43
- const script = `#!/usr/bin/env node
44
- const fs = require('node:fs');
45
- const args = process.argv.slice(2);
46
- fs.appendFileSync(${JSON.stringify(callLog)}, JSON.stringify(args) + '\\n');
47
- if (args[0] === '--version') { process.stdout.write('5.0.0-stub\\n'); process.exit(0); }
48
- if (args[0] === 'jlist') {
49
- const sentinel = ${JSON.stringify(path.join(dir, 'registered'))};
50
- if (fs.existsSync(sentinel)) {
51
- process.stdout.write(JSON.stringify([{
52
- name: 'pgserve',
53
- pid: 12345,
54
- pm2_env: { status: 'online', pm_uptime: Date.now() - 1000, restart_time: 0 }
55
- }]) + '\\n');
56
- } else {
57
- process.stdout.write('[]\\n');
58
- }
59
- process.exit(0);
60
- }
61
- if (args[0] === 'start') {
62
- fs.writeFileSync(${JSON.stringify(path.join(dir, 'registered'))}, '');
63
- process.exit(${exitCode});
64
- }
65
- if (args[0] === 'delete') {
66
- try { fs.unlinkSync(${JSON.stringify(path.join(dir, 'registered'))}); } catch {}
67
- process.exit(${exitCode});
68
- }
69
- process.exit(0);
70
- `;
71
- const pm2Path = path.join(dir, 'pm2');
72
- fs.writeFileSync(pm2Path, script, { mode: 0o755 });
73
- return { dir, calls: callLog };
74
- }
75
-
76
- function readCallLog(callsPath) {
77
- if (!fs.existsSync(callsPath)) return [];
78
- return fs.readFileSync(callsPath, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
79
- }
80
-
81
- function runCli(args, env = {}) {
82
- return spawnSync('node', [BIN, ...args], {
83
- encoding: 'utf8',
84
- env: {
85
- ...process.env,
86
- PGSERVE_CONFIG_DIR: tmpHome,
87
- PATH: `${stubBin.dir}:${process.env.PATH}`,
88
- ...env,
89
- },
90
- });
91
- }
92
-
93
- beforeEach(() => {
94
- tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'pgserve-cfg-'));
95
- stubBin = makeStubPm2('success');
96
- originalConfigDir = process.env.PGSERVE_CONFIG_DIR;
97
- originalPath = process.env.PATH;
98
- });
99
-
100
- afterEach(() => {
101
- fs.rmSync(tmpHome, { recursive: true, force: true });
102
- if (stubBin?.dir) fs.rmSync(stubBin.dir, { recursive: true, force: true });
103
- if (originalConfigDir === undefined) delete process.env.PGSERVE_CONFIG_DIR;
104
- else process.env.PGSERVE_CONFIG_DIR = originalConfigDir;
105
- process.env.PATH = originalPath;
106
- });
107
-
108
- describe('pgserve install', () => {
109
- test('first install registers under pm2 and writes config', () => {
110
- const result = runCli(['install']);
111
- expect(result.status).toBe(0);
112
- expect(result.stdout).toContain('installed');
113
- expect(result.stdout).toContain('postgres://localhost:8432');
114
-
115
- const config = JSON.parse(fs.readFileSync(path.join(tmpHome, 'config.json'), 'utf8'));
116
- expect(config.port).toBe(8432);
117
- expect(config.dataDir).toBe(path.join(tmpHome, 'data'));
118
- expect(config.registeredAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
119
-
120
- const calls = readCallLog(stubBin.calls);
121
- const startCall = calls.find((c) => c[0] === 'start');
122
- expect(startCall).toBeDefined();
123
- expect(startCall).toContain('--name');
124
- expect(startCall).toContain('pgserve');
125
- expect(startCall).toContain('--max-restarts');
126
- expect(startCall).toContain('50');
127
- // pm2 ≥ 6.0 dropped `--min-uptime` from the CLI surface — it now lives
128
- // only inside ecosystem files. Passing it on the command line aborts
129
- // `pm2 start` with `error: unknown option --min-uptime`. Lock that out:
130
- // `pgserve install` must NOT pass `--min-uptime` so it stays compatible
131
- // across pm2 5.x → 6.x. See cli-install.cjs:HARDENED_DEFAULTS.
132
- expect(startCall).not.toContain('--min-uptime');
133
- expect(startCall).toContain('--exp-backoff-restart-delay');
134
- expect(startCall).toContain('--max-memory-restart');
135
- expect(startCall).toContain('4G');
136
- expect(startCall).toContain('--kill-timeout');
137
- expect(startCall).toContain('60000');
138
- expect(startCall).toContain('--interpreter');
139
- expect(startCall).toContain('none');
140
-
141
- // pgserve install must launch the foreground multi-tenant server, NOT
142
- // the daemon. Daemon mode rejects `--port` (it only accepts --data,
143
- // --ram, --log, --no-provision, --listen, --pgvector) and its TCP
144
- // listeners require fingerprint+token auth which downstream services
145
- // (omni, genie) don't speak. Foreground mode binds plain TCP on
146
- // 127.0.0.1:<port> with auto-provisioning. Lock that out:
147
- expect(startCall).not.toContain('daemon');
148
- // The script-arg handover (after `--`) must include `--port` so the
149
- // foreground parser binds the right TCP port.
150
- const dashDashIdx = startCall.indexOf('--');
151
- const scriptArgs = startCall.slice(dashDashIdx + 1);
152
- expect(scriptArgs).toContain('--port');
153
- expect(scriptArgs).toContain('--data');
154
- expect(scriptArgs).toContain('--log');
155
- expect(scriptArgs).not.toContain('daemon');
156
- });
157
-
158
- test('second install is idempotent (no second pm2 start)', () => {
159
- runCli(['install']);
160
- const calls1 = readCallLog(stubBin.calls);
161
- const startCount1 = calls1.filter((c) => c[0] === 'start').length;
162
- expect(startCount1).toBe(1);
163
-
164
- const result2 = runCli(['install']);
165
- expect(result2.status).toBe(0);
166
- expect(result2.stdout).toContain('already installed');
167
-
168
- const calls2 = readCallLog(stubBin.calls);
169
- const startCount2 = calls2.filter((c) => c[0] === 'start').length;
170
- expect(startCount2).toBe(1); // no second start
171
- });
172
-
173
- test('--port overrides default', () => {
174
- const result = runCli(['install', '--port', '8442']);
175
- expect(result.status).toBe(0);
176
- const config = JSON.parse(fs.readFileSync(path.join(tmpHome, 'config.json'), 'utf8'));
177
- expect(config.port).toBe(8442);
178
- expect(result.stdout).toContain('postgres://localhost:8442');
179
- });
180
-
181
- test('rejects malformed --port', () => {
182
- const result = runCli(['install', '--port', 'not-a-number']);
183
- expect(result.status).not.toBe(0);
184
- expect(result.stderr).toContain('invalid --port');
185
- });
186
-
187
- test('PGSERVE_MAX_MEMORY env overrides the default memory ceiling', () => {
188
- const result = runCli(['install'], { PGSERVE_MAX_MEMORY: '8G' });
189
- expect(result.status).toBe(0);
190
- const calls = readCallLog(stubBin.calls);
191
- const startCall = calls.find((c) => c[0] === 'start');
192
- // The env value flows through to pm2's --max-memory-restart flag so
193
- // operators on big-iron hosts can tune up without a recompile.
194
- expect(startCall).toContain('8G');
195
- expect(startCall).not.toContain('4G');
196
- });
197
-
198
- test('fails clearly when pm2 is missing', () => {
199
- // Build a sanitized PATH that has node (so spawnSync can resolve the
200
- // interpreter) but explicitly NO directory containing pm2. Skipping
201
- // /usr/bin etc. would make the test brittle on different hosts.
202
- fs.rmSync(stubBin.dir, { recursive: true, force: true });
203
- stubBin = makeStubPm2('missing');
204
- const nodeDir = path.dirname(process.execPath);
205
- const sanitizedPath = (process.env.PATH || '')
206
- .split(':')
207
- .filter((p) => {
208
- try {
209
- return !fs.existsSync(path.join(p, 'pm2'));
210
- } catch {
211
- return true;
212
- }
213
- })
214
- .concat([nodeDir, stubBin.dir])
215
- .join(':');
216
- const result = spawnSync('node', [BIN, 'install'], {
217
- encoding: 'utf8',
218
- env: { ...process.env, PGSERVE_CONFIG_DIR: tmpHome, PATH: sanitizedPath },
219
- });
220
- expect(result.status).not.toBe(0);
221
- expect(result.stderr).toContain('pm2 not found');
222
- });
223
- });
224
-
225
- describe('pgserve url / port', () => {
226
- test('url after install prints canonical connection string', () => {
227
- runCli(['install']);
228
- const result = runCli(['url']);
229
- expect(result.status).toBe(0);
230
- expect(result.stdout.trim()).toBe('postgres://localhost:8432/postgres');
231
- });
232
-
233
- test('port after install prints the registered port', () => {
234
- runCli(['install', '--port', '8442']);
235
- const result = runCli(['port']);
236
- expect(result.status).toBe(0);
237
- expect(result.stdout.trim()).toBe('8442');
238
- });
239
-
240
- test('url before install fails with helpful message', () => {
241
- const result = runCli(['url']);
242
- expect(result.status).not.toBe(0);
243
- expect(result.stderr).toContain('not installed');
244
- });
245
- });
246
-
247
- describe('pgserve status', () => {
248
- test('status before install reports installed=false (exit 1)', () => {
249
- const result = runCli(['status', '--json']);
250
- expect(result.status).toBe(1);
251
- const out = JSON.parse(result.stdout);
252
- expect(out.installed).toBe(false);
253
- });
254
-
255
- test('status after install reports running with port from config', () => {
256
- runCli(['install', '--port', '8482']);
257
- const result = runCli(['status', '--json']);
258
- expect(result.status).toBe(0);
259
- const out = JSON.parse(result.stdout);
260
- expect(out.installed).toBe(true);
261
- expect(out.name).toBe('pgserve');
262
- expect(out.status).toBe('online');
263
- expect(out.port).toBe(8482);
264
- expect(out.url).toBe('postgres://localhost:8482/postgres');
265
- });
266
-
267
- test('status human-readable output includes port + url', () => {
268
- runCli(['install']);
269
- const result = runCli(['status']);
270
- expect(result.status).toBe(0);
271
- expect(result.stdout).toContain('port');
272
- expect(result.stdout).toContain('8432');
273
- expect(result.stdout).toContain('postgres://localhost:8432/postgres');
274
- });
275
- });
276
-
277
- describe('pgserve uninstall', () => {
278
- test('uninstall removes pm2 process but preserves config', () => {
279
- runCli(['install']);
280
- expect(fs.existsSync(path.join(tmpHome, 'config.json'))).toBe(true);
281
-
282
- const result = runCli(['uninstall']);
283
- expect(result.status).toBe(0);
284
- expect(result.stdout).toContain('uninstalled');
285
-
286
- const calls = readCallLog(stubBin.calls);
287
- expect(calls.find((c) => c[0] === 'delete' && c[1] === 'pgserve')).toBeDefined();
288
-
289
- // Config preserved so a re-install reuses port/dataDir.
290
- expect(fs.existsSync(path.join(tmpHome, 'config.json'))).toBe(true);
291
- });
292
-
293
- test('uninstall when not installed is a no-op success', () => {
294
- const result = runCli(['uninstall']);
295
- expect(result.status).toBe(0);
296
- expect(result.stdout).toContain('not registered');
297
- });
298
- });
299
-
300
- describe('serve alias', () => {
301
- test('pgserve serve --help re-routes to daemon (which postgres-server.js handles)', () => {
302
- // We can't fully exercise `serve` without starting a real daemon.
303
- // Instead, verify the wrapper's argv-rewrite happens by passing
304
- // `serve --bogus-flag` and asserting the wrapper proceeded past the
305
- // install short-circuit (i.e. stderr mentions bun, not "pgserve: ...").
306
- // Note: bun probe might fail in tests; we don't assert exit code.
307
- const result = spawnSync('node', [BIN, 'serve', '--bogus-flag'], {
308
- encoding: 'utf8',
309
- env: {
310
- ...process.env,
311
- PGSERVE_CONFIG_DIR: tmpHome,
312
- PATH: `${stubBin.dir}:${process.env.PATH}`,
313
- },
314
- });
315
- // Must NOT have hit the install dispatcher (would print
316
- // "pgserve: not installed" or similar). Because serve passes through
317
- // to the bun + postgres-server.js path, we expect EITHER a bun error
318
- // OR a daemon-mode error — never an install-module error.
319
- expect(result.stderr).not.toContain('pgserve: not installed');
320
- expect(result.stderr).not.toContain('pm2 not found');
321
- });
322
- });