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.
Files changed (45) hide show
  1. package/README.md +53 -14
  2. package/dist/index.cjs +1298 -126
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +676 -30
  5. package/dist/index.d.ts +676 -30
  6. package/dist/index.js +1293 -126
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/codegen/typescript.ts +6 -2
  10. package/src/core/ast/expression-builders.ts +25 -4
  11. package/src/core/ast/expression-nodes.ts +3 -1
  12. package/src/core/ast/expression.ts +2 -2
  13. package/src/core/ast/query.ts +24 -2
  14. package/src/core/dialect/abstract.ts +6 -2
  15. package/src/core/dialect/base/join-compiler.ts +9 -12
  16. package/src/core/dialect/base/sql-dialect.ts +98 -17
  17. package/src/core/dialect/mssql/index.ts +30 -62
  18. package/src/core/dialect/sqlite/index.ts +39 -34
  19. package/src/core/execution/db-executor.ts +46 -6
  20. package/src/core/execution/executors/mssql-executor.ts +39 -22
  21. package/src/core/execution/executors/mysql-executor.ts +23 -6
  22. package/src/core/execution/executors/sqlite-executor.ts +29 -3
  23. package/src/core/execution/pooling/pool-types.ts +30 -0
  24. package/src/core/execution/pooling/pool.ts +268 -0
  25. package/src/decorators/bootstrap.ts +7 -7
  26. package/src/index.ts +6 -0
  27. package/src/orm/domain-event-bus.ts +49 -0
  28. package/src/orm/entity-metadata.ts +9 -9
  29. package/src/orm/entity.ts +58 -0
  30. package/src/orm/orm-session.ts +465 -270
  31. package/src/orm/orm.ts +61 -11
  32. package/src/orm/pooled-executor-factory.ts +131 -0
  33. package/src/orm/query-logger.ts +6 -12
  34. package/src/orm/relation-change-processor.ts +75 -0
  35. package/src/orm/relations/many-to-many.ts +4 -2
  36. package/src/orm/save-graph.ts +303 -0
  37. package/src/orm/transaction-runner.ts +3 -3
  38. package/src/orm/unit-of-work.ts +128 -0
  39. package/src/query-builder/delete-query-state.ts +67 -38
  40. package/src/query-builder/delete.ts +37 -1
  41. package/src/query-builder/insert-query-state.ts +131 -61
  42. package/src/query-builder/insert.ts +27 -1
  43. package/src/query-builder/update-query-state.ts +114 -77
  44. package/src/query-builder/update.ts +38 -1
  45. 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?(): Promise<void>;
13
- commitTransaction?(): Promise<void>;
14
- rollbackTransaction?(): Promise<void>;
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
- beginTransaction: runner.beginTransaction?.bind(runner),
60
- commitTransaction: runner.commitTransaction?.bind(runner),
61
- rollbackTransaction: runner.rollbackTransaction?.bind(runner),
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 (!client.beginTransaction) return;
28
- await client.beginTransaction();
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 (!client.commit) return;
32
- await client.commit();
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 (!client.rollback) return;
36
- await client.rollback();
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 (sql: string, callback: (err?: Error | null) => void): TediousRequest;
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
- new Promise<void>((resolve, reject) => {
144
- connection.beginTransaction!(err =>
145
- err ? reject(err) : resolve()
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
- new Promise<void>((resolve, reject) => {
153
- connection.commitTransaction!(err =>
154
- err ? reject(err) : resolve()
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
- new Promise<void>((resolve, reject) => {
162
- connection.rollbackTransaction!(err =>
163
- err ? reject(err) : resolve()
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 (!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
+
@@ -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';