orez 0.0.38 → 0.0.40

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/dist/pg-proxy.js CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * tcp proxy that makes pglite speak postgresql wire protocol.
3
3
  *
4
- * uses pg-gateway to handle protocol lifecycle for regular connections,
5
- * and directly handles the raw socket for replication connections.
4
+ * handles the postgresql wire protocol directly using raw tcp sockets,
5
+ * avoiding pg-gateway's Duplex.toWeb() which deadlocks under concurrent
6
+ * connections with large responses.
6
7
  *
7
8
  * regular connections: forwarded to pglite via execProtocolRaw()
8
9
  * replication connections: intercepted, replication protocol faked
@@ -12,7 +13,6 @@
12
13
  * query interleaving that causes CVR concurrent modification errors.
13
14
  */
14
15
  import { createServer } from 'node:net';
15
- import { fromNodeSocket } from 'pg-gateway/node';
16
16
  import { log } from './log.js';
17
17
  import { Mutex } from './mutex.js';
18
18
  import { handleReplicationQuery, handleStartReplication } from './replication/handler.js';
@@ -55,6 +55,7 @@ const QUERY_REWRITES = [
55
55
  // parameter status messages sent during connection handshake
56
56
  // pg_restore and other tools read these to determine server capabilities
57
57
  const SERVER_PARAMS = [
58
+ ['server_version', '16.4'],
58
59
  ['server_encoding', 'UTF8'],
59
60
  ['client_encoding', 'UTF8'],
60
61
  ['DateStyle', 'ISO, MDY'],
@@ -63,7 +64,10 @@ const SERVER_PARAMS = [
63
64
  ['TimeZone', 'UTC'],
64
65
  ['IntervalStyle', 'postgres'],
65
66
  ];
66
- // build a ParameterStatus wire protocol message (type 'S', 0x53)
67
+ // queries to intercept and return no-op success (synthetic SET response)
68
+ // pglite rejects SET TRANSACTION if any query (e.g. SET search_path) ran first
69
+ const NOOP_QUERY_PATTERNS = [/^\s*SET\s+TRANSACTION\b/i, /^\s*SET\s+SESSION\b/i];
70
+ // ── wire protocol helpers ──
67
71
  function buildParameterStatus(name, value) {
68
72
  const encoder = new TextEncoder();
69
73
  const nameBytes = encoder.encode(name);
@@ -81,12 +85,58 @@ function buildParameterStatus(name, value) {
81
85
  buf[pos] = 0;
82
86
  return buf;
83
87
  }
84
- // queries to intercept and return no-op success (synthetic SET response)
85
- // pglite rejects SET TRANSACTION if any query (e.g. SET search_path) ran first
86
- const NOOP_QUERY_PATTERNS = [/^\s*SET\s+TRANSACTION\b/i, /^\s*SET\s+SESSION\b/i];
87
- /**
88
- * extract query text from a Parse message (0x50).
89
- */
88
+ function buildAuthOk() {
89
+ const buf = new Uint8Array(9);
90
+ buf[0] = 0x52; // 'R' AuthenticationOk
91
+ new DataView(buf.buffer).setInt32(1, 8);
92
+ new DataView(buf.buffer).setInt32(5, 0); // auth ok
93
+ return buf;
94
+ }
95
+ function buildAuthCleartextPassword() {
96
+ const buf = new Uint8Array(9);
97
+ buf[0] = 0x52; // 'R'
98
+ new DataView(buf.buffer).setInt32(1, 8);
99
+ new DataView(buf.buffer).setInt32(5, 3); // cleartext password
100
+ return buf;
101
+ }
102
+ function buildBackendKeyData() {
103
+ const buf = new Uint8Array(13);
104
+ buf[0] = 0x4b; // 'K'
105
+ new DataView(buf.buffer).setInt32(1, 12);
106
+ new DataView(buf.buffer).setInt32(5, process.pid);
107
+ new DataView(buf.buffer).setInt32(9, 0);
108
+ return buf;
109
+ }
110
+ function buildReadyForQuery(status = 0x49) {
111
+ const buf = new Uint8Array(6);
112
+ buf[0] = 0x5a; // 'Z'
113
+ new DataView(buf.buffer).setInt32(1, 5);
114
+ buf[5] = status; // 'I' = idle
115
+ return buf;
116
+ }
117
+ function buildErrorResponse(message) {
118
+ const encoder = new TextEncoder();
119
+ const msgBytes = encoder.encode(message);
120
+ // S(ERROR) + C(code) + M(message) + terminator
121
+ const sField = new Uint8Array([0x53, ...encoder.encode('ERROR'), 0]);
122
+ const cField = new Uint8Array([0x43, ...encoder.encode('08006'), 0]);
123
+ const mField = new Uint8Array([0x4d, ...msgBytes, 0]);
124
+ const terminator = new Uint8Array([0]);
125
+ const bodyLen = 4 + sField.length + cField.length + mField.length + terminator.length;
126
+ const buf = new Uint8Array(1 + bodyLen);
127
+ buf[0] = 0x45; // 'E'
128
+ new DataView(buf.buffer).setInt32(1, bodyLen);
129
+ let pos = 5;
130
+ buf.set(sField, pos);
131
+ pos += sField.length;
132
+ buf.set(cField, pos);
133
+ pos += cField.length;
134
+ buf.set(mField, pos);
135
+ pos += mField.length;
136
+ buf.set(terminator, pos);
137
+ return buf;
138
+ }
139
+ // ── query helpers ──
90
140
  function extractParseQuery(data) {
91
141
  if (data[0] !== 0x50)
92
142
  return null;
@@ -99,9 +149,6 @@ function extractParseQuery(data) {
99
149
  offset++;
100
150
  return new TextDecoder().decode(data.subarray(queryStart, offset));
101
151
  }
102
- /**
103
- * rebuild a Parse message with a modified query string.
104
- */
105
152
  function rebuildParseMessage(data, newQuery) {
106
153
  let offset = 5;
107
154
  while (offset < data.length && data[offset] !== 0)
@@ -129,9 +176,6 @@ function rebuildParseMessage(data, newQuery) {
129
176
  result.set(suffix, pos);
130
177
  return result;
131
178
  }
132
- /**
133
- * rebuild a Simple Query message with a modified query string.
134
- */
135
179
  function rebuildSimpleQuery(newQuery) {
136
180
  const encoder = new TextEncoder();
137
181
  const queryBytes = encoder.encode(newQuery + '\0');
@@ -141,9 +185,6 @@ function rebuildSimpleQuery(newQuery) {
141
185
  buf.set(queryBytes, 5);
142
186
  return buf;
143
187
  }
144
- /**
145
- * intercept and rewrite query messages to make pglite look like real postgres.
146
- */
147
188
  function interceptQuery(data) {
148
189
  const msgType = data[0];
149
190
  if (msgType === 0x51) {
@@ -183,9 +224,6 @@ function interceptQuery(data) {
183
224
  }
184
225
  return data;
185
226
  }
186
- /**
187
- * check if a query should be intercepted as a no-op.
188
- */
189
227
  function isNoopQuery(data) {
190
228
  let query = null;
191
229
  if (data[0] === 0x51) {
@@ -200,9 +238,6 @@ function isNoopQuery(data) {
200
238
  return false;
201
239
  return NOOP_QUERY_PATTERNS.some((p) => p.test(query));
202
240
  }
203
- /**
204
- * build a synthetic "SET" command complete response.
205
- */
206
241
  function buildSetCompleteResponse() {
207
242
  const encoder = new TextEncoder();
208
243
  const tag = encoder.encode('SET\0');
@@ -219,18 +254,12 @@ function buildSetCompleteResponse() {
219
254
  result.set(rfq, cc.length);
220
255
  return result;
221
256
  }
222
- /**
223
- * build a synthetic ParseComplete response for extended protocol no-ops.
224
- */
225
257
  function buildParseCompleteResponse() {
226
258
  const pc = new Uint8Array(5);
227
259
  pc[0] = 0x31; // ParseComplete
228
260
  new DataView(pc.buffer).setInt32(1, 4);
229
261
  return pc;
230
262
  }
231
- /**
232
- * strip ReadyForQuery messages from a response buffer.
233
- */
234
263
  function stripReadyForQuery(data) {
235
264
  if (data.length === 0)
236
265
  return data;
@@ -260,6 +289,257 @@ function stripReadyForQuery(data) {
260
289
  }
261
290
  return result;
262
291
  }
292
+ // ── socket write with backpressure ──
293
+ function socketWrite(socket, data) {
294
+ if (data.length === 0 || socket.destroyed)
295
+ return Promise.resolve();
296
+ return new Promise((resolve, reject) => {
297
+ const ok = socket.write(data, (err) => (err ? reject(err) : resolve()));
298
+ // if buffer is full, the callback still fires when flushed
299
+ if (!ok)
300
+ void 0;
301
+ });
302
+ }
303
+ // ── startup handshake ──
304
+ // parse startup message from raw bytes.
305
+ // handles SSLRequest (8 bytes, code 80877103) and StartupMessage.
306
+ function parseStartupMessage(buf) {
307
+ const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
308
+ const len = dv.getInt32(0);
309
+ const code = dv.getInt32(4);
310
+ // SSL request: length=8, code=80877103
311
+ if (len === 8 && code === 80877103) {
312
+ return { isSSL: true, params: {} };
313
+ }
314
+ // startup message: length, protocol(196608=3.0), then key=value pairs
315
+ const params = {};
316
+ let offset = 8;
317
+ while (offset < len) {
318
+ const keyStart = offset;
319
+ while (offset < buf.length && buf[offset] !== 0)
320
+ offset++;
321
+ const key = buf.subarray(keyStart, offset).toString();
322
+ offset++;
323
+ if (!key)
324
+ break; // double-null = end of params
325
+ const valStart = offset;
326
+ while (offset < buf.length && buf[offset] !== 0)
327
+ offset++;
328
+ params[key] = buf.subarray(valStart, offset).toString();
329
+ offset++;
330
+ }
331
+ return { isSSL: false, params };
332
+ }
333
+ // read exactly `n` bytes from socket
334
+ function readBytes(socket, n) {
335
+ return new Promise((resolve, reject) => {
336
+ let collected = Buffer.alloc(0);
337
+ const onData = (chunk) => {
338
+ collected = Buffer.concat([collected, chunk]);
339
+ if (collected.length >= n) {
340
+ socket.removeListener('data', onData);
341
+ socket.removeListener('error', onError);
342
+ socket.removeListener('close', onClose);
343
+ socket.pause();
344
+ resolve(collected);
345
+ }
346
+ };
347
+ const onError = (err) => {
348
+ socket.removeListener('data', onData);
349
+ socket.removeListener('close', onClose);
350
+ reject(err);
351
+ };
352
+ const onClose = () => {
353
+ socket.removeListener('data', onData);
354
+ socket.removeListener('error', onError);
355
+ reject(new Error('socket closed'));
356
+ };
357
+ socket.on('data', onData);
358
+ socket.on('error', onError);
359
+ socket.on('close', onClose);
360
+ socket.resume();
361
+ });
362
+ }
363
+ // perform the startup handshake (SSL negotiation, auth, parameter status)
364
+ async function performHandshake(socket, config) {
365
+ // read initial message length (first 4 bytes)
366
+ let buf = await readBytes(socket, 8);
367
+ // check for SSL request
368
+ const startup = parseStartupMessage(buf);
369
+ if (startup.isSSL) {
370
+ // reject SSL, client will reconnect without it
371
+ socket.write(Buffer.from('N'));
372
+ buf = await readBytes(socket, 8);
373
+ }
374
+ // now we have startup message header - read the rest if needed
375
+ const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
376
+ const msgLen = dv.getInt32(0);
377
+ if (buf.length < msgLen) {
378
+ const rest = await readBytes(socket, msgLen - buf.length);
379
+ buf = Buffer.concat([buf, rest]);
380
+ }
381
+ const { params } = parseStartupMessage(buf);
382
+ // request cleartext password
383
+ socket.write(buildAuthCleartextPassword());
384
+ // read password message: type(1) + len(4) + password + null
385
+ const pwBuf = await readBytes(socket, 5);
386
+ const pwDv = new DataView(pwBuf.buffer, pwBuf.byteOffset, pwBuf.byteLength);
387
+ const pwLen = pwDv.getInt32(1);
388
+ let fullPwBuf = pwBuf;
389
+ if (fullPwBuf.length < 1 + pwLen) {
390
+ const rest = await readBytes(socket, 1 + pwLen - fullPwBuf.length);
391
+ fullPwBuf = Buffer.concat([fullPwBuf, rest]);
392
+ }
393
+ const password = fullPwBuf.subarray(5, 1 + pwLen - 1).toString();
394
+ // validate credentials
395
+ if (params.user !== config.pgUser || password !== config.pgPassword) {
396
+ socket.write(buildErrorResponse('authentication failed'));
397
+ socket.write(buildReadyForQuery());
398
+ socket.destroy();
399
+ throw new Error('auth failed');
400
+ }
401
+ // auth ok
402
+ socket.write(buildAuthOk());
403
+ // send parameter status messages
404
+ for (const [name, value] of SERVER_PARAMS) {
405
+ socket.write(buildParameterStatus(name, value));
406
+ }
407
+ // backend key data
408
+ socket.write(buildBackendKeyData());
409
+ // ready for query
410
+ socket.write(buildReadyForQuery());
411
+ return { params };
412
+ }
413
+ // ── message loop ──
414
+ // process messages from a connected, authenticated client.
415
+ // uses callback-based 'data' events instead of async iterators
416
+ // for reliable behavior across runtimes (node.js, bun).
417
+ function messageLoop(socket, db, mutex, isReplicationConnection, replicationDb, replicationMutex) {
418
+ return new Promise((resolve, reject) => {
419
+ let buffer = Buffer.alloc(0);
420
+ let processing = false;
421
+ async function processBuffer() {
422
+ if (processing)
423
+ return;
424
+ processing = true;
425
+ socket.pause();
426
+ try {
427
+ while (buffer.length >= 5) {
428
+ const msgType = buffer[0];
429
+ const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
430
+ const msgLen = dv.getInt32(1);
431
+ const totalLen = 1 + msgLen;
432
+ if (buffer.length < totalLen)
433
+ break; // need more data
434
+ // copy message out before modifying buffer
435
+ const message = new Uint8Array(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + totalLen));
436
+ buffer = buffer.subarray(totalLen);
437
+ // handle Terminate message
438
+ if (msgType === 0x58) {
439
+ resolve();
440
+ return;
441
+ }
442
+ // handle replication connections
443
+ if (isReplicationConnection) {
444
+ await handleReplicationMsg(message, socket, replicationDb, replicationMutex);
445
+ continue;
446
+ }
447
+ // handle regular messages
448
+ await handleRegularMessage(message, socket, db, mutex);
449
+ }
450
+ }
451
+ catch (err) {
452
+ reject(err);
453
+ return;
454
+ }
455
+ processing = false;
456
+ socket.resume();
457
+ }
458
+ socket.on('data', (chunk) => {
459
+ buffer = buffer.length > 0 ? Buffer.concat([buffer, chunk]) : chunk;
460
+ processBuffer();
461
+ });
462
+ socket.on('end', () => resolve());
463
+ socket.on('error', (err) => reject(err));
464
+ socket.on('close', () => resolve());
465
+ socket.resume();
466
+ });
467
+ }
468
+ async function handleRegularMessage(data, socket, db, mutex) {
469
+ // check for no-op queries
470
+ if (isNoopQuery(data)) {
471
+ if (data[0] === 0x51) {
472
+ await socketWrite(socket, buildSetCompleteResponse());
473
+ return;
474
+ }
475
+ else if (data[0] === 0x50) {
476
+ await socketWrite(socket, buildParseCompleteResponse());
477
+ return;
478
+ }
479
+ }
480
+ // intercept and rewrite queries
481
+ data = interceptQuery(data);
482
+ // serialize pglite access
483
+ await mutex.acquire();
484
+ let result;
485
+ try {
486
+ result = await db.execProtocolRaw(data, { throwOnError: false });
487
+ }
488
+ catch (err) {
489
+ mutex.release();
490
+ throw err;
491
+ }
492
+ // strip ReadyForQuery from non-Sync/non-SimpleQuery responses
493
+ if (data[0] !== 0x53 && data[0] !== 0x51) {
494
+ result = stripReadyForQuery(result);
495
+ }
496
+ mutex.release();
497
+ // write response directly to socket
498
+ await socketWrite(socket, result);
499
+ }
500
+ async function handleReplicationMsg(data, socket, db, mutex) {
501
+ if (data[0] !== 0x51)
502
+ return;
503
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
504
+ const len = view.getInt32(1);
505
+ const query = new TextDecoder().decode(data.subarray(5, 1 + len - 1)).replace(/\0$/, '');
506
+ const upper = query.trim().toUpperCase();
507
+ log.debug.proxy(`repl query: ${query.slice(0, 200)}`);
508
+ if (upper.startsWith('START_REPLICATION')) {
509
+ const writer = {
510
+ write(chunk) {
511
+ if (!socket.destroyed) {
512
+ socket.write(chunk);
513
+ }
514
+ },
515
+ };
516
+ // drain incoming standby status updates
517
+ socket.on('data', (_chunk) => { });
518
+ socket.on('close', () => socket.destroy());
519
+ // this runs indefinitely until the socket closes
520
+ await handleStartReplication(query, writer, db, mutex).catch((err) => {
521
+ log.debug.proxy(`replication stream ended: ${err}`);
522
+ });
523
+ return;
524
+ }
525
+ // handle replication queries + fallthrough to pglite
526
+ await mutex.acquire();
527
+ try {
528
+ const response = await handleReplicationQuery(query, db);
529
+ if (response) {
530
+ await socketWrite(socket, response);
531
+ return;
532
+ }
533
+ // apply query rewrites before forwarding
534
+ data = interceptQuery(data);
535
+ const result = await db.execProtocolRaw(data, { throwOnError: false });
536
+ await socketWrite(socket, result);
537
+ }
538
+ finally {
539
+ mutex.release();
540
+ }
541
+ }
542
+ // ── main entry point ──
263
543
  export async function startPgProxy(dbInput, config) {
264
544
  // normalize input: single PGlite instance = use it for all databases (backwards compat for tests)
265
545
  const instances = 'postgres' in dbInput
@@ -271,7 +551,6 @@ export async function startPgProxy(dbInput, config) {
271
551
  cvr: new Mutex(),
272
552
  cdb: new Mutex(),
273
553
  };
274
- // helper to get instance + mutex for a database name
275
554
  function getDbContext(dbName) {
276
555
  if (dbName === 'zero_cvr')
277
556
  return { db: instances.cvr, mutex: mutexes.cvr };
@@ -280,105 +559,39 @@ export async function startPgProxy(dbInput, config) {
280
559
  return { db: instances.postgres, mutex: mutexes.postgres };
281
560
  }
282
561
  const server = createServer(async (socket) => {
283
- // prevent idle timeouts from killing connections
284
562
  socket.setKeepAlive(true, 30000);
285
563
  socket.setTimeout(0);
564
+ socket.setNoDelay(true);
286
565
  let dbName = 'postgres';
287
566
  let isReplicationConnection = false;
288
- // clean up pglite transaction state when a client disconnects
289
- socket.on('close', async () => {
290
- const { db, mutex } = getDbContext(dbName);
291
- await mutex.acquire();
292
- try {
293
- await db.exec('ROLLBACK');
294
- }
295
- catch {
296
- // no transaction to rollback
297
- }
298
- finally {
299
- mutex.release();
300
- }
301
- });
302
567
  try {
303
- const connection = await fromNodeSocket(socket, {
304
- serverVersion: '16.4',
305
- auth: {
306
- method: 'password',
307
- getClearTextPassword() {
308
- return config.pgPassword;
309
- },
310
- validateCredentials(credentials) {
311
- return (credentials.password === credentials.clearTextPassword &&
312
- credentials.username === config.pgUser);
313
- },
314
- },
315
- // send ParameterStatus messages that standard postgres tools expect
316
- // pg-gateway sends server_version via the serverVersion option above,
317
- // but tools like pg_restore also need encoding, datestyle, etc.
318
- onAuthenticated() {
319
- for (const [name, value] of SERVER_PARAMS) {
320
- socket.write(buildParameterStatus(name, value));
321
- }
322
- },
323
- async onStartup(state) {
324
- const params = state.clientParams;
325
- if (params?.replication === 'database') {
326
- isReplicationConnection = true;
327
- }
328
- dbName = params?.database || 'postgres';
329
- log.debug.proxy(`connection: db=${dbName} user=${params?.user} replication=${params?.replication || 'none'}`);
330
- const { db } = getDbContext(dbName);
331
- await db.waitReady;
332
- },
333
- async onMessage(data, state) {
334
- if (!state.isAuthenticated)
335
- return;
336
- // handle replication connections (always go to postgres instance)
337
- if (isReplicationConnection) {
338
- if (data[0] === 0x51) {
339
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
340
- const len = view.getInt32(1);
341
- const query = new TextDecoder()
342
- .decode(data.subarray(5, 1 + len - 1))
343
- .replace(/\0$/, '');
344
- log.debug.proxy(`repl query: ${query.slice(0, 200)}`);
345
- }
346
- return handleReplicationMessage(data, socket, instances.postgres, mutexes.postgres, connection);
347
- }
348
- // check for no-op queries
349
- if (isNoopQuery(data)) {
350
- if (data[0] === 0x51) {
351
- return buildSetCompleteResponse();
352
- }
353
- else if (data[0] === 0x50) {
354
- return buildParseCompleteResponse();
355
- }
356
- }
357
- // intercept and rewrite queries
358
- data = interceptQuery(data);
359
- // message-level locking on the connection's pglite instance
360
- const { db, mutex } = getDbContext(dbName);
361
- await mutex.acquire();
362
- let result;
363
- try {
364
- result = await db.execProtocolRaw(data, {
365
- throwOnError: false,
366
- });
367
- }
368
- catch (err) {
369
- mutex.release();
370
- throw err;
371
- }
372
- // strip ReadyForQuery from non-Sync/non-SimpleQuery responses
373
- if (data[0] !== 0x53 && data[0] !== 0x51) {
374
- result = stripReadyForQuery(result);
375
- }
376
- mutex.release();
377
- return result;
378
- },
568
+ // perform startup handshake
569
+ const { params } = await performHandshake(socket, config);
570
+ dbName = params.database || 'postgres';
571
+ isReplicationConnection = params.replication === 'database';
572
+ log.debug.proxy(`connection: db=${dbName} user=${params.user} replication=${params.replication || 'none'}`);
573
+ const { db } = getDbContext(dbName);
574
+ await db.waitReady;
575
+ // clean up pglite transaction state when client disconnects
576
+ socket.on('close', async () => {
577
+ const { db: closeDb, mutex: closeMutex } = getDbContext(dbName);
578
+ await closeMutex.acquire();
579
+ try {
580
+ await closeDb.exec('ROLLBACK');
581
+ }
582
+ catch {
583
+ // no transaction to rollback
584
+ }
585
+ finally {
586
+ closeMutex.release();
587
+ }
379
588
  });
589
+ // enter message processing loop
590
+ const { db: msgDb, mutex: msgMutex } = getDbContext(dbName);
591
+ await messageLoop(socket, msgDb, msgMutex, isReplicationConnection, instances.postgres, mutexes.postgres);
380
592
  }
381
593
  catch (err) {
594
+ // connection error during handshake or message loop
382
595
  if (!socket.destroyed) {
383
596
  socket.destroy();
384
597
  }
@@ -392,48 +605,4 @@ export async function startPgProxy(dbInput, config) {
392
605
  server.on('error', reject);
393
606
  });
394
607
  }
395
- async function handleReplicationMessage(data, socket, db, mutex, connection) {
396
- if (data[0] !== 0x51)
397
- return undefined;
398
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
399
- const len = view.getInt32(1);
400
- const query = new TextDecoder().decode(data.subarray(5, 1 + len - 1)).replace(/\0$/, '');
401
- const upper = query.trim().toUpperCase();
402
- // check if this is a START_REPLICATION command
403
- if (upper.startsWith('START_REPLICATION')) {
404
- await connection.detach();
405
- const writer = {
406
- write(chunk) {
407
- if (!socket.destroyed) {
408
- socket.write(chunk);
409
- }
410
- },
411
- };
412
- // drain incoming standby status updates
413
- socket.on('data', (_chunk) => { });
414
- socket.on('close', () => {
415
- socket.destroy();
416
- });
417
- handleStartReplication(query, writer, db, mutex).catch((err) => {
418
- log.debug.proxy(`replication stream ended: ${err}`);
419
- });
420
- return undefined;
421
- }
422
- // handle replication queries + fallthrough to pglite, all under mutex
423
- await mutex.acquire();
424
- try {
425
- const response = await handleReplicationQuery(query, db);
426
- if (response)
427
- return response;
428
- // apply query rewrites before forwarding
429
- data = interceptQuery(data);
430
- // fall through to pglite for unrecognized queries
431
- return await db.execProtocolRaw(data, {
432
- throwOnError: false,
433
- });
434
- }
435
- finally {
436
- mutex.release();
437
- }
438
- }
439
608
  //# sourceMappingURL=pg-proxy.js.map