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