pgserve 0.1.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.
- package/.genie/AGENTS.md +13 -0
- package/.genie/agents/README.md +110 -0
- package/.genie/agents/analyze.md +176 -0
- package/.genie/agents/forge.md +290 -0
- package/.genie/agents/garbage-cleaner.md +324 -0
- package/.genie/agents/garbage-collector.md +596 -0
- package/.genie/agents/github-issue-gc.md +618 -0
- package/.genie/agents/review.md +380 -0
- package/.genie/agents/semantic-analyzer/find-duplicates.md +90 -0
- package/.genie/agents/semantic-analyzer/find-orphans.md +99 -0
- package/.genie/agents/semantic-analyzer.md +101 -0
- package/.genie/agents/update.md +182 -0
- package/.genie/agents/wish.md +357 -0
- package/.genie/code/AGENTS.md +692 -0
- package/.genie/code/agents/audit/risk.md +173 -0
- package/.genie/code/agents/audit/security.md +189 -0
- package/.genie/code/agents/audit.md +145 -0
- package/.genie/code/agents/challenge.md +230 -0
- package/.genie/code/agents/change-reviewer.md +295 -0
- package/.genie/code/agents/code-garbage-collector.md +425 -0
- package/.genie/code/agents/code-quality.md +410 -0
- package/.genie/code/agents/commit-suggester.md +255 -0
- package/.genie/code/agents/commit.md +124 -0
- package/.genie/code/agents/consensus.md +204 -0
- package/.genie/code/agents/daily-standup.md +722 -0
- package/.genie/code/agents/docgen.md +48 -0
- package/.genie/code/agents/explore.md +79 -0
- package/.genie/code/agents/fix.md +100 -0
- package/.genie/code/agents/git/commit-advisory.md +219 -0
- package/.genie/code/agents/git/workflows/issue.md +244 -0
- package/.genie/code/agents/git/workflows/pr.md +179 -0
- package/.genie/code/agents/git/workflows/release.md +460 -0
- package/.genie/code/agents/git/workflows/report.md +342 -0
- package/.genie/code/agents/git.md +432 -0
- package/.genie/code/agents/implementor.md +161 -0
- package/.genie/code/agents/install.md +515 -0
- package/.genie/code/agents/issue-creator.md +344 -0
- package/.genie/code/agents/polish.md +116 -0
- package/.genie/code/agents/qa.md +653 -0
- package/.genie/code/agents/refactor.md +294 -0
- package/.genie/code/agents/release.md +1129 -0
- package/.genie/code/agents/roadmap.md +885 -0
- package/.genie/code/agents/tests.md +557 -0
- package/.genie/code/agents/tracer.md +50 -0
- package/.genie/code/agents/update/upstream-update.md +85 -0
- package/.genie/code/agents/update/versions/generic-update.md +305 -0
- package/.genie/code/agents/vibe.md +1317 -0
- package/.genie/code/spells/agent-configuration.md +58 -0
- package/.genie/code/spells/automated-rc-publishing.md +106 -0
- package/.genie/code/spells/branch-tracker-guidance.md +28 -0
- package/.genie/code/spells/debug.md +320 -0
- package/.genie/code/spells/emoji-naming-convention.md +303 -0
- package/.genie/code/spells/evidence-storage.md +26 -0
- package/.genie/code/spells/file-naming-rules.md +35 -0
- package/.genie/code/spells/forge-code-blueprints.md +195 -0
- package/.genie/code/spells/genie-integration.md +153 -0
- package/.genie/code/spells/publishing-protocol.md +61 -0
- package/.genie/code/spells/team-consultation-protocol.md +284 -0
- package/.genie/code/spells/tool-requirements.md +20 -0
- package/.genie/code/spells/triad-maintenance-protocol.md +154 -0
- package/.genie/code/teams/tech-council/council.md +328 -0
- package/.genie/code/teams/tech-council/jt.md +352 -0
- package/.genie/code/teams/tech-council/nayr.md +305 -0
- package/.genie/code/teams/tech-council/oettam.md +375 -0
- package/.genie/neurons/README.md +193 -0
- package/.genie/neurons/forge.md +106 -0
- package/.genie/neurons/genie.md +63 -0
- package/.genie/neurons/review.md +106 -0
- package/.genie/neurons/wish.md +104 -0
- package/.genie/product/README.md +20 -0
- package/.genie/product/cli-automation.md +359 -0
- package/.genie/product/environment.md +60 -0
- package/.genie/product/mission.md +60 -0
- package/.genie/product/roadmap.md +44 -0
- package/.genie/product/tech-stack.md +34 -0
- package/.genie/product/templates/context-template.md +218 -0
- package/.genie/product/templates/qa-done-report-template.md +68 -0
- package/.genie/product/templates/review-report-template.md +89 -0
- package/.genie/product/templates/wish-template.md +120 -0
- package/.genie/scripts/helpers/analyze-commit.js +195 -0
- package/.genie/scripts/helpers/bullet-counter.js +194 -0
- package/.genie/scripts/helpers/bullet-find.js +289 -0
- package/.genie/scripts/helpers/bullet-id.js +244 -0
- package/.genie/scripts/helpers/check-secrets.js +237 -0
- package/.genie/scripts/helpers/count-tokens.js +200 -0
- package/.genie/scripts/helpers/create-frontmatter.js +456 -0
- package/.genie/scripts/helpers/detect-markers.js +293 -0
- package/.genie/scripts/helpers/detect-todos.js +267 -0
- package/.genie/scripts/helpers/detect-unlabeled-blocks.js +135 -0
- package/.genie/scripts/helpers/embeddings.js +344 -0
- package/.genie/scripts/helpers/find-empty-sections.js +158 -0
- package/.genie/scripts/helpers/index.js +319 -0
- package/.genie/scripts/helpers/validate-frontmatter.js +578 -0
- package/.genie/scripts/helpers/validate-links.js +207 -0
- package/.genie/scripts/helpers/validate-paths.js +373 -0
- package/.genie/spells/README.md +9 -0
- package/.genie/spells/ace-protocol.md +118 -0
- package/.genie/spells/ask-one-at-a-time.md +175 -0
- package/.genie/spells/backup-analyzer.md +542 -0
- package/.genie/spells/blocker.md +12 -0
- package/.genie/spells/break-things-move-fast.md +56 -0
- package/.genie/spells/context-candidates.md +72 -0
- package/.genie/spells/context-critic.md +51 -0
- package/.genie/spells/defer-to-expertise.md +278 -0
- package/.genie/spells/delegate-dont-do.md +292 -0
- package/.genie/spells/error-investigation-protocol.md +328 -0
- package/.genie/spells/evidence-based-completion.md +273 -0
- package/.genie/spells/experiment.md +65 -0
- package/.genie/spells/file-creation-protocol.md +229 -0
- package/.genie/spells/forge-integration.md +281 -0
- package/.genie/spells/forge-orchestration.md +514 -0
- package/.genie/spells/gather-context.md +18 -0
- package/.genie/spells/global-health-check.md +34 -0
- package/.genie/spells/global-noop-roundtrip.md +25 -0
- package/.genie/spells/install-genie.md +1232 -0
- package/.genie/spells/install.md +82 -0
- package/.genie/spells/investigate-before-commit.md +112 -0
- package/.genie/spells/know-yourself.md +288 -0
- package/.genie/spells/learn.md +828 -0
- package/.genie/spells/mcp-diagnostic-protocol.md +246 -0
- package/.genie/spells/mcp-first.md +124 -0
- package/.genie/spells/multi-step-execution.md +67 -0
- package/.genie/spells/orchestration-boundary-protocol.md +256 -0
- package/.genie/spells/orchestrator-not-implementor.md +189 -0
- package/.genie/spells/prompt.md +746 -0
- package/.genie/spells/reflect.md +404 -0
- package/.genie/spells/routing-decision-matrix.md +368 -0
- package/.genie/spells/run-in-parallel.md +12 -0
- package/.genie/spells/session-state-updater-example.md +196 -0
- package/.genie/spells/session-state-updater.md +220 -0
- package/.genie/spells/track-long-running-tasks.md +133 -0
- package/.genie/spells/troubleshoot-infrastructure.md +176 -0
- package/.genie/spells/upgrade-genie.md +415 -0
- package/.genie/spells/url-presentation-protocol.md +301 -0
- package/.genie/spells/wish-initiation.md +158 -0
- package/.genie/spells/wish-issue-linkage.md +410 -0
- package/.genie/spells/wish-lifecycle.md +100 -0
- package/.genie/state/provider-status.json +3 -0
- package/.genie/state/version.json +16 -0
- package/AGENTS.md +422 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +21 -0
- package/Makefile +235 -0
- package/README.md +323 -0
- package/bin/pglite-server.js +457 -0
- package/ecosystem.config.cjs +23 -0
- package/examples/multi-tenant-demo.js +104 -0
- package/package.json +47 -0
- package/src/detector.js +105 -0
- package/src/index.js +177 -0
- package/src/pool.js +320 -0
- package/src/ports.js +114 -0
- package/src/protocol.js +216 -0
- package/src/registry.js +134 -0
- package/src/router.js +289 -0
- package/src/server.js +265 -0
- package/tests/benchmarks/runner.js +489 -0
- package/tests/multi-tenant.test.js +201 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import {
|
|
6
|
+
startServer,
|
|
7
|
+
stopServer,
|
|
8
|
+
list,
|
|
9
|
+
findByDataDir,
|
|
10
|
+
findByPort,
|
|
11
|
+
portInfo,
|
|
12
|
+
cleanup,
|
|
13
|
+
startMultiTenantServer
|
|
14
|
+
} from '../src/index.js';
|
|
15
|
+
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
// Global error handlers
|
|
19
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
20
|
+
// ExitStatus errors are expected from PGlite WASM cleanup - ignore them
|
|
21
|
+
if (reason && reason.name === 'ExitStatus') {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.error('ā Unhandled Promise Rejection:', reason);
|
|
25
|
+
// Don't exit - log and continue (PM2 will handle restarts if needed)
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
process.on('uncaughtException', (error) => {
|
|
29
|
+
console.error('ā Uncaught Exception:', error);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Parse CLI arguments
|
|
34
|
+
const args = process.argv.slice(2);
|
|
35
|
+
|
|
36
|
+
// Default to router mode if first arg is a flag (e.g., --port) or no args
|
|
37
|
+
let command = args[0];
|
|
38
|
+
if (command?.startsWith('--') || command === undefined) {
|
|
39
|
+
command = 'router';
|
|
40
|
+
// Don't modify args - router will parse them
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Print usage help
|
|
45
|
+
*/
|
|
46
|
+
function printHelp() {
|
|
47
|
+
console.log(`
|
|
48
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
49
|
+
ā pgserve - Multi-Tenant PostgreSQL Router using PGlite ā
|
|
50
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
51
|
+
|
|
52
|
+
USAGE:
|
|
53
|
+
pgserve <command> [options]
|
|
54
|
+
|
|
55
|
+
COMMANDS:
|
|
56
|
+
š MULTI-TENANT MODE (Recommended):
|
|
57
|
+
|
|
58
|
+
router Start multi-tenant router (single port, auto-provision)
|
|
59
|
+
--port <number> PostgreSQL port (default: 8432)
|
|
60
|
+
--dir <path> Base directory for databases (default: ./data)
|
|
61
|
+
--max <number> Max concurrent databases (default: 100)
|
|
62
|
+
--log <level> Log level: error, warn, info, debug (default: info)
|
|
63
|
+
--memory Use in-memory databases (ephemeral, for testing)
|
|
64
|
+
--no-provision Disable auto-provisioning
|
|
65
|
+
|
|
66
|
+
š¦ LEGACY MODE (Single instance):
|
|
67
|
+
|
|
68
|
+
start <dataDir> Start server for specific data directory
|
|
69
|
+
--port <number> Use specific port (default: auto-allocate 12000-12999)
|
|
70
|
+
--log <level> Log level (default: info)
|
|
71
|
+
|
|
72
|
+
stop <dataDir> Stop server by data directory
|
|
73
|
+
stop --port <number> Stop server by port
|
|
74
|
+
stop --all Stop all running instances
|
|
75
|
+
|
|
76
|
+
list List all running instances
|
|
77
|
+
|
|
78
|
+
url <dataDir> Get connection URL for instance
|
|
79
|
+
|
|
80
|
+
health <dataDir> Check health of instance
|
|
81
|
+
health --port <number> Check health by port
|
|
82
|
+
|
|
83
|
+
cleanup Remove stale instances from registry
|
|
84
|
+
|
|
85
|
+
info Show port range and system info
|
|
86
|
+
|
|
87
|
+
help Show this help message
|
|
88
|
+
|
|
89
|
+
EXAMPLES:
|
|
90
|
+
š Multi-tenant mode (RECOMMENDED):
|
|
91
|
+
|
|
92
|
+
# Start router on default port 8432
|
|
93
|
+
pgserve router
|
|
94
|
+
|
|
95
|
+
# Start on custom port with custom data directory
|
|
96
|
+
pgserve router --port 8433 --dir /var/lib/pglite
|
|
97
|
+
|
|
98
|
+
# Connect clients:
|
|
99
|
+
# postgresql://localhost:8432/user123 ā auto-creates ./data/user123/
|
|
100
|
+
# postgresql://localhost:8432/app456 ā auto-creates ./data/app456/
|
|
101
|
+
|
|
102
|
+
š¦ Legacy mode:
|
|
103
|
+
|
|
104
|
+
# Start single instance
|
|
105
|
+
pgserve start ./data/my-db --port 12000
|
|
106
|
+
|
|
107
|
+
# List instances
|
|
108
|
+
pgserve list
|
|
109
|
+
|
|
110
|
+
# Stop all
|
|
111
|
+
pgserve stop --all
|
|
112
|
+
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Format uptime
|
|
118
|
+
*/
|
|
119
|
+
function formatUptime(started) {
|
|
120
|
+
const now = new Date();
|
|
121
|
+
const start = new Date(started);
|
|
122
|
+
const diff = now - start;
|
|
123
|
+
|
|
124
|
+
const hours = Math.floor(diff / (1000 * 60 * 60));
|
|
125
|
+
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
126
|
+
|
|
127
|
+
if (hours > 0) {
|
|
128
|
+
return `${hours}h ${minutes}m`;
|
|
129
|
+
}
|
|
130
|
+
return `${minutes}m`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Command: start
|
|
135
|
+
*/
|
|
136
|
+
async function cmdStart() {
|
|
137
|
+
const dataDir = args[1];
|
|
138
|
+
|
|
139
|
+
if (!dataDir) {
|
|
140
|
+
console.error('ā Error: Data directory required');
|
|
141
|
+
console.log('Usage: pglite-server start <dataDir> [--port <number>]');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const portIndex = args.indexOf('--port');
|
|
146
|
+
const port = portIndex >= 0 ? parseInt(args[portIndex + 1], 10) : null;
|
|
147
|
+
|
|
148
|
+
const logIndex = args.indexOf('--log');
|
|
149
|
+
const logLevel = logIndex >= 0 ? args[logIndex + 1] : 'info';
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const instance = await startServer({ dataDir, port, logLevel });
|
|
153
|
+
|
|
154
|
+
console.log(`\nā
Server started successfully`);
|
|
155
|
+
console.log(`š Connection: ${instance.connectionUrl}`);
|
|
156
|
+
console.log(`š Data: ${instance.dataDir}`);
|
|
157
|
+
console.log(`š Port: ${instance.port}`);
|
|
158
|
+
console.log(`š PID: ${instance.pid}`);
|
|
159
|
+
console.log(`\nPress Ctrl+C to stop\n`);
|
|
160
|
+
|
|
161
|
+
// Keep process alive
|
|
162
|
+
await new Promise(() => {});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(`ā Failed to start server: ${error.message}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Command: stop
|
|
171
|
+
*/
|
|
172
|
+
async function cmdStop() {
|
|
173
|
+
const stopAll = args.includes('--all');
|
|
174
|
+
|
|
175
|
+
if (stopAll) {
|
|
176
|
+
const instances = list();
|
|
177
|
+
|
|
178
|
+
if (instances.length === 0) {
|
|
179
|
+
console.log('ā¹ļø No running instances');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log(`š Stopping ${instances.length} instances...`);
|
|
184
|
+
|
|
185
|
+
for (const instance of instances) {
|
|
186
|
+
try {
|
|
187
|
+
await stopServer({ dataDir: instance.dataDir });
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error(`ā ļø Failed to stop ${instance.dataDir}: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log(`ā
Stopped ${instances.length} instances`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const portIndex = args.indexOf('--port');
|
|
198
|
+
|
|
199
|
+
if (portIndex >= 0) {
|
|
200
|
+
const port = parseInt(args[portIndex + 1], 10);
|
|
201
|
+
try {
|
|
202
|
+
await stopServer({ port });
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(`ā ${error.message}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const dataDir = args[1];
|
|
211
|
+
|
|
212
|
+
if (!dataDir) {
|
|
213
|
+
console.error('ā Error: Data directory or --port required');
|
|
214
|
+
console.log('Usage: pglite-server stop <dataDir> | --port <number> | --all');
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
await stopServer({ dataDir });
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error(`ā ${error.message}`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Command: list
|
|
228
|
+
*/
|
|
229
|
+
function cmdList() {
|
|
230
|
+
const instances = list();
|
|
231
|
+
|
|
232
|
+
if (instances.length === 0) {
|
|
233
|
+
console.log('ā¹ļø No running instances');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log('\nActive Instances:');
|
|
238
|
+
console.log('āāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāā¬āāāāāāāāāāāāāā');
|
|
239
|
+
console.log('ā Port ā Data Directory ā PID ā Uptime ā');
|
|
240
|
+
console.log('āāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāā¼āāāāāāāāāāāāāā¤');
|
|
241
|
+
|
|
242
|
+
for (const instance of instances) {
|
|
243
|
+
const dataDir = instance.dataDir.length > 39
|
|
244
|
+
? '...' + instance.dataDir.slice(-36)
|
|
245
|
+
: instance.dataDir;
|
|
246
|
+
|
|
247
|
+
console.log(
|
|
248
|
+
`ā ${String(instance.port).padEnd(6)} ā ${dataDir.padEnd(43)} ā ${String(instance.pid).padEnd(6)} ā ${formatUptime(instance.started).padEnd(11)} ā`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log('āāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāā“āāāāāāāāāāāāāā');
|
|
253
|
+
console.log(`\nTotal: ${instances.length} instances`);
|
|
254
|
+
|
|
255
|
+
const info = portInfo();
|
|
256
|
+
console.log(`Port range: ${info.start}-${info.end} (${info.used}/${info.total} used)\n`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Command: url
|
|
261
|
+
*/
|
|
262
|
+
function cmdUrl() {
|
|
263
|
+
const dataDir = args[1];
|
|
264
|
+
|
|
265
|
+
if (!dataDir) {
|
|
266
|
+
console.error('ā Error: Data directory required');
|
|
267
|
+
console.log('Usage: pglite-server url <dataDir>');
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const instance = findByDataDir(dataDir);
|
|
272
|
+
|
|
273
|
+
if (!instance) {
|
|
274
|
+
console.error(`ā No running instance found for ${dataDir}`);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log(`postgresql://localhost:${instance.port}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Command: health
|
|
283
|
+
*/
|
|
284
|
+
function cmdHealth() {
|
|
285
|
+
const portIndex = args.indexOf('--port');
|
|
286
|
+
let instance;
|
|
287
|
+
|
|
288
|
+
if (portIndex >= 0) {
|
|
289
|
+
const port = parseInt(args[portIndex + 1], 10);
|
|
290
|
+
instance = findByPort(port);
|
|
291
|
+
} else {
|
|
292
|
+
const dataDir = args[1];
|
|
293
|
+
if (!dataDir) {
|
|
294
|
+
console.error('ā Error: Data directory or --port required');
|
|
295
|
+
console.log('Usage: pglite-server health <dataDir> | --port <number>');
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
instance = findByDataDir(dataDir);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!instance) {
|
|
302
|
+
console.error('ā No running instance found');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
console.log(`\nā
Instance healthy`);
|
|
307
|
+
console.log(`š URL: postgresql://localhost:${instance.port}`);
|
|
308
|
+
console.log(`š Data: ${instance.dataDir}`);
|
|
309
|
+
console.log(`š Port: ${instance.port}`);
|
|
310
|
+
console.log(`š PID: ${instance.pid}`);
|
|
311
|
+
console.log(`ā±ļø Uptime: ${formatUptime(instance.started)}`);
|
|
312
|
+
console.log(`š Version: ${instance.version}\n`);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Command: info
|
|
317
|
+
*/
|
|
318
|
+
function cmdInfo() {
|
|
319
|
+
const info = portInfo();
|
|
320
|
+
|
|
321
|
+
console.log('\nš Port Range Information:');
|
|
322
|
+
console.log(` Range: ${info.start}-${info.end}`);
|
|
323
|
+
console.log(` Total: ${info.total} ports`);
|
|
324
|
+
console.log(` Used: ${info.used} ports`);
|
|
325
|
+
console.log(` Available: ${info.available} ports`);
|
|
326
|
+
|
|
327
|
+
if (info.usedPorts.length > 0) {
|
|
328
|
+
console.log(` Active ports: ${info.usedPorts.join(', ')}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log('');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Command: cleanup
|
|
336
|
+
*/
|
|
337
|
+
function cmdCleanup() {
|
|
338
|
+
const cleaned = cleanup();
|
|
339
|
+
|
|
340
|
+
if (cleaned === 0) {
|
|
341
|
+
console.log('ā
No stale instances to clean up');
|
|
342
|
+
} else {
|
|
343
|
+
console.log(`ā
Cleaned up ${cleaned} stale instance${cleaned > 1 ? 's' : ''}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Command: router (multi-tenant mode)
|
|
349
|
+
*/
|
|
350
|
+
async function cmdRouter() {
|
|
351
|
+
// Parse options
|
|
352
|
+
const portIndex = args.indexOf('--port');
|
|
353
|
+
const port = portIndex >= 0 ? parseInt(args[portIndex + 1], 10) : 8432;
|
|
354
|
+
|
|
355
|
+
const dirIndex = args.indexOf('--dir');
|
|
356
|
+
const dataDir = dirIndex >= 0 ? args[dirIndex + 1] : './data';
|
|
357
|
+
|
|
358
|
+
const maxIndex = args.indexOf('--max');
|
|
359
|
+
const maxInstances = maxIndex >= 0 ? parseInt(args[maxIndex + 1], 10) : 100;
|
|
360
|
+
|
|
361
|
+
const logIndex = args.indexOf('--log');
|
|
362
|
+
const logLevel = logIndex >= 0 ? args[logIndex + 1] : 'info';
|
|
363
|
+
|
|
364
|
+
const memoryMode = args.includes('--memory');
|
|
365
|
+
const autoProvision = !args.includes('--no-provision');
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
console.log(`
|
|
369
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
370
|
+
ā Starting Multi-Tenant PostgreSQL Router ā
|
|
371
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
372
|
+
`);
|
|
373
|
+
|
|
374
|
+
const router = await startMultiTenantServer({
|
|
375
|
+
port,
|
|
376
|
+
baseDir: memoryMode ? null : dataDir,
|
|
377
|
+
memoryMode,
|
|
378
|
+
maxInstances,
|
|
379
|
+
logLevel,
|
|
380
|
+
autoProvision
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
console.log(`
|
|
384
|
+
ā
Multi-tenant router started successfully!
|
|
385
|
+
|
|
386
|
+
š PostgreSQL endpoint: postgresql://localhost:${port}/<database>
|
|
387
|
+
š Data directory: ${memoryMode ? '(in-memory)' : dataDir}
|
|
388
|
+
šÆ Auto-provision: ${autoProvision ? 'enabled' : 'disabled'}
|
|
389
|
+
š¾ Mode: ${memoryMode ? 'In-memory (ephemeral)' : 'Persistent'}
|
|
390
|
+
š Max instances: ${maxInstances}
|
|
391
|
+
|
|
392
|
+
š” Examples:
|
|
393
|
+
postgresql://localhost:${port}/user123 ā ${memoryMode ? 'Creates in-memory database' : `Creates ${dataDir}/user123/`}
|
|
394
|
+
postgresql://localhost:${port}/app456 ā ${memoryMode ? 'Creates in-memory database' : `Creates ${dataDir}/app456/`}
|
|
395
|
+
|
|
396
|
+
Press Ctrl+C to stop
|
|
397
|
+
`);
|
|
398
|
+
|
|
399
|
+
// Keep process alive
|
|
400
|
+
await new Promise(() => {});
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.error(`ā Failed to start router: ${error.message}`);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Main CLI router
|
|
408
|
+
async function main() {
|
|
409
|
+
switch (command) {
|
|
410
|
+
case 'router':
|
|
411
|
+
await cmdRouter();
|
|
412
|
+
break;
|
|
413
|
+
|
|
414
|
+
case 'start':
|
|
415
|
+
await cmdStart();
|
|
416
|
+
break;
|
|
417
|
+
|
|
418
|
+
case 'stop':
|
|
419
|
+
await cmdStop();
|
|
420
|
+
break;
|
|
421
|
+
|
|
422
|
+
case 'list':
|
|
423
|
+
cmdList();
|
|
424
|
+
break;
|
|
425
|
+
|
|
426
|
+
case 'url':
|
|
427
|
+
cmdUrl();
|
|
428
|
+
break;
|
|
429
|
+
|
|
430
|
+
case 'health':
|
|
431
|
+
cmdHealth();
|
|
432
|
+
break;
|
|
433
|
+
|
|
434
|
+
case 'info':
|
|
435
|
+
cmdInfo();
|
|
436
|
+
break;
|
|
437
|
+
|
|
438
|
+
case 'cleanup':
|
|
439
|
+
cmdCleanup();
|
|
440
|
+
break;
|
|
441
|
+
|
|
442
|
+
case 'help':
|
|
443
|
+
case undefined:
|
|
444
|
+
printHelp();
|
|
445
|
+
break;
|
|
446
|
+
|
|
447
|
+
default:
|
|
448
|
+
console.error(`ā Unknown command: ${command}`);
|
|
449
|
+
printHelp();
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
main().catch((error) => {
|
|
455
|
+
console.error(`ā Error: ${error.message}`);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
apps: [
|
|
3
|
+
{
|
|
4
|
+
name: 'PGlite Local Server',
|
|
5
|
+
script: './bin/pglite-server.js',
|
|
6
|
+
args: 'start ./data/genieos-local --port 12000 --log info',
|
|
7
|
+
cwd: '/home/namastex/dev/pglite-embedded-server',
|
|
8
|
+
interpreter: 'node',
|
|
9
|
+
instances: 1,
|
|
10
|
+
autorestart: true,
|
|
11
|
+
watch: false,
|
|
12
|
+
max_memory_restart: '512M',
|
|
13
|
+
env: {
|
|
14
|
+
NODE_ENV: 'production'
|
|
15
|
+
},
|
|
16
|
+
error_file: '/home/namastex/logs/pglite-server-error.log',
|
|
17
|
+
out_file: '/home/namastex/logs/pglite-server-out.log',
|
|
18
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
|
19
|
+
merge_logs: true,
|
|
20
|
+
time: true
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Tenant Router Demo
|
|
3
|
+
*
|
|
4
|
+
* Shows how to use the new multi-tenant architecture
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { startMultiTenantServer } from '../src/index.js';
|
|
8
|
+
import pg from 'pg';
|
|
9
|
+
|
|
10
|
+
const { Client } = pg;
|
|
11
|
+
|
|
12
|
+
async function demo() {
|
|
13
|
+
console.log('š Starting multi-tenant router demo...\n');
|
|
14
|
+
|
|
15
|
+
// Start multi-tenant router
|
|
16
|
+
const router = await startMultiTenantServer({
|
|
17
|
+
port: 15432,
|
|
18
|
+
baseDir: './demo-data',
|
|
19
|
+
logLevel: 'info'
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
console.log('\nš Initial stats:');
|
|
23
|
+
console.log(JSON.stringify(router.getStats(), null, 2));
|
|
24
|
+
|
|
25
|
+
// Connect to database "user123" (auto-created)
|
|
26
|
+
console.log('\nš„ Connecting to database: user123');
|
|
27
|
+
const client1 = new Client({
|
|
28
|
+
host: '127.0.0.1',
|
|
29
|
+
port: 15432,
|
|
30
|
+
database: 'user123'
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await client1.connect();
|
|
34
|
+
console.log('ā
Connected to user123');
|
|
35
|
+
|
|
36
|
+
// Create table and insert data
|
|
37
|
+
await client1.query('CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)');
|
|
38
|
+
await client1.query("INSERT INTO users (name) VALUES ('Alice'), ('Bob')");
|
|
39
|
+
|
|
40
|
+
const result1 = await client1.query('SELECT * FROM users');
|
|
41
|
+
console.log('š user123 data:', result1.rows);
|
|
42
|
+
|
|
43
|
+
await client1.end();
|
|
44
|
+
console.log('š Disconnected from user123');
|
|
45
|
+
|
|
46
|
+
// Connect to database "app456" (auto-created)
|
|
47
|
+
console.log('\nš„ Connecting to database: app456');
|
|
48
|
+
const client2 = new Client({
|
|
49
|
+
host: '127.0.0.1',
|
|
50
|
+
port: 15432,
|
|
51
|
+
database: 'app456'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await client2.connect();
|
|
55
|
+
console.log('ā
Connected to app456');
|
|
56
|
+
|
|
57
|
+
// Different schema in different database
|
|
58
|
+
await client2.query('CREATE TABLE posts (id SERIAL PRIMARY KEY, title TEXT)');
|
|
59
|
+
await client2.query("INSERT INTO posts (title) VALUES ('Hello World'), ('Multi-tenant magic')");
|
|
60
|
+
|
|
61
|
+
const result2 = await client2.query('SELECT * FROM posts');
|
|
62
|
+
console.log('š app456 data:', result2.rows);
|
|
63
|
+
|
|
64
|
+
await client2.end();
|
|
65
|
+
console.log('š Disconnected from app456');
|
|
66
|
+
|
|
67
|
+
// Show final stats
|
|
68
|
+
console.log('\nš Final stats:');
|
|
69
|
+
console.log(JSON.stringify(router.getStats(), null, 2));
|
|
70
|
+
|
|
71
|
+
console.log('\nš All databases:');
|
|
72
|
+
console.log(JSON.stringify(router.listDatabases(), null, 2));
|
|
73
|
+
|
|
74
|
+
// Reconnect to user123 to verify data persists
|
|
75
|
+
console.log('\nš Reconnecting to user123 to verify data persists...');
|
|
76
|
+
const client1Again = new Client({
|
|
77
|
+
host: '127.0.0.1',
|
|
78
|
+
port: 15432,
|
|
79
|
+
database: 'user123'
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await client1Again.connect();
|
|
83
|
+
const persistedData = await client1Again.query('SELECT * FROM users');
|
|
84
|
+
console.log('ā
Persisted data in user123:', persistedData.rows);
|
|
85
|
+
|
|
86
|
+
await client1Again.end();
|
|
87
|
+
|
|
88
|
+
// Stop router
|
|
89
|
+
console.log('\nš Stopping router...');
|
|
90
|
+
await router.stop();
|
|
91
|
+
|
|
92
|
+
console.log('\nā
Demo complete!');
|
|
93
|
+
console.log('\nšÆ Key achievements:');
|
|
94
|
+
console.log(' ⢠Single port (15432) handled multiple databases');
|
|
95
|
+
console.log(' ⢠Auto-provisioned user123 and app456');
|
|
96
|
+
console.log(' ⢠Data isolated between databases');
|
|
97
|
+
console.log(' ⢠Data persisted across reconnections');
|
|
98
|
+
console.log(' ⢠Zero configuration required!');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
demo().catch((error) => {
|
|
102
|
+
console.error('ā Demo failed:', error);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pgserve",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Multi-instance PostgreSQL embedded server using PGlite - zero config, auto-port allocation, perfect for development and embedded apps",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pgserve": "./bin/pglite-server.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"bench": "node tests/benchmarks/runner.js",
|
|
12
|
+
"test": "node --test tests/**/*.test.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"pglite",
|
|
16
|
+
"postgresql",
|
|
17
|
+
"embedded",
|
|
18
|
+
"database",
|
|
19
|
+
"wasm",
|
|
20
|
+
"electron",
|
|
21
|
+
"development",
|
|
22
|
+
"multi-instance"
|
|
23
|
+
],
|
|
24
|
+
"author": "Namastex Labs <labs@namastex.com>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/namastexlabs/pglite-embedded-server.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/namastexlabs/pglite-embedded-server/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/namastexlabs/pglite-embedded-server#readme",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@electric-sql/pglite": "^0.3.14",
|
|
36
|
+
"@electric-sql/pglite-socket": "^0.0.19",
|
|
37
|
+
"pino": "^10.1.0",
|
|
38
|
+
"pino-pretty": "^13.1.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"better-sqlite3": "^11.10.0",
|
|
42
|
+
"pg": "^8.16.3"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|