@slates-integrations/postgresql 0.2.0-rc.6 → 0.2.0-rc.9

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/src/lib/client.ts DELETED
@@ -1,799 +0,0 @@
1
- import * as crypto from 'crypto';
2
- import * as net from 'net';
3
- import * as tls from 'tls';
4
- import {
5
- AuthenticationTypes,
6
- MessageTypes,
7
- MessageWriter,
8
- buildMessage,
9
- buildPasswordMessage,
10
- buildQueryMessage,
11
- buildStartupMessage,
12
- buildTerminateMessage,
13
- oidToTypeName,
14
- parseCommandComplete,
15
- parseDataRow,
16
- parseErrorFields,
17
- parseMessages,
18
- parseRowDescription,
19
- type ColumnDescription,
20
- type ParsedMessage
21
- } from './protocol';
22
- import {
23
- postgresFieldsError,
24
- postgresServiceError,
25
- postgresUpstreamError,
26
- toPostgresServiceError
27
- } from './errors';
28
-
29
- export interface ConnectionConfig {
30
- host: string;
31
- port: number;
32
- database: string;
33
- username: string;
34
- password: string;
35
- sslMode: 'disable' | 'require' | 'verify-ca' | 'verify-full';
36
- queryTimeout?: number;
37
- }
38
-
39
- export interface QueryResult {
40
- columns: { name: string; type: string; typeOid: number }[];
41
- rows: Record<string, any>[];
42
- rowCount: number | null;
43
- command: string;
44
- }
45
-
46
- export class PostgresClient {
47
- private config: ConnectionConfig;
48
-
49
- constructor(config: ConnectionConfig) {
50
- this.config = config;
51
- }
52
-
53
- async query(sql: string, timeoutMs?: number): Promise<QueryResult> {
54
- let timeout = timeoutMs || this.config.queryTimeout || 30000;
55
- let socket = await this.connect(timeout);
56
-
57
- try {
58
- let result = await this.executeQuery(socket, sql, timeout);
59
- await this.terminate(socket);
60
- return result;
61
- } catch (err) {
62
- socket.destroy();
63
- throw toPostgresServiceError(err, 'PostgreSQL query failed');
64
- }
65
- }
66
-
67
- async multiQuery(statements: string[], timeoutMs?: number): Promise<QueryResult[]> {
68
- let timeout = timeoutMs || this.config.queryTimeout || 30000;
69
- let socket = await this.connect(timeout);
70
-
71
- try {
72
- let results: QueryResult[] = [];
73
- for (let sql of statements) {
74
- let result = await this.executeQuery(socket, sql, timeout);
75
- results.push(result);
76
- }
77
- await this.terminate(socket);
78
- return results;
79
- } catch (err) {
80
- socket.destroy();
81
- throw toPostgresServiceError(err, 'PostgreSQL query failed');
82
- }
83
- }
84
-
85
- private connect(timeoutMs: number): Promise<net.Socket> {
86
- return new Promise((resolve, reject) => {
87
- let socket: net.Socket;
88
-
89
- let timer = setTimeout(() => {
90
- socket.destroy();
91
- reject(
92
- postgresUpstreamError(`PostgreSQL connection timeout after ${timeoutMs}ms`, {
93
- reason: 'postgresql_connection_timeout'
94
- })
95
- );
96
- }, timeoutMs);
97
-
98
- let handleConnection = async (sock: net.Socket) => {
99
- try {
100
- await this.authenticate(sock, timeoutMs);
101
- clearTimeout(timer);
102
- resolve(sock);
103
- } catch (err) {
104
- clearTimeout(timer);
105
- sock.destroy();
106
- reject(toPostgresServiceError(err, 'PostgreSQL authentication failed'));
107
- }
108
- };
109
-
110
- if (this.config.sslMode !== 'disable') {
111
- // First establish a plain TCP connection, then upgrade to TLS
112
- socket = net.createConnection({
113
- host: this.config.host,
114
- port: this.config.port
115
- });
116
-
117
- socket.once('error', err => {
118
- clearTimeout(timer);
119
- reject(
120
- postgresUpstreamError(`PostgreSQL connection failed: ${err.message}`, {
121
- reason: 'postgresql_connection_failed',
122
- parent: err
123
- })
124
- );
125
- });
126
-
127
- socket.once('connect', () => {
128
- // Send SSL request
129
- let sslRequest = new Uint8Array(8);
130
- let view = new DataView(sslRequest.buffer);
131
- view.setInt32(0, 8, false); // message length
132
- view.setInt32(4, 80877103, false); // SSL request code
133
- socket.write(sslRequest);
134
-
135
- socket.once('data', response => {
136
- let responseChar = String.fromCharCode(response[0]!);
137
- if (responseChar === 'S') {
138
- // Server supports SSL, upgrade connection
139
- let tlsOptions: tls.ConnectionOptions = {
140
- socket: socket,
141
- rejectUnauthorized:
142
- this.config.sslMode === 'verify-ca' || this.config.sslMode === 'verify-full',
143
- servername:
144
- this.config.sslMode === 'verify-full' ? this.config.host : undefined
145
- };
146
- let tlsSocket = tls.connect(tlsOptions);
147
- tlsSocket.once('secureConnect', () => {
148
- handleConnection(tlsSocket as any);
149
- });
150
- tlsSocket.once('error', err => {
151
- clearTimeout(timer);
152
- reject(
153
- postgresUpstreamError(`PostgreSQL SSL connection failed: ${err.message}`, {
154
- reason: 'postgresql_ssl_failed',
155
- parent: err
156
- })
157
- );
158
- });
159
- } else if (responseChar === 'N') {
160
- clearTimeout(timer);
161
- socket.destroy();
162
- reject(
163
- postgresUpstreamError(
164
- `PostgreSQL server does not support SSL connections for sslmode=${this.config.sslMode}`,
165
- {
166
- reason: 'postgresql_ssl_unsupported'
167
- }
168
- )
169
- );
170
- } else {
171
- clearTimeout(timer);
172
- socket.destroy();
173
- reject(
174
- postgresUpstreamError(`Unexpected PostgreSQL SSL response: ${responseChar}`, {
175
- reason: 'postgresql_ssl_unexpected_response'
176
- })
177
- );
178
- }
179
- });
180
- });
181
- } else {
182
- socket = net.createConnection({
183
- host: this.config.host,
184
- port: this.config.port
185
- });
186
-
187
- socket.once('error', err => {
188
- clearTimeout(timer);
189
- reject(
190
- postgresUpstreamError(`PostgreSQL connection failed: ${err.message}`, {
191
- reason: 'postgresql_connection_failed',
192
- parent: err
193
- })
194
- );
195
- });
196
-
197
- socket.once('connect', () => {
198
- handleConnection(socket);
199
- });
200
- }
201
- });
202
- }
203
-
204
- private authenticate(socket: net.Socket, timeoutMs: number): Promise<void> {
205
- return new Promise((resolve, reject) => {
206
- let buffer: Uint8Array = new Uint8Array(0);
207
- let authenticated = false;
208
-
209
- let timer = setTimeout(() => {
210
- cleanup();
211
- reject(
212
- postgresUpstreamError(`PostgreSQL authentication timeout after ${timeoutMs}ms`, {
213
- reason: 'postgresql_auth_timeout'
214
- })
215
- );
216
- }, timeoutMs);
217
-
218
- let cleanup = () => {
219
- clearTimeout(timer);
220
- socket.removeListener('data', onData);
221
- socket.removeListener('error', onError);
222
- };
223
-
224
- let onError = (err: Error) => {
225
- cleanup();
226
- reject(
227
- postgresUpstreamError(`PostgreSQL authentication error: ${err.message}`, {
228
- reason: 'postgresql_auth_error',
229
- parent: err
230
- })
231
- );
232
- };
233
-
234
- let onData = (data: Uint8Array) => {
235
- let newBuffer = new Uint8Array(buffer.length + data.length);
236
- newBuffer.set(buffer);
237
- newBuffer.set(data, buffer.length);
238
- buffer = newBuffer;
239
-
240
- let { messages, remaining } = parseMessages(buffer);
241
- buffer = remaining;
242
-
243
- for (let msg of messages) {
244
- if (msg.type === MessageTypes.AUTHENTICATION) {
245
- let view = new DataView(msg.body.buffer, msg.body.byteOffset, msg.body.byteLength);
246
- let authType = view.getInt32(0, false);
247
-
248
- if (authType === AuthenticationTypes.OK) {
249
- authenticated = true;
250
- } else if (authType === AuthenticationTypes.CLEARTEXT_PASSWORD) {
251
- socket.write(buildPasswordMessage(this.config.password));
252
- } else if (authType === AuthenticationTypes.MD5_PASSWORD) {
253
- let salt = msg.body.slice(4, 8);
254
- let md5Password = this.computeMd5Password(
255
- this.config.username,
256
- this.config.password,
257
- salt
258
- );
259
- socket.write(buildPasswordMessage(md5Password));
260
- } else if (authType === AuthenticationTypes.SASL) {
261
- cleanup();
262
- this.handleSaslAuth(socket, msg, buffer, timeoutMs)
263
- .then(() => {
264
- resolve();
265
- })
266
- .catch(err => {
267
- reject(toPostgresServiceError(err, 'PostgreSQL SASL authentication failed'));
268
- });
269
- return;
270
- } else {
271
- cleanup();
272
- reject(
273
- postgresServiceError(
274
- `Unsupported PostgreSQL authentication method: ${authType}`
275
- )
276
- );
277
- return;
278
- }
279
- } else if (msg.type === MessageTypes.ERROR_RESPONSE) {
280
- let fields = parseErrorFields(msg.body);
281
- cleanup();
282
- reject(postgresFieldsError(fields));
283
- return;
284
- } else if (msg.type === MessageTypes.READY_FOR_QUERY) {
285
- if (authenticated) {
286
- cleanup();
287
- resolve();
288
- return;
289
- }
290
- }
291
- // Ignore ParameterStatus, BackendKeyData, NoticeResponse
292
- }
293
- };
294
-
295
- socket.on('data', onData);
296
- socket.on('error', onError);
297
-
298
- // Send startup message
299
- socket.write(buildStartupMessage(this.config.username, this.config.database));
300
- });
301
- }
302
-
303
- private computeMd5Password(username: string, password: string, salt: Uint8Array): string {
304
- let inner = crypto
305
- .createHash('md5')
306
- .update(password + username)
307
- .digest('hex');
308
- let outer = crypto.createHash('md5').update(inner).update(salt).digest('hex');
309
- return 'md5' + outer;
310
- }
311
-
312
- private async handleSaslAuth(
313
- socket: net.Socket,
314
- initialMsg: ParsedMessage,
315
- currentBuffer: Uint8Array,
316
- timeoutMs: number
317
- ): Promise<{ remaining: Uint8Array }> {
318
- // Parse available SASL mechanisms
319
- let decoder = new TextDecoder();
320
- let mechanisms: string[] = [];
321
- let offset = 4; // skip auth type int
322
- while (offset < initialMsg.body.length) {
323
- let end = initialMsg.body.indexOf(0, offset);
324
- if (end === -1 || end === offset) break;
325
- mechanisms.push(decoder.decode(initialMsg.body.slice(offset, end)));
326
- offset = end + 1;
327
- }
328
-
329
- if (!mechanisms.includes('SCRAM-SHA-256')) {
330
- throw postgresServiceError(
331
- `Unsupported SASL mechanisms: ${mechanisms.join(', ')}. Only SCRAM-SHA-256 is supported.`
332
- );
333
- }
334
-
335
- // SCRAM-SHA-256 authentication
336
- let nonce = crypto.randomBytes(18).toString('base64');
337
- let clientFirstBare = `n=,r=${nonce}`;
338
- let clientFirstMessage = `n,,${clientFirstBare}`;
339
-
340
- // Send SASLInitialResponse
341
- let encoder = new TextEncoder();
342
- let mechanismBytes = encoder.encode('SCRAM-SHA-256');
343
- let clientFirstBytes = encoder.encode(clientFirstMessage);
344
- let writer = new MessageWriter();
345
- writer.writeBytes(mechanismBytes);
346
- writer.writeByte(0); // null terminator for mechanism name
347
- writer.writeInt32(clientFirstBytes.length);
348
- writer.writeBytes(clientFirstBytes);
349
- socket.write(buildMessage(MessageTypes.PASSWORD, writer.toBuffer()));
350
-
351
- // Wait for SASLContinue
352
- let saslContinueMsg = await this.waitForMessage(
353
- socket,
354
- MessageTypes.AUTHENTICATION,
355
- currentBuffer,
356
- timeoutMs
357
- );
358
- let continueView = new DataView(
359
- saslContinueMsg.msg.body.buffer,
360
- saslContinueMsg.msg.body.byteOffset,
361
- saslContinueMsg.msg.body.byteLength
362
- );
363
- let continueAuthType = continueView.getInt32(0, false);
364
- if (continueAuthType !== AuthenticationTypes.SASL_CONTINUE) {
365
- throw postgresUpstreamError(
366
- `Expected SASL_CONTINUE, got auth type: ${continueAuthType}`,
367
- {
368
- reason: 'postgresql_sasl_protocol_error'
369
- }
370
- );
371
- }
372
-
373
- let serverFirstMessage = decoder.decode(saslContinueMsg.msg.body.slice(4));
374
- let serverParams = this.parseScramMessage(serverFirstMessage);
375
- let serverNonce = serverParams['r'] || '';
376
- let saltBase64 = serverParams['s'] || '';
377
- let iterations = parseInt(serverParams['i'] || '4096', 10);
378
-
379
- if (!serverNonce.startsWith(nonce)) {
380
- throw postgresUpstreamError('Server nonce does not start with client nonce', {
381
- reason: 'postgresql_sasl_protocol_error'
382
- });
383
- }
384
-
385
- // Compute SCRAM proof
386
- let saltBytes = this.base64Decode(saltBase64);
387
- let saltedPassword = this.hi(
388
- this.normalizePassword(this.config.password),
389
- saltBytes,
390
- iterations
391
- );
392
- let clientKey = this.hmacSha256(saltedPassword, 'Client Key');
393
- let storedKey = new Uint8Array(crypto.createHash('sha256').update(clientKey).digest());
394
-
395
- let channelBinding = btoa('n,,');
396
- let clientFinalWithoutProof = `c=${channelBinding},r=${serverNonce}`;
397
- let authMessage = `${clientFirstBare},${serverFirstMessage},${clientFinalWithoutProof}`;
398
-
399
- let clientSignature = this.hmacSha256(storedKey, authMessage);
400
- let clientProof = new Uint8Array(clientKey.length);
401
- for (let i = 0; i < clientKey.length; i++) {
402
- clientProof[i] = clientKey[i]! ^ clientSignature[i]!;
403
- }
404
-
405
- let clientFinalMessage = `${clientFinalWithoutProof},p=${this.base64Encode(clientProof)}`;
406
-
407
- // Send SASLResponse
408
- let responseBytes = encoder.encode(clientFinalMessage);
409
- socket.write(buildMessage(MessageTypes.PASSWORD, responseBytes));
410
-
411
- // Wait for SASLFinal
412
- let saslFinalMsg = await this.waitForMessage(
413
- socket,
414
- MessageTypes.AUTHENTICATION,
415
- saslContinueMsg.remaining,
416
- timeoutMs
417
- );
418
- let finalView = new DataView(
419
- saslFinalMsg.msg.body.buffer,
420
- saslFinalMsg.msg.body.byteOffset,
421
- saslFinalMsg.msg.body.byteLength
422
- );
423
- let finalAuthType = finalView.getInt32(0, false);
424
-
425
- if (finalAuthType === AuthenticationTypes.SASL_FINAL) {
426
- let serverFinalMessage = decoder.decode(saslFinalMsg.msg.body.slice(4));
427
- let serverFinalParams = this.parseScramMessage(serverFinalMessage);
428
- let serverSignature = serverFinalParams['v'];
429
-
430
- // Verify server signature
431
- let serverKey = this.hmacSha256(saltedPassword, 'Server Key');
432
- let expectedServerSignature = this.base64Encode(this.hmacSha256(serverKey, authMessage));
433
-
434
- if (serverSignature !== expectedServerSignature) {
435
- throw postgresUpstreamError('PostgreSQL server signature verification failed', {
436
- reason: 'postgresql_sasl_signature_verification_failed'
437
- });
438
- }
439
- }
440
-
441
- // Wait for AuthenticationOk + ReadyForQuery
442
- let readyBuffer = saslFinalMsg.remaining;
443
- return new Promise((resolve, reject) => {
444
- let timer = setTimeout(() => {
445
- cleanup();
446
- reject(
447
- postgresUpstreamError('PostgreSQL SASL auth completion timeout', {
448
- reason: 'postgresql_sasl_timeout'
449
- })
450
- );
451
- }, timeoutMs);
452
-
453
- let cleanup = () => {
454
- clearTimeout(timer);
455
- socket.removeListener('data', onData);
456
- socket.removeListener('error', onError);
457
- };
458
-
459
- let onError = (err: Error) => {
460
- cleanup();
461
- reject(
462
- postgresUpstreamError(`PostgreSQL SASL auth error: ${err.message}`, {
463
- reason: 'postgresql_sasl_error',
464
- parent: err
465
- })
466
- );
467
- };
468
-
469
- let onData = (data: Uint8Array) => {
470
- let newBuf = new Uint8Array(readyBuffer.length + data.length);
471
- newBuf.set(readyBuffer);
472
- newBuf.set(data, readyBuffer.length);
473
- readyBuffer = newBuf;
474
-
475
- let { messages, remaining } = parseMessages(readyBuffer);
476
- readyBuffer = remaining;
477
-
478
- for (let msg of messages) {
479
- if (msg.type === MessageTypes.ERROR_RESPONSE) {
480
- let fields = parseErrorFields(msg.body);
481
- cleanup();
482
- reject(postgresFieldsError(fields));
483
- return;
484
- }
485
- if (msg.type === MessageTypes.READY_FOR_QUERY) {
486
- cleanup();
487
- resolve({ remaining: readyBuffer });
488
- return;
489
- }
490
- }
491
- };
492
-
493
- // Process any data already in buffer
494
- let { messages, remaining } = parseMessages(readyBuffer);
495
- readyBuffer = remaining;
496
- for (let msg of messages) {
497
- if (msg.type === MessageTypes.READY_FOR_QUERY) {
498
- cleanup();
499
- resolve({ remaining: readyBuffer });
500
- return;
501
- }
502
- if (msg.type === MessageTypes.ERROR_RESPONSE) {
503
- let fields = parseErrorFields(msg.body);
504
- cleanup();
505
- reject(postgresFieldsError(fields));
506
- return;
507
- }
508
- }
509
-
510
- socket.on('data', onData);
511
- socket.on('error', onError);
512
- });
513
- }
514
-
515
- private parseScramMessage(msg: string): Record<string, string> {
516
- let result: Record<string, string> = {};
517
- for (let part of msg.split(',')) {
518
- let eqIndex = part.indexOf('=');
519
- if (eqIndex > 0) {
520
- result[part.substring(0, eqIndex)] = part.substring(eqIndex + 1);
521
- }
522
- }
523
- return result;
524
- }
525
-
526
- private normalizePassword(password: string): string {
527
- return password;
528
- }
529
-
530
- private hi(password: string, salt: Uint8Array, iterations: number): Uint8Array {
531
- let encoder = new TextEncoder();
532
- let passwordBytes = encoder.encode(password);
533
- // PBKDF2 with HMAC-SHA-256
534
- let u = new Uint8Array(
535
- crypto
536
- .createHmac('sha256', passwordBytes)
537
- .update(salt)
538
- .update(new Uint8Array([0, 0, 0, 1]))
539
- .digest()
540
- );
541
- let result = new Uint8Array(u);
542
- for (let i = 1; i < iterations; i++) {
543
- u = new Uint8Array(crypto.createHmac('sha256', passwordBytes).update(u).digest());
544
- for (let j = 0; j < result.length; j++) {
545
- result[j] = result[j]! ^ u[j]!;
546
- }
547
- }
548
- return result;
549
- }
550
-
551
- private hmacSha256(key: Uint8Array | string, data: string): Uint8Array {
552
- let encoder = new TextEncoder();
553
- let keyBytes = typeof key === 'string' ? encoder.encode(key) : key;
554
- let dataBytes = typeof data === 'string' ? encoder.encode(data) : data;
555
- return new Uint8Array(crypto.createHmac('sha256', keyBytes).update(dataBytes).digest());
556
- }
557
-
558
- private base64Encode(data: Uint8Array): string {
559
- let binary = '';
560
- for (let i = 0; i < data.length; i++) {
561
- binary += String.fromCharCode(data[i]!);
562
- }
563
- return btoa(binary);
564
- }
565
-
566
- private base64Decode(str: string): Uint8Array {
567
- let binary = atob(str);
568
- let bytes = new Uint8Array(binary.length);
569
- for (let i = 0; i < binary.length; i++) {
570
- bytes[i] = binary.charCodeAt(i);
571
- }
572
- return bytes;
573
- }
574
-
575
- private waitForMessage(
576
- socket: net.Socket,
577
- expectedType: number,
578
- currentBuffer: Uint8Array,
579
- timeoutMs: number
580
- ): Promise<{ msg: ParsedMessage; remaining: Uint8Array }> {
581
- return new Promise((resolve, reject) => {
582
- let buffer = currentBuffer;
583
-
584
- let timer = setTimeout(() => {
585
- cleanup();
586
- reject(
587
- postgresUpstreamError('Timeout waiting for PostgreSQL message', {
588
- reason: 'postgresql_message_timeout'
589
- })
590
- );
591
- }, timeoutMs);
592
-
593
- let cleanup = () => {
594
- clearTimeout(timer);
595
- socket.removeListener('data', onData);
596
- socket.removeListener('error', onError);
597
- };
598
-
599
- let onError = (err: Error) => {
600
- cleanup();
601
- reject(
602
- postgresUpstreamError(`PostgreSQL socket error: ${err.message}`, {
603
- reason: 'postgresql_socket_error',
604
- parent: err
605
- })
606
- );
607
- };
608
-
609
- let processBuffer = (): boolean => {
610
- let { messages, remaining } = parseMessages(buffer);
611
- buffer = remaining;
612
-
613
- for (let msg of messages) {
614
- if (msg.type === MessageTypes.ERROR_RESPONSE) {
615
- let fields = parseErrorFields(msg.body);
616
- cleanup();
617
- reject(postgresFieldsError(fields));
618
- return true;
619
- }
620
- if (msg.type === expectedType) {
621
- cleanup();
622
- resolve({ msg, remaining: buffer });
623
- return true;
624
- }
625
- }
626
- return false;
627
- };
628
-
629
- // Check existing buffer first
630
- if (processBuffer()) return;
631
-
632
- let onData = (data: Uint8Array) => {
633
- let newBuf = new Uint8Array(buffer.length + data.length);
634
- newBuf.set(buffer);
635
- newBuf.set(data, buffer.length);
636
- buffer = newBuf;
637
- processBuffer();
638
- };
639
-
640
- socket.on('data', onData);
641
- socket.on('error', onError);
642
- });
643
- }
644
-
645
- private executeQuery(
646
- socket: net.Socket,
647
- sql: string,
648
- timeoutMs: number
649
- ): Promise<QueryResult> {
650
- return new Promise((resolve, reject) => {
651
- let buffer: Uint8Array = new Uint8Array(0);
652
- let columns: ColumnDescription[] = [];
653
- let rows: (string | null)[][] = [];
654
- let command = '';
655
- let rowCount: number | null = null;
656
-
657
- let timer = setTimeout(() => {
658
- cleanup();
659
- reject(
660
- postgresUpstreamError(`PostgreSQL query timeout after ${timeoutMs}ms`, {
661
- reason: 'postgresql_query_timeout'
662
- })
663
- );
664
- }, timeoutMs);
665
-
666
- let cleanup = () => {
667
- clearTimeout(timer);
668
- socket.removeListener('data', onData);
669
- socket.removeListener('error', onError);
670
- };
671
-
672
- let onError = (err: Error) => {
673
- cleanup();
674
- reject(
675
- postgresUpstreamError(`PostgreSQL query error: ${err.message}`, {
676
- reason: 'postgresql_query_error',
677
- parent: err
678
- })
679
- );
680
- };
681
-
682
- let onData = (data: Uint8Array) => {
683
- let newBuf = new Uint8Array(buffer.length + data.length);
684
- newBuf.set(buffer);
685
- newBuf.set(data, buffer.length);
686
- buffer = newBuf;
687
-
688
- let { messages, remaining } = parseMessages(buffer);
689
- buffer = remaining;
690
-
691
- for (let msg of messages) {
692
- if (msg.type === MessageTypes.ROW_DESCRIPTION) {
693
- columns = parseRowDescription(msg.body);
694
- } else if (msg.type === MessageTypes.DATA_ROW) {
695
- rows.push(parseDataRow(msg.body));
696
- } else if (msg.type === MessageTypes.COMMAND_COMPLETE) {
697
- let parsed = parseCommandComplete(msg.body);
698
- command = parsed.command;
699
- rowCount = parsed.rowCount;
700
- } else if (msg.type === MessageTypes.ERROR_RESPONSE) {
701
- let fields = parseErrorFields(msg.body);
702
- cleanup();
703
- reject(postgresFieldsError(fields));
704
- return;
705
- } else if (msg.type === MessageTypes.READY_FOR_QUERY) {
706
- cleanup();
707
-
708
- let mappedColumns = columns.map(col => ({
709
- name: col.name,
710
- type: oidToTypeName(col.typeOid),
711
- typeOid: col.typeOid
712
- }));
713
-
714
- let mappedRows = rows.map(row => {
715
- let obj: Record<string, any> = {};
716
- for (let i = 0; i < columns.length; i++) {
717
- let col = columns[i]!;
718
- let value = row[i] ?? null;
719
- obj[col.name] = castValue(value, col.typeOid);
720
- }
721
- return obj;
722
- });
723
-
724
- resolve({
725
- columns: mappedColumns,
726
- rows: mappedRows,
727
- rowCount,
728
- command
729
- });
730
- return;
731
- }
732
- // Ignore NoticeResponse, EmptyQueryResponse
733
- }
734
- };
735
-
736
- socket.on('data', onData);
737
- socket.on('error', onError);
738
- socket.write(buildQueryMessage(sql));
739
- });
740
- }
741
-
742
- private terminate(socket: net.Socket): Promise<void> {
743
- return new Promise(resolve => {
744
- let settled = false;
745
- let timeout: ReturnType<typeof setTimeout>;
746
- let finish = () => {
747
- if (settled) return;
748
- settled = true;
749
- clearTimeout(timeout);
750
- socket.removeListener('close', finish);
751
- socket.removeListener('error', finish);
752
- resolve();
753
- };
754
-
755
- timeout = setTimeout(() => {
756
- socket.destroy();
757
- finish();
758
- }, 1000);
759
-
760
- socket.once('close', finish);
761
- socket.once('error', finish);
762
-
763
- try {
764
- socket.end(buildTerminateMessage(), finish);
765
- } catch {
766
- socket.destroy();
767
- finish();
768
- }
769
- });
770
- }
771
- }
772
-
773
- // Cast PostgreSQL text values to appropriate JS types
774
- let castValue = (value: string | null, typeOid: number): any => {
775
- if (value === null) return null;
776
-
777
- switch (typeOid) {
778
- case 16: // boolean
779
- return value === 't' || value === 'true';
780
- case 20: // bigint
781
- case 21: // smallint
782
- case 23: // integer
783
- case 26: // oid
784
- return parseInt(value, 10);
785
- case 700: // real
786
- case 701: // double precision
787
- case 1700: // numeric
788
- return parseFloat(value);
789
- case 114: // json
790
- case 3802: // jsonb
791
- try {
792
- return JSON.parse(value);
793
- } catch {
794
- return value;
795
- }
796
- default:
797
- return value;
798
- }
799
- };