relq 1.0.65 → 1.0.66
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/cjs/core/relq-client.cjs +122 -11
- package/dist/esm/core/relq-client.js +122 -11
- package/dist/index.d.ts +13 -0
- package/package.json +1 -1
|
@@ -83,6 +83,8 @@ class Relq {
|
|
|
83
83
|
initialized = false;
|
|
84
84
|
initPromise;
|
|
85
85
|
isClosed = false;
|
|
86
|
+
clientConnected = false;
|
|
87
|
+
connectPromise;
|
|
86
88
|
environment;
|
|
87
89
|
poolErrorHandler;
|
|
88
90
|
poolConnectHandler;
|
|
@@ -487,8 +489,14 @@ class Relq {
|
|
|
487
489
|
connectionString: this.config.connectionString,
|
|
488
490
|
ssl
|
|
489
491
|
});
|
|
490
|
-
this.client.on('end', () =>
|
|
491
|
-
|
|
492
|
+
this.client.on('end', () => {
|
|
493
|
+
this.clientConnected = false;
|
|
494
|
+
this.emitter.emit('end');
|
|
495
|
+
});
|
|
496
|
+
this.client.on('error', (err) => {
|
|
497
|
+
this.clientConnected = false;
|
|
498
|
+
this.emitter.emit('error', err);
|
|
499
|
+
});
|
|
492
500
|
this.client.on('notice', (msg) => this.emitter.emit('notice', msg));
|
|
493
501
|
this.client.on('notification', (msg) => this.emitter.emit('notification', msg));
|
|
494
502
|
}
|
|
@@ -527,9 +535,85 @@ class Relq {
|
|
|
527
535
|
await this.initialize();
|
|
528
536
|
if (!this.usePooling && this.client) {
|
|
529
537
|
await this.client.connect();
|
|
538
|
+
this.clientConnected = true;
|
|
530
539
|
this.emitter.emit('connect', this.client);
|
|
531
540
|
}
|
|
532
541
|
}
|
|
542
|
+
async ensureConnection() {
|
|
543
|
+
if (this.usePooling || !this.client)
|
|
544
|
+
return;
|
|
545
|
+
if (this.clientConnected)
|
|
546
|
+
return;
|
|
547
|
+
if (this.connectPromise) {
|
|
548
|
+
return this.connectPromise;
|
|
549
|
+
}
|
|
550
|
+
this.connectPromise = (async () => {
|
|
551
|
+
try {
|
|
552
|
+
if (this.client && !this.clientConnected) {
|
|
553
|
+
try {
|
|
554
|
+
await this.client.connect();
|
|
555
|
+
this.clientConnected = true;
|
|
556
|
+
this.emitter.emit('connect', this.client);
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
if (err.message?.includes('already been connected') ||
|
|
560
|
+
err.code === 'ECONNRESET' ||
|
|
561
|
+
err.code === 'ETIMEDOUT' ||
|
|
562
|
+
err.code === 'EPIPE' ||
|
|
563
|
+
err.message?.includes('Connection terminated')) {
|
|
564
|
+
this.client.removeAllListeners();
|
|
565
|
+
try {
|
|
566
|
+
await this.client.end();
|
|
567
|
+
}
|
|
568
|
+
catch { }
|
|
569
|
+
await this.recreateClient();
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
throw err;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
finally {
|
|
578
|
+
this.connectPromise = undefined;
|
|
579
|
+
}
|
|
580
|
+
})();
|
|
581
|
+
return this.connectPromise;
|
|
582
|
+
}
|
|
583
|
+
async recreateClient() {
|
|
584
|
+
const { Client: PgClientClass } = await loadPg();
|
|
585
|
+
let resolvedPassword = this.config.password;
|
|
586
|
+
const isAws = (0, config_types_1.isAwsDsqlConfig)(this.config);
|
|
587
|
+
if (isAws) {
|
|
588
|
+
resolvedPassword = await (0, aws_dsql_1.getAwsDsqlToken)(this.config.aws);
|
|
589
|
+
}
|
|
590
|
+
const host = isAws ? this.config.aws.hostname : (this.config.host || 'localhost');
|
|
591
|
+
const port = this.config.aws?.port ?? this.config.port ?? 5432;
|
|
592
|
+
const user = this.config.aws?.user ?? this.config.user ?? (isAws ? 'admin' : undefined);
|
|
593
|
+
const ssl = isAws ? (this.config.aws.ssl ?? true) : this.config.ssl;
|
|
594
|
+
this.client = new PgClientClass({
|
|
595
|
+
host,
|
|
596
|
+
port,
|
|
597
|
+
database: this.config.database,
|
|
598
|
+
user,
|
|
599
|
+
password: resolvedPassword,
|
|
600
|
+
connectionString: this.config.connectionString,
|
|
601
|
+
ssl
|
|
602
|
+
});
|
|
603
|
+
this.client.on('end', () => {
|
|
604
|
+
this.clientConnected = false;
|
|
605
|
+
this.emitter.emit('end');
|
|
606
|
+
});
|
|
607
|
+
this.client.on('error', (err) => {
|
|
608
|
+
this.clientConnected = false;
|
|
609
|
+
this.emitter.emit('error', err);
|
|
610
|
+
});
|
|
611
|
+
this.client.on('notice', (msg) => this.emitter.emit('notice', msg));
|
|
612
|
+
this.client.on('notification', (msg) => this.emitter.emit('notification', msg));
|
|
613
|
+
await this.client.connect();
|
|
614
|
+
this.clientConnected = true;
|
|
615
|
+
this.emitter.emit('connect', this.client);
|
|
616
|
+
}
|
|
533
617
|
async subscribe(channel, callback) {
|
|
534
618
|
if (!this.listener) {
|
|
535
619
|
this.listener = new listener_connection_1.ListenerConnection({
|
|
@@ -576,6 +660,7 @@ class Relq {
|
|
|
576
660
|
else if (this.client) {
|
|
577
661
|
this.client.removeAllListeners();
|
|
578
662
|
await this.client.end();
|
|
663
|
+
this.clientConnected = false;
|
|
579
664
|
}
|
|
580
665
|
this.isClosed = true;
|
|
581
666
|
activeRelqInstances.delete(this);
|
|
@@ -601,26 +686,52 @@ class Relq {
|
|
|
601
686
|
return this;
|
|
602
687
|
}
|
|
603
688
|
async _executeQuery(sql) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const startTime = performance.now();
|
|
610
|
-
let result;
|
|
689
|
+
if (!sql || typeof sql !== 'string' || sql.trim() === '') {
|
|
690
|
+
throw new relq_errors_1.RelqConfigError(`Invalid SQL query: ${sql === null ? 'null' : sql === undefined ? 'undefined' : 'empty string'}`);
|
|
691
|
+
}
|
|
692
|
+
await this.initialize();
|
|
693
|
+
const executeQuery = async () => {
|
|
611
694
|
if (this.pool) {
|
|
612
|
-
|
|
695
|
+
return await this.pool.query(sql);
|
|
613
696
|
}
|
|
614
697
|
else if (this.client) {
|
|
615
|
-
|
|
698
|
+
await this.ensureConnection();
|
|
699
|
+
return await this.client.query(sql);
|
|
616
700
|
}
|
|
617
701
|
else {
|
|
618
702
|
throw new relq_errors_1.RelqConfigError('No database connection available');
|
|
619
703
|
}
|
|
704
|
+
};
|
|
705
|
+
try {
|
|
706
|
+
const startTime = performance.now();
|
|
707
|
+
const result = await executeQuery();
|
|
620
708
|
const duration = performance.now() - startTime;
|
|
621
709
|
return { result, duration };
|
|
622
710
|
}
|
|
623
711
|
catch (error) {
|
|
712
|
+
const isConnectionError = error.code === 'ECONNRESET' ||
|
|
713
|
+
error.code === 'ETIMEDOUT' ||
|
|
714
|
+
error.code === 'EPIPE' ||
|
|
715
|
+
error.code === 'ENOTCONN' ||
|
|
716
|
+
error.code === '57P01' ||
|
|
717
|
+
error.code === '57P02' ||
|
|
718
|
+
error.code === '57P03' ||
|
|
719
|
+
error.message?.includes('Connection terminated') ||
|
|
720
|
+
error.message?.includes('connection is closed') ||
|
|
721
|
+
error.message?.includes('Client has encountered a connection error');
|
|
722
|
+
if (isConnectionError && !this.usePooling && this.client) {
|
|
723
|
+
this.clientConnected = false;
|
|
724
|
+
try {
|
|
725
|
+
await this.ensureConnection();
|
|
726
|
+
const startTime = performance.now();
|
|
727
|
+
const result = await this.client.query(sql);
|
|
728
|
+
const duration = performance.now() - startTime;
|
|
729
|
+
return { result, duration };
|
|
730
|
+
}
|
|
731
|
+
catch (retryError) {
|
|
732
|
+
throw (0, relq_errors_1.parsePostgresError)(error, sql);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
624
735
|
throw (0, relq_errors_1.parsePostgresError)(error, sql);
|
|
625
736
|
}
|
|
626
737
|
}
|
|
@@ -47,6 +47,8 @@ export class Relq {
|
|
|
47
47
|
initialized = false;
|
|
48
48
|
initPromise;
|
|
49
49
|
isClosed = false;
|
|
50
|
+
clientConnected = false;
|
|
51
|
+
connectPromise;
|
|
50
52
|
environment;
|
|
51
53
|
poolErrorHandler;
|
|
52
54
|
poolConnectHandler;
|
|
@@ -451,8 +453,14 @@ export class Relq {
|
|
|
451
453
|
connectionString: this.config.connectionString,
|
|
452
454
|
ssl
|
|
453
455
|
});
|
|
454
|
-
this.client.on('end', () =>
|
|
455
|
-
|
|
456
|
+
this.client.on('end', () => {
|
|
457
|
+
this.clientConnected = false;
|
|
458
|
+
this.emitter.emit('end');
|
|
459
|
+
});
|
|
460
|
+
this.client.on('error', (err) => {
|
|
461
|
+
this.clientConnected = false;
|
|
462
|
+
this.emitter.emit('error', err);
|
|
463
|
+
});
|
|
456
464
|
this.client.on('notice', (msg) => this.emitter.emit('notice', msg));
|
|
457
465
|
this.client.on('notification', (msg) => this.emitter.emit('notification', msg));
|
|
458
466
|
}
|
|
@@ -491,9 +499,85 @@ export class Relq {
|
|
|
491
499
|
await this.initialize();
|
|
492
500
|
if (!this.usePooling && this.client) {
|
|
493
501
|
await this.client.connect();
|
|
502
|
+
this.clientConnected = true;
|
|
494
503
|
this.emitter.emit('connect', this.client);
|
|
495
504
|
}
|
|
496
505
|
}
|
|
506
|
+
async ensureConnection() {
|
|
507
|
+
if (this.usePooling || !this.client)
|
|
508
|
+
return;
|
|
509
|
+
if (this.clientConnected)
|
|
510
|
+
return;
|
|
511
|
+
if (this.connectPromise) {
|
|
512
|
+
return this.connectPromise;
|
|
513
|
+
}
|
|
514
|
+
this.connectPromise = (async () => {
|
|
515
|
+
try {
|
|
516
|
+
if (this.client && !this.clientConnected) {
|
|
517
|
+
try {
|
|
518
|
+
await this.client.connect();
|
|
519
|
+
this.clientConnected = true;
|
|
520
|
+
this.emitter.emit('connect', this.client);
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
if (err.message?.includes('already been connected') ||
|
|
524
|
+
err.code === 'ECONNRESET' ||
|
|
525
|
+
err.code === 'ETIMEDOUT' ||
|
|
526
|
+
err.code === 'EPIPE' ||
|
|
527
|
+
err.message?.includes('Connection terminated')) {
|
|
528
|
+
this.client.removeAllListeners();
|
|
529
|
+
try {
|
|
530
|
+
await this.client.end();
|
|
531
|
+
}
|
|
532
|
+
catch { }
|
|
533
|
+
await this.recreateClient();
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
throw err;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
finally {
|
|
542
|
+
this.connectPromise = undefined;
|
|
543
|
+
}
|
|
544
|
+
})();
|
|
545
|
+
return this.connectPromise;
|
|
546
|
+
}
|
|
547
|
+
async recreateClient() {
|
|
548
|
+
const { Client: PgClientClass } = await loadPg();
|
|
549
|
+
let resolvedPassword = this.config.password;
|
|
550
|
+
const isAws = isAwsDsqlConfig(this.config);
|
|
551
|
+
if (isAws) {
|
|
552
|
+
resolvedPassword = await getAwsDsqlToken(this.config.aws);
|
|
553
|
+
}
|
|
554
|
+
const host = isAws ? this.config.aws.hostname : (this.config.host || 'localhost');
|
|
555
|
+
const port = this.config.aws?.port ?? this.config.port ?? 5432;
|
|
556
|
+
const user = this.config.aws?.user ?? this.config.user ?? (isAws ? 'admin' : undefined);
|
|
557
|
+
const ssl = isAws ? (this.config.aws.ssl ?? true) : this.config.ssl;
|
|
558
|
+
this.client = new PgClientClass({
|
|
559
|
+
host,
|
|
560
|
+
port,
|
|
561
|
+
database: this.config.database,
|
|
562
|
+
user,
|
|
563
|
+
password: resolvedPassword,
|
|
564
|
+
connectionString: this.config.connectionString,
|
|
565
|
+
ssl
|
|
566
|
+
});
|
|
567
|
+
this.client.on('end', () => {
|
|
568
|
+
this.clientConnected = false;
|
|
569
|
+
this.emitter.emit('end');
|
|
570
|
+
});
|
|
571
|
+
this.client.on('error', (err) => {
|
|
572
|
+
this.clientConnected = false;
|
|
573
|
+
this.emitter.emit('error', err);
|
|
574
|
+
});
|
|
575
|
+
this.client.on('notice', (msg) => this.emitter.emit('notice', msg));
|
|
576
|
+
this.client.on('notification', (msg) => this.emitter.emit('notification', msg));
|
|
577
|
+
await this.client.connect();
|
|
578
|
+
this.clientConnected = true;
|
|
579
|
+
this.emitter.emit('connect', this.client);
|
|
580
|
+
}
|
|
497
581
|
async subscribe(channel, callback) {
|
|
498
582
|
if (!this.listener) {
|
|
499
583
|
this.listener = new ListenerConnection({
|
|
@@ -540,6 +624,7 @@ export class Relq {
|
|
|
540
624
|
else if (this.client) {
|
|
541
625
|
this.client.removeAllListeners();
|
|
542
626
|
await this.client.end();
|
|
627
|
+
this.clientConnected = false;
|
|
543
628
|
}
|
|
544
629
|
this.isClosed = true;
|
|
545
630
|
activeRelqInstances.delete(this);
|
|
@@ -565,26 +650,52 @@ export class Relq {
|
|
|
565
650
|
return this;
|
|
566
651
|
}
|
|
567
652
|
async _executeQuery(sql) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const startTime = performance.now();
|
|
574
|
-
let result;
|
|
653
|
+
if (!sql || typeof sql !== 'string' || sql.trim() === '') {
|
|
654
|
+
throw new RelqConfigError(`Invalid SQL query: ${sql === null ? 'null' : sql === undefined ? 'undefined' : 'empty string'}`);
|
|
655
|
+
}
|
|
656
|
+
await this.initialize();
|
|
657
|
+
const executeQuery = async () => {
|
|
575
658
|
if (this.pool) {
|
|
576
|
-
|
|
659
|
+
return await this.pool.query(sql);
|
|
577
660
|
}
|
|
578
661
|
else if (this.client) {
|
|
579
|
-
|
|
662
|
+
await this.ensureConnection();
|
|
663
|
+
return await this.client.query(sql);
|
|
580
664
|
}
|
|
581
665
|
else {
|
|
582
666
|
throw new RelqConfigError('No database connection available');
|
|
583
667
|
}
|
|
668
|
+
};
|
|
669
|
+
try {
|
|
670
|
+
const startTime = performance.now();
|
|
671
|
+
const result = await executeQuery();
|
|
584
672
|
const duration = performance.now() - startTime;
|
|
585
673
|
return { result, duration };
|
|
586
674
|
}
|
|
587
675
|
catch (error) {
|
|
676
|
+
const isConnectionError = error.code === 'ECONNRESET' ||
|
|
677
|
+
error.code === 'ETIMEDOUT' ||
|
|
678
|
+
error.code === 'EPIPE' ||
|
|
679
|
+
error.code === 'ENOTCONN' ||
|
|
680
|
+
error.code === '57P01' ||
|
|
681
|
+
error.code === '57P02' ||
|
|
682
|
+
error.code === '57P03' ||
|
|
683
|
+
error.message?.includes('Connection terminated') ||
|
|
684
|
+
error.message?.includes('connection is closed') ||
|
|
685
|
+
error.message?.includes('Client has encountered a connection error');
|
|
686
|
+
if (isConnectionError && !this.usePooling && this.client) {
|
|
687
|
+
this.clientConnected = false;
|
|
688
|
+
try {
|
|
689
|
+
await this.ensureConnection();
|
|
690
|
+
const startTime = performance.now();
|
|
691
|
+
const result = await this.client.query(sql);
|
|
692
|
+
const duration = performance.now() - startTime;
|
|
693
|
+
return { result, duration };
|
|
694
|
+
}
|
|
695
|
+
catch (retryError) {
|
|
696
|
+
throw parsePostgresError(error, sql);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
588
699
|
throw parsePostgresError(error, sql);
|
|
589
700
|
}
|
|
590
701
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8347,6 +8347,8 @@ export declare class Relq<TSchema = any> {
|
|
|
8347
8347
|
private initialized;
|
|
8348
8348
|
private initPromise?;
|
|
8349
8349
|
private isClosed;
|
|
8350
|
+
private clientConnected;
|
|
8351
|
+
private connectPromise?;
|
|
8350
8352
|
private environment;
|
|
8351
8353
|
private poolErrorHandler?;
|
|
8352
8354
|
private poolConnectHandler?;
|
|
@@ -8494,6 +8496,17 @@ export declare class Relq<TSchema = any> {
|
|
|
8494
8496
|
* @internal
|
|
8495
8497
|
*/
|
|
8496
8498
|
private connect;
|
|
8499
|
+
/**
|
|
8500
|
+
* Ensure single client is connected, with auto-reconnect on failure
|
|
8501
|
+
* Handles concurrent calls by reusing the same connect promise
|
|
8502
|
+
* @internal
|
|
8503
|
+
*/
|
|
8504
|
+
private ensureConnection;
|
|
8505
|
+
/**
|
|
8506
|
+
* Recreate the client after connection loss
|
|
8507
|
+
* @internal
|
|
8508
|
+
*/
|
|
8509
|
+
private recreateClient;
|
|
8497
8510
|
/**
|
|
8498
8511
|
* Subscribe to a PostgreSQL NOTIFY channel
|
|
8499
8512
|
*
|