@spfn/core 0.2.0-beta.4 → 0.2.0-beta.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/README.md +260 -1175
- package/dist/{boss-BO8ty33K.d.ts → boss-DI1r4kTS.d.ts} +24 -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 +128 -4
- package/dist/db/index.js +177 -50
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +55 -1
- package/dist/env/index.js +71 -3
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +27 -19
- package/dist/env/loader.js +33 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/event/index.d.ts +27 -1
- package/dist/event/index.js +6 -1
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +77 -2
- package/dist/event/sse/client.js +87 -24
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +10 -4
- package/dist/event/sse/index.js +158 -12
- package/dist/event/sse/index.js.map +1 -1
- package/dist/job/index.d.ts +23 -8
- package/dist/job/index.js +96 -20
- 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 +251 -12
- package/dist/route/index.js.map +1 -1
- package/dist/server/index.d.ts +251 -16
- package/dist/server/index.js +774 -228
- package/dist/server/index.js.map +1 -1
- package/dist/{types-D_N_U-Py.d.ts → types-7Mhoxnnt.d.ts} +21 -1
- package/dist/types-DKQ90YL7.d.ts +372 -0
- package/docs/cache.md +133 -0
- package/docs/codegen.md +74 -0
- package/docs/database.md +370 -0
- package/docs/entity.md +539 -0
- package/docs/env.md +499 -0
- package/docs/errors.md +319 -0
- package/docs/event.md +443 -0
- package/docs/file-upload.md +717 -0
- package/docs/job.md +131 -0
- package/docs/logger.md +108 -0
- package/docs/middleware.md +337 -0
- package/docs/nextjs.md +247 -0
- package/docs/repository.md +496 -0
- package/docs/route.md +497 -0
- package/docs/server.md +429 -0
- package/package.json +2 -1
- package/dist/types-B-e_f2dQ.d.ts +0 -121
package/dist/db/index.d.ts
CHANGED
|
@@ -376,6 +376,12 @@ interface DrizzleConfigOptions {
|
|
|
376
376
|
packageFilter?: string;
|
|
377
377
|
/** Expand glob patterns to actual file paths (useful for Drizzle Studio) */
|
|
378
378
|
expandGlobs?: boolean;
|
|
379
|
+
/** PostgreSQL schema filter for push/introspect commands */
|
|
380
|
+
schemaFilter?: string[];
|
|
381
|
+
/** Auto-detect PostgreSQL schemas from entity files (requires expandGlobs: true) */
|
|
382
|
+
autoDetectSchemas?: boolean;
|
|
383
|
+
/** Migration prefix strategy (default: 'timestamp') */
|
|
384
|
+
migrationPrefix?: 'index' | 'timestamp' | 'unix' | 'none';
|
|
379
385
|
}
|
|
380
386
|
/**
|
|
381
387
|
* Detect database dialect from connection URL
|
|
@@ -407,6 +413,21 @@ declare function getDrizzleConfig(options?: DrizzleConfigOptions): {
|
|
|
407
413
|
dbCredentials: {
|
|
408
414
|
url: string;
|
|
409
415
|
};
|
|
416
|
+
migrations: {
|
|
417
|
+
prefix: "timestamp" | "none" | "index" | "unix";
|
|
418
|
+
};
|
|
419
|
+
schemaFilter?: undefined;
|
|
420
|
+
} | {
|
|
421
|
+
schema: string | string[];
|
|
422
|
+
out: string;
|
|
423
|
+
dialect: "postgresql" | "mysql" | "sqlite";
|
|
424
|
+
dbCredentials: {
|
|
425
|
+
url: string;
|
|
426
|
+
};
|
|
427
|
+
schemaFilter: string[] | undefined;
|
|
428
|
+
migrations: {
|
|
429
|
+
prefix: "timestamp" | "none" | "index" | "unix";
|
|
430
|
+
};
|
|
410
431
|
};
|
|
411
432
|
/**
|
|
412
433
|
* Generate drizzle.config.ts file content
|
|
@@ -462,7 +483,7 @@ declare function timestamps(): {
|
|
|
462
483
|
/**
|
|
463
484
|
* Foreign key reference to another table
|
|
464
485
|
*
|
|
465
|
-
* Creates a
|
|
486
|
+
* Creates a bigint column with cascade delete.
|
|
466
487
|
* Type-safe: ensures the reference points to a valid PostgreSQL column.
|
|
467
488
|
*
|
|
468
489
|
* @param name - Column name (e.g., 'author' creates 'author_id')
|
|
@@ -482,7 +503,7 @@ declare function timestamps(): {
|
|
|
482
503
|
*/
|
|
483
504
|
declare function foreignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
|
|
484
505
|
onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
|
|
485
|
-
}): drizzle_orm.NotNull<drizzle_orm_pg_core.
|
|
506
|
+
}): drizzle_orm.NotNull<drizzle_orm_pg_core.PgBigInt53BuilderInitial<`${string}_id`>>;
|
|
486
507
|
/**
|
|
487
508
|
* Optional foreign key reference (nullable)
|
|
488
509
|
*
|
|
@@ -502,7 +523,7 @@ declare function foreignKey<T extends PgColumn>(name: string, reference: () => T
|
|
|
502
523
|
*/
|
|
503
524
|
declare function optionalForeignKey<T extends PgColumn>(name: string, reference: () => T, options?: {
|
|
504
525
|
onDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';
|
|
505
|
-
}): drizzle_orm_pg_core.
|
|
526
|
+
}): drizzle_orm_pg_core.PgBigInt53BuilderInitial<`${string}_id`>;
|
|
506
527
|
/**
|
|
507
528
|
* UUID primary key
|
|
508
529
|
*
|
|
@@ -833,6 +854,10 @@ declare function getSchemaInfo(packageName: string): {
|
|
|
833
854
|
* Uses Record<string, unknown> to accept any schema shape
|
|
834
855
|
*/
|
|
835
856
|
type TransactionDB = PostgresJsDatabase<Record<string, unknown>>;
|
|
857
|
+
/**
|
|
858
|
+
* afterCommit callback type
|
|
859
|
+
*/
|
|
860
|
+
type AfterCommitCallback = () => void | Promise<void>;
|
|
836
861
|
/**
|
|
837
862
|
* Transaction context stored in AsyncLocalStorage
|
|
838
863
|
*/
|
|
@@ -842,6 +867,8 @@ type TransactionContext = {
|
|
|
842
867
|
/** Unique transaction ID for logging and tracing */
|
|
843
868
|
txId: string;
|
|
844
869
|
level: number;
|
|
870
|
+
/** Callbacks to execute after root transaction commits */
|
|
871
|
+
afterCommitCallbacks: AfterCommitCallback[];
|
|
845
872
|
};
|
|
846
873
|
/**
|
|
847
874
|
* Get current transaction from AsyncLocalStorage
|
|
@@ -862,6 +889,31 @@ declare function getTransaction(): TransactionDB | null;
|
|
|
862
889
|
*/
|
|
863
890
|
declare function runWithTransaction<T>(tx: TransactionDB, txId: string, // Add txId parameter
|
|
864
891
|
callback: () => Promise<T>): Promise<T>;
|
|
892
|
+
/**
|
|
893
|
+
* Register a callback to run after the current transaction commits
|
|
894
|
+
*
|
|
895
|
+
* - Inside a transaction: queued and executed after root transaction commits
|
|
896
|
+
* - Outside a transaction: executed immediately (already "committed")
|
|
897
|
+
* - Nested transactions: callbacks bubble up to root transaction
|
|
898
|
+
* - Callbacks run outside transaction context (new connection for DB access)
|
|
899
|
+
* - Errors are logged but never thrown (commit already succeeded)
|
|
900
|
+
*
|
|
901
|
+
* @example
|
|
902
|
+
* ```typescript
|
|
903
|
+
* import { onAfterCommit } from '@spfn/core/db/transaction';
|
|
904
|
+
*
|
|
905
|
+
* async function submit(spaceId: string, chatId: string)
|
|
906
|
+
* {
|
|
907
|
+
* const publication = await publicationRepo.create({...});
|
|
908
|
+
* await requestRepo.updateStatusAtomically(...);
|
|
909
|
+
*
|
|
910
|
+
* onAfterCommit(() => generateArticle(spaceId, chatId, publication.id));
|
|
911
|
+
*
|
|
912
|
+
* return publication;
|
|
913
|
+
* }
|
|
914
|
+
* ```
|
|
915
|
+
*/
|
|
916
|
+
declare function onAfterCommit(callback: AfterCommitCallback): void;
|
|
865
917
|
|
|
866
918
|
/**
|
|
867
919
|
* Transaction middleware options
|
|
@@ -942,6 +994,78 @@ interface TransactionalOptions {
|
|
|
942
994
|
*/
|
|
943
995
|
declare function Transactional(options?: TransactionalOptions): hono_types.MiddlewareHandler<any, string, {}, Response>;
|
|
944
996
|
|
|
997
|
+
/**
|
|
998
|
+
* Transaction runner options
|
|
999
|
+
*/
|
|
1000
|
+
interface RunInTransactionOptions {
|
|
1001
|
+
/**
|
|
1002
|
+
* Slow transaction warning threshold in milliseconds
|
|
1003
|
+
* @default 1000 (1 second)
|
|
1004
|
+
*/
|
|
1005
|
+
slowThreshold?: number;
|
|
1006
|
+
/**
|
|
1007
|
+
* Enable transaction logging
|
|
1008
|
+
* @default true
|
|
1009
|
+
*/
|
|
1010
|
+
enableLogging?: boolean;
|
|
1011
|
+
/**
|
|
1012
|
+
* Transaction timeout in milliseconds
|
|
1013
|
+
*
|
|
1014
|
+
* Sets PostgreSQL `statement_timeout` to enforce database-level timeout.
|
|
1015
|
+
* If transaction exceeds this duration, PostgreSQL will automatically cancel
|
|
1016
|
+
* the query and rollback the transaction, ensuring data consistency.
|
|
1017
|
+
*
|
|
1018
|
+
* Behavior:
|
|
1019
|
+
* - `timeout: 0` - Disables timeout (unlimited execution time)
|
|
1020
|
+
* - `timeout: null` - Uses default (30s or TRANSACTION_TIMEOUT env var)
|
|
1021
|
+
* - `timeout: undefined` - Uses default (30s or TRANSACTION_TIMEOUT env var)
|
|
1022
|
+
* - `timeout: N` - Sets timeout to N milliseconds (1 to 2147483647)
|
|
1023
|
+
*
|
|
1024
|
+
* Note: Timeout is only applied to root transactions. Nested transactions
|
|
1025
|
+
* (SAVEPOINTs) inherit the timeout from the outer transaction.
|
|
1026
|
+
*
|
|
1027
|
+
* @default 30000 (30 seconds) or TRANSACTION_TIMEOUT environment variable
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```typescript
|
|
1031
|
+
* // Use default timeout (30s)
|
|
1032
|
+
* await runInTransaction(callback);
|
|
1033
|
+
*
|
|
1034
|
+
* // Disable timeout for long-running operations
|
|
1035
|
+
* await runInTransaction(callback, { timeout: 0 });
|
|
1036
|
+
*
|
|
1037
|
+
* // Set custom timeout (60s)
|
|
1038
|
+
* await runInTransaction(callback, { timeout: 60000 });
|
|
1039
|
+
* ```
|
|
1040
|
+
*/
|
|
1041
|
+
timeout?: number;
|
|
1042
|
+
/**
|
|
1043
|
+
* Context string for logging (e.g., 'migration:add-user', 'script:cleanup')
|
|
1044
|
+
* @default 'transaction'
|
|
1045
|
+
*/
|
|
1046
|
+
context?: string;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Run a callback function within a database transaction
|
|
1050
|
+
*
|
|
1051
|
+
* Automatically manages transaction lifecycle:
|
|
1052
|
+
* - Commits on success
|
|
1053
|
+
* - Rolls back on error
|
|
1054
|
+
* - Tracks execution time
|
|
1055
|
+
* - Warns about slow transactions
|
|
1056
|
+
* - Enforces timeout if configured
|
|
1057
|
+
*
|
|
1058
|
+
* Errors are propagated to the caller without modification.
|
|
1059
|
+
* Caller is responsible for error handling and conversion.
|
|
1060
|
+
*
|
|
1061
|
+
* @param callback - Function to execute within transaction
|
|
1062
|
+
* @param options - Transaction options
|
|
1063
|
+
* @returns Result of callback function
|
|
1064
|
+
* @throws TransactionError if database not initialized or timeout exceeded
|
|
1065
|
+
* @throws Any error thrown by callback function
|
|
1066
|
+
*/
|
|
1067
|
+
declare function runInTransaction<T>(callback: (tx: TransactionDB) => Promise<T>, options?: RunInTransactionOptions): Promise<T>;
|
|
1068
|
+
|
|
945
1069
|
/**
|
|
946
1070
|
* PostgreSQL Error Conversion Utilities
|
|
947
1071
|
*
|
|
@@ -1550,4 +1674,4 @@ declare abstract class BaseRepository<TSchema extends Record<string, unknown> =
|
|
|
1550
1674
|
protected _count<T extends PgTable>(table: T, where?: Record<string, any> | SQL | undefined): Promise<number>;
|
|
1551
1675
|
}
|
|
1552
1676
|
|
|
1553
|
-
export { BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, 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 };
|
|
1677
|
+
export { type AfterCommitCallback, BaseRepository, type DatabaseClients, type DrizzleConfigOptions, type PoolConfig, RepositoryError, type RetryConfig, type RunInTransactionOptions, type TransactionContext, type TransactionDB, Transactional, type TransactionalOptions, 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, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runInTransaction, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
|
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) {
|
|
@@ -503,6 +521,8 @@ var setMonitoringConfig = (config) => {
|
|
|
503
521
|
globalThis.__SPFN_DB_MONITORING__ = config;
|
|
504
522
|
};
|
|
505
523
|
var dbLogger3 = logger.child("@spfn/core:database");
|
|
524
|
+
var CLIENT_CLOSE_TIMEOUT = 5;
|
|
525
|
+
var isReconnecting = false;
|
|
506
526
|
async function testDatabaseConnection(db) {
|
|
507
527
|
await db.execute("SELECT 1");
|
|
508
528
|
}
|
|
@@ -514,8 +534,13 @@ async function performHealthCheck(getDatabase2) {
|
|
|
514
534
|
await testDatabaseConnection(read);
|
|
515
535
|
}
|
|
516
536
|
}
|
|
517
|
-
async function
|
|
518
|
-
|
|
537
|
+
async function closeClient(client) {
|
|
538
|
+
try {
|
|
539
|
+
await client.end({ timeout: CLIENT_CLOSE_TIMEOUT });
|
|
540
|
+
} catch {
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async function reconnectAndRestore(options) {
|
|
519
544
|
const result = await createDatabaseFromEnv(options);
|
|
520
545
|
if (!result.write) {
|
|
521
546
|
return false;
|
|
@@ -524,15 +549,23 @@ async function reconnectAndRestore(options, closeDatabase2) {
|
|
|
524
549
|
if (result.read && result.read !== result.write) {
|
|
525
550
|
await testDatabaseConnection(result.read);
|
|
526
551
|
}
|
|
552
|
+
const oldWriteClient = getWriteClient();
|
|
553
|
+
const oldReadClient = getReadClient();
|
|
527
554
|
setWriteInstance(result.write);
|
|
528
555
|
setReadInstance(result.read);
|
|
529
556
|
setWriteClient(result.writeClient);
|
|
530
557
|
setReadClient(result.readClient);
|
|
531
558
|
const monConfig = buildMonitoringConfig(options?.monitoring);
|
|
532
559
|
setMonitoringConfig(monConfig);
|
|
560
|
+
if (oldWriteClient) {
|
|
561
|
+
closeClient(oldWriteClient);
|
|
562
|
+
}
|
|
563
|
+
if (oldReadClient && oldReadClient !== oldWriteClient) {
|
|
564
|
+
closeClient(oldReadClient);
|
|
565
|
+
}
|
|
533
566
|
return true;
|
|
534
567
|
}
|
|
535
|
-
function startHealthCheck(config, options, getDatabase2
|
|
568
|
+
function startHealthCheck(config, options, getDatabase2) {
|
|
536
569
|
const healthCheck = getHealthCheckInterval();
|
|
537
570
|
if (healthCheck) {
|
|
538
571
|
dbLogger3.debug("Health check already running");
|
|
@@ -543,47 +576,56 @@ function startHealthCheck(config, options, getDatabase2, closeDatabase2) {
|
|
|
543
576
|
reconnect: config.reconnect
|
|
544
577
|
});
|
|
545
578
|
const interval = setInterval(async () => {
|
|
579
|
+
if (isReconnecting) {
|
|
580
|
+
dbLogger3.debug("Health check skipped: reconnection in progress");
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
546
583
|
try {
|
|
547
584
|
await performHealthCheck(getDatabase2);
|
|
548
585
|
} catch (error) {
|
|
549
586
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
550
587
|
dbLogger3.error("Database health check failed", { error: message });
|
|
551
588
|
if (config.reconnect) {
|
|
552
|
-
await attemptReconnection(config, options
|
|
589
|
+
await attemptReconnection(config, options);
|
|
553
590
|
}
|
|
554
591
|
}
|
|
555
592
|
}, config.interval);
|
|
556
593
|
setHealthCheckInterval(interval);
|
|
557
594
|
}
|
|
558
|
-
async function attemptReconnection(config, options
|
|
595
|
+
async function attemptReconnection(config, options) {
|
|
596
|
+
isReconnecting = true;
|
|
559
597
|
dbLogger3.warn("Attempting database reconnection", {
|
|
560
598
|
maxRetries: config.maxRetries,
|
|
561
599
|
retryInterval: `${config.retryInterval}ms`
|
|
562
600
|
});
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
601
|
+
try {
|
|
602
|
+
for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
|
|
603
|
+
try {
|
|
604
|
+
dbLogger3.debug(`Reconnection attempt ${attempt}/${config.maxRetries}`);
|
|
605
|
+
if (attempt > 1) {
|
|
606
|
+
await new Promise((resolve) => setTimeout(resolve, config.retryInterval));
|
|
607
|
+
}
|
|
608
|
+
const success = await reconnectAndRestore(options);
|
|
609
|
+
if (success) {
|
|
610
|
+
dbLogger3.info("Database reconnection successful", { attempt });
|
|
611
|
+
return;
|
|
612
|
+
} else {
|
|
613
|
+
dbLogger3.error(`Reconnection attempt ${attempt} failed: No write database instance created`);
|
|
614
|
+
}
|
|
615
|
+
} catch (error) {
|
|
616
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
617
|
+
dbLogger3.error(`Reconnection attempt ${attempt} failed`, {
|
|
618
|
+
error: message,
|
|
619
|
+
attempt,
|
|
620
|
+
maxRetries: config.maxRetries
|
|
621
|
+
});
|
|
568
622
|
}
|
|
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`);
|
|
623
|
+
if (attempt === config.maxRetries) {
|
|
624
|
+
dbLogger3.error("Max reconnection attempts reached, will retry on next health check");
|
|
575
625
|
}
|
|
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
626
|
}
|
|
627
|
+
} finally {
|
|
628
|
+
isReconnecting = false;
|
|
587
629
|
}
|
|
588
630
|
}
|
|
589
631
|
function stopHealthCheck() {
|
|
@@ -593,6 +635,7 @@ function stopHealthCheck() {
|
|
|
593
635
|
setHealthCheckInterval(void 0);
|
|
594
636
|
dbLogger3.info("Database health check stopped");
|
|
595
637
|
}
|
|
638
|
+
isReconnecting = false;
|
|
596
639
|
}
|
|
597
640
|
|
|
598
641
|
// src/db/manager/manager.ts
|
|
@@ -742,7 +785,7 @@ async function initDatabase(options) {
|
|
|
742
785
|
);
|
|
743
786
|
const healthCheckConfig = buildHealthCheckConfig(options?.healthCheck);
|
|
744
787
|
if (healthCheckConfig.enabled) {
|
|
745
|
-
startHealthCheck(healthCheckConfig, options, getDatabase
|
|
788
|
+
startHealthCheck(healthCheckConfig, options, getDatabase);
|
|
746
789
|
}
|
|
747
790
|
const monConfig = buildMonitoringConfig(options?.monitoring);
|
|
748
791
|
setMonitoringConfig(monConfig);
|
|
@@ -811,7 +854,7 @@ function getDatabaseInfo() {
|
|
|
811
854
|
isReplica: !!(readInst && readInst !== writeInst)
|
|
812
855
|
};
|
|
813
856
|
}
|
|
814
|
-
var
|
|
857
|
+
var BARREL_FILE_PATTERNS = [
|
|
815
858
|
"/index",
|
|
816
859
|
"/index.ts",
|
|
817
860
|
"/index.js",
|
|
@@ -819,11 +862,14 @@ var INDEX_FILE_PATTERNS = [
|
|
|
819
862
|
"\\index",
|
|
820
863
|
"\\index.ts",
|
|
821
864
|
"\\index.js",
|
|
822
|
-
"\\index.mjs"
|
|
865
|
+
"\\index.mjs",
|
|
866
|
+
"\\config.ts",
|
|
867
|
+
"\\config.js",
|
|
868
|
+
"\\config.mjs"
|
|
823
869
|
];
|
|
824
870
|
var SUPPORTED_EXTENSIONS = [".ts", ".js", ".mjs"];
|
|
825
|
-
function
|
|
826
|
-
return
|
|
871
|
+
function isBarrelFile(filePath) {
|
|
872
|
+
return BARREL_FILE_PATTERNS.some((pattern) => filePath.endsWith(pattern));
|
|
827
873
|
}
|
|
828
874
|
function isAbsolutePath(path) {
|
|
829
875
|
if (path.startsWith("/")) return true;
|
|
@@ -833,8 +879,8 @@ function hasSupportedExtension(filePath) {
|
|
|
833
879
|
if (filePath.endsWith(".d.ts")) return false;
|
|
834
880
|
return SUPPORTED_EXTENSIONS.some((ext) => filePath.endsWith(ext));
|
|
835
881
|
}
|
|
836
|
-
function
|
|
837
|
-
return files.filter((file) => !
|
|
882
|
+
function filterBarrelFiles(files) {
|
|
883
|
+
return files.filter((file) => !isBarrelFile(file));
|
|
838
884
|
}
|
|
839
885
|
function scanDirectoryRecursive(dir, extension) {
|
|
840
886
|
const files = [];
|
|
@@ -880,6 +926,27 @@ function scanDirectorySingleLevel(dir, filePattern) {
|
|
|
880
926
|
}
|
|
881
927
|
return files;
|
|
882
928
|
}
|
|
929
|
+
function detectSchemasFromFiles(files) {
|
|
930
|
+
const schemas = /* @__PURE__ */ new Set(["public"]);
|
|
931
|
+
const pgSchemaPattern = /pgSchema\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
932
|
+
const createSchemaPattern = /createSchema\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
933
|
+
for (const filePath of files) {
|
|
934
|
+
try {
|
|
935
|
+
const content = readFileSync(filePath, "utf-8");
|
|
936
|
+
let match;
|
|
937
|
+
while ((match = pgSchemaPattern.exec(content)) !== null) {
|
|
938
|
+
schemas.add(match[1]);
|
|
939
|
+
}
|
|
940
|
+
while ((match = createSchemaPattern.exec(content)) !== null) {
|
|
941
|
+
const packageName = match[1];
|
|
942
|
+
const schemaName = packageName.replace(/@/g, "").replace(/\//g, "_").replace(/-/g, "_");
|
|
943
|
+
schemas.add(schemaName);
|
|
944
|
+
}
|
|
945
|
+
} catch {
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return Array.from(schemas);
|
|
949
|
+
}
|
|
883
950
|
function expandGlobPattern(pattern) {
|
|
884
951
|
if (!pattern.includes("*")) {
|
|
885
952
|
return existsSync(pattern) ? [pattern] : [];
|
|
@@ -922,7 +989,7 @@ function discoverPackageSchemas(cwd) {
|
|
|
922
989
|
for (const schema of packageSchemas) {
|
|
923
990
|
const absolutePath = join(pkgPath, schema);
|
|
924
991
|
const expandedFiles = expandGlobPattern(absolutePath);
|
|
925
|
-
const schemaFiles =
|
|
992
|
+
const schemaFiles = filterBarrelFiles(expandedFiles);
|
|
926
993
|
schemas.push(...schemaFiles);
|
|
927
994
|
}
|
|
928
995
|
}
|
|
@@ -984,28 +1051,45 @@ function getDrizzleConfig(options = {}) {
|
|
|
984
1051
|
schema: schema2,
|
|
985
1052
|
out,
|
|
986
1053
|
dialect,
|
|
987
|
-
dbCredentials: getDbCredentials(dialect, databaseUrl)
|
|
1054
|
+
dbCredentials: getDbCredentials(dialect, databaseUrl),
|
|
1055
|
+
migrations: {
|
|
1056
|
+
prefix: options.migrationPrefix ?? "timestamp"
|
|
1057
|
+
}
|
|
988
1058
|
};
|
|
989
1059
|
}
|
|
990
1060
|
const userSchema = options.schema ?? "./src/server/entities/**/*.ts";
|
|
991
1061
|
const userSchemas = Array.isArray(userSchema) ? userSchema : [userSchema];
|
|
992
1062
|
const packageSchemas = options.disablePackageDiscovery ? [] : discoverPackageSchemas(options.cwd ?? process.cwd());
|
|
993
1063
|
let allSchemas = [...userSchemas, ...packageSchemas];
|
|
1064
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1065
|
+
let expandedFiles = [];
|
|
994
1066
|
if (options.expandGlobs) {
|
|
995
|
-
const expandedSchemas = [];
|
|
996
1067
|
for (const schema2 of allSchemas) {
|
|
997
|
-
const
|
|
998
|
-
const
|
|
999
|
-
|
|
1068
|
+
const absoluteSchema = isAbsolutePath(schema2) ? schema2 : join(cwd, schema2);
|
|
1069
|
+
const expanded = expandGlobPattern(absoluteSchema);
|
|
1070
|
+
const filtered = filterBarrelFiles(expanded);
|
|
1071
|
+
expandedFiles.push(...filtered);
|
|
1000
1072
|
}
|
|
1001
|
-
allSchemas =
|
|
1073
|
+
allSchemas = expandedFiles;
|
|
1002
1074
|
}
|
|
1003
1075
|
const schema = allSchemas.length === 1 ? allSchemas[0] : allSchemas;
|
|
1076
|
+
let schemaFilter;
|
|
1077
|
+
if (dialect === "postgresql") {
|
|
1078
|
+
if (options.schemaFilter) {
|
|
1079
|
+
schemaFilter = options.schemaFilter;
|
|
1080
|
+
} else if (options.autoDetectSchemas && expandedFiles.length > 0) {
|
|
1081
|
+
schemaFilter = detectSchemasFromFiles(expandedFiles);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1004
1084
|
return {
|
|
1005
1085
|
schema,
|
|
1006
1086
|
out,
|
|
1007
1087
|
dialect,
|
|
1008
|
-
dbCredentials: getDbCredentials(dialect, databaseUrl)
|
|
1088
|
+
dbCredentials: getDbCredentials(dialect, databaseUrl),
|
|
1089
|
+
schemaFilter,
|
|
1090
|
+
migrations: {
|
|
1091
|
+
prefix: options.migrationPrefix ?? "timestamp"
|
|
1092
|
+
}
|
|
1009
1093
|
};
|
|
1010
1094
|
}
|
|
1011
1095
|
function getDbCredentials(dialect, url) {
|
|
@@ -1032,13 +1116,17 @@ function generateDrizzleConfigFile(options = {}) {
|
|
|
1032
1116
|
const schemaValue = Array.isArray(config.schema) ? `[
|
|
1033
1117
|
${config.schema.map((s) => `'${normalizeSchemaPath(s)}'`).join(",\n ")}
|
|
1034
1118
|
]` : `'${normalizeSchemaPath(config.schema)}'`;
|
|
1119
|
+
const schemaFilterLine = config.schemaFilter && config.schemaFilter.length > 0 ? `
|
|
1120
|
+
schemaFilter: ${JSON.stringify(config.schemaFilter)},` : "";
|
|
1121
|
+
const migrationsLine = config.migrations ? `
|
|
1122
|
+
migrations: ${JSON.stringify(config.migrations)},` : "";
|
|
1035
1123
|
return `import { defineConfig } from 'drizzle-kit';
|
|
1036
1124
|
|
|
1037
1125
|
export default defineConfig({
|
|
1038
1126
|
schema: ${schemaValue},
|
|
1039
1127
|
out: '${config.out}',
|
|
1040
1128
|
dialect: '${config.dialect}',
|
|
1041
|
-
dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)}
|
|
1129
|
+
dbCredentials: ${JSON.stringify(config.dbCredentials, null, 4)},${schemaFilterLine}${migrationsLine}
|
|
1042
1130
|
});
|
|
1043
1131
|
`;
|
|
1044
1132
|
}
|
|
@@ -1052,10 +1140,10 @@ function timestamps() {
|
|
|
1052
1140
|
};
|
|
1053
1141
|
}
|
|
1054
1142
|
function foreignKey(name, reference, options) {
|
|
1055
|
-
return
|
|
1143
|
+
return bigint(`${name}_id`, { mode: "number" }).notNull().references(reference, { onDelete: options?.onDelete ?? "cascade" });
|
|
1056
1144
|
}
|
|
1057
1145
|
function optionalForeignKey(name, reference, options) {
|
|
1058
|
-
return
|
|
1146
|
+
return bigint(`${name}_id`, { mode: "number" }).references(reference, { onDelete: options?.onDelete ?? "set null" });
|
|
1059
1147
|
}
|
|
1060
1148
|
function uuid() {
|
|
1061
1149
|
return uuid$1("id").defaultRandom().primaryKey();
|
|
@@ -1134,7 +1222,20 @@ function runWithTransaction(tx, txId, callback) {
|
|
|
1134
1222
|
} else {
|
|
1135
1223
|
txLogger.debug("Root transaction context set", { txId, level: newLevel });
|
|
1136
1224
|
}
|
|
1137
|
-
|
|
1225
|
+
const afterCommitCallbacks = existingContext ? existingContext.afterCommitCallbacks : [];
|
|
1226
|
+
return asyncContext.run({ tx, txId, level: newLevel, afterCommitCallbacks }, callback);
|
|
1227
|
+
}
|
|
1228
|
+
function onAfterCommit(callback) {
|
|
1229
|
+
const context = getTransactionContext();
|
|
1230
|
+
if (!context) {
|
|
1231
|
+
Promise.resolve().then(callback).catch((err) => {
|
|
1232
|
+
txLogger.error("afterCommit callback failed (no transaction)", {
|
|
1233
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
context.afterCommitCallbacks.push(callback);
|
|
1138
1239
|
}
|
|
1139
1240
|
var MAX_TIMEOUT_MS = 2147483647;
|
|
1140
1241
|
var txLogger2 = logger.child("@spfn/core:transaction");
|
|
@@ -1217,13 +1318,21 @@ async function runInTransaction(callback, options = {}) {
|
|
|
1217
1318
|
txLogger2.debug("Transaction started", { txId, context });
|
|
1218
1319
|
}
|
|
1219
1320
|
const startTime = Date.now();
|
|
1321
|
+
let afterCommitCallbacks = [];
|
|
1220
1322
|
try {
|
|
1221
1323
|
const result = await writeDb.transaction(async (tx) => {
|
|
1222
1324
|
if (timeout > 0 && !isNested) {
|
|
1223
1325
|
await tx.execute(sql.raw(`SET LOCAL statement_timeout = ${timeout}`));
|
|
1224
1326
|
}
|
|
1225
1327
|
return await runWithTransaction(tx, txId, async () => {
|
|
1226
|
-
|
|
1328
|
+
const innerResult = await callback(tx);
|
|
1329
|
+
if (!isNested) {
|
|
1330
|
+
const ctx = getTransactionContext();
|
|
1331
|
+
if (ctx) {
|
|
1332
|
+
afterCommitCallbacks = [...ctx.afterCommitCallbacks];
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return innerResult;
|
|
1227
1336
|
});
|
|
1228
1337
|
});
|
|
1229
1338
|
const duration = Date.now() - startTime;
|
|
@@ -1243,6 +1352,24 @@ async function runInTransaction(callback, options = {}) {
|
|
|
1243
1352
|
});
|
|
1244
1353
|
}
|
|
1245
1354
|
}
|
|
1355
|
+
if (!isNested && afterCommitCallbacks.length > 0) {
|
|
1356
|
+
if (enableLogging) {
|
|
1357
|
+
txLogger2.debug("Executing afterCommit callbacks", {
|
|
1358
|
+
txId,
|
|
1359
|
+
context,
|
|
1360
|
+
count: afterCommitCallbacks.length
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
for (const cb of afterCommitCallbacks) {
|
|
1364
|
+
Promise.resolve().then(cb).catch((err) => {
|
|
1365
|
+
txLogger2.error("afterCommit callback failed", {
|
|
1366
|
+
txId,
|
|
1367
|
+
context,
|
|
1368
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1369
|
+
});
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1246
1373
|
return result;
|
|
1247
1374
|
} catch (error) {
|
|
1248
1375
|
const duration = Date.now() - startTime;
|
|
@@ -1788,6 +1915,6 @@ var BaseRepository = class {
|
|
|
1788
1915
|
}
|
|
1789
1916
|
};
|
|
1790
1917
|
|
|
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 };
|
|
1918
|
+
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, onAfterCommit, optionalForeignKey, packageNameToSchema, publishingFields, runInTransaction, runWithTransaction, setDatabase, softDelete, timestamps, typedJsonb, updateMany, updateOne, upsert, utcTimestamp, uuid, verificationTimestamp };
|
|
1792
1919
|
//# sourceMappingURL=index.js.map
|
|
1793
1920
|
//# sourceMappingURL=index.js.map
|