metal-orm 1.0.57 → 1.0.59

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 (46) hide show
  1. package/README.md +23 -13
  2. package/dist/index.cjs +1750 -733
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +244 -157
  5. package/dist/index.d.ts +244 -157
  6. package/dist/index.js +1745 -733
  7. package/dist/index.js.map +1 -1
  8. package/package.json +69 -69
  9. package/src/core/ddl/schema-generator.ts +44 -1
  10. package/src/decorators/bootstrap.ts +186 -113
  11. package/src/decorators/column-decorator.ts +8 -49
  12. package/src/decorators/decorator-metadata.ts +10 -46
  13. package/src/decorators/entity.ts +30 -40
  14. package/src/decorators/relations.ts +30 -56
  15. package/src/orm/entity-hydration.ts +72 -0
  16. package/src/orm/entity-meta.ts +18 -13
  17. package/src/orm/entity-metadata.ts +240 -238
  18. package/src/orm/entity-relation-cache.ts +39 -0
  19. package/src/orm/entity-relations.ts +207 -0
  20. package/src/orm/entity.ts +124 -343
  21. package/src/orm/execute.ts +87 -20
  22. package/src/orm/lazy-batch/belongs-to-many.ts +134 -0
  23. package/src/orm/lazy-batch/belongs-to.ts +108 -0
  24. package/src/orm/lazy-batch/has-many.ts +69 -0
  25. package/src/orm/lazy-batch/has-one.ts +68 -0
  26. package/src/orm/lazy-batch/shared.ts +125 -0
  27. package/src/orm/lazy-batch.ts +4 -309
  28. package/src/orm/relations/belongs-to.ts +2 -2
  29. package/src/orm/relations/has-many.ts +23 -9
  30. package/src/orm/relations/has-one.ts +2 -2
  31. package/src/orm/relations/many-to-many.ts +29 -14
  32. package/src/orm/save-graph-types.ts +2 -2
  33. package/src/orm/save-graph.ts +18 -18
  34. package/src/query-builder/relation-conditions.ts +80 -59
  35. package/src/query-builder/relation-cte-builder.ts +63 -0
  36. package/src/query-builder/relation-filter-utils.ts +159 -0
  37. package/src/query-builder/relation-include-strategies.ts +177 -0
  38. package/src/query-builder/relation-join-planner.ts +80 -0
  39. package/src/query-builder/relation-service.ts +103 -159
  40. package/src/query-builder/relation-types.ts +43 -12
  41. package/src/query-builder/select/projection-facet.ts +23 -23
  42. package/src/query-builder/select/select-operations.ts +145 -0
  43. package/src/query-builder/select.ts +373 -426
  44. package/src/schema/relation.ts +22 -18
  45. package/src/schema/table.ts +22 -9
  46. package/src/schema/types.ts +103 -84
package/dist/index.cjs CHANGED
@@ -143,6 +143,8 @@ __export(index_exports, {
143
143
  esel: () => esel,
144
144
  executeHydrated: () => executeHydrated,
145
145
  executeHydratedWithContexts: () => executeHydratedWithContexts,
146
+ executeSchemaSql: () => executeSchemaSql,
147
+ executeSchemaSqlFor: () => executeSchemaSqlFor,
146
148
  exists: () => exists,
147
149
  exp: () => exp,
148
150
  extract: () => extract,
@@ -151,6 +153,7 @@ __export(index_exports, {
151
153
  fromUnixTime: () => fromUnixTime,
152
154
  generateCreateTableSql: () => generateCreateTableSql,
153
155
  generateSchemaSql: () => generateSchemaSql,
156
+ generateSchemaSqlFor: () => generateSchemaSqlFor,
154
157
  getColumn: () => getColumn,
155
158
  getDecoratorMetadata: () => getDecoratorMetadata,
156
159
  getSchemaIntrospector: () => getSchemaIntrospector,
@@ -241,6 +244,7 @@ __export(index_exports, {
241
244
  registerExpressionDispatcher: () => registerExpressionDispatcher,
242
245
  registerOperandDispatcher: () => registerOperandDispatcher,
243
246
  registerSchemaIntrospector: () => registerSchemaIntrospector,
247
+ relationLoaderCache: () => relationLoaderCache,
244
248
  renderColumnDefinition: () => renderColumnDefinition,
245
249
  renderTypeWithArgs: () => renderTypeWithArgs,
246
250
  repeat: () => repeat,
@@ -255,6 +259,7 @@ __export(index_exports, {
255
259
  second: () => second,
256
260
  sel: () => sel,
257
261
  selectFromEntity: () => selectFromEntity,
262
+ setRelations: () => setRelations,
258
263
  sha1: () => sha1,
259
264
  sha2: () => sha2,
260
265
  shiftLeft: () => shiftLeft,
@@ -310,6 +315,9 @@ var defineTable = (name, columns, relations = {}, hooks, options = {}) => {
310
315
  collation: options.collation
311
316
  };
312
317
  };
318
+ function setRelations(table, relations) {
319
+ table.relations = relations;
320
+ }
313
321
  var TABLE_REF_CACHE = /* @__PURE__ */ new WeakMap();
314
322
  var withColumnProps = (table) => {
315
323
  const cached = TABLE_REF_CACHE.get(table);
@@ -953,6 +961,7 @@ var variance = buildAggregate("VARIANCE");
953
961
 
954
962
  // src/core/ast/expression-visitor.ts
955
963
  var DispatcherRegistry = class _DispatcherRegistry {
964
+ dispatchers;
956
965
  constructor(dispatchers = /* @__PURE__ */ new Map()) {
957
966
  this.dispatchers = dispatchers;
958
967
  }
@@ -1086,61 +1095,9 @@ var toTableRef = (table) => ({
1086
1095
  alias: hasAlias(table) ? table.alias : void 0
1087
1096
  });
1088
1097
 
1089
- // src/core/ast/builders.ts
1090
- var isColumnNode = (col2) => "type" in col2 && col2.type === "Column";
1091
- var resolveTableName = (def, table) => {
1092
- if (!def.table) {
1093
- return table.alias || table.name;
1094
- }
1095
- if (table.alias && def.table === table.name) {
1096
- return table.alias;
1097
- }
1098
- return def.table;
1099
- };
1100
- var buildColumnNode = (table, column) => {
1101
- if (isColumnNode(column)) {
1102
- return column;
1103
- }
1104
- const def = column;
1105
- const baseTable = resolveTableName(def, table);
1106
- return {
1107
- type: "Column",
1108
- table: baseTable,
1109
- name: def.name
1110
- };
1111
- };
1112
- var buildColumnNodes = (table, names) => names.map((name) => ({
1113
- type: "Column",
1114
- table: table.alias || table.name,
1115
- name
1116
- }));
1117
- var createTableNode = (table) => ({
1118
- type: "Table",
1119
- name: table.name,
1120
- schema: table.schema
1121
- });
1122
- var fnTable = (name, args = [], alias, opts) => ({
1123
- type: "FunctionTable",
1124
- name,
1125
- args,
1126
- alias,
1127
- lateral: opts?.lateral,
1128
- withOrdinality: opts?.withOrdinality,
1129
- columnAliases: opts?.columnAliases,
1130
- schema: opts?.schema
1131
- });
1132
- var derivedTable = (query, alias, columnAliases) => ({
1133
- type: "DerivedTable",
1134
- query,
1135
- alias,
1136
- columnAliases
1137
- });
1138
-
1139
1098
  // src/core/functions/function-registry.ts
1140
1099
  var FunctionRegistry = class {
1141
- constructor() {
1142
- this.renderers = /* @__PURE__ */ new Map();
1143
- }
1100
+ renderers = /* @__PURE__ */ new Map();
1144
1101
  /**
1145
1102
  * Registers or overrides a renderer for the given function name.
1146
1103
  */
@@ -1435,6 +1392,7 @@ function renderStandardGroupConcat(ctx) {
1435
1392
 
1436
1393
  // src/core/functions/standard-strategy.ts
1437
1394
  var StandardFunctionStrategy = class {
1395
+ registry;
1438
1396
  /**
1439
1397
  * Creates a new StandardFunctionStrategy and registers standard functions.
1440
1398
  */
@@ -1500,17 +1458,13 @@ var StandardFunctionStrategy = class {
1500
1458
  getGroupConcatSeparatorOperand(ctx) {
1501
1459
  return getGroupConcatSeparatorOperand(ctx);
1502
1460
  }
1503
- static {
1504
- /** Default separator for GROUP_CONCAT, a comma. */
1505
- this.DEFAULT_GROUP_CONCAT_SEPARATOR = DEFAULT_GROUP_CONCAT_SEPARATOR;
1506
- }
1461
+ /** Default separator for GROUP_CONCAT, a comma. */
1462
+ static DEFAULT_GROUP_CONCAT_SEPARATOR = DEFAULT_GROUP_CONCAT_SEPARATOR;
1507
1463
  };
1508
1464
 
1509
1465
  // src/core/functions/standard-table-strategy.ts
1510
1466
  var StandardTableFunctionStrategy = class {
1511
- constructor() {
1512
- this.renderers = /* @__PURE__ */ new Map();
1513
- }
1467
+ renderers = /* @__PURE__ */ new Map();
1514
1468
  add(key, renderer) {
1515
1469
  this.renderers.set(key, renderer);
1516
1470
  }
@@ -1689,6 +1643,10 @@ var Dialect = class _Dialect {
1689
1643
  const combinedCtes = [...normalized.ctes ?? [], ...hoistedCtes];
1690
1644
  return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
1691
1645
  }
1646
+ expressionCompilers;
1647
+ operandCompilers;
1648
+ functionStrategy;
1649
+ tableFunctionStrategy;
1692
1650
  constructor(functionStrategy, tableFunctionStrategy) {
1693
1651
  this.expressionCompilers = /* @__PURE__ */ new Map();
1694
1652
  this.operandCompilers = /* @__PURE__ */ new Map();
@@ -1704,10 +1662,7 @@ var Dialect = class _Dialect {
1704
1662
  */
1705
1663
  static create(functionStrategy, tableFunctionStrategy) {
1706
1664
  class TestDialect extends _Dialect {
1707
- constructor() {
1708
- super(...arguments);
1709
- this.dialect = "sqlite";
1710
- }
1665
+ dialect = "sqlite";
1711
1666
  quoteIdentifier(id) {
1712
1667
  return `"${id}"`;
1713
1668
  }
@@ -2147,11 +2102,8 @@ var OrderByCompiler = class {
2147
2102
 
2148
2103
  // src/core/dialect/base/sql-dialect.ts
2149
2104
  var SqlDialectBase = class extends Dialect {
2150
- constructor() {
2151
- super(...arguments);
2152
- this.paginationStrategy = new StandardLimitOffsetPagination();
2153
- this.returningStrategy = new NoReturningStrategy();
2154
- }
2105
+ paginationStrategy = new StandardLimitOffsetPagination();
2106
+ returningStrategy = new NoReturningStrategy();
2155
2107
  compileSelectAst(ast, ctx) {
2156
2108
  const hasSetOps = !!(ast.setOps && ast.setOps.length);
2157
2109
  const ctes = CteCompiler.compileCtes(
@@ -2539,12 +2491,12 @@ var PostgresTableFunctionStrategy = class extends StandardTableFunctionStrategy
2539
2491
 
2540
2492
  // src/core/dialect/postgres/index.ts
2541
2493
  var PostgresDialect = class extends SqlDialectBase {
2494
+ dialect = "postgres";
2542
2495
  /**
2543
2496
  * Creates a new PostgresDialect instance
2544
2497
  */
2545
2498
  constructor() {
2546
2499
  super(new PostgresFunctionStrategy(), new PostgresTableFunctionStrategy());
2547
- this.dialect = "postgres";
2548
2500
  this.registerExpressionCompiler("BitwiseExpression", (node, ctx) => {
2549
2501
  const left2 = this.compileOperand(node.left, ctx);
2550
2502
  const right2 = this.compileOperand(node.right, ctx);
@@ -2678,12 +2630,12 @@ var MysqlFunctionStrategy = class extends StandardFunctionStrategy {
2678
2630
 
2679
2631
  // src/core/dialect/mysql/index.ts
2680
2632
  var MySqlDialect = class extends SqlDialectBase {
2633
+ dialect = "mysql";
2681
2634
  /**
2682
2635
  * Creates a new MySqlDialect instance
2683
2636
  */
2684
2637
  constructor() {
2685
2638
  super(new MysqlFunctionStrategy());
2686
- this.dialect = "mysql";
2687
2639
  }
2688
2640
  /**
2689
2641
  * Quotes an identifier using MySQL backtick syntax
@@ -2834,12 +2786,12 @@ var SqliteFunctionStrategy = class extends StandardFunctionStrategy {
2834
2786
 
2835
2787
  // src/core/dialect/sqlite/index.ts
2836
2788
  var SqliteDialect = class extends SqlDialectBase {
2789
+ dialect = "sqlite";
2837
2790
  /**
2838
2791
  * Creates a new SqliteDialect instance
2839
2792
  */
2840
2793
  constructor() {
2841
2794
  super(new SqliteFunctionStrategy());
2842
- this.dialect = "sqlite";
2843
2795
  this.registerExpressionCompiler("BitwiseExpression", (node, ctx) => {
2844
2796
  const left2 = this.compileOperand(node.left, ctx);
2845
2797
  const right2 = this.compileOperand(node.right, ctx);
@@ -3012,12 +2964,12 @@ var MssqlFunctionStrategy = class extends StandardFunctionStrategy {
3012
2964
 
3013
2965
  // src/core/dialect/mssql/index.ts
3014
2966
  var SqlServerDialect = class extends SqlDialectBase {
2967
+ dialect = "mssql";
3015
2968
  /**
3016
2969
  * Creates a new SqlServerDialect instance
3017
2970
  */
3018
2971
  constructor() {
3019
2972
  super(new MssqlFunctionStrategy());
3020
- this.dialect = "mssql";
3021
2973
  }
3022
2974
  /**
3023
2975
  * Quotes an identifier using SQL Server bracket syntax
@@ -3144,12 +3096,8 @@ var SqlServerDialect = class extends SqlDialectBase {
3144
3096
 
3145
3097
  // src/core/dialect/dialect-factory.ts
3146
3098
  var DialectFactory = class {
3147
- static {
3148
- this.registry = /* @__PURE__ */ new Map();
3149
- }
3150
- static {
3151
- this.defaultsInitialized = false;
3152
- }
3099
+ static registry = /* @__PURE__ */ new Map();
3100
+ static defaultsInitialized = false;
3153
3101
  static ensureDefaults() {
3154
3102
  if (this.defaultsInitialized) return;
3155
3103
  this.defaultsInitialized = true;
@@ -3208,8 +3156,66 @@ var resolveDialectInput = (dialect) => {
3208
3156
  return dialect;
3209
3157
  };
3210
3158
 
3159
+ // src/core/ast/builders.ts
3160
+ var isColumnNode = (col2) => "type" in col2 && col2.type === "Column";
3161
+ var resolveTableName = (def, table) => {
3162
+ if (!def.table) {
3163
+ return table.alias || table.name;
3164
+ }
3165
+ if (table.alias && def.table === table.name) {
3166
+ return table.alias;
3167
+ }
3168
+ return def.table;
3169
+ };
3170
+ var buildColumnNode = (table, column) => {
3171
+ if (isColumnNode(column)) {
3172
+ return column;
3173
+ }
3174
+ const def = column;
3175
+ const baseTable = resolveTableName(def, table);
3176
+ return {
3177
+ type: "Column",
3178
+ table: baseTable,
3179
+ name: def.name
3180
+ };
3181
+ };
3182
+ var buildColumnNodes = (table, names) => names.map((name) => ({
3183
+ type: "Column",
3184
+ table: table.alias || table.name,
3185
+ name
3186
+ }));
3187
+ var createTableNode = (table) => ({
3188
+ type: "Table",
3189
+ name: table.name,
3190
+ schema: table.schema
3191
+ });
3192
+ var fnTable = (name, args = [], alias, opts) => ({
3193
+ type: "FunctionTable",
3194
+ name,
3195
+ args,
3196
+ alias,
3197
+ lateral: opts?.lateral,
3198
+ withOrdinality: opts?.withOrdinality,
3199
+ columnAliases: opts?.columnAliases,
3200
+ schema: opts?.schema
3201
+ });
3202
+ var derivedTable = (query, alias, columnAliases) => ({
3203
+ type: "DerivedTable",
3204
+ query,
3205
+ alias,
3206
+ columnAliases
3207
+ });
3208
+
3211
3209
  // src/query-builder/select-query-state.ts
3212
3210
  var SelectQueryState = class _SelectQueryState {
3211
+ /**
3212
+ * Table definition for the query
3213
+ */
3214
+ table;
3215
+ /**
3216
+ * Abstract Syntax Tree (AST) representation of the query
3217
+ */
3218
+ ast;
3213
3219
  /**
3214
3220
  * Creates a new SelectQueryState instance
3215
3221
  * @param table - Table definition
@@ -4111,20 +4117,21 @@ var RelationProjectionHelper = class {
4111
4117
  var assertNever = (value) => {
4112
4118
  throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
4113
4119
  };
4114
- var baseRelationCondition = (root, relation, rootAlias) => {
4120
+ var baseRelationCondition = (root, relation, rootAlias, targetTableName) => {
4115
4121
  const rootTable = rootAlias || root.name;
4122
+ const targetTable = targetTableName ?? relation.target.name;
4116
4123
  const defaultLocalKey = relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne ? findPrimaryKey(root) : findPrimaryKey(relation.target);
4117
4124
  const localKey = relation.localKey || defaultLocalKey;
4118
4125
  switch (relation.type) {
4119
4126
  case RelationKinds.HasMany:
4120
4127
  case RelationKinds.HasOne:
4121
4128
  return eq(
4122
- { type: "Column", table: relation.target.name, name: relation.foreignKey },
4129
+ { type: "Column", table: targetTable, name: relation.foreignKey },
4123
4130
  { type: "Column", table: rootTable, name: localKey }
4124
4131
  );
4125
4132
  case RelationKinds.BelongsTo:
4126
4133
  return eq(
4127
- { type: "Column", table: relation.target.name, name: localKey },
4134
+ { type: "Column", table: targetTable, name: localKey },
4128
4135
  { type: "Column", table: rootTable, name: relation.foreignKey }
4129
4136
  );
4130
4137
  case RelationKinds.BelongsToMany:
@@ -4133,7 +4140,7 @@ var baseRelationCondition = (root, relation, rootAlias) => {
4133
4140
  return assertNever(relation);
4134
4141
  }
4135
4142
  };
4136
- var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias) => {
4143
+ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias, targetTable, targetTableName) => {
4137
4144
  const rootKey = relation.localKey || findPrimaryKey(root);
4138
4145
  const targetKey = relation.targetKey || findPrimaryKey(relation.target);
4139
4146
  const rootTable = rootAlias || root.name;
@@ -4146,8 +4153,14 @@ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, ro
4146
4153
  { type: "Table", name: relation.pivotTable.name, schema: relation.pivotTable.schema },
4147
4154
  pivotCondition
4148
4155
  );
4156
+ const targetSource = targetTable ?? {
4157
+ type: "Table",
4158
+ name: relation.target.name,
4159
+ schema: relation.target.schema
4160
+ };
4161
+ const effectiveTargetName = targetTableName ?? relation.target.name;
4149
4162
  let targetCondition = eq(
4150
- { type: "Column", table: relation.target.name, name: targetKey },
4163
+ { type: "Column", table: effectiveTargetName, name: targetKey },
4151
4164
  { type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
4152
4165
  );
4153
4166
  if (extra) {
@@ -4155,141 +4168,487 @@ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, ro
4155
4168
  }
4156
4169
  const targetJoin = createJoinNode(
4157
4170
  joinKind,
4158
- { type: "Table", name: relation.target.name, schema: relation.target.schema },
4171
+ targetSource,
4159
4172
  targetCondition,
4160
4173
  relationName
4161
4174
  );
4162
4175
  return [pivotJoin, targetJoin];
4163
4176
  };
4164
- var buildRelationJoinCondition = (root, relation, extra, rootAlias) => {
4165
- const base = baseRelationCondition(root, relation, rootAlias);
4177
+ var buildRelationJoinCondition = (root, relation, extra, rootAlias, targetTableName) => {
4178
+ const base = baseRelationCondition(root, relation, rootAlias, targetTableName);
4166
4179
  return extra ? and(base, extra) : base;
4167
4180
  };
4168
- var buildRelationCorrelation = (root, relation, rootAlias) => {
4169
- return baseRelationCondition(root, relation, rootAlias);
4181
+ var buildRelationCorrelation = (root, relation, rootAlias, targetTableName) => {
4182
+ return baseRelationCondition(root, relation, rootAlias, targetTableName);
4170
4183
  };
4171
4184
 
4172
4185
  // src/core/ast/join-metadata.ts
4173
4186
  var getJoinRelationName = (join) => join.meta?.relationName;
4174
4187
 
4175
- // src/query-builder/relation-service.ts
4176
- var RelationService = class {
4177
- /**
4178
- * Creates a new RelationService instance
4179
- * @param table - Table definition
4180
- * @param state - Current query state
4181
- * @param hydration - Hydration manager
4182
- */
4183
- constructor(table, state, hydration, createQueryAstService) {
4184
- this.table = table;
4185
- this.state = state;
4186
- this.hydration = hydration;
4187
- this.createQueryAstService = createQueryAstService;
4188
- this.projectionHelper = new RelationProjectionHelper(
4189
- table,
4190
- (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
4191
- );
4188
+ // src/query-builder/relation-filter-utils.ts
4189
+ var splitFilterExpressions = (filter, allowedTables) => {
4190
+ const terms = flattenAnd(filter);
4191
+ const selfFilters = [];
4192
+ const crossFilters = [];
4193
+ for (const term of terms) {
4194
+ if (isExpressionSelfContained(term, allowedTables)) {
4195
+ selfFilters.push(term);
4196
+ } else {
4197
+ crossFilters.push(term);
4198
+ }
4192
4199
  }
4193
- /**
4194
- * Joins a relation to the query
4195
- * @param relationName - Name of the relation to join
4196
- * @param joinKind - Type of join to use
4197
- * @param extraCondition - Additional join condition
4198
- * @returns Relation result with updated state and hydration
4199
- */
4200
- joinRelation(relationName, joinKind, extraCondition) {
4201
- const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
4202
- return { state: nextState, hydration: this.hydration };
4200
+ return { selfFilters, crossFilters };
4201
+ };
4202
+ var flattenAnd = (node) => {
4203
+ if (!node) return [];
4204
+ if (node.type === "LogicalExpression" && node.operator === "AND") {
4205
+ return node.operands.flatMap((operand) => flattenAnd(operand));
4203
4206
  }
4204
- /**
4205
- * Matches records based on a relation with an optional predicate
4206
- * @param relationName - Name of the relation to match
4207
- * @param predicate - Optional predicate expression
4208
- * @returns Relation result with updated state and hydration
4209
- */
4210
- match(relationName, predicate) {
4211
- const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
4212
- const pk = findPrimaryKey(this.table);
4213
- const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
4214
- const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
4215
- const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
4216
- return { state: nextState, hydration: joined.hydration };
4207
+ return [node];
4208
+ };
4209
+ var isExpressionSelfContained = (expr, allowedTables) => {
4210
+ const collector = collectReferencedTables(expr);
4211
+ if (collector.hasSubquery) return false;
4212
+ if (collector.tables.size === 0) return true;
4213
+ for (const table of collector.tables) {
4214
+ if (!allowedTables.has(table)) {
4215
+ return false;
4216
+ }
4217
4217
  }
4218
- /**
4219
- * Includes a relation in the query result
4220
- * @param relationName - Name of the relation to include
4221
- * @param options - Options for relation inclusion
4222
- * @returns Relation result with updated state and hydration
4223
- */
4224
- include(relationName, options) {
4225
- let state = this.state;
4226
- let hydration = this.hydration;
4227
- const relation = this.getRelation(relationName);
4228
- const aliasPrefix = options?.aliasPrefix ?? relationName;
4229
- const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
4230
- if (!alreadyJoined) {
4231
- const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
4232
- state = joined.state;
4218
+ return true;
4219
+ };
4220
+ var collectReferencedTables = (expr) => {
4221
+ const collector = {
4222
+ tables: /* @__PURE__ */ new Set(),
4223
+ hasSubquery: false
4224
+ };
4225
+ collectFromExpression(expr, collector);
4226
+ return collector;
4227
+ };
4228
+ var collectFromExpression = (expr, collector) => {
4229
+ switch (expr.type) {
4230
+ case "BinaryExpression":
4231
+ collectFromOperand(expr.left, collector);
4232
+ collectFromOperand(expr.right, collector);
4233
+ break;
4234
+ case "LogicalExpression":
4235
+ expr.operands.forEach((operand) => collectFromExpression(operand, collector));
4236
+ break;
4237
+ case "NullExpression":
4238
+ collectFromOperand(expr.left, collector);
4239
+ break;
4240
+ case "InExpression":
4241
+ collectFromOperand(expr.left, collector);
4242
+ if (Array.isArray(expr.right)) {
4243
+ expr.right.forEach((value) => collectFromOperand(value, collector));
4244
+ } else {
4245
+ collector.hasSubquery = true;
4246
+ }
4247
+ break;
4248
+ case "ExistsExpression":
4249
+ collector.hasSubquery = true;
4250
+ break;
4251
+ case "BetweenExpression":
4252
+ collectFromOperand(expr.left, collector);
4253
+ collectFromOperand(expr.lower, collector);
4254
+ collectFromOperand(expr.upper, collector);
4255
+ break;
4256
+ case "ArithmeticExpression":
4257
+ case "BitwiseExpression":
4258
+ collectFromOperand(expr.left, collector);
4259
+ collectFromOperand(expr.right, collector);
4260
+ break;
4261
+ default:
4262
+ break;
4263
+ }
4264
+ };
4265
+ var collectFromOperand = (node, collector) => {
4266
+ switch (node.type) {
4267
+ case "Column":
4268
+ collector.tables.add(node.table);
4269
+ break;
4270
+ case "Function":
4271
+ node.args.forEach((arg) => collectFromOperand(arg, collector));
4272
+ if (node.separator) {
4273
+ collectFromOperand(node.separator, collector);
4274
+ }
4275
+ if (node.orderBy) {
4276
+ node.orderBy.forEach((order) => collectFromOrderingTerm(order.term, collector));
4277
+ }
4278
+ break;
4279
+ case "JsonPath":
4280
+ collectFromOperand(node.column, collector);
4281
+ break;
4282
+ case "ScalarSubquery":
4283
+ collector.hasSubquery = true;
4284
+ break;
4285
+ case "CaseExpression":
4286
+ node.conditions.forEach(({ when, then }) => {
4287
+ collectFromExpression(when, collector);
4288
+ collectFromOperand(then, collector);
4289
+ });
4290
+ if (node.else) {
4291
+ collectFromOperand(node.else, collector);
4292
+ }
4293
+ break;
4294
+ case "Cast":
4295
+ collectFromOperand(node.expression, collector);
4296
+ break;
4297
+ case "WindowFunction":
4298
+ node.args.forEach((arg) => collectFromOperand(arg, collector));
4299
+ node.partitionBy?.forEach((part) => collectFromOperand(part, collector));
4300
+ node.orderBy?.forEach((order) => collectFromOrderingTerm(order.term, collector));
4301
+ break;
4302
+ case "Collate":
4303
+ collectFromOperand(node.expression, collector);
4304
+ break;
4305
+ case "ArithmeticExpression":
4306
+ case "BitwiseExpression":
4307
+ collectFromOperand(node.left, collector);
4308
+ collectFromOperand(node.right, collector);
4309
+ break;
4310
+ case "Literal":
4311
+ case "AliasRef":
4312
+ break;
4313
+ default:
4314
+ break;
4315
+ }
4316
+ };
4317
+ var collectFromOrderingTerm = (term, collector) => {
4318
+ if (isOperandNode(term)) {
4319
+ collectFromOperand(term, collector);
4320
+ return;
4321
+ }
4322
+ collectFromExpression(term, collector);
4323
+ };
4324
+
4325
+ // src/query-builder/relation-join-planner.ts
4326
+ var RelationJoinPlanner = class {
4327
+ constructor(table, createQueryAstService) {
4328
+ this.table = table;
4329
+ this.createQueryAstService = createQueryAstService;
4330
+ }
4331
+ withJoin(state, relationName, relation, joinKind, extraCondition, tableSource) {
4332
+ const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4333
+ if (relation.type === RelationKinds.BelongsToMany) {
4334
+ const targetTableSource = tableSource ?? {
4335
+ type: "Table",
4336
+ name: relation.target.name,
4337
+ schema: relation.target.schema
4338
+ };
4339
+ const targetName2 = this.resolveTargetTableName(targetTableSource, relation);
4340
+ const joins = buildBelongsToManyJoins(
4341
+ this.table,
4342
+ relationName,
4343
+ relation,
4344
+ joinKind,
4345
+ extraCondition,
4346
+ rootAlias,
4347
+ targetTableSource,
4348
+ targetName2
4349
+ );
4350
+ return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
4233
4351
  }
4234
- const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
4235
- state = projectionResult.state;
4236
- hydration = projectionResult.hydration;
4237
- const targetColumns = options?.columns?.length ? options.columns : Object.keys(relation.target.columns);
4238
- const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
4239
- return keys.reduce((acc, key) => {
4240
- const def = columns[key];
4241
- if (!def) {
4242
- throw new Error(missingMsg(key));
4243
- }
4244
- acc[makeRelationAlias(prefix, key)] = def;
4245
- return acc;
4246
- }, {});
4352
+ const targetTable = tableSource ?? {
4353
+ type: "Table",
4354
+ name: relation.target.name,
4355
+ schema: relation.target.schema
4247
4356
  };
4248
- const targetSelection = buildTypedSelection(
4249
- relation.target.columns,
4250
- aliasPrefix,
4251
- targetColumns,
4252
- (key) => `Column '${key}' not found on relation '${relationName}'`
4357
+ const targetName = this.resolveTargetTableName(targetTable, relation);
4358
+ const condition = buildRelationJoinCondition(
4359
+ this.table,
4360
+ relation,
4361
+ extraCondition,
4362
+ rootAlias,
4363
+ targetName
4253
4364
  );
4254
- if (relation.type !== RelationKinds.BelongsToMany) {
4255
- const relationSelectionResult2 = this.selectColumns(state, hydration, targetSelection);
4256
- state = relationSelectionResult2.state;
4257
- hydration = relationSelectionResult2.hydration;
4258
- hydration = hydration.onRelationIncluded(
4259
- state,
4365
+ const joinNode = createJoinNode(joinKind, targetTable, condition, relationName);
4366
+ return this.astService(state).withJoin(joinNode);
4367
+ }
4368
+ astService(state) {
4369
+ return this.createQueryAstService(this.table, state);
4370
+ }
4371
+ resolveTargetTableName(target, relation) {
4372
+ if (target.type === "Table") {
4373
+ return target.alias ?? target.name;
4374
+ }
4375
+ if (target.type === "DerivedTable") {
4376
+ return target.alias;
4377
+ }
4378
+ if (target.type === "FunctionTable") {
4379
+ return target.alias ?? relation.target.name;
4380
+ }
4381
+ return relation.target.name;
4382
+ }
4383
+ };
4384
+
4385
+ // src/query-builder/relation-cte-builder.ts
4386
+ var RelationCteBuilder = class {
4387
+ constructor(table, createQueryAstService) {
4388
+ this.table = table;
4389
+ this.createQueryAstService = createQueryAstService;
4390
+ }
4391
+ createFilteredRelationCte(state, relationName, relation, predicate) {
4392
+ const cteName = this.generateUniqueCteName(state, relationName);
4393
+ if (!predicate) {
4394
+ throw new Error("Unable to build filter CTE without predicates.");
4395
+ }
4396
+ const columns = Object.keys(relation.target.columns).map((name) => ({
4397
+ type: "Column",
4398
+ table: relation.target.name,
4399
+ name
4400
+ }));
4401
+ const cteQuery = {
4402
+ type: "SelectQuery",
4403
+ from: { type: "Table", name: relation.target.name, schema: relation.target.schema },
4404
+ columns,
4405
+ joins: [],
4406
+ where: predicate
4407
+ };
4408
+ const nextState = this.astService(state).withCte(cteName, cteQuery);
4409
+ const tableNode3 = {
4410
+ type: "Table",
4411
+ name: cteName,
4412
+ alias: relation.target.name
4413
+ };
4414
+ return { state: nextState, table: tableNode3 };
4415
+ }
4416
+ generateUniqueCteName(state, relationName) {
4417
+ const existing = new Set((state.ast.ctes ?? []).map((cte) => cte.name));
4418
+ let candidate = `${relationName}__filtered`;
4419
+ let suffix = 1;
4420
+ while (existing.has(candidate)) {
4421
+ candidate = `${relationName}__filtered_${suffix}`;
4422
+ suffix += 1;
4423
+ }
4424
+ return candidate;
4425
+ }
4426
+ astService(state) {
4427
+ return this.createQueryAstService(this.table, state);
4428
+ }
4429
+ };
4430
+
4431
+ // src/query-builder/relation-include-strategies.ts
4432
+ var buildTypedSelection = (columns, prefix, keys, missingMsg) => {
4433
+ return keys.reduce((acc, key) => {
4434
+ const def = columns[key];
4435
+ if (!def) {
4436
+ throw new Error(missingMsg(key));
4437
+ }
4438
+ acc[makeRelationAlias(prefix, key)] = def;
4439
+ return acc;
4440
+ }, {});
4441
+ };
4442
+ var resolveTargetColumns = (relation, options) => {
4443
+ const requestedColumns = options?.columns?.length ? [...options.columns] : Object.keys(relation.target.columns);
4444
+ const targetPrimaryKey = findPrimaryKey(relation.target);
4445
+ if (!requestedColumns.includes(targetPrimaryKey)) {
4446
+ requestedColumns.push(targetPrimaryKey);
4447
+ }
4448
+ return requestedColumns;
4449
+ };
4450
+ var ensureRootForeignKeySelected = (context, relation) => {
4451
+ const fkColumn = context.rootTable.columns[relation.foreignKey];
4452
+ if (!fkColumn) {
4453
+ return { state: context.state, hydration: context.hydration };
4454
+ }
4455
+ const hasForeignKeySelected = context.state.ast.columns.some((col2) => {
4456
+ if (col2.type !== "Column") return false;
4457
+ const node = col2;
4458
+ const alias = node.alias ?? node.name;
4459
+ return alias === relation.foreignKey;
4460
+ });
4461
+ if (hasForeignKeySelected) {
4462
+ return { state: context.state, hydration: context.hydration };
4463
+ }
4464
+ return context.selectColumns(context.state, context.hydration, {
4465
+ [relation.foreignKey]: fkColumn
4466
+ });
4467
+ };
4468
+ var standardIncludeStrategy = (context) => {
4469
+ const relation = context.relation;
4470
+ let { state, hydration } = context;
4471
+ const fkSelectionResult = ensureRootForeignKeySelected(context, relation);
4472
+ state = fkSelectionResult.state;
4473
+ hydration = fkSelectionResult.hydration;
4474
+ const targetColumns = resolveTargetColumns(relation, context.options);
4475
+ const targetSelection = buildTypedSelection(
4476
+ relation.target.columns,
4477
+ context.aliasPrefix,
4478
+ targetColumns,
4479
+ (key) => `Column '${key}' not found on relation '${context.relationName}'`
4480
+ );
4481
+ const relationSelectionResult = context.selectColumns(state, hydration, targetSelection);
4482
+ state = relationSelectionResult.state;
4483
+ hydration = relationSelectionResult.hydration;
4484
+ hydration = hydration.onRelationIncluded(
4485
+ state,
4486
+ relation,
4487
+ context.relationName,
4488
+ context.aliasPrefix,
4489
+ targetColumns
4490
+ );
4491
+ return { state, hydration };
4492
+ };
4493
+ var belongsToManyStrategy = (context) => {
4494
+ const relation = context.relation;
4495
+ let { state, hydration } = context;
4496
+ const targetColumns = resolveTargetColumns(relation, context.options);
4497
+ const targetSelection = buildTypedSelection(
4498
+ relation.target.columns,
4499
+ context.aliasPrefix,
4500
+ targetColumns,
4501
+ (key) => `Column '${key}' not found on relation '${context.relationName}'`
4502
+ );
4503
+ const pivotAliasPrefix = context.options?.pivot?.aliasPrefix ?? `${context.aliasPrefix}_pivot`;
4504
+ const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
4505
+ const defaultPivotColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
4506
+ const pivotColumns = context.options?.pivot?.columns ? [...context.options.pivot.columns] : [...defaultPivotColumns];
4507
+ const pivotSelection = buildTypedSelection(
4508
+ relation.pivotTable.columns,
4509
+ pivotAliasPrefix,
4510
+ pivotColumns,
4511
+ (key) => `Column '${key}' not found on pivot table '${relation.pivotTable.name}'`
4512
+ );
4513
+ const combinedSelection = {
4514
+ ...targetSelection,
4515
+ ...pivotSelection
4516
+ };
4517
+ const relationSelectionResult = context.selectColumns(state, hydration, combinedSelection);
4518
+ state = relationSelectionResult.state;
4519
+ hydration = relationSelectionResult.hydration;
4520
+ hydration = hydration.onRelationIncluded(
4521
+ state,
4522
+ relation,
4523
+ context.relationName,
4524
+ context.aliasPrefix,
4525
+ targetColumns,
4526
+ { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
4527
+ );
4528
+ return { state, hydration };
4529
+ };
4530
+ var relationIncludeStrategies = {
4531
+ [RelationKinds.HasMany]: standardIncludeStrategy,
4532
+ [RelationKinds.HasOne]: standardIncludeStrategy,
4533
+ [RelationKinds.BelongsTo]: standardIncludeStrategy,
4534
+ [RelationKinds.BelongsToMany]: belongsToManyStrategy
4535
+ };
4536
+
4537
+ // src/query-builder/relation-service.ts
4538
+ var RelationService = class {
4539
+ /**
4540
+ * Creates a new RelationService instance
4541
+ * @param table - Table definition
4542
+ * @param state - Current query state
4543
+ * @param hydration - Hydration manager
4544
+ */
4545
+ constructor(table, state, hydration, createQueryAstService) {
4546
+ this.table = table;
4547
+ this.state = state;
4548
+ this.hydration = hydration;
4549
+ this.createQueryAstService = createQueryAstService;
4550
+ this.projectionHelper = new RelationProjectionHelper(
4551
+ table,
4552
+ (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
4553
+ );
4554
+ this.joinPlanner = new RelationJoinPlanner(table, createQueryAstService);
4555
+ this.cteBuilder = new RelationCteBuilder(table, createQueryAstService);
4556
+ }
4557
+ projectionHelper;
4558
+ joinPlanner;
4559
+ cteBuilder;
4560
+ /**
4561
+ * Joins a relation to the query
4562
+ * @param relationName - Name of the relation to join
4563
+ * @param joinKind - Type of join to use
4564
+ * @param extraCondition - Additional join condition
4565
+ * @returns Relation result with updated state and hydration
4566
+ */
4567
+ joinRelation(relationName, joinKind, extraCondition, tableSource) {
4568
+ const relation = this.getRelation(relationName);
4569
+ const nextState = this.joinPlanner.withJoin(
4570
+ this.state,
4571
+ relationName,
4572
+ relation,
4573
+ joinKind,
4574
+ extraCondition,
4575
+ tableSource
4576
+ );
4577
+ return { state: nextState, hydration: this.hydration };
4578
+ }
4579
+ /**
4580
+ * Matches records based on a relation with an optional predicate
4581
+ * @param relationName - Name of the relation to match
4582
+ * @param predicate - Optional predicate expression
4583
+ * @returns Relation result with updated state and hydration
4584
+ */
4585
+ match(relationName, predicate) {
4586
+ const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
4587
+ const pk = findPrimaryKey(this.table);
4588
+ const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
4589
+ const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
4590
+ const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
4591
+ return { state: nextState, hydration: joined.hydration };
4592
+ }
4593
+ /**
4594
+ * Includes a relation in the query result
4595
+ * @param relationName - Name of the relation to include
4596
+ * @param options - Options for relation inclusion
4597
+ * @returns Relation result with updated state and hydration
4598
+ */
4599
+ include(relationName, options) {
4600
+ let state = this.state;
4601
+ let hydration = this.hydration;
4602
+ const relation = this.getRelation(relationName);
4603
+ const aliasPrefix = options?.aliasPrefix ?? relationName;
4604
+ const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
4605
+ const { selfFilters, crossFilters } = splitFilterExpressions(
4606
+ options?.filter,
4607
+ /* @__PURE__ */ new Set([relation.target.name])
4608
+ );
4609
+ const canUseCte = !alreadyJoined && selfFilters.length > 0;
4610
+ const joinFilters = [...crossFilters];
4611
+ if (!canUseCte) {
4612
+ joinFilters.push(...selfFilters);
4613
+ }
4614
+ const joinCondition = this.combineWithAnd(joinFilters);
4615
+ let tableSourceOverride;
4616
+ if (canUseCte) {
4617
+ const predicate = this.combineWithAnd(selfFilters);
4618
+ const cteInfo = this.cteBuilder.createFilteredRelationCte(
4619
+ state,
4620
+ relationName,
4260
4621
  relation,
4622
+ predicate
4623
+ );
4624
+ state = cteInfo.state;
4625
+ tableSourceOverride = cteInfo.table;
4626
+ }
4627
+ if (!alreadyJoined) {
4628
+ state = this.joinPlanner.withJoin(
4629
+ state,
4261
4630
  relationName,
4262
- aliasPrefix,
4263
- targetColumns
4631
+ relation,
4632
+ options?.joinKind ?? JOIN_KINDS.LEFT,
4633
+ joinCondition,
4634
+ tableSourceOverride
4264
4635
  );
4265
- return { state, hydration };
4266
- }
4267
- const many = relation;
4268
- const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
4269
- const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
4270
- const pivotColumns = options?.pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
4271
- const pivotSelection = buildTypedSelection(
4272
- many.pivotTable.columns,
4273
- pivotAliasPrefix,
4274
- pivotColumns,
4275
- (key) => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
4276
- );
4277
- const combinedSelection = {
4278
- ...targetSelection,
4279
- ...pivotSelection
4280
- };
4281
- const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
4282
- state = relationSelectionResult.state;
4283
- hydration = relationSelectionResult.hydration;
4284
- hydration = hydration.onRelationIncluded(
4636
+ }
4637
+ const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
4638
+ state = projectionResult.state;
4639
+ hydration = projectionResult.hydration;
4640
+ const strategy = relationIncludeStrategies[relation.type];
4641
+ const result = strategy({
4642
+ rootTable: this.table,
4285
4643
  state,
4644
+ hydration,
4286
4645
  relation,
4287
4646
  relationName,
4288
4647
  aliasPrefix,
4289
- targetColumns,
4290
- { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
4291
- );
4292
- return { state, hydration };
4648
+ options,
4649
+ selectColumns: (nextState, nextHydration, columns) => this.selectColumns(nextState, nextHydration, columns)
4650
+ });
4651
+ return { state: result.state, hydration: result.hydration };
4293
4652
  }
4294
4653
  /**
4295
4654
  * Applies relation correlation to a query AST
@@ -4310,37 +4669,6 @@ var RelationService = class {
4310
4669
  where: whereInSubquery
4311
4670
  };
4312
4671
  }
4313
- /**
4314
- * Creates a join node for a relation
4315
- * @param state - Current query state
4316
- * @param relationName - Name of the relation
4317
- * @param joinKind - Type of join to use
4318
- * @param extraCondition - Additional join condition
4319
- * @returns Updated query state with join
4320
- */
4321
- withJoin(state, relationName, joinKind, extraCondition) {
4322
- const relation = this.getRelation(relationName);
4323
- const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4324
- if (relation.type === RelationKinds.BelongsToMany) {
4325
- const joins = buildBelongsToManyJoins(
4326
- this.table,
4327
- relationName,
4328
- relation,
4329
- joinKind,
4330
- extraCondition,
4331
- rootAlias
4332
- );
4333
- return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
4334
- }
4335
- const condition = buildRelationJoinCondition(this.table, relation, extraCondition, rootAlias);
4336
- const joinNode = createJoinNode(
4337
- joinKind,
4338
- { type: "Table", name: relation.target.name, schema: relation.target.schema },
4339
- condition,
4340
- relationName
4341
- );
4342
- return this.astService(state).withJoin(joinNode);
4343
- }
4344
4672
  /**
4345
4673
  * Selects columns for a relation
4346
4674
  * @param state - Current query state
@@ -4355,6 +4683,15 @@ var RelationService = class {
4355
4683
  hydration: hydration.onColumnsSelected(nextState, addedColumns)
4356
4684
  };
4357
4685
  }
4686
+ combineWithAnd(expressions) {
4687
+ if (expressions.length === 0) return void 0;
4688
+ if (expressions.length === 1) return expressions[0];
4689
+ return {
4690
+ type: "LogicalExpression",
4691
+ operator: "AND",
4692
+ operands: expressions
4693
+ };
4694
+ }
4358
4695
  /**
4359
4696
  * Gets a relation definition by name
4360
4697
  * @param relationName - Name of the relation
@@ -4643,8 +4980,52 @@ var hasEntityMeta = (entity) => {
4643
4980
  return Boolean(getEntityMeta(entity));
4644
4981
  };
4645
4982
 
4646
- // src/orm/relations/has-many.ts
4983
+ // src/orm/entity-hydration.ts
4647
4984
  var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
4985
+ var populateHydrationCache = (entity, row, meta) => {
4986
+ for (const relationName of Object.keys(meta.table.relations)) {
4987
+ const relation = meta.table.relations[relationName];
4988
+ const data = row[relationName];
4989
+ if (relation.type === RelationKinds.HasOne) {
4990
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
4991
+ const rootValue = entity[localKey];
4992
+ if (rootValue === void 0 || rootValue === null) continue;
4993
+ if (!data || typeof data !== "object") continue;
4994
+ const cache = /* @__PURE__ */ new Map();
4995
+ cache.set(toKey2(rootValue), data);
4996
+ meta.relationHydration.set(relationName, cache);
4997
+ meta.relationCache.set(relationName, Promise.resolve(cache));
4998
+ continue;
4999
+ }
5000
+ if (!Array.isArray(data)) continue;
5001
+ if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
5002
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
5003
+ const rootValue = entity[localKey];
5004
+ if (rootValue === void 0 || rootValue === null) continue;
5005
+ const cache = /* @__PURE__ */ new Map();
5006
+ cache.set(toKey2(rootValue), data);
5007
+ meta.relationHydration.set(relationName, cache);
5008
+ meta.relationCache.set(relationName, Promise.resolve(cache));
5009
+ continue;
5010
+ }
5011
+ if (relation.type === RelationKinds.BelongsTo) {
5012
+ const targetKey = relation.localKey || findPrimaryKey(relation.target);
5013
+ const cache = /* @__PURE__ */ new Map();
5014
+ for (const item of data) {
5015
+ const pkValue = item[targetKey];
5016
+ if (pkValue === void 0 || pkValue === null) continue;
5017
+ cache.set(toKey2(pkValue), item);
5018
+ }
5019
+ if (cache.size) {
5020
+ meta.relationHydration.set(relationName, cache);
5021
+ meta.relationCache.set(relationName, Promise.resolve(cache));
5022
+ }
5023
+ }
5024
+ }
5025
+ };
5026
+
5027
+ // src/orm/relations/has-many.ts
5028
+ var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
4648
5029
  var hideInternal = (obj, keys) => {
4649
5030
  for (const key of keys) {
4650
5031
  Object.defineProperty(obj, key, {
@@ -4678,13 +5059,13 @@ var DefaultHasManyCollection = class {
4678
5059
  this.loader = loader;
4679
5060
  this.createEntity = createEntity;
4680
5061
  this.localKey = localKey;
4681
- this.loaded = false;
4682
- this.items = [];
4683
- this.added = /* @__PURE__ */ new Set();
4684
- this.removed = /* @__PURE__ */ new Set();
4685
5062
  hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
4686
5063
  this.hydrateFromCache();
4687
5064
  }
5065
+ loaded = false;
5066
+ items = [];
5067
+ added = /* @__PURE__ */ new Set();
5068
+ removed = /* @__PURE__ */ new Set();
4688
5069
  /**
4689
5070
  * Loads the related entities if not already loaded.
4690
5071
  * @returns Promise resolving to the array of child entities
@@ -4692,7 +5073,7 @@ var DefaultHasManyCollection = class {
4692
5073
  async load() {
4693
5074
  if (this.loaded) return this.items;
4694
5075
  const map = await this.loader();
4695
- const key = toKey2(this.root[this.localKey]);
5076
+ const key = toKey3(this.root[this.localKey]);
4696
5077
  const rows = map.get(key) ?? [];
4697
5078
  this.items = rows.map((row) => this.createEntity(row));
4698
5079
  this.loaded = true;
@@ -4705,6 +5086,18 @@ var DefaultHasManyCollection = class {
4705
5086
  getItems() {
4706
5087
  return this.items;
4707
5088
  }
5089
+ /**
5090
+ * Array-compatible length for testing frameworks.
5091
+ */
5092
+ get length() {
5093
+ return this.items.length;
5094
+ }
5095
+ /**
5096
+ * Enables iteration over the collection like an array.
5097
+ */
5098
+ [Symbol.iterator]() {
5099
+ return this.items[Symbol.iterator]();
5100
+ }
4708
5101
  /**
4709
5102
  * Adds a new child entity to the collection.
4710
5103
  * @param data - Partial data for the new entity
@@ -4792,7 +5185,7 @@ var DefaultHasManyCollection = class {
4792
5185
  };
4793
5186
 
4794
5187
  // src/orm/relations/has-one.ts
4795
- var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
5188
+ var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
4796
5189
  var hideInternal2 = (obj, keys) => {
4797
5190
  for (const key of keys) {
4798
5191
  Object.defineProperty(obj, key, {
@@ -4825,8 +5218,6 @@ var DefaultHasOneReference = class {
4825
5218
  this.loader = loader;
4826
5219
  this.createEntity = createEntity;
4827
5220
  this.localKey = localKey;
4828
- this.loaded = false;
4829
- this.current = null;
4830
5221
  hideInternal2(this, [
4831
5222
  "ctx",
4832
5223
  "meta",
@@ -4840,6 +5231,8 @@ var DefaultHasOneReference = class {
4840
5231
  ]);
4841
5232
  this.populateFromHydrationCache();
4842
5233
  }
5234
+ loaded = false;
5235
+ current = null;
4843
5236
  async load() {
4844
5237
  if (this.loaded) return this.current;
4845
5238
  const map = await this.loader();
@@ -4848,7 +5241,7 @@ var DefaultHasOneReference = class {
4848
5241
  this.loaded = true;
4849
5242
  return this.current;
4850
5243
  }
4851
- const row = map.get(toKey3(keyValue));
5244
+ const row = map.get(toKey4(keyValue));
4852
5245
  this.current = row ? this.createEntity(row) : null;
4853
5246
  this.loaded = true;
4854
5247
  return this.current;
@@ -4920,7 +5313,7 @@ var DefaultHasOneReference = class {
4920
5313
  };
4921
5314
 
4922
5315
  // src/orm/relations/belongs-to.ts
4923
- var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
5316
+ var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
4924
5317
  var hideInternal3 = (obj, keys) => {
4925
5318
  for (const key of keys) {
4926
5319
  Object.defineProperty(obj, key, {
@@ -4953,11 +5346,11 @@ var DefaultBelongsToReference = class {
4953
5346
  this.loader = loader;
4954
5347
  this.createEntity = createEntity;
4955
5348
  this.targetKey = targetKey;
4956
- this.loaded = false;
4957
- this.current = null;
4958
5349
  hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
4959
5350
  this.populateFromHydrationCache();
4960
5351
  }
5352
+ loaded = false;
5353
+ current = null;
4961
5354
  async load() {
4962
5355
  if (this.loaded) return this.current;
4963
5356
  const map = await this.loader();
@@ -4965,7 +5358,7 @@ var DefaultBelongsToReference = class {
4965
5358
  if (fkValue === null || fkValue === void 0) {
4966
5359
  this.current = null;
4967
5360
  } else {
4968
- const row = map.get(toKey4(fkValue));
5361
+ const row = map.get(toKey5(fkValue));
4969
5362
  this.current = row ? this.createEntity(row) : null;
4970
5363
  }
4971
5364
  this.loaded = true;
@@ -5022,7 +5415,7 @@ var DefaultBelongsToReference = class {
5022
5415
  };
5023
5416
 
5024
5417
  // src/orm/relations/many-to-many.ts
5025
- var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
5418
+ var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
5026
5419
  var hideInternal4 = (obj, keys) => {
5027
5420
  for (const key of keys) {
5028
5421
  Object.defineProperty(obj, key, {
@@ -5055,11 +5448,11 @@ var DefaultManyToManyCollection = class {
5055
5448
  this.loader = loader;
5056
5449
  this.createEntity = createEntity;
5057
5450
  this.localKey = localKey;
5058
- this.loaded = false;
5059
- this.items = [];
5060
5451
  hideInternal4(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
5061
5452
  this.hydrateFromCache();
5062
5453
  }
5454
+ loaded = false;
5455
+ items = [];
5063
5456
  /**
5064
5457
  * Loads the collection items if not already loaded.
5065
5458
  * @returns A promise that resolves to the array of target entities.
@@ -5067,7 +5460,7 @@ var DefaultManyToManyCollection = class {
5067
5460
  async load() {
5068
5461
  if (this.loaded) return this.items;
5069
5462
  const map = await this.loader();
5070
- const key = toKey5(this.root[this.localKey]);
5463
+ const key = toKey6(this.root[this.localKey]);
5071
5464
  const rows = map.get(key) ?? [];
5072
5465
  this.items = rows.map((row) => {
5073
5466
  const entity = this.createEntity(row);
@@ -5086,6 +5479,18 @@ var DefaultManyToManyCollection = class {
5086
5479
  getItems() {
5087
5480
  return this.items;
5088
5481
  }
5482
+ /**
5483
+ * Array-compatible length for testing frameworks.
5484
+ */
5485
+ get length() {
5486
+ return this.items.length;
5487
+ }
5488
+ /**
5489
+ * Enables iteration over the collection like an array.
5490
+ */
5491
+ [Symbol.iterator]() {
5492
+ return this.items[Symbol.iterator]();
5493
+ }
5089
5494
  /**
5090
5495
  * Attaches an entity to the collection.
5091
5496
  * Registers an 'attach' change in the entity context.
@@ -5137,15 +5542,15 @@ var DefaultManyToManyCollection = class {
5137
5542
  */
5138
5543
  async syncByIds(ids) {
5139
5544
  await this.load();
5140
- const normalized = new Set(ids.map((id) => toKey5(id)));
5141
- const currentIds = new Set(this.items.map((item) => toKey5(this.extractId(item))));
5545
+ const normalized = new Set(ids.map((id) => toKey6(id)));
5546
+ const currentIds = new Set(this.items.map((item) => toKey6(this.extractId(item))));
5142
5547
  for (const id of normalized) {
5143
5548
  if (!currentIds.has(id)) {
5144
5549
  this.attach(id);
5145
5550
  }
5146
5551
  }
5147
5552
  for (const item of [...this.items]) {
5148
- const itemId = toKey5(this.extractId(item));
5553
+ const itemId = toKey6(this.extractId(item));
5149
5554
  if (!normalized.has(itemId)) {
5150
5555
  this.detach(item);
5151
5556
  }
@@ -5192,11 +5597,28 @@ var DefaultManyToManyCollection = class {
5192
5597
  }
5193
5598
  };
5194
5599
 
5195
- // src/orm/lazy-batch.ts
5196
- var selectAllColumns = (table) => Object.entries(table.columns).reduce((acc, [name, def]) => {
5197
- acc[name] = def;
5198
- return acc;
5199
- }, {});
5600
+ // src/orm/lazy-batch/shared.ts
5601
+ var hasColumns = (columns) => Boolean(columns && columns.length > 0);
5602
+ var buildColumnSelection = (table, columns, missingMsg) => {
5603
+ return columns.reduce((acc, column) => {
5604
+ const def = table.columns[column];
5605
+ if (!def) {
5606
+ throw new Error(missingMsg(column));
5607
+ }
5608
+ acc[column] = def;
5609
+ return acc;
5610
+ }, {});
5611
+ };
5612
+ var filterRow = (row, columns) => {
5613
+ const filtered = {};
5614
+ for (const column of columns) {
5615
+ if (column in row) {
5616
+ filtered[column] = row[column];
5617
+ }
5618
+ }
5619
+ return filtered;
5620
+ };
5621
+ var filterRows = (rows, columns) => rows.map((row) => filterRow(row, columns));
5200
5622
  var rowsFromResults = (results) => {
5201
5623
  const rows = [];
5202
5624
  for (const result of results) {
@@ -5216,7 +5638,7 @@ var executeQuery = async (ctx, qb) => {
5216
5638
  const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
5217
5639
  return rowsFromResults(results);
5218
5640
  };
5219
- var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
5641
+ var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
5220
5642
  var collectKeysFromRoots = (roots, key) => {
5221
5643
  const collected = /* @__PURE__ */ new Set();
5222
5644
  for (const tracked of roots) {
@@ -5228,9 +5650,12 @@ var collectKeysFromRoots = (roots, key) => {
5228
5650
  return collected;
5229
5651
  };
5230
5652
  var buildInListValues = (keys) => Array.from(keys);
5231
- var fetchRowsForKeys = async (ctx, table, column, keys) => {
5232
- const qb = new SelectQueryBuilder(table).select(selectAllColumns(table));
5233
- qb.where(inList(column, buildInListValues(keys)));
5653
+ var fetchRowsForKeys = async (ctx, table, column, keys, selection, filter) => {
5654
+ let qb = new SelectQueryBuilder(table).select(selection);
5655
+ qb = qb.where(inList(column, buildInListValues(keys)));
5656
+ if (filter) {
5657
+ qb = qb.where(filter);
5658
+ }
5234
5659
  return executeQuery(ctx, qb);
5235
5660
  };
5236
5661
  var groupRowsByMany = (rows, keyColumn) => {
@@ -5238,7 +5663,7 @@ var groupRowsByMany = (rows, keyColumn) => {
5238
5663
  for (const row of rows) {
5239
5664
  const value = row[keyColumn];
5240
5665
  if (value === null || value === void 0) continue;
5241
- const key = toKey6(value);
5666
+ const key = toKey7(value);
5242
5667
  const bucket = grouped.get(key) ?? [];
5243
5668
  bucket.push(row);
5244
5669
  grouped.set(key, bucket);
@@ -5250,14 +5675,16 @@ var groupRowsByUnique = (rows, keyColumn) => {
5250
5675
  for (const row of rows) {
5251
5676
  const value = row[keyColumn];
5252
5677
  if (value === null || value === void 0) continue;
5253
- const key = toKey6(value);
5678
+ const key = toKey7(value);
5254
5679
  if (!lookup.has(key)) {
5255
5680
  lookup.set(key, row);
5256
5681
  }
5257
5682
  }
5258
5683
  return lookup;
5259
5684
  };
5260
- var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
5685
+
5686
+ // src/orm/lazy-batch/has-many.ts
5687
+ var loadHasManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5261
5688
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5262
5689
  const roots = ctx.getEntitiesForTable(rootTable);
5263
5690
  const keys = collectKeysFromRoots(roots, localKey);
@@ -5266,10 +5693,32 @@ var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
5266
5693
  }
5267
5694
  const fkColumn = relation.target.columns[relation.foreignKey];
5268
5695
  if (!fkColumn) return /* @__PURE__ */ new Map();
5269
- const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys);
5270
- return groupRowsByMany(rows, relation.foreignKey);
5696
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5697
+ const targetPrimaryKey = findPrimaryKey(relation.target);
5698
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5699
+ if (!selectedColumns.includes(targetPrimaryKey)) {
5700
+ selectedColumns.push(targetPrimaryKey);
5701
+ }
5702
+ const queryColumns = new Set(selectedColumns);
5703
+ queryColumns.add(relation.foreignKey);
5704
+ const selection = buildColumnSelection(
5705
+ relation.target,
5706
+ Array.from(queryColumns),
5707
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5708
+ );
5709
+ const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys, selection, options?.filter);
5710
+ const grouped = groupRowsByMany(rows, relation.foreignKey);
5711
+ if (!requestedColumns) return grouped;
5712
+ const visibleColumns = new Set(selectedColumns);
5713
+ const filtered = /* @__PURE__ */ new Map();
5714
+ for (const [key, bucket] of grouped.entries()) {
5715
+ filtered.set(key, filterRows(bucket, visibleColumns));
5716
+ }
5717
+ return filtered;
5271
5718
  };
5272
- var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
5719
+
5720
+ // src/orm/lazy-batch/has-one.ts
5721
+ var loadHasOneRelation = async (ctx, rootTable, relationName, relation, options) => {
5273
5722
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5274
5723
  const roots = ctx.getEntitiesForTable(rootTable);
5275
5724
  const keys = collectKeysFromRoots(roots, localKey);
@@ -5278,22 +5727,102 @@ var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
5278
5727
  }
5279
5728
  const fkColumn = relation.target.columns[relation.foreignKey];
5280
5729
  if (!fkColumn) return /* @__PURE__ */ new Map();
5281
- const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys);
5282
- return groupRowsByUnique(rows, relation.foreignKey);
5730
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5731
+ const targetPrimaryKey = findPrimaryKey(relation.target);
5732
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5733
+ if (!selectedColumns.includes(targetPrimaryKey)) {
5734
+ selectedColumns.push(targetPrimaryKey);
5735
+ }
5736
+ const queryColumns = new Set(selectedColumns);
5737
+ queryColumns.add(relation.foreignKey);
5738
+ const selection = buildColumnSelection(
5739
+ relation.target,
5740
+ Array.from(queryColumns),
5741
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5742
+ );
5743
+ const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys, selection, options?.filter);
5744
+ const grouped = groupRowsByUnique(rows, relation.foreignKey);
5745
+ if (!requestedColumns) return grouped;
5746
+ const visibleColumns = new Set(selectedColumns);
5747
+ const filtered = /* @__PURE__ */ new Map();
5748
+ for (const [key, row] of grouped.entries()) {
5749
+ filtered.set(key, filterRow(row, visibleColumns));
5750
+ }
5751
+ return filtered;
5283
5752
  };
5284
- var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
5753
+
5754
+ // src/orm/lazy-batch/belongs-to.ts
5755
+ var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, options) => {
5285
5756
  const roots = ctx.getEntitiesForTable(rootTable);
5286
- const foreignKeys = collectKeysFromRoots(roots, relation.foreignKey);
5757
+ const getForeignKeys = () => collectKeysFromRoots(roots, relation.foreignKey);
5758
+ let foreignKeys = getForeignKeys();
5759
+ if (!foreignKeys.size) {
5760
+ const pkName = findPrimaryKey(rootTable);
5761
+ const pkColumn2 = rootTable.columns[pkName];
5762
+ const fkColumn = rootTable.columns[relation.foreignKey];
5763
+ if (pkColumn2 && fkColumn) {
5764
+ const missingKeys = /* @__PURE__ */ new Set();
5765
+ const entityByPk = /* @__PURE__ */ new Map();
5766
+ for (const tracked of roots) {
5767
+ const entity = tracked.entity;
5768
+ const pkValue = entity[pkName];
5769
+ if (pkValue === void 0 || pkValue === null) continue;
5770
+ const fkValue = entity[relation.foreignKey];
5771
+ if (fkValue === void 0 || fkValue === null) {
5772
+ missingKeys.add(pkValue);
5773
+ entityByPk.set(pkValue, entity);
5774
+ }
5775
+ }
5776
+ if (missingKeys.size) {
5777
+ const selection2 = buildColumnSelection(
5778
+ rootTable,
5779
+ [pkName, relation.foreignKey],
5780
+ (column) => `Column '${column}' not found on table '${rootTable.name}'`
5781
+ );
5782
+ const keyRows = await fetchRowsForKeys(ctx, rootTable, pkColumn2, missingKeys, selection2);
5783
+ for (const row of keyRows) {
5784
+ const pkValue = row[pkName];
5785
+ if (pkValue === void 0 || pkValue === null) continue;
5786
+ const entity = entityByPk.get(pkValue);
5787
+ if (!entity) continue;
5788
+ const fkValue = row[relation.foreignKey];
5789
+ if (fkValue !== void 0 && fkValue !== null) {
5790
+ entity[relation.foreignKey] = fkValue;
5791
+ }
5792
+ }
5793
+ foreignKeys = getForeignKeys();
5794
+ }
5795
+ }
5796
+ }
5287
5797
  if (!foreignKeys.size) {
5288
5798
  return /* @__PURE__ */ new Map();
5289
5799
  }
5290
5800
  const targetKey = relation.localKey || findPrimaryKey(relation.target);
5291
5801
  const pkColumn = relation.target.columns[targetKey];
5292
5802
  if (!pkColumn) return /* @__PURE__ */ new Map();
5293
- const rows = await fetchRowsForKeys(ctx, relation.target, pkColumn, foreignKeys);
5294
- return groupRowsByUnique(rows, targetKey);
5803
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5804
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5805
+ if (!selectedColumns.includes(targetKey)) {
5806
+ selectedColumns.push(targetKey);
5807
+ }
5808
+ const selection = buildColumnSelection(
5809
+ relation.target,
5810
+ selectedColumns,
5811
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5812
+ );
5813
+ const rows = await fetchRowsForKeys(ctx, relation.target, pkColumn, foreignKeys, selection, options?.filter);
5814
+ const grouped = groupRowsByUnique(rows, targetKey);
5815
+ if (!requestedColumns) return grouped;
5816
+ const visibleColumns = new Set(selectedColumns);
5817
+ const filtered = /* @__PURE__ */ new Map();
5818
+ for (const [key, row] of grouped.entries()) {
5819
+ filtered.set(key, filterRow(row, visibleColumns));
5820
+ }
5821
+ return filtered;
5295
5822
  };
5296
- var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation) => {
5823
+
5824
+ // src/orm/lazy-batch/belongs-to-many.ts
5825
+ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5297
5826
  const rootKey = relation.localKey || findPrimaryKey(rootTable);
5298
5827
  const roots = ctx.getEntitiesForTable(rootTable);
5299
5828
  const rootIds = collectKeysFromRoots(roots, rootKey);
@@ -5302,21 +5831,41 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5302
5831
  }
5303
5832
  const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
5304
5833
  if (!pivotColumn) return /* @__PURE__ */ new Map();
5305
- const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds);
5834
+ const pivotColumnsRequested = hasColumns(options?.pivot?.columns) ? [...options.pivot.columns] : void 0;
5835
+ const useIncludeDefaults = options !== void 0;
5836
+ let pivotSelectedColumns;
5837
+ if (pivotColumnsRequested) {
5838
+ pivotSelectedColumns = [...pivotColumnsRequested];
5839
+ } else if (useIncludeDefaults) {
5840
+ const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
5841
+ pivotSelectedColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
5842
+ } else {
5843
+ pivotSelectedColumns = Object.keys(relation.pivotTable.columns);
5844
+ }
5845
+ const pivotQueryColumns = new Set(pivotSelectedColumns);
5846
+ pivotQueryColumns.add(relation.pivotForeignKeyToRoot);
5847
+ pivotQueryColumns.add(relation.pivotForeignKeyToTarget);
5848
+ const pivotSelection = buildColumnSelection(
5849
+ relation.pivotTable,
5850
+ Array.from(pivotQueryColumns),
5851
+ (column) => `Column '${column}' not found on pivot table '${relation.pivotTable.name}'`
5852
+ );
5853
+ const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds, pivotSelection);
5306
5854
  const rootLookup = /* @__PURE__ */ new Map();
5307
5855
  const targetIds = /* @__PURE__ */ new Set();
5856
+ const pivotVisibleColumns = new Set(pivotSelectedColumns);
5308
5857
  for (const pivot of pivotRows) {
5309
5858
  const rootValue = pivot[relation.pivotForeignKeyToRoot];
5310
5859
  const targetValue = pivot[relation.pivotForeignKeyToTarget];
5311
5860
  if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
5312
5861
  continue;
5313
5862
  }
5314
- const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
5863
+ const bucket = rootLookup.get(toKey7(rootValue)) ?? [];
5315
5864
  bucket.push({
5316
5865
  targetId: targetValue,
5317
- pivot: { ...pivot }
5866
+ pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
5318
5867
  });
5319
- rootLookup.set(toKey6(rootValue), bucket);
5868
+ rootLookup.set(toKey7(rootValue), bucket);
5320
5869
  targetIds.add(targetValue);
5321
5870
  }
5322
5871
  if (!targetIds.size) {
@@ -5325,16 +5874,34 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5325
5874
  const targetKey = relation.targetKey || findPrimaryKey(relation.target);
5326
5875
  const targetPkColumn = relation.target.columns[targetKey];
5327
5876
  if (!targetPkColumn) return /* @__PURE__ */ new Map();
5328
- const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds);
5877
+ const targetRequestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5878
+ const targetSelectedColumns = targetRequestedColumns ? [...targetRequestedColumns] : Object.keys(relation.target.columns);
5879
+ if (!targetSelectedColumns.includes(targetKey)) {
5880
+ targetSelectedColumns.push(targetKey);
5881
+ }
5882
+ const targetSelection = buildColumnSelection(
5883
+ relation.target,
5884
+ targetSelectedColumns,
5885
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5886
+ );
5887
+ const targetRows = await fetchRowsForKeys(
5888
+ ctx,
5889
+ relation.target,
5890
+ targetPkColumn,
5891
+ targetIds,
5892
+ targetSelection,
5893
+ options?.filter
5894
+ );
5329
5895
  const targetMap = groupRowsByUnique(targetRows, targetKey);
5896
+ const targetVisibleColumns = new Set(targetSelectedColumns);
5330
5897
  const result = /* @__PURE__ */ new Map();
5331
5898
  for (const [rootId, entries] of rootLookup.entries()) {
5332
5899
  const bucket = [];
5333
5900
  for (const entry of entries) {
5334
- const targetRow = targetMap.get(toKey6(entry.targetId));
5901
+ const targetRow = targetMap.get(toKey7(entry.targetId));
5335
5902
  if (!targetRow) continue;
5336
5903
  bucket.push({
5337
- ...targetRow,
5904
+ ...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
5338
5905
  _pivot: entry.pivot
5339
5906
  });
5340
5907
  }
@@ -5343,7 +5910,7 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5343
5910
  return result;
5344
5911
  };
5345
5912
 
5346
- // src/orm/entity.ts
5913
+ // src/orm/entity-relation-cache.ts
5347
5914
  var relationLoaderCache = (meta, relationName, factory) => {
5348
5915
  if (meta.relationCache.has(relationName)) {
5349
5916
  return meta.relationCache.get(relationName);
@@ -5364,200 +5931,147 @@ var relationLoaderCache = (meta, relationName, factory) => {
5364
5931
  }
5365
5932
  return promise;
5366
5933
  };
5367
- var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
5368
- const target = { ...row };
5369
- const meta = {
5370
- ctx,
5371
- table,
5372
- lazyRelations: [...lazyRelations],
5373
- relationCache: /* @__PURE__ */ new Map(),
5374
- relationHydration: /* @__PURE__ */ new Map(),
5375
- relationWrappers: /* @__PURE__ */ new Map()
5376
- };
5377
- Object.defineProperty(target, ENTITY_META, {
5378
- value: meta,
5379
- enumerable: false,
5380
- writable: false
5381
- });
5382
- const handler = {
5383
- get(targetObj, prop, receiver) {
5384
- if (prop === ENTITY_META) {
5385
- return meta;
5934
+
5935
+ // src/orm/entity-relations.ts
5936
+ var proxifyRelationWrapper = (wrapper) => {
5937
+ return new Proxy(wrapper, {
5938
+ get(target, prop, receiver) {
5939
+ if (typeof prop === "symbol") {
5940
+ return Reflect.get(target, prop, receiver);
5386
5941
  }
5387
- if (prop === "$load") {
5388
- return async (relationName) => {
5389
- const wrapper = getRelationWrapper(meta, relationName, receiver);
5390
- if (wrapper && typeof wrapper.load === "function") {
5391
- return wrapper.load();
5392
- }
5393
- return void 0;
5394
- };
5942
+ if (prop in target) {
5943
+ return Reflect.get(target, prop, receiver);
5395
5944
  }
5396
- if (typeof prop === "string" && table.relations[prop]) {
5397
- return getRelationWrapper(meta, prop, receiver);
5945
+ const getItems = target.getItems;
5946
+ if (typeof getItems === "function") {
5947
+ const items = getItems.call(target);
5948
+ if (items && prop in items) {
5949
+ const propName = prop;
5950
+ const value = items[propName];
5951
+ return typeof value === "function" ? value.bind(items) : value;
5952
+ }
5398
5953
  }
5399
- return Reflect.get(targetObj, prop, receiver);
5954
+ const getRef = target.get;
5955
+ if (typeof getRef === "function") {
5956
+ const current = getRef.call(target);
5957
+ if (current && prop in current) {
5958
+ const propName = prop;
5959
+ const value = current[propName];
5960
+ return typeof value === "function" ? value.bind(current) : value;
5961
+ }
5962
+ }
5963
+ return void 0;
5400
5964
  },
5401
- set(targetObj, prop, value, receiver) {
5402
- const result = Reflect.set(targetObj, prop, value, receiver);
5403
- if (typeof prop === "string" && table.columns[prop]) {
5404
- ctx.markDirty(receiver);
5965
+ set(target, prop, value, receiver) {
5966
+ if (typeof prop === "symbol") {
5967
+ return Reflect.set(target, prop, value, receiver);
5405
5968
  }
5406
- return result;
5407
- }
5408
- };
5409
- const proxy = new Proxy(target, handler);
5410
- populateHydrationCache(proxy, row, meta);
5411
- return proxy;
5412
- };
5413
- var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
5414
- const pkName = findPrimaryKey(table);
5415
- const pkValue = row[pkName];
5416
- if (pkValue !== void 0 && pkValue !== null) {
5417
- const tracked = ctx.getEntity(table, pkValue);
5418
- if (tracked) return tracked;
5419
- }
5420
- const entity = createEntityProxy(ctx, table, row, lazyRelations);
5421
- if (pkValue !== void 0 && pkValue !== null) {
5422
- ctx.trackManaged(table, pkValue, entity);
5423
- } else {
5424
- ctx.trackNew(table, entity);
5425
- }
5426
- return entity;
5427
- };
5428
- var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
5429
- var populateHydrationCache = (entity, row, meta) => {
5430
- for (const relationName of Object.keys(meta.table.relations)) {
5431
- const relation = meta.table.relations[relationName];
5432
- const data = row[relationName];
5433
- if (relation.type === RelationKinds.HasOne) {
5434
- const localKey = relation.localKey || findPrimaryKey(meta.table);
5435
- const rootValue = entity[localKey];
5436
- if (rootValue === void 0 || rootValue === null) continue;
5437
- if (!data || typeof data !== "object") continue;
5438
- const cache = /* @__PURE__ */ new Map();
5439
- cache.set(toKey7(rootValue), data);
5440
- meta.relationHydration.set(relationName, cache);
5441
- meta.relationCache.set(relationName, Promise.resolve(cache));
5442
- continue;
5443
- }
5444
- if (!Array.isArray(data)) continue;
5445
- if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
5446
- const localKey = relation.localKey || findPrimaryKey(meta.table);
5447
- const rootValue = entity[localKey];
5448
- if (rootValue === void 0 || rootValue === null) continue;
5449
- const cache = /* @__PURE__ */ new Map();
5450
- cache.set(toKey7(rootValue), data);
5451
- meta.relationHydration.set(relationName, cache);
5452
- meta.relationCache.set(relationName, Promise.resolve(cache));
5453
- continue;
5454
- }
5455
- if (relation.type === RelationKinds.BelongsTo) {
5456
- const targetKey = relation.localKey || findPrimaryKey(relation.target);
5457
- const cache = /* @__PURE__ */ new Map();
5458
- for (const item of data) {
5459
- const pkValue = item[targetKey];
5460
- if (pkValue === void 0 || pkValue === null) continue;
5461
- cache.set(toKey7(pkValue), item);
5969
+ if (prop in target) {
5970
+ return Reflect.set(target, prop, value, receiver);
5462
5971
  }
5463
- if (cache.size) {
5464
- meta.relationHydration.set(relationName, cache);
5465
- meta.relationCache.set(relationName, Promise.resolve(cache));
5972
+ const getRef = target.get;
5973
+ if (typeof getRef === "function") {
5974
+ const current = getRef.call(target);
5975
+ if (current && typeof current === "object") {
5976
+ return Reflect.set(current, prop, value);
5977
+ }
5978
+ }
5979
+ const getItems = target.getItems;
5980
+ if (typeof getItems === "function") {
5981
+ const items = getItems.call(target);
5982
+ return Reflect.set(items, prop, value);
5466
5983
  }
5984
+ return Reflect.set(target, prop, value, receiver);
5467
5985
  }
5468
- }
5986
+ });
5469
5987
  };
5470
- var getRelationWrapper = (meta, relationName, owner) => {
5471
- if (meta.relationWrappers.has(relationName)) {
5472
- return meta.relationWrappers.get(relationName);
5988
+ var getRelationWrapper = (meta, relationName, owner, createEntity) => {
5989
+ const relationKey = relationName;
5990
+ if (meta.relationWrappers.has(relationKey)) {
5991
+ return meta.relationWrappers.get(relationKey);
5473
5992
  }
5474
- const relation = meta.table.relations[relationName];
5993
+ const relation = meta.table.relations[relationKey];
5475
5994
  if (!relation) return void 0;
5476
- const wrapper = instantiateWrapper(meta, relationName, relation, owner);
5477
- if (wrapper) {
5478
- meta.relationWrappers.set(relationName, wrapper);
5479
- }
5480
- return wrapper;
5481
- };
5482
- var instantiateWrapper = (meta, relationName, relation, owner) => {
5995
+ const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
5996
+ if (!wrapper) return void 0;
5997
+ const proxied = proxifyRelationWrapper(wrapper);
5998
+ meta.relationWrappers.set(relationKey, proxied);
5999
+ return proxied;
6000
+ };
6001
+ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) => {
6002
+ const metaBase = meta;
6003
+ const lazyOptions = meta.lazyRelationOptions.get(relationName);
6004
+ const loadCached = (factory) => relationLoaderCache(metaBase, relationName, factory);
5483
6005
  switch (relation.type) {
5484
6006
  case RelationKinds.HasOne: {
5485
6007
  const hasOne2 = relation;
5486
6008
  const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
5487
- const loader = () => relationLoaderCache(
5488
- meta,
5489
- relationName,
5490
- () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2)
6009
+ const loader = () => loadCached(
6010
+ () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, lazyOptions)
5491
6011
  );
5492
6012
  return new DefaultHasOneReference(
5493
6013
  meta.ctx,
5494
- meta,
6014
+ metaBase,
5495
6015
  owner,
5496
6016
  relationName,
5497
6017
  hasOne2,
5498
6018
  meta.table,
5499
6019
  loader,
5500
- (row) => createEntityFromRow(meta.ctx, hasOne2.target, row),
6020
+ (row) => createEntity(hasOne2.target, row),
5501
6021
  localKey
5502
6022
  );
5503
6023
  }
5504
6024
  case RelationKinds.HasMany: {
5505
6025
  const hasMany2 = relation;
5506
6026
  const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
5507
- const loader = () => relationLoaderCache(
5508
- meta,
5509
- relationName,
5510
- () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2)
6027
+ const loader = () => loadCached(
6028
+ () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, lazyOptions)
5511
6029
  );
5512
6030
  return new DefaultHasManyCollection(
5513
6031
  meta.ctx,
5514
- meta,
6032
+ metaBase,
5515
6033
  owner,
5516
6034
  relationName,
5517
6035
  hasMany2,
5518
6036
  meta.table,
5519
6037
  loader,
5520
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
6038
+ (row) => createEntity(relation.target, row),
5521
6039
  localKey
5522
6040
  );
5523
6041
  }
5524
6042
  case RelationKinds.BelongsTo: {
5525
6043
  const belongsTo2 = relation;
5526
6044
  const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
5527
- const loader = () => relationLoaderCache(
5528
- meta,
5529
- relationName,
5530
- () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2)
6045
+ const loader = () => loadCached(
6046
+ () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, lazyOptions)
5531
6047
  );
5532
6048
  return new DefaultBelongsToReference(
5533
6049
  meta.ctx,
5534
- meta,
6050
+ metaBase,
5535
6051
  owner,
5536
6052
  relationName,
5537
6053
  belongsTo2,
5538
6054
  meta.table,
5539
6055
  loader,
5540
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
6056
+ (row) => createEntity(relation.target, row),
5541
6057
  targetKey
5542
6058
  );
5543
6059
  }
5544
6060
  case RelationKinds.BelongsToMany: {
5545
6061
  const many = relation;
5546
6062
  const localKey = many.localKey || findPrimaryKey(meta.table);
5547
- const loader = () => relationLoaderCache(
5548
- meta,
5549
- relationName,
5550
- () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
6063
+ const loader = () => loadCached(
6064
+ () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
5551
6065
  );
5552
6066
  return new DefaultManyToManyCollection(
5553
6067
  meta.ctx,
5554
- meta,
6068
+ metaBase,
5555
6069
  owner,
5556
6070
  relationName,
5557
6071
  many,
5558
6072
  meta.table,
5559
6073
  loader,
5560
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
6074
+ (row) => createEntity(relation.target, row),
5561
6075
  localKey
5562
6076
  );
5563
6077
  }
@@ -5566,51 +6080,570 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
5566
6080
  }
5567
6081
  };
5568
6082
 
5569
- // src/orm/execute.ts
5570
- var flattenResults = (results) => {
5571
- const rows = [];
5572
- for (const result of results) {
5573
- const { columns, values } = result;
5574
- for (const valueRow of values) {
5575
- const row = {};
5576
- columns.forEach((column, idx) => {
5577
- row[column] = valueRow[idx];
5578
- });
5579
- rows.push(row);
5580
- }
6083
+ // src/orm/entity.ts
6084
+ var createEntityProxy = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
6085
+ const target = { ...row };
6086
+ const meta = {
6087
+ ctx,
6088
+ table,
6089
+ lazyRelations: [...lazyRelations],
6090
+ lazyRelationOptions: new Map(lazyRelationOptions),
6091
+ relationCache: /* @__PURE__ */ new Map(),
6092
+ relationHydration: /* @__PURE__ */ new Map(),
6093
+ relationWrappers: /* @__PURE__ */ new Map()
6094
+ };
6095
+ const createRelationEntity = (relationTable, relationRow) => createEntityFromRow(meta.ctx, relationTable, relationRow);
6096
+ Object.defineProperty(target, ENTITY_META, {
6097
+ value: meta,
6098
+ enumerable: false,
6099
+ writable: false
6100
+ });
6101
+ const handler = {
6102
+ get(targetObj, prop, receiver) {
6103
+ if (prop === ENTITY_META) {
6104
+ return meta;
6105
+ }
6106
+ if (prop === "$load") {
6107
+ return async (relationName) => {
6108
+ const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
6109
+ if (wrapper && typeof wrapper.load === "function") {
6110
+ return wrapper.load();
6111
+ }
6112
+ return void 0;
6113
+ };
6114
+ }
6115
+ if (typeof prop === "string" && table.relations[prop]) {
6116
+ return getRelationWrapper(meta, prop, receiver, createRelationEntity);
6117
+ }
6118
+ return Reflect.get(targetObj, prop, receiver);
6119
+ },
6120
+ set(targetObj, prop, value, receiver) {
6121
+ const result = Reflect.set(targetObj, prop, value, receiver);
6122
+ if (typeof prop === "string" && table.columns[prop]) {
6123
+ ctx.markDirty(receiver);
6124
+ }
6125
+ return result;
6126
+ }
6127
+ };
6128
+ const proxy = new Proxy(target, handler);
6129
+ populateHydrationCache(proxy, row, meta);
6130
+ return proxy;
6131
+ };
6132
+ var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
6133
+ const pkName = findPrimaryKey(table);
6134
+ const pkValue = row[pkName];
6135
+ if (pkValue !== void 0 && pkValue !== null) {
6136
+ const tracked = ctx.getEntity(table, pkValue);
6137
+ if (tracked) return tracked;
6138
+ }
6139
+ const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
6140
+ if (pkValue !== void 0 && pkValue !== null) {
6141
+ ctx.trackManaged(table, pkValue, entity);
6142
+ } else {
6143
+ ctx.trackNew(table, entity);
6144
+ }
6145
+ return entity;
6146
+ };
6147
+
6148
+ // src/orm/execute.ts
6149
+ var flattenResults = (results) => {
6150
+ const rows = [];
6151
+ for (const result of results) {
6152
+ const { columns, values } = result;
6153
+ for (const valueRow of values) {
6154
+ const row = {};
6155
+ columns.forEach((column, idx) => {
6156
+ row[column] = valueRow[idx];
6157
+ });
6158
+ rows.push(row);
6159
+ }
6160
+ }
6161
+ return rows;
6162
+ };
6163
+ var executeWithContexts = async (execCtx, entityCtx, qb) => {
6164
+ const ast = qb.getAST();
6165
+ const compiled = execCtx.dialect.compileSelect(ast);
6166
+ const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
6167
+ const rows = flattenResults(executed);
6168
+ const lazyRelations = qb.getLazyRelations();
6169
+ const lazyRelationOptions = qb.getLazyRelationOptions();
6170
+ if (ast.setOps && ast.setOps.length > 0) {
6171
+ const proxies = rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
6172
+ await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
6173
+ return proxies;
6174
+ }
6175
+ const hydrated = hydrateRows(rows, qb.getHydrationPlan());
6176
+ const entities = hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
6177
+ await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
6178
+ return entities;
6179
+ };
6180
+ async function executeHydrated(session, qb) {
6181
+ return executeWithContexts(session.getExecutionContext(), session, qb);
6182
+ }
6183
+ async function executeHydratedWithContexts(execCtx, hydCtx, qb) {
6184
+ const entityCtx = hydCtx.entityContext;
6185
+ if (!entityCtx) {
6186
+ throw new Error("Hydration context is missing an EntityContext");
6187
+ }
6188
+ return executeWithContexts(execCtx, entityCtx, qb);
6189
+ }
6190
+ var loadLazyRelationsForTable = async (ctx, table, lazyRelations, lazyRelationOptions) => {
6191
+ if (!lazyRelations.length) return;
6192
+ const tracked = ctx.getEntitiesForTable(table);
6193
+ if (!tracked.length) return;
6194
+ const meta = getEntityMeta(tracked[0].entity);
6195
+ if (!meta) return;
6196
+ for (const relationName of lazyRelations) {
6197
+ const relation = table.relations[relationName];
6198
+ if (!relation) continue;
6199
+ const key = relationName;
6200
+ const options = lazyRelationOptions.get(key);
6201
+ if (!options) {
6202
+ continue;
6203
+ }
6204
+ switch (relation.type) {
6205
+ case RelationKinds.HasOne:
6206
+ await relationLoaderCache(
6207
+ meta,
6208
+ key,
6209
+ () => loadHasOneRelation(ctx, table, key, relation, options)
6210
+ );
6211
+ break;
6212
+ case RelationKinds.HasMany:
6213
+ await relationLoaderCache(
6214
+ meta,
6215
+ key,
6216
+ () => loadHasManyRelation(ctx, table, key, relation, options)
6217
+ );
6218
+ break;
6219
+ case RelationKinds.BelongsTo:
6220
+ await relationLoaderCache(
6221
+ meta,
6222
+ key,
6223
+ () => loadBelongsToRelation(ctx, table, key, relation, options)
6224
+ );
6225
+ break;
6226
+ case RelationKinds.BelongsToMany:
6227
+ await relationLoaderCache(
6228
+ meta,
6229
+ key,
6230
+ () => loadBelongsToManyRelation(ctx, table, key, relation, options)
6231
+ );
6232
+ break;
6233
+ }
6234
+ }
6235
+ };
6236
+
6237
+ // src/query-builder/query-resolution.ts
6238
+ function resolveSelectQuery(query) {
6239
+ const candidate = query;
6240
+ return typeof candidate.getAST === "function" && candidate.getAST ? candidate.getAST() : query;
6241
+ }
6242
+
6243
+ // src/query-builder/select/select-operations.ts
6244
+ function applyOrderBy(context, predicateFacet, term, directionOrOptions) {
6245
+ const options = typeof directionOrOptions === "string" ? { direction: directionOrOptions } : directionOrOptions;
6246
+ const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
6247
+ return predicateFacet.orderBy(context, term, dir, options.nulls, options.collation);
6248
+ }
6249
+ async function executeCount(context, env, session) {
6250
+ const unpagedAst = {
6251
+ ...context.state.ast,
6252
+ orderBy: void 0,
6253
+ limit: void 0,
6254
+ offset: void 0
6255
+ };
6256
+ const nextState = new SelectQueryState(env.table, unpagedAst);
6257
+ const nextContext = {
6258
+ ...context,
6259
+ state: nextState
6260
+ };
6261
+ const subAst = nextContext.hydration.applyToAst(nextState.ast);
6262
+ const countQuery = {
6263
+ type: "SelectQuery",
6264
+ from: derivedTable(subAst, "__metal_count"),
6265
+ columns: [{ type: "Function", name: "COUNT", args: [], alias: "total" }],
6266
+ joins: []
6267
+ };
6268
+ const execCtx = session.getExecutionContext();
6269
+ const compiled = execCtx.dialect.compileSelect(countQuery);
6270
+ const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
6271
+ const value = results[0]?.values?.[0]?.[0];
6272
+ if (typeof value === "number") return value;
6273
+ if (typeof value === "bigint") return Number(value);
6274
+ if (typeof value === "string") return Number(value);
6275
+ return value === null || value === void 0 ? 0 : Number(value);
6276
+ }
6277
+ async function executePagedQuery(builder, session, options, countCallback) {
6278
+ const { page, pageSize } = options;
6279
+ if (!Number.isInteger(page) || page < 1) {
6280
+ throw new Error("executePaged: page must be an integer >= 1");
6281
+ }
6282
+ if (!Number.isInteger(pageSize) || pageSize < 1) {
6283
+ throw new Error("executePaged: pageSize must be an integer >= 1");
6284
+ }
6285
+ const offset = (page - 1) * pageSize;
6286
+ const [items, totalItems] = await Promise.all([
6287
+ builder.limit(pageSize).offset(offset).execute(session),
6288
+ countCallback(session)
6289
+ ]);
6290
+ return { items, totalItems };
6291
+ }
6292
+ function buildWhereHasPredicate(env, context, relationFacet, createChildBuilder, relationName, callbackOrOptions, maybeOptions, negate = false) {
6293
+ const relation = env.table.relations[relationName];
6294
+ if (!relation) {
6295
+ throw new Error(`Relation '${relationName}' not found on table '${env.table.name}'`);
6296
+ }
6297
+ const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6298
+ const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6299
+ let subQb = createChildBuilder(relation.target);
6300
+ if (callback) {
6301
+ subQb = callback(subQb);
6302
+ }
6303
+ const subAst = subQb.getAST();
6304
+ const finalSubAst = relationFacet.applyRelationCorrelation(
6305
+ context,
6306
+ relationName,
6307
+ subAst,
6308
+ options?.correlate
6309
+ );
6310
+ return negate ? notExists(finalSubAst) : exists(finalSubAst);
6311
+ }
6312
+
6313
+ // src/query-builder/select/from-facet.ts
6314
+ var SelectFromFacet = class {
6315
+ /**
6316
+ * Creates a new SelectFromFacet instance
6317
+ * @param env - Query builder environment
6318
+ * @param createAstService - Function to create AST service
6319
+ */
6320
+ constructor(env, createAstService) {
6321
+ this.env = env;
6322
+ this.createAstService = createAstService;
6323
+ }
6324
+ /**
6325
+ * Applies an alias to the FROM table
6326
+ * @param context - Current query context
6327
+ * @param alias - Alias to apply
6328
+ * @returns Updated query context with aliased FROM
6329
+ */
6330
+ as(context, alias) {
6331
+ const from = context.state.ast.from;
6332
+ if (from.type !== "Table") {
6333
+ throw new Error("Cannot alias non-table FROM sources");
6334
+ }
6335
+ const nextFrom = { ...from, alias };
6336
+ const astService = this.createAstService(context.state);
6337
+ const nextState = astService.withFrom(nextFrom);
6338
+ return { state: nextState, hydration: context.hydration };
6339
+ }
6340
+ /**
6341
+ * Sets the FROM clause to a subquery
6342
+ * @param context - Current query context
6343
+ * @param subAst - Subquery AST
6344
+ * @param alias - Alias for the subquery
6345
+ * @param columnAliases - Optional column aliases
6346
+ * @returns Updated query context with subquery FROM
6347
+ */
6348
+ fromSubquery(context, subAst, alias, columnAliases) {
6349
+ const fromNode = derivedTable(subAst, alias, columnAliases);
6350
+ const astService = this.createAstService(context.state);
6351
+ const nextState = astService.withFrom(fromNode);
6352
+ return { state: nextState, hydration: context.hydration };
6353
+ }
6354
+ /**
6355
+ * Sets the FROM clause to a function table
6356
+ * @param context - Current query context
6357
+ * @param name - Function name
6358
+ * @param args - Function arguments
6359
+ * @param alias - Optional alias for the function table
6360
+ * @param options - Optional function table options
6361
+ * @returns Updated query context with function table FROM
6362
+ */
6363
+ fromFunctionTable(context, name, args, alias, options) {
6364
+ const functionTable = fnTable(name, args, alias, options);
6365
+ const astService = this.createAstService(context.state);
6366
+ const nextState = astService.withFrom(functionTable);
6367
+ return { state: nextState, hydration: context.hydration };
6368
+ }
6369
+ };
6370
+
6371
+ // src/query-builder/select/join-facet.ts
6372
+ var SelectJoinFacet = class {
6373
+ constructor(env, createAstService) {
6374
+ this.env = env;
6375
+ this.createAstService = createAstService;
6376
+ }
6377
+ applyJoin(context, table, condition, kind) {
6378
+ const joinNode = createJoinNode(kind, { type: "Table", name: table.name, schema: table.schema }, condition);
6379
+ const astService = this.createAstService(context.state);
6380
+ const nextState = astService.withJoin(joinNode);
6381
+ return { state: nextState, hydration: context.hydration };
6382
+ }
6383
+ joinSubquery(context, subAst, alias, condition, joinKind, columnAliases) {
6384
+ const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
6385
+ const astService = this.createAstService(context.state);
6386
+ const nextState = astService.withJoin(joinNode);
6387
+ return { state: nextState, hydration: context.hydration };
6388
+ }
6389
+ joinFunctionTable(context, name, args, alias, condition, joinKind, options) {
6390
+ const functionTable = fnTable(name, args, alias, options);
6391
+ const joinNode = createJoinNode(joinKind, functionTable, condition);
6392
+ const astService = this.createAstService(context.state);
6393
+ const nextState = astService.withJoin(joinNode);
6394
+ return { state: nextState, hydration: context.hydration };
6395
+ }
6396
+ };
6397
+
6398
+ // src/query-builder/select/projection-facet.ts
6399
+ var SelectProjectionFacet = class {
6400
+ /**
6401
+ * Creates a new SelectProjectionFacet instance
6402
+ * @param columnSelector - Column selector dependency
6403
+ */
6404
+ constructor(columnSelector) {
6405
+ this.columnSelector = columnSelector;
6406
+ }
6407
+ /**
6408
+ * Selects columns for the query
6409
+ * @param context - Current query context
6410
+ * @param columns - Columns to select
6411
+ * @returns Updated query context with selected columns
6412
+ */
6413
+ select(context, columns) {
6414
+ return this.columnSelector.select(context, columns);
6415
+ }
6416
+ /**
6417
+ * Selects raw column expressions
6418
+ * @param context - Current query context
6419
+ * @param cols - Raw column expressions
6420
+ * @returns Updated query context with raw column selections
6421
+ */
6422
+ selectRaw(context, cols) {
6423
+ return this.columnSelector.selectRaw(context, cols);
6424
+ }
6425
+ /**
6426
+ * Selects a subquery as a column
6427
+ * @param context - Current query context
6428
+ * @param alias - Alias for the subquery
6429
+ * @param query - Subquery to select
6430
+ * @returns Updated query context with subquery selection
6431
+ */
6432
+ selectSubquery(context, alias, query) {
6433
+ return this.columnSelector.selectSubquery(context, alias, query);
6434
+ }
6435
+ /**
6436
+ * Adds DISTINCT clause to the query
6437
+ * @param context - Current query context
6438
+ * @param cols - Columns to make distinct
6439
+ * @returns Updated query context with DISTINCT clause
6440
+ */
6441
+ distinct(context, cols) {
6442
+ return this.columnSelector.distinct(context, cols);
6443
+ }
6444
+ };
6445
+
6446
+ // src/query-builder/select/predicate-facet.ts
6447
+ var SelectPredicateFacet = class {
6448
+ /**
6449
+ * Creates a new SelectPredicateFacet instance
6450
+ * @param env - Query builder environment
6451
+ * @param createAstService - Function to create AST service
6452
+ */
6453
+ constructor(env, createAstService) {
6454
+ this.env = env;
6455
+ this.createAstService = createAstService;
6456
+ }
6457
+ /**
6458
+ * Adds a WHERE condition to the query
6459
+ * @param context - Current query context
6460
+ * @param expr - WHERE expression
6461
+ * @returns Updated query context with WHERE condition
6462
+ */
6463
+ where(context, expr) {
6464
+ const astService = this.createAstService(context.state);
6465
+ const nextState = astService.withWhere(expr);
6466
+ return { state: nextState, hydration: context.hydration };
6467
+ }
6468
+ /**
6469
+ * Adds a GROUP BY clause to the query
6470
+ * @param context - Current query context
6471
+ * @param term - Column or ordering term to group by
6472
+ * @returns Updated query context with GROUP BY clause
6473
+ */
6474
+ groupBy(context, term) {
6475
+ const astService = this.createAstService(context.state);
6476
+ const nextState = astService.withGroupBy(term);
6477
+ return { state: nextState, hydration: context.hydration };
6478
+ }
6479
+ /**
6480
+ * Adds a HAVING condition to the query
6481
+ * @param context - Current query context
6482
+ * @param expr - HAVING expression
6483
+ * @returns Updated query context with HAVING condition
6484
+ */
6485
+ having(context, expr) {
6486
+ const astService = this.createAstService(context.state);
6487
+ const nextState = astService.withHaving(expr);
6488
+ return { state: nextState, hydration: context.hydration };
6489
+ }
6490
+ /**
6491
+ * Adds an ORDER BY clause to the query
6492
+ * @param context - Current query context
6493
+ * @param term - Column or ordering term to order by
6494
+ * @param direction - Order direction
6495
+ * @param nulls - Nulls ordering
6496
+ * @param collation - Collation
6497
+ * @returns Updated query context with ORDER BY clause
6498
+ */
6499
+ orderBy(context, term, direction, nulls, collation) {
6500
+ const astService = this.createAstService(context.state);
6501
+ const nextState = astService.withOrderBy(term, direction, nulls, collation);
6502
+ return { state: nextState, hydration: context.hydration };
6503
+ }
6504
+ /**
6505
+ * Adds a LIMIT clause to the query
6506
+ * @param context - Current query context
6507
+ * @param n - Maximum number of rows
6508
+ * @returns Updated query context with LIMIT clause
6509
+ */
6510
+ limit(context, n) {
6511
+ const astService = this.createAstService(context.state);
6512
+ const nextState = astService.withLimit(n);
6513
+ return { state: nextState, hydration: context.hydration };
6514
+ }
6515
+ /**
6516
+ * Adds an OFFSET clause to the query
6517
+ * @param context - Current query context
6518
+ * @param n - Number of rows to skip
6519
+ * @returns Updated query context with OFFSET clause
6520
+ */
6521
+ offset(context, n) {
6522
+ const astService = this.createAstService(context.state);
6523
+ const nextState = astService.withOffset(n);
6524
+ return { state: nextState, hydration: context.hydration };
6525
+ }
6526
+ };
6527
+
6528
+ // src/query-builder/select/cte-facet.ts
6529
+ var SelectCTEFacet = class {
6530
+ /**
6531
+ * Creates a new SelectCTEFacet instance
6532
+ * @param env - Query builder environment
6533
+ * @param createAstService - Function to create AST service
6534
+ */
6535
+ constructor(env, createAstService) {
6536
+ this.env = env;
6537
+ this.createAstService = createAstService;
5581
6538
  }
5582
- return rows;
5583
- };
5584
- var executeWithContexts = async (execCtx, entityCtx, qb) => {
5585
- const ast = qb.getAST();
5586
- const compiled = execCtx.dialect.compileSelect(ast);
5587
- const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
5588
- const rows = flattenResults(executed);
5589
- if (ast.setOps && ast.setOps.length > 0) {
5590
- return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
6539
+ /**
6540
+ * Adds a Common Table Expression to the query
6541
+ * @param context - Current query context
6542
+ * @param name - CTE name
6543
+ * @param subAst - CTE query AST
6544
+ * @param columns - Optional column names
6545
+ * @param recursive - Whether the CTE is recursive
6546
+ * @returns Updated query context with CTE
6547
+ */
6548
+ withCTE(context, name, subAst, columns, recursive) {
6549
+ const astService = this.createAstService(context.state);
6550
+ const nextState = astService.withCte(name, subAst, columns, recursive);
6551
+ return { state: nextState, hydration: context.hydration };
5591
6552
  }
5592
- const hydrated = hydrateRows(rows, qb.getHydrationPlan());
5593
- return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
5594
6553
  };
5595
- async function executeHydrated(session, qb) {
5596
- return executeWithContexts(session.getExecutionContext(), session, qb);
5597
- }
5598
- async function executeHydratedWithContexts(execCtx, hydCtx, qb) {
5599
- const entityCtx = hydCtx.entityContext;
5600
- if (!entityCtx) {
5601
- throw new Error("Hydration context is missing an EntityContext");
6554
+
6555
+ // src/query-builder/select/setop-facet.ts
6556
+ var SelectSetOpFacet = class {
6557
+ /**
6558
+ * Creates a new SelectSetOpFacet instance
6559
+ * @param env - Query builder environment
6560
+ * @param createAstService - Function to create AST service
6561
+ */
6562
+ constructor(env, createAstService) {
6563
+ this.env = env;
6564
+ this.createAstService = createAstService;
5602
6565
  }
5603
- return executeWithContexts(execCtx, entityCtx, qb);
5604
- }
6566
+ /**
6567
+ * Applies a set operation to the query
6568
+ * @param context - Current query context
6569
+ * @param operator - Set operation kind
6570
+ * @param subAst - Subquery AST to combine
6571
+ * @returns Updated query context with set operation
6572
+ */
6573
+ applySetOperation(context, operator, subAst) {
6574
+ const astService = this.createAstService(context.state);
6575
+ const nextState = astService.withSetOperation(operator, subAst);
6576
+ return { state: nextState, hydration: context.hydration };
6577
+ }
6578
+ };
5605
6579
 
5606
- // src/query-builder/query-resolution.ts
5607
- function resolveSelectQuery(query) {
5608
- const candidate = query;
5609
- return typeof candidate.getAST === "function" && candidate.getAST ? candidate.getAST() : query;
5610
- }
6580
+ // src/query-builder/select/relation-facet.ts
6581
+ var SelectRelationFacet = class {
6582
+ /**
6583
+ * Creates a new SelectRelationFacet instance
6584
+ * @param relationManager - Relation manager dependency
6585
+ */
6586
+ constructor(relationManager) {
6587
+ this.relationManager = relationManager;
6588
+ }
6589
+ /**
6590
+ * Matches records based on a relationship
6591
+ * @param context - Current query context
6592
+ * @param relationName - Name of the relationship
6593
+ * @param predicate - Optional predicate
6594
+ * @returns Updated query context with relation match
6595
+ */
6596
+ match(context, relationName, predicate) {
6597
+ return this.relationManager.match(context, relationName, predicate);
6598
+ }
6599
+ /**
6600
+ * Joins a related table
6601
+ * @param context - Current query context
6602
+ * @param relationName - Name of the relationship
6603
+ * @param joinKind - Type of join
6604
+ * @param extraCondition - Optional additional condition
6605
+ * @returns Updated query context with relation join
6606
+ */
6607
+ joinRelation(context, relationName, joinKind, extraCondition) {
6608
+ return this.relationManager.joinRelation(context, relationName, joinKind, extraCondition);
6609
+ }
6610
+ /**
6611
+ * Includes related data in the query results
6612
+ * @param context - Current query context
6613
+ * @param relationName - Name of the relationship to include
6614
+ * @param options - Optional include options
6615
+ * @returns Updated query context with relation inclusion
6616
+ */
6617
+ include(context, relationName, options) {
6618
+ return this.relationManager.include(context, relationName, options);
6619
+ }
6620
+ /**
6621
+ * Applies correlation for relation-based subqueries
6622
+ * @param context - Current query context
6623
+ * @param relationName - Name of the relationship
6624
+ * @param subAst - Subquery AST
6625
+ * @param extraCorrelate - Optional additional correlation
6626
+ * @returns Modified subquery AST with correlation
6627
+ */
6628
+ applyRelationCorrelation(context, relationName, subAst, extraCorrelate) {
6629
+ return this.relationManager.applyRelationCorrelation(context, relationName, subAst, extraCorrelate);
6630
+ }
6631
+ };
5611
6632
 
5612
6633
  // src/query-builder/select.ts
5613
6634
  var SelectQueryBuilder = class _SelectQueryBuilder {
6635
+ env;
6636
+ context;
6637
+ columnSelector;
6638
+ fromFacet;
6639
+ joinFacet;
6640
+ projectionFacet;
6641
+ predicateFacet;
6642
+ cteFacet;
6643
+ setOpFacet;
6644
+ relationFacet;
6645
+ lazyRelations;
6646
+ lazyRelationOptions;
5614
6647
  /**
5615
6648
  * Creates a new SelectQueryBuilder instance
5616
6649
  * @param table - Table definition to query
@@ -5618,9 +6651,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5618
6651
  * @param hydration - Optional hydration manager
5619
6652
  * @param dependencies - Optional query builder dependencies
5620
6653
  */
5621
- constructor(table, state, hydration, dependencies, lazyRelations) {
6654
+ constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions) {
5622
6655
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
5623
6656
  this.env = { table, deps };
6657
+ const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
5624
6658
  const initialState = state ?? deps.createState(table);
5625
6659
  const initialHydration = hydration ?? deps.createHydration(table);
5626
6660
  this.context = {
@@ -5628,8 +6662,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5628
6662
  hydration: initialHydration
5629
6663
  };
5630
6664
  this.lazyRelations = new Set(lazyRelations ?? []);
6665
+ this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
5631
6666
  this.columnSelector = deps.createColumnSelector(this.env);
5632
- this.relationManager = deps.createRelationManager(this.env);
6667
+ const relationManager = deps.createRelationManager(this.env);
6668
+ this.fromFacet = new SelectFromFacet(this.env, createAstService);
6669
+ this.joinFacet = new SelectJoinFacet(this.env, createAstService);
6670
+ this.projectionFacet = new SelectProjectionFacet(this.columnSelector);
6671
+ this.predicateFacet = new SelectPredicateFacet(this.env, createAstService);
6672
+ this.cteFacet = new SelectCTEFacet(this.env, createAstService);
6673
+ this.setOpFacet = new SelectSetOpFacet(this.env, createAstService);
6674
+ this.relationFacet = new SelectRelationFacet(relationManager);
5633
6675
  }
5634
6676
  /**
5635
6677
  * Creates a new SelectQueryBuilder instance with updated context and lazy relations
@@ -5637,20 +6679,22 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5637
6679
  * @param lazyRelations - Updated lazy relations set
5638
6680
  * @returns New SelectQueryBuilder instance
5639
6681
  */
5640
- clone(context = this.context, lazyRelations = new Set(this.lazyRelations)) {
5641
- return new _SelectQueryBuilder(this.env.table, context.state, context.hydration, this.env.deps, lazyRelations);
6682
+ clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions)) {
6683
+ return new _SelectQueryBuilder(
6684
+ this.env.table,
6685
+ context.state,
6686
+ context.hydration,
6687
+ this.env.deps,
6688
+ lazyRelations,
6689
+ lazyRelationOptions
6690
+ );
5642
6691
  }
5643
6692
  /**
5644
6693
  * Applies an alias to the root FROM table.
5645
6694
  * @param alias - Alias to apply
5646
6695
  */
5647
6696
  as(alias) {
5648
- const from = this.context.state.ast.from;
5649
- if (from.type !== "Table") {
5650
- throw new Error("Cannot alias non-table FROM sources");
5651
- }
5652
- const nextFrom = { ...from, alias };
5653
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(nextFrom));
6697
+ const nextContext = this.fromFacet.as(this.context, alias);
5654
6698
  return this.clone(nextContext);
5655
6699
  }
5656
6700
  /**
@@ -5675,29 +6719,6 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5675
6719
  createChildBuilder(table) {
5676
6720
  return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
5677
6721
  }
5678
- /**
5679
- * Applies an AST mutation using the query AST service
5680
- * @param context - Current query context
5681
- * @param mutator - Function that mutates the AST
5682
- * @returns Updated query context
5683
- */
5684
- applyAst(context, mutator) {
5685
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
5686
- const nextState = mutator(astService);
5687
- return { state: nextState, hydration: context.hydration };
5688
- }
5689
- /**
5690
- * Applies a join to the query context
5691
- * @param context - Current query context
5692
- * @param table - Table to join
5693
- * @param condition - Join condition
5694
- * @param kind - Join kind
5695
- * @returns Updated query context with join applied
5696
- */
5697
- applyJoin(context, table, condition, kind) {
5698
- const joinNode = createJoinNode(kind, { type: "Table", name: table.name, schema: table.schema }, condition);
5699
- return this.applyAst(context, (service) => service.withJoin(joinNode));
5700
- }
5701
6722
  /**
5702
6723
  * Applies a set operation to the query
5703
6724
  * @param operator - Set operation kind
@@ -5706,12 +6727,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5706
6727
  */
5707
6728
  applySetOperation(operator, query) {
5708
6729
  const subAst = resolveSelectQuery(query);
5709
- return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
6730
+ return this.setOpFacet.applySetOperation(this.context, operator, subAst);
5710
6731
  }
5711
6732
  select(...args) {
5712
6733
  if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && typeof args[0] !== "string") {
5713
6734
  const columns = args[0];
5714
- return this.clone(this.columnSelector.select(this.context, columns));
6735
+ return this.clone(this.projectionFacet.select(this.context, columns));
5715
6736
  }
5716
6737
  const cols = args;
5717
6738
  const selection = {};
@@ -5722,7 +6743,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5722
6743
  }
5723
6744
  selection[key] = col2;
5724
6745
  }
5725
- return this.clone(this.columnSelector.select(this.context, selection));
6746
+ return this.clone(this.projectionFacet.select(this.context, selection));
5726
6747
  }
5727
6748
  /**
5728
6749
  * Selects raw column expressions
@@ -5730,7 +6751,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5730
6751
  * @returns New query builder instance with raw column selections
5731
6752
  */
5732
6753
  selectRaw(...cols) {
5733
- return this.clone(this.columnSelector.selectRaw(this.context, cols));
6754
+ return this.clone(this.projectionFacet.selectRaw(this.context, cols));
5734
6755
  }
5735
6756
  /**
5736
6757
  * Adds a Common Table Expression (CTE) to the query
@@ -5741,7 +6762,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5741
6762
  */
5742
6763
  with(name, query, columns) {
5743
6764
  const subAst = resolveSelectQuery(query);
5744
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
6765
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, false);
5745
6766
  return this.clone(nextContext);
5746
6767
  }
5747
6768
  /**
@@ -5753,7 +6774,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5753
6774
  */
5754
6775
  withRecursive(name, query, columns) {
5755
6776
  const subAst = resolveSelectQuery(query);
5756
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
6777
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, true);
5757
6778
  return this.clone(nextContext);
5758
6779
  }
5759
6780
  /**
@@ -5765,8 +6786,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5765
6786
  */
5766
6787
  fromSubquery(subquery, alias, columnAliases) {
5767
6788
  const subAst = resolveSelectQuery(subquery);
5768
- const fromNode = derivedTable(subAst, alias, columnAliases);
5769
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(fromNode));
6789
+ const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
5770
6790
  return this.clone(nextContext);
5771
6791
  }
5772
6792
  /**
@@ -5777,8 +6797,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5777
6797
  * @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
5778
6798
  */
5779
6799
  fromFunctionTable(name, args = [], alias, options) {
5780
- const functionTable = fnTable(name, args, alias, options);
5781
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(functionTable));
6800
+ const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
5782
6801
  return this.clone(nextContext);
5783
6802
  }
5784
6803
  /**
@@ -5789,7 +6808,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5789
6808
  */
5790
6809
  selectSubquery(alias, sub2) {
5791
6810
  const query = resolveSelectQuery(sub2);
5792
- return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
6811
+ return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
5793
6812
  }
5794
6813
  /**
5795
6814
  * Adds a JOIN against a derived table (subquery with alias)
@@ -5802,8 +6821,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5802
6821
  */
5803
6822
  joinSubquery(subquery, alias, condition, joinKind = JOIN_KINDS.INNER, columnAliases) {
5804
6823
  const subAst = resolveSelectQuery(subquery);
5805
- const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
5806
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
6824
+ const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
5807
6825
  return this.clone(nextContext);
5808
6826
  }
5809
6827
  /**
@@ -5816,9 +6834,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5816
6834
  * @param options - Optional metadata (lateral, ordinality, column aliases, schema)
5817
6835
  */
5818
6836
  joinFunctionTable(name, args = [], alias, condition, joinKind = JOIN_KINDS.INNER, options) {
5819
- const functionTable = fnTable(name, args, alias, options);
5820
- const joinNode = createJoinNode(joinKind, functionTable, condition);
5821
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
6837
+ const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
5822
6838
  return this.clone(nextContext);
5823
6839
  }
5824
6840
  /**
@@ -5828,7 +6844,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5828
6844
  * @returns New query builder instance with the INNER JOIN
5829
6845
  */
5830
6846
  innerJoin(table, condition) {
5831
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
6847
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
5832
6848
  return this.clone(nextContext);
5833
6849
  }
5834
6850
  /**
@@ -5838,7 +6854,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5838
6854
  * @returns New query builder instance with the LEFT JOIN
5839
6855
  */
5840
6856
  leftJoin(table, condition) {
5841
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
6857
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
5842
6858
  return this.clone(nextContext);
5843
6859
  }
5844
6860
  /**
@@ -5848,7 +6864,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5848
6864
  * @returns New query builder instance with the RIGHT JOIN
5849
6865
  */
5850
6866
  rightJoin(table, condition) {
5851
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
6867
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
5852
6868
  return this.clone(nextContext);
5853
6869
  }
5854
6870
  /**
@@ -5858,7 +6874,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5858
6874
  * @returns New query builder instance with the relationship match
5859
6875
  */
5860
6876
  match(relationName, predicate) {
5861
- const nextContext = this.relationManager.match(this.context, relationName, predicate);
6877
+ const nextContext = this.relationFacet.match(this.context, relationName, predicate);
5862
6878
  return this.clone(nextContext);
5863
6879
  }
5864
6880
  /**
@@ -5869,7 +6885,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5869
6885
  * @returns New query builder instance with the relationship join
5870
6886
  */
5871
6887
  joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
5872
- const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
6888
+ const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
5873
6889
  return this.clone(nextContext);
5874
6890
  }
5875
6891
  /**
@@ -5879,42 +6895,47 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5879
6895
  * @returns New query builder instance with the relationship inclusion
5880
6896
  */
5881
6897
  include(relationName, options) {
5882
- const nextContext = this.relationManager.include(this.context, relationName, options);
6898
+ const nextContext = this.relationFacet.include(this.context, relationName, options);
5883
6899
  return this.clone(nextContext);
5884
6900
  }
5885
6901
  /**
5886
6902
  * Includes a relation lazily in the query results
5887
6903
  * @param relationName - Name of the relation to include lazily
6904
+ * @param options - Optional include options for lazy loading
5888
6905
  * @returns New query builder instance with lazy relation inclusion
5889
6906
  */
5890
- includeLazy(relationName) {
5891
- const nextLazy = new Set(this.lazyRelations);
5892
- nextLazy.add(relationName);
5893
- return this.clone(this.context, nextLazy);
5894
- }
5895
- /**
5896
- * Selects columns for a related table in a single hop.
5897
- */
5898
- selectRelationColumns(relationName, ...cols) {
6907
+ includeLazy(relationName, options) {
6908
+ let nextContext = this.context;
5899
6909
  const relation = this.env.table.relations[relationName];
5900
- if (!relation) {
5901
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
5902
- }
5903
- const target = relation.target;
5904
- for (const col2 of cols) {
5905
- if (!target.columns[col2]) {
5906
- throw new Error(
5907
- `Column '${col2}' not found on related table '${target.name}' for relation '${relationName}'`
5908
- );
6910
+ if (relation?.type === RelationKinds.BelongsTo) {
6911
+ const foreignKey = relation.foreignKey;
6912
+ const fkColumn = this.env.table.columns[foreignKey];
6913
+ if (fkColumn) {
6914
+ const hasAlias2 = nextContext.state.ast.columns.some((col2) => {
6915
+ const node = col2;
6916
+ return (node.alias ?? node.name) === foreignKey;
6917
+ });
6918
+ if (!hasAlias2) {
6919
+ nextContext = this.columnSelector.select(nextContext, { [foreignKey]: fkColumn });
6920
+ }
5909
6921
  }
5910
6922
  }
5911
- return this.include(relationName, { columns: cols });
6923
+ const nextLazy = new Set(this.lazyRelations);
6924
+ nextLazy.add(relationName);
6925
+ const nextOptions = new Map(this.lazyRelationOptions);
6926
+ if (options) {
6927
+ nextOptions.set(relationName, options);
6928
+ } else {
6929
+ nextOptions.delete(relationName);
6930
+ }
6931
+ return this.clone(nextContext, nextLazy, nextOptions);
5912
6932
  }
5913
6933
  /**
5914
- * Convenience alias for selecting specific columns from a relation.
6934
+ * Convenience alias for including only specific columns from a relation.
5915
6935
  */
5916
6936
  includePick(relationName, cols) {
5917
- return this.selectRelationColumns(relationName, ...cols);
6937
+ const options = { columns: cols };
6938
+ return this.include(relationName, options);
5918
6939
  }
5919
6940
  /**
5920
6941
  * Selects columns for the root table and relations from an array of entries
@@ -5927,7 +6948,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5927
6948
  if (entry.type === "root") {
5928
6949
  currBuilder = currBuilder.select(...entry.columns);
5929
6950
  } else {
5930
- currBuilder = currBuilder.selectRelationColumns(entry.relationName, ...entry.columns);
6951
+ const options = { columns: entry.columns };
6952
+ currBuilder = currBuilder.include(entry.relationName, options);
5931
6953
  }
5932
6954
  }
5933
6955
  return currBuilder;
@@ -5939,6 +6961,13 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5939
6961
  getLazyRelations() {
5940
6962
  return Array.from(this.lazyRelations);
5941
6963
  }
6964
+ /**
6965
+ * Gets lazy relation include options
6966
+ * @returns Map of relation names to include options
6967
+ */
6968
+ getLazyRelationOptions() {
6969
+ return new Map(this.lazyRelationOptions);
6970
+ }
5942
6971
  /**
5943
6972
  * Gets the table definition for this query builder
5944
6973
  * @returns Table definition
@@ -5954,51 +6983,23 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5954
6983
  async execute(ctx) {
5955
6984
  return executeHydrated(ctx, this);
5956
6985
  }
5957
- withAst(ast) {
5958
- const nextState = new SelectQueryState(this.env.table, ast);
5959
- const nextContext = {
5960
- ...this.context,
5961
- state: nextState
5962
- };
5963
- return this.clone(nextContext);
5964
- }
6986
+ /**
6987
+ * Executes a count query for the current builder without LIMIT/OFFSET clauses.
6988
+ *
6989
+ * @example
6990
+ * const total = await qb.count(session);
6991
+ */
5965
6992
  async count(session) {
5966
- const unpagedAst = {
5967
- ...this.context.state.ast,
5968
- orderBy: void 0,
5969
- limit: void 0,
5970
- offset: void 0
5971
- };
5972
- const subAst = this.withAst(unpagedAst).getAST();
5973
- const countQuery = {
5974
- type: "SelectQuery",
5975
- from: derivedTable(subAst, "__metal_count"),
5976
- columns: [{ type: "Function", name: "COUNT", args: [], alias: "total" }],
5977
- joins: []
5978
- };
5979
- const execCtx = session.getExecutionContext();
5980
- const compiled = execCtx.dialect.compileSelect(countQuery);
5981
- const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
5982
- const value = results[0]?.values?.[0]?.[0];
5983
- if (typeof value === "number") return value;
5984
- if (typeof value === "bigint") return Number(value);
5985
- if (typeof value === "string") return Number(value);
5986
- return value === null || value === void 0 ? 0 : Number(value);
6993
+ return executeCount(this.context, this.env, session);
5987
6994
  }
6995
+ /**
6996
+ * Executes the query and returns both the paged items and the total.
6997
+ *
6998
+ * @example
6999
+ * const { items, totalItems } = await qb.executePaged(session, { page: 1, pageSize: 20 });
7000
+ */
5988
7001
  async executePaged(session, options) {
5989
- const { page, pageSize } = options;
5990
- if (!Number.isInteger(page) || page < 1) {
5991
- throw new Error("executePaged: page must be an integer >= 1");
5992
- }
5993
- if (!Number.isInteger(pageSize) || pageSize < 1) {
5994
- throw new Error("executePaged: pageSize must be an integer >= 1");
5995
- }
5996
- const offset = (page - 1) * pageSize;
5997
- const [items, totalItems] = await Promise.all([
5998
- this.limit(pageSize).offset(offset).execute(session),
5999
- this.count(session)
6000
- ]);
6001
- return { items, totalItems };
7002
+ return executePagedQuery(this, session, options, (sess) => this.count(sess));
6002
7003
  }
6003
7004
  /**
6004
7005
  * Executes the query with provided execution and hydration contexts
@@ -6015,7 +7016,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6015
7016
  * @returns New query builder instance with the WHERE condition
6016
7017
  */
6017
7018
  where(expr) {
6018
- const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
7019
+ const nextContext = this.predicateFacet.where(this.context, expr);
6019
7020
  return this.clone(nextContext);
6020
7021
  }
6021
7022
  /**
@@ -6024,7 +7025,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6024
7025
  * @returns New query builder instance with the GROUP BY clause
6025
7026
  */
6026
7027
  groupBy(term) {
6027
- const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(term));
7028
+ const nextContext = this.predicateFacet.groupBy(this.context, term);
6028
7029
  return this.clone(nextContext);
6029
7030
  }
6030
7031
  /**
@@ -6033,7 +7034,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6033
7034
  * @returns New query builder instance with the HAVING condition
6034
7035
  */
6035
7036
  having(expr) {
6036
- const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
7037
+ const nextContext = this.predicateFacet.having(this.context, expr);
6037
7038
  return this.clone(nextContext);
6038
7039
  }
6039
7040
  /**
@@ -6041,14 +7042,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6041
7042
  * @param term - Column definition or ordering term to order by
6042
7043
  * @param directionOrOptions - Order direction or options (defaults to ASC)
6043
7044
  * @returns New query builder instance with the ORDER BY clause
7045
+ *
7046
+ * @example
7047
+ * qb.orderBy(userTable.columns.createdAt, 'DESC');
6044
7048
  */
6045
7049
  orderBy(term, directionOrOptions = ORDER_DIRECTIONS.ASC) {
6046
- const options = typeof directionOrOptions === "string" ? { direction: directionOrOptions } : directionOrOptions;
6047
- const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
6048
- const nextContext = this.applyAst(
6049
- this.context,
6050
- (service) => service.withOrderBy(term, dir, options.nulls, options.collation)
6051
- );
7050
+ const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
6052
7051
  return this.clone(nextContext);
6053
7052
  }
6054
7053
  /**
@@ -6057,7 +7056,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6057
7056
  * @returns New query builder instance with the DISTINCT clause
6058
7057
  */
6059
7058
  distinct(...cols) {
6060
- return this.clone(this.columnSelector.distinct(this.context, cols));
7059
+ return this.clone(this.projectionFacet.distinct(this.context, cols));
6061
7060
  }
6062
7061
  /**
6063
7062
  * Adds a LIMIT clause to the query
@@ -6065,7 +7064,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6065
7064
  * @returns New query builder instance with the LIMIT clause
6066
7065
  */
6067
7066
  limit(n) {
6068
- const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
7067
+ const nextContext = this.predicateFacet.limit(this.context, n);
6069
7068
  return this.clone(nextContext);
6070
7069
  }
6071
7070
  /**
@@ -6074,7 +7073,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6074
7073
  * @returns New query builder instance with the OFFSET clause
6075
7074
  */
6076
7075
  offset(n) {
6077
- const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
7076
+ const nextContext = this.predicateFacet.offset(this.context, n);
6078
7077
  return this.clone(nextContext);
6079
7078
  }
6080
7079
  /**
@@ -6134,42 +7133,44 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6134
7133
  * @param relationName - Name of the relationship to check
6135
7134
  * @param callback - Optional callback to modify the relationship query
6136
7135
  * @returns New query builder instance with the relationship existence check
7136
+ *
7137
+ * @example
7138
+ * qb.whereHas('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
6137
7139
  */
6138
7140
  whereHas(relationName, callbackOrOptions, maybeOptions) {
6139
- const relation = this.env.table.relations[relationName];
6140
- if (!relation) {
6141
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
6142
- }
6143
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6144
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6145
- let subQb = this.createChildBuilder(relation.target);
6146
- if (callback) {
6147
- subQb = callback(subQb);
6148
- }
6149
- const subAst = subQb.getAST();
6150
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
6151
- return this.where(exists(finalSubAst));
7141
+ const predicate = buildWhereHasPredicate(
7142
+ this.env,
7143
+ this.context,
7144
+ this.relationFacet,
7145
+ (table) => this.createChildBuilder(table),
7146
+ relationName,
7147
+ callbackOrOptions,
7148
+ maybeOptions,
7149
+ false
7150
+ );
7151
+ return this.where(predicate);
6152
7152
  }
6153
7153
  /**
6154
7154
  * Adds a WHERE NOT EXISTS condition based on a relationship
6155
7155
  * @param relationName - Name of the relationship to check
6156
7156
  * @param callback - Optional callback to modify the relationship query
6157
7157
  * @returns New query builder instance with the relationship non-existence check
7158
+ *
7159
+ * @example
7160
+ * qb.whereHasNot('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
6158
7161
  */
6159
7162
  whereHasNot(relationName, callbackOrOptions, maybeOptions) {
6160
- const relation = this.env.table.relations[relationName];
6161
- if (!relation) {
6162
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
6163
- }
6164
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6165
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6166
- let subQb = this.createChildBuilder(relation.target);
6167
- if (callback) {
6168
- subQb = callback(subQb);
6169
- }
6170
- const subAst = subQb.getAST();
6171
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
6172
- return this.where(notExists(finalSubAst));
7163
+ const predicate = buildWhereHasPredicate(
7164
+ this.env,
7165
+ this.context,
7166
+ this.relationFacet,
7167
+ (table) => this.createChildBuilder(table),
7168
+ relationName,
7169
+ callbackOrOptions,
7170
+ maybeOptions,
7171
+ true
7172
+ );
7173
+ return this.where(predicate);
6173
7174
  }
6174
7175
  /**
6175
7176
  * Compiles the query to SQL for a specific dialect
@@ -6301,23 +7302,44 @@ var resolveTableTarget = (target, tableMap) => {
6301
7302
  }
6302
7303
  return table;
6303
7304
  };
7305
+ var toSnakeCase = (value) => {
7306
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
7307
+ };
7308
+ var normalizeEntityName = (value) => {
7309
+ const stripped = value.replace(/Entity$/i, "");
7310
+ const normalized = toSnakeCase(stripped || value);
7311
+ return normalized || "unknown";
7312
+ };
7313
+ var getPivotKeyBaseFromTarget = (target) => {
7314
+ const resolved = unwrapTarget(target);
7315
+ if (isTableDef(resolved)) {
7316
+ return toSnakeCase(resolved.name || "unknown");
7317
+ }
7318
+ const ctor = resolved;
7319
+ return normalizeEntityName(ctor.name || "unknown");
7320
+ };
7321
+ var getPivotKeyBaseFromRoot = (meta) => {
7322
+ return normalizeEntityName(meta.target.name || meta.tableName || "unknown");
7323
+ };
6304
7324
  var buildRelationDefinitions = (meta, tableMap) => {
6305
7325
  const relations = {};
6306
7326
  for (const [name, relation] of Object.entries(meta.relations)) {
6307
7327
  switch (relation.kind) {
6308
7328
  case RelationKinds.HasOne: {
7329
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6309
7330
  relations[name] = hasOne(
6310
7331
  resolveTableTarget(relation.target, tableMap),
6311
- relation.foreignKey,
7332
+ foreignKey,
6312
7333
  relation.localKey,
6313
7334
  relation.cascade
6314
7335
  );
6315
7336
  break;
6316
7337
  }
6317
7338
  case RelationKinds.HasMany: {
7339
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6318
7340
  relations[name] = hasMany(
6319
7341
  resolveTableTarget(relation.target, tableMap),
6320
- relation.foreignKey,
7342
+ foreignKey,
6321
7343
  relation.localKey,
6322
7344
  relation.cascade
6323
7345
  );
@@ -6333,12 +7355,14 @@ var buildRelationDefinitions = (meta, tableMap) => {
6333
7355
  break;
6334
7356
  }
6335
7357
  case RelationKinds.BelongsToMany: {
7358
+ const pivotForeignKeyToRoot = relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
7359
+ const pivotForeignKeyToTarget = relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
6336
7360
  relations[name] = belongsToMany(
6337
7361
  resolveTableTarget(relation.target, tableMap),
6338
7362
  resolveTableTarget(relation.pivotTable, tableMap),
6339
7363
  {
6340
- pivotForeignKeyToRoot: relation.pivotForeignKeyToRoot,
6341
- pivotForeignKeyToTarget: relation.pivotForeignKeyToTarget,
7364
+ pivotForeignKeyToRoot,
7365
+ pivotForeignKeyToTarget,
6342
7366
  localKey: relation.localKey,
6343
7367
  targetKey: relation.targetKey,
6344
7368
  pivotPrimaryKey: relation.pivotPrimaryKey,
@@ -6419,6 +7443,8 @@ function esel(entity, ...props) {
6419
7443
 
6420
7444
  // src/query-builder/insert-query-state.ts
6421
7445
  var InsertQueryState = class _InsertQueryState {
7446
+ table;
7447
+ ast;
6422
7448
  /**
6423
7449
  * Creates a new InsertQueryState instance
6424
7450
  * @param table - The table definition for the INSERT query
@@ -6539,6 +7565,8 @@ var InsertQueryState = class _InsertQueryState {
6539
7565
 
6540
7566
  // src/query-builder/insert.ts
6541
7567
  var InsertQueryBuilder = class _InsertQueryBuilder {
7568
+ table;
7569
+ state;
6542
7570
  /**
6543
7571
  * Creates a new InsertQueryBuilder instance
6544
7572
  * @param table - The table definition for the INSERT query
@@ -6638,6 +7666,8 @@ var isUpdateValue = (value) => {
6638
7666
  }
6639
7667
  };
6640
7668
  var UpdateQueryState = class _UpdateQueryState {
7669
+ table;
7670
+ ast;
6641
7671
  /**
6642
7672
  * Creates a new UpdateQueryState instance
6643
7673
  * @param table - Table definition for the update
@@ -6748,6 +7778,8 @@ var UpdateQueryState = class _UpdateQueryState {
6748
7778
 
6749
7779
  // src/query-builder/update.ts
6750
7780
  var UpdateQueryBuilder = class _UpdateQueryBuilder {
7781
+ table;
7782
+ state;
6751
7783
  /**
6752
7784
  * Creates a new UpdateQueryBuilder instance
6753
7785
  * @param table - The table definition for the UPDATE query
@@ -6865,6 +7897,8 @@ var isTableSourceNode = (source) => typeof source.type === "string";
6865
7897
 
6866
7898
  // src/query-builder/delete-query-state.ts
6867
7899
  var DeleteQueryState = class _DeleteQueryState {
7900
+ table;
7901
+ ast;
6868
7902
  /**
6869
7903
  * Creates a new DeleteQueryState instance
6870
7904
  * @param table - The table definition for the DELETE query
@@ -6943,6 +7977,8 @@ var DeleteQueryState = class _DeleteQueryState {
6943
7977
 
6944
7978
  // src/query-builder/delete.ts
6945
7979
  var DeleteQueryBuilder = class _DeleteQueryBuilder {
7980
+ table;
7981
+ state;
6946
7982
  /**
6947
7983
  * Creates a new DeleteQueryBuilder instance
6948
7984
  * @param table - The table definition for the DELETE query
@@ -7070,7 +8106,8 @@ var renderColumnDefinition = (table, col2, dialect, options = {}) => {
7070
8106
  if (col2.default !== void 0) {
7071
8107
  parts.push(`DEFAULT ${dialect.renderDefault(col2.default, col2)}`);
7072
8108
  }
7073
- if (options.includePrimary && col2.primary) {
8109
+ const autoIncIncludesPrimary = typeof autoInc === "string" && /\bPRIMARY\s+KEY\b/i.test(autoInc);
8110
+ if (options.includePrimary && col2.primary && !autoIncIncludesPrimary) {
7074
8111
  parts.push("PRIMARY KEY");
7075
8112
  }
7076
8113
  if (col2.check) {
@@ -7128,6 +8165,16 @@ var generateSchemaSql = (tables, dialect) => {
7128
8165
  });
7129
8166
  return statements;
7130
8167
  };
8168
+ var generateSchemaSqlFor = (dialect, ...tables) => generateSchemaSql(tables, dialect);
8169
+ var executeSchemaSql = async (executor, tables, dialect) => {
8170
+ const statements = generateSchemaSql(tables, dialect);
8171
+ for (const sql of statements) {
8172
+ await executor.executeSql(sql);
8173
+ }
8174
+ };
8175
+ var executeSchemaSqlFor = async (executor, dialect, ...tables) => {
8176
+ await executeSchemaSql(executor, tables, dialect);
8177
+ };
7131
8178
  var orderTablesByDependencies = (tables) => {
7132
8179
  const map = /* @__PURE__ */ new Map();
7133
8180
  tables.forEach((t) => map.set(t.name, t));
@@ -9034,6 +10081,7 @@ var arrayAppend = (array, value) => fn7("ARRAY_APPEND", [array, value]);
9034
10081
 
9035
10082
  // src/orm/als.ts
9036
10083
  var AsyncLocalStorage = class {
10084
+ store;
9037
10085
  /**
9038
10086
  * Executes a callback function within a context containing the specified store value.
9039
10087
  * The store value is only available during the callback's execution and is automatically
@@ -9530,9 +10578,7 @@ var TypeScriptGenerator = class {
9530
10578
 
9531
10579
  // src/orm/identity-map.ts
9532
10580
  var IdentityMap = class {
9533
- constructor() {
9534
- this.buckets = /* @__PURE__ */ new Map();
9535
- }
10581
+ buckets = /* @__PURE__ */ new Map();
9536
10582
  get bucketsMap() {
9537
10583
  return this.buckets;
9538
10584
  }
@@ -9602,8 +10648,8 @@ var UnitOfWork = class {
9602
10648
  this.executor = executor;
9603
10649
  this.identityMap = identityMap;
9604
10650
  this.hookContext = hookContext;
9605
- this.trackedEntities = /* @__PURE__ */ new Map();
9606
10651
  }
10652
+ trackedEntities = /* @__PURE__ */ new Map();
9607
10653
  /**
9608
10654
  * Gets the identity buckets map.
9609
10655
  */
@@ -9933,12 +10979,12 @@ var UnitOfWork = class {
9933
10979
 
9934
10980
  // src/orm/domain-event-bus.ts
9935
10981
  var DomainEventBus = class {
10982
+ handlers = /* @__PURE__ */ new Map();
9936
10983
  /**
9937
10984
  * Creates a new DomainEventBus instance.
9938
10985
  * @param initialHandlers - Optional initial event handlers
9939
10986
  */
9940
10987
  constructor(initialHandlers) {
9941
- this.handlers = /* @__PURE__ */ new Map();
9942
10988
  if (initialHandlers) {
9943
10989
  for (const key in initialHandlers) {
9944
10990
  const type = key;
@@ -10007,8 +11053,8 @@ var RelationChangeProcessor = class {
10007
11053
  this.unitOfWork = unitOfWork;
10008
11054
  this.dialect = dialect;
10009
11055
  this.executor = executor;
10010
- this.relationChanges = [];
10011
11056
  }
11057
+ relationChanges = [];
10012
11058
  /**
10013
11059
  * Registers a relation change for processing.
10014
11060
  * @param entry - The relation change entry
@@ -10442,25 +11488,24 @@ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
10442
11488
 
10443
11489
  // src/orm/orm-session.ts
10444
11490
  var OrmSession = class {
11491
+ /** The ORM instance */
11492
+ orm;
11493
+ /** The database executor */
11494
+ executor;
11495
+ /** The identity map for tracking entity instances */
11496
+ identityMap;
11497
+ /** The unit of work for tracking entity changes */
11498
+ unitOfWork;
11499
+ /** The domain event bus */
11500
+ domainEvents;
11501
+ /** The relation change processor */
11502
+ relationChanges;
11503
+ interceptors;
10445
11504
  /**
10446
11505
  * Creates a new OrmSession instance.
10447
11506
  * @param opts - Session options
10448
11507
  */
10449
11508
  constructor(opts) {
10450
- /**
10451
- * Registers a relation change.
10452
- * @param root - The root entity
10453
- * @param relationKey - The relation key
10454
- * @param rootTable - The root table definition
10455
- * @param relationName - The relation name
10456
- * @param relation - The relation definition
10457
- * @param change - The relation change
10458
- */
10459
- this.registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
10460
- this.relationChanges.registerChange(
10461
- buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
10462
- );
10463
- };
10464
11509
  this.orm = opts.orm;
10465
11510
  this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
10466
11511
  this.interceptors = [...opts.interceptors ?? []];
@@ -10549,6 +11594,20 @@ var OrmSession = class {
10549
11594
  markRemoved(entity) {
10550
11595
  this.unitOfWork.markRemoved(entity);
10551
11596
  }
11597
+ /**
11598
+ * Registers a relation change.
11599
+ * @param root - The root entity
11600
+ * @param relationKey - The relation key
11601
+ * @param rootTable - The root table definition
11602
+ * @param relationName - The relation name
11603
+ * @param relation - The relation definition
11604
+ * @param change - The relation change
11605
+ */
11606
+ registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
11607
+ this.relationChanges.registerChange(
11608
+ buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
11609
+ );
11610
+ };
10552
11611
  /**
10553
11612
  * Gets all tracked entities for a specific table.
10554
11613
  * @param table - The table definition
@@ -10754,9 +11813,7 @@ var buildRelationChangeEntry = (root, relationKey, rootTable, relationName, rela
10754
11813
 
10755
11814
  // src/orm/interceptor-pipeline.ts
10756
11815
  var InterceptorPipeline = class {
10757
- constructor() {
10758
- this.interceptors = [];
10759
- }
11816
+ interceptors = [];
10760
11817
  use(interceptor) {
10761
11818
  this.interceptors.push(interceptor);
10762
11819
  }
@@ -10775,6 +11832,13 @@ var InterceptorPipeline = class {
10775
11832
 
10776
11833
  // src/orm/orm.ts
10777
11834
  var Orm = class {
11835
+ /** The database dialect */
11836
+ dialect;
11837
+ /** The interceptors pipeline */
11838
+ interceptors;
11839
+ /** The naming strategy */
11840
+ namingStrategy;
11841
+ executorFactory;
10778
11842
  /**
10779
11843
  * Creates a new ORM instance.
10780
11844
  * @param opts - ORM options
@@ -10831,17 +11895,13 @@ var jsonify = (value) => {
10831
11895
 
10832
11896
  // src/decorators/decorator-metadata.ts
10833
11897
  var METADATA_KEY = "metal-orm:decorators";
10834
- var isStandardDecoratorContext = (value) => {
10835
- return typeof value === "object" && value !== null && "kind" in value;
10836
- };
10837
11898
  var getOrCreateMetadataBag = (context) => {
10838
11899
  const metadata = context.metadata || (context.metadata = {});
10839
- const existing = metadata[METADATA_KEY];
10840
- if (existing) {
10841
- return existing;
11900
+ let bag = metadata[METADATA_KEY];
11901
+ if (!bag) {
11902
+ bag = { columns: [], relations: [] };
11903
+ metadata[METADATA_KEY] = bag;
10842
11904
  }
10843
- const bag = { columns: [], relations: [] };
10844
- metadata[METADATA_KEY] = bag;
10845
11905
  return bag;
10846
11906
  };
10847
11907
  var readMetadataBag = (context) => {
@@ -10856,57 +11916,50 @@ var readMetadataBagFromConstructor = (ctor) => {
10856
11916
  var getDecoratorMetadata = (ctor) => readMetadataBagFromConstructor(ctor);
10857
11917
 
10858
11918
  // src/decorators/entity.ts
10859
- var toSnakeCase = (value) => {
11919
+ var toSnakeCase2 = (value) => {
10860
11920
  return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
10861
11921
  };
10862
11922
  var deriveTableNameFromConstructor = (ctor) => {
10863
11923
  const fallback = "unknown";
10864
11924
  const rawName = ctor.name || fallback;
10865
11925
  const strippedName = rawName.replace(/Entity$/i, "");
10866
- const normalized = toSnakeCase(strippedName || rawName);
11926
+ const normalized = toSnakeCase2(strippedName || rawName);
10867
11927
  if (!normalized) {
10868
11928
  return fallback;
10869
11929
  }
10870
11930
  return normalized.endsWith("s") ? normalized : `${normalized}s`;
10871
11931
  };
10872
11932
  function Entity(options = {}) {
10873
- const decorator = (value) => {
10874
- const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
10875
- setEntityTableName(value, tableName, options.hooks);
10876
- return value;
10877
- };
10878
- const decoratorWithContext = (value, context) => {
11933
+ return function(value, context) {
10879
11934
  const ctor = value;
10880
- decorator(ctor);
10881
- if (context && isStandardDecoratorContext(context)) {
10882
- const bag = readMetadataBag(context);
10883
- if (bag) {
10884
- const meta = ensureEntityMetadata(ctor);
10885
- for (const entry of bag.columns) {
10886
- if (meta.columns[entry.propertyName]) {
10887
- throw new Error(
10888
- `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
10889
- );
10890
- }
10891
- addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
11935
+ const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
11936
+ setEntityTableName(ctor, tableName, options.hooks);
11937
+ const bag = readMetadataBag(context);
11938
+ if (bag) {
11939
+ const meta = ensureEntityMetadata(ctor);
11940
+ for (const entry of bag.columns) {
11941
+ if (meta.columns[entry.propertyName]) {
11942
+ throw new Error(
11943
+ `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11944
+ );
10892
11945
  }
10893
- for (const entry of bag.relations) {
10894
- if (meta.relations[entry.propertyName]) {
10895
- throw new Error(
10896
- `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
10897
- );
10898
- }
10899
- const relationCopy = entry.relation.kind === RelationKinds.BelongsToMany ? {
10900
- ...entry.relation,
10901
- defaultPivotColumns: entry.relation.defaultPivotColumns ? [...entry.relation.defaultPivotColumns] : void 0
10902
- } : { ...entry.relation };
10903
- addRelationMetadata(ctor, entry.propertyName, relationCopy);
11946
+ addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
11947
+ }
11948
+ for (const entry of bag.relations) {
11949
+ if (meta.relations[entry.propertyName]) {
11950
+ throw new Error(
11951
+ `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11952
+ );
10904
11953
  }
11954
+ const relationCopy = entry.relation.kind === RelationKinds.BelongsToMany ? {
11955
+ ...entry.relation,
11956
+ defaultPivotColumns: entry.relation.defaultPivotColumns ? [...entry.relation.defaultPivotColumns] : void 0
11957
+ } : { ...entry.relation };
11958
+ addRelationMetadata(ctor, entry.propertyName, relationCopy);
10905
11959
  }
10906
11960
  }
10907
11961
  return ctor;
10908
11962
  };
10909
- return decoratorWithContext;
10910
11963
  }
10911
11964
 
10912
11965
  // src/decorators/column-decorator.ts
@@ -10939,26 +11992,13 @@ var normalizePropertyName = (name) => {
10939
11992
  }
10940
11993
  return name;
10941
11994
  };
10942
- var resolveConstructor = (target) => {
10943
- if (typeof target === "function") {
10944
- return target;
10945
- }
10946
- if (target && typeof target.constructor === "function") {
10947
- return target.constructor;
10948
- }
10949
- return void 0;
10950
- };
10951
- var registerColumn = (ctor, propertyName, column) => {
10952
- const meta = ensureEntityMetadata(ctor);
10953
- if (meta.columns[propertyName]) {
10954
- return;
10955
- }
10956
- addColumnMetadata(ctor, propertyName, column);
10957
- };
10958
11995
  var registerColumnFromContext = (context, column) => {
10959
11996
  if (!context.name) {
10960
11997
  throw new Error("Column decorator requires a property name");
10961
11998
  }
11999
+ if (context.private) {
12000
+ throw new Error("Column decorator does not support private fields");
12001
+ }
10962
12002
  const propertyName = normalizePropertyName(context.name);
10963
12003
  const bag = getOrCreateMetadataBag(context);
10964
12004
  if (!bag.columns.some((entry) => entry.propertyName === propertyName)) {
@@ -10967,19 +12007,9 @@ var registerColumnFromContext = (context, column) => {
10967
12007
  };
10968
12008
  function Column(definition) {
10969
12009
  const normalized = normalizeColumnInput(definition);
10970
- const decorator = (targetOrValue, propertyKeyOrContext) => {
10971
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
10972
- registerColumnFromContext(propertyKeyOrContext, normalized);
10973
- return;
10974
- }
10975
- const propertyName = normalizePropertyName(propertyKeyOrContext);
10976
- const ctor = resolveConstructor(targetOrValue);
10977
- if (!ctor) {
10978
- throw new Error("Unable to resolve constructor when registering column metadata");
10979
- }
10980
- registerColumn(ctor, propertyName, { ...normalized });
12010
+ return function(_value, context) {
12011
+ registerColumnFromContext(context, normalized);
10981
12012
  };
10982
- return decorator;
10983
12013
  }
10984
12014
  function PrimaryKey(definition) {
10985
12015
  const normalized = normalizeColumnInput(definition);
@@ -10994,41 +12024,21 @@ var normalizePropertyName2 = (name) => {
10994
12024
  }
10995
12025
  return name;
10996
12026
  };
10997
- var resolveConstructor2 = (instanceOrCtor) => {
10998
- if (typeof instanceOrCtor === "function") {
10999
- return instanceOrCtor;
11000
- }
11001
- if (instanceOrCtor && typeof instanceOrCtor.constructor === "function") {
11002
- return instanceOrCtor.constructor;
11003
- }
11004
- return void 0;
11005
- };
11006
- var registerRelation = (ctor, propertyName, metadata) => {
11007
- addRelationMetadata(ctor, propertyName, metadata);
11008
- };
11009
12027
  var createFieldDecorator = (metadataFactory) => {
11010
- const decorator = (targetOrValue, propertyKeyOrContext) => {
11011
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
11012
- const ctx = propertyKeyOrContext;
11013
- if (!ctx.name) {
11014
- throw new Error("Relation decorator requires a property name");
11015
- }
11016
- const propertyName2 = normalizePropertyName2(ctx.name);
11017
- const bag = getOrCreateMetadataBag(ctx);
11018
- const relationMetadata = metadataFactory(propertyName2);
11019
- if (!bag.relations.some((entry) => entry.propertyName === propertyName2)) {
11020
- bag.relations.push({ propertyName: propertyName2, relation: relationMetadata });
11021
- }
11022
- return;
12028
+ return function(_value, context) {
12029
+ if (!context.name) {
12030
+ throw new Error("Relation decorator requires a property name");
11023
12031
  }
11024
- const propertyName = normalizePropertyName2(propertyKeyOrContext);
11025
- const ctor = resolveConstructor2(targetOrValue);
11026
- if (!ctor) {
11027
- throw new Error("Unable to resolve constructor when registering relation metadata");
12032
+ if (context.private) {
12033
+ throw new Error("Relation decorator does not support private fields");
12034
+ }
12035
+ const propertyName = normalizePropertyName2(context.name);
12036
+ const bag = getOrCreateMetadataBag(context);
12037
+ const relationMetadata = metadataFactory(propertyName);
12038
+ if (!bag.relations.some((entry) => entry.propertyName === propertyName)) {
12039
+ bag.relations.push({ propertyName, relation: relationMetadata });
11028
12040
  }
11029
- registerRelation(ctor, propertyName, metadataFactory(propertyName));
11030
12041
  };
11031
- return decorator;
11032
12042
  };
11033
12043
  function HasMany(options) {
11034
12044
  return createFieldDecorator((propertyName) => ({
@@ -11055,7 +12065,7 @@ function BelongsTo(options) {
11055
12065
  kind: RelationKinds.BelongsTo,
11056
12066
  propertyKey: propertyName,
11057
12067
  target: options.target,
11058
- foreignKey: options.foreignKey,
12068
+ foreignKey: options.foreignKey ?? `${propertyName}_id`,
11059
12069
  localKey: options.localKey,
11060
12070
  cascade: options.cascade
11061
12071
  }));
@@ -11131,13 +12141,15 @@ var deferred = () => {
11131
12141
  return { promise, resolve, reject };
11132
12142
  };
11133
12143
  var Pool = class {
12144
+ adapter;
12145
+ options;
12146
+ destroyed = false;
12147
+ creating = 0;
12148
+ leased = 0;
12149
+ idle = [];
12150
+ waiters = [];
12151
+ reapTimer = null;
11134
12152
  constructor(adapter, options) {
11135
- this.destroyed = false;
11136
- this.creating = 0;
11137
- this.leased = 0;
11138
- this.idle = [];
11139
- this.waiters = [];
11140
- this.reapTimer = null;
11141
12153
  if (!Number.isFinite(options.max) || options.max <= 0) {
11142
12154
  throw new Error("Pool options.max must be a positive number");
11143
12155
  }
@@ -11700,6 +12712,8 @@ function createPooledExecutorFactory(opts) {
11700
12712
  esel,
11701
12713
  executeHydrated,
11702
12714
  executeHydratedWithContexts,
12715
+ executeSchemaSql,
12716
+ executeSchemaSqlFor,
11703
12717
  exists,
11704
12718
  exp,
11705
12719
  extract,
@@ -11708,6 +12722,7 @@ function createPooledExecutorFactory(opts) {
11708
12722
  fromUnixTime,
11709
12723
  generateCreateTableSql,
11710
12724
  generateSchemaSql,
12725
+ generateSchemaSqlFor,
11711
12726
  getColumn,
11712
12727
  getDecoratorMetadata,
11713
12728
  getSchemaIntrospector,
@@ -11798,6 +12813,7 @@ function createPooledExecutorFactory(opts) {
11798
12813
  registerExpressionDispatcher,
11799
12814
  registerOperandDispatcher,
11800
12815
  registerSchemaIntrospector,
12816
+ relationLoaderCache,
11801
12817
  renderColumnDefinition,
11802
12818
  renderTypeWithArgs,
11803
12819
  repeat,
@@ -11812,6 +12828,7 @@ function createPooledExecutorFactory(opts) {
11812
12828
  second,
11813
12829
  sel,
11814
12830
  selectFromEntity,
12831
+ setRelations,
11815
12832
  sha1,
11816
12833
  sha2,
11817
12834
  shiftLeft,