pgserve 0.1.4 → 1.0.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/.claude/settings.local.json +11 -0
- package/README.md +281 -256
- package/bin/pglite-server.js +212 -395
- package/package.json +13 -10
- package/src/cluster.js +322 -0
- package/src/index.js +8 -171
- package/src/postgres.js +479 -0
- package/src/protocol.js +49 -10
- package/src/router.js +117 -114
- package/src/sync.js +344 -0
- package/tests/benchmarks/runner.js +300 -155
- package/tests/sync-perf-test.js +150 -0
- package/src/detector.js +0 -105
- package/src/pool.js +0 -320
- package/src/ports.js +0 -114
- package/src/registry.js +0 -134
- package/src/server.js +0 -265
package/bin/pglite-server.js
CHANGED
|
@@ -1,458 +1,275 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* pgserve - Embedded PostgreSQL Server
|
|
5
|
+
*
|
|
6
|
+
* True concurrent connections, zero config, auto-provision databases.
|
|
7
|
+
* Uses embedded-postgres (real PostgreSQL binaries).
|
|
8
|
+
*/
|
|
9
|
+
|
|
3
10
|
import { fileURLToPath } from 'url';
|
|
4
11
|
import path from 'path';
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
list,
|
|
9
|
-
findByDataDir,
|
|
10
|
-
findByPort,
|
|
11
|
-
portInfo,
|
|
12
|
-
cleanup,
|
|
13
|
-
startMultiTenantServer
|
|
14
|
-
} from '../src/index.js';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import { startMultiTenantServer } from '../src/index.js';
|
|
14
|
+
import { startClusterServer } from '../src/cluster.js';
|
|
15
15
|
|
|
16
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
|
|
18
18
|
// Global error handlers
|
|
19
19
|
process.on('unhandledRejection', (reason, promise) => {
|
|
20
|
-
|
|
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)
|
|
20
|
+
console.error('Unhandled Promise Rejection:', reason);
|
|
26
21
|
});
|
|
27
22
|
|
|
28
23
|
process.on('uncaughtException', (error) => {
|
|
29
|
-
console.error('
|
|
24
|
+
console.error('Uncaught Exception:', error);
|
|
30
25
|
process.exit(1);
|
|
31
26
|
});
|
|
32
27
|
|
|
33
28
|
// Parse CLI arguments
|
|
34
29
|
const args = process.argv.slice(2);
|
|
35
30
|
|
|
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
31
|
/**
|
|
44
32
|
* Print usage help
|
|
45
33
|
*/
|
|
46
34
|
function printHelp() {
|
|
47
35
|
console.log(`
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
--data <path> Base directory for databases (enables persistence)
|
|
61
|
-
--max <number> Max concurrent databases (default: 100)
|
|
62
|
-
--log <level> Log level: error, warn, info, debug (default: info)
|
|
63
|
-
--no-provision Disable auto-provisioning
|
|
64
|
-
|
|
65
|
-
📦 LEGACY MODE (Single instance):
|
|
66
|
-
|
|
67
|
-
start <dataDir> Start server for specific data directory
|
|
68
|
-
--port <number> Use specific port (default: auto-allocate 12000-12999)
|
|
69
|
-
--log <level> Log level (default: info)
|
|
70
|
-
|
|
71
|
-
stop <dataDir> Stop server by data directory
|
|
72
|
-
stop --port <number> Stop server by port
|
|
73
|
-
stop --all Stop all running instances
|
|
74
|
-
|
|
75
|
-
list List all running instances
|
|
76
|
-
|
|
77
|
-
url <dataDir> Get connection URL for instance
|
|
78
|
-
|
|
79
|
-
health <dataDir> Check health of instance
|
|
80
|
-
health --port <number> Check health by port
|
|
81
|
-
|
|
82
|
-
cleanup Remove stale instances from registry
|
|
36
|
+
pgserve - Embedded PostgreSQL Server
|
|
37
|
+
=====================================
|
|
83
38
|
|
|
84
|
-
|
|
39
|
+
True concurrent connections, zero config, auto-provision databases.
|
|
85
40
|
|
|
86
|
-
|
|
41
|
+
USAGE:
|
|
42
|
+
pgserve [options]
|
|
43
|
+
|
|
44
|
+
OPTIONS:
|
|
45
|
+
--port <number> PostgreSQL port (default: 5432)
|
|
46
|
+
--data <path> Data directory for persistence (default: in-memory)
|
|
47
|
+
--host <host> Host to bind to (default: 127.0.0.1)
|
|
48
|
+
--log <level> Log level: error, warn, info, debug (default: info)
|
|
49
|
+
--cluster Force cluster mode (auto-enabled on multi-core systems)
|
|
50
|
+
--no-cluster Force single-process mode (disables auto-cluster)
|
|
51
|
+
--workers <n> Number of worker processes (default: CPU cores)
|
|
52
|
+
--no-provision Disable auto-provisioning of databases
|
|
53
|
+
--sync-to <url> Sync to real PostgreSQL (async replication)
|
|
54
|
+
--sync-databases Database patterns to sync (comma-separated, e.g. "myapp,tenant_*")
|
|
55
|
+
--help Show this help message
|
|
56
|
+
|
|
57
|
+
MODES:
|
|
58
|
+
In-memory (default): Fast, ephemeral - data lost on restart
|
|
59
|
+
Persistent: Use --data to persist databases to disk
|
|
87
60
|
|
|
88
61
|
EXAMPLES:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
# Start router (in-memory mode, default)
|
|
62
|
+
# Start in memory mode (default, fast, ephemeral)
|
|
92
63
|
pgserve
|
|
93
64
|
|
|
94
65
|
# Start with persistent storage
|
|
95
66
|
pgserve --data ./data
|
|
96
67
|
|
|
97
|
-
# Custom port
|
|
98
|
-
pgserve --port
|
|
99
|
-
|
|
100
|
-
# Connect clients:
|
|
101
|
-
# postgresql://localhost:8432/user123 → in-memory db "user123"
|
|
102
|
-
# postgresql://localhost:8432/app456 → in-memory db "app456"
|
|
68
|
+
# Custom port
|
|
69
|
+
pgserve --port 5433
|
|
103
70
|
|
|
104
|
-
|
|
71
|
+
# Sync to real PostgreSQL (async replication)
|
|
72
|
+
pgserve --sync-to "postgresql://user:pass@host:5432/db"
|
|
105
73
|
|
|
106
|
-
#
|
|
107
|
-
pgserve
|
|
74
|
+
# Sync specific databases
|
|
75
|
+
pgserve --sync-to "postgresql://..." --sync-databases "myapp,tenant_*"
|
|
108
76
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
#
|
|
113
|
-
pgserve stop --all
|
|
77
|
+
CONNECTING:
|
|
78
|
+
# Any PostgreSQL client works (psql, pg, Prisma, etc.)
|
|
79
|
+
postgresql://localhost:5432/mydb # Auto-creates "mydb" database
|
|
80
|
+
postgresql://localhost:5432/app123 # Auto-creates "app123" database
|
|
114
81
|
|
|
82
|
+
FEATURES:
|
|
83
|
+
- TRUE concurrent connections (native PostgreSQL)
|
|
84
|
+
- Auto-provision databases on first connection
|
|
85
|
+
- Zero configuration required
|
|
86
|
+
- PostgreSQL 17 (native binaries, auto-downloaded)
|
|
115
87
|
`);
|
|
116
88
|
}
|
|
117
89
|
|
|
118
90
|
/**
|
|
119
|
-
*
|
|
120
|
-
*/
|
|
121
|
-
function formatUptime(started) {
|
|
122
|
-
const now = new Date();
|
|
123
|
-
const start = new Date(started);
|
|
124
|
-
const diff = now - start;
|
|
125
|
-
|
|
126
|
-
const hours = Math.floor(diff / (1000 * 60 * 60));
|
|
127
|
-
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
128
|
-
|
|
129
|
-
if (hours > 0) {
|
|
130
|
-
return `${hours}h ${minutes}m`;
|
|
131
|
-
}
|
|
132
|
-
return `${minutes}m`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Command: start
|
|
137
|
-
*/
|
|
138
|
-
async function cmdStart() {
|
|
139
|
-
const dataDir = args[1];
|
|
140
|
-
|
|
141
|
-
if (!dataDir) {
|
|
142
|
-
console.error('❌ Error: Data directory required');
|
|
143
|
-
console.log('Usage: pglite-server start <dataDir> [--port <number>]');
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const portIndex = args.indexOf('--port');
|
|
148
|
-
const port = portIndex >= 0 ? parseInt(args[portIndex + 1], 10) : null;
|
|
149
|
-
|
|
150
|
-
const logIndex = args.indexOf('--log');
|
|
151
|
-
const logLevel = logIndex >= 0 ? args[logIndex + 1] : 'info';
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
const instance = await startServer({ dataDir, port, logLevel });
|
|
155
|
-
|
|
156
|
-
console.log(`\n✅ Server started successfully`);
|
|
157
|
-
console.log(`📍 Connection: ${instance.connectionUrl}`);
|
|
158
|
-
console.log(`📁 Data: ${instance.dataDir}`);
|
|
159
|
-
console.log(`🔌 Port: ${instance.port}`);
|
|
160
|
-
console.log(`🆔 PID: ${instance.pid}`);
|
|
161
|
-
console.log(`\nPress Ctrl+C to stop\n`);
|
|
162
|
-
|
|
163
|
-
// Keep process alive
|
|
164
|
-
await new Promise(() => {});
|
|
165
|
-
} catch (error) {
|
|
166
|
-
console.error(`❌ Failed to start server: ${error.message}`);
|
|
167
|
-
process.exit(1);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Command: stop
|
|
173
|
-
*/
|
|
174
|
-
async function cmdStop() {
|
|
175
|
-
const stopAll = args.includes('--all');
|
|
176
|
-
|
|
177
|
-
if (stopAll) {
|
|
178
|
-
const instances = list();
|
|
179
|
-
|
|
180
|
-
if (instances.length === 0) {
|
|
181
|
-
console.log('ℹ️ No running instances');
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
console.log(`🛑 Stopping ${instances.length} instances...`);
|
|
186
|
-
|
|
187
|
-
for (const instance of instances) {
|
|
188
|
-
try {
|
|
189
|
-
await stopServer({ dataDir: instance.dataDir });
|
|
190
|
-
} catch (error) {
|
|
191
|
-
console.error(`⚠️ Failed to stop ${instance.dataDir}: ${error.message}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
console.log(`✅ Stopped ${instances.length} instances`);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const portIndex = args.indexOf('--port');
|
|
200
|
-
|
|
201
|
-
if (portIndex >= 0) {
|
|
202
|
-
const port = parseInt(args[portIndex + 1], 10);
|
|
203
|
-
try {
|
|
204
|
-
await stopServer({ port });
|
|
205
|
-
} catch (error) {
|
|
206
|
-
console.error(`❌ ${error.message}`);
|
|
207
|
-
process.exit(1);
|
|
208
|
-
}
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const dataDir = args[1];
|
|
213
|
-
|
|
214
|
-
if (!dataDir) {
|
|
215
|
-
console.error('❌ Error: Data directory or --port required');
|
|
216
|
-
console.log('Usage: pglite-server stop <dataDir> | --port <number> | --all');
|
|
217
|
-
process.exit(1);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
await stopServer({ dataDir });
|
|
222
|
-
} catch (error) {
|
|
223
|
-
console.error(`❌ ${error.message}`);
|
|
224
|
-
process.exit(1);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Command: list
|
|
230
|
-
*/
|
|
231
|
-
function cmdList() {
|
|
232
|
-
const instances = list();
|
|
233
|
-
|
|
234
|
-
if (instances.length === 0) {
|
|
235
|
-
console.log('ℹ️ No running instances');
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
console.log('\nActive Instances:');
|
|
240
|
-
console.log('┌────────┬─────────────────────────────────────────────┬────────┬─────────────┐');
|
|
241
|
-
console.log('│ Port │ Data Directory │ PID │ Uptime │');
|
|
242
|
-
console.log('├────────┼─────────────────────────────────────────────┼────────┼─────────────┤');
|
|
243
|
-
|
|
244
|
-
for (const instance of instances) {
|
|
245
|
-
const dataDir = instance.dataDir.length > 39
|
|
246
|
-
? '...' + instance.dataDir.slice(-36)
|
|
247
|
-
: instance.dataDir;
|
|
248
|
-
|
|
249
|
-
console.log(
|
|
250
|
-
`│ ${String(instance.port).padEnd(6)} │ ${dataDir.padEnd(43)} │ ${String(instance.pid).padEnd(6)} │ ${formatUptime(instance.started).padEnd(11)} │`
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
console.log('└────────┴─────────────────────────────────────────────┴────────┴─────────────┘');
|
|
255
|
-
console.log(`\nTotal: ${instances.length} instances`);
|
|
256
|
-
|
|
257
|
-
const info = portInfo();
|
|
258
|
-
console.log(`Port range: ${info.start}-${info.end} (${info.used}/${info.total} used)\n`);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Command: url
|
|
91
|
+
* Parse command line arguments
|
|
263
92
|
*/
|
|
264
|
-
function
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
93
|
+
function parseArgs() {
|
|
94
|
+
// Auto-enable cluster mode on multi-core systems for best performance
|
|
95
|
+
const cpuCount = os.cpus().length;
|
|
96
|
+
|
|
97
|
+
const options = {
|
|
98
|
+
port: 5432,
|
|
99
|
+
host: '127.0.0.1',
|
|
100
|
+
dataDir: null, // null = memory mode
|
|
101
|
+
logLevel: 'info',
|
|
102
|
+
autoProvision: true,
|
|
103
|
+
cluster: cpuCount > 1, // Auto-enable on multi-core (use --no-cluster to disable)
|
|
104
|
+
workers: null, // null = use CPU count
|
|
105
|
+
syncTo: null, // Sync target PostgreSQL URL
|
|
106
|
+
syncDatabases: null // Database patterns to sync (comma-separated)
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < args.length; i++) {
|
|
110
|
+
const arg = args[i];
|
|
111
|
+
|
|
112
|
+
switch (arg) {
|
|
113
|
+
case '--port':
|
|
114
|
+
case '-p':
|
|
115
|
+
options.port = parseInt(args[++i], 10);
|
|
116
|
+
break;
|
|
117
|
+
|
|
118
|
+
case '--data':
|
|
119
|
+
case '-d':
|
|
120
|
+
options.dataDir = args[++i];
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case '--host':
|
|
124
|
+
case '-h':
|
|
125
|
+
options.host = args[++i];
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
case '--log':
|
|
129
|
+
case '-l':
|
|
130
|
+
options.logLevel = args[++i];
|
|
131
|
+
break;
|
|
132
|
+
|
|
133
|
+
case '--cluster':
|
|
134
|
+
options.cluster = true;
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case '--no-cluster':
|
|
138
|
+
options.cluster = false;
|
|
139
|
+
break;
|
|
140
|
+
|
|
141
|
+
case '--workers':
|
|
142
|
+
options.workers = parseInt(args[++i], 10);
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case '--no-provision':
|
|
146
|
+
options.autoProvision = false;
|
|
147
|
+
break;
|
|
148
|
+
|
|
149
|
+
case '--sync-to':
|
|
150
|
+
options.syncTo = args[++i];
|
|
151
|
+
break;
|
|
152
|
+
|
|
153
|
+
case '--sync-databases':
|
|
154
|
+
options.syncDatabases = args[++i];
|
|
155
|
+
break;
|
|
156
|
+
|
|
157
|
+
case '--help':
|
|
158
|
+
case 'help':
|
|
159
|
+
printHelp();
|
|
160
|
+
process.exit(0);
|
|
161
|
+
|
|
162
|
+
default:
|
|
163
|
+
if (arg.startsWith('-')) {
|
|
164
|
+
console.error(`Unknown option: ${arg}`);
|
|
165
|
+
printHelp();
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
299
168
|
}
|
|
300
|
-
instance = findByDataDir(dataDir);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (!instance) {
|
|
304
|
-
console.error('❌ No running instance found');
|
|
305
|
-
process.exit(1);
|
|
306
169
|
}
|
|
307
170
|
|
|
308
|
-
|
|
309
|
-
console.log(`📍 URL: postgresql://localhost:${instance.port}`);
|
|
310
|
-
console.log(`📁 Data: ${instance.dataDir}`);
|
|
311
|
-
console.log(`🔌 Port: ${instance.port}`);
|
|
312
|
-
console.log(`🆔 PID: ${instance.pid}`);
|
|
313
|
-
console.log(`⏱️ Uptime: ${formatUptime(instance.started)}`);
|
|
314
|
-
console.log(`📊 Version: ${instance.version}\n`);
|
|
171
|
+
return options;
|
|
315
172
|
}
|
|
316
173
|
|
|
317
174
|
/**
|
|
318
|
-
*
|
|
175
|
+
* Main entry point
|
|
319
176
|
*/
|
|
320
|
-
function
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
console.log('\n📊 Port Range Information:');
|
|
324
|
-
console.log(` Range: ${info.start}-${info.end}`);
|
|
325
|
-
console.log(` Total: ${info.total} ports`);
|
|
326
|
-
console.log(` Used: ${info.used} ports`);
|
|
327
|
-
console.log(` Available: ${info.available} ports`);
|
|
328
|
-
|
|
329
|
-
if (info.usedPorts.length > 0) {
|
|
330
|
-
console.log(` Active ports: ${info.usedPorts.join(', ')}`);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
console.log('');
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Command: cleanup
|
|
338
|
-
*/
|
|
339
|
-
function cmdCleanup() {
|
|
340
|
-
const cleaned = cleanup();
|
|
341
|
-
|
|
342
|
-
if (cleaned === 0) {
|
|
343
|
-
console.log('✅ No stale instances to clean up');
|
|
344
|
-
} else {
|
|
345
|
-
console.log(`✅ Cleaned up ${cleaned} stale instance${cleaned > 1 ? 's' : ''}`);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Command: router (multi-tenant mode)
|
|
351
|
-
*/
|
|
352
|
-
async function cmdRouter() {
|
|
353
|
-
// Parse options
|
|
354
|
-
const portIndex = args.indexOf('--port');
|
|
355
|
-
const port = portIndex >= 0 ? parseInt(args[portIndex + 1], 10) : 8432;
|
|
356
|
-
|
|
357
|
-
const dataIndex = args.indexOf('--data');
|
|
358
|
-
const dataDir = dataIndex >= 0 ? args[dataIndex + 1] : null;
|
|
359
|
-
const memoryMode = dataDir === null;
|
|
360
|
-
|
|
361
|
-
const maxIndex = args.indexOf('--max');
|
|
362
|
-
const maxInstances = maxIndex >= 0 ? parseInt(args[maxIndex + 1], 10) : 100;
|
|
363
|
-
|
|
364
|
-
const logIndex = args.indexOf('--log');
|
|
365
|
-
const logLevel = logIndex >= 0 ? args[logIndex + 1] : 'info';
|
|
366
|
-
const autoProvision = !args.includes('--no-provision');
|
|
177
|
+
async function main() {
|
|
178
|
+
const options = parseArgs();
|
|
179
|
+
const memoryMode = !options.dataDir;
|
|
367
180
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
║ Starting Multi-Tenant PostgreSQL Router ║
|
|
372
|
-
╚═══════════════════════════════════════════════════════════════════╝
|
|
181
|
+
console.log(`
|
|
182
|
+
pgserve - Embedded PostgreSQL Server
|
|
183
|
+
=====================================
|
|
373
184
|
`);
|
|
374
185
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
186
|
+
try {
|
|
187
|
+
let server;
|
|
188
|
+
|
|
189
|
+
if (options.cluster) {
|
|
190
|
+
// Cluster mode - multi-core scaling
|
|
191
|
+
server = await startClusterServer({
|
|
192
|
+
port: options.port,
|
|
193
|
+
host: options.host,
|
|
194
|
+
baseDir: options.dataDir,
|
|
195
|
+
logLevel: options.logLevel,
|
|
196
|
+
autoProvision: options.autoProvision,
|
|
197
|
+
workers: options.workers
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Only primary process shows full startup message
|
|
201
|
+
if (server.workers) {
|
|
202
|
+
const stats = server.getStats();
|
|
203
|
+
console.log(`
|
|
204
|
+
Cluster started successfully!
|
|
205
|
+
|
|
206
|
+
Endpoint: postgresql://${options.host}:${options.port}/<database>
|
|
207
|
+
Mode: ${memoryMode ? 'In-memory (ephemeral)' : 'Persistent'} (Cluster)
|
|
208
|
+
Workers: ${stats.workers} processes
|
|
209
|
+
Data: ${memoryMode ? '(temp directory)' : options.dataDir}
|
|
210
|
+
Auto-create: ${options.autoProvision ? 'Enabled' : 'Disabled'}
|
|
211
|
+
|
|
212
|
+
Examples:
|
|
213
|
+
postgresql://${options.host}:${options.port}/myapp
|
|
214
|
+
postgresql://${options.host}:${options.port}/testdb
|
|
392
215
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
216
|
+
Press Ctrl+C to stop
|
|
217
|
+
`);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Single process mode
|
|
221
|
+
const router = await startMultiTenantServer({
|
|
222
|
+
port: options.port,
|
|
223
|
+
host: options.host,
|
|
224
|
+
baseDir: options.dataDir,
|
|
225
|
+
logLevel: options.logLevel,
|
|
226
|
+
autoProvision: options.autoProvision,
|
|
227
|
+
syncTo: options.syncTo,
|
|
228
|
+
syncDatabases: options.syncDatabases
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
server = router;
|
|
232
|
+
|
|
233
|
+
// Build sync status string
|
|
234
|
+
const syncStatus = options.syncTo
|
|
235
|
+
? `Enabled → ${options.syncTo.replace(/:[^:@]+@/, ':***@')}`
|
|
236
|
+
: 'Disabled';
|
|
237
|
+
|
|
238
|
+
console.log(`
|
|
239
|
+
Server started successfully!
|
|
240
|
+
|
|
241
|
+
Endpoint: postgresql://${options.host}:${options.port}/<database>
|
|
242
|
+
Mode: ${memoryMode ? 'In-memory (ephemeral)' : 'Persistent'}
|
|
243
|
+
Data: ${memoryMode ? '(temp directory)' : options.dataDir}
|
|
244
|
+
PostgreSQL: Port ${router.pgPort} (internal)
|
|
245
|
+
Auto-create: ${options.autoProvision ? 'Enabled' : 'Disabled'}
|
|
246
|
+
Sync: ${syncStatus}${options.syncDatabases ? ` (${options.syncDatabases})` : ''}
|
|
247
|
+
|
|
248
|
+
Examples:
|
|
249
|
+
postgresql://${options.host}:${options.port}/myapp
|
|
250
|
+
postgresql://${options.host}:${options.port}/testdb
|
|
396
251
|
|
|
397
252
|
Press Ctrl+C to stop
|
|
398
253
|
`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Graceful shutdown
|
|
257
|
+
const shutdown = async () => {
|
|
258
|
+
console.log('\nShutting down...');
|
|
259
|
+
await server.stop();
|
|
260
|
+
console.log('Server stopped.');
|
|
261
|
+
process.exit(0);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
process.on('SIGINT', shutdown);
|
|
265
|
+
process.on('SIGTERM', shutdown);
|
|
399
266
|
|
|
400
267
|
// Keep process alive
|
|
401
268
|
await new Promise(() => {});
|
|
402
269
|
} catch (error) {
|
|
403
|
-
console.error(
|
|
270
|
+
console.error(`Failed to start server:`, error);
|
|
404
271
|
process.exit(1);
|
|
405
272
|
}
|
|
406
273
|
}
|
|
407
274
|
|
|
408
|
-
|
|
409
|
-
async function main() {
|
|
410
|
-
switch (command) {
|
|
411
|
-
case 'router':
|
|
412
|
-
await cmdRouter();
|
|
413
|
-
break;
|
|
414
|
-
|
|
415
|
-
case 'start':
|
|
416
|
-
await cmdStart();
|
|
417
|
-
break;
|
|
418
|
-
|
|
419
|
-
case 'stop':
|
|
420
|
-
await cmdStop();
|
|
421
|
-
break;
|
|
422
|
-
|
|
423
|
-
case 'list':
|
|
424
|
-
cmdList();
|
|
425
|
-
break;
|
|
426
|
-
|
|
427
|
-
case 'url':
|
|
428
|
-
cmdUrl();
|
|
429
|
-
break;
|
|
430
|
-
|
|
431
|
-
case 'health':
|
|
432
|
-
cmdHealth();
|
|
433
|
-
break;
|
|
434
|
-
|
|
435
|
-
case 'info':
|
|
436
|
-
cmdInfo();
|
|
437
|
-
break;
|
|
438
|
-
|
|
439
|
-
case 'cleanup':
|
|
440
|
-
cmdCleanup();
|
|
441
|
-
break;
|
|
442
|
-
|
|
443
|
-
case 'help':
|
|
444
|
-
case undefined:
|
|
445
|
-
printHelp();
|
|
446
|
-
break;
|
|
447
|
-
|
|
448
|
-
default:
|
|
449
|
-
console.error(`❌ Unknown command: ${command}`);
|
|
450
|
-
printHelp();
|
|
451
|
-
process.exit(1);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
main().catch((error) => {
|
|
456
|
-
console.error(`❌ Error: ${error.message}`);
|
|
457
|
-
process.exit(1);
|
|
458
|
-
});
|
|
275
|
+
main();
|