pgserve 1.0.5 → 1.0.7

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.
@@ -179,10 +179,13 @@ async function main() {
179
179
  const options = parseArgs();
180
180
  const memoryMode = !options.dataDir;
181
181
 
182
- console.log(`
182
+ // Only print header if not a cluster worker (workers get PGSERVE_WORKER env)
183
+ if (!process.env.PGSERVE_WORKER) {
184
+ console.log(`
183
185
  pgserve - Embedded PostgreSQL Server
184
186
  =====================================
185
187
  `);
188
+ }
186
189
 
187
190
  try {
188
191
  let server;
@@ -254,16 +257,18 @@ Press Ctrl+C to stop
254
257
  `);
255
258
  }
256
259
 
257
- // Graceful shutdown
258
- const shutdown = async () => {
259
- console.log('\nShutting down...');
260
- await server.stop();
261
- console.log('Server stopped.');
262
- process.exit(0);
263
- };
260
+ // Graceful shutdown (only for primary/single-process, workers handle via IPC)
261
+ if (!process.env.PGSERVE_WORKER) {
262
+ const shutdown = async () => {
263
+ console.log('\nShutting down...');
264
+ await server.stop();
265
+ console.log('Server stopped.');
266
+ process.exit(0);
267
+ };
264
268
 
265
- process.on('SIGINT', shutdown);
266
- process.on('SIGTERM', shutdown);
269
+ process.on('SIGINT', shutdown);
270
+ process.on('SIGTERM', shutdown);
271
+ }
267
272
 
268
273
  // Keep process alive
269
274
  await new Promise(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgserve",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Embedded PostgreSQL server with true concurrent connections - zero config, auto-provision databases",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/cluster.js CHANGED
@@ -13,7 +13,7 @@ import cluster from 'cluster';
13
13
  import os from 'os';
14
14
  import net from 'net';
15
15
  import pg from 'pg';
16
- import pino from 'pino';
16
+ import { createLogger } from './logger.js';
17
17
  import { PostgresManager } from './postgres.js';
18
18
  import { extractDatabaseNameFromSocket } from './protocol.js';
19
19
  import { EventEmitter } from 'events';
@@ -34,7 +34,7 @@ class ClusterRouter extends EventEmitter {
34
34
  this.autoProvision = options.autoProvision !== false;
35
35
  this.maxConnections = options.maxConnections || 1000;
36
36
 
37
- this.logger = pino({ level: options.logLevel || 'info' });
37
+ this.logger = createLogger({ level: options.logLevel || 'info' });
38
38
  this.adminClient = null;
39
39
  this.server = null;
40
40
  this.connections = new Set();
@@ -74,6 +74,8 @@ class ClusterRouter extends EventEmitter {
74
74
  }
75
75
 
76
76
  this.adminClient = new pg.Client(connectionConfig);
77
+ // Suppress errors during shutdown (PostgreSQL terminating connections)
78
+ this.adminClient.on('error', () => {});
77
79
  await this.adminClient.connect();
78
80
  }
79
81
 
@@ -178,7 +180,11 @@ class ClusterRouter extends EventEmitter {
178
180
  this.connections.clear();
179
181
 
180
182
  if (this.adminClient) {
181
- await this.adminClient.end();
183
+ try {
184
+ await this.adminClient.end();
185
+ } catch {
186
+ // Ignore - connection may already be terminated
187
+ }
182
188
  }
183
189
 
184
190
  if (this.server) {
@@ -200,7 +206,7 @@ export async function startClusterServer(options = {}) {
200
206
  console.log(`[pgserve] Cluster mode: ${numWorkers} workers`);
201
207
 
202
208
  // PRIMARY: Start our embedded PostgreSQL (single instance)
203
- const logger = pino({ level: options.logLevel || 'info' });
209
+ const logger = createLogger({ level: options.logLevel || 'info' });
204
210
  const pgManager = new PostgresManager({
205
211
  dataDir: options.baseDir,
206
212
  port: pgPort,
@@ -231,11 +237,18 @@ export async function startClusterServer(options = {}) {
231
237
  workers.set(worker.id, worker);
232
238
  }
233
239
 
234
- // Restart dead workers
240
+ // Track shutdown state to prevent worker restart during shutdown
241
+ let shuttingDown = false;
242
+
243
+ // Restart dead workers (unless shutting down)
235
244
  cluster.on('exit', (worker, code, signal) => {
236
- console.log(`[pgserve] Worker ${worker.id} died (${signal || code}), restarting...`);
237
245
  workers.delete(worker.id);
238
246
 
247
+ if (shuttingDown) {
248
+ return; // Don't restart during shutdown
249
+ }
250
+
251
+ console.log(`[pgserve] Worker ${worker.id} died (${signal || code}), restarting...`);
239
252
  const newWorker = cluster.fork({
240
253
  PGSERVE_WORKER: 'true',
241
254
  PGSERVE_PORT: String(port),
@@ -270,6 +283,7 @@ export async function startClusterServer(options = {}) {
270
283
  pgSocketPath,
271
284
  stop: async () => {
272
285
  console.log('[pgserve] Stopping cluster...');
286
+ shuttingDown = true; // Prevent worker restart during shutdown
273
287
  for (const worker of workers.values()) {
274
288
  worker.send({ type: 'shutdown' });
275
289
  }
package/src/logger.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shared Logger Configuration
3
+ *
4
+ * Provides colorful, human-readable logging via pino-pretty.
5
+ * All modules should use createLogger() for consistent output.
6
+ */
7
+
8
+ import pino from 'pino';
9
+
10
+ /**
11
+ * Create a configured pino logger with pretty output
12
+ * @param {Object} options - Logger options
13
+ * @param {string} options.level - Log level (default: 'info')
14
+ * @param {string} options.component - Component name for log context
15
+ * @returns {pino.Logger} Configured pino logger
16
+ */
17
+ export function createLogger(options = {}) {
18
+ const level = options.level || process.env.LOG_LEVEL || 'info';
19
+
20
+ const logger = pino({
21
+ level,
22
+ transport: {
23
+ target: 'pino-pretty',
24
+ options: {
25
+ colorize: true,
26
+ ignore: 'pid,hostname', // Remove noise
27
+ translateTime: 'HH:MM:ss', // Short timestamp
28
+ hideObject: true, // Hide JSON attributes for clean output
29
+ }
30
+ }
31
+ });
32
+
33
+ // Return child logger with component if specified
34
+ if (options.component) {
35
+ return logger.child({ component: options.component });
36
+ }
37
+
38
+ return logger;
39
+ }
package/src/restore.js CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  import pg from 'pg';
17
17
  import { from as copyFrom, to as copyTo } from 'pg-copy-streams';
18
- import pino from 'pino';
18
+ import { createLogger } from './logger.js';
19
19
 
20
20
  /**
21
21
  * Match database name against patterns (supports wildcards)
@@ -46,7 +46,7 @@ export class RestoreManager {
46
46
  this.targetPort = options.targetPort; // Local embedded PostgreSQL port
47
47
  this.targetSocketPath = options.targetSocketPath; // Unix socket path (optional)
48
48
 
49
- this.logger = options.logger || pino({ level: options.logLevel || 'info' }).child({ component: 'restore' });
49
+ this.logger = options.logger || createLogger({ level: options.logLevel || 'info', component: 'restore' });
50
50
 
51
51
  // Connection pools (lazy initialized)
52
52
  this.sourcePool = null;
package/src/router.js CHANGED
@@ -19,7 +19,7 @@ import { RestoreManager } from './restore.js';
19
19
  import { Dashboard } from './dashboard.js';
20
20
  import { extractDatabaseNameFromSocket } from './protocol.js';
21
21
  import { EventEmitter } from 'events';
22
- import pino from 'pino';
22
+ import { createLogger } from './logger.js';
23
23
 
24
24
  /**
25
25
  * Multi-Tenant Router Server
@@ -39,13 +39,7 @@ export class MultiTenantRouter extends EventEmitter {
39
39
 
40
40
  // Pino logger (ultra-fast structured logging)
41
41
  const logLevel = options.logLevel || 'info';
42
- this.logger = options.logger || pino({
43
- level: logLevel,
44
- transport: logLevel === 'debug' ? {
45
- target: 'pino-pretty',
46
- options: { colorize: true }
47
- } : undefined
48
- });
42
+ this.logger = options.logger || createLogger({ level: logLevel });
49
43
 
50
44
  // Sync options (async replication to real PostgreSQL)
51
45
  this.syncTo = options.syncTo || null;
package/src/sync.js CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import pg from 'pg';
9
- import pino from 'pino';
9
+ import { createLogger } from './logger.js';
10
10
 
11
11
  /**
12
12
  * Match database name against patterns (supports wildcards)
@@ -36,7 +36,7 @@ export class SyncManager {
36
36
  this.sourcePort = options.sourcePort; // pgserve PostgreSQL port
37
37
  this.sourceSocketPath = options.sourceSocketPath; // pgserve socket path (optional)
38
38
 
39
- this.logger = pino({ level: options.logLevel || 'info' }).child({ component: 'sync' });
39
+ this.logger = createLogger({ level: options.logLevel || 'info', component: 'sync' });
40
40
 
41
41
  this.sourcePool = null; // Connection to pgserve's PostgreSQL
42
42
  this.targetPool = null; // Connection to real PostgreSQL