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.
- package/CHANGELOG.md +86 -0
- package/README.md +105 -1
- package/bin/autopg-wrapper.cjs +16 -0
- package/bin/pgserve-wrapper.cjs +31 -6
- package/bin/postgres-server.js +80 -7
- package/console/README.md +131 -0
- package/console/api.js +173 -0
- package/console/app.jsx +483 -0
- package/console/colors_and_type.css +227 -0
- package/console/components.jsx +167 -0
- package/console/console.css +1666 -0
- package/console/data.jsx +350 -0
- package/console/index.html +31 -0
- package/console/screens/databases.jsx +5 -0
- package/console/screens/health.jsx +5 -0
- package/console/screens/ingress.jsx +5 -0
- package/console/screens/optimizer.jsx +5 -0
- package/console/screens/rlm-sim.jsx +5 -0
- package/console/screens/rlm-trace.jsx +5 -0
- package/console/screens/security.jsx +5 -0
- package/console/screens/settings.jsx +611 -0
- package/console/screens/sql.jsx +5 -0
- package/console/screens/sync.jsx +5 -0
- package/console/screens/tables.jsx +5 -0
- package/console/tweaks-panel.jsx +425 -0
- package/package.json +11 -1
- package/src/cli-config.cjs +310 -0
- package/src/cli-install.cjs +98 -11
- package/src/cli-restart.cjs +228 -0
- package/src/cli-ui.cjs +580 -0
- package/src/cluster.js +43 -38
- package/src/postgres.js +141 -19
- package/src/settings-loader.cjs +235 -0
- package/src/settings-migrate.cjs +212 -0
- package/src/settings-pg-args.cjs +146 -0
- package/src/settings-schema.cjs +422 -0
- package/src/settings-validator.cjs +416 -0
- package/src/settings-writer.cjs +288 -0
- package/.claude/context/windows-debug.md +0 -119
- package/.genie/AGENTS.md +0 -15
- package/.genie/agents/README.md +0 -110
- package/.genie/agents/analyze.md +0 -176
- package/.genie/agents/forge.md +0 -290
- package/.genie/agents/garbage-cleaner.md +0 -324
- package/.genie/agents/garbage-collector.md +0 -596
- package/.genie/agents/github-issue-gc.md +0 -618
- package/.genie/agents/review.md +0 -380
- package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
- package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
- package/.genie/agents/semantic-analyzer.md +0 -101
- package/.genie/agents/update.md +0 -182
- package/.genie/agents/wish.md +0 -357
- package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
- package/.genie/code/AGENTS.md +0 -694
- package/.genie/code/agents/audit/risk.md +0 -173
- package/.genie/code/agents/audit/security.md +0 -189
- package/.genie/code/agents/audit.md +0 -145
- package/.genie/code/agents/challenge.md +0 -230
- package/.genie/code/agents/change-reviewer.md +0 -295
- package/.genie/code/agents/code-garbage-collector.md +0 -425
- package/.genie/code/agents/code-quality.md +0 -410
- package/.genie/code/agents/commit-suggester.md +0 -255
- package/.genie/code/agents/commit.md +0 -124
- package/.genie/code/agents/consensus.md +0 -204
- package/.genie/code/agents/daily-standup.md +0 -722
- package/.genie/code/agents/docgen.md +0 -48
- package/.genie/code/agents/explore.md +0 -79
- package/.genie/code/agents/fix.md +0 -100
- package/.genie/code/agents/git/commit-advisory.md +0 -219
- package/.genie/code/agents/git/workflows/issue.md +0 -244
- package/.genie/code/agents/git/workflows/pr.md +0 -179
- package/.genie/code/agents/git/workflows/release.md +0 -460
- package/.genie/code/agents/git/workflows/report.md +0 -342
- package/.genie/code/agents/git.md +0 -432
- package/.genie/code/agents/implementor.md +0 -161
- package/.genie/code/agents/install.md +0 -515
- package/.genie/code/agents/issue-creator.md +0 -344
- package/.genie/code/agents/polish.md +0 -116
- package/.genie/code/agents/qa.md +0 -653
- package/.genie/code/agents/refactor.md +0 -294
- package/.genie/code/agents/release.md +0 -1129
- package/.genie/code/agents/roadmap.md +0 -885
- package/.genie/code/agents/tests.md +0 -557
- package/.genie/code/agents/tracer.md +0 -50
- package/.genie/code/agents/update/upstream-update.md +0 -85
- package/.genie/code/agents/update/versions/generic-update.md +0 -305
- package/.genie/code/agents/vibe.md +0 -1317
- package/.genie/code/spells/agent-configuration.md +0 -58
- package/.genie/code/spells/automated-rc-publishing.md +0 -106
- package/.genie/code/spells/branch-tracker-guidance.md +0 -28
- package/.genie/code/spells/debug.md +0 -320
- package/.genie/code/spells/emoji-naming-convention.md +0 -303
- package/.genie/code/spells/evidence-storage.md +0 -26
- package/.genie/code/spells/file-naming-rules.md +0 -35
- package/.genie/code/spells/forge-code-blueprints.md +0 -195
- package/.genie/code/spells/genie-integration.md +0 -153
- package/.genie/code/spells/publishing-protocol.md +0 -61
- package/.genie/code/spells/team-consultation-protocol.md +0 -284
- package/.genie/code/spells/tool-requirements.md +0 -20
- package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
- package/.genie/code/teams/tech-council/council.md +0 -328
- package/.genie/code/teams/tech-council/jt.md +0 -352
- package/.genie/code/teams/tech-council/nayr.md +0 -305
- package/.genie/code/teams/tech-council/oettam.md +0 -375
- package/.genie/neurons/README.md +0 -193
- package/.genie/neurons/forge.md +0 -106
- package/.genie/neurons/genie.md +0 -63
- package/.genie/neurons/review.md +0 -106
- package/.genie/neurons/wish.md +0 -104
- package/.genie/product/README.md +0 -20
- package/.genie/product/cli-automation.md +0 -359
- package/.genie/product/environment.md +0 -60
- package/.genie/product/mission.md +0 -60
- package/.genie/product/roadmap.md +0 -44
- package/.genie/product/tech-stack.md +0 -34
- package/.genie/product/templates/context-template.md +0 -218
- package/.genie/product/templates/qa-done-report-template.md +0 -68
- package/.genie/product/templates/review-report-template.md +0 -89
- package/.genie/product/templates/wish-template.md +0 -120
- package/.genie/scripts/helpers/analyze-commit.js +0 -195
- package/.genie/scripts/helpers/bullet-counter.js +0 -194
- package/.genie/scripts/helpers/bullet-find.js +0 -289
- package/.genie/scripts/helpers/bullet-id.js +0 -244
- package/.genie/scripts/helpers/check-secrets.js +0 -237
- package/.genie/scripts/helpers/count-tokens.js +0 -200
- package/.genie/scripts/helpers/create-frontmatter.js +0 -456
- package/.genie/scripts/helpers/detect-markers.js +0 -293
- package/.genie/scripts/helpers/detect-todos.js +0 -267
- package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
- package/.genie/scripts/helpers/embeddings.js +0 -344
- package/.genie/scripts/helpers/find-empty-sections.js +0 -158
- package/.genie/scripts/helpers/index.js +0 -319
- package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
- package/.genie/scripts/helpers/validate-links.js +0 -207
- package/.genie/scripts/helpers/validate-paths.js +0 -373
- package/.genie/spells/README.md +0 -9
- package/.genie/spells/ace-protocol.md +0 -118
- package/.genie/spells/ask-one-at-a-time.md +0 -175
- package/.genie/spells/backup-analyzer.md +0 -542
- package/.genie/spells/blocker.md +0 -12
- package/.genie/spells/break-things-move-fast.md +0 -56
- package/.genie/spells/context-candidates.md +0 -72
- package/.genie/spells/context-critic.md +0 -51
- package/.genie/spells/defer-to-expertise.md +0 -278
- package/.genie/spells/delegate-dont-do.md +0 -292
- package/.genie/spells/error-investigation-protocol.md +0 -328
- package/.genie/spells/evidence-based-completion.md +0 -273
- package/.genie/spells/experiment.md +0 -65
- package/.genie/spells/file-creation-protocol.md +0 -229
- package/.genie/spells/forge-integration.md +0 -281
- package/.genie/spells/forge-orchestration.md +0 -514
- package/.genie/spells/gather-context.md +0 -18
- package/.genie/spells/global-health-check.md +0 -34
- package/.genie/spells/global-noop-roundtrip.md +0 -25
- package/.genie/spells/install-genie.md +0 -1232
- package/.genie/spells/install.md +0 -82
- package/.genie/spells/investigate-before-commit.md +0 -112
- package/.genie/spells/know-yourself.md +0 -288
- package/.genie/spells/learn.md +0 -828
- package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
- package/.genie/spells/mcp-first.md +0 -124
- package/.genie/spells/multi-step-execution.md +0 -67
- package/.genie/spells/orchestration-boundary-protocol.md +0 -256
- package/.genie/spells/orchestrator-not-implementor.md +0 -189
- package/.genie/spells/prompt.md +0 -746
- package/.genie/spells/reflect.md +0 -404
- package/.genie/spells/routing-decision-matrix.md +0 -368
- package/.genie/spells/run-in-parallel.md +0 -12
- package/.genie/spells/session-state-updater-example.md +0 -196
- package/.genie/spells/session-state-updater.md +0 -220
- package/.genie/spells/track-long-running-tasks.md +0 -133
- package/.genie/spells/troubleshoot-infrastructure.md +0 -176
- package/.genie/spells/upgrade-genie.md +0 -415
- package/.genie/spells/url-presentation-protocol.md +0 -301
- package/.genie/spells/wish-initiation.md +0 -158
- package/.genie/spells/wish-issue-linkage.md +0 -410
- package/.genie/spells/wish-lifecycle.md +0 -100
- package/.genie/state/provider-status.json +0 -3
- package/.genie/state/version.json +0 -16
- package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
- package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
- package/.genie/wishes/pgserve-v2/WISH.md +0 -442
- package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
- package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
- package/.gitguardian.yaml +0 -29
- package/.gitguardianignore +0 -16
- package/.github/workflows/ci.yml +0 -122
- package/.github/workflows/release.yml +0 -289
- package/.github/workflows/version.yml +0 -228
- package/.husky/pre-commit +0 -2
- package/AGENTS.md +0 -433
- package/CLAUDE.md +0 -1
- package/Makefile +0 -285
- package/assets/icon.ico +0 -0
- package/bun.lock +0 -435
- package/bunfig.toml +0 -28
- package/ecosystem.config.cjs +0 -23
- package/eslint.config.js +0 -63
- package/examples/multi-tenant-demo.js +0 -104
- package/install.sh +0 -123
- package/knip.json +0 -9
- package/scripts/test-bun-self-heal.sh +0 -163
- package/scripts/test-npx.sh +0 -60
- package/tests/audit.test.js +0 -189
- package/tests/backpressure.test.js +0 -167
- package/tests/benchmarks/runner.js +0 -1197
- package/tests/benchmarks/vector-generator.js +0 -368
- package/tests/cli-install.test.js +0 -322
- package/tests/control-db.test.js +0 -285
- package/tests/daemon-control.test.js +0 -171
- package/tests/daemon-fingerprint-integration.test.js +0 -111
- package/tests/daemon-pr24-regression.test.js +0 -198
- package/tests/fingerprint.test.js +0 -263
- package/tests/fixtures/240-orphan-seed.sql +0 -30
- package/tests/multi-tenant.test.js +0 -374
- package/tests/orphan-cleanup.test.js +0 -390
- package/tests/pg-version-regex.test.js +0 -129
- package/tests/quick-bench.js +0 -135
- package/tests/router-handshake-retry.test.js +0 -119
- package/tests/router-handshake-watchdog.test.js +0 -110
- package/tests/sdk.test.js +0 -71
- package/tests/stale-postmaster-pid.test.js +0 -85
- package/tests/stress-test.js +0 -439
- package/tests/sync-perf-test.js +0 -150
- package/tests/tcp-listen.test.js +0 -368
- package/tests/tenancy.test.js +0 -403
- 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
|
-
});
|