metal-orm 1.0.39 → 1.0.41
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/dist/index.cjs +1466 -189
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +723 -51
- package/dist/index.d.ts +723 -51
- package/dist/index.js +1457 -189
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/typescript.ts +66 -5
- package/src/core/ast/aggregate-functions.ts +15 -15
- package/src/core/ast/expression-builders.ts +378 -316
- package/src/core/ast/expression-nodes.ts +210 -186
- package/src/core/ast/expression-visitor.ts +40 -30
- package/src/core/ast/query.ts +164 -132
- package/src/core/ast/window-functions.ts +86 -86
- package/src/core/dialect/abstract.ts +509 -479
- package/src/core/dialect/base/groupby-compiler.ts +6 -6
- package/src/core/dialect/base/join-compiler.ts +9 -12
- package/src/core/dialect/base/orderby-compiler.ts +20 -6
- package/src/core/dialect/base/sql-dialect.ts +237 -138
- package/src/core/dialect/mssql/index.ts +164 -185
- 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/core/functions/standard-strategy.ts +46 -37
- 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/hydration-manager.ts +93 -79
- package/src/query-builder/insert-query-state.ts +131 -61
- package/src/query-builder/insert.ts +27 -1
- package/src/query-builder/query-ast-service.ts +207 -170
- package/src/query-builder/select-query-state.ts +169 -162
- package/src/query-builder/select.ts +15 -23
- package/src/query-builder/update-query-state.ts +114 -77
- package/src/query-builder/update.ts +38 -1
|
@@ -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
|
+
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { FunctionStrategy, FunctionRenderer, FunctionRenderContext } from './types.js';
|
|
2
|
-
import { LiteralNode, OperandNode } from '../ast/expression.js';
|
|
3
|
-
|
|
4
|
-
export class StandardFunctionStrategy implements FunctionStrategy {
|
|
5
|
-
protected renderers: Map<string, FunctionRenderer> = new Map();
|
|
6
|
-
|
|
7
|
-
constructor() {
|
|
8
|
-
this.registerStandard();
|
|
9
|
-
}
|
|
10
|
-
|
|
2
|
+
import { LiteralNode, OperandNode, isOperandNode } from '../ast/expression.js';
|
|
3
|
+
|
|
4
|
+
export class StandardFunctionStrategy implements FunctionStrategy {
|
|
5
|
+
protected renderers: Map<string, FunctionRenderer> = new Map();
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
this.registerStandard();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
11
|
protected registerStandard() {
|
|
12
12
|
// Register ANSI standard implementations
|
|
13
13
|
this.add('COUNT', ({ compiledArgs }) => `COUNT(${compiledArgs.join(', ')})`);
|
|
@@ -18,36 +18,36 @@ export class StandardFunctionStrategy implements FunctionStrategy {
|
|
|
18
18
|
this.add('ABS', ({ compiledArgs }) => `ABS(${compiledArgs[0]})`);
|
|
19
19
|
this.add('UPPER', ({ compiledArgs }) => `UPPER(${compiledArgs[0]})`);
|
|
20
20
|
this.add('LOWER', ({ compiledArgs }) => `LOWER(${compiledArgs[0]})`);
|
|
21
|
-
this.add('LENGTH', ({ compiledArgs }) => `LENGTH(${compiledArgs[0]})`);
|
|
22
|
-
this.add('TRIM', ({ compiledArgs }) => `TRIM(${compiledArgs[0]})`);
|
|
23
|
-
this.add('LTRIM', ({ compiledArgs }) => `LTRIM(${compiledArgs[0]})`);
|
|
24
|
-
this.add('RTRIM', ({ compiledArgs }) => `RTRIM(${compiledArgs[0]})`);
|
|
25
|
-
this.add('SUBSTRING', ({ compiledArgs }) => `SUBSTRING(${compiledArgs.join(', ')})`);
|
|
26
|
-
this.add('CONCAT', ({ compiledArgs }) => `CONCAT(${compiledArgs.join(', ')})`);
|
|
27
|
-
this.add('NOW', () => `NOW()`);
|
|
28
|
-
this.add('CURRENT_DATE', () => `CURRENT_DATE`);
|
|
29
|
-
this.add('CURRENT_TIME', () => `CURRENT_TIME`);
|
|
30
|
-
this.add('EXTRACT', ({ compiledArgs }) => `EXTRACT(${compiledArgs[0]} FROM ${compiledArgs[1]})`);
|
|
31
|
-
this.add('YEAR', ({ compiledArgs }) => `EXTRACT(YEAR FROM ${compiledArgs[0]})`);
|
|
32
|
-
this.add('MONTH', ({ compiledArgs }) => `EXTRACT(MONTH FROM ${compiledArgs[0]})`);
|
|
33
|
-
this.add('DAY', ({ compiledArgs }) => `EXTRACT(DAY FROM ${compiledArgs[0]})`);
|
|
34
|
-
this.add('DATE_ADD', ({ compiledArgs }) => `(${compiledArgs[0]} + INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
|
|
35
|
-
this.add('DATE_SUB', ({ compiledArgs }) => `(${compiledArgs[0]} - INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
|
|
36
|
-
this.add('DATE_DIFF', ({ compiledArgs }) => `DATEDIFF(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
37
|
-
this.add('DATE_FORMAT', ({ compiledArgs }) => `DATE_FORMAT(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
38
|
-
this.add('UNIX_TIMESTAMP', () => `UNIX_TIMESTAMP()`);
|
|
39
|
-
this.add('FROM_UNIXTIME', ({ compiledArgs }) => `FROM_UNIXTIME(${compiledArgs[0]})`);
|
|
40
|
-
this.add('END_OF_MONTH', ({ compiledArgs }) => `LAST_DAY(${compiledArgs[0]})`);
|
|
41
|
-
this.add('DAY_OF_WEEK', ({ compiledArgs }) => `DAYOFWEEK(${compiledArgs[0]})`);
|
|
21
|
+
this.add('LENGTH', ({ compiledArgs }) => `LENGTH(${compiledArgs[0]})`);
|
|
22
|
+
this.add('TRIM', ({ compiledArgs }) => `TRIM(${compiledArgs[0]})`);
|
|
23
|
+
this.add('LTRIM', ({ compiledArgs }) => `LTRIM(${compiledArgs[0]})`);
|
|
24
|
+
this.add('RTRIM', ({ compiledArgs }) => `RTRIM(${compiledArgs[0]})`);
|
|
25
|
+
this.add('SUBSTRING', ({ compiledArgs }) => `SUBSTRING(${compiledArgs.join(', ')})`);
|
|
26
|
+
this.add('CONCAT', ({ compiledArgs }) => `CONCAT(${compiledArgs.join(', ')})`);
|
|
27
|
+
this.add('NOW', () => `NOW()`);
|
|
28
|
+
this.add('CURRENT_DATE', () => `CURRENT_DATE`);
|
|
29
|
+
this.add('CURRENT_TIME', () => `CURRENT_TIME`);
|
|
30
|
+
this.add('EXTRACT', ({ compiledArgs }) => `EXTRACT(${compiledArgs[0]} FROM ${compiledArgs[1]})`);
|
|
31
|
+
this.add('YEAR', ({ compiledArgs }) => `EXTRACT(YEAR FROM ${compiledArgs[0]})`);
|
|
32
|
+
this.add('MONTH', ({ compiledArgs }) => `EXTRACT(MONTH FROM ${compiledArgs[0]})`);
|
|
33
|
+
this.add('DAY', ({ compiledArgs }) => `EXTRACT(DAY FROM ${compiledArgs[0]})`);
|
|
34
|
+
this.add('DATE_ADD', ({ compiledArgs }) => `(${compiledArgs[0]} + INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
|
|
35
|
+
this.add('DATE_SUB', ({ compiledArgs }) => `(${compiledArgs[0]} - INTERVAL ${compiledArgs[1]} ${compiledArgs[2]})`);
|
|
36
|
+
this.add('DATE_DIFF', ({ compiledArgs }) => `DATEDIFF(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
37
|
+
this.add('DATE_FORMAT', ({ compiledArgs }) => `DATE_FORMAT(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
38
|
+
this.add('UNIX_TIMESTAMP', () => `UNIX_TIMESTAMP()`);
|
|
39
|
+
this.add('FROM_UNIXTIME', ({ compiledArgs }) => `FROM_UNIXTIME(${compiledArgs[0]})`);
|
|
40
|
+
this.add('END_OF_MONTH', ({ compiledArgs }) => `LAST_DAY(${compiledArgs[0]})`);
|
|
41
|
+
this.add('DAY_OF_WEEK', ({ compiledArgs }) => `DAYOFWEEK(${compiledArgs[0]})`);
|
|
42
42
|
this.add('WEEK_OF_YEAR', ({ compiledArgs }) => `WEEKOFYEAR(${compiledArgs[0]})`);
|
|
43
43
|
this.add('DATE_TRUNC', ({ compiledArgs }) => `DATE_TRUNC(${compiledArgs[0]}, ${compiledArgs[1]})`);
|
|
44
44
|
this.add('GROUP_CONCAT', ctx => this.renderGroupConcat(ctx));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
protected add(name: string, renderer: FunctionRenderer) {
|
|
48
|
-
this.renderers.set(name, renderer);
|
|
49
|
-
}
|
|
50
|
-
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected add(name: string, renderer: FunctionRenderer) {
|
|
48
|
+
this.renderers.set(name, renderer);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
51
|
getRenderer(name: string): FunctionRenderer | undefined {
|
|
52
52
|
return this.renderers.get(name);
|
|
53
53
|
}
|
|
@@ -65,7 +65,16 @@ export class StandardFunctionStrategy implements FunctionStrategy {
|
|
|
65
65
|
if (!orderBy || orderBy.length === 0) {
|
|
66
66
|
return '';
|
|
67
67
|
}
|
|
68
|
-
const parts = orderBy.map(order =>
|
|
68
|
+
const parts = orderBy.map(order => {
|
|
69
|
+
const term = isOperandNode(order.term)
|
|
70
|
+
? ctx.compileOperand(order.term)
|
|
71
|
+
: (() => {
|
|
72
|
+
throw new Error('ORDER BY expressions inside functions must be operands');
|
|
73
|
+
})();
|
|
74
|
+
const collation = order.collation ? ` COLLATE ${order.collation}` : '';
|
|
75
|
+
const nulls = order.nulls ? ` NULLS ${order.nulls}` : '';
|
|
76
|
+
return `${term} ${order.direction}${collation}${nulls}`;
|
|
77
|
+
});
|
|
69
78
|
return `ORDER BY ${parts.join(', ')}`;
|
|
70
79
|
}
|
|
71
80
|
|
|
@@ -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';
|
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
import type { DomainEvent, HasDomainEvents, TrackedEntity } from './runtime-types.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Extracts domain events of a specific type.
|
|
5
|
+
* @template E - The domain event type
|
|
6
|
+
* @template TType - The specific event type
|
|
7
|
+
*/
|
|
3
8
|
type EventOfType<E extends DomainEvent, TType extends E['type']> =
|
|
4
9
|
Extract<E, { type: TType }>;
|
|
5
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Domain event handler function.
|
|
13
|
+
* @template E - The domain event type
|
|
14
|
+
* @template Context - The context type
|
|
15
|
+
* @param event - The domain event
|
|
16
|
+
* @param ctx - The context
|
|
17
|
+
*/
|
|
6
18
|
export type DomainEventHandler<E extends DomainEvent, Context> =
|
|
7
19
|
(event: E, ctx: Context) => Promise<void> | void;
|
|
8
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Initial handlers for domain events.
|
|
23
|
+
* @template E - The domain event type
|
|
24
|
+
* @template Context - The context type
|
|
25
|
+
*/
|
|
9
26
|
export type InitialHandlers<E extends DomainEvent, Context> = {
|
|
10
27
|
[K in E['type']]?: DomainEventHandler<EventOfType<E, K>, Context>[];
|
|
11
28
|
};
|
|
12
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Domain event bus for managing and dispatching domain events.
|
|
32
|
+
* @template E - The domain event type
|
|
33
|
+
* @template Context - The context type
|
|
34
|
+
*/
|
|
13
35
|
export class DomainEventBus<E extends DomainEvent, Context> {
|
|
14
36
|
private readonly handlers = new Map<E['type'], DomainEventHandler<E, Context>[]>();
|
|
15
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Creates a new DomainEventBus instance.
|
|
40
|
+
* @param initialHandlers - Optional initial event handlers
|
|
41
|
+
*/
|
|
16
42
|
constructor(initialHandlers?: InitialHandlers<E, Context>) {
|
|
17
43
|
if (initialHandlers) {
|
|
18
44
|
for (const key in initialHandlers) {
|
|
@@ -23,6 +49,12 @@ export class DomainEventBus<E extends DomainEvent, Context> {
|
|
|
23
49
|
}
|
|
24
50
|
}
|
|
25
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Registers an event handler for a specific event type.
|
|
54
|
+
* @template TType - The event type
|
|
55
|
+
* @param type - The event type
|
|
56
|
+
* @param handler - The event handler
|
|
57
|
+
*/
|
|
26
58
|
on<TType extends E['type']>(
|
|
27
59
|
type: TType,
|
|
28
60
|
handler: DomainEventHandler<EventOfType<E, TType>, Context>
|
|
@@ -33,6 +65,12 @@ export class DomainEventBus<E extends DomainEvent, Context> {
|
|
|
33
65
|
this.handlers.set(key, existing);
|
|
34
66
|
}
|
|
35
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Registers an event handler for a specific event type (alias for on).
|
|
70
|
+
* @template TType - The event type
|
|
71
|
+
* @param type - The event type
|
|
72
|
+
* @param handler - The event handler
|
|
73
|
+
*/
|
|
36
74
|
register<TType extends E['type']>(
|
|
37
75
|
type: TType,
|
|
38
76
|
handler: DomainEventHandler<EventOfType<E, TType>, Context>
|
|
@@ -40,6 +78,11 @@ export class DomainEventBus<E extends DomainEvent, Context> {
|
|
|
40
78
|
this.on(type, handler);
|
|
41
79
|
}
|
|
42
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Dispatches domain events for tracked entities.
|
|
83
|
+
* @param trackedEntities - Iterable of tracked entities
|
|
84
|
+
* @param ctx - The context to pass to handlers
|
|
85
|
+
*/
|
|
43
86
|
async dispatch(trackedEntities: Iterable<TrackedEntity>, ctx: Context): Promise<void> {
|
|
44
87
|
for (const tracked of trackedEntities) {
|
|
45
88
|
const entity = tracked.entity as HasDomainEvents<E>;
|
|
@@ -59,6 +102,12 @@ export class DomainEventBus<E extends DomainEvent, Context> {
|
|
|
59
102
|
}
|
|
60
103
|
}
|
|
61
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Adds a domain event to an entity.
|
|
107
|
+
* @template E - The domain event type
|
|
108
|
+
* @param entity - The entity to add the event to
|
|
109
|
+
* @param event - The domain event to add
|
|
110
|
+
*/
|
|
62
111
|
export const addDomainEvent = <E extends DomainEvent>(
|
|
63
112
|
entity: HasDomainEvents<E>,
|
|
64
113
|
event: E
|