metal-orm 1.0.40 → 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 (42) hide show
  1. package/dist/index.cjs +1244 -122
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +655 -30
  4. package/dist/index.d.ts +655 -30
  5. package/dist/index.js +1240 -122
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/codegen/typescript.ts +6 -2
  9. package/src/core/ast/expression-builders.ts +25 -4
  10. package/src/core/ast/expression-nodes.ts +3 -1
  11. package/src/core/ast/query.ts +24 -2
  12. package/src/core/dialect/abstract.ts +6 -2
  13. package/src/core/dialect/base/join-compiler.ts +9 -12
  14. package/src/core/dialect/base/sql-dialect.ts +98 -17
  15. package/src/core/dialect/mssql/index.ts +30 -62
  16. package/src/core/dialect/sqlite/index.ts +39 -34
  17. package/src/core/execution/db-executor.ts +46 -6
  18. package/src/core/execution/executors/mssql-executor.ts +39 -22
  19. package/src/core/execution/executors/mysql-executor.ts +23 -6
  20. package/src/core/execution/executors/sqlite-executor.ts +29 -3
  21. package/src/core/execution/pooling/pool-types.ts +30 -0
  22. package/src/core/execution/pooling/pool.ts +268 -0
  23. package/src/decorators/bootstrap.ts +7 -7
  24. package/src/index.ts +6 -0
  25. package/src/orm/domain-event-bus.ts +49 -0
  26. package/src/orm/entity-metadata.ts +9 -9
  27. package/src/orm/entity.ts +58 -0
  28. package/src/orm/orm-session.ts +465 -270
  29. package/src/orm/orm.ts +61 -11
  30. package/src/orm/pooled-executor-factory.ts +131 -0
  31. package/src/orm/query-logger.ts +6 -12
  32. package/src/orm/relation-change-processor.ts +75 -0
  33. package/src/orm/relations/many-to-many.ts +4 -2
  34. package/src/orm/save-graph.ts +303 -0
  35. package/src/orm/transaction-runner.ts +3 -3
  36. package/src/orm/unit-of-work.ts +128 -0
  37. package/src/query-builder/delete-query-state.ts +67 -38
  38. package/src/query-builder/delete.ts +37 -1
  39. package/src/query-builder/insert-query-state.ts +131 -61
  40. package/src/query-builder/insert.ts +27 -1
  41. package/src/query-builder/update-query-state.ts +114 -77
  42. package/src/query-builder/update.ts +38 -1
package/dist/index.cjs CHANGED
@@ -60,6 +60,7 @@ __export(index_exports, {
60
60
  MySqlDialect: () => MySqlDialect,
61
61
  Orm: () => Orm,
62
62
  OrmSession: () => OrmSession,
63
+ Pool: () => Pool,
63
64
  PostgresDialect: () => PostgresDialect,
64
65
  PrimaryKey: () => PrimaryKey,
65
66
  RelationKinds: () => RelationKinds,
@@ -105,6 +106,7 @@ __export(index_exports, {
105
106
  createLiteral: () => createLiteral,
106
107
  createMssqlExecutor: () => createMssqlExecutor,
107
108
  createMysqlExecutor: () => createMysqlExecutor,
109
+ createPooledExecutorFactory: () => createPooledExecutorFactory,
108
110
  createPostgresExecutor: () => createPostgresExecutor,
109
111
  createQueryLoggingExecutor: () => createQueryLoggingExecutor,
110
112
  createSqliteExecutor: () => createSqliteExecutor,
@@ -146,6 +148,7 @@ __export(index_exports, {
146
148
  hasOne: () => hasOne,
147
149
  hydrateRows: () => hydrateRows,
148
150
  inList: () => inList,
151
+ inSubquery: () => inSubquery,
149
152
  instr: () => instr,
150
153
  introspectSchema: () => introspectSchema,
151
154
  isCaseExpressionNode: () => isCaseExpressionNode,
@@ -186,6 +189,7 @@ __export(index_exports, {
186
189
  notBetween: () => notBetween,
187
190
  notExists: () => notExists,
188
191
  notInList: () => notInList,
192
+ notInSubquery: () => notInSubquery,
189
193
  notLike: () => notLike,
190
194
  now: () => now,
191
195
  ntile: () => ntile,
@@ -497,6 +501,12 @@ var toOperand = (val) => {
497
501
  }
498
502
  return toNode(val);
499
503
  };
504
+ var hasQueryAst = (value) => typeof value.getAST === "function";
505
+ var resolveSelectQueryNode = (query) => hasQueryAst(query) ? query.getAST() : query;
506
+ var toScalarSubqueryNode = (query) => ({
507
+ type: "ScalarSubquery",
508
+ query: resolveSelectQueryNode(query)
509
+ });
500
510
  var columnOperand = (col2) => toNode(col2);
501
511
  var outerRef = (col2) => ({
502
512
  ...columnOperand(col2),
@@ -547,14 +557,16 @@ var isNotNull = (left2) => ({
547
557
  left: toNode(left2),
548
558
  operator: "IS NOT NULL"
549
559
  });
550
- var createInExpression = (operator, left2, values) => ({
560
+ var createInExpression = (operator, left2, right2) => ({
551
561
  type: "InExpression",
552
562
  left: toNode(left2),
553
563
  operator,
554
- right: values.map((v) => toOperand(v))
564
+ right: right2
555
565
  });
556
- var inList = (left2, values) => createInExpression("IN", left2, values);
557
- var notInList = (left2, values) => createInExpression("NOT IN", left2, values);
566
+ var inList = (left2, values) => createInExpression("IN", left2, values.map((v) => toOperand(v)));
567
+ var notInList = (left2, values) => createInExpression("NOT IN", left2, values.map((v) => toOperand(v)));
568
+ var inSubquery = (left2, subquery) => createInExpression("IN", left2, toScalarSubqueryNode(subquery));
569
+ var notInSubquery = (left2, subquery) => createInExpression("NOT IN", left2, toScalarSubqueryNode(subquery));
558
570
  var createBetweenExpression = (operator, left2, lower2, upper2) => ({
559
571
  type: "BetweenExpression",
560
572
  left: toNode(left2),
@@ -1237,8 +1249,12 @@ var Dialect = class _Dialect {
1237
1249
  });
1238
1250
  this.registerExpressionCompiler("InExpression", (inExpr, ctx) => {
1239
1251
  const left2 = this.compileOperand(inExpr.left, ctx);
1240
- const values = inExpr.right.map((v) => this.compileOperand(v, ctx)).join(", ");
1241
- return `${left2} ${inExpr.operator} (${values})`;
1252
+ if (Array.isArray(inExpr.right)) {
1253
+ const values = inExpr.right.map((v) => this.compileOperand(v, ctx)).join(", ");
1254
+ return `${left2} ${inExpr.operator} (${values})`;
1255
+ }
1256
+ const subquerySql = this.compileSelectAst(inExpr.right.query, ctx).trim().replace(/;$/, "");
1257
+ return `${left2} ${inExpr.operator} (${subquerySql})`;
1242
1258
  });
1243
1259
  this.registerExpressionCompiler("ExistsExpression", (existsExpr, ctx) => {
1244
1260
  const subquerySql = this.compileSelectForExists(existsExpr.subquery, ctx);
@@ -1496,17 +1512,9 @@ var NoReturningStrategy = class {
1496
1512
 
1497
1513
  // src/core/dialect/base/join-compiler.ts
1498
1514
  var JoinCompiler = class {
1499
- /**
1500
- * Compiles all JOIN clauses from a SELECT query AST.
1501
- * @param ast - The SELECT query AST containing join definitions.
1502
- * @param ctx - The compiler context for expression compilation.
1503
- * @param compileFrom - Function to compile table sources (tables or subqueries).
1504
- * @param compileExpression - Function to compile join condition expressions.
1505
- * @returns SQL JOIN clauses (e.g., " LEFT JOIN table ON condition") or empty string if no joins.
1506
- */
1507
- static compileJoins(ast, ctx, compileFrom, compileExpression) {
1508
- if (!ast.joins || ast.joins.length === 0) return "";
1509
- const parts = ast.joins.map((j) => {
1515
+ static compileJoins(joins, ctx, compileFrom, compileExpression) {
1516
+ if (!joins || joins.length === 0) return "";
1517
+ const parts = joins.map((j) => {
1510
1518
  const table = compileFrom(j.table, ctx);
1511
1519
  const cond = compileExpression(j.condition, ctx);
1512
1520
  return `${j.kind} JOIN ${table} ON ${cond}`;
@@ -1589,25 +1597,41 @@ var SqlDialectBase = class extends Dialect {
1589
1597
  return `${ctes}${combined}${orderBy}${pagination}`;
1590
1598
  }
1591
1599
  compileInsertAst(ast, ctx) {
1600
+ if (!ast.columns.length) {
1601
+ throw new Error("INSERT queries must specify columns.");
1602
+ }
1592
1603
  const table = this.compileTableName(ast.into);
1593
1604
  const columnList = this.compileInsertColumnList(ast.columns);
1594
- const values = this.compileInsertValues(ast.values, ctx);
1605
+ const source = this.compileInsertSource(ast.source, ctx);
1595
1606
  const returning = this.compileReturning(ast.returning, ctx);
1596
- return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
1607
+ return `INSERT INTO ${table} (${columnList}) ${source}${returning}`;
1597
1608
  }
1598
1609
  compileReturning(returning, ctx) {
1599
1610
  return this.returningStrategy.compileReturning(returning, ctx);
1600
1611
  }
1612
+ compileInsertSource(source, ctx) {
1613
+ if (source.type === "InsertValues") {
1614
+ if (!source.rows.length) {
1615
+ throw new Error("INSERT ... VALUES requires at least one row.");
1616
+ }
1617
+ const values = source.rows.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
1618
+ return `VALUES ${values}`;
1619
+ }
1620
+ const normalized = this.normalizeSelectAst(source.query);
1621
+ return this.compileSelectAst(normalized, ctx).trim();
1622
+ }
1601
1623
  compileInsertColumnList(columns) {
1602
1624
  return columns.map((column) => this.quoteIdentifier(column.name)).join(", ");
1603
1625
  }
1604
- compileInsertValues(values, ctx) {
1605
- return values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
1606
- }
1607
1626
  compileSelectCore(ast, ctx) {
1608
1627
  const columns = this.compileSelectColumns(ast, ctx);
1609
1628
  const from = this.compileFrom(ast.from, ctx);
1610
- const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
1629
+ const joins = JoinCompiler.compileJoins(
1630
+ ast.joins,
1631
+ ctx,
1632
+ this.compileFrom.bind(this),
1633
+ this.compileExpression.bind(this)
1634
+ );
1611
1635
  const whereClause = this.compileWhere(ast.where, ctx);
1612
1636
  const groupBy = GroupByCompiler.compileGroupBy(ast, (term) => this.compileOrderingTerm(term, ctx));
1613
1637
  const having = this.compileHaving(ast, ctx);
@@ -1621,25 +1645,37 @@ var SqlDialectBase = class extends Dialect {
1621
1645
  return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
1622
1646
  }
1623
1647
  compileUpdateAst(ast, ctx) {
1624
- const table = this.compileTableName(ast.table);
1625
- const assignments = this.compileUpdateAssignments(ast.set, ctx);
1648
+ const target = this.compileTableReference(ast.table);
1649
+ const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
1650
+ const fromClause = this.compileUpdateFromClause(ast, ctx);
1626
1651
  const whereClause = this.compileWhere(ast.where, ctx);
1627
1652
  const returning = this.compileReturning(ast.returning, ctx);
1628
- return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
1653
+ return `UPDATE ${target} SET ${assignments}${fromClause}${whereClause}${returning}`;
1629
1654
  }
1630
- compileUpdateAssignments(assignments, ctx) {
1655
+ compileUpdateAssignments(assignments, table, ctx) {
1631
1656
  return assignments.map((assignment) => {
1632
1657
  const col2 = assignment.column;
1633
- const target = this.quoteIdentifier(col2.name);
1658
+ const target = this.compileQualifiedColumn(col2, table);
1634
1659
  const value = this.compileOperand(assignment.value, ctx);
1635
1660
  return `${target} = ${value}`;
1636
1661
  }).join(", ");
1637
1662
  }
1663
+ compileQualifiedColumn(column, table) {
1664
+ const baseTableName = table.name;
1665
+ const alias = table.alias;
1666
+ const columnTable = column.table ?? alias ?? baseTableName;
1667
+ const tableQualifier = alias && column.table === baseTableName ? alias : columnTable;
1668
+ if (!tableQualifier) {
1669
+ return this.quoteIdentifier(column.name);
1670
+ }
1671
+ return `${this.quoteIdentifier(tableQualifier)}.${this.quoteIdentifier(column.name)}`;
1672
+ }
1638
1673
  compileDeleteAst(ast, ctx) {
1639
- const table = this.compileTableName(ast.from);
1674
+ const target = this.compileTableReference(ast.from);
1675
+ const usingClause = this.compileDeleteUsingClause(ast, ctx);
1640
1676
  const whereClause = this.compileWhere(ast.where, ctx);
1641
1677
  const returning = this.compileReturning(ast.returning, ctx);
1642
- return `DELETE FROM ${table}${whereClause}${returning}`;
1678
+ return `DELETE FROM ${target}${usingClause}${whereClause}${returning}`;
1643
1679
  }
1644
1680
  formatReturningColumns(returning) {
1645
1681
  return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
@@ -1694,6 +1730,38 @@ var SqlDialectBase = class extends Dialect {
1694
1730
  }
1695
1731
  return this.quoteIdentifier(table.name);
1696
1732
  }
1733
+ compileTableReference(table) {
1734
+ const base = this.compileTableName(table);
1735
+ return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
1736
+ }
1737
+ compileUpdateFromClause(ast, ctx) {
1738
+ if (!ast.from && (!ast.joins || ast.joins.length === 0)) return "";
1739
+ if (!ast.from) {
1740
+ throw new Error("UPDATE with JOINs requires an explicit FROM clause.");
1741
+ }
1742
+ const from = this.compileFrom(ast.from, ctx);
1743
+ const joins = JoinCompiler.compileJoins(
1744
+ ast.joins,
1745
+ ctx,
1746
+ this.compileFrom.bind(this),
1747
+ this.compileExpression.bind(this)
1748
+ );
1749
+ return ` FROM ${from}${joins}`;
1750
+ }
1751
+ compileDeleteUsingClause(ast, ctx) {
1752
+ if (!ast.using && (!ast.joins || ast.joins.length === 0)) return "";
1753
+ if (!ast.using) {
1754
+ throw new Error("DELETE with JOINs requires a USING clause.");
1755
+ }
1756
+ const usingTable = this.compileFrom(ast.using, ctx);
1757
+ const joins = JoinCompiler.compileJoins(
1758
+ ast.joins,
1759
+ ctx,
1760
+ this.compileFrom.bind(this),
1761
+ this.compileExpression.bind(this)
1762
+ );
1763
+ return ` USING ${usingTable}${joins}`;
1764
+ }
1697
1765
  compileHaving(ast, ctx) {
1698
1766
  if (!ast.having) return "";
1699
1767
  return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
@@ -2074,6 +2142,9 @@ var SqliteDialect = class extends SqlDialectBase {
2074
2142
  const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
2075
2143
  return `json_extract(${col2}, '${node.path}')`;
2076
2144
  }
2145
+ compileQualifiedColumn(column, _table) {
2146
+ return this.quoteIdentifier(column.name);
2147
+ }
2077
2148
  compileReturning(returning, ctx) {
2078
2149
  if (!returning || returning.length === 0) return "";
2079
2150
  const columns = this.formatReturningColumns(returning);
@@ -2179,7 +2250,7 @@ var MssqlFunctionStrategy = class extends StandardFunctionStrategy {
2179
2250
  };
2180
2251
 
2181
2252
  // src/core/dialect/mssql/index.ts
2182
- var SqlServerDialect = class extends Dialect {
2253
+ var SqlServerDialect = class extends SqlDialectBase {
2183
2254
  /**
2184
2255
  * Creates a new SqlServerDialect instance
2185
2256
  */
@@ -2222,7 +2293,7 @@ var SqlServerDialect = class extends Dialect {
2222
2293
  const hasSetOps = !!(ast.setOps && ast.setOps.length);
2223
2294
  const ctes = this.compileCtes(ast, ctx);
2224
2295
  const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
2225
- const baseSelect = this.compileSelectCore(baseAst, ctx);
2296
+ const baseSelect = this.compileSelectCoreForMssql(baseAst, ctx);
2226
2297
  if (!hasSetOps) {
2227
2298
  return `${ctes}${baseSelect}`;
2228
2299
  }
@@ -2233,32 +2304,26 @@ var SqlServerDialect = class extends Dialect {
2233
2304
  const tail = pagination || orderBy;
2234
2305
  return `${ctes}${combined}${tail}`;
2235
2306
  }
2236
- compileInsertAst(ast, ctx) {
2237
- const table = this.quoteIdentifier(ast.into.name);
2238
- const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
2239
- const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
2240
- return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
2241
- }
2242
- compileUpdateAst(ast, ctx) {
2243
- const table = this.quoteIdentifier(ast.table.name);
2244
- const assignments = ast.set.map((assignment) => {
2245
- const col2 = assignment.column;
2246
- const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
2247
- const value = this.compileOperand(assignment.value, ctx);
2248
- return `${target} = ${value}`;
2249
- }).join(", ");
2250
- const whereClause = this.compileWhere(ast.where, ctx);
2251
- return `UPDATE ${table} SET ${assignments}${whereClause};`;
2252
- }
2253
2307
  compileDeleteAst(ast, ctx) {
2308
+ if (ast.using) {
2309
+ throw new Error("DELETE ... USING is not supported in the MSSQL dialect; use join() instead.");
2310
+ }
2254
2311
  if (ast.from.type !== "Table") {
2255
2312
  throw new Error("DELETE only supports base tables in the MSSQL dialect.");
2256
2313
  }
2257
- const table = this.quoteIdentifier(ast.from.name);
2314
+ const alias = ast.from.alias ?? ast.from.name;
2315
+ const target = this.compileTableReference(ast.from);
2316
+ const joins = JoinCompiler.compileJoins(
2317
+ ast.joins,
2318
+ ctx,
2319
+ this.compileFrom.bind(this),
2320
+ this.compileExpression.bind(this)
2321
+ );
2258
2322
  const whereClause = this.compileWhere(ast.where, ctx);
2259
- return `DELETE FROM ${table}${whereClause};`;
2323
+ const returning = this.compileReturning(ast.returning, ctx);
2324
+ return `DELETE ${this.quoteIdentifier(alias)} FROM ${target}${joins}${whereClause}${returning}`;
2260
2325
  }
2261
- compileSelectCore(ast, ctx) {
2326
+ compileSelectCoreForMssql(ast, ctx) {
2262
2327
  const columns = ast.columns.map((c) => {
2263
2328
  let expr = "";
2264
2329
  if (c.type === "Function") {
@@ -2277,9 +2342,9 @@ var SqlServerDialect = class extends Dialect {
2277
2342
  return expr;
2278
2343
  }).join(", ");
2279
2344
  const distinct = ast.distinct ? "DISTINCT " : "";
2280
- const from = this.compileTableSource(ast.from, ctx);
2345
+ const from = this.compileTableSource(ast.from);
2281
2346
  const joins = ast.joins.map((j) => {
2282
- const table = this.compileTableSource(j.table, ctx);
2347
+ const table = this.compileTableSource(j.table);
2283
2348
  const cond = this.compileExpression(j.condition, ctx);
2284
2349
  return `${j.kind} JOIN ${table} ON ${cond}`;
2285
2350
  }).join(" ");
@@ -2313,27 +2378,6 @@ var SqlServerDialect = class extends Dialect {
2313
2378
  }
2314
2379
  return pagination;
2315
2380
  }
2316
- renderOrderByNulls(order) {
2317
- return order.nulls ? ` NULLS ${order.nulls}` : "";
2318
- }
2319
- renderOrderByCollation(order) {
2320
- return order.collation ? ` COLLATE ${order.collation}` : "";
2321
- }
2322
- compileTableSource(table, ctx) {
2323
- if (table.type === "FunctionTable") {
2324
- return FunctionTableFormatter.format(table, ctx, this);
2325
- }
2326
- if (table.type === "DerivedTable") {
2327
- return this.compileDerivedTable(table, ctx);
2328
- }
2329
- const base = table.schema ? `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}` : this.quoteIdentifier(table.name);
2330
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
2331
- }
2332
- compileDerivedTable(table, ctx) {
2333
- const sub2 = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx).trim().replace(/;$/, "");
2334
- const cols = table.columnAliases?.length ? ` (${table.columnAliases.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
2335
- return `(${sub2}) AS ${this.quoteIdentifier(table.alias)}${cols}`;
2336
- }
2337
2381
  compileCtes(ast, ctx) {
2338
2382
  if (!ast.ctes || ast.ctes.length === 0) return "";
2339
2383
  const defs = ast.ctes.map((cte) => {
@@ -2344,10 +2388,6 @@ var SqlServerDialect = class extends Dialect {
2344
2388
  }).join(", ");
2345
2389
  return `WITH ${defs} `;
2346
2390
  }
2347
- wrapSetOperand(sql) {
2348
- const trimmed = sql.trim().replace(/;$/, "");
2349
- return `(${trimmed})`;
2350
- }
2351
2391
  };
2352
2392
 
2353
2393
  // src/core/dialect/dialect-factory.ts
@@ -4142,8 +4182,10 @@ var DefaultManyToManyCollection = class {
4142
4182
  attach(target) {
4143
4183
  const entity = this.ensureEntity(target);
4144
4184
  const id = this.extractId(entity);
4145
- if (id == null) return;
4146
- if (this.items.some((item) => this.extractId(item) === id)) {
4185
+ if (id != null && this.items.some((item) => this.extractId(item) === id)) {
4186
+ return;
4187
+ }
4188
+ if (id == null && this.items.includes(entity)) {
4147
4189
  return;
4148
4190
  }
4149
4191
  this.items.push(entity);
@@ -5466,15 +5508,36 @@ var InsertQueryState = class _InsertQueryState {
5466
5508
  type: "InsertQuery",
5467
5509
  into: createTableNode(table),
5468
5510
  columns: [],
5469
- values: []
5511
+ source: {
5512
+ type: "InsertValues",
5513
+ rows: []
5514
+ }
5470
5515
  };
5471
5516
  }
5472
5517
  clone(nextAst) {
5473
5518
  return new _InsertQueryState(this.table, nextAst);
5474
5519
  }
5520
+ ensureColumnsFromRow(rows) {
5521
+ if (this.ast.columns.length) return this.ast.columns;
5522
+ return buildColumnNodes(this.table, Object.keys(rows[0]));
5523
+ }
5524
+ appendValues(rows) {
5525
+ if (this.ast.source.type === "InsertValues") {
5526
+ return [...this.ast.source.rows, ...rows];
5527
+ }
5528
+ return rows;
5529
+ }
5530
+ getTableColumns() {
5531
+ const names = Object.keys(this.table.columns);
5532
+ if (!names.length) return [];
5533
+ return buildColumnNodes(this.table, names);
5534
+ }
5475
5535
  withValues(rows) {
5476
5536
  if (!rows.length) return this;
5477
- const definedColumns = this.ast.columns.length ? this.ast.columns : buildColumnNodes(this.table, Object.keys(rows[0]));
5537
+ if (this.ast.source.type === "InsertSelect") {
5538
+ throw new Error("Cannot mix INSERT ... VALUES with INSERT ... SELECT source.");
5539
+ }
5540
+ const definedColumns = this.ensureColumnsFromRow(rows);
5478
5541
  const newRows = rows.map(
5479
5542
  (row, rowIndex) => definedColumns.map((column) => {
5480
5543
  const rawValue = row[column.name];
@@ -5489,7 +5552,34 @@ var InsertQueryState = class _InsertQueryState {
5489
5552
  return this.clone({
5490
5553
  ...this.ast,
5491
5554
  columns: definedColumns,
5492
- values: [...this.ast.values, ...newRows]
5555
+ source: {
5556
+ type: "InsertValues",
5557
+ rows: this.appendValues(newRows)
5558
+ }
5559
+ });
5560
+ }
5561
+ withColumns(columns) {
5562
+ if (!columns.length) return this;
5563
+ return this.clone({
5564
+ ...this.ast,
5565
+ columns: [...columns]
5566
+ });
5567
+ }
5568
+ withSelect(query, columns) {
5569
+ const targetColumns = columns.length ? columns : this.ast.columns.length ? this.ast.columns : this.getTableColumns();
5570
+ if (!targetColumns.length) {
5571
+ throw new Error("INSERT ... SELECT requires specifying destination columns.");
5572
+ }
5573
+ if (this.ast.source.type === "InsertValues" && this.ast.source.rows.length) {
5574
+ throw new Error("Cannot mix INSERT ... SELECT with INSERT ... VALUES source.");
5575
+ }
5576
+ return this.clone({
5577
+ ...this.ast,
5578
+ columns: [...targetColumns],
5579
+ source: {
5580
+ type: "InsertSelect",
5581
+ query
5582
+ }
5493
5583
  });
5494
5584
  }
5495
5585
  withReturning(columns) {
@@ -5514,11 +5604,27 @@ var InsertQueryBuilder = class _InsertQueryBuilder {
5514
5604
  if (!rows.length) return this;
5515
5605
  return this.clone(this.state.withValues(rows));
5516
5606
  }
5607
+ columns(...columns) {
5608
+ if (!columns.length) return this;
5609
+ return this.clone(this.state.withColumns(this.resolveColumnNodes(columns)));
5610
+ }
5611
+ fromSelect(query, columns = []) {
5612
+ const ast = this.resolveSelectQuery(query);
5613
+ const nodes = columns.length ? this.resolveColumnNodes(columns) : [];
5614
+ return this.clone(this.state.withSelect(ast, nodes));
5615
+ }
5517
5616
  returning(...columns) {
5518
5617
  if (!columns.length) return this;
5519
5618
  const nodes = columns.map((column) => buildColumnNode(this.table, column));
5520
5619
  return this.clone(this.state.withReturning(nodes));
5521
5620
  }
5621
+ // Helpers for column/AST resolution
5622
+ resolveColumnNodes(columns) {
5623
+ return columns.map((column) => buildColumnNode(this.table, column));
5624
+ }
5625
+ resolveSelectQuery(query) {
5626
+ return typeof query.getAST === "function" ? query.getAST() : query;
5627
+ }
5522
5628
  compile(arg) {
5523
5629
  if (typeof arg.compileInsert === "function") {
5524
5630
  return arg.compileInsert(this.state.ast);
@@ -5552,7 +5658,8 @@ var UpdateQueryState = class _UpdateQueryState {
5552
5658
  this.ast = ast ?? {
5553
5659
  type: "UpdateQuery",
5554
5660
  table: createTableNode(table),
5555
- set: []
5661
+ set: [],
5662
+ joins: []
5556
5663
  };
5557
5664
  }
5558
5665
  clone(nextAst) {
@@ -5591,6 +5698,27 @@ var UpdateQueryState = class _UpdateQueryState {
5591
5698
  returning: [...columns]
5592
5699
  });
5593
5700
  }
5701
+ withFrom(from) {
5702
+ return this.clone({
5703
+ ...this.ast,
5704
+ from
5705
+ });
5706
+ }
5707
+ withJoin(join) {
5708
+ return this.clone({
5709
+ ...this.ast,
5710
+ joins: [...this.ast.joins ?? [], join]
5711
+ });
5712
+ }
5713
+ withTableAlias(alias) {
5714
+ return this.clone({
5715
+ ...this.ast,
5716
+ table: {
5717
+ ...this.ast.table,
5718
+ alias
5719
+ }
5720
+ });
5721
+ }
5594
5722
  };
5595
5723
 
5596
5724
  // src/query-builder/update.ts
@@ -5602,6 +5730,18 @@ var UpdateQueryBuilder = class _UpdateQueryBuilder {
5602
5730
  clone(state) {
5603
5731
  return new _UpdateQueryBuilder(this.table, state);
5604
5732
  }
5733
+ as(alias) {
5734
+ return this.clone(this.state.withTableAlias(alias));
5735
+ }
5736
+ from(source) {
5737
+ const tableSource = this.resolveTableSource(source);
5738
+ return this.clone(this.state.withFrom(tableSource));
5739
+ }
5740
+ join(table, condition, kind = JOIN_KINDS.INNER, relationName) {
5741
+ const joinTarget = this.resolveJoinTarget(table);
5742
+ const joinNode = createJoinNode(kind, joinTarget, condition, relationName);
5743
+ return this.clone(this.state.withJoin(joinNode));
5744
+ }
5605
5745
  set(values) {
5606
5746
  return this.clone(this.state.withSet(values));
5607
5747
  }
@@ -5613,6 +5753,16 @@ var UpdateQueryBuilder = class _UpdateQueryBuilder {
5613
5753
  const nodes = columns.map((column) => buildColumnNode(this.table, column));
5614
5754
  return this.clone(this.state.withReturning(nodes));
5615
5755
  }
5756
+ resolveTableSource(source) {
5757
+ if (isTableSourceNode(source)) {
5758
+ return source;
5759
+ }
5760
+ return { type: "Table", name: source.name, schema: source.schema };
5761
+ }
5762
+ resolveJoinTarget(table) {
5763
+ if (typeof table === "string") return table;
5764
+ return this.resolveTableSource(table);
5765
+ }
5616
5766
  compile(arg) {
5617
5767
  if (typeof arg.compileUpdate === "function") {
5618
5768
  return arg.compileUpdate(this.state.ast);
@@ -5627,6 +5777,7 @@ var UpdateQueryBuilder = class _UpdateQueryBuilder {
5627
5777
  return this.state.ast;
5628
5778
  }
5629
5779
  };
5780
+ var isTableSourceNode = (source) => typeof source.type === "string";
5630
5781
 
5631
5782
  // src/query-builder/delete-query-state.ts
5632
5783
  var DeleteQueryState = class _DeleteQueryState {
@@ -5634,7 +5785,8 @@ var DeleteQueryState = class _DeleteQueryState {
5634
5785
  this.table = table;
5635
5786
  this.ast = ast ?? {
5636
5787
  type: "DeleteQuery",
5637
- from: createTableNode(table)
5788
+ from: createTableNode(table),
5789
+ joins: []
5638
5790
  };
5639
5791
  }
5640
5792
  clone(nextAst) {
@@ -5652,6 +5804,27 @@ var DeleteQueryState = class _DeleteQueryState {
5652
5804
  returning: [...columns]
5653
5805
  });
5654
5806
  }
5807
+ withUsing(source) {
5808
+ return this.clone({
5809
+ ...this.ast,
5810
+ using: source
5811
+ });
5812
+ }
5813
+ withJoin(join) {
5814
+ return this.clone({
5815
+ ...this.ast,
5816
+ joins: [...this.ast.joins ?? [], join]
5817
+ });
5818
+ }
5819
+ withTableAlias(alias) {
5820
+ return this.clone({
5821
+ ...this.ast,
5822
+ from: {
5823
+ ...this.ast.from,
5824
+ alias
5825
+ }
5826
+ });
5827
+ }
5655
5828
  };
5656
5829
 
5657
5830
  // src/query-builder/delete.ts
@@ -5666,11 +5839,32 @@ var DeleteQueryBuilder = class _DeleteQueryBuilder {
5666
5839
  where(expr) {
5667
5840
  return this.clone(this.state.withWhere(expr));
5668
5841
  }
5842
+ as(alias) {
5843
+ return this.clone(this.state.withTableAlias(alias));
5844
+ }
5845
+ using(source) {
5846
+ return this.clone(this.state.withUsing(this.resolveTableSource(source)));
5847
+ }
5848
+ join(table, condition, kind = JOIN_KINDS.INNER, relationName) {
5849
+ const target = this.resolveJoinTarget(table);
5850
+ const joinNode = createJoinNode(kind, target, condition, relationName);
5851
+ return this.clone(this.state.withJoin(joinNode));
5852
+ }
5669
5853
  returning(...columns) {
5670
5854
  if (!columns.length) return this;
5671
5855
  const nodes = columns.map((column) => buildColumnNode(this.table, column));
5672
5856
  return this.clone(this.state.withReturning(nodes));
5673
5857
  }
5858
+ resolveTableSource(source) {
5859
+ if (isTableSourceNode2(source)) {
5860
+ return source;
5861
+ }
5862
+ return { type: "Table", name: source.name, schema: source.schema };
5863
+ }
5864
+ resolveJoinTarget(table) {
5865
+ if (typeof table === "string") return table;
5866
+ return this.resolveTableSource(table);
5867
+ }
5674
5868
  compile(arg) {
5675
5869
  if (typeof arg.compileDelete === "function") {
5676
5870
  return arg.compileDelete(this.state.ast);
@@ -5685,6 +5879,7 @@ var DeleteQueryBuilder = class _DeleteQueryBuilder {
5685
5879
  return this.state.ast;
5686
5880
  }
5687
5881
  };
5882
+ var isTableSourceNode2 = (source) => typeof source.type === "string";
5688
5883
 
5689
5884
  // src/core/ddl/sql-writing.ts
5690
5885
  var resolvePrimaryKey = (table) => {
@@ -7033,9 +7228,13 @@ var TypeScriptGenerator = class {
7033
7228
  */
7034
7229
  printInExpression(inExpr) {
7035
7230
  const left2 = this.printOperand(inExpr.left);
7036
- const values = inExpr.right.map((v) => this.printOperand(v)).join(", ");
7037
7231
  const fn4 = this.mapOp(inExpr.operator);
7038
- return `${fn4}(${left2}, [${values}])`;
7232
+ if (Array.isArray(inExpr.right)) {
7233
+ const values = inExpr.right.map((v) => this.printOperand(v)).join(", ");
7234
+ return `${fn4}(${left2}, [${values}])`;
7235
+ }
7236
+ const subquery = this.inlineChain(this.buildSelectLines(inExpr.right.query));
7237
+ return `${fn4}(${left2}, (${subquery}))`;
7039
7238
  }
7040
7239
  /**
7041
7240
  * Prints a null expression to TypeScript code
@@ -7220,6 +7419,13 @@ var EntityStatus = /* @__PURE__ */ ((EntityStatus2) => {
7220
7419
 
7221
7420
  // src/orm/unit-of-work.ts
7222
7421
  var UnitOfWork = class {
7422
+ /**
7423
+ * Creates a new UnitOfWork instance.
7424
+ * @param dialect - The database dialect
7425
+ * @param executor - The database executor
7426
+ * @param identityMap - The identity map
7427
+ * @param hookContext - Function to get the hook context
7428
+ */
7223
7429
  constructor(dialect, executor, identityMap, hookContext) {
7224
7430
  this.dialect = dialect;
7225
7431
  this.executor = executor;
@@ -7227,21 +7433,50 @@ var UnitOfWork = class {
7227
7433
  this.hookContext = hookContext;
7228
7434
  this.trackedEntities = /* @__PURE__ */ new Map();
7229
7435
  }
7436
+ /**
7437
+ * Gets the identity buckets map.
7438
+ */
7230
7439
  get identityBuckets() {
7231
7440
  return this.identityMap.bucketsMap;
7232
7441
  }
7442
+ /**
7443
+ * Gets all tracked entities.
7444
+ * @returns Array of tracked entities
7445
+ */
7233
7446
  getTracked() {
7234
7447
  return Array.from(this.trackedEntities.values());
7235
7448
  }
7449
+ /**
7450
+ * Gets an entity by table and primary key.
7451
+ * @param table - The table definition
7452
+ * @param pk - The primary key value
7453
+ * @returns The entity or undefined if not found
7454
+ */
7236
7455
  getEntity(table, pk) {
7237
7456
  return this.identityMap.getEntity(table, pk);
7238
7457
  }
7458
+ /**
7459
+ * Gets all tracked entities for a specific table.
7460
+ * @param table - The table definition
7461
+ * @returns Array of tracked entities
7462
+ */
7239
7463
  getEntitiesForTable(table) {
7240
7464
  return this.identityMap.getEntitiesForTable(table);
7241
7465
  }
7466
+ /**
7467
+ * Finds a tracked entity.
7468
+ * @param entity - The entity to find
7469
+ * @returns The tracked entity or undefined if not found
7470
+ */
7242
7471
  findTracked(entity) {
7243
7472
  return this.trackedEntities.get(entity);
7244
7473
  }
7474
+ /**
7475
+ * Sets an entity in the identity map.
7476
+ * @param table - The table definition
7477
+ * @param pk - The primary key value
7478
+ * @param entity - The entity instance
7479
+ */
7245
7480
  setEntity(table, pk, entity) {
7246
7481
  if (pk === null || pk === void 0) return;
7247
7482
  let tracked = this.trackedEntities.get(entity);
@@ -7259,6 +7494,12 @@ var UnitOfWork = class {
7259
7494
  }
7260
7495
  this.registerIdentity(tracked);
7261
7496
  }
7497
+ /**
7498
+ * Tracks a new entity.
7499
+ * @param table - The table definition
7500
+ * @param entity - The entity instance
7501
+ * @param pk - Optional primary key value
7502
+ */
7262
7503
  trackNew(table, entity, pk) {
7263
7504
  const tracked = {
7264
7505
  table,
@@ -7272,6 +7513,12 @@ var UnitOfWork = class {
7272
7513
  this.registerIdentity(tracked);
7273
7514
  }
7274
7515
  }
7516
+ /**
7517
+ * Tracks a managed entity.
7518
+ * @param table - The table definition
7519
+ * @param pk - The primary key value
7520
+ * @param entity - The entity instance
7521
+ */
7275
7522
  trackManaged(table, pk, entity) {
7276
7523
  const tracked = {
7277
7524
  table,
@@ -7283,17 +7530,28 @@ var UnitOfWork = class {
7283
7530
  this.trackedEntities.set(entity, tracked);
7284
7531
  this.registerIdentity(tracked);
7285
7532
  }
7533
+ /**
7534
+ * Marks an entity as dirty (modified).
7535
+ * @param entity - The entity to mark as dirty
7536
+ */
7286
7537
  markDirty(entity) {
7287
7538
  const tracked = this.trackedEntities.get(entity);
7288
7539
  if (!tracked) return;
7289
7540
  if (tracked.status === "new" /* New */ || tracked.status === "removed" /* Removed */) return;
7290
7541
  tracked.status = "dirty" /* Dirty */;
7291
7542
  }
7543
+ /**
7544
+ * Marks an entity as removed.
7545
+ * @param entity - The entity to mark as removed
7546
+ */
7292
7547
  markRemoved(entity) {
7293
7548
  const tracked = this.trackedEntities.get(entity);
7294
7549
  if (!tracked) return;
7295
7550
  tracked.status = "removed" /* Removed */;
7296
7551
  }
7552
+ /**
7553
+ * Flushes pending changes to the database.
7554
+ */
7297
7555
  async flush() {
7298
7556
  const toFlush = Array.from(this.trackedEntities.values());
7299
7557
  for (const tracked of toFlush) {
@@ -7312,10 +7570,17 @@ var UnitOfWork = class {
7312
7570
  }
7313
7571
  }
7314
7572
  }
7573
+ /**
7574
+ * Resets the unit of work by clearing all tracked entities and identity map.
7575
+ */
7315
7576
  reset() {
7316
7577
  this.trackedEntities.clear();
7317
7578
  this.identityMap.clear();
7318
7579
  }
7580
+ /**
7581
+ * Flushes an insert operation for a new entity.
7582
+ * @param tracked - The tracked entity to insert
7583
+ */
7319
7584
  async flushInsert(tracked) {
7320
7585
  await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
7321
7586
  const payload = this.extractColumns(tracked.table, tracked.entity);
@@ -7332,6 +7597,10 @@ var UnitOfWork = class {
7332
7597
  this.registerIdentity(tracked);
7333
7598
  await this.runHook(tracked.table.hooks?.afterInsert, tracked);
7334
7599
  }
7600
+ /**
7601
+ * Flushes an update operation for a modified entity.
7602
+ * @param tracked - The tracked entity to update
7603
+ */
7335
7604
  async flushUpdate(tracked) {
7336
7605
  if (tracked.pk == null) return;
7337
7606
  const changes = this.computeChanges(tracked);
@@ -7354,6 +7623,10 @@ var UnitOfWork = class {
7354
7623
  this.registerIdentity(tracked);
7355
7624
  await this.runHook(tracked.table.hooks?.afterUpdate, tracked);
7356
7625
  }
7626
+ /**
7627
+ * Flushes a delete operation for a removed entity.
7628
+ * @param tracked - The tracked entity to delete
7629
+ */
7357
7630
  async flushDelete(tracked) {
7358
7631
  if (tracked.pk == null) return;
7359
7632
  await this.runHook(tracked.table.hooks?.beforeDelete, tracked);
@@ -7367,10 +7640,20 @@ var UnitOfWork = class {
7367
7640
  this.identityMap.remove(tracked);
7368
7641
  await this.runHook(tracked.table.hooks?.afterDelete, tracked);
7369
7642
  }
7643
+ /**
7644
+ * Runs a table hook if defined.
7645
+ * @param hook - The hook function
7646
+ * @param tracked - The tracked entity
7647
+ */
7370
7648
  async runHook(hook, tracked) {
7371
7649
  if (!hook) return;
7372
7650
  await hook(this.hookContext(), tracked.entity);
7373
7651
  }
7652
+ /**
7653
+ * Computes changes between current entity state and original snapshot.
7654
+ * @param tracked - The tracked entity
7655
+ * @returns Object with changed column values
7656
+ */
7374
7657
  computeChanges(tracked) {
7375
7658
  const snapshot = tracked.original ?? {};
7376
7659
  const changes = {};
@@ -7382,6 +7665,12 @@ var UnitOfWork = class {
7382
7665
  }
7383
7666
  return changes;
7384
7667
  }
7668
+ /**
7669
+ * Extracts column values from an entity.
7670
+ * @param table - The table definition
7671
+ * @param entity - The entity instance
7672
+ * @returns Object with column values
7673
+ */
7385
7674
  extractColumns(table, entity) {
7386
7675
  const payload = {};
7387
7676
  for (const column of Object.keys(table.columns)) {
@@ -7390,9 +7679,19 @@ var UnitOfWork = class {
7390
7679
  }
7391
7680
  return payload;
7392
7681
  }
7682
+ /**
7683
+ * Executes a compiled query.
7684
+ * @param compiled - The compiled query
7685
+ * @returns Query results
7686
+ */
7393
7687
  async executeCompiled(compiled) {
7394
7688
  return this.executor.executeSql(compiled.sql, compiled.params);
7395
7689
  }
7690
+ /**
7691
+ * Gets columns for RETURNING clause.
7692
+ * @param table - The table definition
7693
+ * @returns Array of column nodes
7694
+ */
7396
7695
  getReturningColumns(table) {
7397
7696
  return Object.values(table.columns).map((column) => ({
7398
7697
  type: "Column",
@@ -7401,6 +7700,11 @@ var UnitOfWork = class {
7401
7700
  alias: column.name
7402
7701
  }));
7403
7702
  }
7703
+ /**
7704
+ * Applies RETURNING clause results to the tracked entity.
7705
+ * @param tracked - The tracked entity
7706
+ * @param results - Query results
7707
+ */
7404
7708
  applyReturningResults(tracked, results) {
7405
7709
  if (!this.dialect.supportsReturning()) return;
7406
7710
  const first = results[0];
@@ -7412,15 +7716,30 @@ var UnitOfWork = class {
7412
7716
  tracked.entity[columnName] = row[i];
7413
7717
  }
7414
7718
  }
7719
+ /**
7720
+ * Normalizes a column name by removing quotes and table prefixes.
7721
+ * @param column - The column name to normalize
7722
+ * @returns Normalized column name
7723
+ */
7415
7724
  normalizeColumnName(column) {
7416
7725
  const parts = column.split(".");
7417
7726
  const candidate = parts[parts.length - 1];
7418
7727
  return candidate.replace(/^["`[\]]+|["`[\]]+$/g, "");
7419
7728
  }
7729
+ /**
7730
+ * Registers an entity in the identity map.
7731
+ * @param tracked - The tracked entity to register
7732
+ */
7420
7733
  registerIdentity(tracked) {
7421
7734
  if (tracked.pk == null) return;
7422
7735
  this.identityMap.register(tracked);
7423
7736
  }
7737
+ /**
7738
+ * Creates a snapshot of an entity's current state.
7739
+ * @param table - The table definition
7740
+ * @param entity - The entity instance
7741
+ * @returns Object with entity state
7742
+ */
7424
7743
  createSnapshot(table, entity) {
7425
7744
  const snapshot = {};
7426
7745
  for (const column of Object.keys(table.columns)) {
@@ -7428,6 +7747,11 @@ var UnitOfWork = class {
7428
7747
  }
7429
7748
  return snapshot;
7430
7749
  }
7750
+ /**
7751
+ * Gets the primary key value from a tracked entity.
7752
+ * @param tracked - The tracked entity
7753
+ * @returns Primary key value or null
7754
+ */
7431
7755
  getPrimaryKeyValue(tracked) {
7432
7756
  const key = findPrimaryKey(tracked.table);
7433
7757
  const val = tracked.entity[key];
@@ -7438,6 +7762,10 @@ var UnitOfWork = class {
7438
7762
 
7439
7763
  // src/orm/domain-event-bus.ts
7440
7764
  var DomainEventBus = class {
7765
+ /**
7766
+ * Creates a new DomainEventBus instance.
7767
+ * @param initialHandlers - Optional initial event handlers
7768
+ */
7441
7769
  constructor(initialHandlers) {
7442
7770
  this.handlers = /* @__PURE__ */ new Map();
7443
7771
  if (initialHandlers) {
@@ -7448,15 +7776,32 @@ var DomainEventBus = class {
7448
7776
  }
7449
7777
  }
7450
7778
  }
7779
+ /**
7780
+ * Registers an event handler for a specific event type.
7781
+ * @template TType - The event type
7782
+ * @param type - The event type
7783
+ * @param handler - The event handler
7784
+ */
7451
7785
  on(type, handler) {
7452
7786
  const key = type;
7453
7787
  const existing = this.handlers.get(key) ?? [];
7454
7788
  existing.push(handler);
7455
7789
  this.handlers.set(key, existing);
7456
7790
  }
7791
+ /**
7792
+ * Registers an event handler for a specific event type (alias for on).
7793
+ * @template TType - The event type
7794
+ * @param type - The event type
7795
+ * @param handler - The event handler
7796
+ */
7457
7797
  register(type, handler) {
7458
7798
  this.on(type, handler);
7459
7799
  }
7800
+ /**
7801
+ * Dispatches domain events for tracked entities.
7802
+ * @param trackedEntities - Iterable of tracked entities
7803
+ * @param ctx - The context to pass to handlers
7804
+ */
7460
7805
  async dispatch(trackedEntities, ctx) {
7461
7806
  for (const tracked of trackedEntities) {
7462
7807
  const entity = tracked.entity;
@@ -7481,18 +7826,34 @@ var addDomainEvent = (entity, event) => {
7481
7826
 
7482
7827
  // src/orm/relation-change-processor.ts
7483
7828
  var RelationChangeProcessor = class {
7829
+ /**
7830
+ * Creates a new RelationChangeProcessor instance.
7831
+ * @param unitOfWork - The unit of work instance
7832
+ * @param dialect - The database dialect
7833
+ * @param executor - The database executor
7834
+ */
7484
7835
  constructor(unitOfWork, dialect, executor) {
7485
7836
  this.unitOfWork = unitOfWork;
7486
7837
  this.dialect = dialect;
7487
7838
  this.executor = executor;
7488
7839
  this.relationChanges = [];
7489
7840
  }
7841
+ /**
7842
+ * Registers a relation change for processing.
7843
+ * @param entry - The relation change entry
7844
+ */
7490
7845
  registerChange(entry) {
7491
7846
  this.relationChanges.push(entry);
7492
7847
  }
7848
+ /**
7849
+ * Resets the relation change processor by clearing all pending changes.
7850
+ */
7493
7851
  reset() {
7494
7852
  this.relationChanges.length = 0;
7495
7853
  }
7854
+ /**
7855
+ * Processes all pending relation changes.
7856
+ */
7496
7857
  async process() {
7497
7858
  if (!this.relationChanges.length) return;
7498
7859
  const entries = [...this.relationChanges];
@@ -7514,6 +7875,10 @@ var RelationChangeProcessor = class {
7514
7875
  }
7515
7876
  }
7516
7877
  }
7878
+ /**
7879
+ * Handles changes for has-many relations.
7880
+ * @param entry - The relation change entry
7881
+ */
7517
7882
  async handleHasManyChange(entry) {
7518
7883
  const relation = entry.relation;
7519
7884
  const target = entry.change.entity;
@@ -7532,6 +7897,10 @@ var RelationChangeProcessor = class {
7532
7897
  this.detachHasManyChild(tracked.entity, relation);
7533
7898
  }
7534
7899
  }
7900
+ /**
7901
+ * Handles changes for has-one relations.
7902
+ * @param entry - The relation change entry
7903
+ */
7535
7904
  async handleHasOneChange(entry) {
7536
7905
  const relation = entry.relation;
7537
7906
  const target = entry.change.entity;
@@ -7550,8 +7919,16 @@ var RelationChangeProcessor = class {
7550
7919
  this.detachHasOneChild(tracked.entity, relation);
7551
7920
  }
7552
7921
  }
7922
+ /**
7923
+ * Handles changes for belongs-to relations.
7924
+ * @param _entry - The relation change entry (reserved for future use)
7925
+ */
7553
7926
  async handleBelongsToChange(_entry) {
7554
7927
  }
7928
+ /**
7929
+ * Handles changes for belongs-to-many relations.
7930
+ * @param entry - The relation change entry
7931
+ */
7555
7932
  async handleBelongsToManyChange(entry) {
7556
7933
  const relation = entry.relation;
7557
7934
  const rootKey = relation.localKey || findPrimaryKey(entry.rootTable);
@@ -7570,11 +7947,22 @@ var RelationChangeProcessor = class {
7570
7947
  }
7571
7948
  }
7572
7949
  }
7950
+ /**
7951
+ * Assigns a foreign key for has-many relations.
7952
+ * @param child - The child entity
7953
+ * @param relation - The has-many relation
7954
+ * @param rootValue - The root entity's primary key value
7955
+ */
7573
7956
  assignHasManyForeignKey(child, relation, rootValue) {
7574
7957
  const current = child[relation.foreignKey];
7575
7958
  if (current === rootValue) return;
7576
7959
  child[relation.foreignKey] = rootValue;
7577
7960
  }
7961
+ /**
7962
+ * Detaches a child entity from has-many relations.
7963
+ * @param child - The child entity
7964
+ * @param relation - The has-many relation
7965
+ */
7578
7966
  detachHasManyChild(child, relation) {
7579
7967
  if (relation.cascade === "all" || relation.cascade === "remove") {
7580
7968
  this.unitOfWork.markRemoved(child);
@@ -7583,11 +7971,22 @@ var RelationChangeProcessor = class {
7583
7971
  child[relation.foreignKey] = null;
7584
7972
  this.unitOfWork.markDirty(child);
7585
7973
  }
7974
+ /**
7975
+ * Assigns a foreign key for has-one relations.
7976
+ * @param child - The child entity
7977
+ * @param relation - The has-one relation
7978
+ * @param rootValue - The root entity's primary key value
7979
+ */
7586
7980
  assignHasOneForeignKey(child, relation, rootValue) {
7587
7981
  const current = child[relation.foreignKey];
7588
7982
  if (current === rootValue) return;
7589
7983
  child[relation.foreignKey] = rootValue;
7590
7984
  }
7985
+ /**
7986
+ * Detaches a child entity from has-one relations.
7987
+ * @param child - The child entity
7988
+ * @param relation - The has-one relation
7989
+ */
7591
7990
  detachHasOneChild(child, relation) {
7592
7991
  if (relation.cascade === "all" || relation.cascade === "remove") {
7593
7992
  this.unitOfWork.markRemoved(child);
@@ -7596,6 +7995,12 @@ var RelationChangeProcessor = class {
7596
7995
  child[relation.foreignKey] = null;
7597
7996
  this.unitOfWork.markDirty(child);
7598
7997
  }
7998
+ /**
7999
+ * Inserts a pivot row for belongs-to-many relations.
8000
+ * @param relation - The belongs-to-many relation
8001
+ * @param rootId - The root entity's primary key value
8002
+ * @param targetId - The target entity's primary key value
8003
+ */
7599
8004
  async insertPivotRow(relation, rootId, targetId) {
7600
8005
  const payload = {
7601
8006
  [relation.pivotForeignKeyToRoot]: rootId,
@@ -7605,6 +8010,12 @@ var RelationChangeProcessor = class {
7605
8010
  const compiled = builder.compile(this.dialect);
7606
8011
  await this.executor.executeSql(compiled.sql, compiled.params);
7607
8012
  }
8013
+ /**
8014
+ * Deletes a pivot row for belongs-to-many relations.
8015
+ * @param relation - The belongs-to-many relation
8016
+ * @param rootId - The root entity's primary key value
8017
+ * @param targetId - The target entity's primary key value
8018
+ */
7608
8019
  async deletePivotRow(relation, rootId, targetId) {
7609
8020
  const rootCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
7610
8021
  const targetCol = relation.pivotTable.columns[relation.pivotForeignKeyToTarget];
@@ -7615,6 +8026,12 @@ var RelationChangeProcessor = class {
7615
8026
  const compiled = builder.compile(this.dialect);
7616
8027
  await this.executor.executeSql(compiled.sql, compiled.params);
7617
8028
  }
8029
+ /**
8030
+ * Resolves the primary key value from an entity.
8031
+ * @param entity - The entity
8032
+ * @param table - The table definition
8033
+ * @returns The primary key value or null
8034
+ */
7618
8035
  resolvePrimaryKeyValue(entity, table) {
7619
8036
  if (!entity) return null;
7620
8037
  const key = findPrimaryKey(table);
@@ -7630,42 +8047,231 @@ var createQueryLoggingExecutor = (executor, logger) => {
7630
8047
  return executor;
7631
8048
  }
7632
8049
  const wrapped = {
8050
+ capabilities: executor.capabilities,
7633
8051
  async executeSql(sql, params) {
7634
8052
  logger({ sql, params });
7635
8053
  return executor.executeSql(sql, params);
7636
- }
8054
+ },
8055
+ beginTransaction: () => executor.beginTransaction(),
8056
+ commitTransaction: () => executor.commitTransaction(),
8057
+ rollbackTransaction: () => executor.rollbackTransaction(),
8058
+ dispose: () => executor.dispose()
7637
8059
  };
7638
- if (executor.beginTransaction) {
7639
- wrapped.beginTransaction = executor.beginTransaction.bind(executor);
7640
- }
7641
- if (executor.commitTransaction) {
7642
- wrapped.commitTransaction = executor.commitTransaction.bind(executor);
7643
- }
7644
- if (executor.rollbackTransaction) {
7645
- wrapped.rollbackTransaction = executor.rollbackTransaction.bind(executor);
7646
- }
7647
8060
  return wrapped;
7648
8061
  };
7649
8062
 
7650
8063
  // src/orm/transaction-runner.ts
7651
8064
  var runInTransaction = async (executor, action) => {
7652
- if (!executor.beginTransaction) {
8065
+ if (!executor.capabilities.transactions) {
7653
8066
  await action();
7654
8067
  return;
7655
8068
  }
7656
8069
  await executor.beginTransaction();
7657
8070
  try {
7658
8071
  await action();
7659
- await executor.commitTransaction?.();
8072
+ await executor.commitTransaction();
7660
8073
  } catch (error) {
7661
- await executor.rollbackTransaction?.();
8074
+ await executor.rollbackTransaction();
7662
8075
  throw error;
7663
8076
  }
7664
8077
  };
7665
8078
 
8079
+ // src/orm/save-graph.ts
8080
+ var toKey8 = (value) => value === null || value === void 0 ? "" : String(value);
8081
+ var pickColumns = (table, payload) => {
8082
+ const columns = {};
8083
+ for (const key of Object.keys(table.columns)) {
8084
+ if (payload[key] !== void 0) {
8085
+ columns[key] = payload[key];
8086
+ }
8087
+ }
8088
+ return columns;
8089
+ };
8090
+ var ensureEntity = (session, table, payload) => {
8091
+ const pk = findPrimaryKey(table);
8092
+ const row = pickColumns(table, payload);
8093
+ const pkValue = payload[pk];
8094
+ if (pkValue !== void 0 && pkValue !== null) {
8095
+ const tracked = session.getEntity(table, pkValue);
8096
+ if (tracked) {
8097
+ return tracked;
8098
+ }
8099
+ if (row[pk] === void 0) {
8100
+ row[pk] = pkValue;
8101
+ }
8102
+ }
8103
+ return createEntityFromRow(session, table, row);
8104
+ };
8105
+ var assignColumns = (table, entity, payload) => {
8106
+ for (const key of Object.keys(table.columns)) {
8107
+ if (payload[key] !== void 0) {
8108
+ entity[key] = payload[key];
8109
+ }
8110
+ }
8111
+ };
8112
+ var isEntityInCollection = (items, pkName, entity) => {
8113
+ if (items.includes(entity)) return true;
8114
+ const entityPk = entity[pkName];
8115
+ if (entityPk === void 0 || entityPk === null) return false;
8116
+ return items.some((item) => toKey8(item[pkName]) === toKey8(entityPk));
8117
+ };
8118
+ var findInCollectionByPk = (items, pkName, pkValue) => {
8119
+ if (pkValue === void 0 || pkValue === null) return void 0;
8120
+ return items.find((item) => toKey8(item[pkName]) === toKey8(pkValue));
8121
+ };
8122
+ var handleHasMany = async (session, root, relationName, relation, payload, options) => {
8123
+ if (!Array.isArray(payload)) return;
8124
+ const collection = root[relationName];
8125
+ await collection.load();
8126
+ const targetTable = relation.target;
8127
+ const targetPk = findPrimaryKey(targetTable);
8128
+ const existing = collection.getItems();
8129
+ const seen = /* @__PURE__ */ new Set();
8130
+ for (const item of payload) {
8131
+ if (item === null || item === void 0) continue;
8132
+ const asObj = typeof item === "object" ? item : { [targetPk]: item };
8133
+ const pkValue = asObj[targetPk];
8134
+ const current = findInCollectionByPk(existing, targetPk, pkValue) ?? (pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) : void 0);
8135
+ const entity = current ?? ensureEntity(session, targetTable, asObj);
8136
+ assignColumns(targetTable, entity, asObj);
8137
+ await applyGraphToEntity(session, targetTable, entity, asObj, options);
8138
+ if (!isEntityInCollection(collection.getItems(), targetPk, entity)) {
8139
+ collection.attach(entity);
8140
+ }
8141
+ if (pkValue !== void 0 && pkValue !== null) {
8142
+ seen.add(toKey8(pkValue));
8143
+ }
8144
+ }
8145
+ if (options.pruneMissing) {
8146
+ for (const item of [...collection.getItems()]) {
8147
+ const pkValue = item[targetPk];
8148
+ if (pkValue !== void 0 && pkValue !== null && !seen.has(toKey8(pkValue))) {
8149
+ collection.remove(item);
8150
+ }
8151
+ }
8152
+ }
8153
+ };
8154
+ var handleHasOne = async (session, root, relationName, relation, payload, options) => {
8155
+ const ref = root[relationName];
8156
+ if (payload === void 0) return;
8157
+ if (payload === null) {
8158
+ ref.set(null);
8159
+ return;
8160
+ }
8161
+ const pk = findPrimaryKey(relation.target);
8162
+ if (typeof payload === "number" || typeof payload === "string") {
8163
+ const entity = ref.set({ [pk]: payload });
8164
+ if (entity) {
8165
+ await applyGraphToEntity(session, relation.target, entity, { [pk]: payload }, options);
8166
+ }
8167
+ return;
8168
+ }
8169
+ const attached = ref.set(payload);
8170
+ if (attached) {
8171
+ await applyGraphToEntity(session, relation.target, attached, payload, options);
8172
+ }
8173
+ };
8174
+ var handleBelongsTo = async (session, root, relationName, relation, payload, options) => {
8175
+ const ref = root[relationName];
8176
+ if (payload === void 0) return;
8177
+ if (payload === null) {
8178
+ ref.set(null);
8179
+ return;
8180
+ }
8181
+ const pk = relation.localKey || findPrimaryKey(relation.target);
8182
+ if (typeof payload === "number" || typeof payload === "string") {
8183
+ const entity = ref.set({ [pk]: payload });
8184
+ if (entity) {
8185
+ await applyGraphToEntity(session, relation.target, entity, { [pk]: payload }, options);
8186
+ }
8187
+ return;
8188
+ }
8189
+ const attached = ref.set(payload);
8190
+ if (attached) {
8191
+ await applyGraphToEntity(session, relation.target, attached, payload, options);
8192
+ }
8193
+ };
8194
+ var handleBelongsToMany = async (session, root, relationName, relation, payload, options) => {
8195
+ if (!Array.isArray(payload)) return;
8196
+ const collection = root[relationName];
8197
+ await collection.load();
8198
+ const targetTable = relation.target;
8199
+ const targetPk = relation.targetKey || findPrimaryKey(targetTable);
8200
+ const seen = /* @__PURE__ */ new Set();
8201
+ for (const item of payload) {
8202
+ if (item === null || item === void 0) continue;
8203
+ if (typeof item === "number" || typeof item === "string") {
8204
+ const id = item;
8205
+ collection.attach(id);
8206
+ seen.add(toKey8(id));
8207
+ continue;
8208
+ }
8209
+ const asObj = item;
8210
+ const pkValue = asObj[targetPk];
8211
+ const entity = pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) ?? ensureEntity(session, targetTable, asObj) : ensureEntity(session, targetTable, asObj);
8212
+ assignColumns(targetTable, entity, asObj);
8213
+ await applyGraphToEntity(session, targetTable, entity, asObj, options);
8214
+ if (!isEntityInCollection(collection.getItems(), targetPk, entity)) {
8215
+ collection.attach(entity);
8216
+ }
8217
+ if (pkValue !== void 0 && pkValue !== null) {
8218
+ seen.add(toKey8(pkValue));
8219
+ }
8220
+ }
8221
+ if (options.pruneMissing) {
8222
+ for (const item of [...collection.getItems()]) {
8223
+ const pkValue = item[targetPk];
8224
+ if (pkValue !== void 0 && pkValue !== null && !seen.has(toKey8(pkValue))) {
8225
+ collection.detach(item);
8226
+ }
8227
+ }
8228
+ }
8229
+ };
8230
+ var applyRelation = async (session, table, entity, relationName, relation, payload, options) => {
8231
+ switch (relation.type) {
8232
+ case RelationKinds.HasMany:
8233
+ return handleHasMany(session, entity, relationName, relation, payload, options);
8234
+ case RelationKinds.HasOne:
8235
+ return handleHasOne(session, entity, relationName, relation, payload, options);
8236
+ case RelationKinds.BelongsTo:
8237
+ return handleBelongsTo(session, entity, relationName, relation, payload, options);
8238
+ case RelationKinds.BelongsToMany:
8239
+ return handleBelongsToMany(session, entity, relationName, relation, payload, options);
8240
+ }
8241
+ };
8242
+ var applyGraphToEntity = async (session, table, entity, payload, options) => {
8243
+ assignColumns(table, entity, payload);
8244
+ for (const [relationName, relation] of Object.entries(table.relations)) {
8245
+ if (!(relationName in payload)) continue;
8246
+ await applyRelation(session, table, entity, relationName, relation, payload[relationName], options);
8247
+ }
8248
+ };
8249
+ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
8250
+ const table = getTableDefFromEntity(entityClass);
8251
+ if (!table) {
8252
+ throw new Error("Entity metadata has not been bootstrapped");
8253
+ }
8254
+ const root = ensureEntity(session, table, payload);
8255
+ await applyGraphToEntity(session, table, root, payload, options);
8256
+ return root;
8257
+ };
8258
+
7666
8259
  // src/orm/orm-session.ts
7667
8260
  var OrmSession = class {
8261
+ /**
8262
+ * Creates a new OrmSession instance.
8263
+ * @param opts - Session options
8264
+ */
7668
8265
  constructor(opts) {
8266
+ /**
8267
+ * Registers a relation change.
8268
+ * @param root - The root entity
8269
+ * @param relationKey - The relation key
8270
+ * @param rootTable - The root table definition
8271
+ * @param relationName - The relation name
8272
+ * @param relation - The relation definition
8273
+ * @param change - The relation change
8274
+ */
7669
8275
  this.registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
7670
8276
  this.relationChanges.registerChange(
7671
8277
  buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
@@ -7679,42 +8285,117 @@ var OrmSession = class {
7679
8285
  this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
7680
8286
  this.domainEvents = new DomainEventBus(opts.domainEventHandlers);
7681
8287
  }
8288
+ /**
8289
+ * Releases resources associated with this session (executor/pool leases) and resets tracking.
8290
+ * Must be safe to call multiple times.
8291
+ */
8292
+ async dispose() {
8293
+ try {
8294
+ await this.executor.dispose();
8295
+ } finally {
8296
+ this.unitOfWork.reset();
8297
+ this.relationChanges.reset();
8298
+ }
8299
+ }
8300
+ /**
8301
+ * Gets the database dialect.
8302
+ */
7682
8303
  get dialect() {
7683
8304
  return this.orm.dialect;
7684
8305
  }
8306
+ /**
8307
+ * Gets the identity buckets map.
8308
+ */
7685
8309
  get identityBuckets() {
7686
8310
  return this.unitOfWork.identityBuckets;
7687
8311
  }
8312
+ /**
8313
+ * Gets all tracked entities.
8314
+ */
7688
8315
  get tracked() {
7689
8316
  return this.unitOfWork.getTracked();
7690
8317
  }
8318
+ /**
8319
+ * Gets an entity by table and primary key.
8320
+ * @param table - The table definition
8321
+ * @param pk - The primary key value
8322
+ * @returns The entity or undefined if not found
8323
+ */
7691
8324
  getEntity(table, pk) {
7692
8325
  return this.unitOfWork.getEntity(table, pk);
7693
8326
  }
8327
+ /**
8328
+ * Sets an entity in the identity map.
8329
+ * @param table - The table definition
8330
+ * @param pk - The primary key value
8331
+ * @param entity - The entity instance
8332
+ */
7694
8333
  setEntity(table, pk, entity) {
7695
8334
  this.unitOfWork.setEntity(table, pk, entity);
7696
8335
  }
8336
+ /**
8337
+ * Tracks a new entity.
8338
+ * @param table - The table definition
8339
+ * @param entity - The entity instance
8340
+ * @param pk - Optional primary key value
8341
+ */
7697
8342
  trackNew(table, entity, pk) {
7698
8343
  this.unitOfWork.trackNew(table, entity, pk);
7699
8344
  }
8345
+ /**
8346
+ * Tracks a managed entity.
8347
+ * @param table - The table definition
8348
+ * @param pk - The primary key value
8349
+ * @param entity - The entity instance
8350
+ */
7700
8351
  trackManaged(table, pk, entity) {
7701
8352
  this.unitOfWork.trackManaged(table, pk, entity);
7702
8353
  }
8354
+ /**
8355
+ * Marks an entity as dirty (modified).
8356
+ * @param entity - The entity to mark as dirty
8357
+ */
7703
8358
  markDirty(entity) {
7704
8359
  this.unitOfWork.markDirty(entity);
7705
8360
  }
8361
+ /**
8362
+ * Marks an entity as removed.
8363
+ * @param entity - The entity to mark as removed
8364
+ */
7706
8365
  markRemoved(entity) {
7707
8366
  this.unitOfWork.markRemoved(entity);
7708
8367
  }
8368
+ /**
8369
+ * Gets all tracked entities for a specific table.
8370
+ * @param table - The table definition
8371
+ * @returns Array of tracked entities
8372
+ */
7709
8373
  getEntitiesForTable(table) {
7710
8374
  return this.unitOfWork.getEntitiesForTable(table);
7711
8375
  }
8376
+ /**
8377
+ * Registers an interceptor for flush lifecycle hooks.
8378
+ * @param interceptor - The interceptor to register
8379
+ */
7712
8380
  registerInterceptor(interceptor) {
7713
8381
  this.interceptors.push(interceptor);
7714
8382
  }
8383
+ /**
8384
+ * Registers a domain event handler.
8385
+ * @param type - The event type
8386
+ * @param handler - The event handler
8387
+ */
7715
8388
  registerDomainEventHandler(type, handler) {
7716
8389
  this.domainEvents.on(type, handler);
7717
8390
  }
8391
+ /**
8392
+ * Finds an entity by its primary key.
8393
+ * @template TCtor - The entity constructor type
8394
+ * @param entityClass - The entity constructor
8395
+ * @param id - The primary key value
8396
+ * @returns The entity instance or null if not found
8397
+ * @throws If entity metadata is not bootstrapped or table has no primary key
8398
+ */
7718
8399
  async find(entityClass, id) {
7719
8400
  const table = getTableDefFromEntity(entityClass);
7720
8401
  if (!table) {
@@ -7733,14 +8414,46 @@ var OrmSession = class {
7733
8414
  const rows = await executeHydrated(this, qb);
7734
8415
  return rows[0] ?? null;
7735
8416
  }
8417
+ /**
8418
+ * Finds a single entity using a query builder.
8419
+ * @template TTable - The table type
8420
+ * @param qb - The query builder
8421
+ * @returns The first entity instance or null if not found
8422
+ */
7736
8423
  async findOne(qb) {
7737
8424
  const limited = qb.limit(1);
7738
8425
  const rows = await executeHydrated(this, limited);
7739
8426
  return rows[0] ?? null;
7740
8427
  }
8428
+ /**
8429
+ * Finds multiple entities using a query builder.
8430
+ * @template TTable - The table type
8431
+ * @param qb - The query builder
8432
+ * @returns Array of entity instances
8433
+ */
7741
8434
  async findMany(qb) {
7742
8435
  return executeHydrated(this, qb);
7743
8436
  }
8437
+ /**
8438
+ * Saves an entity graph (root + nested relations) based on a DTO-like payload.
8439
+ * @param entityClass - Root entity constructor
8440
+ * @param payload - DTO payload containing column values and nested relations
8441
+ * @param options - Graph save options
8442
+ * @returns The root entity instance
8443
+ */
8444
+ async saveGraph(entityClass, payload, options) {
8445
+ const { transactional = true, ...graphOptions } = options ?? {};
8446
+ const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
8447
+ if (!transactional) {
8448
+ return execute();
8449
+ }
8450
+ return this.transaction(() => execute());
8451
+ }
8452
+ /**
8453
+ * Persists an entity (either inserts or updates).
8454
+ * @param entity - The entity to persist
8455
+ * @throws If entity metadata is not bootstrapped
8456
+ */
7744
8457
  async persist(entity) {
7745
8458
  if (this.unitOfWork.findTracked(entity)) {
7746
8459
  return;
@@ -7757,12 +8470,22 @@ var OrmSession = class {
7757
8470
  this.trackNew(table, entity);
7758
8471
  }
7759
8472
  }
8473
+ /**
8474
+ * Marks an entity for removal.
8475
+ * @param entity - The entity to remove
8476
+ */
7760
8477
  async remove(entity) {
7761
8478
  this.markRemoved(entity);
7762
8479
  }
8480
+ /**
8481
+ * Flushes pending changes to the database.
8482
+ */
7763
8483
  async flush() {
7764
8484
  await this.unitOfWork.flush();
7765
8485
  }
8486
+ /**
8487
+ * Flushes pending changes with interceptors and relation processing.
8488
+ */
7766
8489
  async flushWithHooks() {
7767
8490
  for (const interceptor of this.interceptors) {
7768
8491
  await interceptor.beforeFlush?.(this);
@@ -7774,14 +8497,24 @@ var OrmSession = class {
7774
8497
  await interceptor.afterFlush?.(this);
7775
8498
  }
7776
8499
  }
8500
+ /**
8501
+ * Commits the current transaction.
8502
+ */
7777
8503
  async commit() {
7778
8504
  await runInTransaction(this.executor, async () => {
7779
8505
  await this.flushWithHooks();
7780
8506
  });
7781
8507
  await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
7782
8508
  }
8509
+ /**
8510
+ * Executes a function within a transaction.
8511
+ * @template T - The return type
8512
+ * @param fn - The function to execute
8513
+ * @returns The result of the function
8514
+ * @throws If the transaction fails
8515
+ */
7783
8516
  async transaction(fn4) {
7784
- if (!this.executor.beginTransaction) {
8517
+ if (!this.executor.capabilities.transactions) {
7785
8518
  const result = await fn4(this);
7786
8519
  await this.commit();
7787
8520
  return result;
@@ -7790,7 +8523,7 @@ var OrmSession = class {
7790
8523
  try {
7791
8524
  const result = await fn4(this);
7792
8525
  await this.flushWithHooks();
7793
- await this.executor.commitTransaction?.();
8526
+ await this.executor.commitTransaction();
7794
8527
  await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
7795
8528
  return result;
7796
8529
  } catch (err) {
@@ -7798,11 +8531,20 @@ var OrmSession = class {
7798
8531
  throw err;
7799
8532
  }
7800
8533
  }
8534
+ /**
8535
+ * Rolls back the current transaction.
8536
+ */
7801
8537
  async rollback() {
7802
- await this.executor.rollbackTransaction?.();
8538
+ if (this.executor.capabilities.transactions) {
8539
+ await this.executor.rollbackTransaction();
8540
+ }
7803
8541
  this.unitOfWork.reset();
7804
8542
  this.relationChanges.reset();
7805
8543
  }
8544
+ /**
8545
+ * Gets the execution context.
8546
+ * @returns The execution context
8547
+ */
7806
8548
  getExecutionContext() {
7807
8549
  return {
7808
8550
  dialect: this.orm.dialect,
@@ -7810,6 +8552,10 @@ var OrmSession = class {
7810
8552
  interceptors: this.orm.interceptors
7811
8553
  };
7812
8554
  }
8555
+ /**
8556
+ * Gets the hydration context.
8557
+ * @returns The hydration context
8558
+ */
7813
8559
  getHydrationContext() {
7814
8560
  return {
7815
8561
  identityMap: this.identityMap,
@@ -7852,29 +8598,49 @@ var InterceptorPipeline = class {
7852
8598
 
7853
8599
  // src/orm/orm.ts
7854
8600
  var Orm = class {
8601
+ /**
8602
+ * Creates a new ORM instance.
8603
+ * @param opts - ORM options
8604
+ */
7855
8605
  constructor(opts) {
7856
8606
  this.dialect = opts.dialect;
7857
8607
  this.interceptors = opts.interceptors ?? new InterceptorPipeline();
7858
8608
  this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
7859
8609
  this.executorFactory = opts.executorFactory;
7860
8610
  }
7861
- createSession(options) {
7862
- const executor = this.executorFactory.createExecutor(options?.tx);
8611
+ /**
8612
+ * Creates a new ORM session.
8613
+ * @param options - Optional session options
8614
+ * @returns The ORM session
8615
+ */
8616
+ createSession() {
8617
+ const executor = this.executorFactory.createExecutor();
7863
8618
  return new OrmSession({ orm: this, executor });
7864
8619
  }
8620
+ /**
8621
+ * Executes a function within a transaction.
8622
+ * @template T - The return type
8623
+ * @param fn - The function to execute
8624
+ * @returns The result of the function
8625
+ * @throws If the transaction fails
8626
+ */
7865
8627
  async transaction(fn4) {
7866
8628
  const executor = this.executorFactory.createTransactionalExecutor();
7867
8629
  const session = new OrmSession({ orm: this, executor });
7868
8630
  try {
7869
- const result = await fn4(session);
7870
- await session.commit();
7871
- return result;
8631
+ return await session.transaction(() => fn4(session));
7872
8632
  } catch (err) {
7873
- await session.rollback();
7874
8633
  throw err;
7875
8634
  } finally {
8635
+ await session.dispose();
7876
8636
  }
7877
8637
  }
8638
+ /**
8639
+ * Shuts down the ORM and releases underlying resources (pools, timers).
8640
+ */
8641
+ async dispose() {
8642
+ await this.executorFactory.dispose();
8643
+ }
7878
8644
  };
7879
8645
 
7880
8646
  // src/decorators/decorator-metadata.ts
@@ -8133,18 +8899,245 @@ function rowsToQueryResult(rows) {
8133
8899
  return { columns, values };
8134
8900
  }
8135
8901
  function createExecutorFromQueryRunner(runner) {
8902
+ const supportsTransactions = typeof runner.beginTransaction === "function" && typeof runner.commitTransaction === "function" && typeof runner.rollbackTransaction === "function";
8136
8903
  return {
8904
+ capabilities: {
8905
+ transactions: supportsTransactions
8906
+ },
8137
8907
  async executeSql(sql, params) {
8138
8908
  const rows = await runner.query(sql, params);
8139
8909
  const result = rowsToQueryResult(rows);
8140
8910
  return [result];
8141
8911
  },
8142
- beginTransaction: runner.beginTransaction?.bind(runner),
8143
- commitTransaction: runner.commitTransaction?.bind(runner),
8144
- rollbackTransaction: runner.rollbackTransaction?.bind(runner)
8912
+ async beginTransaction() {
8913
+ if (!supportsTransactions) {
8914
+ throw new Error("Transactions are not supported by this executor");
8915
+ }
8916
+ await runner.beginTransaction.call(runner);
8917
+ },
8918
+ async commitTransaction() {
8919
+ if (!supportsTransactions) {
8920
+ throw new Error("Transactions are not supported by this executor");
8921
+ }
8922
+ await runner.commitTransaction.call(runner);
8923
+ },
8924
+ async rollbackTransaction() {
8925
+ if (!supportsTransactions) {
8926
+ throw new Error("Transactions are not supported by this executor");
8927
+ }
8928
+ await runner.rollbackTransaction.call(runner);
8929
+ },
8930
+ async dispose() {
8931
+ await runner.dispose?.call(runner);
8932
+ }
8145
8933
  };
8146
8934
  }
8147
8935
 
8936
+ // src/core/execution/pooling/pool.ts
8937
+ var deferred = () => {
8938
+ let resolve;
8939
+ let reject;
8940
+ const promise = new Promise((res, rej) => {
8941
+ resolve = res;
8942
+ reject = rej;
8943
+ });
8944
+ return { promise, resolve, reject };
8945
+ };
8946
+ var Pool = class {
8947
+ constructor(adapter, options) {
8948
+ this.destroyed = false;
8949
+ this.creating = 0;
8950
+ this.leased = 0;
8951
+ this.idle = [];
8952
+ this.waiters = [];
8953
+ this.reapTimer = null;
8954
+ if (!Number.isFinite(options.max) || options.max <= 0) {
8955
+ throw new Error("Pool options.max must be a positive number");
8956
+ }
8957
+ this.adapter = adapter;
8958
+ this.options = { max: options.max, ...options };
8959
+ const idleTimeout = this.options.idleTimeoutMillis;
8960
+ if (idleTimeout && idleTimeout > 0) {
8961
+ const interval = this.options.reapIntervalMillis ?? Math.max(1e3, Math.floor(idleTimeout / 2));
8962
+ this.reapTimer = setInterval(() => {
8963
+ void this.reapIdle();
8964
+ }, interval);
8965
+ this.reapTimer.unref?.();
8966
+ }
8967
+ const min2 = this.options.min ?? 0;
8968
+ if (min2 > 0) {
8969
+ void this.warm(min2);
8970
+ }
8971
+ }
8972
+ /**
8973
+ * Acquire a resource lease.
8974
+ * The returned lease MUST be released or destroyed.
8975
+ */
8976
+ async acquire() {
8977
+ if (this.destroyed) {
8978
+ throw new Error("Pool is destroyed");
8979
+ }
8980
+ const idle = await this.takeIdleValidated();
8981
+ if (idle) {
8982
+ this.leased++;
8983
+ return this.makeLease(idle);
8984
+ }
8985
+ if (this.totalLive() < this.options.max) {
8986
+ this.creating++;
8987
+ try {
8988
+ const created = await this.adapter.create();
8989
+ this.leased++;
8990
+ return this.makeLease(created);
8991
+ } finally {
8992
+ this.creating--;
8993
+ }
8994
+ }
8995
+ const waiter = deferred();
8996
+ this.waiters.push(waiter);
8997
+ const timeout = this.options.acquireTimeoutMillis;
8998
+ let timer = null;
8999
+ if (timeout && timeout > 0) {
9000
+ timer = setTimeout(() => {
9001
+ const idx = this.waiters.indexOf(waiter);
9002
+ if (idx >= 0) this.waiters.splice(idx, 1);
9003
+ waiter.reject(new Error("Pool acquire timeout"));
9004
+ }, timeout);
9005
+ timer.unref?.();
9006
+ }
9007
+ try {
9008
+ return await waiter.promise;
9009
+ } finally {
9010
+ if (timer) clearTimeout(timer);
9011
+ }
9012
+ }
9013
+ /** Destroy pool and all idle resources; waits for in-flight creations to settle. */
9014
+ async destroy() {
9015
+ if (this.destroyed) return;
9016
+ this.destroyed = true;
9017
+ if (this.reapTimer) {
9018
+ clearInterval(this.reapTimer);
9019
+ this.reapTimer = null;
9020
+ }
9021
+ while (this.waiters.length) {
9022
+ this.waiters.shift().reject(new Error("Pool destroyed"));
9023
+ }
9024
+ while (this.idle.length) {
9025
+ const entry = this.idle.shift();
9026
+ await this.adapter.destroy(entry.resource);
9027
+ }
9028
+ }
9029
+ totalLive() {
9030
+ return this.idle.length + this.leased + this.creating;
9031
+ }
9032
+ makeLease(resource) {
9033
+ let done = false;
9034
+ return {
9035
+ resource,
9036
+ release: async () => {
9037
+ if (done) return;
9038
+ done = true;
9039
+ await this.releaseResource(resource);
9040
+ },
9041
+ destroy: async () => {
9042
+ if (done) return;
9043
+ done = true;
9044
+ await this.destroyResource(resource);
9045
+ }
9046
+ };
9047
+ }
9048
+ async releaseResource(resource) {
9049
+ this.leased = Math.max(0, this.leased - 1);
9050
+ if (this.destroyed) {
9051
+ await this.adapter.destroy(resource);
9052
+ return;
9053
+ }
9054
+ const next = this.waiters.shift();
9055
+ if (next) {
9056
+ this.leased++;
9057
+ next.resolve(this.makeLease(resource));
9058
+ return;
9059
+ }
9060
+ this.idle.push({ resource, lastUsedAt: Date.now() });
9061
+ await this.trimToMinMax();
9062
+ }
9063
+ async destroyResource(resource) {
9064
+ this.leased = Math.max(0, this.leased - 1);
9065
+ await this.adapter.destroy(resource);
9066
+ if (!this.destroyed && this.waiters.length && this.totalLive() < this.options.max) {
9067
+ const waiter = this.waiters.shift();
9068
+ this.creating++;
9069
+ try {
9070
+ const created = await this.adapter.create();
9071
+ this.leased++;
9072
+ waiter.resolve(this.makeLease(created));
9073
+ } catch (err) {
9074
+ waiter.reject(err);
9075
+ } finally {
9076
+ this.creating--;
9077
+ }
9078
+ }
9079
+ }
9080
+ async takeIdleValidated() {
9081
+ while (this.idle.length) {
9082
+ const entry = this.idle.pop();
9083
+ if (!this.adapter.validate) {
9084
+ return entry.resource;
9085
+ }
9086
+ const ok = await this.adapter.validate(entry.resource);
9087
+ if (ok) {
9088
+ return entry.resource;
9089
+ }
9090
+ await this.adapter.destroy(entry.resource);
9091
+ }
9092
+ return null;
9093
+ }
9094
+ async reapIdle() {
9095
+ if (this.destroyed) return;
9096
+ const idleTimeout = this.options.idleTimeoutMillis;
9097
+ if (!idleTimeout || idleTimeout <= 0) return;
9098
+ const now2 = Date.now();
9099
+ const min2 = this.options.min ?? 0;
9100
+ const keep = [];
9101
+ const kill = [];
9102
+ for (const entry of this.idle) {
9103
+ const expired = now2 - entry.lastUsedAt >= idleTimeout;
9104
+ if (expired) kill.push(entry);
9105
+ else keep.push(entry);
9106
+ }
9107
+ while (keep.length < min2 && kill.length) {
9108
+ keep.push(kill.pop());
9109
+ }
9110
+ this.idle.length = 0;
9111
+ this.idle.push(...keep);
9112
+ for (const entry of kill) {
9113
+ await this.adapter.destroy(entry.resource);
9114
+ }
9115
+ }
9116
+ async warm(targetMin) {
9117
+ const min2 = Math.max(0, targetMin);
9118
+ while (!this.destroyed && this.idle.length < min2 && this.totalLive() < this.options.max) {
9119
+ this.creating++;
9120
+ try {
9121
+ const created = await this.adapter.create();
9122
+ this.idle.push({ resource: created, lastUsedAt: Date.now() });
9123
+ } catch {
9124
+ break;
9125
+ } finally {
9126
+ this.creating--;
9127
+ }
9128
+ }
9129
+ }
9130
+ async trimToMinMax() {
9131
+ const max2 = this.options.max;
9132
+ const min2 = this.options.min ?? 0;
9133
+ while (this.totalLive() > max2 && this.idle.length > min2) {
9134
+ const entry = this.idle.shift();
9135
+ if (!entry) break;
9136
+ await this.adapter.destroy(entry.resource);
9137
+ }
9138
+ }
9139
+ };
9140
+
8148
9141
  // src/core/execution/executors/postgres-executor.ts
8149
9142
  function createPostgresExecutor(client) {
8150
9143
  return createExecutorFromQueryRunner({
@@ -8166,7 +9159,11 @@ function createPostgresExecutor(client) {
8166
9159
 
8167
9160
  // src/core/execution/executors/mysql-executor.ts
8168
9161
  function createMysqlExecutor(client) {
9162
+ const supportsTransactions = typeof client.beginTransaction === "function" && typeof client.commit === "function" && typeof client.rollback === "function";
8169
9163
  return {
9164
+ capabilities: {
9165
+ transactions: supportsTransactions
9166
+ },
8170
9167
  async executeSql(sql, params) {
8171
9168
  const [rows] = await client.query(sql, params);
8172
9169
  if (!Array.isArray(rows)) {
@@ -8178,53 +9175,94 @@ function createMysqlExecutor(client) {
8178
9175
  return [result];
8179
9176
  },
8180
9177
  async beginTransaction() {
8181
- if (!client.beginTransaction) return;
9178
+ if (!supportsTransactions) {
9179
+ throw new Error("Transactions are not supported by this executor");
9180
+ }
8182
9181
  await client.beginTransaction();
8183
9182
  },
8184
9183
  async commitTransaction() {
8185
- if (!client.commit) return;
9184
+ if (!supportsTransactions) {
9185
+ throw new Error("Transactions are not supported by this executor");
9186
+ }
8186
9187
  await client.commit();
8187
9188
  },
8188
9189
  async rollbackTransaction() {
8189
- if (!client.rollback) return;
9190
+ if (!supportsTransactions) {
9191
+ throw new Error("Transactions are not supported by this executor");
9192
+ }
8190
9193
  await client.rollback();
9194
+ },
9195
+ async dispose() {
8191
9196
  }
8192
9197
  };
8193
9198
  }
8194
9199
 
8195
9200
  // src/core/execution/executors/sqlite-executor.ts
8196
9201
  function createSqliteExecutor(client) {
9202
+ const supportsTransactions = typeof client.beginTransaction === "function" && typeof client.commitTransaction === "function" && typeof client.rollbackTransaction === "function";
8197
9203
  return {
9204
+ capabilities: {
9205
+ transactions: supportsTransactions
9206
+ },
8198
9207
  async executeSql(sql, params) {
8199
9208
  const rows = await client.all(sql, params);
8200
9209
  const result = rowsToQueryResult(rows);
8201
9210
  return [result];
8202
9211
  },
8203
- beginTransaction: client.beginTransaction?.bind(client),
8204
- commitTransaction: client.commitTransaction?.bind(client),
8205
- rollbackTransaction: client.rollbackTransaction?.bind(client)
9212
+ async beginTransaction() {
9213
+ if (!supportsTransactions) {
9214
+ throw new Error("Transactions are not supported by this executor");
9215
+ }
9216
+ await client.beginTransaction();
9217
+ },
9218
+ async commitTransaction() {
9219
+ if (!supportsTransactions) {
9220
+ throw new Error("Transactions are not supported by this executor");
9221
+ }
9222
+ await client.commitTransaction();
9223
+ },
9224
+ async rollbackTransaction() {
9225
+ if (!supportsTransactions) {
9226
+ throw new Error("Transactions are not supported by this executor");
9227
+ }
9228
+ await client.rollbackTransaction();
9229
+ },
9230
+ async dispose() {
9231
+ }
8206
9232
  };
8207
9233
  }
8208
9234
 
8209
9235
  // src/core/execution/executors/mssql-executor.ts
8210
9236
  function createMssqlExecutor(client) {
9237
+ const supportsTransactions = typeof client.beginTransaction === "function" && typeof client.commit === "function" && typeof client.rollback === "function";
8211
9238
  return {
9239
+ capabilities: {
9240
+ transactions: supportsTransactions
9241
+ },
8212
9242
  async executeSql(sql, params) {
8213
9243
  const { recordset } = await client.query(sql, params);
8214
9244
  const result = rowsToQueryResult(recordset ?? []);
8215
9245
  return [result];
8216
9246
  },
8217
9247
  async beginTransaction() {
8218
- if (!client.beginTransaction) return;
9248
+ if (!supportsTransactions) {
9249
+ throw new Error("Transactions are not supported by this executor");
9250
+ }
8219
9251
  await client.beginTransaction();
8220
9252
  },
8221
9253
  async commitTransaction() {
8222
- if (!client.commit) return;
9254
+ if (!supportsTransactions) {
9255
+ throw new Error("Transactions are not supported by this executor");
9256
+ }
8223
9257
  await client.commit();
8224
9258
  },
8225
9259
  async rollbackTransaction() {
8226
- if (!client.rollback) return;
9260
+ if (!supportsTransactions) {
9261
+ throw new Error("Transactions are not supported by this executor");
9262
+ }
8227
9263
  await client.rollback();
9264
+ },
9265
+ async dispose() {
8228
9266
  }
8229
9267
  };
8230
9268
  }
@@ -8293,6 +9331,86 @@ function createTediousExecutor(connection, module2, options) {
8293
9331
  const client = createTediousMssqlClient(connection, module2, options);
8294
9332
  return createMssqlExecutor(client);
8295
9333
  }
9334
+
9335
+ // src/orm/pooled-executor-factory.ts
9336
+ function createPooledExecutorFactory(opts) {
9337
+ const { pool, adapter } = opts;
9338
+ const makeExecutor = (mode) => {
9339
+ let lease = null;
9340
+ const getLease = async () => {
9341
+ if (lease) return lease;
9342
+ lease = await pool.acquire();
9343
+ return lease;
9344
+ };
9345
+ const executeWithConn = async (conn, sql, params) => {
9346
+ const rows = await adapter.query(conn, sql, params);
9347
+ return [rowsToQueryResult(rows)];
9348
+ };
9349
+ return {
9350
+ capabilities: { transactions: true },
9351
+ async executeSql(sql, params) {
9352
+ if (mode === "sticky") {
9353
+ const l2 = await getLease();
9354
+ return executeWithConn(l2.resource, sql, params);
9355
+ }
9356
+ if (lease) {
9357
+ return executeWithConn(lease.resource, sql, params);
9358
+ }
9359
+ const l = await pool.acquire();
9360
+ try {
9361
+ return await executeWithConn(l.resource, sql, params);
9362
+ } finally {
9363
+ await l.release();
9364
+ }
9365
+ },
9366
+ async beginTransaction() {
9367
+ const l = await getLease();
9368
+ await adapter.beginTransaction(l.resource);
9369
+ },
9370
+ async commitTransaction() {
9371
+ if (!lease) {
9372
+ throw new Error("commitTransaction called without an active transaction");
9373
+ }
9374
+ const l = lease;
9375
+ try {
9376
+ await adapter.commitTransaction(l.resource);
9377
+ } finally {
9378
+ lease = null;
9379
+ await l.release();
9380
+ }
9381
+ },
9382
+ async rollbackTransaction() {
9383
+ if (!lease) {
9384
+ return;
9385
+ }
9386
+ const l = lease;
9387
+ try {
9388
+ await adapter.rollbackTransaction(l.resource);
9389
+ } finally {
9390
+ lease = null;
9391
+ await l.release();
9392
+ }
9393
+ },
9394
+ async dispose() {
9395
+ if (!lease) return;
9396
+ const l = lease;
9397
+ lease = null;
9398
+ await l.release();
9399
+ }
9400
+ };
9401
+ };
9402
+ return {
9403
+ createExecutor() {
9404
+ return makeExecutor("session");
9405
+ },
9406
+ createTransactionalExecutor() {
9407
+ return makeExecutor("sticky");
9408
+ },
9409
+ async dispose() {
9410
+ await pool.destroy();
9411
+ }
9412
+ };
9413
+ }
8296
9414
  // Annotate the CommonJS export names for ESM import in node:
8297
9415
  0 && (module.exports = {
8298
9416
  AsyncLocalStorage,
@@ -8312,6 +9430,7 @@ function createTediousExecutor(connection, module2, options) {
8312
9430
  MySqlDialect,
8313
9431
  Orm,
8314
9432
  OrmSession,
9433
+ Pool,
8315
9434
  PostgresDialect,
8316
9435
  PrimaryKey,
8317
9436
  RelationKinds,
@@ -8357,6 +9476,7 @@ function createTediousExecutor(connection, module2, options) {
8357
9476
  createLiteral,
8358
9477
  createMssqlExecutor,
8359
9478
  createMysqlExecutor,
9479
+ createPooledExecutorFactory,
8360
9480
  createPostgresExecutor,
8361
9481
  createQueryLoggingExecutor,
8362
9482
  createSqliteExecutor,
@@ -8398,6 +9518,7 @@ function createTediousExecutor(connection, module2, options) {
8398
9518
  hasOne,
8399
9519
  hydrateRows,
8400
9520
  inList,
9521
+ inSubquery,
8401
9522
  instr,
8402
9523
  introspectSchema,
8403
9524
  isCaseExpressionNode,
@@ -8438,6 +9559,7 @@ function createTediousExecutor(connection, module2, options) {
8438
9559
  notBetween,
8439
9560
  notExists,
8440
9561
  notInList,
9562
+ notInSubquery,
8441
9563
  notLike,
8442
9564
  now,
8443
9565
  ntile,