@bunnykit/orm 0.1.14 → 0.1.15
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.
|
@@ -21,15 +21,28 @@ export type TenantResolution = {
|
|
|
21
21
|
setting?: string;
|
|
22
22
|
};
|
|
23
23
|
export type TenantResolver = (tenantId: string) => TenantResolution | Promise<TenantResolution>;
|
|
24
|
+
export interface PoolConfig {
|
|
25
|
+
maxConnections?: number;
|
|
26
|
+
minConnections?: number;
|
|
27
|
+
idleTimeout?: number;
|
|
28
|
+
}
|
|
24
29
|
export declare class ConnectionManager {
|
|
25
30
|
private static defaultConnection?;
|
|
26
31
|
private static connections;
|
|
32
|
+
private static pools;
|
|
33
|
+
private static poolConfigs;
|
|
27
34
|
private static tenantResolver?;
|
|
28
35
|
private static tenantCache;
|
|
29
36
|
static setDefault(connection: Connection): void;
|
|
30
37
|
static getDefault(): Connection | undefined;
|
|
38
|
+
static setPoolConfig(name: string, config: PoolConfig): void;
|
|
39
|
+
static getPoolConfig(name: string): PoolConfig | undefined;
|
|
40
|
+
private static getPooledConnection;
|
|
41
|
+
private static releasePooledConnection;
|
|
31
42
|
static add(name: string, connection: Connection | ConnectionConfig): Connection;
|
|
32
43
|
static get(name: string): Connection | undefined;
|
|
44
|
+
static getPooled(name: string, config?: ConnectionConfig): Promise<Connection>;
|
|
45
|
+
static release(name: string, connection: Connection): void;
|
|
33
46
|
static require(name: string): Connection;
|
|
34
47
|
static setTenantResolver(resolver: TenantResolver): void;
|
|
35
48
|
static resolveTenant(tenantId: string): Promise<ActiveTenantContext>;
|
|
@@ -2,6 +2,8 @@ import { Connection } from "./Connection.js";
|
|
|
2
2
|
export class ConnectionManager {
|
|
3
3
|
static defaultConnection;
|
|
4
4
|
static connections = new Map();
|
|
5
|
+
static pools = new Map();
|
|
6
|
+
static poolConfigs = new Map();
|
|
5
7
|
static tenantResolver;
|
|
6
8
|
static tenantCache = new Map();
|
|
7
9
|
static setDefault(connection) {
|
|
@@ -10,6 +12,67 @@ export class ConnectionManager {
|
|
|
10
12
|
static getDefault() {
|
|
11
13
|
return this.defaultConnection;
|
|
12
14
|
}
|
|
15
|
+
static setPoolConfig(name, config) {
|
|
16
|
+
this.poolConfigs.set(name, { maxConnections: 10, minConnections: 1, idleTimeout: 30000, ...config });
|
|
17
|
+
}
|
|
18
|
+
static getPoolConfig(name) {
|
|
19
|
+
return this.poolConfigs.get(name);
|
|
20
|
+
}
|
|
21
|
+
static async getPooledConnection(name, config) {
|
|
22
|
+
const poolConfig = this.poolConfigs.get(name) || { maxConnections: 10, minConnections: 1, idleTimeout: 30000 };
|
|
23
|
+
let pool = this.pools.get(name);
|
|
24
|
+
if (!pool) {
|
|
25
|
+
pool = [];
|
|
26
|
+
this.pools.set(name, pool);
|
|
27
|
+
}
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const idleTimeout = poolConfig.idleTimeout || 30000;
|
|
30
|
+
while (pool.length > 0) {
|
|
31
|
+
const idx = pool.findIndex((c) => !c.inUse && (now - c.lastUsed) < idleTimeout);
|
|
32
|
+
if (idx === -1)
|
|
33
|
+
break;
|
|
34
|
+
const pooled = pool[idx];
|
|
35
|
+
pool.splice(idx, 1);
|
|
36
|
+
try {
|
|
37
|
+
pooled.connection.query("SELECT 1").catch(() => null);
|
|
38
|
+
pooled.inUse = true;
|
|
39
|
+
return pooled.connection;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
await pooled.connection.close().catch(() => null);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (pool.length < (poolConfig.maxConnections || 10)) {
|
|
46
|
+
const connection = new Connection(config);
|
|
47
|
+
pool.push({ connection, lastUsed: Date.now(), inUse: true });
|
|
48
|
+
return connection;
|
|
49
|
+
}
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const checkInterval = setInterval(() => {
|
|
52
|
+
const available = pool.find((c) => !c.inUse);
|
|
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);
|
|
62
|
+
reject(new Error(`Connection pool exhausted for "${name}"`));
|
|
63
|
+
}, 30000);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
static releasePooledConnection(name, connection) {
|
|
67
|
+
const pool = this.pools.get(name);
|
|
68
|
+
if (!pool)
|
|
69
|
+
return;
|
|
70
|
+
const pooled = pool.find((p) => p.connection === connection);
|
|
71
|
+
if (pooled) {
|
|
72
|
+
pooled.inUse = false;
|
|
73
|
+
pooled.lastUsed = Date.now();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
13
76
|
static add(name, connection) {
|
|
14
77
|
const resolved = connection instanceof Connection ? connection : new Connection(connection);
|
|
15
78
|
this.connections.set(name, resolved);
|
|
@@ -18,6 +81,18 @@ export class ConnectionManager {
|
|
|
18
81
|
static get(name) {
|
|
19
82
|
return this.connections.get(name);
|
|
20
83
|
}
|
|
84
|
+
static async getPooled(name, config) {
|
|
85
|
+
if (config) {
|
|
86
|
+
return this.getPooledConnection(name, config);
|
|
87
|
+
}
|
|
88
|
+
const existing = this.connections.get(name);
|
|
89
|
+
if (existing)
|
|
90
|
+
return existing;
|
|
91
|
+
throw new Error(`No connection registered for "${name}". Use add() first or provide config.`);
|
|
92
|
+
}
|
|
93
|
+
static release(name, connection) {
|
|
94
|
+
this.releasePooledConnection(name, connection);
|
|
95
|
+
}
|
|
21
96
|
static require(name) {
|
|
22
97
|
const connection = this.get(name);
|
|
23
98
|
if (!connection) {
|
|
@@ -93,9 +168,15 @@ export class ConnectionManager {
|
|
|
93
168
|
}
|
|
94
169
|
static async closeAll() {
|
|
95
170
|
const connections = new Set(this.connections.values());
|
|
171
|
+
for (const pool of this.pools.values()) {
|
|
172
|
+
for (const { connection } of pool) {
|
|
173
|
+
connections.add(connection);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
96
176
|
if (this.defaultConnection)
|
|
97
177
|
connections.add(this.defaultConnection);
|
|
98
178
|
this.connections.clear();
|
|
179
|
+
this.pools.clear();
|
|
99
180
|
this.tenantCache.clear();
|
|
100
181
|
for (const connection of connections) {
|
|
101
182
|
await connection.close();
|
|
@@ -30,6 +30,7 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
30
30
|
unions: UnionClause[];
|
|
31
31
|
fromRaw?: string;
|
|
32
32
|
updateJoins: string[];
|
|
33
|
+
bindings: any[];
|
|
33
34
|
constructor(connection: Connection, table: string);
|
|
34
35
|
private get grammar();
|
|
35
36
|
setModel(model: ModelConstructor): this;
|
|
@@ -157,7 +158,7 @@ export declare class Builder<T = Record<string, any>> {
|
|
|
157
158
|
paginate(perPage?: number, page?: number): Promise<Paginator<T>>;
|
|
158
159
|
chunk(count: number, callback: (items: T[]) => void | Promise<void>): Promise<void>;
|
|
159
160
|
each(count: number, callback: (item: T) => void | Promise<void>): Promise<void>;
|
|
160
|
-
cursor(): AsyncGenerator<T>;
|
|
161
|
+
cursor(keyset?: Record<string, any>): AsyncGenerator<T>;
|
|
161
162
|
lazy(count?: number): AsyncGenerator<T>;
|
|
162
163
|
insert(data: ModelAttributeInput<T> | ModelAttributeInput<T>[]): Promise<any>;
|
|
163
164
|
insertGetId(data: ModelAttributeInput<T>, idColumn?: ModelColumn<T>): Promise<any>;
|
|
@@ -18,6 +18,7 @@ export class Builder {
|
|
|
18
18
|
unions = [];
|
|
19
19
|
fromRaw;
|
|
20
20
|
updateJoins = [];
|
|
21
|
+
bindings = [];
|
|
21
22
|
constructor(connection, table) {
|
|
22
23
|
this.connection = connection;
|
|
23
24
|
this.tableName = table;
|
|
@@ -455,6 +456,7 @@ export class Builder {
|
|
|
455
456
|
cloned.unions = [...this.unions];
|
|
456
457
|
cloned.fromRaw = this.fromRaw;
|
|
457
458
|
cloned.updateJoins = [...this.updateJoins];
|
|
459
|
+
cloned.bindings = [...this.bindings];
|
|
458
460
|
return cloned;
|
|
459
461
|
}
|
|
460
462
|
wrapColumn(value) {
|
|
@@ -697,14 +699,34 @@ export class Builder {
|
|
|
697
699
|
}
|
|
698
700
|
});
|
|
699
701
|
}
|
|
700
|
-
async *cursor() {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
702
|
+
async *cursor(keyset) {
|
|
703
|
+
const model = this.model;
|
|
704
|
+
const primaryKey = model ? model.primaryKey || "id" : "id";
|
|
705
|
+
const orderColumn = this.orders[0]?.column || primaryKey;
|
|
706
|
+
const orderDirection = this.orders[0]?.direction || "asc";
|
|
707
|
+
const builder = this.clone();
|
|
708
|
+
builder.orders = [{ column: orderColumn, direction: orderDirection }];
|
|
709
|
+
builder.offsetValue = undefined;
|
|
710
|
+
if (keyset) {
|
|
711
|
+
const op = orderDirection === "asc" ? ">" : "<";
|
|
712
|
+
builder.wheres.push({
|
|
713
|
+
type: "basic",
|
|
714
|
+
column: orderColumn,
|
|
715
|
+
operator: op,
|
|
716
|
+
value: keyset[orderColumn],
|
|
717
|
+
boolean: "and",
|
|
718
|
+
scope: undefined,
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
const items = await builder.limit(1).get();
|
|
722
|
+
if (items.length === 0)
|
|
723
|
+
return;
|
|
724
|
+
yield items[0];
|
|
725
|
+
const nextKeyset = items[0] && typeof items[0] === "object"
|
|
726
|
+
? { [orderColumn]: items[0][orderColumn] }
|
|
727
|
+
: undefined;
|
|
728
|
+
if (nextKeyset) {
|
|
729
|
+
yield* this.cursor(nextKeyset);
|
|
708
730
|
}
|
|
709
731
|
}
|
|
710
732
|
async *lazy(count = 1000) {
|
package/package.json
CHANGED