metal-orm 1.0.40 → 1.0.42
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 +53 -14
- package/dist/index.cjs +1298 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +676 -30
- package/dist/index.d.ts +676 -30
- package/dist/index.js +1293 -126
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/typescript.ts +6 -2
- package/src/core/ast/expression-builders.ts +25 -4
- package/src/core/ast/expression-nodes.ts +3 -1
- package/src/core/ast/expression.ts +2 -2
- package/src/core/ast/query.ts +24 -2
- package/src/core/dialect/abstract.ts +6 -2
- package/src/core/dialect/base/join-compiler.ts +9 -12
- package/src/core/dialect/base/sql-dialect.ts +98 -17
- package/src/core/dialect/mssql/index.ts +30 -62
- package/src/core/dialect/sqlite/index.ts +39 -34
- package/src/core/execution/db-executor.ts +46 -6
- package/src/core/execution/executors/mssql-executor.ts +39 -22
- package/src/core/execution/executors/mysql-executor.ts +23 -6
- package/src/core/execution/executors/sqlite-executor.ts +29 -3
- package/src/core/execution/pooling/pool-types.ts +30 -0
- package/src/core/execution/pooling/pool.ts +268 -0
- package/src/decorators/bootstrap.ts +7 -7
- package/src/index.ts +6 -0
- package/src/orm/domain-event-bus.ts +49 -0
- package/src/orm/entity-metadata.ts +9 -9
- package/src/orm/entity.ts +58 -0
- package/src/orm/orm-session.ts +465 -270
- package/src/orm/orm.ts +61 -11
- package/src/orm/pooled-executor-factory.ts +131 -0
- package/src/orm/query-logger.ts +6 -12
- package/src/orm/relation-change-processor.ts +75 -0
- package/src/orm/relations/many-to-many.ts +4 -2
- package/src/orm/save-graph.ts +303 -0
- package/src/orm/transaction-runner.ts +3 -3
- package/src/orm/unit-of-work.ts +128 -0
- package/src/query-builder/delete-query-state.ts +67 -38
- package/src/query-builder/delete.ts +37 -1
- package/src/query-builder/insert-query-state.ts +131 -61
- package/src/query-builder/insert.ts +27 -1
- package/src/query-builder/update-query-state.ts +114 -77
- package/src/query-builder/update.ts +38 -1
- package/src/schema/table.ts +210 -115
|
@@ -7,11 +7,20 @@ export type QueryResult = {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export interface DbExecutor {
|
|
10
|
+
/** Capability flags so the runtime can make correct decisions without relying on optional methods. */
|
|
11
|
+
readonly capabilities: {
|
|
12
|
+
/** True if begin/commit/rollback are real and should be used to provide atomicity. */
|
|
13
|
+
transactions: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
10
16
|
executeSql(sql: string, params?: unknown[]): Promise<QueryResult[]>;
|
|
11
17
|
|
|
12
|
-
beginTransaction
|
|
13
|
-
commitTransaction
|
|
14
|
-
rollbackTransaction
|
|
18
|
+
beginTransaction(): Promise<void>;
|
|
19
|
+
commitTransaction(): Promise<void>;
|
|
20
|
+
rollbackTransaction(): Promise<void>;
|
|
21
|
+
|
|
22
|
+
/** Release any underlying resources (connections, pool leases, etc). Must be idempotent. */
|
|
23
|
+
dispose(): Promise<void>;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
// --- helpers ---
|
|
@@ -39,9 +48,14 @@ export interface SimpleQueryRunner {
|
|
|
39
48
|
sql: string,
|
|
40
49
|
params?: unknown[]
|
|
41
50
|
): Promise<Array<Record<string, unknown>>>;
|
|
51
|
+
|
|
52
|
+
/** Optional: used to support real transactions. */
|
|
42
53
|
beginTransaction?(): Promise<void>;
|
|
43
54
|
commitTransaction?(): Promise<void>;
|
|
44
55
|
rollbackTransaction?(): Promise<void>;
|
|
56
|
+
|
|
57
|
+
/** Optional: release resources (connection close, pool lease release, etc). */
|
|
58
|
+
dispose?(): Promise<void>;
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
/**
|
|
@@ -50,14 +64,40 @@ export interface SimpleQueryRunner {
|
|
|
50
64
|
export function createExecutorFromQueryRunner(
|
|
51
65
|
runner: SimpleQueryRunner
|
|
52
66
|
): DbExecutor {
|
|
67
|
+
const supportsTransactions =
|
|
68
|
+
typeof runner.beginTransaction === 'function' &&
|
|
69
|
+
typeof runner.commitTransaction === 'function' &&
|
|
70
|
+
typeof runner.rollbackTransaction === 'function';
|
|
71
|
+
|
|
53
72
|
return {
|
|
73
|
+
capabilities: {
|
|
74
|
+
transactions: supportsTransactions,
|
|
75
|
+
},
|
|
54
76
|
async executeSql(sql, params) {
|
|
55
77
|
const rows = await runner.query(sql, params);
|
|
56
78
|
const result = rowsToQueryResult(rows);
|
|
57
79
|
return [result];
|
|
58
80
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
async beginTransaction() {
|
|
82
|
+
if (!supportsTransactions) {
|
|
83
|
+
throw new Error('Transactions are not supported by this executor');
|
|
84
|
+
}
|
|
85
|
+
await runner.beginTransaction!.call(runner);
|
|
86
|
+
},
|
|
87
|
+
async commitTransaction() {
|
|
88
|
+
if (!supportsTransactions) {
|
|
89
|
+
throw new Error('Transactions are not supported by this executor');
|
|
90
|
+
}
|
|
91
|
+
await runner.commitTransaction!.call(runner);
|
|
92
|
+
},
|
|
93
|
+
async rollbackTransaction() {
|
|
94
|
+
if (!supportsTransactions) {
|
|
95
|
+
throw new Error('Transactions are not supported by this executor');
|
|
96
|
+
}
|
|
97
|
+
await runner.rollbackTransaction!.call(runner);
|
|
98
|
+
},
|
|
99
|
+
async dispose() {
|
|
100
|
+
await runner.dispose?.call(runner);
|
|
101
|
+
},
|
|
62
102
|
};
|
|
63
103
|
}
|
|
@@ -17,23 +17,40 @@ export interface MssqlClientLike {
|
|
|
17
17
|
export function createMssqlExecutor(
|
|
18
18
|
client: MssqlClientLike
|
|
19
19
|
): DbExecutor {
|
|
20
|
+
const supportsTransactions =
|
|
21
|
+
typeof client.beginTransaction === 'function' &&
|
|
22
|
+
typeof client.commit === 'function' &&
|
|
23
|
+
typeof client.rollback === 'function';
|
|
24
|
+
|
|
20
25
|
return {
|
|
26
|
+
capabilities: {
|
|
27
|
+
transactions: supportsTransactions,
|
|
28
|
+
},
|
|
21
29
|
async executeSql(sql, params) {
|
|
22
30
|
const { recordset } = await client.query(sql, params);
|
|
23
31
|
const result = rowsToQueryResult(recordset ?? []);
|
|
24
32
|
return [result];
|
|
25
33
|
},
|
|
26
34
|
async beginTransaction() {
|
|
27
|
-
if (!
|
|
28
|
-
|
|
35
|
+
if (!supportsTransactions) {
|
|
36
|
+
throw new Error('Transactions are not supported by this executor');
|
|
37
|
+
}
|
|
38
|
+
await client.beginTransaction!();
|
|
29
39
|
},
|
|
30
40
|
async commitTransaction() {
|
|
31
|
-
if (!
|
|
32
|
-
|
|
41
|
+
if (!supportsTransactions) {
|
|
42
|
+
throw new Error('Transactions are not supported by this executor');
|
|
43
|
+
}
|
|
44
|
+
await client.commit!();
|
|
33
45
|
},
|
|
34
46
|
async rollbackTransaction() {
|
|
35
|
-
if (!
|
|
36
|
-
|
|
47
|
+
if (!supportsTransactions) {
|
|
48
|
+
throw new Error('Transactions are not supported by this executor');
|
|
49
|
+
}
|
|
50
|
+
await client.rollback!();
|
|
51
|
+
},
|
|
52
|
+
async dispose() {
|
|
53
|
+
// Connection lifecycle is owned by the caller/driver. Pool lease executors should implement dispose.
|
|
37
54
|
},
|
|
38
55
|
};
|
|
39
56
|
}
|
|
@@ -53,7 +70,7 @@ export interface TediousRequest {
|
|
|
53
70
|
}
|
|
54
71
|
|
|
55
72
|
export interface TediousRequestCtor {
|
|
56
|
-
new
|
|
73
|
+
new(sql: string, callback: (err?: Error | null) => void): TediousRequest;
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
export interface TediousTypes {
|
|
@@ -140,29 +157,29 @@ export function createTediousMssqlClient(
|
|
|
140
157
|
|
|
141
158
|
beginTransaction: connection.beginTransaction
|
|
142
159
|
? () =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
new Promise<void>((resolve, reject) => {
|
|
161
|
+
connection.beginTransaction!(err =>
|
|
162
|
+
err ? reject(err) : resolve()
|
|
163
|
+
);
|
|
164
|
+
})
|
|
148
165
|
: undefined,
|
|
149
166
|
|
|
150
167
|
commit: connection.commitTransaction
|
|
151
168
|
? () =>
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
new Promise<void>((resolve, reject) => {
|
|
170
|
+
connection.commitTransaction!(err =>
|
|
171
|
+
err ? reject(err) : resolve()
|
|
172
|
+
);
|
|
173
|
+
})
|
|
157
174
|
: undefined,
|
|
158
175
|
|
|
159
176
|
rollback: connection.rollbackTransaction
|
|
160
177
|
? () =>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
178
|
+
new Promise<void>((resolve, reject) => {
|
|
179
|
+
connection.rollbackTransaction!(err =>
|
|
180
|
+
err ? reject(err) : resolve()
|
|
181
|
+
);
|
|
182
|
+
})
|
|
166
183
|
: undefined,
|
|
167
184
|
};
|
|
168
185
|
}
|
|
@@ -17,7 +17,15 @@ export interface MysqlClientLike {
|
|
|
17
17
|
export function createMysqlExecutor(
|
|
18
18
|
client: MysqlClientLike
|
|
19
19
|
): DbExecutor {
|
|
20
|
+
const supportsTransactions =
|
|
21
|
+
typeof client.beginTransaction === 'function' &&
|
|
22
|
+
typeof client.commit === 'function' &&
|
|
23
|
+
typeof client.rollback === 'function';
|
|
24
|
+
|
|
20
25
|
return {
|
|
26
|
+
capabilities: {
|
|
27
|
+
transactions: supportsTransactions,
|
|
28
|
+
},
|
|
21
29
|
async executeSql(sql, params) {
|
|
22
30
|
const [rows] = await client.query(sql, params as any[]);
|
|
23
31
|
|
|
@@ -32,16 +40,25 @@ export function createMysqlExecutor(
|
|
|
32
40
|
return [result];
|
|
33
41
|
},
|
|
34
42
|
async beginTransaction() {
|
|
35
|
-
if (!
|
|
36
|
-
|
|
43
|
+
if (!supportsTransactions) {
|
|
44
|
+
throw new Error('Transactions are not supported by this executor');
|
|
45
|
+
}
|
|
46
|
+
await client.beginTransaction!();
|
|
37
47
|
},
|
|
38
48
|
async commitTransaction() {
|
|
39
|
-
if (!
|
|
40
|
-
|
|
49
|
+
if (!supportsTransactions) {
|
|
50
|
+
throw new Error('Transactions are not supported by this executor');
|
|
51
|
+
}
|
|
52
|
+
await client.commit!();
|
|
41
53
|
},
|
|
42
54
|
async rollbackTransaction() {
|
|
43
|
-
if (!
|
|
44
|
-
|
|
55
|
+
if (!supportsTransactions) {
|
|
56
|
+
throw new Error('Transactions are not supported by this executor');
|
|
57
|
+
}
|
|
58
|
+
await client.rollback!();
|
|
59
|
+
},
|
|
60
|
+
async dispose() {
|
|
61
|
+
// Connection lifecycle is owned by the caller/driver. Pool lease executors should implement dispose.
|
|
45
62
|
},
|
|
46
63
|
};
|
|
47
64
|
}
|
|
@@ -18,14 +18,40 @@ export interface SqliteClientLike {
|
|
|
18
18
|
export function createSqliteExecutor(
|
|
19
19
|
client: SqliteClientLike
|
|
20
20
|
): DbExecutor {
|
|
21
|
+
const supportsTransactions =
|
|
22
|
+
typeof client.beginTransaction === 'function' &&
|
|
23
|
+
typeof client.commitTransaction === 'function' &&
|
|
24
|
+
typeof client.rollbackTransaction === 'function';
|
|
25
|
+
|
|
21
26
|
return {
|
|
27
|
+
capabilities: {
|
|
28
|
+
transactions: supportsTransactions,
|
|
29
|
+
},
|
|
22
30
|
async executeSql(sql, params) {
|
|
23
31
|
const rows = await client.all(sql, params);
|
|
24
32
|
const result = rowsToQueryResult(rows);
|
|
25
33
|
return [result];
|
|
26
34
|
},
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
async beginTransaction() {
|
|
36
|
+
if (!supportsTransactions) {
|
|
37
|
+
throw new Error('Transactions are not supported by this executor');
|
|
38
|
+
}
|
|
39
|
+
await client.beginTransaction!();
|
|
40
|
+
},
|
|
41
|
+
async commitTransaction() {
|
|
42
|
+
if (!supportsTransactions) {
|
|
43
|
+
throw new Error('Transactions are not supported by this executor');
|
|
44
|
+
}
|
|
45
|
+
await client.commitTransaction!();
|
|
46
|
+
},
|
|
47
|
+
async rollbackTransaction() {
|
|
48
|
+
if (!supportsTransactions) {
|
|
49
|
+
throw new Error('Transactions are not supported by this executor');
|
|
50
|
+
}
|
|
51
|
+
await client.rollbackTransaction!();
|
|
52
|
+
},
|
|
53
|
+
async dispose() {
|
|
54
|
+
// Connection lifecycle is owned by the caller/driver. Pool lease executors should implement dispose.
|
|
55
|
+
},
|
|
30
56
|
};
|
|
31
57
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type PoolOptions = {
|
|
2
|
+
/** Maximum number of live resources (idle + leased). */
|
|
3
|
+
max: number;
|
|
4
|
+
/** Minimum number of idle resources to keep warm (best-effort). */
|
|
5
|
+
min?: number;
|
|
6
|
+
|
|
7
|
+
/** How long an idle resource can sit before being destroyed. */
|
|
8
|
+
idleTimeoutMillis?: number;
|
|
9
|
+
/** How often to reap idle resources. Defaults to idleTimeoutMillis / 2 (min 1s). */
|
|
10
|
+
reapIntervalMillis?: number;
|
|
11
|
+
|
|
12
|
+
/** How long callers wait for a resource before acquire() rejects. */
|
|
13
|
+
acquireTimeoutMillis?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface PoolAdapter<TResource> {
|
|
17
|
+
create(): Promise<TResource>;
|
|
18
|
+
destroy(resource: TResource): Promise<void>;
|
|
19
|
+
validate?(resource: TResource): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PoolLease<TResource> {
|
|
23
|
+
readonly resource: TResource;
|
|
24
|
+
|
|
25
|
+
/** Returns the resource to the pool. Idempotent. */
|
|
26
|
+
release(): Promise<void>;
|
|
27
|
+
/** Permanently removes the resource from the pool. Idempotent. */
|
|
28
|
+
destroy(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import type { PoolAdapter, PoolLease, PoolOptions } from './pool-types.js';
|
|
2
|
+
|
|
3
|
+
type Deferred<T> = {
|
|
4
|
+
promise: Promise<T>;
|
|
5
|
+
resolve: (value: T) => void;
|
|
6
|
+
reject: (err: unknown) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const deferred = <T>(): Deferred<T> => {
|
|
10
|
+
let resolve!: (value: T) => void;
|
|
11
|
+
let reject!: (err: unknown) => void;
|
|
12
|
+
const promise = new Promise<T>((res, rej) => {
|
|
13
|
+
resolve = res;
|
|
14
|
+
reject = rej;
|
|
15
|
+
});
|
|
16
|
+
return { promise, resolve, reject };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type IdleEntry<T> = {
|
|
20
|
+
resource: T;
|
|
21
|
+
lastUsedAt: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class Pool<TResource> {
|
|
25
|
+
private readonly adapter: PoolAdapter<TResource>;
|
|
26
|
+
private readonly options: Required<Pick<PoolOptions, 'max'>> & PoolOptions;
|
|
27
|
+
|
|
28
|
+
private destroyed = false;
|
|
29
|
+
private creating = 0;
|
|
30
|
+
private leased = 0;
|
|
31
|
+
private readonly idle: IdleEntry<TResource>[] = [];
|
|
32
|
+
private readonly waiters: Array<Deferred<PoolLease<TResource>>> = [];
|
|
33
|
+
private reapTimer: ReturnType<typeof setInterval> | null = null;
|
|
34
|
+
|
|
35
|
+
constructor(adapter: PoolAdapter<TResource>, options: PoolOptions) {
|
|
36
|
+
if (!Number.isFinite(options.max) || options.max <= 0) {
|
|
37
|
+
throw new Error('Pool options.max must be a positive number');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.adapter = adapter;
|
|
41
|
+
this.options = { max: options.max, ...options };
|
|
42
|
+
|
|
43
|
+
const idleTimeout = this.options.idleTimeoutMillis;
|
|
44
|
+
if (idleTimeout && idleTimeout > 0) {
|
|
45
|
+
const interval =
|
|
46
|
+
this.options.reapIntervalMillis ?? Math.max(1_000, Math.floor(idleTimeout / 2));
|
|
47
|
+
this.reapTimer = setInterval(() => {
|
|
48
|
+
void this.reapIdle();
|
|
49
|
+
}, interval);
|
|
50
|
+
|
|
51
|
+
// Best-effort: avoid keeping the event loop alive.
|
|
52
|
+
(this.reapTimer as any).unref?.();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Best-effort warmup.
|
|
56
|
+
const min = this.options.min ?? 0;
|
|
57
|
+
if (min > 0) {
|
|
58
|
+
void this.warm(min);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Acquire a resource lease.
|
|
64
|
+
* The returned lease MUST be released or destroyed.
|
|
65
|
+
*/
|
|
66
|
+
async acquire(): Promise<PoolLease<TResource>> {
|
|
67
|
+
if (this.destroyed) {
|
|
68
|
+
throw new Error('Pool is destroyed');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 1) Prefer idle.
|
|
72
|
+
const idle = await this.takeIdleValidated();
|
|
73
|
+
if (idle) {
|
|
74
|
+
this.leased++;
|
|
75
|
+
return this.makeLease(idle);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 2) Create if capacity allows.
|
|
79
|
+
if (this.totalLive() < this.options.max) {
|
|
80
|
+
this.creating++;
|
|
81
|
+
try {
|
|
82
|
+
const created = await this.adapter.create();
|
|
83
|
+
this.leased++;
|
|
84
|
+
return this.makeLease(created);
|
|
85
|
+
} finally {
|
|
86
|
+
this.creating--;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 3) Wait.
|
|
91
|
+
const waiter = deferred<PoolLease<TResource>>();
|
|
92
|
+
this.waiters.push(waiter);
|
|
93
|
+
|
|
94
|
+
const timeout = this.options.acquireTimeoutMillis;
|
|
95
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
96
|
+
if (timeout && timeout > 0) {
|
|
97
|
+
timer = setTimeout(() => {
|
|
98
|
+
// Remove from queue if still waiting.
|
|
99
|
+
const idx = this.waiters.indexOf(waiter);
|
|
100
|
+
if (idx >= 0) this.waiters.splice(idx, 1);
|
|
101
|
+
waiter.reject(new Error('Pool acquire timeout'));
|
|
102
|
+
}, timeout);
|
|
103
|
+
(timer as any).unref?.();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
return await waiter.promise;
|
|
108
|
+
} finally {
|
|
109
|
+
if (timer) clearTimeout(timer);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Destroy pool and all idle resources; waits for in-flight creations to settle. */
|
|
114
|
+
async destroy(): Promise<void> {
|
|
115
|
+
if (this.destroyed) return;
|
|
116
|
+
this.destroyed = true;
|
|
117
|
+
|
|
118
|
+
if (this.reapTimer) {
|
|
119
|
+
clearInterval(this.reapTimer);
|
|
120
|
+
this.reapTimer = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Reject all waiters.
|
|
124
|
+
while (this.waiters.length) {
|
|
125
|
+
this.waiters.shift()!.reject(new Error('Pool destroyed'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Destroy idle resources.
|
|
129
|
+
while (this.idle.length) {
|
|
130
|
+
const entry = this.idle.shift()!;
|
|
131
|
+
await this.adapter.destroy(entry.resource);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private totalLive(): number {
|
|
136
|
+
return this.idle.length + this.leased + this.creating;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private makeLease(resource: TResource): PoolLease<TResource> {
|
|
140
|
+
let done = false;
|
|
141
|
+
return {
|
|
142
|
+
resource,
|
|
143
|
+
release: async () => {
|
|
144
|
+
if (done) return;
|
|
145
|
+
done = true;
|
|
146
|
+
await this.releaseResource(resource);
|
|
147
|
+
},
|
|
148
|
+
destroy: async () => {
|
|
149
|
+
if (done) return;
|
|
150
|
+
done = true;
|
|
151
|
+
await this.destroyResource(resource);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async releaseResource(resource: TResource): Promise<void> {
|
|
157
|
+
this.leased = Math.max(0, this.leased - 1);
|
|
158
|
+
if (this.destroyed) {
|
|
159
|
+
await this.adapter.destroy(resource);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Prefer handing directly to waiters.
|
|
164
|
+
const next = this.waiters.shift();
|
|
165
|
+
if (next) {
|
|
166
|
+
this.leased++;
|
|
167
|
+
next.resolve(this.makeLease(resource));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.idle.push({ resource, lastUsedAt: Date.now() });
|
|
172
|
+
await this.trimToMinMax();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private async destroyResource(resource: TResource): Promise<void> {
|
|
176
|
+
this.leased = Math.max(0, this.leased - 1);
|
|
177
|
+
await this.adapter.destroy(resource);
|
|
178
|
+
|
|
179
|
+
// If there are waiters and we have capacity, create a replacement.
|
|
180
|
+
if (!this.destroyed && this.waiters.length && this.totalLive() < this.options.max) {
|
|
181
|
+
const waiter = this.waiters.shift()!;
|
|
182
|
+
this.creating++;
|
|
183
|
+
try {
|
|
184
|
+
const created = await this.adapter.create();
|
|
185
|
+
this.leased++;
|
|
186
|
+
waiter.resolve(this.makeLease(created));
|
|
187
|
+
} catch (err) {
|
|
188
|
+
waiter.reject(err);
|
|
189
|
+
} finally {
|
|
190
|
+
this.creating--;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async takeIdleValidated(): Promise<TResource | null> {
|
|
196
|
+
while (this.idle.length) {
|
|
197
|
+
const entry = this.idle.pop()!;
|
|
198
|
+
if (!this.adapter.validate) {
|
|
199
|
+
return entry.resource;
|
|
200
|
+
}
|
|
201
|
+
const ok = await this.adapter.validate(entry.resource);
|
|
202
|
+
if (ok) {
|
|
203
|
+
return entry.resource;
|
|
204
|
+
}
|
|
205
|
+
await this.adapter.destroy(entry.resource);
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private async reapIdle(): Promise<void> {
|
|
211
|
+
if (this.destroyed) return;
|
|
212
|
+
const idleTimeout = this.options.idleTimeoutMillis;
|
|
213
|
+
if (!idleTimeout || idleTimeout <= 0) return;
|
|
214
|
+
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
const min = this.options.min ?? 0;
|
|
217
|
+
|
|
218
|
+
// Remove expired resources beyond min.
|
|
219
|
+
const keep: IdleEntry<TResource>[] = [];
|
|
220
|
+
const kill: IdleEntry<TResource>[] = [];
|
|
221
|
+
for (const entry of this.idle) {
|
|
222
|
+
const expired = now - entry.lastUsedAt >= idleTimeout;
|
|
223
|
+
if (expired) kill.push(entry);
|
|
224
|
+
else keep.push(entry);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Keep at least `min`.
|
|
228
|
+
while (keep.length < min && kill.length) {
|
|
229
|
+
keep.push(kill.pop()!);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
this.idle.length = 0;
|
|
233
|
+
this.idle.push(...keep);
|
|
234
|
+
|
|
235
|
+
for (const entry of kill) {
|
|
236
|
+
await this.adapter.destroy(entry.resource);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private async warm(targetMin: number): Promise<void> {
|
|
241
|
+
const min = Math.max(0, targetMin);
|
|
242
|
+
while (!this.destroyed && this.idle.length < min && this.totalLive() < this.options.max) {
|
|
243
|
+
this.creating++;
|
|
244
|
+
try {
|
|
245
|
+
const created = await this.adapter.create();
|
|
246
|
+
this.idle.push({ resource: created, lastUsedAt: Date.now() });
|
|
247
|
+
} catch {
|
|
248
|
+
// If warmup fails, stop trying.
|
|
249
|
+
break;
|
|
250
|
+
} finally {
|
|
251
|
+
this.creating--;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private async trimToMinMax(): Promise<void> {
|
|
257
|
+
const max = this.options.max;
|
|
258
|
+
const min = this.options.min ?? 0;
|
|
259
|
+
|
|
260
|
+
// Ensure we don't exceed max in idle (best-effort; leased/creating already counted elsewhere).
|
|
261
|
+
while (this.totalLive() > max && this.idle.length > min) {
|
|
262
|
+
const entry = this.idle.shift();
|
|
263
|
+
if (!entry) break;
|
|
264
|
+
await this.adapter.destroy(entry.resource);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
@@ -31,22 +31,22 @@ const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget
|
|
|
31
31
|
|
|
32
32
|
const resolveTableTarget = (
|
|
33
33
|
target: EntityOrTableTargetResolver,
|
|
34
|
-
tableMap: Map<EntityConstructor
|
|
34
|
+
tableMap: Map<EntityConstructor<any>, TableDef>
|
|
35
35
|
): TableDef => {
|
|
36
36
|
const resolved = unwrapTarget(target);
|
|
37
37
|
if (isTableDef(resolved)) {
|
|
38
38
|
return resolved;
|
|
39
39
|
}
|
|
40
|
-
const table = tableMap.get(resolved as EntityConstructor);
|
|
40
|
+
const table = tableMap.get(resolved as EntityConstructor<any>);
|
|
41
41
|
if (!table) {
|
|
42
|
-
throw new Error(`Entity '${(resolved as EntityConstructor).name}' is not registered with decorators`);
|
|
42
|
+
throw new Error(`Entity '${(resolved as EntityConstructor<any>).name}' is not registered with decorators`);
|
|
43
43
|
}
|
|
44
44
|
return table;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const buildRelationDefinitions = (
|
|
48
48
|
meta: { relations: Record<string, RelationMetadata> },
|
|
49
|
-
tableMap: Map<EntityConstructor
|
|
49
|
+
tableMap: Map<EntityConstructor<any>, TableDef>
|
|
50
50
|
): Record<string, RelationDef> => {
|
|
51
51
|
const relations: Record<string, RelationDef> = {};
|
|
52
52
|
|
|
@@ -103,7 +103,7 @@ const buildRelationDefinitions = (
|
|
|
103
103
|
|
|
104
104
|
export const bootstrapEntities = (): TableDef[] => {
|
|
105
105
|
const metas = getAllEntityMetadata();
|
|
106
|
-
const tableMap = new Map<EntityConstructor
|
|
106
|
+
const tableMap = new Map<EntityConstructor<any>, TableDef>();
|
|
107
107
|
|
|
108
108
|
for (const meta of metas) {
|
|
109
109
|
const table = buildTableDef(meta);
|
|
@@ -119,7 +119,7 @@ export const bootstrapEntities = (): TableDef[] => {
|
|
|
119
119
|
return metas.map(meta => meta.table!) as TableDef[];
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
-
export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor): TTable | undefined => {
|
|
122
|
+
export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor<any>): TTable | undefined => {
|
|
123
123
|
const meta = getEntityMetadata(ctor);
|
|
124
124
|
if (!meta) return undefined;
|
|
125
125
|
if (!meta.table) {
|
|
@@ -129,7 +129,7 @@ export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor:
|
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
export const selectFromEntity = <TTable extends TableDef = TableDef>(
|
|
132
|
-
ctor: EntityConstructor
|
|
132
|
+
ctor: EntityConstructor<any>
|
|
133
133
|
): SelectQueryBuilder<any, TTable> => {
|
|
134
134
|
const table = getTableDefFromEntity(ctor);
|
|
135
135
|
if (!table) {
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './query-builder/insert.js';
|
|
|
8
8
|
export * from './query-builder/update.js';
|
|
9
9
|
export * from './query-builder/delete.js';
|
|
10
10
|
export * from './core/ast/expression.js';
|
|
11
|
+
export * from './core/ast/window-functions.js';
|
|
11
12
|
export * from './core/hydration/types.js';
|
|
12
13
|
export * from './core/dialect/mysql/index.js';
|
|
13
14
|
export * from './core/dialect/mssql/index.js';
|
|
@@ -42,7 +43,12 @@ export * from './decorators/index.js';
|
|
|
42
43
|
|
|
43
44
|
// NEW: execution abstraction + helpers
|
|
44
45
|
export * from './core/execution/db-executor.js';
|
|
46
|
+
export * from './core/execution/pooling/pool-types.js';
|
|
47
|
+
export * from './core/execution/pooling/pool.js';
|
|
45
48
|
export * from './core/execution/executors/postgres-executor.js';
|
|
46
49
|
export * from './core/execution/executors/mysql-executor.js';
|
|
47
50
|
export * from './core/execution/executors/sqlite-executor.js';
|
|
48
51
|
export * from './core/execution/executors/mssql-executor.js';
|
|
52
|
+
|
|
53
|
+
// NEW: first-class pooling integration
|
|
54
|
+
export * from './orm/pooled-executor-factory.js';
|