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.js CHANGED
@@ -287,6 +287,12 @@ var toOperand = (val) => {
287
287
  }
288
288
  return toNode(val);
289
289
  };
290
+ var hasQueryAst = (value) => typeof value.getAST === "function";
291
+ var resolveSelectQueryNode = (query) => hasQueryAst(query) ? query.getAST() : query;
292
+ var toScalarSubqueryNode = (query) => ({
293
+ type: "ScalarSubquery",
294
+ query: resolveSelectQueryNode(query)
295
+ });
290
296
  var columnOperand = (col2) => toNode(col2);
291
297
  var outerRef = (col2) => ({
292
298
  ...columnOperand(col2),
@@ -337,14 +343,16 @@ var isNotNull = (left2) => ({
337
343
  left: toNode(left2),
338
344
  operator: "IS NOT NULL"
339
345
  });
340
- var createInExpression = (operator, left2, values) => ({
346
+ var createInExpression = (operator, left2, right2) => ({
341
347
  type: "InExpression",
342
348
  left: toNode(left2),
343
349
  operator,
344
- right: values.map((v) => toOperand(v))
350
+ right: right2
345
351
  });
346
- var inList = (left2, values) => createInExpression("IN", left2, values);
347
- var notInList = (left2, values) => createInExpression("NOT IN", left2, values);
352
+ var inList = (left2, values) => createInExpression("IN", left2, values.map((v) => toOperand(v)));
353
+ var notInList = (left2, values) => createInExpression("NOT IN", left2, values.map((v) => toOperand(v)));
354
+ var inSubquery = (left2, subquery) => createInExpression("IN", left2, toScalarSubqueryNode(subquery));
355
+ var notInSubquery = (left2, subquery) => createInExpression("NOT IN", left2, toScalarSubqueryNode(subquery));
348
356
  var createBetweenExpression = (operator, left2, lower2, upper2) => ({
349
357
  type: "BetweenExpression",
350
358
  left: toNode(left2),
@@ -1027,8 +1035,12 @@ var Dialect = class _Dialect {
1027
1035
  });
1028
1036
  this.registerExpressionCompiler("InExpression", (inExpr, ctx) => {
1029
1037
  const left2 = this.compileOperand(inExpr.left, ctx);
1030
- const values = inExpr.right.map((v) => this.compileOperand(v, ctx)).join(", ");
1031
- return `${left2} ${inExpr.operator} (${values})`;
1038
+ if (Array.isArray(inExpr.right)) {
1039
+ const values = inExpr.right.map((v) => this.compileOperand(v, ctx)).join(", ");
1040
+ return `${left2} ${inExpr.operator} (${values})`;
1041
+ }
1042
+ const subquerySql = this.compileSelectAst(inExpr.right.query, ctx).trim().replace(/;$/, "");
1043
+ return `${left2} ${inExpr.operator} (${subquerySql})`;
1032
1044
  });
1033
1045
  this.registerExpressionCompiler("ExistsExpression", (existsExpr, ctx) => {
1034
1046
  const subquerySql = this.compileSelectForExists(existsExpr.subquery, ctx);
@@ -1286,17 +1298,9 @@ var NoReturningStrategy = class {
1286
1298
 
1287
1299
  // src/core/dialect/base/join-compiler.ts
1288
1300
  var JoinCompiler = class {
1289
- /**
1290
- * Compiles all JOIN clauses from a SELECT query AST.
1291
- * @param ast - The SELECT query AST containing join definitions.
1292
- * @param ctx - The compiler context for expression compilation.
1293
- * @param compileFrom - Function to compile table sources (tables or subqueries).
1294
- * @param compileExpression - Function to compile join condition expressions.
1295
- * @returns SQL JOIN clauses (e.g., " LEFT JOIN table ON condition") or empty string if no joins.
1296
- */
1297
- static compileJoins(ast, ctx, compileFrom, compileExpression) {
1298
- if (!ast.joins || ast.joins.length === 0) return "";
1299
- const parts = ast.joins.map((j) => {
1301
+ static compileJoins(joins, ctx, compileFrom, compileExpression) {
1302
+ if (!joins || joins.length === 0) return "";
1303
+ const parts = joins.map((j) => {
1300
1304
  const table = compileFrom(j.table, ctx);
1301
1305
  const cond = compileExpression(j.condition, ctx);
1302
1306
  return `${j.kind} JOIN ${table} ON ${cond}`;
@@ -1379,25 +1383,41 @@ var SqlDialectBase = class extends Dialect {
1379
1383
  return `${ctes}${combined}${orderBy}${pagination}`;
1380
1384
  }
1381
1385
  compileInsertAst(ast, ctx) {
1386
+ if (!ast.columns.length) {
1387
+ throw new Error("INSERT queries must specify columns.");
1388
+ }
1382
1389
  const table = this.compileTableName(ast.into);
1383
1390
  const columnList = this.compileInsertColumnList(ast.columns);
1384
- const values = this.compileInsertValues(ast.values, ctx);
1391
+ const source = this.compileInsertSource(ast.source, ctx);
1385
1392
  const returning = this.compileReturning(ast.returning, ctx);
1386
- return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning}`;
1393
+ return `INSERT INTO ${table} (${columnList}) ${source}${returning}`;
1387
1394
  }
1388
1395
  compileReturning(returning, ctx) {
1389
1396
  return this.returningStrategy.compileReturning(returning, ctx);
1390
1397
  }
1398
+ compileInsertSource(source, ctx) {
1399
+ if (source.type === "InsertValues") {
1400
+ if (!source.rows.length) {
1401
+ throw new Error("INSERT ... VALUES requires at least one row.");
1402
+ }
1403
+ const values = source.rows.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
1404
+ return `VALUES ${values}`;
1405
+ }
1406
+ const normalized = this.normalizeSelectAst(source.query);
1407
+ return this.compileSelectAst(normalized, ctx).trim();
1408
+ }
1391
1409
  compileInsertColumnList(columns) {
1392
1410
  return columns.map((column) => this.quoteIdentifier(column.name)).join(", ");
1393
1411
  }
1394
- compileInsertValues(values, ctx) {
1395
- return values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
1396
- }
1397
1412
  compileSelectCore(ast, ctx) {
1398
1413
  const columns = this.compileSelectColumns(ast, ctx);
1399
1414
  const from = this.compileFrom(ast.from, ctx);
1400
- const joins = JoinCompiler.compileJoins(ast, ctx, this.compileFrom.bind(this), this.compileExpression.bind(this));
1415
+ const joins = JoinCompiler.compileJoins(
1416
+ ast.joins,
1417
+ ctx,
1418
+ this.compileFrom.bind(this),
1419
+ this.compileExpression.bind(this)
1420
+ );
1401
1421
  const whereClause = this.compileWhere(ast.where, ctx);
1402
1422
  const groupBy = GroupByCompiler.compileGroupBy(ast, (term) => this.compileOrderingTerm(term, ctx));
1403
1423
  const having = this.compileHaving(ast, ctx);
@@ -1411,25 +1431,37 @@ var SqlDialectBase = class extends Dialect {
1411
1431
  return `SELECT ${this.compileDistinct(ast)}${columns} FROM ${from}${joins}${whereClause}${groupBy}${having}${orderBy}${pagination}`;
1412
1432
  }
1413
1433
  compileUpdateAst(ast, ctx) {
1414
- const table = this.compileTableName(ast.table);
1415
- const assignments = this.compileUpdateAssignments(ast.set, ctx);
1434
+ const target = this.compileTableReference(ast.table);
1435
+ const assignments = this.compileUpdateAssignments(ast.set, ast.table, ctx);
1436
+ const fromClause = this.compileUpdateFromClause(ast, ctx);
1416
1437
  const whereClause = this.compileWhere(ast.where, ctx);
1417
1438
  const returning = this.compileReturning(ast.returning, ctx);
1418
- return `UPDATE ${table} SET ${assignments}${whereClause}${returning}`;
1439
+ return `UPDATE ${target} SET ${assignments}${fromClause}${whereClause}${returning}`;
1419
1440
  }
1420
- compileUpdateAssignments(assignments, ctx) {
1441
+ compileUpdateAssignments(assignments, table, ctx) {
1421
1442
  return assignments.map((assignment) => {
1422
1443
  const col2 = assignment.column;
1423
- const target = this.quoteIdentifier(col2.name);
1444
+ const target = this.compileQualifiedColumn(col2, table);
1424
1445
  const value = this.compileOperand(assignment.value, ctx);
1425
1446
  return `${target} = ${value}`;
1426
1447
  }).join(", ");
1427
1448
  }
1449
+ compileQualifiedColumn(column, table) {
1450
+ const baseTableName = table.name;
1451
+ const alias = table.alias;
1452
+ const columnTable = column.table ?? alias ?? baseTableName;
1453
+ const tableQualifier = alias && column.table === baseTableName ? alias : columnTable;
1454
+ if (!tableQualifier) {
1455
+ return this.quoteIdentifier(column.name);
1456
+ }
1457
+ return `${this.quoteIdentifier(tableQualifier)}.${this.quoteIdentifier(column.name)}`;
1458
+ }
1428
1459
  compileDeleteAst(ast, ctx) {
1429
- const table = this.compileTableName(ast.from);
1460
+ const target = this.compileTableReference(ast.from);
1461
+ const usingClause = this.compileDeleteUsingClause(ast, ctx);
1430
1462
  const whereClause = this.compileWhere(ast.where, ctx);
1431
1463
  const returning = this.compileReturning(ast.returning, ctx);
1432
- return `DELETE FROM ${table}${whereClause}${returning}`;
1464
+ return `DELETE FROM ${target}${usingClause}${whereClause}${returning}`;
1433
1465
  }
1434
1466
  formatReturningColumns(returning) {
1435
1467
  return this.returningStrategy.formatReturningColumns(returning, this.quoteIdentifier.bind(this));
@@ -1484,6 +1516,38 @@ var SqlDialectBase = class extends Dialect {
1484
1516
  }
1485
1517
  return this.quoteIdentifier(table.name);
1486
1518
  }
1519
+ compileTableReference(table) {
1520
+ const base = this.compileTableName(table);
1521
+ return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
1522
+ }
1523
+ compileUpdateFromClause(ast, ctx) {
1524
+ if (!ast.from && (!ast.joins || ast.joins.length === 0)) return "";
1525
+ if (!ast.from) {
1526
+ throw new Error("UPDATE with JOINs requires an explicit FROM clause.");
1527
+ }
1528
+ const from = this.compileFrom(ast.from, ctx);
1529
+ const joins = JoinCompiler.compileJoins(
1530
+ ast.joins,
1531
+ ctx,
1532
+ this.compileFrom.bind(this),
1533
+ this.compileExpression.bind(this)
1534
+ );
1535
+ return ` FROM ${from}${joins}`;
1536
+ }
1537
+ compileDeleteUsingClause(ast, ctx) {
1538
+ if (!ast.using && (!ast.joins || ast.joins.length === 0)) return "";
1539
+ if (!ast.using) {
1540
+ throw new Error("DELETE with JOINs requires a USING clause.");
1541
+ }
1542
+ const usingTable = this.compileFrom(ast.using, ctx);
1543
+ const joins = JoinCompiler.compileJoins(
1544
+ ast.joins,
1545
+ ctx,
1546
+ this.compileFrom.bind(this),
1547
+ this.compileExpression.bind(this)
1548
+ );
1549
+ return ` USING ${usingTable}${joins}`;
1550
+ }
1487
1551
  compileHaving(ast, ctx) {
1488
1552
  if (!ast.having) return "";
1489
1553
  return ` HAVING ${this.compileExpression(ast.having, ctx)}`;
@@ -1864,6 +1928,9 @@ var SqliteDialect = class extends SqlDialectBase {
1864
1928
  const col2 = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
1865
1929
  return `json_extract(${col2}, '${node.path}')`;
1866
1930
  }
1931
+ compileQualifiedColumn(column, _table) {
1932
+ return this.quoteIdentifier(column.name);
1933
+ }
1867
1934
  compileReturning(returning, ctx) {
1868
1935
  if (!returning || returning.length === 0) return "";
1869
1936
  const columns = this.formatReturningColumns(returning);
@@ -1969,7 +2036,7 @@ var MssqlFunctionStrategy = class extends StandardFunctionStrategy {
1969
2036
  };
1970
2037
 
1971
2038
  // src/core/dialect/mssql/index.ts
1972
- var SqlServerDialect = class extends Dialect {
2039
+ var SqlServerDialect = class extends SqlDialectBase {
1973
2040
  /**
1974
2041
  * Creates a new SqlServerDialect instance
1975
2042
  */
@@ -2012,7 +2079,7 @@ var SqlServerDialect = class extends Dialect {
2012
2079
  const hasSetOps = !!(ast.setOps && ast.setOps.length);
2013
2080
  const ctes = this.compileCtes(ast, ctx);
2014
2081
  const baseAst = hasSetOps ? { ...ast, setOps: void 0, orderBy: void 0, limit: void 0, offset: void 0 } : ast;
2015
- const baseSelect = this.compileSelectCore(baseAst, ctx);
2082
+ const baseSelect = this.compileSelectCoreForMssql(baseAst, ctx);
2016
2083
  if (!hasSetOps) {
2017
2084
  return `${ctes}${baseSelect}`;
2018
2085
  }
@@ -2023,32 +2090,26 @@ var SqlServerDialect = class extends Dialect {
2023
2090
  const tail = pagination || orderBy;
2024
2091
  return `${ctes}${combined}${tail}`;
2025
2092
  }
2026
- compileInsertAst(ast, ctx) {
2027
- const table = this.quoteIdentifier(ast.into.name);
2028
- const columnList = ast.columns.map((column) => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(", ");
2029
- const values = ast.values.map((row) => `(${row.map((value) => this.compileOperand(value, ctx)).join(", ")})`).join(", ");
2030
- return `INSERT INTO ${table} (${columnList}) VALUES ${values};`;
2031
- }
2032
- compileUpdateAst(ast, ctx) {
2033
- const table = this.quoteIdentifier(ast.table.name);
2034
- const assignments = ast.set.map((assignment) => {
2035
- const col2 = assignment.column;
2036
- const target = `${this.quoteIdentifier(col2.table)}.${this.quoteIdentifier(col2.name)}`;
2037
- const value = this.compileOperand(assignment.value, ctx);
2038
- return `${target} = ${value}`;
2039
- }).join(", ");
2040
- const whereClause = this.compileWhere(ast.where, ctx);
2041
- return `UPDATE ${table} SET ${assignments}${whereClause};`;
2042
- }
2043
2093
  compileDeleteAst(ast, ctx) {
2094
+ if (ast.using) {
2095
+ throw new Error("DELETE ... USING is not supported in the MSSQL dialect; use join() instead.");
2096
+ }
2044
2097
  if (ast.from.type !== "Table") {
2045
2098
  throw new Error("DELETE only supports base tables in the MSSQL dialect.");
2046
2099
  }
2047
- const table = this.quoteIdentifier(ast.from.name);
2100
+ const alias = ast.from.alias ?? ast.from.name;
2101
+ const target = this.compileTableReference(ast.from);
2102
+ const joins = JoinCompiler.compileJoins(
2103
+ ast.joins,
2104
+ ctx,
2105
+ this.compileFrom.bind(this),
2106
+ this.compileExpression.bind(this)
2107
+ );
2048
2108
  const whereClause = this.compileWhere(ast.where, ctx);
2049
- return `DELETE FROM ${table}${whereClause};`;
2109
+ const returning = this.compileReturning(ast.returning, ctx);
2110
+ return `DELETE ${this.quoteIdentifier(alias)} FROM ${target}${joins}${whereClause}${returning}`;
2050
2111
  }
2051
- compileSelectCore(ast, ctx) {
2112
+ compileSelectCoreForMssql(ast, ctx) {
2052
2113
  const columns = ast.columns.map((c) => {
2053
2114
  let expr = "";
2054
2115
  if (c.type === "Function") {
@@ -2067,9 +2128,9 @@ var SqlServerDialect = class extends Dialect {
2067
2128
  return expr;
2068
2129
  }).join(", ");
2069
2130
  const distinct = ast.distinct ? "DISTINCT " : "";
2070
- const from = this.compileTableSource(ast.from, ctx);
2131
+ const from = this.compileTableSource(ast.from);
2071
2132
  const joins = ast.joins.map((j) => {
2072
- const table = this.compileTableSource(j.table, ctx);
2133
+ const table = this.compileTableSource(j.table);
2073
2134
  const cond = this.compileExpression(j.condition, ctx);
2074
2135
  return `${j.kind} JOIN ${table} ON ${cond}`;
2075
2136
  }).join(" ");
@@ -2103,27 +2164,6 @@ var SqlServerDialect = class extends Dialect {
2103
2164
  }
2104
2165
  return pagination;
2105
2166
  }
2106
- renderOrderByNulls(order) {
2107
- return order.nulls ? ` NULLS ${order.nulls}` : "";
2108
- }
2109
- renderOrderByCollation(order) {
2110
- return order.collation ? ` COLLATE ${order.collation}` : "";
2111
- }
2112
- compileTableSource(table, ctx) {
2113
- if (table.type === "FunctionTable") {
2114
- return FunctionTableFormatter.format(table, ctx, this);
2115
- }
2116
- if (table.type === "DerivedTable") {
2117
- return this.compileDerivedTable(table, ctx);
2118
- }
2119
- const base = table.schema ? `${this.quoteIdentifier(table.schema)}.${this.quoteIdentifier(table.name)}` : this.quoteIdentifier(table.name);
2120
- return table.alias ? `${base} AS ${this.quoteIdentifier(table.alias)}` : base;
2121
- }
2122
- compileDerivedTable(table, ctx) {
2123
- const sub2 = this.compileSelectAst(this.normalizeSelectAst(table.query), ctx).trim().replace(/;$/, "");
2124
- const cols = table.columnAliases?.length ? ` (${table.columnAliases.map((c) => this.quoteIdentifier(c)).join(", ")})` : "";
2125
- return `(${sub2}) AS ${this.quoteIdentifier(table.alias)}${cols}`;
2126
- }
2127
2167
  compileCtes(ast, ctx) {
2128
2168
  if (!ast.ctes || ast.ctes.length === 0) return "";
2129
2169
  const defs = ast.ctes.map((cte) => {
@@ -2134,10 +2174,6 @@ var SqlServerDialect = class extends Dialect {
2134
2174
  }).join(", ");
2135
2175
  return `WITH ${defs} `;
2136
2176
  }
2137
- wrapSetOperand(sql) {
2138
- const trimmed = sql.trim().replace(/;$/, "");
2139
- return `(${trimmed})`;
2140
- }
2141
2177
  };
2142
2178
 
2143
2179
  // src/core/dialect/dialect-factory.ts
@@ -3932,8 +3968,10 @@ var DefaultManyToManyCollection = class {
3932
3968
  attach(target) {
3933
3969
  const entity = this.ensureEntity(target);
3934
3970
  const id = this.extractId(entity);
3935
- if (id == null) return;
3936
- if (this.items.some((item) => this.extractId(item) === id)) {
3971
+ if (id != null && this.items.some((item) => this.extractId(item) === id)) {
3972
+ return;
3973
+ }
3974
+ if (id == null && this.items.includes(entity)) {
3937
3975
  return;
3938
3976
  }
3939
3977
  this.items.push(entity);
@@ -5256,15 +5294,36 @@ var InsertQueryState = class _InsertQueryState {
5256
5294
  type: "InsertQuery",
5257
5295
  into: createTableNode(table),
5258
5296
  columns: [],
5259
- values: []
5297
+ source: {
5298
+ type: "InsertValues",
5299
+ rows: []
5300
+ }
5260
5301
  };
5261
5302
  }
5262
5303
  clone(nextAst) {
5263
5304
  return new _InsertQueryState(this.table, nextAst);
5264
5305
  }
5306
+ ensureColumnsFromRow(rows) {
5307
+ if (this.ast.columns.length) return this.ast.columns;
5308
+ return buildColumnNodes(this.table, Object.keys(rows[0]));
5309
+ }
5310
+ appendValues(rows) {
5311
+ if (this.ast.source.type === "InsertValues") {
5312
+ return [...this.ast.source.rows, ...rows];
5313
+ }
5314
+ return rows;
5315
+ }
5316
+ getTableColumns() {
5317
+ const names = Object.keys(this.table.columns);
5318
+ if (!names.length) return [];
5319
+ return buildColumnNodes(this.table, names);
5320
+ }
5265
5321
  withValues(rows) {
5266
5322
  if (!rows.length) return this;
5267
- const definedColumns = this.ast.columns.length ? this.ast.columns : buildColumnNodes(this.table, Object.keys(rows[0]));
5323
+ if (this.ast.source.type === "InsertSelect") {
5324
+ throw new Error("Cannot mix INSERT ... VALUES with INSERT ... SELECT source.");
5325
+ }
5326
+ const definedColumns = this.ensureColumnsFromRow(rows);
5268
5327
  const newRows = rows.map(
5269
5328
  (row, rowIndex) => definedColumns.map((column) => {
5270
5329
  const rawValue = row[column.name];
@@ -5279,7 +5338,34 @@ var InsertQueryState = class _InsertQueryState {
5279
5338
  return this.clone({
5280
5339
  ...this.ast,
5281
5340
  columns: definedColumns,
5282
- values: [...this.ast.values, ...newRows]
5341
+ source: {
5342
+ type: "InsertValues",
5343
+ rows: this.appendValues(newRows)
5344
+ }
5345
+ });
5346
+ }
5347
+ withColumns(columns) {
5348
+ if (!columns.length) return this;
5349
+ return this.clone({
5350
+ ...this.ast,
5351
+ columns: [...columns]
5352
+ });
5353
+ }
5354
+ withSelect(query, columns) {
5355
+ const targetColumns = columns.length ? columns : this.ast.columns.length ? this.ast.columns : this.getTableColumns();
5356
+ if (!targetColumns.length) {
5357
+ throw new Error("INSERT ... SELECT requires specifying destination columns.");
5358
+ }
5359
+ if (this.ast.source.type === "InsertValues" && this.ast.source.rows.length) {
5360
+ throw new Error("Cannot mix INSERT ... SELECT with INSERT ... VALUES source.");
5361
+ }
5362
+ return this.clone({
5363
+ ...this.ast,
5364
+ columns: [...targetColumns],
5365
+ source: {
5366
+ type: "InsertSelect",
5367
+ query
5368
+ }
5283
5369
  });
5284
5370
  }
5285
5371
  withReturning(columns) {
@@ -5304,11 +5390,27 @@ var InsertQueryBuilder = class _InsertQueryBuilder {
5304
5390
  if (!rows.length) return this;
5305
5391
  return this.clone(this.state.withValues(rows));
5306
5392
  }
5393
+ columns(...columns) {
5394
+ if (!columns.length) return this;
5395
+ return this.clone(this.state.withColumns(this.resolveColumnNodes(columns)));
5396
+ }
5397
+ fromSelect(query, columns = []) {
5398
+ const ast = this.resolveSelectQuery(query);
5399
+ const nodes = columns.length ? this.resolveColumnNodes(columns) : [];
5400
+ return this.clone(this.state.withSelect(ast, nodes));
5401
+ }
5307
5402
  returning(...columns) {
5308
5403
  if (!columns.length) return this;
5309
5404
  const nodes = columns.map((column) => buildColumnNode(this.table, column));
5310
5405
  return this.clone(this.state.withReturning(nodes));
5311
5406
  }
5407
+ // Helpers for column/AST resolution
5408
+ resolveColumnNodes(columns) {
5409
+ return columns.map((column) => buildColumnNode(this.table, column));
5410
+ }
5411
+ resolveSelectQuery(query) {
5412
+ return typeof query.getAST === "function" ? query.getAST() : query;
5413
+ }
5312
5414
  compile(arg) {
5313
5415
  if (typeof arg.compileInsert === "function") {
5314
5416
  return arg.compileInsert(this.state.ast);
@@ -5342,7 +5444,8 @@ var UpdateQueryState = class _UpdateQueryState {
5342
5444
  this.ast = ast ?? {
5343
5445
  type: "UpdateQuery",
5344
5446
  table: createTableNode(table),
5345
- set: []
5447
+ set: [],
5448
+ joins: []
5346
5449
  };
5347
5450
  }
5348
5451
  clone(nextAst) {
@@ -5381,6 +5484,27 @@ var UpdateQueryState = class _UpdateQueryState {
5381
5484
  returning: [...columns]
5382
5485
  });
5383
5486
  }
5487
+ withFrom(from) {
5488
+ return this.clone({
5489
+ ...this.ast,
5490
+ from
5491
+ });
5492
+ }
5493
+ withJoin(join) {
5494
+ return this.clone({
5495
+ ...this.ast,
5496
+ joins: [...this.ast.joins ?? [], join]
5497
+ });
5498
+ }
5499
+ withTableAlias(alias) {
5500
+ return this.clone({
5501
+ ...this.ast,
5502
+ table: {
5503
+ ...this.ast.table,
5504
+ alias
5505
+ }
5506
+ });
5507
+ }
5384
5508
  };
5385
5509
 
5386
5510
  // src/query-builder/update.ts
@@ -5392,6 +5516,18 @@ var UpdateQueryBuilder = class _UpdateQueryBuilder {
5392
5516
  clone(state) {
5393
5517
  return new _UpdateQueryBuilder(this.table, state);
5394
5518
  }
5519
+ as(alias) {
5520
+ return this.clone(this.state.withTableAlias(alias));
5521
+ }
5522
+ from(source) {
5523
+ const tableSource = this.resolveTableSource(source);
5524
+ return this.clone(this.state.withFrom(tableSource));
5525
+ }
5526
+ join(table, condition, kind = JOIN_KINDS.INNER, relationName) {
5527
+ const joinTarget = this.resolveJoinTarget(table);
5528
+ const joinNode = createJoinNode(kind, joinTarget, condition, relationName);
5529
+ return this.clone(this.state.withJoin(joinNode));
5530
+ }
5395
5531
  set(values) {
5396
5532
  return this.clone(this.state.withSet(values));
5397
5533
  }
@@ -5403,6 +5539,16 @@ var UpdateQueryBuilder = class _UpdateQueryBuilder {
5403
5539
  const nodes = columns.map((column) => buildColumnNode(this.table, column));
5404
5540
  return this.clone(this.state.withReturning(nodes));
5405
5541
  }
5542
+ resolveTableSource(source) {
5543
+ if (isTableSourceNode(source)) {
5544
+ return source;
5545
+ }
5546
+ return { type: "Table", name: source.name, schema: source.schema };
5547
+ }
5548
+ resolveJoinTarget(table) {
5549
+ if (typeof table === "string") return table;
5550
+ return this.resolveTableSource(table);
5551
+ }
5406
5552
  compile(arg) {
5407
5553
  if (typeof arg.compileUpdate === "function") {
5408
5554
  return arg.compileUpdate(this.state.ast);
@@ -5417,6 +5563,7 @@ var UpdateQueryBuilder = class _UpdateQueryBuilder {
5417
5563
  return this.state.ast;
5418
5564
  }
5419
5565
  };
5566
+ var isTableSourceNode = (source) => typeof source.type === "string";
5420
5567
 
5421
5568
  // src/query-builder/delete-query-state.ts
5422
5569
  var DeleteQueryState = class _DeleteQueryState {
@@ -5424,7 +5571,8 @@ var DeleteQueryState = class _DeleteQueryState {
5424
5571
  this.table = table;
5425
5572
  this.ast = ast ?? {
5426
5573
  type: "DeleteQuery",
5427
- from: createTableNode(table)
5574
+ from: createTableNode(table),
5575
+ joins: []
5428
5576
  };
5429
5577
  }
5430
5578
  clone(nextAst) {
@@ -5442,6 +5590,27 @@ var DeleteQueryState = class _DeleteQueryState {
5442
5590
  returning: [...columns]
5443
5591
  });
5444
5592
  }
5593
+ withUsing(source) {
5594
+ return this.clone({
5595
+ ...this.ast,
5596
+ using: source
5597
+ });
5598
+ }
5599
+ withJoin(join) {
5600
+ return this.clone({
5601
+ ...this.ast,
5602
+ joins: [...this.ast.joins ?? [], join]
5603
+ });
5604
+ }
5605
+ withTableAlias(alias) {
5606
+ return this.clone({
5607
+ ...this.ast,
5608
+ from: {
5609
+ ...this.ast.from,
5610
+ alias
5611
+ }
5612
+ });
5613
+ }
5445
5614
  };
5446
5615
 
5447
5616
  // src/query-builder/delete.ts
@@ -5456,11 +5625,32 @@ var DeleteQueryBuilder = class _DeleteQueryBuilder {
5456
5625
  where(expr) {
5457
5626
  return this.clone(this.state.withWhere(expr));
5458
5627
  }
5628
+ as(alias) {
5629
+ return this.clone(this.state.withTableAlias(alias));
5630
+ }
5631
+ using(source) {
5632
+ return this.clone(this.state.withUsing(this.resolveTableSource(source)));
5633
+ }
5634
+ join(table, condition, kind = JOIN_KINDS.INNER, relationName) {
5635
+ const target = this.resolveJoinTarget(table);
5636
+ const joinNode = createJoinNode(kind, target, condition, relationName);
5637
+ return this.clone(this.state.withJoin(joinNode));
5638
+ }
5459
5639
  returning(...columns) {
5460
5640
  if (!columns.length) return this;
5461
5641
  const nodes = columns.map((column) => buildColumnNode(this.table, column));
5462
5642
  return this.clone(this.state.withReturning(nodes));
5463
5643
  }
5644
+ resolveTableSource(source) {
5645
+ if (isTableSourceNode2(source)) {
5646
+ return source;
5647
+ }
5648
+ return { type: "Table", name: source.name, schema: source.schema };
5649
+ }
5650
+ resolveJoinTarget(table) {
5651
+ if (typeof table === "string") return table;
5652
+ return this.resolveTableSource(table);
5653
+ }
5464
5654
  compile(arg) {
5465
5655
  if (typeof arg.compileDelete === "function") {
5466
5656
  return arg.compileDelete(this.state.ast);
@@ -5475,6 +5665,7 @@ var DeleteQueryBuilder = class _DeleteQueryBuilder {
5475
5665
  return this.state.ast;
5476
5666
  }
5477
5667
  };
5668
+ var isTableSourceNode2 = (source) => typeof source.type === "string";
5478
5669
 
5479
5670
  // src/core/ddl/sql-writing.ts
5480
5671
  var resolvePrimaryKey = (table) => {
@@ -6823,9 +7014,13 @@ var TypeScriptGenerator = class {
6823
7014
  */
6824
7015
  printInExpression(inExpr) {
6825
7016
  const left2 = this.printOperand(inExpr.left);
6826
- const values = inExpr.right.map((v) => this.printOperand(v)).join(", ");
6827
7017
  const fn4 = this.mapOp(inExpr.operator);
6828
- return `${fn4}(${left2}, [${values}])`;
7018
+ if (Array.isArray(inExpr.right)) {
7019
+ const values = inExpr.right.map((v) => this.printOperand(v)).join(", ");
7020
+ return `${fn4}(${left2}, [${values}])`;
7021
+ }
7022
+ const subquery = this.inlineChain(this.buildSelectLines(inExpr.right.query));
7023
+ return `${fn4}(${left2}, (${subquery}))`;
6829
7024
  }
6830
7025
  /**
6831
7026
  * Prints a null expression to TypeScript code
@@ -7010,6 +7205,13 @@ var EntityStatus = /* @__PURE__ */ ((EntityStatus2) => {
7010
7205
 
7011
7206
  // src/orm/unit-of-work.ts
7012
7207
  var UnitOfWork = class {
7208
+ /**
7209
+ * Creates a new UnitOfWork instance.
7210
+ * @param dialect - The database dialect
7211
+ * @param executor - The database executor
7212
+ * @param identityMap - The identity map
7213
+ * @param hookContext - Function to get the hook context
7214
+ */
7013
7215
  constructor(dialect, executor, identityMap, hookContext) {
7014
7216
  this.dialect = dialect;
7015
7217
  this.executor = executor;
@@ -7017,21 +7219,50 @@ var UnitOfWork = class {
7017
7219
  this.hookContext = hookContext;
7018
7220
  this.trackedEntities = /* @__PURE__ */ new Map();
7019
7221
  }
7222
+ /**
7223
+ * Gets the identity buckets map.
7224
+ */
7020
7225
  get identityBuckets() {
7021
7226
  return this.identityMap.bucketsMap;
7022
7227
  }
7228
+ /**
7229
+ * Gets all tracked entities.
7230
+ * @returns Array of tracked entities
7231
+ */
7023
7232
  getTracked() {
7024
7233
  return Array.from(this.trackedEntities.values());
7025
7234
  }
7235
+ /**
7236
+ * Gets an entity by table and primary key.
7237
+ * @param table - The table definition
7238
+ * @param pk - The primary key value
7239
+ * @returns The entity or undefined if not found
7240
+ */
7026
7241
  getEntity(table, pk) {
7027
7242
  return this.identityMap.getEntity(table, pk);
7028
7243
  }
7244
+ /**
7245
+ * Gets all tracked entities for a specific table.
7246
+ * @param table - The table definition
7247
+ * @returns Array of tracked entities
7248
+ */
7029
7249
  getEntitiesForTable(table) {
7030
7250
  return this.identityMap.getEntitiesForTable(table);
7031
7251
  }
7252
+ /**
7253
+ * Finds a tracked entity.
7254
+ * @param entity - The entity to find
7255
+ * @returns The tracked entity or undefined if not found
7256
+ */
7032
7257
  findTracked(entity) {
7033
7258
  return this.trackedEntities.get(entity);
7034
7259
  }
7260
+ /**
7261
+ * Sets an entity in the identity map.
7262
+ * @param table - The table definition
7263
+ * @param pk - The primary key value
7264
+ * @param entity - The entity instance
7265
+ */
7035
7266
  setEntity(table, pk, entity) {
7036
7267
  if (pk === null || pk === void 0) return;
7037
7268
  let tracked = this.trackedEntities.get(entity);
@@ -7049,6 +7280,12 @@ var UnitOfWork = class {
7049
7280
  }
7050
7281
  this.registerIdentity(tracked);
7051
7282
  }
7283
+ /**
7284
+ * Tracks a new entity.
7285
+ * @param table - The table definition
7286
+ * @param entity - The entity instance
7287
+ * @param pk - Optional primary key value
7288
+ */
7052
7289
  trackNew(table, entity, pk) {
7053
7290
  const tracked = {
7054
7291
  table,
@@ -7062,6 +7299,12 @@ var UnitOfWork = class {
7062
7299
  this.registerIdentity(tracked);
7063
7300
  }
7064
7301
  }
7302
+ /**
7303
+ * Tracks a managed entity.
7304
+ * @param table - The table definition
7305
+ * @param pk - The primary key value
7306
+ * @param entity - The entity instance
7307
+ */
7065
7308
  trackManaged(table, pk, entity) {
7066
7309
  const tracked = {
7067
7310
  table,
@@ -7073,17 +7316,28 @@ var UnitOfWork = class {
7073
7316
  this.trackedEntities.set(entity, tracked);
7074
7317
  this.registerIdentity(tracked);
7075
7318
  }
7319
+ /**
7320
+ * Marks an entity as dirty (modified).
7321
+ * @param entity - The entity to mark as dirty
7322
+ */
7076
7323
  markDirty(entity) {
7077
7324
  const tracked = this.trackedEntities.get(entity);
7078
7325
  if (!tracked) return;
7079
7326
  if (tracked.status === "new" /* New */ || tracked.status === "removed" /* Removed */) return;
7080
7327
  tracked.status = "dirty" /* Dirty */;
7081
7328
  }
7329
+ /**
7330
+ * Marks an entity as removed.
7331
+ * @param entity - The entity to mark as removed
7332
+ */
7082
7333
  markRemoved(entity) {
7083
7334
  const tracked = this.trackedEntities.get(entity);
7084
7335
  if (!tracked) return;
7085
7336
  tracked.status = "removed" /* Removed */;
7086
7337
  }
7338
+ /**
7339
+ * Flushes pending changes to the database.
7340
+ */
7087
7341
  async flush() {
7088
7342
  const toFlush = Array.from(this.trackedEntities.values());
7089
7343
  for (const tracked of toFlush) {
@@ -7102,10 +7356,17 @@ var UnitOfWork = class {
7102
7356
  }
7103
7357
  }
7104
7358
  }
7359
+ /**
7360
+ * Resets the unit of work by clearing all tracked entities and identity map.
7361
+ */
7105
7362
  reset() {
7106
7363
  this.trackedEntities.clear();
7107
7364
  this.identityMap.clear();
7108
7365
  }
7366
+ /**
7367
+ * Flushes an insert operation for a new entity.
7368
+ * @param tracked - The tracked entity to insert
7369
+ */
7109
7370
  async flushInsert(tracked) {
7110
7371
  await this.runHook(tracked.table.hooks?.beforeInsert, tracked);
7111
7372
  const payload = this.extractColumns(tracked.table, tracked.entity);
@@ -7122,6 +7383,10 @@ var UnitOfWork = class {
7122
7383
  this.registerIdentity(tracked);
7123
7384
  await this.runHook(tracked.table.hooks?.afterInsert, tracked);
7124
7385
  }
7386
+ /**
7387
+ * Flushes an update operation for a modified entity.
7388
+ * @param tracked - The tracked entity to update
7389
+ */
7125
7390
  async flushUpdate(tracked) {
7126
7391
  if (tracked.pk == null) return;
7127
7392
  const changes = this.computeChanges(tracked);
@@ -7144,6 +7409,10 @@ var UnitOfWork = class {
7144
7409
  this.registerIdentity(tracked);
7145
7410
  await this.runHook(tracked.table.hooks?.afterUpdate, tracked);
7146
7411
  }
7412
+ /**
7413
+ * Flushes a delete operation for a removed entity.
7414
+ * @param tracked - The tracked entity to delete
7415
+ */
7147
7416
  async flushDelete(tracked) {
7148
7417
  if (tracked.pk == null) return;
7149
7418
  await this.runHook(tracked.table.hooks?.beforeDelete, tracked);
@@ -7157,10 +7426,20 @@ var UnitOfWork = class {
7157
7426
  this.identityMap.remove(tracked);
7158
7427
  await this.runHook(tracked.table.hooks?.afterDelete, tracked);
7159
7428
  }
7429
+ /**
7430
+ * Runs a table hook if defined.
7431
+ * @param hook - The hook function
7432
+ * @param tracked - The tracked entity
7433
+ */
7160
7434
  async runHook(hook, tracked) {
7161
7435
  if (!hook) return;
7162
7436
  await hook(this.hookContext(), tracked.entity);
7163
7437
  }
7438
+ /**
7439
+ * Computes changes between current entity state and original snapshot.
7440
+ * @param tracked - The tracked entity
7441
+ * @returns Object with changed column values
7442
+ */
7164
7443
  computeChanges(tracked) {
7165
7444
  const snapshot = tracked.original ?? {};
7166
7445
  const changes = {};
@@ -7172,6 +7451,12 @@ var UnitOfWork = class {
7172
7451
  }
7173
7452
  return changes;
7174
7453
  }
7454
+ /**
7455
+ * Extracts column values from an entity.
7456
+ * @param table - The table definition
7457
+ * @param entity - The entity instance
7458
+ * @returns Object with column values
7459
+ */
7175
7460
  extractColumns(table, entity) {
7176
7461
  const payload = {};
7177
7462
  for (const column of Object.keys(table.columns)) {
@@ -7180,9 +7465,19 @@ var UnitOfWork = class {
7180
7465
  }
7181
7466
  return payload;
7182
7467
  }
7468
+ /**
7469
+ * Executes a compiled query.
7470
+ * @param compiled - The compiled query
7471
+ * @returns Query results
7472
+ */
7183
7473
  async executeCompiled(compiled) {
7184
7474
  return this.executor.executeSql(compiled.sql, compiled.params);
7185
7475
  }
7476
+ /**
7477
+ * Gets columns for RETURNING clause.
7478
+ * @param table - The table definition
7479
+ * @returns Array of column nodes
7480
+ */
7186
7481
  getReturningColumns(table) {
7187
7482
  return Object.values(table.columns).map((column) => ({
7188
7483
  type: "Column",
@@ -7191,6 +7486,11 @@ var UnitOfWork = class {
7191
7486
  alias: column.name
7192
7487
  }));
7193
7488
  }
7489
+ /**
7490
+ * Applies RETURNING clause results to the tracked entity.
7491
+ * @param tracked - The tracked entity
7492
+ * @param results - Query results
7493
+ */
7194
7494
  applyReturningResults(tracked, results) {
7195
7495
  if (!this.dialect.supportsReturning()) return;
7196
7496
  const first = results[0];
@@ -7202,15 +7502,30 @@ var UnitOfWork = class {
7202
7502
  tracked.entity[columnName] = row[i];
7203
7503
  }
7204
7504
  }
7505
+ /**
7506
+ * Normalizes a column name by removing quotes and table prefixes.
7507
+ * @param column - The column name to normalize
7508
+ * @returns Normalized column name
7509
+ */
7205
7510
  normalizeColumnName(column) {
7206
7511
  const parts = column.split(".");
7207
7512
  const candidate = parts[parts.length - 1];
7208
7513
  return candidate.replace(/^["`[\]]+|["`[\]]+$/g, "");
7209
7514
  }
7515
+ /**
7516
+ * Registers an entity in the identity map.
7517
+ * @param tracked - The tracked entity to register
7518
+ */
7210
7519
  registerIdentity(tracked) {
7211
7520
  if (tracked.pk == null) return;
7212
7521
  this.identityMap.register(tracked);
7213
7522
  }
7523
+ /**
7524
+ * Creates a snapshot of an entity's current state.
7525
+ * @param table - The table definition
7526
+ * @param entity - The entity instance
7527
+ * @returns Object with entity state
7528
+ */
7214
7529
  createSnapshot(table, entity) {
7215
7530
  const snapshot = {};
7216
7531
  for (const column of Object.keys(table.columns)) {
@@ -7218,6 +7533,11 @@ var UnitOfWork = class {
7218
7533
  }
7219
7534
  return snapshot;
7220
7535
  }
7536
+ /**
7537
+ * Gets the primary key value from a tracked entity.
7538
+ * @param tracked - The tracked entity
7539
+ * @returns Primary key value or null
7540
+ */
7221
7541
  getPrimaryKeyValue(tracked) {
7222
7542
  const key = findPrimaryKey(tracked.table);
7223
7543
  const val = tracked.entity[key];
@@ -7228,6 +7548,10 @@ var UnitOfWork = class {
7228
7548
 
7229
7549
  // src/orm/domain-event-bus.ts
7230
7550
  var DomainEventBus = class {
7551
+ /**
7552
+ * Creates a new DomainEventBus instance.
7553
+ * @param initialHandlers - Optional initial event handlers
7554
+ */
7231
7555
  constructor(initialHandlers) {
7232
7556
  this.handlers = /* @__PURE__ */ new Map();
7233
7557
  if (initialHandlers) {
@@ -7238,15 +7562,32 @@ var DomainEventBus = class {
7238
7562
  }
7239
7563
  }
7240
7564
  }
7565
+ /**
7566
+ * Registers an event handler for a specific event type.
7567
+ * @template TType - The event type
7568
+ * @param type - The event type
7569
+ * @param handler - The event handler
7570
+ */
7241
7571
  on(type, handler) {
7242
7572
  const key = type;
7243
7573
  const existing = this.handlers.get(key) ?? [];
7244
7574
  existing.push(handler);
7245
7575
  this.handlers.set(key, existing);
7246
7576
  }
7577
+ /**
7578
+ * Registers an event handler for a specific event type (alias for on).
7579
+ * @template TType - The event type
7580
+ * @param type - The event type
7581
+ * @param handler - The event handler
7582
+ */
7247
7583
  register(type, handler) {
7248
7584
  this.on(type, handler);
7249
7585
  }
7586
+ /**
7587
+ * Dispatches domain events for tracked entities.
7588
+ * @param trackedEntities - Iterable of tracked entities
7589
+ * @param ctx - The context to pass to handlers
7590
+ */
7250
7591
  async dispatch(trackedEntities, ctx) {
7251
7592
  for (const tracked of trackedEntities) {
7252
7593
  const entity = tracked.entity;
@@ -7271,18 +7612,34 @@ var addDomainEvent = (entity, event) => {
7271
7612
 
7272
7613
  // src/orm/relation-change-processor.ts
7273
7614
  var RelationChangeProcessor = class {
7615
+ /**
7616
+ * Creates a new RelationChangeProcessor instance.
7617
+ * @param unitOfWork - The unit of work instance
7618
+ * @param dialect - The database dialect
7619
+ * @param executor - The database executor
7620
+ */
7274
7621
  constructor(unitOfWork, dialect, executor) {
7275
7622
  this.unitOfWork = unitOfWork;
7276
7623
  this.dialect = dialect;
7277
7624
  this.executor = executor;
7278
7625
  this.relationChanges = [];
7279
7626
  }
7627
+ /**
7628
+ * Registers a relation change for processing.
7629
+ * @param entry - The relation change entry
7630
+ */
7280
7631
  registerChange(entry) {
7281
7632
  this.relationChanges.push(entry);
7282
7633
  }
7634
+ /**
7635
+ * Resets the relation change processor by clearing all pending changes.
7636
+ */
7283
7637
  reset() {
7284
7638
  this.relationChanges.length = 0;
7285
7639
  }
7640
+ /**
7641
+ * Processes all pending relation changes.
7642
+ */
7286
7643
  async process() {
7287
7644
  if (!this.relationChanges.length) return;
7288
7645
  const entries = [...this.relationChanges];
@@ -7304,6 +7661,10 @@ var RelationChangeProcessor = class {
7304
7661
  }
7305
7662
  }
7306
7663
  }
7664
+ /**
7665
+ * Handles changes for has-many relations.
7666
+ * @param entry - The relation change entry
7667
+ */
7307
7668
  async handleHasManyChange(entry) {
7308
7669
  const relation = entry.relation;
7309
7670
  const target = entry.change.entity;
@@ -7322,6 +7683,10 @@ var RelationChangeProcessor = class {
7322
7683
  this.detachHasManyChild(tracked.entity, relation);
7323
7684
  }
7324
7685
  }
7686
+ /**
7687
+ * Handles changes for has-one relations.
7688
+ * @param entry - The relation change entry
7689
+ */
7325
7690
  async handleHasOneChange(entry) {
7326
7691
  const relation = entry.relation;
7327
7692
  const target = entry.change.entity;
@@ -7340,8 +7705,16 @@ var RelationChangeProcessor = class {
7340
7705
  this.detachHasOneChild(tracked.entity, relation);
7341
7706
  }
7342
7707
  }
7708
+ /**
7709
+ * Handles changes for belongs-to relations.
7710
+ * @param _entry - The relation change entry (reserved for future use)
7711
+ */
7343
7712
  async handleBelongsToChange(_entry) {
7344
7713
  }
7714
+ /**
7715
+ * Handles changes for belongs-to-many relations.
7716
+ * @param entry - The relation change entry
7717
+ */
7345
7718
  async handleBelongsToManyChange(entry) {
7346
7719
  const relation = entry.relation;
7347
7720
  const rootKey = relation.localKey || findPrimaryKey(entry.rootTable);
@@ -7360,11 +7733,22 @@ var RelationChangeProcessor = class {
7360
7733
  }
7361
7734
  }
7362
7735
  }
7736
+ /**
7737
+ * Assigns a foreign key for has-many relations.
7738
+ * @param child - The child entity
7739
+ * @param relation - The has-many relation
7740
+ * @param rootValue - The root entity's primary key value
7741
+ */
7363
7742
  assignHasManyForeignKey(child, relation, rootValue) {
7364
7743
  const current = child[relation.foreignKey];
7365
7744
  if (current === rootValue) return;
7366
7745
  child[relation.foreignKey] = rootValue;
7367
7746
  }
7747
+ /**
7748
+ * Detaches a child entity from has-many relations.
7749
+ * @param child - The child entity
7750
+ * @param relation - The has-many relation
7751
+ */
7368
7752
  detachHasManyChild(child, relation) {
7369
7753
  if (relation.cascade === "all" || relation.cascade === "remove") {
7370
7754
  this.unitOfWork.markRemoved(child);
@@ -7373,11 +7757,22 @@ var RelationChangeProcessor = class {
7373
7757
  child[relation.foreignKey] = null;
7374
7758
  this.unitOfWork.markDirty(child);
7375
7759
  }
7760
+ /**
7761
+ * Assigns a foreign key for has-one relations.
7762
+ * @param child - The child entity
7763
+ * @param relation - The has-one relation
7764
+ * @param rootValue - The root entity's primary key value
7765
+ */
7376
7766
  assignHasOneForeignKey(child, relation, rootValue) {
7377
7767
  const current = child[relation.foreignKey];
7378
7768
  if (current === rootValue) return;
7379
7769
  child[relation.foreignKey] = rootValue;
7380
7770
  }
7771
+ /**
7772
+ * Detaches a child entity from has-one relations.
7773
+ * @param child - The child entity
7774
+ * @param relation - The has-one relation
7775
+ */
7381
7776
  detachHasOneChild(child, relation) {
7382
7777
  if (relation.cascade === "all" || relation.cascade === "remove") {
7383
7778
  this.unitOfWork.markRemoved(child);
@@ -7386,6 +7781,12 @@ var RelationChangeProcessor = class {
7386
7781
  child[relation.foreignKey] = null;
7387
7782
  this.unitOfWork.markDirty(child);
7388
7783
  }
7784
+ /**
7785
+ * Inserts a pivot row for belongs-to-many relations.
7786
+ * @param relation - The belongs-to-many relation
7787
+ * @param rootId - The root entity's primary key value
7788
+ * @param targetId - The target entity's primary key value
7789
+ */
7389
7790
  async insertPivotRow(relation, rootId, targetId) {
7390
7791
  const payload = {
7391
7792
  [relation.pivotForeignKeyToRoot]: rootId,
@@ -7395,6 +7796,12 @@ var RelationChangeProcessor = class {
7395
7796
  const compiled = builder.compile(this.dialect);
7396
7797
  await this.executor.executeSql(compiled.sql, compiled.params);
7397
7798
  }
7799
+ /**
7800
+ * Deletes a pivot row for belongs-to-many relations.
7801
+ * @param relation - The belongs-to-many relation
7802
+ * @param rootId - The root entity's primary key value
7803
+ * @param targetId - The target entity's primary key value
7804
+ */
7398
7805
  async deletePivotRow(relation, rootId, targetId) {
7399
7806
  const rootCol = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
7400
7807
  const targetCol = relation.pivotTable.columns[relation.pivotForeignKeyToTarget];
@@ -7405,6 +7812,12 @@ var RelationChangeProcessor = class {
7405
7812
  const compiled = builder.compile(this.dialect);
7406
7813
  await this.executor.executeSql(compiled.sql, compiled.params);
7407
7814
  }
7815
+ /**
7816
+ * Resolves the primary key value from an entity.
7817
+ * @param entity - The entity
7818
+ * @param table - The table definition
7819
+ * @returns The primary key value or null
7820
+ */
7408
7821
  resolvePrimaryKeyValue(entity, table) {
7409
7822
  if (!entity) return null;
7410
7823
  const key = findPrimaryKey(table);
@@ -7420,42 +7833,231 @@ var createQueryLoggingExecutor = (executor, logger) => {
7420
7833
  return executor;
7421
7834
  }
7422
7835
  const wrapped = {
7836
+ capabilities: executor.capabilities,
7423
7837
  async executeSql(sql, params) {
7424
7838
  logger({ sql, params });
7425
7839
  return executor.executeSql(sql, params);
7426
- }
7840
+ },
7841
+ beginTransaction: () => executor.beginTransaction(),
7842
+ commitTransaction: () => executor.commitTransaction(),
7843
+ rollbackTransaction: () => executor.rollbackTransaction(),
7844
+ dispose: () => executor.dispose()
7427
7845
  };
7428
- if (executor.beginTransaction) {
7429
- wrapped.beginTransaction = executor.beginTransaction.bind(executor);
7430
- }
7431
- if (executor.commitTransaction) {
7432
- wrapped.commitTransaction = executor.commitTransaction.bind(executor);
7433
- }
7434
- if (executor.rollbackTransaction) {
7435
- wrapped.rollbackTransaction = executor.rollbackTransaction.bind(executor);
7436
- }
7437
7846
  return wrapped;
7438
7847
  };
7439
7848
 
7440
7849
  // src/orm/transaction-runner.ts
7441
7850
  var runInTransaction = async (executor, action) => {
7442
- if (!executor.beginTransaction) {
7851
+ if (!executor.capabilities.transactions) {
7443
7852
  await action();
7444
7853
  return;
7445
7854
  }
7446
7855
  await executor.beginTransaction();
7447
7856
  try {
7448
7857
  await action();
7449
- await executor.commitTransaction?.();
7858
+ await executor.commitTransaction();
7450
7859
  } catch (error) {
7451
- await executor.rollbackTransaction?.();
7860
+ await executor.rollbackTransaction();
7452
7861
  throw error;
7453
7862
  }
7454
7863
  };
7455
7864
 
7865
+ // src/orm/save-graph.ts
7866
+ var toKey8 = (value) => value === null || value === void 0 ? "" : String(value);
7867
+ var pickColumns = (table, payload) => {
7868
+ const columns = {};
7869
+ for (const key of Object.keys(table.columns)) {
7870
+ if (payload[key] !== void 0) {
7871
+ columns[key] = payload[key];
7872
+ }
7873
+ }
7874
+ return columns;
7875
+ };
7876
+ var ensureEntity = (session, table, payload) => {
7877
+ const pk = findPrimaryKey(table);
7878
+ const row = pickColumns(table, payload);
7879
+ const pkValue = payload[pk];
7880
+ if (pkValue !== void 0 && pkValue !== null) {
7881
+ const tracked = session.getEntity(table, pkValue);
7882
+ if (tracked) {
7883
+ return tracked;
7884
+ }
7885
+ if (row[pk] === void 0) {
7886
+ row[pk] = pkValue;
7887
+ }
7888
+ }
7889
+ return createEntityFromRow(session, table, row);
7890
+ };
7891
+ var assignColumns = (table, entity, payload) => {
7892
+ for (const key of Object.keys(table.columns)) {
7893
+ if (payload[key] !== void 0) {
7894
+ entity[key] = payload[key];
7895
+ }
7896
+ }
7897
+ };
7898
+ var isEntityInCollection = (items, pkName, entity) => {
7899
+ if (items.includes(entity)) return true;
7900
+ const entityPk = entity[pkName];
7901
+ if (entityPk === void 0 || entityPk === null) return false;
7902
+ return items.some((item) => toKey8(item[pkName]) === toKey8(entityPk));
7903
+ };
7904
+ var findInCollectionByPk = (items, pkName, pkValue) => {
7905
+ if (pkValue === void 0 || pkValue === null) return void 0;
7906
+ return items.find((item) => toKey8(item[pkName]) === toKey8(pkValue));
7907
+ };
7908
+ var handleHasMany = async (session, root, relationName, relation, payload, options) => {
7909
+ if (!Array.isArray(payload)) return;
7910
+ const collection = root[relationName];
7911
+ await collection.load();
7912
+ const targetTable = relation.target;
7913
+ const targetPk = findPrimaryKey(targetTable);
7914
+ const existing = collection.getItems();
7915
+ const seen = /* @__PURE__ */ new Set();
7916
+ for (const item of payload) {
7917
+ if (item === null || item === void 0) continue;
7918
+ const asObj = typeof item === "object" ? item : { [targetPk]: item };
7919
+ const pkValue = asObj[targetPk];
7920
+ const current = findInCollectionByPk(existing, targetPk, pkValue) ?? (pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) : void 0);
7921
+ const entity = current ?? ensureEntity(session, targetTable, asObj);
7922
+ assignColumns(targetTable, entity, asObj);
7923
+ await applyGraphToEntity(session, targetTable, entity, asObj, options);
7924
+ if (!isEntityInCollection(collection.getItems(), targetPk, entity)) {
7925
+ collection.attach(entity);
7926
+ }
7927
+ if (pkValue !== void 0 && pkValue !== null) {
7928
+ seen.add(toKey8(pkValue));
7929
+ }
7930
+ }
7931
+ if (options.pruneMissing) {
7932
+ for (const item of [...collection.getItems()]) {
7933
+ const pkValue = item[targetPk];
7934
+ if (pkValue !== void 0 && pkValue !== null && !seen.has(toKey8(pkValue))) {
7935
+ collection.remove(item);
7936
+ }
7937
+ }
7938
+ }
7939
+ };
7940
+ var handleHasOne = async (session, root, relationName, relation, payload, options) => {
7941
+ const ref = root[relationName];
7942
+ if (payload === void 0) return;
7943
+ if (payload === null) {
7944
+ ref.set(null);
7945
+ return;
7946
+ }
7947
+ const pk = findPrimaryKey(relation.target);
7948
+ if (typeof payload === "number" || typeof payload === "string") {
7949
+ const entity = ref.set({ [pk]: payload });
7950
+ if (entity) {
7951
+ await applyGraphToEntity(session, relation.target, entity, { [pk]: payload }, options);
7952
+ }
7953
+ return;
7954
+ }
7955
+ const attached = ref.set(payload);
7956
+ if (attached) {
7957
+ await applyGraphToEntity(session, relation.target, attached, payload, options);
7958
+ }
7959
+ };
7960
+ var handleBelongsTo = async (session, root, relationName, relation, payload, options) => {
7961
+ const ref = root[relationName];
7962
+ if (payload === void 0) return;
7963
+ if (payload === null) {
7964
+ ref.set(null);
7965
+ return;
7966
+ }
7967
+ const pk = relation.localKey || findPrimaryKey(relation.target);
7968
+ if (typeof payload === "number" || typeof payload === "string") {
7969
+ const entity = ref.set({ [pk]: payload });
7970
+ if (entity) {
7971
+ await applyGraphToEntity(session, relation.target, entity, { [pk]: payload }, options);
7972
+ }
7973
+ return;
7974
+ }
7975
+ const attached = ref.set(payload);
7976
+ if (attached) {
7977
+ await applyGraphToEntity(session, relation.target, attached, payload, options);
7978
+ }
7979
+ };
7980
+ var handleBelongsToMany = async (session, root, relationName, relation, payload, options) => {
7981
+ if (!Array.isArray(payload)) return;
7982
+ const collection = root[relationName];
7983
+ await collection.load();
7984
+ const targetTable = relation.target;
7985
+ const targetPk = relation.targetKey || findPrimaryKey(targetTable);
7986
+ const seen = /* @__PURE__ */ new Set();
7987
+ for (const item of payload) {
7988
+ if (item === null || item === void 0) continue;
7989
+ if (typeof item === "number" || typeof item === "string") {
7990
+ const id = item;
7991
+ collection.attach(id);
7992
+ seen.add(toKey8(id));
7993
+ continue;
7994
+ }
7995
+ const asObj = item;
7996
+ const pkValue = asObj[targetPk];
7997
+ const entity = pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) ?? ensureEntity(session, targetTable, asObj) : ensureEntity(session, targetTable, asObj);
7998
+ assignColumns(targetTable, entity, asObj);
7999
+ await applyGraphToEntity(session, targetTable, entity, asObj, options);
8000
+ if (!isEntityInCollection(collection.getItems(), targetPk, entity)) {
8001
+ collection.attach(entity);
8002
+ }
8003
+ if (pkValue !== void 0 && pkValue !== null) {
8004
+ seen.add(toKey8(pkValue));
8005
+ }
8006
+ }
8007
+ if (options.pruneMissing) {
8008
+ for (const item of [...collection.getItems()]) {
8009
+ const pkValue = item[targetPk];
8010
+ if (pkValue !== void 0 && pkValue !== null && !seen.has(toKey8(pkValue))) {
8011
+ collection.detach(item);
8012
+ }
8013
+ }
8014
+ }
8015
+ };
8016
+ var applyRelation = async (session, table, entity, relationName, relation, payload, options) => {
8017
+ switch (relation.type) {
8018
+ case RelationKinds.HasMany:
8019
+ return handleHasMany(session, entity, relationName, relation, payload, options);
8020
+ case RelationKinds.HasOne:
8021
+ return handleHasOne(session, entity, relationName, relation, payload, options);
8022
+ case RelationKinds.BelongsTo:
8023
+ return handleBelongsTo(session, entity, relationName, relation, payload, options);
8024
+ case RelationKinds.BelongsToMany:
8025
+ return handleBelongsToMany(session, entity, relationName, relation, payload, options);
8026
+ }
8027
+ };
8028
+ var applyGraphToEntity = async (session, table, entity, payload, options) => {
8029
+ assignColumns(table, entity, payload);
8030
+ for (const [relationName, relation] of Object.entries(table.relations)) {
8031
+ if (!(relationName in payload)) continue;
8032
+ await applyRelation(session, table, entity, relationName, relation, payload[relationName], options);
8033
+ }
8034
+ };
8035
+ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
8036
+ const table = getTableDefFromEntity(entityClass);
8037
+ if (!table) {
8038
+ throw new Error("Entity metadata has not been bootstrapped");
8039
+ }
8040
+ const root = ensureEntity(session, table, payload);
8041
+ await applyGraphToEntity(session, table, root, payload, options);
8042
+ return root;
8043
+ };
8044
+
7456
8045
  // src/orm/orm-session.ts
7457
8046
  var OrmSession = class {
8047
+ /**
8048
+ * Creates a new OrmSession instance.
8049
+ * @param opts - Session options
8050
+ */
7458
8051
  constructor(opts) {
8052
+ /**
8053
+ * Registers a relation change.
8054
+ * @param root - The root entity
8055
+ * @param relationKey - The relation key
8056
+ * @param rootTable - The root table definition
8057
+ * @param relationName - The relation name
8058
+ * @param relation - The relation definition
8059
+ * @param change - The relation change
8060
+ */
7459
8061
  this.registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
7460
8062
  this.relationChanges.registerChange(
7461
8063
  buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
@@ -7469,42 +8071,117 @@ var OrmSession = class {
7469
8071
  this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
7470
8072
  this.domainEvents = new DomainEventBus(opts.domainEventHandlers);
7471
8073
  }
8074
+ /**
8075
+ * Releases resources associated with this session (executor/pool leases) and resets tracking.
8076
+ * Must be safe to call multiple times.
8077
+ */
8078
+ async dispose() {
8079
+ try {
8080
+ await this.executor.dispose();
8081
+ } finally {
8082
+ this.unitOfWork.reset();
8083
+ this.relationChanges.reset();
8084
+ }
8085
+ }
8086
+ /**
8087
+ * Gets the database dialect.
8088
+ */
7472
8089
  get dialect() {
7473
8090
  return this.orm.dialect;
7474
8091
  }
8092
+ /**
8093
+ * Gets the identity buckets map.
8094
+ */
7475
8095
  get identityBuckets() {
7476
8096
  return this.unitOfWork.identityBuckets;
7477
8097
  }
8098
+ /**
8099
+ * Gets all tracked entities.
8100
+ */
7478
8101
  get tracked() {
7479
8102
  return this.unitOfWork.getTracked();
7480
8103
  }
8104
+ /**
8105
+ * Gets an entity by table and primary key.
8106
+ * @param table - The table definition
8107
+ * @param pk - The primary key value
8108
+ * @returns The entity or undefined if not found
8109
+ */
7481
8110
  getEntity(table, pk) {
7482
8111
  return this.unitOfWork.getEntity(table, pk);
7483
8112
  }
8113
+ /**
8114
+ * Sets an entity in the identity map.
8115
+ * @param table - The table definition
8116
+ * @param pk - The primary key value
8117
+ * @param entity - The entity instance
8118
+ */
7484
8119
  setEntity(table, pk, entity) {
7485
8120
  this.unitOfWork.setEntity(table, pk, entity);
7486
8121
  }
8122
+ /**
8123
+ * Tracks a new entity.
8124
+ * @param table - The table definition
8125
+ * @param entity - The entity instance
8126
+ * @param pk - Optional primary key value
8127
+ */
7487
8128
  trackNew(table, entity, pk) {
7488
8129
  this.unitOfWork.trackNew(table, entity, pk);
7489
8130
  }
8131
+ /**
8132
+ * Tracks a managed entity.
8133
+ * @param table - The table definition
8134
+ * @param pk - The primary key value
8135
+ * @param entity - The entity instance
8136
+ */
7490
8137
  trackManaged(table, pk, entity) {
7491
8138
  this.unitOfWork.trackManaged(table, pk, entity);
7492
8139
  }
8140
+ /**
8141
+ * Marks an entity as dirty (modified).
8142
+ * @param entity - The entity to mark as dirty
8143
+ */
7493
8144
  markDirty(entity) {
7494
8145
  this.unitOfWork.markDirty(entity);
7495
8146
  }
8147
+ /**
8148
+ * Marks an entity as removed.
8149
+ * @param entity - The entity to mark as removed
8150
+ */
7496
8151
  markRemoved(entity) {
7497
8152
  this.unitOfWork.markRemoved(entity);
7498
8153
  }
8154
+ /**
8155
+ * Gets all tracked entities for a specific table.
8156
+ * @param table - The table definition
8157
+ * @returns Array of tracked entities
8158
+ */
7499
8159
  getEntitiesForTable(table) {
7500
8160
  return this.unitOfWork.getEntitiesForTable(table);
7501
8161
  }
8162
+ /**
8163
+ * Registers an interceptor for flush lifecycle hooks.
8164
+ * @param interceptor - The interceptor to register
8165
+ */
7502
8166
  registerInterceptor(interceptor) {
7503
8167
  this.interceptors.push(interceptor);
7504
8168
  }
8169
+ /**
8170
+ * Registers a domain event handler.
8171
+ * @param type - The event type
8172
+ * @param handler - The event handler
8173
+ */
7505
8174
  registerDomainEventHandler(type, handler) {
7506
8175
  this.domainEvents.on(type, handler);
7507
8176
  }
8177
+ /**
8178
+ * Finds an entity by its primary key.
8179
+ * @template TCtor - The entity constructor type
8180
+ * @param entityClass - The entity constructor
8181
+ * @param id - The primary key value
8182
+ * @returns The entity instance or null if not found
8183
+ * @throws If entity metadata is not bootstrapped or table has no primary key
8184
+ */
7508
8185
  async find(entityClass, id) {
7509
8186
  const table = getTableDefFromEntity(entityClass);
7510
8187
  if (!table) {
@@ -7523,14 +8200,46 @@ var OrmSession = class {
7523
8200
  const rows = await executeHydrated(this, qb);
7524
8201
  return rows[0] ?? null;
7525
8202
  }
8203
+ /**
8204
+ * Finds a single entity using a query builder.
8205
+ * @template TTable - The table type
8206
+ * @param qb - The query builder
8207
+ * @returns The first entity instance or null if not found
8208
+ */
7526
8209
  async findOne(qb) {
7527
8210
  const limited = qb.limit(1);
7528
8211
  const rows = await executeHydrated(this, limited);
7529
8212
  return rows[0] ?? null;
7530
8213
  }
8214
+ /**
8215
+ * Finds multiple entities using a query builder.
8216
+ * @template TTable - The table type
8217
+ * @param qb - The query builder
8218
+ * @returns Array of entity instances
8219
+ */
7531
8220
  async findMany(qb) {
7532
8221
  return executeHydrated(this, qb);
7533
8222
  }
8223
+ /**
8224
+ * Saves an entity graph (root + nested relations) based on a DTO-like payload.
8225
+ * @param entityClass - Root entity constructor
8226
+ * @param payload - DTO payload containing column values and nested relations
8227
+ * @param options - Graph save options
8228
+ * @returns The root entity instance
8229
+ */
8230
+ async saveGraph(entityClass, payload, options) {
8231
+ const { transactional = true, ...graphOptions } = options ?? {};
8232
+ const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
8233
+ if (!transactional) {
8234
+ return execute();
8235
+ }
8236
+ return this.transaction(() => execute());
8237
+ }
8238
+ /**
8239
+ * Persists an entity (either inserts or updates).
8240
+ * @param entity - The entity to persist
8241
+ * @throws If entity metadata is not bootstrapped
8242
+ */
7534
8243
  async persist(entity) {
7535
8244
  if (this.unitOfWork.findTracked(entity)) {
7536
8245
  return;
@@ -7547,12 +8256,22 @@ var OrmSession = class {
7547
8256
  this.trackNew(table, entity);
7548
8257
  }
7549
8258
  }
8259
+ /**
8260
+ * Marks an entity for removal.
8261
+ * @param entity - The entity to remove
8262
+ */
7550
8263
  async remove(entity) {
7551
8264
  this.markRemoved(entity);
7552
8265
  }
8266
+ /**
8267
+ * Flushes pending changes to the database.
8268
+ */
7553
8269
  async flush() {
7554
8270
  await this.unitOfWork.flush();
7555
8271
  }
8272
+ /**
8273
+ * Flushes pending changes with interceptors and relation processing.
8274
+ */
7556
8275
  async flushWithHooks() {
7557
8276
  for (const interceptor of this.interceptors) {
7558
8277
  await interceptor.beforeFlush?.(this);
@@ -7564,14 +8283,24 @@ var OrmSession = class {
7564
8283
  await interceptor.afterFlush?.(this);
7565
8284
  }
7566
8285
  }
8286
+ /**
8287
+ * Commits the current transaction.
8288
+ */
7567
8289
  async commit() {
7568
8290
  await runInTransaction(this.executor, async () => {
7569
8291
  await this.flushWithHooks();
7570
8292
  });
7571
8293
  await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
7572
8294
  }
8295
+ /**
8296
+ * Executes a function within a transaction.
8297
+ * @template T - The return type
8298
+ * @param fn - The function to execute
8299
+ * @returns The result of the function
8300
+ * @throws If the transaction fails
8301
+ */
7573
8302
  async transaction(fn4) {
7574
- if (!this.executor.beginTransaction) {
8303
+ if (!this.executor.capabilities.transactions) {
7575
8304
  const result = await fn4(this);
7576
8305
  await this.commit();
7577
8306
  return result;
@@ -7580,7 +8309,7 @@ var OrmSession = class {
7580
8309
  try {
7581
8310
  const result = await fn4(this);
7582
8311
  await this.flushWithHooks();
7583
- await this.executor.commitTransaction?.();
8312
+ await this.executor.commitTransaction();
7584
8313
  await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
7585
8314
  return result;
7586
8315
  } catch (err) {
@@ -7588,11 +8317,20 @@ var OrmSession = class {
7588
8317
  throw err;
7589
8318
  }
7590
8319
  }
8320
+ /**
8321
+ * Rolls back the current transaction.
8322
+ */
7591
8323
  async rollback() {
7592
- await this.executor.rollbackTransaction?.();
8324
+ if (this.executor.capabilities.transactions) {
8325
+ await this.executor.rollbackTransaction();
8326
+ }
7593
8327
  this.unitOfWork.reset();
7594
8328
  this.relationChanges.reset();
7595
8329
  }
8330
+ /**
8331
+ * Gets the execution context.
8332
+ * @returns The execution context
8333
+ */
7596
8334
  getExecutionContext() {
7597
8335
  return {
7598
8336
  dialect: this.orm.dialect,
@@ -7600,6 +8338,10 @@ var OrmSession = class {
7600
8338
  interceptors: this.orm.interceptors
7601
8339
  };
7602
8340
  }
8341
+ /**
8342
+ * Gets the hydration context.
8343
+ * @returns The hydration context
8344
+ */
7603
8345
  getHydrationContext() {
7604
8346
  return {
7605
8347
  identityMap: this.identityMap,
@@ -7642,29 +8384,49 @@ var InterceptorPipeline = class {
7642
8384
 
7643
8385
  // src/orm/orm.ts
7644
8386
  var Orm = class {
8387
+ /**
8388
+ * Creates a new ORM instance.
8389
+ * @param opts - ORM options
8390
+ */
7645
8391
  constructor(opts) {
7646
8392
  this.dialect = opts.dialect;
7647
8393
  this.interceptors = opts.interceptors ?? new InterceptorPipeline();
7648
8394
  this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
7649
8395
  this.executorFactory = opts.executorFactory;
7650
8396
  }
7651
- createSession(options) {
7652
- const executor = this.executorFactory.createExecutor(options?.tx);
8397
+ /**
8398
+ * Creates a new ORM session.
8399
+ * @param options - Optional session options
8400
+ * @returns The ORM session
8401
+ */
8402
+ createSession() {
8403
+ const executor = this.executorFactory.createExecutor();
7653
8404
  return new OrmSession({ orm: this, executor });
7654
8405
  }
8406
+ /**
8407
+ * Executes a function within a transaction.
8408
+ * @template T - The return type
8409
+ * @param fn - The function to execute
8410
+ * @returns The result of the function
8411
+ * @throws If the transaction fails
8412
+ */
7655
8413
  async transaction(fn4) {
7656
8414
  const executor = this.executorFactory.createTransactionalExecutor();
7657
8415
  const session = new OrmSession({ orm: this, executor });
7658
8416
  try {
7659
- const result = await fn4(session);
7660
- await session.commit();
7661
- return result;
8417
+ return await session.transaction(() => fn4(session));
7662
8418
  } catch (err) {
7663
- await session.rollback();
7664
8419
  throw err;
7665
8420
  } finally {
8421
+ await session.dispose();
7666
8422
  }
7667
8423
  }
8424
+ /**
8425
+ * Shuts down the ORM and releases underlying resources (pools, timers).
8426
+ */
8427
+ async dispose() {
8428
+ await this.executorFactory.dispose();
8429
+ }
7668
8430
  };
7669
8431
 
7670
8432
  // src/decorators/decorator-metadata.ts
@@ -7923,18 +8685,245 @@ function rowsToQueryResult(rows) {
7923
8685
  return { columns, values };
7924
8686
  }
7925
8687
  function createExecutorFromQueryRunner(runner) {
8688
+ const supportsTransactions = typeof runner.beginTransaction === "function" && typeof runner.commitTransaction === "function" && typeof runner.rollbackTransaction === "function";
7926
8689
  return {
8690
+ capabilities: {
8691
+ transactions: supportsTransactions
8692
+ },
7927
8693
  async executeSql(sql, params) {
7928
8694
  const rows = await runner.query(sql, params);
7929
8695
  const result = rowsToQueryResult(rows);
7930
8696
  return [result];
7931
8697
  },
7932
- beginTransaction: runner.beginTransaction?.bind(runner),
7933
- commitTransaction: runner.commitTransaction?.bind(runner),
7934
- rollbackTransaction: runner.rollbackTransaction?.bind(runner)
8698
+ async beginTransaction() {
8699
+ if (!supportsTransactions) {
8700
+ throw new Error("Transactions are not supported by this executor");
8701
+ }
8702
+ await runner.beginTransaction.call(runner);
8703
+ },
8704
+ async commitTransaction() {
8705
+ if (!supportsTransactions) {
8706
+ throw new Error("Transactions are not supported by this executor");
8707
+ }
8708
+ await runner.commitTransaction.call(runner);
8709
+ },
8710
+ async rollbackTransaction() {
8711
+ if (!supportsTransactions) {
8712
+ throw new Error("Transactions are not supported by this executor");
8713
+ }
8714
+ await runner.rollbackTransaction.call(runner);
8715
+ },
8716
+ async dispose() {
8717
+ await runner.dispose?.call(runner);
8718
+ }
7935
8719
  };
7936
8720
  }
7937
8721
 
8722
+ // src/core/execution/pooling/pool.ts
8723
+ var deferred = () => {
8724
+ let resolve;
8725
+ let reject;
8726
+ const promise = new Promise((res, rej) => {
8727
+ resolve = res;
8728
+ reject = rej;
8729
+ });
8730
+ return { promise, resolve, reject };
8731
+ };
8732
+ var Pool = class {
8733
+ constructor(adapter, options) {
8734
+ this.destroyed = false;
8735
+ this.creating = 0;
8736
+ this.leased = 0;
8737
+ this.idle = [];
8738
+ this.waiters = [];
8739
+ this.reapTimer = null;
8740
+ if (!Number.isFinite(options.max) || options.max <= 0) {
8741
+ throw new Error("Pool options.max must be a positive number");
8742
+ }
8743
+ this.adapter = adapter;
8744
+ this.options = { max: options.max, ...options };
8745
+ const idleTimeout = this.options.idleTimeoutMillis;
8746
+ if (idleTimeout && idleTimeout > 0) {
8747
+ const interval = this.options.reapIntervalMillis ?? Math.max(1e3, Math.floor(idleTimeout / 2));
8748
+ this.reapTimer = setInterval(() => {
8749
+ void this.reapIdle();
8750
+ }, interval);
8751
+ this.reapTimer.unref?.();
8752
+ }
8753
+ const min2 = this.options.min ?? 0;
8754
+ if (min2 > 0) {
8755
+ void this.warm(min2);
8756
+ }
8757
+ }
8758
+ /**
8759
+ * Acquire a resource lease.
8760
+ * The returned lease MUST be released or destroyed.
8761
+ */
8762
+ async acquire() {
8763
+ if (this.destroyed) {
8764
+ throw new Error("Pool is destroyed");
8765
+ }
8766
+ const idle = await this.takeIdleValidated();
8767
+ if (idle) {
8768
+ this.leased++;
8769
+ return this.makeLease(idle);
8770
+ }
8771
+ if (this.totalLive() < this.options.max) {
8772
+ this.creating++;
8773
+ try {
8774
+ const created = await this.adapter.create();
8775
+ this.leased++;
8776
+ return this.makeLease(created);
8777
+ } finally {
8778
+ this.creating--;
8779
+ }
8780
+ }
8781
+ const waiter = deferred();
8782
+ this.waiters.push(waiter);
8783
+ const timeout = this.options.acquireTimeoutMillis;
8784
+ let timer = null;
8785
+ if (timeout && timeout > 0) {
8786
+ timer = setTimeout(() => {
8787
+ const idx = this.waiters.indexOf(waiter);
8788
+ if (idx >= 0) this.waiters.splice(idx, 1);
8789
+ waiter.reject(new Error("Pool acquire timeout"));
8790
+ }, timeout);
8791
+ timer.unref?.();
8792
+ }
8793
+ try {
8794
+ return await waiter.promise;
8795
+ } finally {
8796
+ if (timer) clearTimeout(timer);
8797
+ }
8798
+ }
8799
+ /** Destroy pool and all idle resources; waits for in-flight creations to settle. */
8800
+ async destroy() {
8801
+ if (this.destroyed) return;
8802
+ this.destroyed = true;
8803
+ if (this.reapTimer) {
8804
+ clearInterval(this.reapTimer);
8805
+ this.reapTimer = null;
8806
+ }
8807
+ while (this.waiters.length) {
8808
+ this.waiters.shift().reject(new Error("Pool destroyed"));
8809
+ }
8810
+ while (this.idle.length) {
8811
+ const entry = this.idle.shift();
8812
+ await this.adapter.destroy(entry.resource);
8813
+ }
8814
+ }
8815
+ totalLive() {
8816
+ return this.idle.length + this.leased + this.creating;
8817
+ }
8818
+ makeLease(resource) {
8819
+ let done = false;
8820
+ return {
8821
+ resource,
8822
+ release: async () => {
8823
+ if (done) return;
8824
+ done = true;
8825
+ await this.releaseResource(resource);
8826
+ },
8827
+ destroy: async () => {
8828
+ if (done) return;
8829
+ done = true;
8830
+ await this.destroyResource(resource);
8831
+ }
8832
+ };
8833
+ }
8834
+ async releaseResource(resource) {
8835
+ this.leased = Math.max(0, this.leased - 1);
8836
+ if (this.destroyed) {
8837
+ await this.adapter.destroy(resource);
8838
+ return;
8839
+ }
8840
+ const next = this.waiters.shift();
8841
+ if (next) {
8842
+ this.leased++;
8843
+ next.resolve(this.makeLease(resource));
8844
+ return;
8845
+ }
8846
+ this.idle.push({ resource, lastUsedAt: Date.now() });
8847
+ await this.trimToMinMax();
8848
+ }
8849
+ async destroyResource(resource) {
8850
+ this.leased = Math.max(0, this.leased - 1);
8851
+ await this.adapter.destroy(resource);
8852
+ if (!this.destroyed && this.waiters.length && this.totalLive() < this.options.max) {
8853
+ const waiter = this.waiters.shift();
8854
+ this.creating++;
8855
+ try {
8856
+ const created = await this.adapter.create();
8857
+ this.leased++;
8858
+ waiter.resolve(this.makeLease(created));
8859
+ } catch (err) {
8860
+ waiter.reject(err);
8861
+ } finally {
8862
+ this.creating--;
8863
+ }
8864
+ }
8865
+ }
8866
+ async takeIdleValidated() {
8867
+ while (this.idle.length) {
8868
+ const entry = this.idle.pop();
8869
+ if (!this.adapter.validate) {
8870
+ return entry.resource;
8871
+ }
8872
+ const ok = await this.adapter.validate(entry.resource);
8873
+ if (ok) {
8874
+ return entry.resource;
8875
+ }
8876
+ await this.adapter.destroy(entry.resource);
8877
+ }
8878
+ return null;
8879
+ }
8880
+ async reapIdle() {
8881
+ if (this.destroyed) return;
8882
+ const idleTimeout = this.options.idleTimeoutMillis;
8883
+ if (!idleTimeout || idleTimeout <= 0) return;
8884
+ const now2 = Date.now();
8885
+ const min2 = this.options.min ?? 0;
8886
+ const keep = [];
8887
+ const kill = [];
8888
+ for (const entry of this.idle) {
8889
+ const expired = now2 - entry.lastUsedAt >= idleTimeout;
8890
+ if (expired) kill.push(entry);
8891
+ else keep.push(entry);
8892
+ }
8893
+ while (keep.length < min2 && kill.length) {
8894
+ keep.push(kill.pop());
8895
+ }
8896
+ this.idle.length = 0;
8897
+ this.idle.push(...keep);
8898
+ for (const entry of kill) {
8899
+ await this.adapter.destroy(entry.resource);
8900
+ }
8901
+ }
8902
+ async warm(targetMin) {
8903
+ const min2 = Math.max(0, targetMin);
8904
+ while (!this.destroyed && this.idle.length < min2 && this.totalLive() < this.options.max) {
8905
+ this.creating++;
8906
+ try {
8907
+ const created = await this.adapter.create();
8908
+ this.idle.push({ resource: created, lastUsedAt: Date.now() });
8909
+ } catch {
8910
+ break;
8911
+ } finally {
8912
+ this.creating--;
8913
+ }
8914
+ }
8915
+ }
8916
+ async trimToMinMax() {
8917
+ const max2 = this.options.max;
8918
+ const min2 = this.options.min ?? 0;
8919
+ while (this.totalLive() > max2 && this.idle.length > min2) {
8920
+ const entry = this.idle.shift();
8921
+ if (!entry) break;
8922
+ await this.adapter.destroy(entry.resource);
8923
+ }
8924
+ }
8925
+ };
8926
+
7938
8927
  // src/core/execution/executors/postgres-executor.ts
7939
8928
  function createPostgresExecutor(client) {
7940
8929
  return createExecutorFromQueryRunner({
@@ -7956,7 +8945,11 @@ function createPostgresExecutor(client) {
7956
8945
 
7957
8946
  // src/core/execution/executors/mysql-executor.ts
7958
8947
  function createMysqlExecutor(client) {
8948
+ const supportsTransactions = typeof client.beginTransaction === "function" && typeof client.commit === "function" && typeof client.rollback === "function";
7959
8949
  return {
8950
+ capabilities: {
8951
+ transactions: supportsTransactions
8952
+ },
7960
8953
  async executeSql(sql, params) {
7961
8954
  const [rows] = await client.query(sql, params);
7962
8955
  if (!Array.isArray(rows)) {
@@ -7968,53 +8961,94 @@ function createMysqlExecutor(client) {
7968
8961
  return [result];
7969
8962
  },
7970
8963
  async beginTransaction() {
7971
- if (!client.beginTransaction) return;
8964
+ if (!supportsTransactions) {
8965
+ throw new Error("Transactions are not supported by this executor");
8966
+ }
7972
8967
  await client.beginTransaction();
7973
8968
  },
7974
8969
  async commitTransaction() {
7975
- if (!client.commit) return;
8970
+ if (!supportsTransactions) {
8971
+ throw new Error("Transactions are not supported by this executor");
8972
+ }
7976
8973
  await client.commit();
7977
8974
  },
7978
8975
  async rollbackTransaction() {
7979
- if (!client.rollback) return;
8976
+ if (!supportsTransactions) {
8977
+ throw new Error("Transactions are not supported by this executor");
8978
+ }
7980
8979
  await client.rollback();
8980
+ },
8981
+ async dispose() {
7981
8982
  }
7982
8983
  };
7983
8984
  }
7984
8985
 
7985
8986
  // src/core/execution/executors/sqlite-executor.ts
7986
8987
  function createSqliteExecutor(client) {
8988
+ const supportsTransactions = typeof client.beginTransaction === "function" && typeof client.commitTransaction === "function" && typeof client.rollbackTransaction === "function";
7987
8989
  return {
8990
+ capabilities: {
8991
+ transactions: supportsTransactions
8992
+ },
7988
8993
  async executeSql(sql, params) {
7989
8994
  const rows = await client.all(sql, params);
7990
8995
  const result = rowsToQueryResult(rows);
7991
8996
  return [result];
7992
8997
  },
7993
- beginTransaction: client.beginTransaction?.bind(client),
7994
- commitTransaction: client.commitTransaction?.bind(client),
7995
- rollbackTransaction: client.rollbackTransaction?.bind(client)
8998
+ async beginTransaction() {
8999
+ if (!supportsTransactions) {
9000
+ throw new Error("Transactions are not supported by this executor");
9001
+ }
9002
+ await client.beginTransaction();
9003
+ },
9004
+ async commitTransaction() {
9005
+ if (!supportsTransactions) {
9006
+ throw new Error("Transactions are not supported by this executor");
9007
+ }
9008
+ await client.commitTransaction();
9009
+ },
9010
+ async rollbackTransaction() {
9011
+ if (!supportsTransactions) {
9012
+ throw new Error("Transactions are not supported by this executor");
9013
+ }
9014
+ await client.rollbackTransaction();
9015
+ },
9016
+ async dispose() {
9017
+ }
7996
9018
  };
7997
9019
  }
7998
9020
 
7999
9021
  // src/core/execution/executors/mssql-executor.ts
8000
9022
  function createMssqlExecutor(client) {
9023
+ const supportsTransactions = typeof client.beginTransaction === "function" && typeof client.commit === "function" && typeof client.rollback === "function";
8001
9024
  return {
9025
+ capabilities: {
9026
+ transactions: supportsTransactions
9027
+ },
8002
9028
  async executeSql(sql, params) {
8003
9029
  const { recordset } = await client.query(sql, params);
8004
9030
  const result = rowsToQueryResult(recordset ?? []);
8005
9031
  return [result];
8006
9032
  },
8007
9033
  async beginTransaction() {
8008
- if (!client.beginTransaction) return;
9034
+ if (!supportsTransactions) {
9035
+ throw new Error("Transactions are not supported by this executor");
9036
+ }
8009
9037
  await client.beginTransaction();
8010
9038
  },
8011
9039
  async commitTransaction() {
8012
- if (!client.commit) return;
9040
+ if (!supportsTransactions) {
9041
+ throw new Error("Transactions are not supported by this executor");
9042
+ }
8013
9043
  await client.commit();
8014
9044
  },
8015
9045
  async rollbackTransaction() {
8016
- if (!client.rollback) return;
9046
+ if (!supportsTransactions) {
9047
+ throw new Error("Transactions are not supported by this executor");
9048
+ }
8017
9049
  await client.rollback();
9050
+ },
9051
+ async dispose() {
8018
9052
  }
8019
9053
  };
8020
9054
  }
@@ -8083,6 +9117,86 @@ function createTediousExecutor(connection, module, options) {
8083
9117
  const client = createTediousMssqlClient(connection, module, options);
8084
9118
  return createMssqlExecutor(client);
8085
9119
  }
9120
+
9121
+ // src/orm/pooled-executor-factory.ts
9122
+ function createPooledExecutorFactory(opts) {
9123
+ const { pool, adapter } = opts;
9124
+ const makeExecutor = (mode) => {
9125
+ let lease = null;
9126
+ const getLease = async () => {
9127
+ if (lease) return lease;
9128
+ lease = await pool.acquire();
9129
+ return lease;
9130
+ };
9131
+ const executeWithConn = async (conn, sql, params) => {
9132
+ const rows = await adapter.query(conn, sql, params);
9133
+ return [rowsToQueryResult(rows)];
9134
+ };
9135
+ return {
9136
+ capabilities: { transactions: true },
9137
+ async executeSql(sql, params) {
9138
+ if (mode === "sticky") {
9139
+ const l2 = await getLease();
9140
+ return executeWithConn(l2.resource, sql, params);
9141
+ }
9142
+ if (lease) {
9143
+ return executeWithConn(lease.resource, sql, params);
9144
+ }
9145
+ const l = await pool.acquire();
9146
+ try {
9147
+ return await executeWithConn(l.resource, sql, params);
9148
+ } finally {
9149
+ await l.release();
9150
+ }
9151
+ },
9152
+ async beginTransaction() {
9153
+ const l = await getLease();
9154
+ await adapter.beginTransaction(l.resource);
9155
+ },
9156
+ async commitTransaction() {
9157
+ if (!lease) {
9158
+ throw new Error("commitTransaction called without an active transaction");
9159
+ }
9160
+ const l = lease;
9161
+ try {
9162
+ await adapter.commitTransaction(l.resource);
9163
+ } finally {
9164
+ lease = null;
9165
+ await l.release();
9166
+ }
9167
+ },
9168
+ async rollbackTransaction() {
9169
+ if (!lease) {
9170
+ return;
9171
+ }
9172
+ const l = lease;
9173
+ try {
9174
+ await adapter.rollbackTransaction(l.resource);
9175
+ } finally {
9176
+ lease = null;
9177
+ await l.release();
9178
+ }
9179
+ },
9180
+ async dispose() {
9181
+ if (!lease) return;
9182
+ const l = lease;
9183
+ lease = null;
9184
+ await l.release();
9185
+ }
9186
+ };
9187
+ };
9188
+ return {
9189
+ createExecutor() {
9190
+ return makeExecutor("session");
9191
+ },
9192
+ createTransactionalExecutor() {
9193
+ return makeExecutor("sticky");
9194
+ },
9195
+ async dispose() {
9196
+ await pool.destroy();
9197
+ }
9198
+ };
9199
+ }
8086
9200
  export {
8087
9201
  AsyncLocalStorage,
8088
9202
  BelongsTo,
@@ -8101,6 +9215,7 @@ export {
8101
9215
  MySqlDialect,
8102
9216
  Orm,
8103
9217
  OrmSession,
9218
+ Pool,
8104
9219
  PostgresDialect,
8105
9220
  PrimaryKey,
8106
9221
  RelationKinds,
@@ -8146,6 +9261,7 @@ export {
8146
9261
  createLiteral,
8147
9262
  createMssqlExecutor,
8148
9263
  createMysqlExecutor,
9264
+ createPooledExecutorFactory,
8149
9265
  createPostgresExecutor,
8150
9266
  createQueryLoggingExecutor,
8151
9267
  createSqliteExecutor,
@@ -8187,6 +9303,7 @@ export {
8187
9303
  hasOne,
8188
9304
  hydrateRows,
8189
9305
  inList,
9306
+ inSubquery,
8190
9307
  instr,
8191
9308
  introspectSchema,
8192
9309
  isCaseExpressionNode,
@@ -8227,6 +9344,7 @@ export {
8227
9344
  notBetween,
8228
9345
  notExists,
8229
9346
  notInList,
9347
+ notInSubquery,
8230
9348
  notLike,
8231
9349
  now,
8232
9350
  ntile,