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.
Files changed (52) hide show
  1. package/dist/index.cjs +1466 -189
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +723 -51
  4. package/dist/index.d.ts +723 -51
  5. package/dist/index.js +1457 -189
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/codegen/typescript.ts +66 -5
  9. package/src/core/ast/aggregate-functions.ts +15 -15
  10. package/src/core/ast/expression-builders.ts +378 -316
  11. package/src/core/ast/expression-nodes.ts +210 -186
  12. package/src/core/ast/expression-visitor.ts +40 -30
  13. package/src/core/ast/query.ts +164 -132
  14. package/src/core/ast/window-functions.ts +86 -86
  15. package/src/core/dialect/abstract.ts +509 -479
  16. package/src/core/dialect/base/groupby-compiler.ts +6 -6
  17. package/src/core/dialect/base/join-compiler.ts +9 -12
  18. package/src/core/dialect/base/orderby-compiler.ts +20 -6
  19. package/src/core/dialect/base/sql-dialect.ts +237 -138
  20. package/src/core/dialect/mssql/index.ts +164 -185
  21. package/src/core/dialect/sqlite/index.ts +39 -34
  22. package/src/core/execution/db-executor.ts +46 -6
  23. package/src/core/execution/executors/mssql-executor.ts +39 -22
  24. package/src/core/execution/executors/mysql-executor.ts +23 -6
  25. package/src/core/execution/executors/sqlite-executor.ts +29 -3
  26. package/src/core/execution/pooling/pool-types.ts +30 -0
  27. package/src/core/execution/pooling/pool.ts +268 -0
  28. package/src/core/functions/standard-strategy.ts +46 -37
  29. package/src/decorators/bootstrap.ts +7 -7
  30. package/src/index.ts +6 -0
  31. package/src/orm/domain-event-bus.ts +49 -0
  32. package/src/orm/entity-metadata.ts +9 -9
  33. package/src/orm/entity.ts +58 -0
  34. package/src/orm/orm-session.ts +465 -270
  35. package/src/orm/orm.ts +61 -11
  36. package/src/orm/pooled-executor-factory.ts +131 -0
  37. package/src/orm/query-logger.ts +6 -12
  38. package/src/orm/relation-change-processor.ts +75 -0
  39. package/src/orm/relations/many-to-many.ts +4 -2
  40. package/src/orm/save-graph.ts +303 -0
  41. package/src/orm/transaction-runner.ts +3 -3
  42. package/src/orm/unit-of-work.ts +128 -0
  43. package/src/query-builder/delete-query-state.ts +67 -38
  44. package/src/query-builder/delete.ts +37 -1
  45. package/src/query-builder/hydration-manager.ts +93 -79
  46. package/src/query-builder/insert-query-state.ts +131 -61
  47. package/src/query-builder/insert.ts +27 -1
  48. package/src/query-builder/query-ast-service.ts +207 -170
  49. package/src/query-builder/select-query-state.ts +169 -162
  50. package/src/query-builder/select.ts +15 -23
  51. package/src/query-builder/update-query-state.ts +114 -77
  52. 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 (!client.beginTransaction) return;
36
- await client.beginTransaction();
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 (!client.commit) return;
40
- await client.commit();
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 (!client.rollback) return;
44
- await client.rollback();
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
- beginTransaction: client.beginTransaction?.bind(client),
28
- commitTransaction: client.commitTransaction?.bind(client),
29
- rollbackTransaction: client.rollbackTransaction?.bind(client),
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 => `${ctx.compileOperand(order.column)} ${order.direction}`);
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, TableDef>
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, TableDef>
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, TableDef>();
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