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.js CHANGED
@@ -51,6 +51,9 @@ var defineTable = (name, columns, relations = {}, hooks, options = {}) => {
51
51
  collation: options.collation
52
52
  };
53
53
  };
54
+ function setRelations(table, relations) {
55
+ table.relations = relations;
56
+ }
54
57
  var TABLE_REF_CACHE = /* @__PURE__ */ new WeakMap();
55
58
  var withColumnProps = (table) => {
56
59
  const cached = TABLE_REF_CACHE.get(table);
@@ -694,6 +697,7 @@ var variance = buildAggregate("VARIANCE");
694
697
 
695
698
  // src/core/ast/expression-visitor.ts
696
699
  var DispatcherRegistry = class _DispatcherRegistry {
700
+ dispatchers;
697
701
  constructor(dispatchers = /* @__PURE__ */ new Map()) {
698
702
  this.dispatchers = dispatchers;
699
703
  }
@@ -827,61 +831,9 @@ var toTableRef = (table) => ({
827
831
  alias: hasAlias(table) ? table.alias : void 0
828
832
  });
829
833
 
830
- // src/core/ast/builders.ts
831
- var isColumnNode = (col2) => "type" in col2 && col2.type === "Column";
832
- var resolveTableName = (def, table) => {
833
- if (!def.table) {
834
- return table.alias || table.name;
835
- }
836
- if (table.alias && def.table === table.name) {
837
- return table.alias;
838
- }
839
- return def.table;
840
- };
841
- var buildColumnNode = (table, column) => {
842
- if (isColumnNode(column)) {
843
- return column;
844
- }
845
- const def = column;
846
- const baseTable = resolveTableName(def, table);
847
- return {
848
- type: "Column",
849
- table: baseTable,
850
- name: def.name
851
- };
852
- };
853
- var buildColumnNodes = (table, names) => names.map((name) => ({
854
- type: "Column",
855
- table: table.alias || table.name,
856
- name
857
- }));
858
- var createTableNode = (table) => ({
859
- type: "Table",
860
- name: table.name,
861
- schema: table.schema
862
- });
863
- var fnTable = (name, args = [], alias, opts) => ({
864
- type: "FunctionTable",
865
- name,
866
- args,
867
- alias,
868
- lateral: opts?.lateral,
869
- withOrdinality: opts?.withOrdinality,
870
- columnAliases: opts?.columnAliases,
871
- schema: opts?.schema
872
- });
873
- var derivedTable = (query, alias, columnAliases) => ({
874
- type: "DerivedTable",
875
- query,
876
- alias,
877
- columnAliases
878
- });
879
-
880
834
  // src/core/functions/function-registry.ts
881
835
  var FunctionRegistry = class {
882
- constructor() {
883
- this.renderers = /* @__PURE__ */ new Map();
884
- }
836
+ renderers = /* @__PURE__ */ new Map();
885
837
  /**
886
838
  * Registers or overrides a renderer for the given function name.
887
839
  */
@@ -1176,6 +1128,7 @@ function renderStandardGroupConcat(ctx) {
1176
1128
 
1177
1129
  // src/core/functions/standard-strategy.ts
1178
1130
  var StandardFunctionStrategy = class {
1131
+ registry;
1179
1132
  /**
1180
1133
  * Creates a new StandardFunctionStrategy and registers standard functions.
1181
1134
  */
@@ -1241,17 +1194,13 @@ var StandardFunctionStrategy = class {
1241
1194
  getGroupConcatSeparatorOperand(ctx) {
1242
1195
  return getGroupConcatSeparatorOperand(ctx);
1243
1196
  }
1244
- static {
1245
- /** Default separator for GROUP_CONCAT, a comma. */
1246
- this.DEFAULT_GROUP_CONCAT_SEPARATOR = DEFAULT_GROUP_CONCAT_SEPARATOR;
1247
- }
1197
+ /** Default separator for GROUP_CONCAT, a comma. */
1198
+ static DEFAULT_GROUP_CONCAT_SEPARATOR = DEFAULT_GROUP_CONCAT_SEPARATOR;
1248
1199
  };
1249
1200
 
1250
1201
  // src/core/functions/standard-table-strategy.ts
1251
1202
  var StandardTableFunctionStrategy = class {
1252
- constructor() {
1253
- this.renderers = /* @__PURE__ */ new Map();
1254
- }
1203
+ renderers = /* @__PURE__ */ new Map();
1255
1204
  add(key, renderer) {
1256
1205
  this.renderers.set(key, renderer);
1257
1206
  }
@@ -1430,6 +1379,10 @@ var Dialect = class _Dialect {
1430
1379
  const combinedCtes = [...normalized.ctes ?? [], ...hoistedCtes];
1431
1380
  return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
1432
1381
  }
1382
+ expressionCompilers;
1383
+ operandCompilers;
1384
+ functionStrategy;
1385
+ tableFunctionStrategy;
1433
1386
  constructor(functionStrategy, tableFunctionStrategy) {
1434
1387
  this.expressionCompilers = /* @__PURE__ */ new Map();
1435
1388
  this.operandCompilers = /* @__PURE__ */ new Map();
@@ -1445,10 +1398,7 @@ var Dialect = class _Dialect {
1445
1398
  */
1446
1399
  static create(functionStrategy, tableFunctionStrategy) {
1447
1400
  class TestDialect extends _Dialect {
1448
- constructor() {
1449
- super(...arguments);
1450
- this.dialect = "sqlite";
1451
- }
1401
+ dialect = "sqlite";
1452
1402
  quoteIdentifier(id) {
1453
1403
  return `"${id}"`;
1454
1404
  }
@@ -1888,11 +1838,8 @@ var OrderByCompiler = class {
1888
1838
 
1889
1839
  // src/core/dialect/base/sql-dialect.ts
1890
1840
  var SqlDialectBase = class extends Dialect {
1891
- constructor() {
1892
- super(...arguments);
1893
- this.paginationStrategy = new StandardLimitOffsetPagination();
1894
- this.returningStrategy = new NoReturningStrategy();
1895
- }
1841
+ paginationStrategy = new StandardLimitOffsetPagination();
1842
+ returningStrategy = new NoReturningStrategy();
1896
1843
  compileSelectAst(ast, ctx) {
1897
1844
  const hasSetOps = !!(ast.setOps && ast.setOps.length);
1898
1845
  const ctes = CteCompiler.compileCtes(
@@ -2280,12 +2227,12 @@ var PostgresTableFunctionStrategy = class extends StandardTableFunctionStrategy
2280
2227
 
2281
2228
  // src/core/dialect/postgres/index.ts
2282
2229
  var PostgresDialect = class extends SqlDialectBase {
2230
+ dialect = "postgres";
2283
2231
  /**
2284
2232
  * Creates a new PostgresDialect instance
2285
2233
  */
2286
2234
  constructor() {
2287
2235
  super(new PostgresFunctionStrategy(), new PostgresTableFunctionStrategy());
2288
- this.dialect = "postgres";
2289
2236
  this.registerExpressionCompiler("BitwiseExpression", (node, ctx) => {
2290
2237
  const left2 = this.compileOperand(node.left, ctx);
2291
2238
  const right2 = this.compileOperand(node.right, ctx);
@@ -2419,12 +2366,12 @@ var MysqlFunctionStrategy = class extends StandardFunctionStrategy {
2419
2366
 
2420
2367
  // src/core/dialect/mysql/index.ts
2421
2368
  var MySqlDialect = class extends SqlDialectBase {
2369
+ dialect = "mysql";
2422
2370
  /**
2423
2371
  * Creates a new MySqlDialect instance
2424
2372
  */
2425
2373
  constructor() {
2426
2374
  super(new MysqlFunctionStrategy());
2427
- this.dialect = "mysql";
2428
2375
  }
2429
2376
  /**
2430
2377
  * Quotes an identifier using MySQL backtick syntax
@@ -2575,12 +2522,12 @@ var SqliteFunctionStrategy = class extends StandardFunctionStrategy {
2575
2522
 
2576
2523
  // src/core/dialect/sqlite/index.ts
2577
2524
  var SqliteDialect = class extends SqlDialectBase {
2525
+ dialect = "sqlite";
2578
2526
  /**
2579
2527
  * Creates a new SqliteDialect instance
2580
2528
  */
2581
2529
  constructor() {
2582
2530
  super(new SqliteFunctionStrategy());
2583
- this.dialect = "sqlite";
2584
2531
  this.registerExpressionCompiler("BitwiseExpression", (node, ctx) => {
2585
2532
  const left2 = this.compileOperand(node.left, ctx);
2586
2533
  const right2 = this.compileOperand(node.right, ctx);
@@ -2753,12 +2700,12 @@ var MssqlFunctionStrategy = class extends StandardFunctionStrategy {
2753
2700
 
2754
2701
  // src/core/dialect/mssql/index.ts
2755
2702
  var SqlServerDialect = class extends SqlDialectBase {
2703
+ dialect = "mssql";
2756
2704
  /**
2757
2705
  * Creates a new SqlServerDialect instance
2758
2706
  */
2759
2707
  constructor() {
2760
2708
  super(new MssqlFunctionStrategy());
2761
- this.dialect = "mssql";
2762
2709
  }
2763
2710
  /**
2764
2711
  * Quotes an identifier using SQL Server bracket syntax
@@ -2885,12 +2832,8 @@ var SqlServerDialect = class extends SqlDialectBase {
2885
2832
 
2886
2833
  // src/core/dialect/dialect-factory.ts
2887
2834
  var DialectFactory = class {
2888
- static {
2889
- this.registry = /* @__PURE__ */ new Map();
2890
- }
2891
- static {
2892
- this.defaultsInitialized = false;
2893
- }
2835
+ static registry = /* @__PURE__ */ new Map();
2836
+ static defaultsInitialized = false;
2894
2837
  static ensureDefaults() {
2895
2838
  if (this.defaultsInitialized) return;
2896
2839
  this.defaultsInitialized = true;
@@ -2949,8 +2892,66 @@ var resolveDialectInput = (dialect) => {
2949
2892
  return dialect;
2950
2893
  };
2951
2894
 
2895
+ // src/core/ast/builders.ts
2896
+ var isColumnNode = (col2) => "type" in col2 && col2.type === "Column";
2897
+ var resolveTableName = (def, table) => {
2898
+ if (!def.table) {
2899
+ return table.alias || table.name;
2900
+ }
2901
+ if (table.alias && def.table === table.name) {
2902
+ return table.alias;
2903
+ }
2904
+ return def.table;
2905
+ };
2906
+ var buildColumnNode = (table, column) => {
2907
+ if (isColumnNode(column)) {
2908
+ return column;
2909
+ }
2910
+ const def = column;
2911
+ const baseTable = resolveTableName(def, table);
2912
+ return {
2913
+ type: "Column",
2914
+ table: baseTable,
2915
+ name: def.name
2916
+ };
2917
+ };
2918
+ var buildColumnNodes = (table, names) => names.map((name) => ({
2919
+ type: "Column",
2920
+ table: table.alias || table.name,
2921
+ name
2922
+ }));
2923
+ var createTableNode = (table) => ({
2924
+ type: "Table",
2925
+ name: table.name,
2926
+ schema: table.schema
2927
+ });
2928
+ var fnTable = (name, args = [], alias, opts) => ({
2929
+ type: "FunctionTable",
2930
+ name,
2931
+ args,
2932
+ alias,
2933
+ lateral: opts?.lateral,
2934
+ withOrdinality: opts?.withOrdinality,
2935
+ columnAliases: opts?.columnAliases,
2936
+ schema: opts?.schema
2937
+ });
2938
+ var derivedTable = (query, alias, columnAliases) => ({
2939
+ type: "DerivedTable",
2940
+ query,
2941
+ alias,
2942
+ columnAliases
2943
+ });
2944
+
2952
2945
  // src/query-builder/select-query-state.ts
2953
2946
  var SelectQueryState = class _SelectQueryState {
2947
+ /**
2948
+ * Table definition for the query
2949
+ */
2950
+ table;
2951
+ /**
2952
+ * Abstract Syntax Tree (AST) representation of the query
2953
+ */
2954
+ ast;
2954
2955
  /**
2955
2956
  * Creates a new SelectQueryState instance
2956
2957
  * @param table - Table definition
@@ -3852,20 +3853,21 @@ var RelationProjectionHelper = class {
3852
3853
  var assertNever = (value) => {
3853
3854
  throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
3854
3855
  };
3855
- var baseRelationCondition = (root, relation, rootAlias) => {
3856
+ var baseRelationCondition = (root, relation, rootAlias, targetTableName) => {
3856
3857
  const rootTable = rootAlias || root.name;
3858
+ const targetTable = targetTableName ?? relation.target.name;
3857
3859
  const defaultLocalKey = relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne ? findPrimaryKey(root) : findPrimaryKey(relation.target);
3858
3860
  const localKey = relation.localKey || defaultLocalKey;
3859
3861
  switch (relation.type) {
3860
3862
  case RelationKinds.HasMany:
3861
3863
  case RelationKinds.HasOne:
3862
3864
  return eq(
3863
- { type: "Column", table: relation.target.name, name: relation.foreignKey },
3865
+ { type: "Column", table: targetTable, name: relation.foreignKey },
3864
3866
  { type: "Column", table: rootTable, name: localKey }
3865
3867
  );
3866
3868
  case RelationKinds.BelongsTo:
3867
3869
  return eq(
3868
- { type: "Column", table: relation.target.name, name: localKey },
3870
+ { type: "Column", table: targetTable, name: localKey },
3869
3871
  { type: "Column", table: rootTable, name: relation.foreignKey }
3870
3872
  );
3871
3873
  case RelationKinds.BelongsToMany:
@@ -3874,7 +3876,7 @@ var baseRelationCondition = (root, relation, rootAlias) => {
3874
3876
  return assertNever(relation);
3875
3877
  }
3876
3878
  };
3877
- var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias) => {
3879
+ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias, targetTable, targetTableName) => {
3878
3880
  const rootKey = relation.localKey || findPrimaryKey(root);
3879
3881
  const targetKey = relation.targetKey || findPrimaryKey(relation.target);
3880
3882
  const rootTable = rootAlias || root.name;
@@ -3887,8 +3889,14 @@ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, ro
3887
3889
  { type: "Table", name: relation.pivotTable.name, schema: relation.pivotTable.schema },
3888
3890
  pivotCondition
3889
3891
  );
3892
+ const targetSource = targetTable ?? {
3893
+ type: "Table",
3894
+ name: relation.target.name,
3895
+ schema: relation.target.schema
3896
+ };
3897
+ const effectiveTargetName = targetTableName ?? relation.target.name;
3890
3898
  let targetCondition = eq(
3891
- { type: "Column", table: relation.target.name, name: targetKey },
3899
+ { type: "Column", table: effectiveTargetName, name: targetKey },
3892
3900
  { type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
3893
3901
  );
3894
3902
  if (extra) {
@@ -3896,141 +3904,487 @@ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, ro
3896
3904
  }
3897
3905
  const targetJoin = createJoinNode(
3898
3906
  joinKind,
3899
- { type: "Table", name: relation.target.name, schema: relation.target.schema },
3907
+ targetSource,
3900
3908
  targetCondition,
3901
3909
  relationName
3902
3910
  );
3903
3911
  return [pivotJoin, targetJoin];
3904
3912
  };
3905
- var buildRelationJoinCondition = (root, relation, extra, rootAlias) => {
3906
- const base = baseRelationCondition(root, relation, rootAlias);
3913
+ var buildRelationJoinCondition = (root, relation, extra, rootAlias, targetTableName) => {
3914
+ const base = baseRelationCondition(root, relation, rootAlias, targetTableName);
3907
3915
  return extra ? and(base, extra) : base;
3908
3916
  };
3909
- var buildRelationCorrelation = (root, relation, rootAlias) => {
3910
- return baseRelationCondition(root, relation, rootAlias);
3917
+ var buildRelationCorrelation = (root, relation, rootAlias, targetTableName) => {
3918
+ return baseRelationCondition(root, relation, rootAlias, targetTableName);
3911
3919
  };
3912
3920
 
3913
3921
  // src/core/ast/join-metadata.ts
3914
3922
  var getJoinRelationName = (join) => join.meta?.relationName;
3915
3923
 
3916
- // src/query-builder/relation-service.ts
3917
- var RelationService = class {
3918
- /**
3919
- * Creates a new RelationService instance
3920
- * @param table - Table definition
3921
- * @param state - Current query state
3922
- * @param hydration - Hydration manager
3923
- */
3924
- constructor(table, state, hydration, createQueryAstService) {
3925
- this.table = table;
3926
- this.state = state;
3927
- this.hydration = hydration;
3928
- this.createQueryAstService = createQueryAstService;
3929
- this.projectionHelper = new RelationProjectionHelper(
3930
- table,
3931
- (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
3932
- );
3924
+ // src/query-builder/relation-filter-utils.ts
3925
+ var splitFilterExpressions = (filter, allowedTables) => {
3926
+ const terms = flattenAnd(filter);
3927
+ const selfFilters = [];
3928
+ const crossFilters = [];
3929
+ for (const term of terms) {
3930
+ if (isExpressionSelfContained(term, allowedTables)) {
3931
+ selfFilters.push(term);
3932
+ } else {
3933
+ crossFilters.push(term);
3934
+ }
3933
3935
  }
3934
- /**
3935
- * Joins a relation to the query
3936
- * @param relationName - Name of the relation to join
3937
- * @param joinKind - Type of join to use
3938
- * @param extraCondition - Additional join condition
3939
- * @returns Relation result with updated state and hydration
3940
- */
3941
- joinRelation(relationName, joinKind, extraCondition) {
3942
- const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
3943
- return { state: nextState, hydration: this.hydration };
3936
+ return { selfFilters, crossFilters };
3937
+ };
3938
+ var flattenAnd = (node) => {
3939
+ if (!node) return [];
3940
+ if (node.type === "LogicalExpression" && node.operator === "AND") {
3941
+ return node.operands.flatMap((operand) => flattenAnd(operand));
3944
3942
  }
3945
- /**
3946
- * Matches records based on a relation with an optional predicate
3947
- * @param relationName - Name of the relation to match
3948
- * @param predicate - Optional predicate expression
3949
- * @returns Relation result with updated state and hydration
3950
- */
3951
- match(relationName, predicate) {
3952
- const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
3953
- const pk = findPrimaryKey(this.table);
3954
- const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
3955
- const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
3956
- const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
3957
- return { state: nextState, hydration: joined.hydration };
3943
+ return [node];
3944
+ };
3945
+ var isExpressionSelfContained = (expr, allowedTables) => {
3946
+ const collector = collectReferencedTables(expr);
3947
+ if (collector.hasSubquery) return false;
3948
+ if (collector.tables.size === 0) return true;
3949
+ for (const table of collector.tables) {
3950
+ if (!allowedTables.has(table)) {
3951
+ return false;
3952
+ }
3958
3953
  }
3959
- /**
3960
- * Includes a relation in the query result
3961
- * @param relationName - Name of the relation to include
3962
- * @param options - Options for relation inclusion
3963
- * @returns Relation result with updated state and hydration
3964
- */
3965
- include(relationName, options) {
3966
- let state = this.state;
3967
- let hydration = this.hydration;
3968
- const relation = this.getRelation(relationName);
3969
- const aliasPrefix = options?.aliasPrefix ?? relationName;
3970
- const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
3971
- if (!alreadyJoined) {
3972
- const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
3973
- state = joined.state;
3954
+ return true;
3955
+ };
3956
+ var collectReferencedTables = (expr) => {
3957
+ const collector = {
3958
+ tables: /* @__PURE__ */ new Set(),
3959
+ hasSubquery: false
3960
+ };
3961
+ collectFromExpression(expr, collector);
3962
+ return collector;
3963
+ };
3964
+ var collectFromExpression = (expr, collector) => {
3965
+ switch (expr.type) {
3966
+ case "BinaryExpression":
3967
+ collectFromOperand(expr.left, collector);
3968
+ collectFromOperand(expr.right, collector);
3969
+ break;
3970
+ case "LogicalExpression":
3971
+ expr.operands.forEach((operand) => collectFromExpression(operand, collector));
3972
+ break;
3973
+ case "NullExpression":
3974
+ collectFromOperand(expr.left, collector);
3975
+ break;
3976
+ case "InExpression":
3977
+ collectFromOperand(expr.left, collector);
3978
+ if (Array.isArray(expr.right)) {
3979
+ expr.right.forEach((value) => collectFromOperand(value, collector));
3980
+ } else {
3981
+ collector.hasSubquery = true;
3982
+ }
3983
+ break;
3984
+ case "ExistsExpression":
3985
+ collector.hasSubquery = true;
3986
+ break;
3987
+ case "BetweenExpression":
3988
+ collectFromOperand(expr.left, collector);
3989
+ collectFromOperand(expr.lower, collector);
3990
+ collectFromOperand(expr.upper, collector);
3991
+ break;
3992
+ case "ArithmeticExpression":
3993
+ case "BitwiseExpression":
3994
+ collectFromOperand(expr.left, collector);
3995
+ collectFromOperand(expr.right, collector);
3996
+ break;
3997
+ default:
3998
+ break;
3999
+ }
4000
+ };
4001
+ var collectFromOperand = (node, collector) => {
4002
+ switch (node.type) {
4003
+ case "Column":
4004
+ collector.tables.add(node.table);
4005
+ break;
4006
+ case "Function":
4007
+ node.args.forEach((arg) => collectFromOperand(arg, collector));
4008
+ if (node.separator) {
4009
+ collectFromOperand(node.separator, collector);
4010
+ }
4011
+ if (node.orderBy) {
4012
+ node.orderBy.forEach((order) => collectFromOrderingTerm(order.term, collector));
4013
+ }
4014
+ break;
4015
+ case "JsonPath":
4016
+ collectFromOperand(node.column, collector);
4017
+ break;
4018
+ case "ScalarSubquery":
4019
+ collector.hasSubquery = true;
4020
+ break;
4021
+ case "CaseExpression":
4022
+ node.conditions.forEach(({ when, then }) => {
4023
+ collectFromExpression(when, collector);
4024
+ collectFromOperand(then, collector);
4025
+ });
4026
+ if (node.else) {
4027
+ collectFromOperand(node.else, collector);
4028
+ }
4029
+ break;
4030
+ case "Cast":
4031
+ collectFromOperand(node.expression, collector);
4032
+ break;
4033
+ case "WindowFunction":
4034
+ node.args.forEach((arg) => collectFromOperand(arg, collector));
4035
+ node.partitionBy?.forEach((part) => collectFromOperand(part, collector));
4036
+ node.orderBy?.forEach((order) => collectFromOrderingTerm(order.term, collector));
4037
+ break;
4038
+ case "Collate":
4039
+ collectFromOperand(node.expression, collector);
4040
+ break;
4041
+ case "ArithmeticExpression":
4042
+ case "BitwiseExpression":
4043
+ collectFromOperand(node.left, collector);
4044
+ collectFromOperand(node.right, collector);
4045
+ break;
4046
+ case "Literal":
4047
+ case "AliasRef":
4048
+ break;
4049
+ default:
4050
+ break;
4051
+ }
4052
+ };
4053
+ var collectFromOrderingTerm = (term, collector) => {
4054
+ if (isOperandNode(term)) {
4055
+ collectFromOperand(term, collector);
4056
+ return;
4057
+ }
4058
+ collectFromExpression(term, collector);
4059
+ };
4060
+
4061
+ // src/query-builder/relation-join-planner.ts
4062
+ var RelationJoinPlanner = class {
4063
+ constructor(table, createQueryAstService) {
4064
+ this.table = table;
4065
+ this.createQueryAstService = createQueryAstService;
4066
+ }
4067
+ withJoin(state, relationName, relation, joinKind, extraCondition, tableSource) {
4068
+ const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4069
+ if (relation.type === RelationKinds.BelongsToMany) {
4070
+ const targetTableSource = tableSource ?? {
4071
+ type: "Table",
4072
+ name: relation.target.name,
4073
+ schema: relation.target.schema
4074
+ };
4075
+ const targetName2 = this.resolveTargetTableName(targetTableSource, relation);
4076
+ const joins = buildBelongsToManyJoins(
4077
+ this.table,
4078
+ relationName,
4079
+ relation,
4080
+ joinKind,
4081
+ extraCondition,
4082
+ rootAlias,
4083
+ targetTableSource,
4084
+ targetName2
4085
+ );
4086
+ return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
3974
4087
  }
3975
- const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
3976
- state = projectionResult.state;
3977
- hydration = projectionResult.hydration;
3978
- const targetColumns = options?.columns?.length ? options.columns : Object.keys(relation.target.columns);
3979
- const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
3980
- return keys.reduce((acc, key) => {
3981
- const def = columns[key];
3982
- if (!def) {
3983
- throw new Error(missingMsg(key));
3984
- }
3985
- acc[makeRelationAlias(prefix, key)] = def;
3986
- return acc;
3987
- }, {});
4088
+ const targetTable = tableSource ?? {
4089
+ type: "Table",
4090
+ name: relation.target.name,
4091
+ schema: relation.target.schema
3988
4092
  };
3989
- const targetSelection = buildTypedSelection(
3990
- relation.target.columns,
3991
- aliasPrefix,
3992
- targetColumns,
3993
- (key) => `Column '${key}' not found on relation '${relationName}'`
4093
+ const targetName = this.resolveTargetTableName(targetTable, relation);
4094
+ const condition = buildRelationJoinCondition(
4095
+ this.table,
4096
+ relation,
4097
+ extraCondition,
4098
+ rootAlias,
4099
+ targetName
3994
4100
  );
3995
- if (relation.type !== RelationKinds.BelongsToMany) {
3996
- const relationSelectionResult2 = this.selectColumns(state, hydration, targetSelection);
3997
- state = relationSelectionResult2.state;
3998
- hydration = relationSelectionResult2.hydration;
3999
- hydration = hydration.onRelationIncluded(
4000
- state,
4101
+ const joinNode = createJoinNode(joinKind, targetTable, condition, relationName);
4102
+ return this.astService(state).withJoin(joinNode);
4103
+ }
4104
+ astService(state) {
4105
+ return this.createQueryAstService(this.table, state);
4106
+ }
4107
+ resolveTargetTableName(target, relation) {
4108
+ if (target.type === "Table") {
4109
+ return target.alias ?? target.name;
4110
+ }
4111
+ if (target.type === "DerivedTable") {
4112
+ return target.alias;
4113
+ }
4114
+ if (target.type === "FunctionTable") {
4115
+ return target.alias ?? relation.target.name;
4116
+ }
4117
+ return relation.target.name;
4118
+ }
4119
+ };
4120
+
4121
+ // src/query-builder/relation-cte-builder.ts
4122
+ var RelationCteBuilder = class {
4123
+ constructor(table, createQueryAstService) {
4124
+ this.table = table;
4125
+ this.createQueryAstService = createQueryAstService;
4126
+ }
4127
+ createFilteredRelationCte(state, relationName, relation, predicate) {
4128
+ const cteName = this.generateUniqueCteName(state, relationName);
4129
+ if (!predicate) {
4130
+ throw new Error("Unable to build filter CTE without predicates.");
4131
+ }
4132
+ const columns = Object.keys(relation.target.columns).map((name) => ({
4133
+ type: "Column",
4134
+ table: relation.target.name,
4135
+ name
4136
+ }));
4137
+ const cteQuery = {
4138
+ type: "SelectQuery",
4139
+ from: { type: "Table", name: relation.target.name, schema: relation.target.schema },
4140
+ columns,
4141
+ joins: [],
4142
+ where: predicate
4143
+ };
4144
+ const nextState = this.astService(state).withCte(cteName, cteQuery);
4145
+ const tableNode3 = {
4146
+ type: "Table",
4147
+ name: cteName,
4148
+ alias: relation.target.name
4149
+ };
4150
+ return { state: nextState, table: tableNode3 };
4151
+ }
4152
+ generateUniqueCteName(state, relationName) {
4153
+ const existing = new Set((state.ast.ctes ?? []).map((cte) => cte.name));
4154
+ let candidate = `${relationName}__filtered`;
4155
+ let suffix = 1;
4156
+ while (existing.has(candidate)) {
4157
+ candidate = `${relationName}__filtered_${suffix}`;
4158
+ suffix += 1;
4159
+ }
4160
+ return candidate;
4161
+ }
4162
+ astService(state) {
4163
+ return this.createQueryAstService(this.table, state);
4164
+ }
4165
+ };
4166
+
4167
+ // src/query-builder/relation-include-strategies.ts
4168
+ var buildTypedSelection = (columns, prefix, keys, missingMsg) => {
4169
+ return keys.reduce((acc, key) => {
4170
+ const def = columns[key];
4171
+ if (!def) {
4172
+ throw new Error(missingMsg(key));
4173
+ }
4174
+ acc[makeRelationAlias(prefix, key)] = def;
4175
+ return acc;
4176
+ }, {});
4177
+ };
4178
+ var resolveTargetColumns = (relation, options) => {
4179
+ const requestedColumns = options?.columns?.length ? [...options.columns] : Object.keys(relation.target.columns);
4180
+ const targetPrimaryKey = findPrimaryKey(relation.target);
4181
+ if (!requestedColumns.includes(targetPrimaryKey)) {
4182
+ requestedColumns.push(targetPrimaryKey);
4183
+ }
4184
+ return requestedColumns;
4185
+ };
4186
+ var ensureRootForeignKeySelected = (context, relation) => {
4187
+ const fkColumn = context.rootTable.columns[relation.foreignKey];
4188
+ if (!fkColumn) {
4189
+ return { state: context.state, hydration: context.hydration };
4190
+ }
4191
+ const hasForeignKeySelected = context.state.ast.columns.some((col2) => {
4192
+ if (col2.type !== "Column") return false;
4193
+ const node = col2;
4194
+ const alias = node.alias ?? node.name;
4195
+ return alias === relation.foreignKey;
4196
+ });
4197
+ if (hasForeignKeySelected) {
4198
+ return { state: context.state, hydration: context.hydration };
4199
+ }
4200
+ return context.selectColumns(context.state, context.hydration, {
4201
+ [relation.foreignKey]: fkColumn
4202
+ });
4203
+ };
4204
+ var standardIncludeStrategy = (context) => {
4205
+ const relation = context.relation;
4206
+ let { state, hydration } = context;
4207
+ const fkSelectionResult = ensureRootForeignKeySelected(context, relation);
4208
+ state = fkSelectionResult.state;
4209
+ hydration = fkSelectionResult.hydration;
4210
+ const targetColumns = resolveTargetColumns(relation, context.options);
4211
+ const targetSelection = buildTypedSelection(
4212
+ relation.target.columns,
4213
+ context.aliasPrefix,
4214
+ targetColumns,
4215
+ (key) => `Column '${key}' not found on relation '${context.relationName}'`
4216
+ );
4217
+ const relationSelectionResult = context.selectColumns(state, hydration, targetSelection);
4218
+ state = relationSelectionResult.state;
4219
+ hydration = relationSelectionResult.hydration;
4220
+ hydration = hydration.onRelationIncluded(
4221
+ state,
4222
+ relation,
4223
+ context.relationName,
4224
+ context.aliasPrefix,
4225
+ targetColumns
4226
+ );
4227
+ return { state, hydration };
4228
+ };
4229
+ var belongsToManyStrategy = (context) => {
4230
+ const relation = context.relation;
4231
+ let { state, hydration } = context;
4232
+ const targetColumns = resolveTargetColumns(relation, context.options);
4233
+ const targetSelection = buildTypedSelection(
4234
+ relation.target.columns,
4235
+ context.aliasPrefix,
4236
+ targetColumns,
4237
+ (key) => `Column '${key}' not found on relation '${context.relationName}'`
4238
+ );
4239
+ const pivotAliasPrefix = context.options?.pivot?.aliasPrefix ?? `${context.aliasPrefix}_pivot`;
4240
+ const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
4241
+ const defaultPivotColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
4242
+ const pivotColumns = context.options?.pivot?.columns ? [...context.options.pivot.columns] : [...defaultPivotColumns];
4243
+ const pivotSelection = buildTypedSelection(
4244
+ relation.pivotTable.columns,
4245
+ pivotAliasPrefix,
4246
+ pivotColumns,
4247
+ (key) => `Column '${key}' not found on pivot table '${relation.pivotTable.name}'`
4248
+ );
4249
+ const combinedSelection = {
4250
+ ...targetSelection,
4251
+ ...pivotSelection
4252
+ };
4253
+ const relationSelectionResult = context.selectColumns(state, hydration, combinedSelection);
4254
+ state = relationSelectionResult.state;
4255
+ hydration = relationSelectionResult.hydration;
4256
+ hydration = hydration.onRelationIncluded(
4257
+ state,
4258
+ relation,
4259
+ context.relationName,
4260
+ context.aliasPrefix,
4261
+ targetColumns,
4262
+ { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
4263
+ );
4264
+ return { state, hydration };
4265
+ };
4266
+ var relationIncludeStrategies = {
4267
+ [RelationKinds.HasMany]: standardIncludeStrategy,
4268
+ [RelationKinds.HasOne]: standardIncludeStrategy,
4269
+ [RelationKinds.BelongsTo]: standardIncludeStrategy,
4270
+ [RelationKinds.BelongsToMany]: belongsToManyStrategy
4271
+ };
4272
+
4273
+ // src/query-builder/relation-service.ts
4274
+ var RelationService = class {
4275
+ /**
4276
+ * Creates a new RelationService instance
4277
+ * @param table - Table definition
4278
+ * @param state - Current query state
4279
+ * @param hydration - Hydration manager
4280
+ */
4281
+ constructor(table, state, hydration, createQueryAstService) {
4282
+ this.table = table;
4283
+ this.state = state;
4284
+ this.hydration = hydration;
4285
+ this.createQueryAstService = createQueryAstService;
4286
+ this.projectionHelper = new RelationProjectionHelper(
4287
+ table,
4288
+ (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
4289
+ );
4290
+ this.joinPlanner = new RelationJoinPlanner(table, createQueryAstService);
4291
+ this.cteBuilder = new RelationCteBuilder(table, createQueryAstService);
4292
+ }
4293
+ projectionHelper;
4294
+ joinPlanner;
4295
+ cteBuilder;
4296
+ /**
4297
+ * Joins a relation to the query
4298
+ * @param relationName - Name of the relation to join
4299
+ * @param joinKind - Type of join to use
4300
+ * @param extraCondition - Additional join condition
4301
+ * @returns Relation result with updated state and hydration
4302
+ */
4303
+ joinRelation(relationName, joinKind, extraCondition, tableSource) {
4304
+ const relation = this.getRelation(relationName);
4305
+ const nextState = this.joinPlanner.withJoin(
4306
+ this.state,
4307
+ relationName,
4308
+ relation,
4309
+ joinKind,
4310
+ extraCondition,
4311
+ tableSource
4312
+ );
4313
+ return { state: nextState, hydration: this.hydration };
4314
+ }
4315
+ /**
4316
+ * Matches records based on a relation with an optional predicate
4317
+ * @param relationName - Name of the relation to match
4318
+ * @param predicate - Optional predicate expression
4319
+ * @returns Relation result with updated state and hydration
4320
+ */
4321
+ match(relationName, predicate) {
4322
+ const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
4323
+ const pk = findPrimaryKey(this.table);
4324
+ const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
4325
+ const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
4326
+ const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
4327
+ return { state: nextState, hydration: joined.hydration };
4328
+ }
4329
+ /**
4330
+ * Includes a relation in the query result
4331
+ * @param relationName - Name of the relation to include
4332
+ * @param options - Options for relation inclusion
4333
+ * @returns Relation result with updated state and hydration
4334
+ */
4335
+ include(relationName, options) {
4336
+ let state = this.state;
4337
+ let hydration = this.hydration;
4338
+ const relation = this.getRelation(relationName);
4339
+ const aliasPrefix = options?.aliasPrefix ?? relationName;
4340
+ const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
4341
+ const { selfFilters, crossFilters } = splitFilterExpressions(
4342
+ options?.filter,
4343
+ /* @__PURE__ */ new Set([relation.target.name])
4344
+ );
4345
+ const canUseCte = !alreadyJoined && selfFilters.length > 0;
4346
+ const joinFilters = [...crossFilters];
4347
+ if (!canUseCte) {
4348
+ joinFilters.push(...selfFilters);
4349
+ }
4350
+ const joinCondition = this.combineWithAnd(joinFilters);
4351
+ let tableSourceOverride;
4352
+ if (canUseCte) {
4353
+ const predicate = this.combineWithAnd(selfFilters);
4354
+ const cteInfo = this.cteBuilder.createFilteredRelationCte(
4355
+ state,
4356
+ relationName,
4001
4357
  relation,
4358
+ predicate
4359
+ );
4360
+ state = cteInfo.state;
4361
+ tableSourceOverride = cteInfo.table;
4362
+ }
4363
+ if (!alreadyJoined) {
4364
+ state = this.joinPlanner.withJoin(
4365
+ state,
4002
4366
  relationName,
4003
- aliasPrefix,
4004
- targetColumns
4367
+ relation,
4368
+ options?.joinKind ?? JOIN_KINDS.LEFT,
4369
+ joinCondition,
4370
+ tableSourceOverride
4005
4371
  );
4006
- return { state, hydration };
4007
- }
4008
- const many = relation;
4009
- const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
4010
- const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
4011
- const pivotColumns = options?.pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
4012
- const pivotSelection = buildTypedSelection(
4013
- many.pivotTable.columns,
4014
- pivotAliasPrefix,
4015
- pivotColumns,
4016
- (key) => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
4017
- );
4018
- const combinedSelection = {
4019
- ...targetSelection,
4020
- ...pivotSelection
4021
- };
4022
- const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
4023
- state = relationSelectionResult.state;
4024
- hydration = relationSelectionResult.hydration;
4025
- hydration = hydration.onRelationIncluded(
4372
+ }
4373
+ const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
4374
+ state = projectionResult.state;
4375
+ hydration = projectionResult.hydration;
4376
+ const strategy = relationIncludeStrategies[relation.type];
4377
+ const result = strategy({
4378
+ rootTable: this.table,
4026
4379
  state,
4380
+ hydration,
4027
4381
  relation,
4028
4382
  relationName,
4029
4383
  aliasPrefix,
4030
- targetColumns,
4031
- { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
4032
- );
4033
- return { state, hydration };
4384
+ options,
4385
+ selectColumns: (nextState, nextHydration, columns) => this.selectColumns(nextState, nextHydration, columns)
4386
+ });
4387
+ return { state: result.state, hydration: result.hydration };
4034
4388
  }
4035
4389
  /**
4036
4390
  * Applies relation correlation to a query AST
@@ -4051,37 +4405,6 @@ var RelationService = class {
4051
4405
  where: whereInSubquery
4052
4406
  };
4053
4407
  }
4054
- /**
4055
- * Creates a join node for a relation
4056
- * @param state - Current query state
4057
- * @param relationName - Name of the relation
4058
- * @param joinKind - Type of join to use
4059
- * @param extraCondition - Additional join condition
4060
- * @returns Updated query state with join
4061
- */
4062
- withJoin(state, relationName, joinKind, extraCondition) {
4063
- const relation = this.getRelation(relationName);
4064
- const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4065
- if (relation.type === RelationKinds.BelongsToMany) {
4066
- const joins = buildBelongsToManyJoins(
4067
- this.table,
4068
- relationName,
4069
- relation,
4070
- joinKind,
4071
- extraCondition,
4072
- rootAlias
4073
- );
4074
- return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
4075
- }
4076
- const condition = buildRelationJoinCondition(this.table, relation, extraCondition, rootAlias);
4077
- const joinNode = createJoinNode(
4078
- joinKind,
4079
- { type: "Table", name: relation.target.name, schema: relation.target.schema },
4080
- condition,
4081
- relationName
4082
- );
4083
- return this.astService(state).withJoin(joinNode);
4084
- }
4085
4408
  /**
4086
4409
  * Selects columns for a relation
4087
4410
  * @param state - Current query state
@@ -4096,6 +4419,15 @@ var RelationService = class {
4096
4419
  hydration: hydration.onColumnsSelected(nextState, addedColumns)
4097
4420
  };
4098
4421
  }
4422
+ combineWithAnd(expressions) {
4423
+ if (expressions.length === 0) return void 0;
4424
+ if (expressions.length === 1) return expressions[0];
4425
+ return {
4426
+ type: "LogicalExpression",
4427
+ operator: "AND",
4428
+ operands: expressions
4429
+ };
4430
+ }
4099
4431
  /**
4100
4432
  * Gets a relation definition by name
4101
4433
  * @param relationName - Name of the relation
@@ -4384,8 +4716,52 @@ var hasEntityMeta = (entity) => {
4384
4716
  return Boolean(getEntityMeta(entity));
4385
4717
  };
4386
4718
 
4387
- // src/orm/relations/has-many.ts
4719
+ // src/orm/entity-hydration.ts
4388
4720
  var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
4721
+ var populateHydrationCache = (entity, row, meta) => {
4722
+ for (const relationName of Object.keys(meta.table.relations)) {
4723
+ const relation = meta.table.relations[relationName];
4724
+ const data = row[relationName];
4725
+ if (relation.type === RelationKinds.HasOne) {
4726
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
4727
+ const rootValue = entity[localKey];
4728
+ if (rootValue === void 0 || rootValue === null) continue;
4729
+ if (!data || typeof data !== "object") continue;
4730
+ const cache = /* @__PURE__ */ new Map();
4731
+ cache.set(toKey2(rootValue), data);
4732
+ meta.relationHydration.set(relationName, cache);
4733
+ meta.relationCache.set(relationName, Promise.resolve(cache));
4734
+ continue;
4735
+ }
4736
+ if (!Array.isArray(data)) continue;
4737
+ if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
4738
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
4739
+ const rootValue = entity[localKey];
4740
+ if (rootValue === void 0 || rootValue === null) continue;
4741
+ const cache = /* @__PURE__ */ new Map();
4742
+ cache.set(toKey2(rootValue), data);
4743
+ meta.relationHydration.set(relationName, cache);
4744
+ meta.relationCache.set(relationName, Promise.resolve(cache));
4745
+ continue;
4746
+ }
4747
+ if (relation.type === RelationKinds.BelongsTo) {
4748
+ const targetKey = relation.localKey || findPrimaryKey(relation.target);
4749
+ const cache = /* @__PURE__ */ new Map();
4750
+ for (const item of data) {
4751
+ const pkValue = item[targetKey];
4752
+ if (pkValue === void 0 || pkValue === null) continue;
4753
+ cache.set(toKey2(pkValue), item);
4754
+ }
4755
+ if (cache.size) {
4756
+ meta.relationHydration.set(relationName, cache);
4757
+ meta.relationCache.set(relationName, Promise.resolve(cache));
4758
+ }
4759
+ }
4760
+ }
4761
+ };
4762
+
4763
+ // src/orm/relations/has-many.ts
4764
+ var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
4389
4765
  var hideInternal = (obj, keys) => {
4390
4766
  for (const key of keys) {
4391
4767
  Object.defineProperty(obj, key, {
@@ -4419,13 +4795,13 @@ var DefaultHasManyCollection = class {
4419
4795
  this.loader = loader;
4420
4796
  this.createEntity = createEntity;
4421
4797
  this.localKey = localKey;
4422
- this.loaded = false;
4423
- this.items = [];
4424
- this.added = /* @__PURE__ */ new Set();
4425
- this.removed = /* @__PURE__ */ new Set();
4426
4798
  hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
4427
4799
  this.hydrateFromCache();
4428
4800
  }
4801
+ loaded = false;
4802
+ items = [];
4803
+ added = /* @__PURE__ */ new Set();
4804
+ removed = /* @__PURE__ */ new Set();
4429
4805
  /**
4430
4806
  * Loads the related entities if not already loaded.
4431
4807
  * @returns Promise resolving to the array of child entities
@@ -4433,7 +4809,7 @@ var DefaultHasManyCollection = class {
4433
4809
  async load() {
4434
4810
  if (this.loaded) return this.items;
4435
4811
  const map = await this.loader();
4436
- const key = toKey2(this.root[this.localKey]);
4812
+ const key = toKey3(this.root[this.localKey]);
4437
4813
  const rows = map.get(key) ?? [];
4438
4814
  this.items = rows.map((row) => this.createEntity(row));
4439
4815
  this.loaded = true;
@@ -4446,6 +4822,18 @@ var DefaultHasManyCollection = class {
4446
4822
  getItems() {
4447
4823
  return this.items;
4448
4824
  }
4825
+ /**
4826
+ * Array-compatible length for testing frameworks.
4827
+ */
4828
+ get length() {
4829
+ return this.items.length;
4830
+ }
4831
+ /**
4832
+ * Enables iteration over the collection like an array.
4833
+ */
4834
+ [Symbol.iterator]() {
4835
+ return this.items[Symbol.iterator]();
4836
+ }
4449
4837
  /**
4450
4838
  * Adds a new child entity to the collection.
4451
4839
  * @param data - Partial data for the new entity
@@ -4533,7 +4921,7 @@ var DefaultHasManyCollection = class {
4533
4921
  };
4534
4922
 
4535
4923
  // src/orm/relations/has-one.ts
4536
- var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
4924
+ var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
4537
4925
  var hideInternal2 = (obj, keys) => {
4538
4926
  for (const key of keys) {
4539
4927
  Object.defineProperty(obj, key, {
@@ -4566,8 +4954,6 @@ var DefaultHasOneReference = class {
4566
4954
  this.loader = loader;
4567
4955
  this.createEntity = createEntity;
4568
4956
  this.localKey = localKey;
4569
- this.loaded = false;
4570
- this.current = null;
4571
4957
  hideInternal2(this, [
4572
4958
  "ctx",
4573
4959
  "meta",
@@ -4581,6 +4967,8 @@ var DefaultHasOneReference = class {
4581
4967
  ]);
4582
4968
  this.populateFromHydrationCache();
4583
4969
  }
4970
+ loaded = false;
4971
+ current = null;
4584
4972
  async load() {
4585
4973
  if (this.loaded) return this.current;
4586
4974
  const map = await this.loader();
@@ -4589,7 +4977,7 @@ var DefaultHasOneReference = class {
4589
4977
  this.loaded = true;
4590
4978
  return this.current;
4591
4979
  }
4592
- const row = map.get(toKey3(keyValue));
4980
+ const row = map.get(toKey4(keyValue));
4593
4981
  this.current = row ? this.createEntity(row) : null;
4594
4982
  this.loaded = true;
4595
4983
  return this.current;
@@ -4661,7 +5049,7 @@ var DefaultHasOneReference = class {
4661
5049
  };
4662
5050
 
4663
5051
  // src/orm/relations/belongs-to.ts
4664
- var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
5052
+ var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
4665
5053
  var hideInternal3 = (obj, keys) => {
4666
5054
  for (const key of keys) {
4667
5055
  Object.defineProperty(obj, key, {
@@ -4694,11 +5082,11 @@ var DefaultBelongsToReference = class {
4694
5082
  this.loader = loader;
4695
5083
  this.createEntity = createEntity;
4696
5084
  this.targetKey = targetKey;
4697
- this.loaded = false;
4698
- this.current = null;
4699
5085
  hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
4700
5086
  this.populateFromHydrationCache();
4701
5087
  }
5088
+ loaded = false;
5089
+ current = null;
4702
5090
  async load() {
4703
5091
  if (this.loaded) return this.current;
4704
5092
  const map = await this.loader();
@@ -4706,7 +5094,7 @@ var DefaultBelongsToReference = class {
4706
5094
  if (fkValue === null || fkValue === void 0) {
4707
5095
  this.current = null;
4708
5096
  } else {
4709
- const row = map.get(toKey4(fkValue));
5097
+ const row = map.get(toKey5(fkValue));
4710
5098
  this.current = row ? this.createEntity(row) : null;
4711
5099
  }
4712
5100
  this.loaded = true;
@@ -4763,7 +5151,7 @@ var DefaultBelongsToReference = class {
4763
5151
  };
4764
5152
 
4765
5153
  // src/orm/relations/many-to-many.ts
4766
- var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
5154
+ var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
4767
5155
  var hideInternal4 = (obj, keys) => {
4768
5156
  for (const key of keys) {
4769
5157
  Object.defineProperty(obj, key, {
@@ -4796,11 +5184,11 @@ var DefaultManyToManyCollection = class {
4796
5184
  this.loader = loader;
4797
5185
  this.createEntity = createEntity;
4798
5186
  this.localKey = localKey;
4799
- this.loaded = false;
4800
- this.items = [];
4801
5187
  hideInternal4(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
4802
5188
  this.hydrateFromCache();
4803
5189
  }
5190
+ loaded = false;
5191
+ items = [];
4804
5192
  /**
4805
5193
  * Loads the collection items if not already loaded.
4806
5194
  * @returns A promise that resolves to the array of target entities.
@@ -4808,7 +5196,7 @@ var DefaultManyToManyCollection = class {
4808
5196
  async load() {
4809
5197
  if (this.loaded) return this.items;
4810
5198
  const map = await this.loader();
4811
- const key = toKey5(this.root[this.localKey]);
5199
+ const key = toKey6(this.root[this.localKey]);
4812
5200
  const rows = map.get(key) ?? [];
4813
5201
  this.items = rows.map((row) => {
4814
5202
  const entity = this.createEntity(row);
@@ -4827,6 +5215,18 @@ var DefaultManyToManyCollection = class {
4827
5215
  getItems() {
4828
5216
  return this.items;
4829
5217
  }
5218
+ /**
5219
+ * Array-compatible length for testing frameworks.
5220
+ */
5221
+ get length() {
5222
+ return this.items.length;
5223
+ }
5224
+ /**
5225
+ * Enables iteration over the collection like an array.
5226
+ */
5227
+ [Symbol.iterator]() {
5228
+ return this.items[Symbol.iterator]();
5229
+ }
4830
5230
  /**
4831
5231
  * Attaches an entity to the collection.
4832
5232
  * Registers an 'attach' change in the entity context.
@@ -4878,15 +5278,15 @@ var DefaultManyToManyCollection = class {
4878
5278
  */
4879
5279
  async syncByIds(ids) {
4880
5280
  await this.load();
4881
- const normalized = new Set(ids.map((id) => toKey5(id)));
4882
- const currentIds = new Set(this.items.map((item) => toKey5(this.extractId(item))));
5281
+ const normalized = new Set(ids.map((id) => toKey6(id)));
5282
+ const currentIds = new Set(this.items.map((item) => toKey6(this.extractId(item))));
4883
5283
  for (const id of normalized) {
4884
5284
  if (!currentIds.has(id)) {
4885
5285
  this.attach(id);
4886
5286
  }
4887
5287
  }
4888
5288
  for (const item of [...this.items]) {
4889
- const itemId = toKey5(this.extractId(item));
5289
+ const itemId = toKey6(this.extractId(item));
4890
5290
  if (!normalized.has(itemId)) {
4891
5291
  this.detach(item);
4892
5292
  }
@@ -4933,11 +5333,28 @@ var DefaultManyToManyCollection = class {
4933
5333
  }
4934
5334
  };
4935
5335
 
4936
- // src/orm/lazy-batch.ts
4937
- var selectAllColumns = (table) => Object.entries(table.columns).reduce((acc, [name, def]) => {
4938
- acc[name] = def;
4939
- return acc;
4940
- }, {});
5336
+ // src/orm/lazy-batch/shared.ts
5337
+ var hasColumns = (columns) => Boolean(columns && columns.length > 0);
5338
+ var buildColumnSelection = (table, columns, missingMsg) => {
5339
+ return columns.reduce((acc, column) => {
5340
+ const def = table.columns[column];
5341
+ if (!def) {
5342
+ throw new Error(missingMsg(column));
5343
+ }
5344
+ acc[column] = def;
5345
+ return acc;
5346
+ }, {});
5347
+ };
5348
+ var filterRow = (row, columns) => {
5349
+ const filtered = {};
5350
+ for (const column of columns) {
5351
+ if (column in row) {
5352
+ filtered[column] = row[column];
5353
+ }
5354
+ }
5355
+ return filtered;
5356
+ };
5357
+ var filterRows = (rows, columns) => rows.map((row) => filterRow(row, columns));
4941
5358
  var rowsFromResults = (results) => {
4942
5359
  const rows = [];
4943
5360
  for (const result of results) {
@@ -4957,7 +5374,7 @@ var executeQuery = async (ctx, qb) => {
4957
5374
  const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
4958
5375
  return rowsFromResults(results);
4959
5376
  };
4960
- var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
5377
+ var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
4961
5378
  var collectKeysFromRoots = (roots, key) => {
4962
5379
  const collected = /* @__PURE__ */ new Set();
4963
5380
  for (const tracked of roots) {
@@ -4969,9 +5386,12 @@ var collectKeysFromRoots = (roots, key) => {
4969
5386
  return collected;
4970
5387
  };
4971
5388
  var buildInListValues = (keys) => Array.from(keys);
4972
- var fetchRowsForKeys = async (ctx, table, column, keys) => {
4973
- const qb = new SelectQueryBuilder(table).select(selectAllColumns(table));
4974
- qb.where(inList(column, buildInListValues(keys)));
5389
+ var fetchRowsForKeys = async (ctx, table, column, keys, selection, filter) => {
5390
+ let qb = new SelectQueryBuilder(table).select(selection);
5391
+ qb = qb.where(inList(column, buildInListValues(keys)));
5392
+ if (filter) {
5393
+ qb = qb.where(filter);
5394
+ }
4975
5395
  return executeQuery(ctx, qb);
4976
5396
  };
4977
5397
  var groupRowsByMany = (rows, keyColumn) => {
@@ -4979,7 +5399,7 @@ var groupRowsByMany = (rows, keyColumn) => {
4979
5399
  for (const row of rows) {
4980
5400
  const value = row[keyColumn];
4981
5401
  if (value === null || value === void 0) continue;
4982
- const key = toKey6(value);
5402
+ const key = toKey7(value);
4983
5403
  const bucket = grouped.get(key) ?? [];
4984
5404
  bucket.push(row);
4985
5405
  grouped.set(key, bucket);
@@ -4991,14 +5411,16 @@ var groupRowsByUnique = (rows, keyColumn) => {
4991
5411
  for (const row of rows) {
4992
5412
  const value = row[keyColumn];
4993
5413
  if (value === null || value === void 0) continue;
4994
- const key = toKey6(value);
5414
+ const key = toKey7(value);
4995
5415
  if (!lookup.has(key)) {
4996
5416
  lookup.set(key, row);
4997
5417
  }
4998
5418
  }
4999
5419
  return lookup;
5000
5420
  };
5001
- var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
5421
+
5422
+ // src/orm/lazy-batch/has-many.ts
5423
+ var loadHasManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5002
5424
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5003
5425
  const roots = ctx.getEntitiesForTable(rootTable);
5004
5426
  const keys = collectKeysFromRoots(roots, localKey);
@@ -5007,10 +5429,32 @@ var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
5007
5429
  }
5008
5430
  const fkColumn = relation.target.columns[relation.foreignKey];
5009
5431
  if (!fkColumn) return /* @__PURE__ */ new Map();
5010
- const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys);
5011
- return groupRowsByMany(rows, relation.foreignKey);
5432
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5433
+ const targetPrimaryKey = findPrimaryKey(relation.target);
5434
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5435
+ if (!selectedColumns.includes(targetPrimaryKey)) {
5436
+ selectedColumns.push(targetPrimaryKey);
5437
+ }
5438
+ const queryColumns = new Set(selectedColumns);
5439
+ queryColumns.add(relation.foreignKey);
5440
+ const selection = buildColumnSelection(
5441
+ relation.target,
5442
+ Array.from(queryColumns),
5443
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5444
+ );
5445
+ const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys, selection, options?.filter);
5446
+ const grouped = groupRowsByMany(rows, relation.foreignKey);
5447
+ if (!requestedColumns) return grouped;
5448
+ const visibleColumns = new Set(selectedColumns);
5449
+ const filtered = /* @__PURE__ */ new Map();
5450
+ for (const [key, bucket] of grouped.entries()) {
5451
+ filtered.set(key, filterRows(bucket, visibleColumns));
5452
+ }
5453
+ return filtered;
5012
5454
  };
5013
- var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
5455
+
5456
+ // src/orm/lazy-batch/has-one.ts
5457
+ var loadHasOneRelation = async (ctx, rootTable, relationName, relation, options) => {
5014
5458
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5015
5459
  const roots = ctx.getEntitiesForTable(rootTable);
5016
5460
  const keys = collectKeysFromRoots(roots, localKey);
@@ -5019,22 +5463,102 @@ var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
5019
5463
  }
5020
5464
  const fkColumn = relation.target.columns[relation.foreignKey];
5021
5465
  if (!fkColumn) return /* @__PURE__ */ new Map();
5022
- const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys);
5023
- return groupRowsByUnique(rows, relation.foreignKey);
5466
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5467
+ const targetPrimaryKey = findPrimaryKey(relation.target);
5468
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5469
+ if (!selectedColumns.includes(targetPrimaryKey)) {
5470
+ selectedColumns.push(targetPrimaryKey);
5471
+ }
5472
+ const queryColumns = new Set(selectedColumns);
5473
+ queryColumns.add(relation.foreignKey);
5474
+ const selection = buildColumnSelection(
5475
+ relation.target,
5476
+ Array.from(queryColumns),
5477
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5478
+ );
5479
+ const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys, selection, options?.filter);
5480
+ const grouped = groupRowsByUnique(rows, relation.foreignKey);
5481
+ if (!requestedColumns) return grouped;
5482
+ const visibleColumns = new Set(selectedColumns);
5483
+ const filtered = /* @__PURE__ */ new Map();
5484
+ for (const [key, row] of grouped.entries()) {
5485
+ filtered.set(key, filterRow(row, visibleColumns));
5486
+ }
5487
+ return filtered;
5024
5488
  };
5025
- var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
5489
+
5490
+ // src/orm/lazy-batch/belongs-to.ts
5491
+ var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, options) => {
5026
5492
  const roots = ctx.getEntitiesForTable(rootTable);
5027
- const foreignKeys = collectKeysFromRoots(roots, relation.foreignKey);
5493
+ const getForeignKeys = () => collectKeysFromRoots(roots, relation.foreignKey);
5494
+ let foreignKeys = getForeignKeys();
5495
+ if (!foreignKeys.size) {
5496
+ const pkName = findPrimaryKey(rootTable);
5497
+ const pkColumn2 = rootTable.columns[pkName];
5498
+ const fkColumn = rootTable.columns[relation.foreignKey];
5499
+ if (pkColumn2 && fkColumn) {
5500
+ const missingKeys = /* @__PURE__ */ new Set();
5501
+ const entityByPk = /* @__PURE__ */ new Map();
5502
+ for (const tracked of roots) {
5503
+ const entity = tracked.entity;
5504
+ const pkValue = entity[pkName];
5505
+ if (pkValue === void 0 || pkValue === null) continue;
5506
+ const fkValue = entity[relation.foreignKey];
5507
+ if (fkValue === void 0 || fkValue === null) {
5508
+ missingKeys.add(pkValue);
5509
+ entityByPk.set(pkValue, entity);
5510
+ }
5511
+ }
5512
+ if (missingKeys.size) {
5513
+ const selection2 = buildColumnSelection(
5514
+ rootTable,
5515
+ [pkName, relation.foreignKey],
5516
+ (column) => `Column '${column}' not found on table '${rootTable.name}'`
5517
+ );
5518
+ const keyRows = await fetchRowsForKeys(ctx, rootTable, pkColumn2, missingKeys, selection2);
5519
+ for (const row of keyRows) {
5520
+ const pkValue = row[pkName];
5521
+ if (pkValue === void 0 || pkValue === null) continue;
5522
+ const entity = entityByPk.get(pkValue);
5523
+ if (!entity) continue;
5524
+ const fkValue = row[relation.foreignKey];
5525
+ if (fkValue !== void 0 && fkValue !== null) {
5526
+ entity[relation.foreignKey] = fkValue;
5527
+ }
5528
+ }
5529
+ foreignKeys = getForeignKeys();
5530
+ }
5531
+ }
5532
+ }
5028
5533
  if (!foreignKeys.size) {
5029
5534
  return /* @__PURE__ */ new Map();
5030
5535
  }
5031
5536
  const targetKey = relation.localKey || findPrimaryKey(relation.target);
5032
5537
  const pkColumn = relation.target.columns[targetKey];
5033
5538
  if (!pkColumn) return /* @__PURE__ */ new Map();
5034
- const rows = await fetchRowsForKeys(ctx, relation.target, pkColumn, foreignKeys);
5035
- return groupRowsByUnique(rows, targetKey);
5539
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5540
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5541
+ if (!selectedColumns.includes(targetKey)) {
5542
+ selectedColumns.push(targetKey);
5543
+ }
5544
+ const selection = buildColumnSelection(
5545
+ relation.target,
5546
+ selectedColumns,
5547
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5548
+ );
5549
+ const rows = await fetchRowsForKeys(ctx, relation.target, pkColumn, foreignKeys, selection, options?.filter);
5550
+ const grouped = groupRowsByUnique(rows, targetKey);
5551
+ if (!requestedColumns) return grouped;
5552
+ const visibleColumns = new Set(selectedColumns);
5553
+ const filtered = /* @__PURE__ */ new Map();
5554
+ for (const [key, row] of grouped.entries()) {
5555
+ filtered.set(key, filterRow(row, visibleColumns));
5556
+ }
5557
+ return filtered;
5036
5558
  };
5037
- var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation) => {
5559
+
5560
+ // src/orm/lazy-batch/belongs-to-many.ts
5561
+ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5038
5562
  const rootKey = relation.localKey || findPrimaryKey(rootTable);
5039
5563
  const roots = ctx.getEntitiesForTable(rootTable);
5040
5564
  const rootIds = collectKeysFromRoots(roots, rootKey);
@@ -5043,21 +5567,41 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5043
5567
  }
5044
5568
  const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
5045
5569
  if (!pivotColumn) return /* @__PURE__ */ new Map();
5046
- const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds);
5570
+ const pivotColumnsRequested = hasColumns(options?.pivot?.columns) ? [...options.pivot.columns] : void 0;
5571
+ const useIncludeDefaults = options !== void 0;
5572
+ let pivotSelectedColumns;
5573
+ if (pivotColumnsRequested) {
5574
+ pivotSelectedColumns = [...pivotColumnsRequested];
5575
+ } else if (useIncludeDefaults) {
5576
+ const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
5577
+ pivotSelectedColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
5578
+ } else {
5579
+ pivotSelectedColumns = Object.keys(relation.pivotTable.columns);
5580
+ }
5581
+ const pivotQueryColumns = new Set(pivotSelectedColumns);
5582
+ pivotQueryColumns.add(relation.pivotForeignKeyToRoot);
5583
+ pivotQueryColumns.add(relation.pivotForeignKeyToTarget);
5584
+ const pivotSelection = buildColumnSelection(
5585
+ relation.pivotTable,
5586
+ Array.from(pivotQueryColumns),
5587
+ (column) => `Column '${column}' not found on pivot table '${relation.pivotTable.name}'`
5588
+ );
5589
+ const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds, pivotSelection);
5047
5590
  const rootLookup = /* @__PURE__ */ new Map();
5048
5591
  const targetIds = /* @__PURE__ */ new Set();
5592
+ const pivotVisibleColumns = new Set(pivotSelectedColumns);
5049
5593
  for (const pivot of pivotRows) {
5050
5594
  const rootValue = pivot[relation.pivotForeignKeyToRoot];
5051
5595
  const targetValue = pivot[relation.pivotForeignKeyToTarget];
5052
5596
  if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
5053
5597
  continue;
5054
5598
  }
5055
- const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
5599
+ const bucket = rootLookup.get(toKey7(rootValue)) ?? [];
5056
5600
  bucket.push({
5057
5601
  targetId: targetValue,
5058
- pivot: { ...pivot }
5602
+ pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
5059
5603
  });
5060
- rootLookup.set(toKey6(rootValue), bucket);
5604
+ rootLookup.set(toKey7(rootValue), bucket);
5061
5605
  targetIds.add(targetValue);
5062
5606
  }
5063
5607
  if (!targetIds.size) {
@@ -5066,16 +5610,34 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5066
5610
  const targetKey = relation.targetKey || findPrimaryKey(relation.target);
5067
5611
  const targetPkColumn = relation.target.columns[targetKey];
5068
5612
  if (!targetPkColumn) return /* @__PURE__ */ new Map();
5069
- const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds);
5613
+ const targetRequestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5614
+ const targetSelectedColumns = targetRequestedColumns ? [...targetRequestedColumns] : Object.keys(relation.target.columns);
5615
+ if (!targetSelectedColumns.includes(targetKey)) {
5616
+ targetSelectedColumns.push(targetKey);
5617
+ }
5618
+ const targetSelection = buildColumnSelection(
5619
+ relation.target,
5620
+ targetSelectedColumns,
5621
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5622
+ );
5623
+ const targetRows = await fetchRowsForKeys(
5624
+ ctx,
5625
+ relation.target,
5626
+ targetPkColumn,
5627
+ targetIds,
5628
+ targetSelection,
5629
+ options?.filter
5630
+ );
5070
5631
  const targetMap = groupRowsByUnique(targetRows, targetKey);
5632
+ const targetVisibleColumns = new Set(targetSelectedColumns);
5071
5633
  const result = /* @__PURE__ */ new Map();
5072
5634
  for (const [rootId, entries] of rootLookup.entries()) {
5073
5635
  const bucket = [];
5074
5636
  for (const entry of entries) {
5075
- const targetRow = targetMap.get(toKey6(entry.targetId));
5637
+ const targetRow = targetMap.get(toKey7(entry.targetId));
5076
5638
  if (!targetRow) continue;
5077
5639
  bucket.push({
5078
- ...targetRow,
5640
+ ...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
5079
5641
  _pivot: entry.pivot
5080
5642
  });
5081
5643
  }
@@ -5084,7 +5646,7 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5084
5646
  return result;
5085
5647
  };
5086
5648
 
5087
- // src/orm/entity.ts
5649
+ // src/orm/entity-relation-cache.ts
5088
5650
  var relationLoaderCache = (meta, relationName, factory) => {
5089
5651
  if (meta.relationCache.has(relationName)) {
5090
5652
  return meta.relationCache.get(relationName);
@@ -5105,200 +5667,147 @@ var relationLoaderCache = (meta, relationName, factory) => {
5105
5667
  }
5106
5668
  return promise;
5107
5669
  };
5108
- var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
5109
- const target = { ...row };
5110
- const meta = {
5111
- ctx,
5112
- table,
5113
- lazyRelations: [...lazyRelations],
5114
- relationCache: /* @__PURE__ */ new Map(),
5115
- relationHydration: /* @__PURE__ */ new Map(),
5116
- relationWrappers: /* @__PURE__ */ new Map()
5117
- };
5118
- Object.defineProperty(target, ENTITY_META, {
5119
- value: meta,
5120
- enumerable: false,
5121
- writable: false
5122
- });
5123
- const handler = {
5124
- get(targetObj, prop, receiver) {
5125
- if (prop === ENTITY_META) {
5126
- return meta;
5670
+
5671
+ // src/orm/entity-relations.ts
5672
+ var proxifyRelationWrapper = (wrapper) => {
5673
+ return new Proxy(wrapper, {
5674
+ get(target, prop, receiver) {
5675
+ if (typeof prop === "symbol") {
5676
+ return Reflect.get(target, prop, receiver);
5127
5677
  }
5128
- if (prop === "$load") {
5129
- return async (relationName) => {
5130
- const wrapper = getRelationWrapper(meta, relationName, receiver);
5131
- if (wrapper && typeof wrapper.load === "function") {
5132
- return wrapper.load();
5133
- }
5134
- return void 0;
5135
- };
5678
+ if (prop in target) {
5679
+ return Reflect.get(target, prop, receiver);
5136
5680
  }
5137
- if (typeof prop === "string" && table.relations[prop]) {
5138
- return getRelationWrapper(meta, prop, receiver);
5681
+ const getItems = target.getItems;
5682
+ if (typeof getItems === "function") {
5683
+ const items = getItems.call(target);
5684
+ if (items && prop in items) {
5685
+ const propName = prop;
5686
+ const value = items[propName];
5687
+ return typeof value === "function" ? value.bind(items) : value;
5688
+ }
5139
5689
  }
5140
- return Reflect.get(targetObj, prop, receiver);
5690
+ const getRef = target.get;
5691
+ if (typeof getRef === "function") {
5692
+ const current = getRef.call(target);
5693
+ if (current && prop in current) {
5694
+ const propName = prop;
5695
+ const value = current[propName];
5696
+ return typeof value === "function" ? value.bind(current) : value;
5697
+ }
5698
+ }
5699
+ return void 0;
5141
5700
  },
5142
- set(targetObj, prop, value, receiver) {
5143
- const result = Reflect.set(targetObj, prop, value, receiver);
5144
- if (typeof prop === "string" && table.columns[prop]) {
5145
- ctx.markDirty(receiver);
5701
+ set(target, prop, value, receiver) {
5702
+ if (typeof prop === "symbol") {
5703
+ return Reflect.set(target, prop, value, receiver);
5146
5704
  }
5147
- return result;
5148
- }
5149
- };
5150
- const proxy = new Proxy(target, handler);
5151
- populateHydrationCache(proxy, row, meta);
5152
- return proxy;
5153
- };
5154
- var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
5155
- const pkName = findPrimaryKey(table);
5156
- const pkValue = row[pkName];
5157
- if (pkValue !== void 0 && pkValue !== null) {
5158
- const tracked = ctx.getEntity(table, pkValue);
5159
- if (tracked) return tracked;
5160
- }
5161
- const entity = createEntityProxy(ctx, table, row, lazyRelations);
5162
- if (pkValue !== void 0 && pkValue !== null) {
5163
- ctx.trackManaged(table, pkValue, entity);
5164
- } else {
5165
- ctx.trackNew(table, entity);
5166
- }
5167
- return entity;
5168
- };
5169
- var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
5170
- var populateHydrationCache = (entity, row, meta) => {
5171
- for (const relationName of Object.keys(meta.table.relations)) {
5172
- const relation = meta.table.relations[relationName];
5173
- const data = row[relationName];
5174
- if (relation.type === RelationKinds.HasOne) {
5175
- const localKey = relation.localKey || findPrimaryKey(meta.table);
5176
- const rootValue = entity[localKey];
5177
- if (rootValue === void 0 || rootValue === null) continue;
5178
- if (!data || typeof data !== "object") continue;
5179
- const cache = /* @__PURE__ */ new Map();
5180
- cache.set(toKey7(rootValue), data);
5181
- meta.relationHydration.set(relationName, cache);
5182
- meta.relationCache.set(relationName, Promise.resolve(cache));
5183
- continue;
5184
- }
5185
- if (!Array.isArray(data)) continue;
5186
- if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
5187
- const localKey = relation.localKey || findPrimaryKey(meta.table);
5188
- const rootValue = entity[localKey];
5189
- if (rootValue === void 0 || rootValue === null) continue;
5190
- const cache = /* @__PURE__ */ new Map();
5191
- cache.set(toKey7(rootValue), data);
5192
- meta.relationHydration.set(relationName, cache);
5193
- meta.relationCache.set(relationName, Promise.resolve(cache));
5194
- continue;
5195
- }
5196
- if (relation.type === RelationKinds.BelongsTo) {
5197
- const targetKey = relation.localKey || findPrimaryKey(relation.target);
5198
- const cache = /* @__PURE__ */ new Map();
5199
- for (const item of data) {
5200
- const pkValue = item[targetKey];
5201
- if (pkValue === void 0 || pkValue === null) continue;
5202
- cache.set(toKey7(pkValue), item);
5705
+ if (prop in target) {
5706
+ return Reflect.set(target, prop, value, receiver);
5203
5707
  }
5204
- if (cache.size) {
5205
- meta.relationHydration.set(relationName, cache);
5206
- meta.relationCache.set(relationName, Promise.resolve(cache));
5708
+ const getRef = target.get;
5709
+ if (typeof getRef === "function") {
5710
+ const current = getRef.call(target);
5711
+ if (current && typeof current === "object") {
5712
+ return Reflect.set(current, prop, value);
5713
+ }
5714
+ }
5715
+ const getItems = target.getItems;
5716
+ if (typeof getItems === "function") {
5717
+ const items = getItems.call(target);
5718
+ return Reflect.set(items, prop, value);
5207
5719
  }
5720
+ return Reflect.set(target, prop, value, receiver);
5208
5721
  }
5209
- }
5722
+ });
5210
5723
  };
5211
- var getRelationWrapper = (meta, relationName, owner) => {
5212
- if (meta.relationWrappers.has(relationName)) {
5213
- return meta.relationWrappers.get(relationName);
5724
+ var getRelationWrapper = (meta, relationName, owner, createEntity) => {
5725
+ const relationKey = relationName;
5726
+ if (meta.relationWrappers.has(relationKey)) {
5727
+ return meta.relationWrappers.get(relationKey);
5214
5728
  }
5215
- const relation = meta.table.relations[relationName];
5729
+ const relation = meta.table.relations[relationKey];
5216
5730
  if (!relation) return void 0;
5217
- const wrapper = instantiateWrapper(meta, relationName, relation, owner);
5218
- if (wrapper) {
5219
- meta.relationWrappers.set(relationName, wrapper);
5220
- }
5221
- return wrapper;
5222
- };
5223
- var instantiateWrapper = (meta, relationName, relation, owner) => {
5731
+ const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
5732
+ if (!wrapper) return void 0;
5733
+ const proxied = proxifyRelationWrapper(wrapper);
5734
+ meta.relationWrappers.set(relationKey, proxied);
5735
+ return proxied;
5736
+ };
5737
+ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) => {
5738
+ const metaBase = meta;
5739
+ const lazyOptions = meta.lazyRelationOptions.get(relationName);
5740
+ const loadCached = (factory) => relationLoaderCache(metaBase, relationName, factory);
5224
5741
  switch (relation.type) {
5225
5742
  case RelationKinds.HasOne: {
5226
5743
  const hasOne2 = relation;
5227
5744
  const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
5228
- const loader = () => relationLoaderCache(
5229
- meta,
5230
- relationName,
5231
- () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2)
5745
+ const loader = () => loadCached(
5746
+ () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, lazyOptions)
5232
5747
  );
5233
5748
  return new DefaultHasOneReference(
5234
5749
  meta.ctx,
5235
- meta,
5750
+ metaBase,
5236
5751
  owner,
5237
5752
  relationName,
5238
5753
  hasOne2,
5239
5754
  meta.table,
5240
5755
  loader,
5241
- (row) => createEntityFromRow(meta.ctx, hasOne2.target, row),
5756
+ (row) => createEntity(hasOne2.target, row),
5242
5757
  localKey
5243
5758
  );
5244
5759
  }
5245
5760
  case RelationKinds.HasMany: {
5246
5761
  const hasMany2 = relation;
5247
5762
  const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
5248
- const loader = () => relationLoaderCache(
5249
- meta,
5250
- relationName,
5251
- () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2)
5763
+ const loader = () => loadCached(
5764
+ () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, lazyOptions)
5252
5765
  );
5253
5766
  return new DefaultHasManyCollection(
5254
5767
  meta.ctx,
5255
- meta,
5768
+ metaBase,
5256
5769
  owner,
5257
5770
  relationName,
5258
5771
  hasMany2,
5259
5772
  meta.table,
5260
5773
  loader,
5261
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
5774
+ (row) => createEntity(relation.target, row),
5262
5775
  localKey
5263
5776
  );
5264
5777
  }
5265
5778
  case RelationKinds.BelongsTo: {
5266
5779
  const belongsTo2 = relation;
5267
5780
  const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
5268
- const loader = () => relationLoaderCache(
5269
- meta,
5270
- relationName,
5271
- () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2)
5781
+ const loader = () => loadCached(
5782
+ () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, lazyOptions)
5272
5783
  );
5273
5784
  return new DefaultBelongsToReference(
5274
5785
  meta.ctx,
5275
- meta,
5786
+ metaBase,
5276
5787
  owner,
5277
5788
  relationName,
5278
5789
  belongsTo2,
5279
5790
  meta.table,
5280
5791
  loader,
5281
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
5792
+ (row) => createEntity(relation.target, row),
5282
5793
  targetKey
5283
5794
  );
5284
5795
  }
5285
5796
  case RelationKinds.BelongsToMany: {
5286
5797
  const many = relation;
5287
5798
  const localKey = many.localKey || findPrimaryKey(meta.table);
5288
- const loader = () => relationLoaderCache(
5289
- meta,
5290
- relationName,
5291
- () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
5799
+ const loader = () => loadCached(
5800
+ () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
5292
5801
  );
5293
5802
  return new DefaultManyToManyCollection(
5294
5803
  meta.ctx,
5295
- meta,
5804
+ metaBase,
5296
5805
  owner,
5297
5806
  relationName,
5298
5807
  many,
5299
5808
  meta.table,
5300
5809
  loader,
5301
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
5810
+ (row) => createEntity(relation.target, row),
5302
5811
  localKey
5303
5812
  );
5304
5813
  }
@@ -5307,51 +5816,570 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
5307
5816
  }
5308
5817
  };
5309
5818
 
5310
- // src/orm/execute.ts
5311
- var flattenResults = (results) => {
5312
- const rows = [];
5313
- for (const result of results) {
5314
- const { columns, values } = result;
5315
- for (const valueRow of values) {
5316
- const row = {};
5317
- columns.forEach((column, idx) => {
5318
- row[column] = valueRow[idx];
5319
- });
5320
- rows.push(row);
5321
- }
5819
+ // src/orm/entity.ts
5820
+ var createEntityProxy = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
5821
+ const target = { ...row };
5822
+ const meta = {
5823
+ ctx,
5824
+ table,
5825
+ lazyRelations: [...lazyRelations],
5826
+ lazyRelationOptions: new Map(lazyRelationOptions),
5827
+ relationCache: /* @__PURE__ */ new Map(),
5828
+ relationHydration: /* @__PURE__ */ new Map(),
5829
+ relationWrappers: /* @__PURE__ */ new Map()
5830
+ };
5831
+ const createRelationEntity = (relationTable, relationRow) => createEntityFromRow(meta.ctx, relationTable, relationRow);
5832
+ Object.defineProperty(target, ENTITY_META, {
5833
+ value: meta,
5834
+ enumerable: false,
5835
+ writable: false
5836
+ });
5837
+ const handler = {
5838
+ get(targetObj, prop, receiver) {
5839
+ if (prop === ENTITY_META) {
5840
+ return meta;
5841
+ }
5842
+ if (prop === "$load") {
5843
+ return async (relationName) => {
5844
+ const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
5845
+ if (wrapper && typeof wrapper.load === "function") {
5846
+ return wrapper.load();
5847
+ }
5848
+ return void 0;
5849
+ };
5850
+ }
5851
+ if (typeof prop === "string" && table.relations[prop]) {
5852
+ return getRelationWrapper(meta, prop, receiver, createRelationEntity);
5853
+ }
5854
+ return Reflect.get(targetObj, prop, receiver);
5855
+ },
5856
+ set(targetObj, prop, value, receiver) {
5857
+ const result = Reflect.set(targetObj, prop, value, receiver);
5858
+ if (typeof prop === "string" && table.columns[prop]) {
5859
+ ctx.markDirty(receiver);
5860
+ }
5861
+ return result;
5862
+ }
5863
+ };
5864
+ const proxy = new Proxy(target, handler);
5865
+ populateHydrationCache(proxy, row, meta);
5866
+ return proxy;
5867
+ };
5868
+ var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
5869
+ const pkName = findPrimaryKey(table);
5870
+ const pkValue = row[pkName];
5871
+ if (pkValue !== void 0 && pkValue !== null) {
5872
+ const tracked = ctx.getEntity(table, pkValue);
5873
+ if (tracked) return tracked;
5874
+ }
5875
+ const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
5876
+ if (pkValue !== void 0 && pkValue !== null) {
5877
+ ctx.trackManaged(table, pkValue, entity);
5878
+ } else {
5879
+ ctx.trackNew(table, entity);
5880
+ }
5881
+ return entity;
5882
+ };
5883
+
5884
+ // src/orm/execute.ts
5885
+ var flattenResults = (results) => {
5886
+ const rows = [];
5887
+ for (const result of results) {
5888
+ const { columns, values } = result;
5889
+ for (const valueRow of values) {
5890
+ const row = {};
5891
+ columns.forEach((column, idx) => {
5892
+ row[column] = valueRow[idx];
5893
+ });
5894
+ rows.push(row);
5895
+ }
5896
+ }
5897
+ return rows;
5898
+ };
5899
+ var executeWithContexts = async (execCtx, entityCtx, qb) => {
5900
+ const ast = qb.getAST();
5901
+ const compiled = execCtx.dialect.compileSelect(ast);
5902
+ const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
5903
+ const rows = flattenResults(executed);
5904
+ const lazyRelations = qb.getLazyRelations();
5905
+ const lazyRelationOptions = qb.getLazyRelationOptions();
5906
+ if (ast.setOps && ast.setOps.length > 0) {
5907
+ const proxies = rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
5908
+ await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
5909
+ return proxies;
5910
+ }
5911
+ const hydrated = hydrateRows(rows, qb.getHydrationPlan());
5912
+ const entities = hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
5913
+ await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
5914
+ return entities;
5915
+ };
5916
+ async function executeHydrated(session, qb) {
5917
+ return executeWithContexts(session.getExecutionContext(), session, qb);
5918
+ }
5919
+ async function executeHydratedWithContexts(execCtx, hydCtx, qb) {
5920
+ const entityCtx = hydCtx.entityContext;
5921
+ if (!entityCtx) {
5922
+ throw new Error("Hydration context is missing an EntityContext");
5923
+ }
5924
+ return executeWithContexts(execCtx, entityCtx, qb);
5925
+ }
5926
+ var loadLazyRelationsForTable = async (ctx, table, lazyRelations, lazyRelationOptions) => {
5927
+ if (!lazyRelations.length) return;
5928
+ const tracked = ctx.getEntitiesForTable(table);
5929
+ if (!tracked.length) return;
5930
+ const meta = getEntityMeta(tracked[0].entity);
5931
+ if (!meta) return;
5932
+ for (const relationName of lazyRelations) {
5933
+ const relation = table.relations[relationName];
5934
+ if (!relation) continue;
5935
+ const key = relationName;
5936
+ const options = lazyRelationOptions.get(key);
5937
+ if (!options) {
5938
+ continue;
5939
+ }
5940
+ switch (relation.type) {
5941
+ case RelationKinds.HasOne:
5942
+ await relationLoaderCache(
5943
+ meta,
5944
+ key,
5945
+ () => loadHasOneRelation(ctx, table, key, relation, options)
5946
+ );
5947
+ break;
5948
+ case RelationKinds.HasMany:
5949
+ await relationLoaderCache(
5950
+ meta,
5951
+ key,
5952
+ () => loadHasManyRelation(ctx, table, key, relation, options)
5953
+ );
5954
+ break;
5955
+ case RelationKinds.BelongsTo:
5956
+ await relationLoaderCache(
5957
+ meta,
5958
+ key,
5959
+ () => loadBelongsToRelation(ctx, table, key, relation, options)
5960
+ );
5961
+ break;
5962
+ case RelationKinds.BelongsToMany:
5963
+ await relationLoaderCache(
5964
+ meta,
5965
+ key,
5966
+ () => loadBelongsToManyRelation(ctx, table, key, relation, options)
5967
+ );
5968
+ break;
5969
+ }
5970
+ }
5971
+ };
5972
+
5973
+ // src/query-builder/query-resolution.ts
5974
+ function resolveSelectQuery(query) {
5975
+ const candidate = query;
5976
+ return typeof candidate.getAST === "function" && candidate.getAST ? candidate.getAST() : query;
5977
+ }
5978
+
5979
+ // src/query-builder/select/select-operations.ts
5980
+ function applyOrderBy(context, predicateFacet, term, directionOrOptions) {
5981
+ const options = typeof directionOrOptions === "string" ? { direction: directionOrOptions } : directionOrOptions;
5982
+ const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
5983
+ return predicateFacet.orderBy(context, term, dir, options.nulls, options.collation);
5984
+ }
5985
+ async function executeCount(context, env, session) {
5986
+ const unpagedAst = {
5987
+ ...context.state.ast,
5988
+ orderBy: void 0,
5989
+ limit: void 0,
5990
+ offset: void 0
5991
+ };
5992
+ const nextState = new SelectQueryState(env.table, unpagedAst);
5993
+ const nextContext = {
5994
+ ...context,
5995
+ state: nextState
5996
+ };
5997
+ const subAst = nextContext.hydration.applyToAst(nextState.ast);
5998
+ const countQuery = {
5999
+ type: "SelectQuery",
6000
+ from: derivedTable(subAst, "__metal_count"),
6001
+ columns: [{ type: "Function", name: "COUNT", args: [], alias: "total" }],
6002
+ joins: []
6003
+ };
6004
+ const execCtx = session.getExecutionContext();
6005
+ const compiled = execCtx.dialect.compileSelect(countQuery);
6006
+ const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
6007
+ const value = results[0]?.values?.[0]?.[0];
6008
+ if (typeof value === "number") return value;
6009
+ if (typeof value === "bigint") return Number(value);
6010
+ if (typeof value === "string") return Number(value);
6011
+ return value === null || value === void 0 ? 0 : Number(value);
6012
+ }
6013
+ async function executePagedQuery(builder, session, options, countCallback) {
6014
+ const { page, pageSize } = options;
6015
+ if (!Number.isInteger(page) || page < 1) {
6016
+ throw new Error("executePaged: page must be an integer >= 1");
6017
+ }
6018
+ if (!Number.isInteger(pageSize) || pageSize < 1) {
6019
+ throw new Error("executePaged: pageSize must be an integer >= 1");
6020
+ }
6021
+ const offset = (page - 1) * pageSize;
6022
+ const [items, totalItems] = await Promise.all([
6023
+ builder.limit(pageSize).offset(offset).execute(session),
6024
+ countCallback(session)
6025
+ ]);
6026
+ return { items, totalItems };
6027
+ }
6028
+ function buildWhereHasPredicate(env, context, relationFacet, createChildBuilder, relationName, callbackOrOptions, maybeOptions, negate = false) {
6029
+ const relation = env.table.relations[relationName];
6030
+ if (!relation) {
6031
+ throw new Error(`Relation '${relationName}' not found on table '${env.table.name}'`);
6032
+ }
6033
+ const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6034
+ const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6035
+ let subQb = createChildBuilder(relation.target);
6036
+ if (callback) {
6037
+ subQb = callback(subQb);
6038
+ }
6039
+ const subAst = subQb.getAST();
6040
+ const finalSubAst = relationFacet.applyRelationCorrelation(
6041
+ context,
6042
+ relationName,
6043
+ subAst,
6044
+ options?.correlate
6045
+ );
6046
+ return negate ? notExists(finalSubAst) : exists(finalSubAst);
6047
+ }
6048
+
6049
+ // src/query-builder/select/from-facet.ts
6050
+ var SelectFromFacet = class {
6051
+ /**
6052
+ * Creates a new SelectFromFacet instance
6053
+ * @param env - Query builder environment
6054
+ * @param createAstService - Function to create AST service
6055
+ */
6056
+ constructor(env, createAstService) {
6057
+ this.env = env;
6058
+ this.createAstService = createAstService;
6059
+ }
6060
+ /**
6061
+ * Applies an alias to the FROM table
6062
+ * @param context - Current query context
6063
+ * @param alias - Alias to apply
6064
+ * @returns Updated query context with aliased FROM
6065
+ */
6066
+ as(context, alias) {
6067
+ const from = context.state.ast.from;
6068
+ if (from.type !== "Table") {
6069
+ throw new Error("Cannot alias non-table FROM sources");
6070
+ }
6071
+ const nextFrom = { ...from, alias };
6072
+ const astService = this.createAstService(context.state);
6073
+ const nextState = astService.withFrom(nextFrom);
6074
+ return { state: nextState, hydration: context.hydration };
6075
+ }
6076
+ /**
6077
+ * Sets the FROM clause to a subquery
6078
+ * @param context - Current query context
6079
+ * @param subAst - Subquery AST
6080
+ * @param alias - Alias for the subquery
6081
+ * @param columnAliases - Optional column aliases
6082
+ * @returns Updated query context with subquery FROM
6083
+ */
6084
+ fromSubquery(context, subAst, alias, columnAliases) {
6085
+ const fromNode = derivedTable(subAst, alias, columnAliases);
6086
+ const astService = this.createAstService(context.state);
6087
+ const nextState = astService.withFrom(fromNode);
6088
+ return { state: nextState, hydration: context.hydration };
6089
+ }
6090
+ /**
6091
+ * Sets the FROM clause to a function table
6092
+ * @param context - Current query context
6093
+ * @param name - Function name
6094
+ * @param args - Function arguments
6095
+ * @param alias - Optional alias for the function table
6096
+ * @param options - Optional function table options
6097
+ * @returns Updated query context with function table FROM
6098
+ */
6099
+ fromFunctionTable(context, name, args, alias, options) {
6100
+ const functionTable = fnTable(name, args, alias, options);
6101
+ const astService = this.createAstService(context.state);
6102
+ const nextState = astService.withFrom(functionTable);
6103
+ return { state: nextState, hydration: context.hydration };
6104
+ }
6105
+ };
6106
+
6107
+ // src/query-builder/select/join-facet.ts
6108
+ var SelectJoinFacet = class {
6109
+ constructor(env, createAstService) {
6110
+ this.env = env;
6111
+ this.createAstService = createAstService;
6112
+ }
6113
+ applyJoin(context, table, condition, kind) {
6114
+ const joinNode = createJoinNode(kind, { type: "Table", name: table.name, schema: table.schema }, condition);
6115
+ const astService = this.createAstService(context.state);
6116
+ const nextState = astService.withJoin(joinNode);
6117
+ return { state: nextState, hydration: context.hydration };
6118
+ }
6119
+ joinSubquery(context, subAst, alias, condition, joinKind, columnAliases) {
6120
+ const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
6121
+ const astService = this.createAstService(context.state);
6122
+ const nextState = astService.withJoin(joinNode);
6123
+ return { state: nextState, hydration: context.hydration };
6124
+ }
6125
+ joinFunctionTable(context, name, args, alias, condition, joinKind, options) {
6126
+ const functionTable = fnTable(name, args, alias, options);
6127
+ const joinNode = createJoinNode(joinKind, functionTable, condition);
6128
+ const astService = this.createAstService(context.state);
6129
+ const nextState = astService.withJoin(joinNode);
6130
+ return { state: nextState, hydration: context.hydration };
6131
+ }
6132
+ };
6133
+
6134
+ // src/query-builder/select/projection-facet.ts
6135
+ var SelectProjectionFacet = class {
6136
+ /**
6137
+ * Creates a new SelectProjectionFacet instance
6138
+ * @param columnSelector - Column selector dependency
6139
+ */
6140
+ constructor(columnSelector) {
6141
+ this.columnSelector = columnSelector;
6142
+ }
6143
+ /**
6144
+ * Selects columns for the query
6145
+ * @param context - Current query context
6146
+ * @param columns - Columns to select
6147
+ * @returns Updated query context with selected columns
6148
+ */
6149
+ select(context, columns) {
6150
+ return this.columnSelector.select(context, columns);
6151
+ }
6152
+ /**
6153
+ * Selects raw column expressions
6154
+ * @param context - Current query context
6155
+ * @param cols - Raw column expressions
6156
+ * @returns Updated query context with raw column selections
6157
+ */
6158
+ selectRaw(context, cols) {
6159
+ return this.columnSelector.selectRaw(context, cols);
6160
+ }
6161
+ /**
6162
+ * Selects a subquery as a column
6163
+ * @param context - Current query context
6164
+ * @param alias - Alias for the subquery
6165
+ * @param query - Subquery to select
6166
+ * @returns Updated query context with subquery selection
6167
+ */
6168
+ selectSubquery(context, alias, query) {
6169
+ return this.columnSelector.selectSubquery(context, alias, query);
6170
+ }
6171
+ /**
6172
+ * Adds DISTINCT clause to the query
6173
+ * @param context - Current query context
6174
+ * @param cols - Columns to make distinct
6175
+ * @returns Updated query context with DISTINCT clause
6176
+ */
6177
+ distinct(context, cols) {
6178
+ return this.columnSelector.distinct(context, cols);
6179
+ }
6180
+ };
6181
+
6182
+ // src/query-builder/select/predicate-facet.ts
6183
+ var SelectPredicateFacet = class {
6184
+ /**
6185
+ * Creates a new SelectPredicateFacet instance
6186
+ * @param env - Query builder environment
6187
+ * @param createAstService - Function to create AST service
6188
+ */
6189
+ constructor(env, createAstService) {
6190
+ this.env = env;
6191
+ this.createAstService = createAstService;
6192
+ }
6193
+ /**
6194
+ * Adds a WHERE condition to the query
6195
+ * @param context - Current query context
6196
+ * @param expr - WHERE expression
6197
+ * @returns Updated query context with WHERE condition
6198
+ */
6199
+ where(context, expr) {
6200
+ const astService = this.createAstService(context.state);
6201
+ const nextState = astService.withWhere(expr);
6202
+ return { state: nextState, hydration: context.hydration };
6203
+ }
6204
+ /**
6205
+ * Adds a GROUP BY clause to the query
6206
+ * @param context - Current query context
6207
+ * @param term - Column or ordering term to group by
6208
+ * @returns Updated query context with GROUP BY clause
6209
+ */
6210
+ groupBy(context, term) {
6211
+ const astService = this.createAstService(context.state);
6212
+ const nextState = astService.withGroupBy(term);
6213
+ return { state: nextState, hydration: context.hydration };
6214
+ }
6215
+ /**
6216
+ * Adds a HAVING condition to the query
6217
+ * @param context - Current query context
6218
+ * @param expr - HAVING expression
6219
+ * @returns Updated query context with HAVING condition
6220
+ */
6221
+ having(context, expr) {
6222
+ const astService = this.createAstService(context.state);
6223
+ const nextState = astService.withHaving(expr);
6224
+ return { state: nextState, hydration: context.hydration };
6225
+ }
6226
+ /**
6227
+ * Adds an ORDER BY clause to the query
6228
+ * @param context - Current query context
6229
+ * @param term - Column or ordering term to order by
6230
+ * @param direction - Order direction
6231
+ * @param nulls - Nulls ordering
6232
+ * @param collation - Collation
6233
+ * @returns Updated query context with ORDER BY clause
6234
+ */
6235
+ orderBy(context, term, direction, nulls, collation) {
6236
+ const astService = this.createAstService(context.state);
6237
+ const nextState = astService.withOrderBy(term, direction, nulls, collation);
6238
+ return { state: nextState, hydration: context.hydration };
6239
+ }
6240
+ /**
6241
+ * Adds a LIMIT clause to the query
6242
+ * @param context - Current query context
6243
+ * @param n - Maximum number of rows
6244
+ * @returns Updated query context with LIMIT clause
6245
+ */
6246
+ limit(context, n) {
6247
+ const astService = this.createAstService(context.state);
6248
+ const nextState = astService.withLimit(n);
6249
+ return { state: nextState, hydration: context.hydration };
6250
+ }
6251
+ /**
6252
+ * Adds an OFFSET clause to the query
6253
+ * @param context - Current query context
6254
+ * @param n - Number of rows to skip
6255
+ * @returns Updated query context with OFFSET clause
6256
+ */
6257
+ offset(context, n) {
6258
+ const astService = this.createAstService(context.state);
6259
+ const nextState = astService.withOffset(n);
6260
+ return { state: nextState, hydration: context.hydration };
6261
+ }
6262
+ };
6263
+
6264
+ // src/query-builder/select/cte-facet.ts
6265
+ var SelectCTEFacet = class {
6266
+ /**
6267
+ * Creates a new SelectCTEFacet instance
6268
+ * @param env - Query builder environment
6269
+ * @param createAstService - Function to create AST service
6270
+ */
6271
+ constructor(env, createAstService) {
6272
+ this.env = env;
6273
+ this.createAstService = createAstService;
5322
6274
  }
5323
- return rows;
5324
- };
5325
- var executeWithContexts = async (execCtx, entityCtx, qb) => {
5326
- const ast = qb.getAST();
5327
- const compiled = execCtx.dialect.compileSelect(ast);
5328
- const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
5329
- const rows = flattenResults(executed);
5330
- if (ast.setOps && ast.setOps.length > 0) {
5331
- return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
6275
+ /**
6276
+ * Adds a Common Table Expression to the query
6277
+ * @param context - Current query context
6278
+ * @param name - CTE name
6279
+ * @param subAst - CTE query AST
6280
+ * @param columns - Optional column names
6281
+ * @param recursive - Whether the CTE is recursive
6282
+ * @returns Updated query context with CTE
6283
+ */
6284
+ withCTE(context, name, subAst, columns, recursive) {
6285
+ const astService = this.createAstService(context.state);
6286
+ const nextState = astService.withCte(name, subAst, columns, recursive);
6287
+ return { state: nextState, hydration: context.hydration };
5332
6288
  }
5333
- const hydrated = hydrateRows(rows, qb.getHydrationPlan());
5334
- return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
5335
6289
  };
5336
- async function executeHydrated(session, qb) {
5337
- return executeWithContexts(session.getExecutionContext(), session, qb);
5338
- }
5339
- async function executeHydratedWithContexts(execCtx, hydCtx, qb) {
5340
- const entityCtx = hydCtx.entityContext;
5341
- if (!entityCtx) {
5342
- throw new Error("Hydration context is missing an EntityContext");
6290
+
6291
+ // src/query-builder/select/setop-facet.ts
6292
+ var SelectSetOpFacet = class {
6293
+ /**
6294
+ * Creates a new SelectSetOpFacet instance
6295
+ * @param env - Query builder environment
6296
+ * @param createAstService - Function to create AST service
6297
+ */
6298
+ constructor(env, createAstService) {
6299
+ this.env = env;
6300
+ this.createAstService = createAstService;
5343
6301
  }
5344
- return executeWithContexts(execCtx, entityCtx, qb);
5345
- }
6302
+ /**
6303
+ * Applies a set operation to the query
6304
+ * @param context - Current query context
6305
+ * @param operator - Set operation kind
6306
+ * @param subAst - Subquery AST to combine
6307
+ * @returns Updated query context with set operation
6308
+ */
6309
+ applySetOperation(context, operator, subAst) {
6310
+ const astService = this.createAstService(context.state);
6311
+ const nextState = astService.withSetOperation(operator, subAst);
6312
+ return { state: nextState, hydration: context.hydration };
6313
+ }
6314
+ };
5346
6315
 
5347
- // src/query-builder/query-resolution.ts
5348
- function resolveSelectQuery(query) {
5349
- const candidate = query;
5350
- return typeof candidate.getAST === "function" && candidate.getAST ? candidate.getAST() : query;
5351
- }
6316
+ // src/query-builder/select/relation-facet.ts
6317
+ var SelectRelationFacet = class {
6318
+ /**
6319
+ * Creates a new SelectRelationFacet instance
6320
+ * @param relationManager - Relation manager dependency
6321
+ */
6322
+ constructor(relationManager) {
6323
+ this.relationManager = relationManager;
6324
+ }
6325
+ /**
6326
+ * Matches records based on a relationship
6327
+ * @param context - Current query context
6328
+ * @param relationName - Name of the relationship
6329
+ * @param predicate - Optional predicate
6330
+ * @returns Updated query context with relation match
6331
+ */
6332
+ match(context, relationName, predicate) {
6333
+ return this.relationManager.match(context, relationName, predicate);
6334
+ }
6335
+ /**
6336
+ * Joins a related table
6337
+ * @param context - Current query context
6338
+ * @param relationName - Name of the relationship
6339
+ * @param joinKind - Type of join
6340
+ * @param extraCondition - Optional additional condition
6341
+ * @returns Updated query context with relation join
6342
+ */
6343
+ joinRelation(context, relationName, joinKind, extraCondition) {
6344
+ return this.relationManager.joinRelation(context, relationName, joinKind, extraCondition);
6345
+ }
6346
+ /**
6347
+ * Includes related data in the query results
6348
+ * @param context - Current query context
6349
+ * @param relationName - Name of the relationship to include
6350
+ * @param options - Optional include options
6351
+ * @returns Updated query context with relation inclusion
6352
+ */
6353
+ include(context, relationName, options) {
6354
+ return this.relationManager.include(context, relationName, options);
6355
+ }
6356
+ /**
6357
+ * Applies correlation for relation-based subqueries
6358
+ * @param context - Current query context
6359
+ * @param relationName - Name of the relationship
6360
+ * @param subAst - Subquery AST
6361
+ * @param extraCorrelate - Optional additional correlation
6362
+ * @returns Modified subquery AST with correlation
6363
+ */
6364
+ applyRelationCorrelation(context, relationName, subAst, extraCorrelate) {
6365
+ return this.relationManager.applyRelationCorrelation(context, relationName, subAst, extraCorrelate);
6366
+ }
6367
+ };
5352
6368
 
5353
6369
  // src/query-builder/select.ts
5354
6370
  var SelectQueryBuilder = class _SelectQueryBuilder {
6371
+ env;
6372
+ context;
6373
+ columnSelector;
6374
+ fromFacet;
6375
+ joinFacet;
6376
+ projectionFacet;
6377
+ predicateFacet;
6378
+ cteFacet;
6379
+ setOpFacet;
6380
+ relationFacet;
6381
+ lazyRelations;
6382
+ lazyRelationOptions;
5355
6383
  /**
5356
6384
  * Creates a new SelectQueryBuilder instance
5357
6385
  * @param table - Table definition to query
@@ -5359,9 +6387,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5359
6387
  * @param hydration - Optional hydration manager
5360
6388
  * @param dependencies - Optional query builder dependencies
5361
6389
  */
5362
- constructor(table, state, hydration, dependencies, lazyRelations) {
6390
+ constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions) {
5363
6391
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
5364
6392
  this.env = { table, deps };
6393
+ const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
5365
6394
  const initialState = state ?? deps.createState(table);
5366
6395
  const initialHydration = hydration ?? deps.createHydration(table);
5367
6396
  this.context = {
@@ -5369,8 +6398,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5369
6398
  hydration: initialHydration
5370
6399
  };
5371
6400
  this.lazyRelations = new Set(lazyRelations ?? []);
6401
+ this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
5372
6402
  this.columnSelector = deps.createColumnSelector(this.env);
5373
- this.relationManager = deps.createRelationManager(this.env);
6403
+ const relationManager = deps.createRelationManager(this.env);
6404
+ this.fromFacet = new SelectFromFacet(this.env, createAstService);
6405
+ this.joinFacet = new SelectJoinFacet(this.env, createAstService);
6406
+ this.projectionFacet = new SelectProjectionFacet(this.columnSelector);
6407
+ this.predicateFacet = new SelectPredicateFacet(this.env, createAstService);
6408
+ this.cteFacet = new SelectCTEFacet(this.env, createAstService);
6409
+ this.setOpFacet = new SelectSetOpFacet(this.env, createAstService);
6410
+ this.relationFacet = new SelectRelationFacet(relationManager);
5374
6411
  }
5375
6412
  /**
5376
6413
  * Creates a new SelectQueryBuilder instance with updated context and lazy relations
@@ -5378,20 +6415,22 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5378
6415
  * @param lazyRelations - Updated lazy relations set
5379
6416
  * @returns New SelectQueryBuilder instance
5380
6417
  */
5381
- clone(context = this.context, lazyRelations = new Set(this.lazyRelations)) {
5382
- return new _SelectQueryBuilder(this.env.table, context.state, context.hydration, this.env.deps, lazyRelations);
6418
+ clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions)) {
6419
+ return new _SelectQueryBuilder(
6420
+ this.env.table,
6421
+ context.state,
6422
+ context.hydration,
6423
+ this.env.deps,
6424
+ lazyRelations,
6425
+ lazyRelationOptions
6426
+ );
5383
6427
  }
5384
6428
  /**
5385
6429
  * Applies an alias to the root FROM table.
5386
6430
  * @param alias - Alias to apply
5387
6431
  */
5388
6432
  as(alias) {
5389
- const from = this.context.state.ast.from;
5390
- if (from.type !== "Table") {
5391
- throw new Error("Cannot alias non-table FROM sources");
5392
- }
5393
- const nextFrom = { ...from, alias };
5394
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(nextFrom));
6433
+ const nextContext = this.fromFacet.as(this.context, alias);
5395
6434
  return this.clone(nextContext);
5396
6435
  }
5397
6436
  /**
@@ -5416,29 +6455,6 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5416
6455
  createChildBuilder(table) {
5417
6456
  return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
5418
6457
  }
5419
- /**
5420
- * Applies an AST mutation using the query AST service
5421
- * @param context - Current query context
5422
- * @param mutator - Function that mutates the AST
5423
- * @returns Updated query context
5424
- */
5425
- applyAst(context, mutator) {
5426
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
5427
- const nextState = mutator(astService);
5428
- return { state: nextState, hydration: context.hydration };
5429
- }
5430
- /**
5431
- * Applies a join to the query context
5432
- * @param context - Current query context
5433
- * @param table - Table to join
5434
- * @param condition - Join condition
5435
- * @param kind - Join kind
5436
- * @returns Updated query context with join applied
5437
- */
5438
- applyJoin(context, table, condition, kind) {
5439
- const joinNode = createJoinNode(kind, { type: "Table", name: table.name, schema: table.schema }, condition);
5440
- return this.applyAst(context, (service) => service.withJoin(joinNode));
5441
- }
5442
6458
  /**
5443
6459
  * Applies a set operation to the query
5444
6460
  * @param operator - Set operation kind
@@ -5447,12 +6463,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5447
6463
  */
5448
6464
  applySetOperation(operator, query) {
5449
6465
  const subAst = resolveSelectQuery(query);
5450
- return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
6466
+ return this.setOpFacet.applySetOperation(this.context, operator, subAst);
5451
6467
  }
5452
6468
  select(...args) {
5453
6469
  if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && typeof args[0] !== "string") {
5454
6470
  const columns = args[0];
5455
- return this.clone(this.columnSelector.select(this.context, columns));
6471
+ return this.clone(this.projectionFacet.select(this.context, columns));
5456
6472
  }
5457
6473
  const cols = args;
5458
6474
  const selection = {};
@@ -5463,7 +6479,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5463
6479
  }
5464
6480
  selection[key] = col2;
5465
6481
  }
5466
- return this.clone(this.columnSelector.select(this.context, selection));
6482
+ return this.clone(this.projectionFacet.select(this.context, selection));
5467
6483
  }
5468
6484
  /**
5469
6485
  * Selects raw column expressions
@@ -5471,7 +6487,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5471
6487
  * @returns New query builder instance with raw column selections
5472
6488
  */
5473
6489
  selectRaw(...cols) {
5474
- return this.clone(this.columnSelector.selectRaw(this.context, cols));
6490
+ return this.clone(this.projectionFacet.selectRaw(this.context, cols));
5475
6491
  }
5476
6492
  /**
5477
6493
  * Adds a Common Table Expression (CTE) to the query
@@ -5482,7 +6498,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5482
6498
  */
5483
6499
  with(name, query, columns) {
5484
6500
  const subAst = resolveSelectQuery(query);
5485
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
6501
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, false);
5486
6502
  return this.clone(nextContext);
5487
6503
  }
5488
6504
  /**
@@ -5494,7 +6510,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5494
6510
  */
5495
6511
  withRecursive(name, query, columns) {
5496
6512
  const subAst = resolveSelectQuery(query);
5497
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
6513
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, true);
5498
6514
  return this.clone(nextContext);
5499
6515
  }
5500
6516
  /**
@@ -5506,8 +6522,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5506
6522
  */
5507
6523
  fromSubquery(subquery, alias, columnAliases) {
5508
6524
  const subAst = resolveSelectQuery(subquery);
5509
- const fromNode = derivedTable(subAst, alias, columnAliases);
5510
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(fromNode));
6525
+ const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
5511
6526
  return this.clone(nextContext);
5512
6527
  }
5513
6528
  /**
@@ -5518,8 +6533,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5518
6533
  * @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
5519
6534
  */
5520
6535
  fromFunctionTable(name, args = [], alias, options) {
5521
- const functionTable = fnTable(name, args, alias, options);
5522
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(functionTable));
6536
+ const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
5523
6537
  return this.clone(nextContext);
5524
6538
  }
5525
6539
  /**
@@ -5530,7 +6544,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5530
6544
  */
5531
6545
  selectSubquery(alias, sub2) {
5532
6546
  const query = resolveSelectQuery(sub2);
5533
- return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
6547
+ return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
5534
6548
  }
5535
6549
  /**
5536
6550
  * Adds a JOIN against a derived table (subquery with alias)
@@ -5543,8 +6557,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5543
6557
  */
5544
6558
  joinSubquery(subquery, alias, condition, joinKind = JOIN_KINDS.INNER, columnAliases) {
5545
6559
  const subAst = resolveSelectQuery(subquery);
5546
- const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
5547
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
6560
+ const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
5548
6561
  return this.clone(nextContext);
5549
6562
  }
5550
6563
  /**
@@ -5557,9 +6570,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5557
6570
  * @param options - Optional metadata (lateral, ordinality, column aliases, schema)
5558
6571
  */
5559
6572
  joinFunctionTable(name, args = [], alias, condition, joinKind = JOIN_KINDS.INNER, options) {
5560
- const functionTable = fnTable(name, args, alias, options);
5561
- const joinNode = createJoinNode(joinKind, functionTable, condition);
5562
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
6573
+ const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
5563
6574
  return this.clone(nextContext);
5564
6575
  }
5565
6576
  /**
@@ -5569,7 +6580,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5569
6580
  * @returns New query builder instance with the INNER JOIN
5570
6581
  */
5571
6582
  innerJoin(table, condition) {
5572
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
6583
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
5573
6584
  return this.clone(nextContext);
5574
6585
  }
5575
6586
  /**
@@ -5579,7 +6590,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5579
6590
  * @returns New query builder instance with the LEFT JOIN
5580
6591
  */
5581
6592
  leftJoin(table, condition) {
5582
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
6593
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
5583
6594
  return this.clone(nextContext);
5584
6595
  }
5585
6596
  /**
@@ -5589,7 +6600,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5589
6600
  * @returns New query builder instance with the RIGHT JOIN
5590
6601
  */
5591
6602
  rightJoin(table, condition) {
5592
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
6603
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
5593
6604
  return this.clone(nextContext);
5594
6605
  }
5595
6606
  /**
@@ -5599,7 +6610,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5599
6610
  * @returns New query builder instance with the relationship match
5600
6611
  */
5601
6612
  match(relationName, predicate) {
5602
- const nextContext = this.relationManager.match(this.context, relationName, predicate);
6613
+ const nextContext = this.relationFacet.match(this.context, relationName, predicate);
5603
6614
  return this.clone(nextContext);
5604
6615
  }
5605
6616
  /**
@@ -5610,7 +6621,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5610
6621
  * @returns New query builder instance with the relationship join
5611
6622
  */
5612
6623
  joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
5613
- const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
6624
+ const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
5614
6625
  return this.clone(nextContext);
5615
6626
  }
5616
6627
  /**
@@ -5620,42 +6631,47 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5620
6631
  * @returns New query builder instance with the relationship inclusion
5621
6632
  */
5622
6633
  include(relationName, options) {
5623
- const nextContext = this.relationManager.include(this.context, relationName, options);
6634
+ const nextContext = this.relationFacet.include(this.context, relationName, options);
5624
6635
  return this.clone(nextContext);
5625
6636
  }
5626
6637
  /**
5627
6638
  * Includes a relation lazily in the query results
5628
6639
  * @param relationName - Name of the relation to include lazily
6640
+ * @param options - Optional include options for lazy loading
5629
6641
  * @returns New query builder instance with lazy relation inclusion
5630
6642
  */
5631
- includeLazy(relationName) {
5632
- const nextLazy = new Set(this.lazyRelations);
5633
- nextLazy.add(relationName);
5634
- return this.clone(this.context, nextLazy);
5635
- }
5636
- /**
5637
- * Selects columns for a related table in a single hop.
5638
- */
5639
- selectRelationColumns(relationName, ...cols) {
6643
+ includeLazy(relationName, options) {
6644
+ let nextContext = this.context;
5640
6645
  const relation = this.env.table.relations[relationName];
5641
- if (!relation) {
5642
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
5643
- }
5644
- const target = relation.target;
5645
- for (const col2 of cols) {
5646
- if (!target.columns[col2]) {
5647
- throw new Error(
5648
- `Column '${col2}' not found on related table '${target.name}' for relation '${relationName}'`
5649
- );
6646
+ if (relation?.type === RelationKinds.BelongsTo) {
6647
+ const foreignKey = relation.foreignKey;
6648
+ const fkColumn = this.env.table.columns[foreignKey];
6649
+ if (fkColumn) {
6650
+ const hasAlias2 = nextContext.state.ast.columns.some((col2) => {
6651
+ const node = col2;
6652
+ return (node.alias ?? node.name) === foreignKey;
6653
+ });
6654
+ if (!hasAlias2) {
6655
+ nextContext = this.columnSelector.select(nextContext, { [foreignKey]: fkColumn });
6656
+ }
5650
6657
  }
5651
6658
  }
5652
- return this.include(relationName, { columns: cols });
6659
+ const nextLazy = new Set(this.lazyRelations);
6660
+ nextLazy.add(relationName);
6661
+ const nextOptions = new Map(this.lazyRelationOptions);
6662
+ if (options) {
6663
+ nextOptions.set(relationName, options);
6664
+ } else {
6665
+ nextOptions.delete(relationName);
6666
+ }
6667
+ return this.clone(nextContext, nextLazy, nextOptions);
5653
6668
  }
5654
6669
  /**
5655
- * Convenience alias for selecting specific columns from a relation.
6670
+ * Convenience alias for including only specific columns from a relation.
5656
6671
  */
5657
6672
  includePick(relationName, cols) {
5658
- return this.selectRelationColumns(relationName, ...cols);
6673
+ const options = { columns: cols };
6674
+ return this.include(relationName, options);
5659
6675
  }
5660
6676
  /**
5661
6677
  * Selects columns for the root table and relations from an array of entries
@@ -5668,7 +6684,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5668
6684
  if (entry.type === "root") {
5669
6685
  currBuilder = currBuilder.select(...entry.columns);
5670
6686
  } else {
5671
- currBuilder = currBuilder.selectRelationColumns(entry.relationName, ...entry.columns);
6687
+ const options = { columns: entry.columns };
6688
+ currBuilder = currBuilder.include(entry.relationName, options);
5672
6689
  }
5673
6690
  }
5674
6691
  return currBuilder;
@@ -5680,6 +6697,13 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5680
6697
  getLazyRelations() {
5681
6698
  return Array.from(this.lazyRelations);
5682
6699
  }
6700
+ /**
6701
+ * Gets lazy relation include options
6702
+ * @returns Map of relation names to include options
6703
+ */
6704
+ getLazyRelationOptions() {
6705
+ return new Map(this.lazyRelationOptions);
6706
+ }
5683
6707
  /**
5684
6708
  * Gets the table definition for this query builder
5685
6709
  * @returns Table definition
@@ -5695,51 +6719,23 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5695
6719
  async execute(ctx) {
5696
6720
  return executeHydrated(ctx, this);
5697
6721
  }
5698
- withAst(ast) {
5699
- const nextState = new SelectQueryState(this.env.table, ast);
5700
- const nextContext = {
5701
- ...this.context,
5702
- state: nextState
5703
- };
5704
- return this.clone(nextContext);
5705
- }
6722
+ /**
6723
+ * Executes a count query for the current builder without LIMIT/OFFSET clauses.
6724
+ *
6725
+ * @example
6726
+ * const total = await qb.count(session);
6727
+ */
5706
6728
  async count(session) {
5707
- const unpagedAst = {
5708
- ...this.context.state.ast,
5709
- orderBy: void 0,
5710
- limit: void 0,
5711
- offset: void 0
5712
- };
5713
- const subAst = this.withAst(unpagedAst).getAST();
5714
- const countQuery = {
5715
- type: "SelectQuery",
5716
- from: derivedTable(subAst, "__metal_count"),
5717
- columns: [{ type: "Function", name: "COUNT", args: [], alias: "total" }],
5718
- joins: []
5719
- };
5720
- const execCtx = session.getExecutionContext();
5721
- const compiled = execCtx.dialect.compileSelect(countQuery);
5722
- const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
5723
- const value = results[0]?.values?.[0]?.[0];
5724
- if (typeof value === "number") return value;
5725
- if (typeof value === "bigint") return Number(value);
5726
- if (typeof value === "string") return Number(value);
5727
- return value === null || value === void 0 ? 0 : Number(value);
6729
+ return executeCount(this.context, this.env, session);
5728
6730
  }
6731
+ /**
6732
+ * Executes the query and returns both the paged items and the total.
6733
+ *
6734
+ * @example
6735
+ * const { items, totalItems } = await qb.executePaged(session, { page: 1, pageSize: 20 });
6736
+ */
5729
6737
  async executePaged(session, options) {
5730
- const { page, pageSize } = options;
5731
- if (!Number.isInteger(page) || page < 1) {
5732
- throw new Error("executePaged: page must be an integer >= 1");
5733
- }
5734
- if (!Number.isInteger(pageSize) || pageSize < 1) {
5735
- throw new Error("executePaged: pageSize must be an integer >= 1");
5736
- }
5737
- const offset = (page - 1) * pageSize;
5738
- const [items, totalItems] = await Promise.all([
5739
- this.limit(pageSize).offset(offset).execute(session),
5740
- this.count(session)
5741
- ]);
5742
- return { items, totalItems };
6738
+ return executePagedQuery(this, session, options, (sess) => this.count(sess));
5743
6739
  }
5744
6740
  /**
5745
6741
  * Executes the query with provided execution and hydration contexts
@@ -5756,7 +6752,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5756
6752
  * @returns New query builder instance with the WHERE condition
5757
6753
  */
5758
6754
  where(expr) {
5759
- const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
6755
+ const nextContext = this.predicateFacet.where(this.context, expr);
5760
6756
  return this.clone(nextContext);
5761
6757
  }
5762
6758
  /**
@@ -5765,7 +6761,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5765
6761
  * @returns New query builder instance with the GROUP BY clause
5766
6762
  */
5767
6763
  groupBy(term) {
5768
- const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(term));
6764
+ const nextContext = this.predicateFacet.groupBy(this.context, term);
5769
6765
  return this.clone(nextContext);
5770
6766
  }
5771
6767
  /**
@@ -5774,7 +6770,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5774
6770
  * @returns New query builder instance with the HAVING condition
5775
6771
  */
5776
6772
  having(expr) {
5777
- const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
6773
+ const nextContext = this.predicateFacet.having(this.context, expr);
5778
6774
  return this.clone(nextContext);
5779
6775
  }
5780
6776
  /**
@@ -5782,14 +6778,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5782
6778
  * @param term - Column definition or ordering term to order by
5783
6779
  * @param directionOrOptions - Order direction or options (defaults to ASC)
5784
6780
  * @returns New query builder instance with the ORDER BY clause
6781
+ *
6782
+ * @example
6783
+ * qb.orderBy(userTable.columns.createdAt, 'DESC');
5785
6784
  */
5786
6785
  orderBy(term, directionOrOptions = ORDER_DIRECTIONS.ASC) {
5787
- const options = typeof directionOrOptions === "string" ? { direction: directionOrOptions } : directionOrOptions;
5788
- const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
5789
- const nextContext = this.applyAst(
5790
- this.context,
5791
- (service) => service.withOrderBy(term, dir, options.nulls, options.collation)
5792
- );
6786
+ const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
5793
6787
  return this.clone(nextContext);
5794
6788
  }
5795
6789
  /**
@@ -5798,7 +6792,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5798
6792
  * @returns New query builder instance with the DISTINCT clause
5799
6793
  */
5800
6794
  distinct(...cols) {
5801
- return this.clone(this.columnSelector.distinct(this.context, cols));
6795
+ return this.clone(this.projectionFacet.distinct(this.context, cols));
5802
6796
  }
5803
6797
  /**
5804
6798
  * Adds a LIMIT clause to the query
@@ -5806,7 +6800,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5806
6800
  * @returns New query builder instance with the LIMIT clause
5807
6801
  */
5808
6802
  limit(n) {
5809
- const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
6803
+ const nextContext = this.predicateFacet.limit(this.context, n);
5810
6804
  return this.clone(nextContext);
5811
6805
  }
5812
6806
  /**
@@ -5815,7 +6809,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5815
6809
  * @returns New query builder instance with the OFFSET clause
5816
6810
  */
5817
6811
  offset(n) {
5818
- const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
6812
+ const nextContext = this.predicateFacet.offset(this.context, n);
5819
6813
  return this.clone(nextContext);
5820
6814
  }
5821
6815
  /**
@@ -5875,42 +6869,44 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5875
6869
  * @param relationName - Name of the relationship to check
5876
6870
  * @param callback - Optional callback to modify the relationship query
5877
6871
  * @returns New query builder instance with the relationship existence check
6872
+ *
6873
+ * @example
6874
+ * qb.whereHas('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
5878
6875
  */
5879
6876
  whereHas(relationName, callbackOrOptions, maybeOptions) {
5880
- const relation = this.env.table.relations[relationName];
5881
- if (!relation) {
5882
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
5883
- }
5884
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
5885
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
5886
- let subQb = this.createChildBuilder(relation.target);
5887
- if (callback) {
5888
- subQb = callback(subQb);
5889
- }
5890
- const subAst = subQb.getAST();
5891
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
5892
- return this.where(exists(finalSubAst));
6877
+ const predicate = buildWhereHasPredicate(
6878
+ this.env,
6879
+ this.context,
6880
+ this.relationFacet,
6881
+ (table) => this.createChildBuilder(table),
6882
+ relationName,
6883
+ callbackOrOptions,
6884
+ maybeOptions,
6885
+ false
6886
+ );
6887
+ return this.where(predicate);
5893
6888
  }
5894
6889
  /**
5895
6890
  * Adds a WHERE NOT EXISTS condition based on a relationship
5896
6891
  * @param relationName - Name of the relationship to check
5897
6892
  * @param callback - Optional callback to modify the relationship query
5898
6893
  * @returns New query builder instance with the relationship non-existence check
6894
+ *
6895
+ * @example
6896
+ * qb.whereHasNot('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
5899
6897
  */
5900
6898
  whereHasNot(relationName, callbackOrOptions, maybeOptions) {
5901
- const relation = this.env.table.relations[relationName];
5902
- if (!relation) {
5903
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
5904
- }
5905
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
5906
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
5907
- let subQb = this.createChildBuilder(relation.target);
5908
- if (callback) {
5909
- subQb = callback(subQb);
5910
- }
5911
- const subAst = subQb.getAST();
5912
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
5913
- return this.where(notExists(finalSubAst));
6899
+ const predicate = buildWhereHasPredicate(
6900
+ this.env,
6901
+ this.context,
6902
+ this.relationFacet,
6903
+ (table) => this.createChildBuilder(table),
6904
+ relationName,
6905
+ callbackOrOptions,
6906
+ maybeOptions,
6907
+ true
6908
+ );
6909
+ return this.where(predicate);
5914
6910
  }
5915
6911
  /**
5916
6912
  * Compiles the query to SQL for a specific dialect
@@ -6042,23 +7038,44 @@ var resolveTableTarget = (target, tableMap) => {
6042
7038
  }
6043
7039
  return table;
6044
7040
  };
7041
+ var toSnakeCase = (value) => {
7042
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
7043
+ };
7044
+ var normalizeEntityName = (value) => {
7045
+ const stripped = value.replace(/Entity$/i, "");
7046
+ const normalized = toSnakeCase(stripped || value);
7047
+ return normalized || "unknown";
7048
+ };
7049
+ var getPivotKeyBaseFromTarget = (target) => {
7050
+ const resolved = unwrapTarget(target);
7051
+ if (isTableDef(resolved)) {
7052
+ return toSnakeCase(resolved.name || "unknown");
7053
+ }
7054
+ const ctor = resolved;
7055
+ return normalizeEntityName(ctor.name || "unknown");
7056
+ };
7057
+ var getPivotKeyBaseFromRoot = (meta) => {
7058
+ return normalizeEntityName(meta.target.name || meta.tableName || "unknown");
7059
+ };
6045
7060
  var buildRelationDefinitions = (meta, tableMap) => {
6046
7061
  const relations = {};
6047
7062
  for (const [name, relation] of Object.entries(meta.relations)) {
6048
7063
  switch (relation.kind) {
6049
7064
  case RelationKinds.HasOne: {
7065
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6050
7066
  relations[name] = hasOne(
6051
7067
  resolveTableTarget(relation.target, tableMap),
6052
- relation.foreignKey,
7068
+ foreignKey,
6053
7069
  relation.localKey,
6054
7070
  relation.cascade
6055
7071
  );
6056
7072
  break;
6057
7073
  }
6058
7074
  case RelationKinds.HasMany: {
7075
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6059
7076
  relations[name] = hasMany(
6060
7077
  resolveTableTarget(relation.target, tableMap),
6061
- relation.foreignKey,
7078
+ foreignKey,
6062
7079
  relation.localKey,
6063
7080
  relation.cascade
6064
7081
  );
@@ -6074,12 +7091,14 @@ var buildRelationDefinitions = (meta, tableMap) => {
6074
7091
  break;
6075
7092
  }
6076
7093
  case RelationKinds.BelongsToMany: {
7094
+ const pivotForeignKeyToRoot = relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
7095
+ const pivotForeignKeyToTarget = relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
6077
7096
  relations[name] = belongsToMany(
6078
7097
  resolveTableTarget(relation.target, tableMap),
6079
7098
  resolveTableTarget(relation.pivotTable, tableMap),
6080
7099
  {
6081
- pivotForeignKeyToRoot: relation.pivotForeignKeyToRoot,
6082
- pivotForeignKeyToTarget: relation.pivotForeignKeyToTarget,
7100
+ pivotForeignKeyToRoot,
7101
+ pivotForeignKeyToTarget,
6083
7102
  localKey: relation.localKey,
6084
7103
  targetKey: relation.targetKey,
6085
7104
  pivotPrimaryKey: relation.pivotPrimaryKey,
@@ -6160,6 +7179,8 @@ function esel(entity, ...props) {
6160
7179
 
6161
7180
  // src/query-builder/insert-query-state.ts
6162
7181
  var InsertQueryState = class _InsertQueryState {
7182
+ table;
7183
+ ast;
6163
7184
  /**
6164
7185
  * Creates a new InsertQueryState instance
6165
7186
  * @param table - The table definition for the INSERT query
@@ -6280,6 +7301,8 @@ var InsertQueryState = class _InsertQueryState {
6280
7301
 
6281
7302
  // src/query-builder/insert.ts
6282
7303
  var InsertQueryBuilder = class _InsertQueryBuilder {
7304
+ table;
7305
+ state;
6283
7306
  /**
6284
7307
  * Creates a new InsertQueryBuilder instance
6285
7308
  * @param table - The table definition for the INSERT query
@@ -6379,6 +7402,8 @@ var isUpdateValue = (value) => {
6379
7402
  }
6380
7403
  };
6381
7404
  var UpdateQueryState = class _UpdateQueryState {
7405
+ table;
7406
+ ast;
6382
7407
  /**
6383
7408
  * Creates a new UpdateQueryState instance
6384
7409
  * @param table - Table definition for the update
@@ -6489,6 +7514,8 @@ var UpdateQueryState = class _UpdateQueryState {
6489
7514
 
6490
7515
  // src/query-builder/update.ts
6491
7516
  var UpdateQueryBuilder = class _UpdateQueryBuilder {
7517
+ table;
7518
+ state;
6492
7519
  /**
6493
7520
  * Creates a new UpdateQueryBuilder instance
6494
7521
  * @param table - The table definition for the UPDATE query
@@ -6606,6 +7633,8 @@ var isTableSourceNode = (source) => typeof source.type === "string";
6606
7633
 
6607
7634
  // src/query-builder/delete-query-state.ts
6608
7635
  var DeleteQueryState = class _DeleteQueryState {
7636
+ table;
7637
+ ast;
6609
7638
  /**
6610
7639
  * Creates a new DeleteQueryState instance
6611
7640
  * @param table - The table definition for the DELETE query
@@ -6684,6 +7713,8 @@ var DeleteQueryState = class _DeleteQueryState {
6684
7713
 
6685
7714
  // src/query-builder/delete.ts
6686
7715
  var DeleteQueryBuilder = class _DeleteQueryBuilder {
7716
+ table;
7717
+ state;
6687
7718
  /**
6688
7719
  * Creates a new DeleteQueryBuilder instance
6689
7720
  * @param table - The table definition for the DELETE query
@@ -6811,7 +7842,8 @@ var renderColumnDefinition = (table, col2, dialect, options = {}) => {
6811
7842
  if (col2.default !== void 0) {
6812
7843
  parts.push(`DEFAULT ${dialect.renderDefault(col2.default, col2)}`);
6813
7844
  }
6814
- if (options.includePrimary && col2.primary) {
7845
+ const autoIncIncludesPrimary = typeof autoInc === "string" && /\bPRIMARY\s+KEY\b/i.test(autoInc);
7846
+ if (options.includePrimary && col2.primary && !autoIncIncludesPrimary) {
6815
7847
  parts.push("PRIMARY KEY");
6816
7848
  }
6817
7849
  if (col2.check) {
@@ -6869,6 +7901,16 @@ var generateSchemaSql = (tables, dialect) => {
6869
7901
  });
6870
7902
  return statements;
6871
7903
  };
7904
+ var generateSchemaSqlFor = (dialect, ...tables) => generateSchemaSql(tables, dialect);
7905
+ var executeSchemaSql = async (executor, tables, dialect) => {
7906
+ const statements = generateSchemaSql(tables, dialect);
7907
+ for (const sql of statements) {
7908
+ await executor.executeSql(sql);
7909
+ }
7910
+ };
7911
+ var executeSchemaSqlFor = async (executor, dialect, ...tables) => {
7912
+ await executeSchemaSql(executor, tables, dialect);
7913
+ };
6872
7914
  var orderTablesByDependencies = (tables) => {
6873
7915
  const map = /* @__PURE__ */ new Map();
6874
7916
  tables.forEach((t) => map.set(t.name, t));
@@ -8775,6 +9817,7 @@ var arrayAppend = (array, value) => fn7("ARRAY_APPEND", [array, value]);
8775
9817
 
8776
9818
  // src/orm/als.ts
8777
9819
  var AsyncLocalStorage = class {
9820
+ store;
8778
9821
  /**
8779
9822
  * Executes a callback function within a context containing the specified store value.
8780
9823
  * The store value is only available during the callback's execution and is automatically
@@ -9271,9 +10314,7 @@ var TypeScriptGenerator = class {
9271
10314
 
9272
10315
  // src/orm/identity-map.ts
9273
10316
  var IdentityMap = class {
9274
- constructor() {
9275
- this.buckets = /* @__PURE__ */ new Map();
9276
- }
10317
+ buckets = /* @__PURE__ */ new Map();
9277
10318
  get bucketsMap() {
9278
10319
  return this.buckets;
9279
10320
  }
@@ -9343,8 +10384,8 @@ var UnitOfWork = class {
9343
10384
  this.executor = executor;
9344
10385
  this.identityMap = identityMap;
9345
10386
  this.hookContext = hookContext;
9346
- this.trackedEntities = /* @__PURE__ */ new Map();
9347
10387
  }
10388
+ trackedEntities = /* @__PURE__ */ new Map();
9348
10389
  /**
9349
10390
  * Gets the identity buckets map.
9350
10391
  */
@@ -9674,12 +10715,12 @@ var UnitOfWork = class {
9674
10715
 
9675
10716
  // src/orm/domain-event-bus.ts
9676
10717
  var DomainEventBus = class {
10718
+ handlers = /* @__PURE__ */ new Map();
9677
10719
  /**
9678
10720
  * Creates a new DomainEventBus instance.
9679
10721
  * @param initialHandlers - Optional initial event handlers
9680
10722
  */
9681
10723
  constructor(initialHandlers) {
9682
- this.handlers = /* @__PURE__ */ new Map();
9683
10724
  if (initialHandlers) {
9684
10725
  for (const key in initialHandlers) {
9685
10726
  const type = key;
@@ -9748,8 +10789,8 @@ var RelationChangeProcessor = class {
9748
10789
  this.unitOfWork = unitOfWork;
9749
10790
  this.dialect = dialect;
9750
10791
  this.executor = executor;
9751
- this.relationChanges = [];
9752
10792
  }
10793
+ relationChanges = [];
9753
10794
  /**
9754
10795
  * Registers a relation change for processing.
9755
10796
  * @param entry - The relation change entry
@@ -10183,25 +11224,24 @@ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
10183
11224
 
10184
11225
  // src/orm/orm-session.ts
10185
11226
  var OrmSession = class {
11227
+ /** The ORM instance */
11228
+ orm;
11229
+ /** The database executor */
11230
+ executor;
11231
+ /** The identity map for tracking entity instances */
11232
+ identityMap;
11233
+ /** The unit of work for tracking entity changes */
11234
+ unitOfWork;
11235
+ /** The domain event bus */
11236
+ domainEvents;
11237
+ /** The relation change processor */
11238
+ relationChanges;
11239
+ interceptors;
10186
11240
  /**
10187
11241
  * Creates a new OrmSession instance.
10188
11242
  * @param opts - Session options
10189
11243
  */
10190
11244
  constructor(opts) {
10191
- /**
10192
- * Registers a relation change.
10193
- * @param root - The root entity
10194
- * @param relationKey - The relation key
10195
- * @param rootTable - The root table definition
10196
- * @param relationName - The relation name
10197
- * @param relation - The relation definition
10198
- * @param change - The relation change
10199
- */
10200
- this.registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
10201
- this.relationChanges.registerChange(
10202
- buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
10203
- );
10204
- };
10205
11245
  this.orm = opts.orm;
10206
11246
  this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
10207
11247
  this.interceptors = [...opts.interceptors ?? []];
@@ -10290,6 +11330,20 @@ var OrmSession = class {
10290
11330
  markRemoved(entity) {
10291
11331
  this.unitOfWork.markRemoved(entity);
10292
11332
  }
11333
+ /**
11334
+ * Registers a relation change.
11335
+ * @param root - The root entity
11336
+ * @param relationKey - The relation key
11337
+ * @param rootTable - The root table definition
11338
+ * @param relationName - The relation name
11339
+ * @param relation - The relation definition
11340
+ * @param change - The relation change
11341
+ */
11342
+ registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
11343
+ this.relationChanges.registerChange(
11344
+ buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
11345
+ );
11346
+ };
10293
11347
  /**
10294
11348
  * Gets all tracked entities for a specific table.
10295
11349
  * @param table - The table definition
@@ -10495,9 +11549,7 @@ var buildRelationChangeEntry = (root, relationKey, rootTable, relationName, rela
10495
11549
 
10496
11550
  // src/orm/interceptor-pipeline.ts
10497
11551
  var InterceptorPipeline = class {
10498
- constructor() {
10499
- this.interceptors = [];
10500
- }
11552
+ interceptors = [];
10501
11553
  use(interceptor) {
10502
11554
  this.interceptors.push(interceptor);
10503
11555
  }
@@ -10516,6 +11568,13 @@ var InterceptorPipeline = class {
10516
11568
 
10517
11569
  // src/orm/orm.ts
10518
11570
  var Orm = class {
11571
+ /** The database dialect */
11572
+ dialect;
11573
+ /** The interceptors pipeline */
11574
+ interceptors;
11575
+ /** The naming strategy */
11576
+ namingStrategy;
11577
+ executorFactory;
10519
11578
  /**
10520
11579
  * Creates a new ORM instance.
10521
11580
  * @param opts - ORM options
@@ -10572,17 +11631,13 @@ var jsonify = (value) => {
10572
11631
 
10573
11632
  // src/decorators/decorator-metadata.ts
10574
11633
  var METADATA_KEY = "metal-orm:decorators";
10575
- var isStandardDecoratorContext = (value) => {
10576
- return typeof value === "object" && value !== null && "kind" in value;
10577
- };
10578
11634
  var getOrCreateMetadataBag = (context) => {
10579
11635
  const metadata = context.metadata || (context.metadata = {});
10580
- const existing = metadata[METADATA_KEY];
10581
- if (existing) {
10582
- return existing;
11636
+ let bag = metadata[METADATA_KEY];
11637
+ if (!bag) {
11638
+ bag = { columns: [], relations: [] };
11639
+ metadata[METADATA_KEY] = bag;
10583
11640
  }
10584
- const bag = { columns: [], relations: [] };
10585
- metadata[METADATA_KEY] = bag;
10586
11641
  return bag;
10587
11642
  };
10588
11643
  var readMetadataBag = (context) => {
@@ -10597,57 +11652,50 @@ var readMetadataBagFromConstructor = (ctor) => {
10597
11652
  var getDecoratorMetadata = (ctor) => readMetadataBagFromConstructor(ctor);
10598
11653
 
10599
11654
  // src/decorators/entity.ts
10600
- var toSnakeCase = (value) => {
11655
+ var toSnakeCase2 = (value) => {
10601
11656
  return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
10602
11657
  };
10603
11658
  var deriveTableNameFromConstructor = (ctor) => {
10604
11659
  const fallback = "unknown";
10605
11660
  const rawName = ctor.name || fallback;
10606
11661
  const strippedName = rawName.replace(/Entity$/i, "");
10607
- const normalized = toSnakeCase(strippedName || rawName);
11662
+ const normalized = toSnakeCase2(strippedName || rawName);
10608
11663
  if (!normalized) {
10609
11664
  return fallback;
10610
11665
  }
10611
11666
  return normalized.endsWith("s") ? normalized : `${normalized}s`;
10612
11667
  };
10613
11668
  function Entity(options = {}) {
10614
- const decorator = (value) => {
10615
- const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
10616
- setEntityTableName(value, tableName, options.hooks);
10617
- return value;
10618
- };
10619
- const decoratorWithContext = (value, context) => {
11669
+ return function(value, context) {
10620
11670
  const ctor = value;
10621
- decorator(ctor);
10622
- if (context && isStandardDecoratorContext(context)) {
10623
- const bag = readMetadataBag(context);
10624
- if (bag) {
10625
- const meta = ensureEntityMetadata(ctor);
10626
- for (const entry of bag.columns) {
10627
- if (meta.columns[entry.propertyName]) {
10628
- throw new Error(
10629
- `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
10630
- );
10631
- }
10632
- addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
11671
+ const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
11672
+ setEntityTableName(ctor, tableName, options.hooks);
11673
+ const bag = readMetadataBag(context);
11674
+ if (bag) {
11675
+ const meta = ensureEntityMetadata(ctor);
11676
+ for (const entry of bag.columns) {
11677
+ if (meta.columns[entry.propertyName]) {
11678
+ throw new Error(
11679
+ `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11680
+ );
10633
11681
  }
10634
- for (const entry of bag.relations) {
10635
- if (meta.relations[entry.propertyName]) {
10636
- throw new Error(
10637
- `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
10638
- );
10639
- }
10640
- const relationCopy = entry.relation.kind === RelationKinds.BelongsToMany ? {
10641
- ...entry.relation,
10642
- defaultPivotColumns: entry.relation.defaultPivotColumns ? [...entry.relation.defaultPivotColumns] : void 0
10643
- } : { ...entry.relation };
10644
- addRelationMetadata(ctor, entry.propertyName, relationCopy);
11682
+ addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
11683
+ }
11684
+ for (const entry of bag.relations) {
11685
+ if (meta.relations[entry.propertyName]) {
11686
+ throw new Error(
11687
+ `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11688
+ );
10645
11689
  }
11690
+ const relationCopy = entry.relation.kind === RelationKinds.BelongsToMany ? {
11691
+ ...entry.relation,
11692
+ defaultPivotColumns: entry.relation.defaultPivotColumns ? [...entry.relation.defaultPivotColumns] : void 0
11693
+ } : { ...entry.relation };
11694
+ addRelationMetadata(ctor, entry.propertyName, relationCopy);
10646
11695
  }
10647
11696
  }
10648
11697
  return ctor;
10649
11698
  };
10650
- return decoratorWithContext;
10651
11699
  }
10652
11700
 
10653
11701
  // src/decorators/column-decorator.ts
@@ -10680,26 +11728,13 @@ var normalizePropertyName = (name) => {
10680
11728
  }
10681
11729
  return name;
10682
11730
  };
10683
- var resolveConstructor = (target) => {
10684
- if (typeof target === "function") {
10685
- return target;
10686
- }
10687
- if (target && typeof target.constructor === "function") {
10688
- return target.constructor;
10689
- }
10690
- return void 0;
10691
- };
10692
- var registerColumn = (ctor, propertyName, column) => {
10693
- const meta = ensureEntityMetadata(ctor);
10694
- if (meta.columns[propertyName]) {
10695
- return;
10696
- }
10697
- addColumnMetadata(ctor, propertyName, column);
10698
- };
10699
11731
  var registerColumnFromContext = (context, column) => {
10700
11732
  if (!context.name) {
10701
11733
  throw new Error("Column decorator requires a property name");
10702
11734
  }
11735
+ if (context.private) {
11736
+ throw new Error("Column decorator does not support private fields");
11737
+ }
10703
11738
  const propertyName = normalizePropertyName(context.name);
10704
11739
  const bag = getOrCreateMetadataBag(context);
10705
11740
  if (!bag.columns.some((entry) => entry.propertyName === propertyName)) {
@@ -10708,19 +11743,9 @@ var registerColumnFromContext = (context, column) => {
10708
11743
  };
10709
11744
  function Column(definition) {
10710
11745
  const normalized = normalizeColumnInput(definition);
10711
- const decorator = (targetOrValue, propertyKeyOrContext) => {
10712
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
10713
- registerColumnFromContext(propertyKeyOrContext, normalized);
10714
- return;
10715
- }
10716
- const propertyName = normalizePropertyName(propertyKeyOrContext);
10717
- const ctor = resolveConstructor(targetOrValue);
10718
- if (!ctor) {
10719
- throw new Error("Unable to resolve constructor when registering column metadata");
10720
- }
10721
- registerColumn(ctor, propertyName, { ...normalized });
11746
+ return function(_value, context) {
11747
+ registerColumnFromContext(context, normalized);
10722
11748
  };
10723
- return decorator;
10724
11749
  }
10725
11750
  function PrimaryKey(definition) {
10726
11751
  const normalized = normalizeColumnInput(definition);
@@ -10735,41 +11760,21 @@ var normalizePropertyName2 = (name) => {
10735
11760
  }
10736
11761
  return name;
10737
11762
  };
10738
- var resolveConstructor2 = (instanceOrCtor) => {
10739
- if (typeof instanceOrCtor === "function") {
10740
- return instanceOrCtor;
10741
- }
10742
- if (instanceOrCtor && typeof instanceOrCtor.constructor === "function") {
10743
- return instanceOrCtor.constructor;
10744
- }
10745
- return void 0;
10746
- };
10747
- var registerRelation = (ctor, propertyName, metadata) => {
10748
- addRelationMetadata(ctor, propertyName, metadata);
10749
- };
10750
11763
  var createFieldDecorator = (metadataFactory) => {
10751
- const decorator = (targetOrValue, propertyKeyOrContext) => {
10752
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
10753
- const ctx = propertyKeyOrContext;
10754
- if (!ctx.name) {
10755
- throw new Error("Relation decorator requires a property name");
10756
- }
10757
- const propertyName2 = normalizePropertyName2(ctx.name);
10758
- const bag = getOrCreateMetadataBag(ctx);
10759
- const relationMetadata = metadataFactory(propertyName2);
10760
- if (!bag.relations.some((entry) => entry.propertyName === propertyName2)) {
10761
- bag.relations.push({ propertyName: propertyName2, relation: relationMetadata });
10762
- }
10763
- return;
11764
+ return function(_value, context) {
11765
+ if (!context.name) {
11766
+ throw new Error("Relation decorator requires a property name");
10764
11767
  }
10765
- const propertyName = normalizePropertyName2(propertyKeyOrContext);
10766
- const ctor = resolveConstructor2(targetOrValue);
10767
- if (!ctor) {
10768
- throw new Error("Unable to resolve constructor when registering relation metadata");
11768
+ if (context.private) {
11769
+ throw new Error("Relation decorator does not support private fields");
11770
+ }
11771
+ const propertyName = normalizePropertyName2(context.name);
11772
+ const bag = getOrCreateMetadataBag(context);
11773
+ const relationMetadata = metadataFactory(propertyName);
11774
+ if (!bag.relations.some((entry) => entry.propertyName === propertyName)) {
11775
+ bag.relations.push({ propertyName, relation: relationMetadata });
10769
11776
  }
10770
- registerRelation(ctor, propertyName, metadataFactory(propertyName));
10771
11777
  };
10772
- return decorator;
10773
11778
  };
10774
11779
  function HasMany(options) {
10775
11780
  return createFieldDecorator((propertyName) => ({
@@ -10796,7 +11801,7 @@ function BelongsTo(options) {
10796
11801
  kind: RelationKinds.BelongsTo,
10797
11802
  propertyKey: propertyName,
10798
11803
  target: options.target,
10799
- foreignKey: options.foreignKey,
11804
+ foreignKey: options.foreignKey ?? `${propertyName}_id`,
10800
11805
  localKey: options.localKey,
10801
11806
  cascade: options.cascade
10802
11807
  }));
@@ -10872,13 +11877,15 @@ var deferred = () => {
10872
11877
  return { promise, resolve, reject };
10873
11878
  };
10874
11879
  var Pool = class {
11880
+ adapter;
11881
+ options;
11882
+ destroyed = false;
11883
+ creating = 0;
11884
+ leased = 0;
11885
+ idle = [];
11886
+ waiters = [];
11887
+ reapTimer = null;
10875
11888
  constructor(adapter, options) {
10876
- this.destroyed = false;
10877
- this.creating = 0;
10878
- this.leased = 0;
10879
- this.idle = [];
10880
- this.waiters = [];
10881
- this.reapTimer = null;
10882
11889
  if (!Number.isFinite(options.max) || options.max <= 0) {
10883
11890
  throw new Error("Pool options.max must be a positive number");
10884
11891
  }
@@ -11440,6 +12447,8 @@ export {
11440
12447
  esel,
11441
12448
  executeHydrated,
11442
12449
  executeHydratedWithContexts,
12450
+ executeSchemaSql,
12451
+ executeSchemaSqlFor,
11443
12452
  exists,
11444
12453
  exp,
11445
12454
  extract,
@@ -11448,6 +12457,7 @@ export {
11448
12457
  fromUnixTime,
11449
12458
  generateCreateTableSql,
11450
12459
  generateSchemaSql,
12460
+ generateSchemaSqlFor,
11451
12461
  getColumn,
11452
12462
  getDecoratorMetadata,
11453
12463
  getSchemaIntrospector,
@@ -11538,6 +12548,7 @@ export {
11538
12548
  registerExpressionDispatcher,
11539
12549
  registerOperandDispatcher,
11540
12550
  registerSchemaIntrospector,
12551
+ relationLoaderCache,
11541
12552
  renderColumnDefinition,
11542
12553
  renderTypeWithArgs,
11543
12554
  repeat,
@@ -11552,6 +12563,7 @@ export {
11552
12563
  second,
11553
12564
  sel,
11554
12565
  selectFromEntity,
12566
+ setRelations,
11555
12567
  sha1,
11556
12568
  sha2,
11557
12569
  shiftLeft,