@bunnykit/orm 0.1.17 → 0.1.18
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.
|
@@ -8,6 +8,7 @@ export declare class Connection {
|
|
|
8
8
|
private config;
|
|
9
9
|
private schema?;
|
|
10
10
|
private ownsDriver;
|
|
11
|
+
private transactionDepth;
|
|
11
12
|
constructor(config: ConnectionConfig, options?: {
|
|
12
13
|
driver?: SQL;
|
|
13
14
|
schema?: string;
|
|
@@ -25,6 +26,7 @@ export declare class Connection {
|
|
|
25
26
|
beginTransaction(): Promise<void>;
|
|
26
27
|
commit(): Promise<void>;
|
|
27
28
|
rollback(): Promise<void>;
|
|
29
|
+
isInTransaction(): boolean;
|
|
28
30
|
transaction<T>(callback: (connection: Connection) => T | Promise<T>): Promise<T>;
|
|
29
31
|
withTenant<T>(tenantId: string, callback: (connection: Connection) => T | Promise<T>, setting?: string): Promise<T>;
|
|
30
32
|
withSearchPath<T>(schema: string, callback: (connection: Connection) => T | Promise<T>): Promise<T>;
|
|
@@ -9,6 +9,7 @@ export class Connection {
|
|
|
9
9
|
config;
|
|
10
10
|
schema;
|
|
11
11
|
ownsDriver;
|
|
12
|
+
transactionDepth = 0;
|
|
12
13
|
constructor(config, options = {}) {
|
|
13
14
|
this.config = config;
|
|
14
15
|
this.schema = options.schema || ("schema" in config ? config.schema : undefined);
|
|
@@ -83,12 +84,18 @@ export class Connection {
|
|
|
83
84
|
}
|
|
84
85
|
async beginTransaction() {
|
|
85
86
|
await this.driver.unsafe("BEGIN");
|
|
87
|
+
this.transactionDepth++;
|
|
86
88
|
}
|
|
87
89
|
async commit() {
|
|
88
90
|
await this.driver.unsafe("COMMIT");
|
|
91
|
+
this.transactionDepth = Math.max(0, this.transactionDepth - 1);
|
|
89
92
|
}
|
|
90
93
|
async rollback() {
|
|
91
94
|
await this.driver.unsafe("ROLLBACK");
|
|
95
|
+
this.transactionDepth = Math.max(0, this.transactionDepth - 1);
|
|
96
|
+
}
|
|
97
|
+
isInTransaction() {
|
|
98
|
+
return this.transactionDepth > 0;
|
|
92
99
|
}
|
|
93
100
|
async transaction(callback) {
|
|
94
101
|
return await this.driver.begin(async (sql) => {
|
|
@@ -33,16 +33,18 @@ export declare class ConnectionManager {
|
|
|
33
33
|
private static poolConfigs;
|
|
34
34
|
private static tenantResolver?;
|
|
35
35
|
private static tenantCache;
|
|
36
|
+
private static waiters;
|
|
36
37
|
static setDefault(connection: Connection): void;
|
|
37
38
|
static getDefault(): Connection | undefined;
|
|
38
39
|
static setPoolConfig(name: string, config: PoolConfig): void;
|
|
39
40
|
static getPoolConfig(name: string): PoolConfig | undefined;
|
|
40
41
|
private static getPooledConnection;
|
|
42
|
+
private static removeWaiter;
|
|
41
43
|
private static releasePooledConnection;
|
|
42
44
|
static add(name: string, connection: Connection | ConnectionConfig): Connection;
|
|
43
45
|
static get(name: string): Connection | undefined;
|
|
44
46
|
static getPooled(name: string, config?: ConnectionConfig): Promise<Connection>;
|
|
45
|
-
static release(name: string, connection: Connection): void
|
|
47
|
+
static release(name: string, connection: Connection): Promise<void>;
|
|
46
48
|
static require(name: string): Connection;
|
|
47
49
|
static setTenantResolver(resolver: TenantResolver): void;
|
|
48
50
|
static resolveTenant(tenantId: string): Promise<ActiveTenantContext>;
|
|
@@ -6,6 +6,7 @@ export class ConnectionManager {
|
|
|
6
6
|
static poolConfigs = new Map();
|
|
7
7
|
static tenantResolver;
|
|
8
8
|
static tenantCache = new Map();
|
|
9
|
+
static waiters = new Map();
|
|
9
10
|
static setDefault(connection) {
|
|
10
11
|
this.defaultConnection = connection;
|
|
11
12
|
}
|
|
@@ -34,7 +35,7 @@ export class ConnectionManager {
|
|
|
34
35
|
const pooled = pool[idx];
|
|
35
36
|
pool.splice(idx, 1);
|
|
36
37
|
try {
|
|
37
|
-
pooled.connection.query("SELECT 1")
|
|
38
|
+
await pooled.connection.query("SELECT 1");
|
|
38
39
|
pooled.inUse = true;
|
|
39
40
|
return pooled.connection;
|
|
40
41
|
}
|
|
@@ -48,30 +49,54 @@ export class ConnectionManager {
|
|
|
48
49
|
return connection;
|
|
49
50
|
}
|
|
50
51
|
return new Promise((resolve, reject) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
if (available) {
|
|
54
|
-
clearInterval(checkInterval);
|
|
55
|
-
available.inUse = true;
|
|
56
|
-
available.lastUsed = Date.now();
|
|
57
|
-
resolve(available.connection);
|
|
58
|
-
}
|
|
59
|
-
}, 50);
|
|
60
|
-
setTimeout(() => {
|
|
61
|
-
clearInterval(checkInterval);
|
|
52
|
+
const timer = setTimeout(() => {
|
|
53
|
+
this.removeWaiter(name, waiter);
|
|
62
54
|
reject(new Error(`Connection pool exhausted for "${name}"`));
|
|
63
55
|
}, 30000);
|
|
56
|
+
const waiter = { resolve, reject, timer };
|
|
57
|
+
let poolWaiters = this.waiters.get(name);
|
|
58
|
+
if (!poolWaiters) {
|
|
59
|
+
poolWaiters = [];
|
|
60
|
+
this.waiters.set(name, poolWaiters);
|
|
61
|
+
}
|
|
62
|
+
poolWaiters.push(waiter);
|
|
64
63
|
});
|
|
65
64
|
}
|
|
66
|
-
static
|
|
65
|
+
static removeWaiter(name, waiter) {
|
|
66
|
+
const poolWaiters = this.waiters.get(name);
|
|
67
|
+
if (!poolWaiters)
|
|
68
|
+
return;
|
|
69
|
+
const idx = poolWaiters.indexOf(waiter);
|
|
70
|
+
if (idx !== -1)
|
|
71
|
+
poolWaiters.splice(idx, 1);
|
|
72
|
+
}
|
|
73
|
+
static async releasePooledConnection(name, connection) {
|
|
67
74
|
const pool = this.pools.get(name);
|
|
68
75
|
if (!pool)
|
|
69
76
|
return;
|
|
70
77
|
const pooled = pool.find((p) => p.connection === connection);
|
|
71
|
-
if (pooled)
|
|
72
|
-
|
|
78
|
+
if (!pooled)
|
|
79
|
+
return;
|
|
80
|
+
// Safety: roll back any leaked transaction before returning to pool
|
|
81
|
+
if (pooled.connection.isInTransaction()) {
|
|
82
|
+
try {
|
|
83
|
+
await pooled.connection.rollback();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Connection may already be dead; ignore rollback errors
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Hand off directly to a waiter if one exists
|
|
90
|
+
const poolWaiters = this.waiters.get(name);
|
|
91
|
+
if (poolWaiters && poolWaiters.length > 0) {
|
|
92
|
+
const waiter = poolWaiters.shift();
|
|
93
|
+
clearTimeout(waiter.timer);
|
|
73
94
|
pooled.lastUsed = Date.now();
|
|
95
|
+
waiter.resolve(connection);
|
|
96
|
+
return;
|
|
74
97
|
}
|
|
98
|
+
pooled.inUse = false;
|
|
99
|
+
pooled.lastUsed = Date.now();
|
|
75
100
|
}
|
|
76
101
|
static add(name, connection) {
|
|
77
102
|
const resolved = connection instanceof Connection ? connection : new Connection(connection);
|
|
@@ -90,8 +115,8 @@ export class ConnectionManager {
|
|
|
90
115
|
return existing;
|
|
91
116
|
throw new Error(`No connection registered for "${name}". Use add() first or provide config.`);
|
|
92
117
|
}
|
|
93
|
-
static release(name, connection) {
|
|
94
|
-
this.releasePooledConnection(name, connection);
|
|
118
|
+
static async release(name, connection) {
|
|
119
|
+
await this.releasePooledConnection(name, connection);
|
|
95
120
|
}
|
|
96
121
|
static require(name) {
|
|
97
122
|
const connection = this.get(name);
|
package/package.json
CHANGED