@spfn/core 0.2.0-beta.5 → 0.2.0-beta.50
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/LICENSE +1 -1
- package/README.md +181 -1281
- package/dist/{boss-BO8ty33K.d.ts → boss-Cxqc-Oiw.d.ts} +37 -7
- package/dist/cache/index.js +32 -29
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +55 -8
- package/dist/codegen/index.js +179 -5
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +168 -6
- package/dist/config/index.js +29 -5
- package/dist/config/index.js.map +1 -1
- package/dist/db/index.d.ts +218 -4
- package/dist/db/index.js +351 -57
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +2 -1
- package/dist/env/index.js +2 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +26 -19
- package/dist/env/loader.js +32 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +33 -3
- package/dist/event/index.js +17 -1
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +42 -3
- package/dist/event/sse/client.js +128 -45
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +12 -5
- package/dist/event/sse/index.js +188 -20
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +59 -0
- package/dist/event/ws/client.js +273 -0
- package/dist/event/ws/client.js.map +1 -0
- package/dist/event/ws/index.d.ts +94 -0
- package/dist/event/ws/index.js +213 -0
- package/dist/event/ws/index.js.map +1 -0
- package/dist/job/index.d.ts +23 -8
- package/dist/job/index.js +154 -44
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.js +14 -0
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +23 -1
- package/dist/middleware/index.js +58 -5
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -2
- package/dist/nextjs/index.js +77 -31
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +44 -23
- package/dist/nextjs/server.js +83 -65
- package/dist/nextjs/server.js.map +1 -1
- package/dist/route/index.d.ts +158 -4
- package/dist/route/index.js +238 -22
- package/dist/route/index.js.map +1 -1
- package/dist/server/index.d.ts +308 -17
- package/dist/server/index.js +1128 -261
- package/dist/server/index.js.map +1 -1
- package/dist/{router-Di7ENoah.d.ts → token-manager-CyG7la3p.d.ts} +116 -1
- package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
- package/dist/types-C1jMLGwK.d.ts +257 -0
- package/dist/types-Cfj--lfr.d.ts +151 -0
- package/docs/file-upload.md +717 -0
- package/package.json +18 -5
- package/dist/types-B-e_f2dQ.d.ts +0 -121
package/dist/db/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { drizzle } from 'drizzle-orm/postgres-js';
|
|
2
2
|
import { env } from '@spfn/core/config';
|
|
3
3
|
import { logger } from '@spfn/core/logger';
|
|
4
|
+
import net from 'net';
|
|
4
5
|
import postgres from 'postgres';
|
|
5
6
|
import { QueryError, ConnectionError, DeadlockError, TransactionError, ConstraintViolationError, DuplicateEntryError, DatabaseError } from '@spfn/core/errors';
|
|
6
7
|
import { parseNumber, parseBoolean } from '@spfn/core/env';
|
|
7
8
|
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
8
9
|
import { join, dirname, basename } from 'path';
|
|
9
|
-
import { bigserial, timestamp, uuid as uuid$1, text, jsonb, pgSchema } from 'drizzle-orm/pg-core';
|
|
10
|
+
import { bigserial, timestamp, bigint, uuid as uuid$1, text, jsonb, pgSchema } from 'drizzle-orm/pg-core';
|
|
10
11
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
11
12
|
import { createMiddleware } from 'hono/factory';
|
|
12
13
|
import { randomUUID } from 'crypto';
|
|
13
|
-
import { count as count$1,
|
|
14
|
+
import { sql, count as count$1, eq, and } from 'drizzle-orm';
|
|
14
15
|
|
|
15
16
|
// src/db/manager/factory.ts
|
|
16
17
|
function parseUniqueViolation(message) {
|
|
@@ -129,6 +130,12 @@ function fromPostgresError(error) {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
// src/db/manager/connection.ts
|
|
133
|
+
function getSocketFamily() {
|
|
134
|
+
const family = process.env.DATABASE_SOCKET_FAMILY;
|
|
135
|
+
if (family === "4") return 4;
|
|
136
|
+
if (family === "6") return 6;
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
132
139
|
var dbLogger = logger.child("@spfn/core:database");
|
|
133
140
|
var DEFAULT_CONNECT_TIMEOUT = 10;
|
|
134
141
|
function delay(ms) {
|
|
@@ -189,10 +196,21 @@ async function createDatabaseConnection(connectionString, poolConfig, retryConfi
|
|
|
189
196
|
let client;
|
|
190
197
|
for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {
|
|
191
198
|
try {
|
|
199
|
+
const socketFamily = getSocketFamily();
|
|
192
200
|
client = postgres(connectionString, {
|
|
193
201
|
max: poolConfig.max,
|
|
194
202
|
idle_timeout: poolConfig.idleTimeout,
|
|
195
|
-
connect_timeout: DEFAULT_CONNECT_TIMEOUT
|
|
203
|
+
connect_timeout: DEFAULT_CONNECT_TIMEOUT,
|
|
204
|
+
...socketFamily && {
|
|
205
|
+
socket: ({ host, port }) => new Promise((resolve, reject) => {
|
|
206
|
+
const socket = new net.Socket();
|
|
207
|
+
socket.on("error", reject);
|
|
208
|
+
socket.connect(
|
|
209
|
+
{ port: port[0], host: host[0], family: socketFamily },
|
|
210
|
+
() => resolve(socket)
|
|
211
|
+
);
|
|
212
|
+
})
|
|
213
|
+
}
|
|
196
214
|
});
|
|
197
215
|
await client`SELECT 1 as test`;
|
|
198
216
|
if (attempt > 0) {
|
|
@@ -502,7 +520,20 @@ var setHealthCheckInterval = (interval) => {
|
|
|
502
520
|
var setMonitoringConfig = (config) => {
|
|
503
521
|
globalThis.__SPFN_DB_MONITORING__ = config;
|
|
504
522
|
};
|
|
523
|
+
var getInitOptions = () => globalThis.__SPFN_DB_INIT_OPTIONS__;
|
|
524
|
+
var setInitOptions = (options) => {
|
|
525
|
+
globalThis.__SPFN_DB_INIT_OPTIONS__ = options;
|
|
526
|
+
};
|
|
527
|
+
var getIsClosing = () => globalThis.__SPFN_DB_CLOSING__ === true;
|
|
528
|
+
var setIsClosing = (closing) => {
|
|
529
|
+
globalThis.__SPFN_DB_CLOSING__ = closing;
|
|
530
|
+
};
|
|
505
531
|
var dbLogger3 = logger.child("@spfn/core:database");
|
|
532
|
+
var CLIENT_CLOSE_TIMEOUT = 5;
|
|
533
|
+
var isReconnecting = false;
|
|
534
|
+
function isReconnectingNow() {
|
|
535
|
+
return isReconnecting;
|
|
536
|
+
}
|
|
506
537
|
async function testDatabaseConnection(db) {
|
|
507
538
|
await db.execute("SELECT 1");
|
|
508
539
|
}
|
|
@@ -514,8 +545,17 @@ async function performHealthCheck(getDatabase2) {
|
|
|
514
545
|
await testDatabaseConnection(read);
|
|
515
546
|
}
|
|
516
547
|
}
|
|
517
|
-
async function
|
|
518
|
-
|
|
548
|
+
async function closeClient(client) {
|
|
549
|
+
try {
|
|
550
|
+
await client.end({ timeout: CLIENT_CLOSE_TIMEOUT });
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
async function reconnectAndRestore(options) {
|
|
555
|
+
if (getIsClosing()) {
|
|
556
|
+
dbLogger3.debug("reconnectAndRestore aborted: database is closing");
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
519
559
|
const result = await createDatabaseFromEnv(options);
|
|
520
560
|
if (!result.write) {
|
|
521
561
|
return false;
|
|
@@ -524,15 +564,33 @@ async function reconnectAndRestore(options, closeDatabase2) {
|
|
|
524
564
|
if (result.read && result.read !== result.write) {
|
|
525
565
|
await testDatabaseConnection(result.read);
|
|
526
566
|
}
|
|
567
|
+
if (getIsClosing()) {
|
|
568
|
+
dbLogger3.warn("reconnectAndRestore: close started mid-rebuild, discarding new pool");
|
|
569
|
+
if (result.writeClient) {
|
|
570
|
+
await closeClient(result.writeClient);
|
|
571
|
+
}
|
|
572
|
+
if (result.readClient && result.readClient !== result.writeClient) {
|
|
573
|
+
await closeClient(result.readClient);
|
|
574
|
+
}
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
const oldWriteClient = getWriteClient();
|
|
578
|
+
const oldReadClient = getReadClient();
|
|
527
579
|
setWriteInstance(result.write);
|
|
528
580
|
setReadInstance(result.read);
|
|
529
581
|
setWriteClient(result.writeClient);
|
|
530
582
|
setReadClient(result.readClient);
|
|
531
583
|
const monConfig = buildMonitoringConfig(options?.monitoring);
|
|
532
584
|
setMonitoringConfig(monConfig);
|
|
585
|
+
if (oldWriteClient) {
|
|
586
|
+
closeClient(oldWriteClient);
|
|
587
|
+
}
|
|
588
|
+
if (oldReadClient && oldReadClient !== oldWriteClient) {
|
|
589
|
+
closeClient(oldReadClient);
|
|
590
|
+
}
|
|
533
591
|
return true;
|
|
534
592
|
}
|
|
535
|
-
function startHealthCheck(config, options, getDatabase2
|
|
593
|
+
function startHealthCheck(config, options, getDatabase2) {
|
|
536
594
|
const healthCheck = getHealthCheckInterval();
|
|
537
595
|
if (healthCheck) {
|
|
538
596
|
dbLogger3.debug("Health check already running");
|
|
@@ -543,48 +601,77 @@ function startHealthCheck(config, options, getDatabase2, closeDatabase2) {
|
|
|
543
601
|
reconnect: config.reconnect
|
|
544
602
|
});
|
|
545
603
|
const interval = setInterval(async () => {
|
|
604
|
+
if (isReconnecting) {
|
|
605
|
+
dbLogger3.debug("Health check skipped: reconnection in progress");
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
546
608
|
try {
|
|
547
609
|
await performHealthCheck(getDatabase2);
|
|
548
610
|
} catch (error) {
|
|
549
611
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
550
612
|
dbLogger3.error("Database health check failed", { error: message });
|
|
551
613
|
if (config.reconnect) {
|
|
552
|
-
await attemptReconnection(config, options,
|
|
614
|
+
await attemptReconnection(config, options, "health_check_failed");
|
|
553
615
|
}
|
|
554
616
|
}
|
|
555
617
|
}, config.interval);
|
|
556
618
|
setHealthCheckInterval(interval);
|
|
557
619
|
}
|
|
558
|
-
async function
|
|
620
|
+
async function triggerForceReconnect(reason) {
|
|
621
|
+
if (!getWriteInstance()) {
|
|
622
|
+
dbLogger3.warn("Force reconnect skipped: database not initialized", { reason });
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
if (getIsClosing()) {
|
|
626
|
+
dbLogger3.debug("Force reconnect skipped: database is closing", { reason });
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
const options = getInitOptions();
|
|
630
|
+
const config = buildHealthCheckConfig(options?.healthCheck);
|
|
631
|
+
dbLogger3.warn("Force reconnect triggered", { reason });
|
|
632
|
+
return await attemptReconnection(config, options, reason);
|
|
633
|
+
}
|
|
634
|
+
async function attemptReconnection(config, options, reason) {
|
|
635
|
+
if (isReconnecting) {
|
|
636
|
+
dbLogger3.debug("Reconnection coalesced: attempt already in progress", { reason });
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
isReconnecting = true;
|
|
559
640
|
dbLogger3.warn("Attempting database reconnection", {
|
|
641
|
+
reason,
|
|
560
642
|
maxRetries: config.maxRetries,
|
|
561
643
|
retryInterval: `${config.retryInterval}ms`
|
|
562
644
|
});
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
645
|
+
try {
|
|
646
|
+
for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
|
|
647
|
+
try {
|
|
648
|
+
dbLogger3.debug(`Reconnection attempt ${attempt}/${config.maxRetries}`);
|
|
649
|
+
if (attempt > 1) {
|
|
650
|
+
await new Promise((resolve) => setTimeout(resolve, config.retryInterval));
|
|
651
|
+
}
|
|
652
|
+
const success = await reconnectAndRestore(options);
|
|
653
|
+
if (success) {
|
|
654
|
+
dbLogger3.info("Database reconnection successful", { attempt });
|
|
655
|
+
return true;
|
|
656
|
+
} else {
|
|
657
|
+
dbLogger3.error(`Reconnection attempt ${attempt} failed: No write database instance created`);
|
|
658
|
+
}
|
|
659
|
+
} catch (error) {
|
|
660
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
661
|
+
dbLogger3.error(`Reconnection attempt ${attempt} failed`, {
|
|
662
|
+
error: message,
|
|
663
|
+
attempt,
|
|
664
|
+
maxRetries: config.maxRetries
|
|
665
|
+
});
|
|
568
666
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
dbLogger3.info("Database reconnection successful", { attempt });
|
|
572
|
-
return;
|
|
573
|
-
} else {
|
|
574
|
-
dbLogger3.error(`Reconnection attempt ${attempt} failed: No write database instance created`);
|
|
667
|
+
if (attempt === config.maxRetries) {
|
|
668
|
+
dbLogger3.error("Max reconnection attempts reached, will retry on next health check");
|
|
575
669
|
}
|
|
576
|
-
} catch (error) {
|
|
577
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
578
|
-
dbLogger3.error(`Reconnection attempt ${attempt} failed`, {
|
|
579
|
-
error: message,
|
|
580
|
-
attempt,
|
|
581
|
-
maxRetries: config.maxRetries
|
|
582
|
-
});
|
|
583
|
-
}
|
|
584
|
-
if (attempt === config.maxRetries) {
|
|
585
|
-
dbLogger3.error("Max reconnection attempts reached, giving up");
|
|
586
670
|
}
|
|
671
|
+
} finally {
|
|
672
|
+
isReconnecting = false;
|
|
587
673
|
}
|
|
674
|
+
return true;
|
|
588
675
|
}
|
|
589
676
|
function stopHealthCheck() {
|
|
590
677
|
const healthCheck = getHealthCheckInterval();
|
|
@@ -593,6 +680,7 @@ function stopHealthCheck() {
|
|
|
593
680
|
setHealthCheckInterval(void 0);
|
|
594
681
|
dbLogger3.info("Database health check stopped");
|
|
595
682
|
}
|
|
683
|
+
isReconnecting = false;
|
|
596
684
|
}
|
|
597
685
|
|
|
598
686
|
// src/db/manager/manager.ts
|
|
@@ -604,7 +692,6 @@ var STACK_TRACE_PATTERNS = {
|
|
|
604
692
|
withoutParens: /at (.+):(\d+):(\d+)/
|
|
605
693
|
};
|
|
606
694
|
var initPromise = null;
|
|
607
|
-
var isClosing = false;
|
|
608
695
|
async function cleanupDatabaseConnections(writeClient, readClient) {
|
|
609
696
|
const cleanupPromises = [];
|
|
610
697
|
if (writeClient) {
|
|
@@ -705,7 +792,7 @@ function setDatabase(write, read) {
|
|
|
705
792
|
setReadInstance(read ?? write);
|
|
706
793
|
}
|
|
707
794
|
async function initDatabase(options) {
|
|
708
|
-
if (
|
|
795
|
+
if (getIsClosing()) {
|
|
709
796
|
throw new Error("Cannot initialize database while closing");
|
|
710
797
|
}
|
|
711
798
|
const writeInst = getWriteInstance();
|
|
@@ -727,7 +814,7 @@ async function initDatabase(options) {
|
|
|
727
814
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
728
815
|
throw new Error(`Database connection test failed: ${message}`);
|
|
729
816
|
}
|
|
730
|
-
if (
|
|
817
|
+
if (getIsClosing()) {
|
|
731
818
|
dbLogger4.warn("Database closed during initialization, cleaning up...");
|
|
732
819
|
await cleanupDatabaseConnections(result.writeClient, result.readClient);
|
|
733
820
|
throw new Error("Database closed during initialization");
|
|
@@ -736,13 +823,14 @@ async function initDatabase(options) {
|
|
|
736
823
|
setReadInstance(result.read);
|
|
737
824
|
setWriteClient(result.writeClient);
|
|
738
825
|
setReadClient(result.readClient);
|
|
826
|
+
setInitOptions(options);
|
|
739
827
|
const hasReplica = result.read && result.read !== result.write;
|
|
740
828
|
dbLogger4.info(
|
|
741
829
|
hasReplica ? "Database connected (Primary + Replica)" : "Database connected"
|
|
742
830
|
);
|
|
743
831
|
const healthCheckConfig = buildHealthCheckConfig(options?.healthCheck);
|
|
744
832
|
if (healthCheckConfig.enabled) {
|
|
745
|
-
startHealthCheck(healthCheckConfig, options, getDatabase
|
|
833
|
+
startHealthCheck(healthCheckConfig, options, getDatabase);
|
|
746
834
|
}
|
|
747
835
|
const monConfig = buildMonitoringConfig(options?.monitoring);
|
|
748
836
|
setMonitoringConfig(monConfig);
|
|
@@ -760,11 +848,11 @@ async function initDatabase(options) {
|
|
|
760
848
|
return await initPromise;
|
|
761
849
|
}
|
|
762
850
|
async function closeDatabase() {
|
|
763
|
-
if (
|
|
851
|
+
if (getIsClosing()) {
|
|
764
852
|
dbLogger4.debug("Database close already in progress");
|
|
765
853
|
return;
|
|
766
854
|
}
|
|
767
|
-
|
|
855
|
+
setIsClosing(true);
|
|
768
856
|
if (initPromise) {
|
|
769
857
|
dbLogger4.debug("Waiting for database initialization to complete before closing...");
|
|
770
858
|
try {
|
|
@@ -777,7 +865,7 @@ async function closeDatabase() {
|
|
|
777
865
|
const readInst = getReadInstance();
|
|
778
866
|
if (!writeInst && !readInst) {
|
|
779
867
|
dbLogger4.debug("No database connections to close");
|
|
780
|
-
|
|
868
|
+
setIsClosing(false);
|
|
781
869
|
return;
|
|
782
870
|
}
|
|
783
871
|
try {
|
|
@@ -799,9 +887,13 @@ async function closeDatabase() {
|
|
|
799
887
|
setWriteClient(void 0);
|
|
800
888
|
setReadClient(void 0);
|
|
801
889
|
setMonitoringConfig(void 0);
|
|
802
|
-
|
|
890
|
+
setInitOptions(void 0);
|
|
891
|
+
setIsClosing(false);
|
|
803
892
|
}
|
|
804
893
|
}
|
|
894
|
+
async function forceReconnectDatabase(reason = "manual") {
|
|
895
|
+
return await triggerForceReconnect(reason);
|
|
896
|
+
}
|
|
805
897
|
function getDatabaseInfo() {
|
|
806
898
|
const writeInst = getWriteInstance();
|
|
807
899
|
const readInst = getReadInstance();
|
|
@@ -811,7 +903,123 @@ function getDatabaseInfo() {
|
|
|
811
903
|
isReplica: !!(readInst && readInst !== writeInst)
|
|
812
904
|
};
|
|
813
905
|
}
|
|
814
|
-
var
|
|
906
|
+
var dbLogger5 = logger.child("@spfn/core:database");
|
|
907
|
+
var POSTGRES_JS_CONNECTION_CODES = /* @__PURE__ */ new Set([
|
|
908
|
+
"CONNECTION_ENDED",
|
|
909
|
+
"CONNECTION_CLOSED",
|
|
910
|
+
"CONNECTION_DESTROYED",
|
|
911
|
+
"CONNECT_TIMEOUT",
|
|
912
|
+
"CONNECTION_CONNECT_TIMEOUT"
|
|
913
|
+
]);
|
|
914
|
+
var NODE_NET_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
915
|
+
"ECONNRESET",
|
|
916
|
+
"ECONNREFUSED",
|
|
917
|
+
"EPIPE",
|
|
918
|
+
"ETIMEDOUT",
|
|
919
|
+
"EHOSTUNREACH",
|
|
920
|
+
"ENETUNREACH",
|
|
921
|
+
"ENOTFOUND"
|
|
922
|
+
]);
|
|
923
|
+
function isConnectionSqlState(code) {
|
|
924
|
+
if (code.startsWith("08")) return true;
|
|
925
|
+
if (code === "53300") return true;
|
|
926
|
+
if (code === "57P01" || code === "57P02" || code === "57P03") return true;
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
function* unwrap(error) {
|
|
930
|
+
const seen = /* @__PURE__ */ new Set();
|
|
931
|
+
const stack = [error];
|
|
932
|
+
while (stack.length > 0) {
|
|
933
|
+
const current = stack.pop();
|
|
934
|
+
if (!current || typeof current !== "object" || seen.has(current)) {
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
seen.add(current);
|
|
938
|
+
const obj = current;
|
|
939
|
+
yield obj;
|
|
940
|
+
for (const key of ["cause", "original", "error", "err", "inner"]) {
|
|
941
|
+
const nested = obj[key];
|
|
942
|
+
if (nested && typeof nested === "object" && !seen.has(nested)) {
|
|
943
|
+
stack.push(nested);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function isConnectionLevelError(error) {
|
|
949
|
+
if (!error) return false;
|
|
950
|
+
if (error instanceof ConnectionError) return true;
|
|
951
|
+
for (const candidate of unwrap(error)) {
|
|
952
|
+
if (candidate instanceof ConnectionError) return true;
|
|
953
|
+
const code = candidate.code;
|
|
954
|
+
if (typeof code === "string") {
|
|
955
|
+
if (POSTGRES_JS_CONNECTION_CODES.has(code)) return true;
|
|
956
|
+
if (NODE_NET_ERROR_CODES.has(code)) return true;
|
|
957
|
+
if (isConnectionSqlState(code)) return true;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return false;
|
|
961
|
+
}
|
|
962
|
+
var DEFAULT_THRESHOLD = 3;
|
|
963
|
+
var DEFAULT_WINDOW_MS = 1e4;
|
|
964
|
+
var MIN_WINDOW_MS = 1e3;
|
|
965
|
+
function readPositiveIntEnv(key, defaultValue, min) {
|
|
966
|
+
const raw = process.env[key];
|
|
967
|
+
if (raw === void 0) return defaultValue;
|
|
968
|
+
try {
|
|
969
|
+
return parseNumber(raw, { min, integer: true });
|
|
970
|
+
} catch {
|
|
971
|
+
return defaultValue;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
var ERROR_THRESHOLD = readPositiveIntEnv("DB_RECONNECT_ERROR_THRESHOLD", DEFAULT_THRESHOLD, 1);
|
|
975
|
+
var ERROR_WINDOW_MS = readPositiveIntEnv("DB_RECONNECT_ERROR_WINDOW_MS", DEFAULT_WINDOW_MS, MIN_WINDOW_MS);
|
|
976
|
+
var errorTimestamps = [];
|
|
977
|
+
var reportedErrors = /* @__PURE__ */ new WeakSet();
|
|
978
|
+
function resetConnectionErrorCounter() {
|
|
979
|
+
errorTimestamps.length = 0;
|
|
980
|
+
}
|
|
981
|
+
function checkAndMarkReported(error) {
|
|
982
|
+
let alreadySeen = false;
|
|
983
|
+
const toMark = [];
|
|
984
|
+
for (const candidate of unwrap(error)) {
|
|
985
|
+
if (reportedErrors.has(candidate)) {
|
|
986
|
+
alreadySeen = true;
|
|
987
|
+
}
|
|
988
|
+
toMark.push(candidate);
|
|
989
|
+
}
|
|
990
|
+
if (alreadySeen) return true;
|
|
991
|
+
for (const obj of toMark) {
|
|
992
|
+
reportedErrors.add(obj);
|
|
993
|
+
}
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
function reportDatabaseError(error) {
|
|
997
|
+
try {
|
|
998
|
+
if (!isConnectionLevelError(error)) return;
|
|
999
|
+
if (isReconnectingNow()) return;
|
|
1000
|
+
if (checkAndMarkReported(error)) return;
|
|
1001
|
+
const now = Date.now();
|
|
1002
|
+
errorTimestamps.push(now);
|
|
1003
|
+
const cutoff = now - ERROR_WINDOW_MS;
|
|
1004
|
+
while (errorTimestamps.length > 0 && errorTimestamps[0] < cutoff) {
|
|
1005
|
+
errorTimestamps.shift();
|
|
1006
|
+
}
|
|
1007
|
+
if (errorTimestamps.length < ERROR_THRESHOLD) return;
|
|
1008
|
+
errorTimestamps.length = 0;
|
|
1009
|
+
dbLogger5.error("Connection-error threshold crossed, forcing pool rebuild", {
|
|
1010
|
+
threshold: ERROR_THRESHOLD,
|
|
1011
|
+
windowMs: ERROR_WINDOW_MS
|
|
1012
|
+
});
|
|
1013
|
+
triggerForceReconnect("query_error_threshold").catch((err) => {
|
|
1014
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1015
|
+
dbLogger5.error("Forced reconnect after error threshold failed", { error: message });
|
|
1016
|
+
});
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1019
|
+
dbLogger5.debug("reportDatabaseError itself threw, ignoring", { error: message });
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
var BARREL_FILE_PATTERNS = [
|
|
815
1023
|
"/index",
|
|
816
1024
|
"/index.ts",
|
|
817
1025
|
"/index.js",
|
|
@@ -819,11 +1027,14 @@ var INDEX_FILE_PATTERNS = [
|
|
|
819
1027
|
"\\index",
|
|
820
1028
|
"\\index.ts",
|
|
821
1029
|
"\\index.js",
|
|
822
|
-
"\\index.mjs"
|
|
1030
|
+
"\\index.mjs",
|
|
1031
|
+
"\\config.ts",
|
|
1032
|
+
"\\config.js",
|
|
1033
|
+
"\\config.mjs"
|
|
823
1034
|
];
|
|
824
1035
|
var SUPPORTED_EXTENSIONS = [".ts", ".js", ".mjs"];
|
|
825
|
-
function
|
|
826
|
-
return
|
|
1036
|
+
function isBarrelFile(filePath) {
|
|
1037
|
+
return BARREL_FILE_PATTERNS.some((pattern) => filePath.endsWith(pattern));
|
|
827
1038
|
}
|
|
828
1039
|
function isAbsolutePath(path) {
|
|
829
1040
|
if (path.startsWith("/")) return true;
|
|
@@ -833,8 +1044,8 @@ function hasSupportedExtension(filePath) {
|
|
|
833
1044
|
if (filePath.endsWith(".d.ts")) return false;
|
|
834
1045
|
return SUPPORTED_EXTENSIONS.some((ext) => filePath.endsWith(ext));
|
|
835
1046
|
}
|
|
836
|
-
function
|
|
837
|
-
return files.filter((file) => !
|
|
1047
|
+
function filterBarrelFiles(files) {
|
|
1048
|
+
return files.filter((file) => !isBarrelFile(file));
|
|
838
1049
|
}
|
|
839
1050
|
function scanDirectoryRecursive(dir, extension) {
|
|
840
1051
|
const files = [];
|
|
@@ -880,6 +1091,27 @@ function scanDirectorySingleLevel(dir, filePattern) {
|
|
|
880
1091
|
}
|
|
881
1092
|
return files;
|
|
882
1093
|
}
|
|
1094
|
+
function detectSchemasFromFiles(files) {
|
|
1095
|
+
const schemas = /* @__PURE__ */ new Set(["public"]);
|
|
1096
|
+
const pgSchemaPattern = /pgSchema\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
1097
|
+
const createSchemaPattern = /createSchema\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
1098
|
+
for (const filePath of files) {
|
|
1099
|
+
try {
|
|
1100
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1101
|
+
let match;
|
|
1102
|
+
while ((match = pgSchemaPattern.exec(content)) !== null) {
|
|
1103
|
+
schemas.add(match[1]);
|
|
1104
|
+
}
|
|
1105
|
+
while ((match = createSchemaPattern.exec(content)) !== null) {
|
|
1106
|
+
const packageName = match[1];
|
|
1107
|
+
const schemaName = packageName.replace(/@/g, "").replace(/\//g, "_").replace(/-/g, "_");
|
|
1108
|
+
schemas.add(schemaName);
|
|
1109
|
+
}
|
|
1110
|
+
} catch {
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return Array.from(schemas);
|
|
1114
|
+
}
|
|
883
1115
|
function expandGlobPattern(pattern) {
|
|
884
1116
|
if (!pattern.includes("*")) {
|
|
885
1117
|
return existsSync(pattern) ? [pattern] : [];
|
|
@@ -922,7 +1154,7 @@ function discoverPackageSchemas(cwd) {
|
|
|
922
1154
|
for (const schema of packageSchemas) {
|
|
923
1155
|
const absolutePath = join(pkgPath, schema);
|
|
924
1156
|
const expandedFiles = expandGlobPattern(absolutePath);
|
|
925
|
-
const schemaFiles =
|
|
1157
|
+
const schemaFiles = filterBarrelFiles(expandedFiles);
|
|
926
1158
|
schemas.push(...schemaFiles);
|
|
927
1159
|
}
|
|
928
1160
|
}
|
|
@@ -984,28 +1216,45 @@ function getDrizzleConfig(options = {}) {
|
|
|
984
1216
|
schema: schema2,
|
|
985
1217
|
out,
|
|
986
1218
|
dialect,
|
|
987
|
-
dbCredentials: getDbCredentials(dialect, databaseUrl)
|
|
1219
|
+
dbCredentials: getDbCredentials(dialect, databaseUrl),
|
|
1220
|
+
migrations: {
|
|
1221
|
+
prefix: options.migrationPrefix ?? "timestamp"
|
|
1222
|
+
}
|
|
988
1223
|
};
|
|
989
1224
|
}
|
|
990
1225
|
const userSchema = options.schema ?? "./src/server/entities/**/*.ts";
|
|
991
1226
|
const userSchemas = Array.isArray(userSchema) ? userSchema : [userSchema];
|
|
992
1227
|
const packageSchemas = options.disablePackageDiscovery ? [] : discoverPackageSchemas(options.cwd ?? process.cwd());
|
|
993
1228
|
let allSchemas = [...userSchemas, ...packageSchemas];
|
|
1229
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1230
|
+
let expandedFiles = [];
|
|
994
1231
|
if (options.expandGlobs) {
|
|
995
|
-
const expandedSchemas = [];
|
|
996
1232
|
for (const schema2 of allSchemas) {
|
|
997
|
-
const
|
|
998
|
-
const
|
|
999
|
-
|
|
1233
|
+
const absoluteSchema = isAbsolutePath(schema2) ? schema2 : join(cwd, schema2);
|
|
1234
|
+
const expanded = expandGlobPattern(absoluteSchema);
|
|
1235
|
+
const filtered = filterBarrelFiles(expanded);
|
|
1236
|
+
expandedFiles.push(...filtered);
|
|
1000
1237
|
}
|
|
1001
|
-
allSchemas =
|
|
1238
|
+
allSchemas = expandedFiles;
|
|
1002
1239
|
}
|
|
1003
1240
|
const schema = allSchemas.length === 1 ? allSchemas[0] : allSchemas;
|
|
1241
|
+
let schemaFilter;
|
|
1242
|
+
if (dialect === "postgresql") {
|
|
1243
|
+
if (options.schemaFilter) {
|
|
1244
|
+
schemaFilter = options.schemaFilter;
|
|
1245
|
+
} else if (options.autoDetectSchemas && expandedFiles.length > 0) {
|
|
1246
|
+
schemaFilter = detectSchemasFromFiles(expandedFiles);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1004
1249
|
return {
|
|
1005
1250
|
schema,
|
|
1006
1251
|
out,
|
|
1007
1252
|
dialect,
|
|
1008
|
-
dbCredentials: getDbCredentials(dialect, databaseUrl)
|
|
1253
|
+
dbCredentials: getDbCredentials(dialect, databaseUrl),
|
|
1254
|
+
schemaFilter,
|
|
1255
|
+
migrations: {
|
|
1256
|
+
prefix: options.migrationPrefix ?? "timestamp"
|
|
1257
|
+
}
|
|
1009
1258
|
};
|
|
1010
1259
|
}
|
|
1011
1260
|
function getDbCredentials(dialect, url) {
|
|
@@ -1032,13 +1281,17 @@ function generateDrizzleConfigFile(options = {}) {
|
|
|
1032
1281
|
const schemaValue = Array.isArray(config.schema) ? `[
|
|
1033
1282
|
${config.schema.map((s) => `'${normalizeSchemaPath(s)}'`).join(",\n ")}
|
|
1034
1283
|
]` : `'${normalizeSchemaPath(config.schema)}'`;
|
|
1284
|
+
const schemaFilterLine = config.schemaFilter && config.schemaFilter.length > 0 ? `
|
|
1285
|
+
schemaFilter: ${JSON.stringify(config.schemaFilter)},` : "";
|
|
1286
|
+
const migrationsLine = config.migrations ? `
|
|
1287
|
+
migrations: ${JSON.stringify(config.migrations)},` : "";
|
|
1035
1288
|
return `import { defineConfig } from 'drizzle-kit';
|
|
1036
1289
|
|
|
1037
1290
|
export default defineConfig({
|
|
1038
1291
|
schema: ${schemaValue},
|
|
1039
1292
|
out: '${config.out}',
|
|
1040
1293
|
dialect: '${config.dialect}',
|
|
1041
|
-
dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)}
|
|
1294
|
+
dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)},${schemaFilterLine}${migrationsLine}
|
|
1042
1295
|
});
|
|
1043
1296
|
`;
|
|
1044
1297
|
}
|
|
@@ -1052,10 +1305,10 @@ function timestamps() {
|
|
|
1052
1305
|
};
|
|
1053
1306
|
}
|
|
1054
1307
|
function foreignKey(name, reference, options) {
|
|
1055
|
-
return
|
|
1308
|
+
return bigint(`${name}_id`, { mode: "number" }).notNull().references(reference, { onDelete: options?.onDelete ?? "cascade" });
|
|
1056
1309
|
}
|
|
1057
1310
|
function optionalForeignKey(name, reference, options) {
|
|
1058
|
-
return
|
|
1311
|
+
return bigint(`${name}_id`, { mode: "number" }).references(reference, { onDelete: options?.onDelete ?? "set null" });
|
|
1059
1312
|
}
|
|
1060
1313
|
function uuid() {
|
|
1061
1314
|
return uuid$1("id").defaultRandom().primaryKey();
|
|
@@ -1134,7 +1387,20 @@ function runWithTransaction(tx, txId, callback) {
|
|
|
1134
1387
|
} else {
|
|
1135
1388
|
txLogger.debug("Root transaction context set", { txId, level: newLevel });
|
|
1136
1389
|
}
|
|
1137
|
-
|
|
1390
|
+
const afterCommitCallbacks = existingContext ? existingContext.afterCommitCallbacks : [];
|
|
1391
|
+
return asyncContext.run({ tx, txId, level: newLevel, afterCommitCallbacks }, callback);
|
|
1392
|
+
}
|
|
1393
|
+
function onAfterCommit(callback) {
|
|
1394
|
+
const context = getTransactionContext();
|
|
1395
|
+
if (!context) {
|
|
1396
|
+
Promise.resolve().then(callback).catch((err) => {
|
|
1397
|
+
txLogger.error("afterCommit callback failed (no transaction)", {
|
|
1398
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1399
|
+
});
|
|
1400
|
+
});
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
context.afterCommitCallbacks.push(callback);
|
|
1138
1404
|
}
|
|
1139
1405
|
var MAX_TIMEOUT_MS = 2147483647;
|
|
1140
1406
|
var txLogger2 = logger.child("@spfn/core:transaction");
|
|
@@ -1217,13 +1483,21 @@ async function runInTransaction(callback, options = {}) {
|
|
|
1217
1483
|
txLogger2.debug("Transaction started", { txId, context });
|
|
1218
1484
|
}
|
|
1219
1485
|
const startTime = Date.now();
|
|
1486
|
+
let afterCommitCallbacks = [];
|
|
1220
1487
|
try {
|
|
1221
1488
|
const result = await writeDb.transaction(async (tx) => {
|
|
1222
1489
|
if (timeout > 0 && !isNested) {
|
|
1223
1490
|
await tx.execute(sql.raw(`SET LOCAL statement_timeout = ${timeout}`));
|
|
1224
1491
|
}
|
|
1225
1492
|
return await runWithTransaction(tx, txId, async () => {
|
|
1226
|
-
|
|
1493
|
+
const innerResult = await callback(tx);
|
|
1494
|
+
if (!isNested) {
|
|
1495
|
+
const ctx = getTransactionContext();
|
|
1496
|
+
if (ctx) {
|
|
1497
|
+
afterCommitCallbacks = [...ctx.afterCommitCallbacks];
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return innerResult;
|
|
1227
1501
|
});
|
|
1228
1502
|
});
|
|
1229
1503
|
const duration = Date.now() - startTime;
|
|
@@ -1243,6 +1517,24 @@ async function runInTransaction(callback, options = {}) {
|
|
|
1243
1517
|
});
|
|
1244
1518
|
}
|
|
1245
1519
|
}
|
|
1520
|
+
if (!isNested && afterCommitCallbacks.length > 0) {
|
|
1521
|
+
if (enableLogging) {
|
|
1522
|
+
txLogger2.debug("Executing afterCommit callbacks", {
|
|
1523
|
+
txId,
|
|
1524
|
+
context,
|
|
1525
|
+
count: afterCommitCallbacks.length
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
for (const cb of afterCommitCallbacks) {
|
|
1529
|
+
Promise.resolve().then(cb).catch((err) => {
|
|
1530
|
+
txLogger2.error("afterCommit callback failed", {
|
|
1531
|
+
txId,
|
|
1532
|
+
context,
|
|
1533
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1246
1538
|
return result;
|
|
1247
1539
|
} catch (error) {
|
|
1248
1540
|
const duration = Date.now() - startTime;
|
|
@@ -1289,6 +1581,7 @@ function Transactional(options = {}) {
|
|
|
1289
1581
|
}
|
|
1290
1582
|
);
|
|
1291
1583
|
} catch (error) {
|
|
1584
|
+
reportDatabaseError(error);
|
|
1292
1585
|
if (error instanceof DatabaseError) {
|
|
1293
1586
|
throw error;
|
|
1294
1587
|
}
|
|
@@ -1536,6 +1829,7 @@ var BaseRepository = class {
|
|
|
1536
1829
|
try {
|
|
1537
1830
|
return await queryFn();
|
|
1538
1831
|
} catch (error) {
|
|
1832
|
+
reportDatabaseError(error);
|
|
1539
1833
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
1540
1834
|
const repositoryName = this.constructor.name;
|
|
1541
1835
|
throw new RepositoryError(
|
|
@@ -1788,6 +2082,6 @@ var BaseRepository = class {
|
|
|
1788
2082
|
}
|
|
1789
2083
|
};
|
|
1790
2084
|
|
|
1791
|
-
export { BaseRepository, RepositoryError, Transactional, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, id, initDatabase, optionalForeignKey, packageNameToSchema, publishingFields, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
|
|
2085
|
+
export { BaseRepository, RepositoryError, Transactional, auditFields, checkConnection, closeDatabase, count, create, createDatabaseConnection, createDatabaseFromEnv, createMany, createSchema, deleteMany, deleteOne, detectDialect, enumText, findMany, findOne, forceReconnectDatabase, foreignKey, fromPostgresError, generateDrizzleConfigFile, getDatabase, getDatabaseInfo, getDrizzleConfig, getSchemaInfo, getTransaction, getTransactionContext, id, initDatabase, isConnectionLevelError, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, reportDatabaseError, resetConnectionErrorCounter, runInTransaction, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
|
|
1792
2086
|
//# sourceMappingURL=index.js.map
|
|
1793
2087
|
//# sourceMappingURL=index.js.map
|