metal-orm 1.0.58 → 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 (40) hide show
  1. package/README.md +34 -31
  2. package/dist/index.cjs +1463 -1003
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +148 -129
  5. package/dist/index.d.ts +148 -129
  6. package/dist/index.js +1459 -1003
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/core/ddl/schema-generator.ts +44 -1
  10. package/src/decorators/bootstrap.ts +183 -146
  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 +13 -11
  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 -410
  21. package/src/orm/execute.ts +4 -4
  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 -492
  28. package/src/orm/relations/many-to-many.ts +2 -1
  29. package/src/query-builder/relation-cte-builder.ts +63 -0
  30. package/src/query-builder/relation-filter-utils.ts +159 -0
  31. package/src/query-builder/relation-include-strategies.ts +177 -0
  32. package/src/query-builder/relation-join-planner.ts +80 -0
  33. package/src/query-builder/relation-service.ts +119 -479
  34. package/src/query-builder/relation-types.ts +41 -10
  35. package/src/query-builder/select/projection-facet.ts +23 -23
  36. package/src/query-builder/select/select-operations.ts +145 -0
  37. package/src/query-builder/select.ts +351 -422
  38. package/src/schema/relation.ts +22 -18
  39. package/src/schema/table.ts +22 -9
  40. package/src/schema/types.ts +14 -12
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
@@ -3920,199 +3921,150 @@ var buildRelationCorrelation = (root, relation, rootAlias, targetTableName) => {
3920
3921
  // src/core/ast/join-metadata.ts
3921
3922
  var getJoinRelationName = (join) => join.meta?.relationName;
3922
3923
 
3923
- // src/query-builder/relation-service.ts
3924
- var hasRelationForeignKey = (relation) => relation.type !== RelationKinds.BelongsToMany;
3925
- var RelationService = class {
3926
- /**
3927
- * Creates a new RelationService instance
3928
- * @param table - Table definition
3929
- * @param state - Current query state
3930
- * @param hydration - Hydration manager
3931
- */
3932
- constructor(table, state, hydration, createQueryAstService) {
3933
- this.table = table;
3934
- this.state = state;
3935
- this.hydration = hydration;
3936
- this.createQueryAstService = createQueryAstService;
3937
- this.projectionHelper = new RelationProjectionHelper(
3938
- table,
3939
- (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
3940
- );
3941
- }
3942
- /**
3943
- * Joins a relation to the query
3944
- * @param relationName - Name of the relation to join
3945
- * @param joinKind - Type of join to use
3946
- * @param extraCondition - Additional join condition
3947
- * @returns Relation result with updated state and hydration
3948
- */
3949
- joinRelation(relationName, joinKind, extraCondition, tableSource) {
3950
- const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition, tableSource);
3951
- return { state: nextState, hydration: this.hydration };
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
+ }
3952
3935
  }
3953
- /**
3954
- * Matches records based on a relation with an optional predicate
3955
- * @param relationName - Name of the relation to match
3956
- * @param predicate - Optional predicate expression
3957
- * @returns Relation result with updated state and hydration
3958
- */
3959
- match(relationName, predicate) {
3960
- const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
3961
- const pk = findPrimaryKey(this.table);
3962
- const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
3963
- const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
3964
- const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
3965
- return { state: nextState, hydration: joined.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));
3966
3942
  }
3967
- /**
3968
- * Includes a relation in the query result
3969
- * @param relationName - Name of the relation to include
3970
- * @param options - Options for relation inclusion
3971
- * @returns Relation result with updated state and hydration
3972
- */
3973
- include(relationName, options) {
3974
- let state = this.state;
3975
- let hydration = this.hydration;
3976
- const relation = this.getRelation(relationName);
3977
- const aliasPrefix = options?.aliasPrefix ?? relationName;
3978
- const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
3979
- const { selfFilters, crossFilters } = this.splitFilterExpressions(
3980
- options?.filter,
3981
- /* @__PURE__ */ new Set([relation.target.name])
3982
- );
3983
- const canUseCte = !alreadyJoined && selfFilters.length > 0;
3984
- const joinFilters = [...crossFilters];
3985
- if (!canUseCte) {
3986
- joinFilters.push(...selfFilters);
3987
- }
3988
- const joinCondition = this.combineWithAnd(joinFilters);
3989
- let tableSourceOverride;
3990
- if (canUseCte) {
3991
- const cteInfo = this.createFilteredRelationCte(state, relationName, relation, selfFilters);
3992
- state = cteInfo.state;
3993
- tableSourceOverride = cteInfo.table;
3994
- }
3995
- if (!alreadyJoined) {
3996
- state = this.withJoin(
3997
- state,
3998
- relationName,
3999
- options?.joinKind ?? JOIN_KINDS.LEFT,
4000
- joinCondition,
4001
- tableSourceOverride
4002
- );
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;
4003
3952
  }
4004
- const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
4005
- state = projectionResult.state;
4006
- hydration = projectionResult.hydration;
4007
- if (hasRelationForeignKey(relation)) {
4008
- const fkColumn = this.table.columns[relation.foreignKey];
4009
- if (fkColumn) {
4010
- const hasForeignKeySelected = state.ast.columns.some((col2) => {
4011
- if (col2.type !== "Column") return false;
4012
- const node = col2;
4013
- const alias = node.alias ?? node.name;
4014
- return alias === relation.foreignKey;
4015
- });
4016
- if (!hasForeignKeySelected) {
4017
- const fkSelectionResult = this.selectColumns(state, hydration, {
4018
- [relation.foreignKey]: fkColumn
4019
- });
4020
- state = fkSelectionResult.state;
4021
- hydration = fkSelectionResult.hydration;
4022
- }
3953
+ }
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;
4023
3982
  }
4024
- }
4025
- const requestedColumns = options?.columns?.length ? [...options.columns] : Object.keys(relation.target.columns);
4026
- const targetPrimaryKey = findPrimaryKey(relation.target);
4027
- if (!requestedColumns.includes(targetPrimaryKey)) {
4028
- requestedColumns.push(targetPrimaryKey);
4029
- }
4030
- const targetColumns = requestedColumns;
4031
- const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
4032
- return keys.reduce((acc, key) => {
4033
- const def = columns[key];
4034
- if (!def) {
4035
- throw new Error(missingMsg(key));
4036
- }
4037
- acc[makeRelationAlias(prefix, key)] = def;
4038
- return acc;
4039
- }, {});
4040
- };
4041
- const targetSelection = buildTypedSelection(
4042
- relation.target.columns,
4043
- aliasPrefix,
4044
- targetColumns,
4045
- (key) => `Column '${key}' not found on relation '${relationName}'`
4046
- );
4047
- if (relation.type !== RelationKinds.BelongsToMany) {
4048
- const relationSelectionResult2 = this.selectColumns(state, hydration, targetSelection);
4049
- state = relationSelectionResult2.state;
4050
- hydration = relationSelectionResult2.hydration;
4051
- hydration = hydration.onRelationIncluded(
4052
- state,
4053
- relation,
4054
- relationName,
4055
- aliasPrefix,
4056
- targetColumns
4057
- );
4058
- return { state, hydration };
4059
- }
4060
- const many = relation;
4061
- const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
4062
- const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
4063
- const pivotColumns = options?.pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
4064
- const pivotSelection = buildTypedSelection(
4065
- many.pivotTable.columns,
4066
- pivotAliasPrefix,
4067
- pivotColumns,
4068
- (key) => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
4069
- );
4070
- const combinedSelection = {
4071
- ...targetSelection,
4072
- ...pivotSelection
4073
- };
4074
- const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
4075
- state = relationSelectionResult.state;
4076
- hydration = relationSelectionResult.hydration;
4077
- hydration = hydration.onRelationIncluded(
4078
- state,
4079
- relation,
4080
- relationName,
4081
- aliasPrefix,
4082
- targetColumns,
4083
- { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
4084
- );
4085
- return { state, hydration };
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;
4086
3999
  }
4087
- /**
4088
- * Applies relation correlation to a query AST
4089
- * @param relationName - Name of the relation
4090
- * @param ast - Query AST to modify
4091
- * @returns Modified query AST with relation correlation
4092
- */
4093
- applyRelationCorrelation(relationName, ast, additionalCorrelation) {
4094
- const relation = this.getRelation(relationName);
4095
- const rootAlias = this.state.ast.from.type === "Table" ? this.state.ast.from.alias : void 0;
4096
- let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
4097
- if (additionalCorrelation) {
4098
- correlation = and(correlation, additionalCorrelation);
4099
- }
4100
- const whereInSubquery = ast.where ? and(correlation, ast.where) : correlation;
4101
- return {
4102
- ...ast,
4103
- where: whereInSubquery
4104
- };
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;
4105
4051
  }
4106
- /**
4107
- * Creates a join node for a relation
4108
- * @param state - Current query state
4109
- * @param relationName - Name of the relation
4110
- * @param joinKind - Type of join to use
4111
- * @param extraCondition - Additional join condition
4112
- * @returns Updated query state with join
4113
- */
4114
- withJoin(state, relationName, joinKind, extraCondition, tableSource) {
4115
- const relation = this.getRelation(relationName);
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) {
4116
4068
  const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4117
4069
  if (relation.type === RelationKinds.BelongsToMany) {
4118
4070
  const targetTableSource = tableSource ?? {
@@ -4149,167 +4101,31 @@ var RelationService = class {
4149
4101
  const joinNode = createJoinNode(joinKind, targetTable, condition, relationName);
4150
4102
  return this.astService(state).withJoin(joinNode);
4151
4103
  }
4152
- /**
4153
- * Selects columns for a relation
4154
- * @param state - Current query state
4155
- * @param hydration - Hydration manager
4156
- * @param columns - Columns to select
4157
- * @returns Relation result with updated state and hydration
4158
- */
4159
- selectColumns(state, hydration, columns) {
4160
- const { state: nextState, addedColumns } = this.astService(state).select(columns);
4161
- return {
4162
- state: nextState,
4163
- hydration: hydration.onColumnsSelected(nextState, addedColumns)
4164
- };
4165
- }
4166
- combineWithAnd(expressions) {
4167
- if (expressions.length === 0) return void 0;
4168
- if (expressions.length === 1) return expressions[0];
4169
- return {
4170
- type: "LogicalExpression",
4171
- operator: "AND",
4172
- operands: expressions
4173
- };
4174
- }
4175
- splitFilterExpressions(filter, allowedTables) {
4176
- const terms = this.flattenAnd(filter);
4177
- const selfFilters = [];
4178
- const crossFilters = [];
4179
- for (const term of terms) {
4180
- if (this.isExpressionSelfContained(term, allowedTables)) {
4181
- selfFilters.push(term);
4182
- } else {
4183
- crossFilters.push(term);
4184
- }
4185
- }
4186
- return { selfFilters, crossFilters };
4187
- }
4188
- flattenAnd(node) {
4189
- if (!node) return [];
4190
- if (node.type === "LogicalExpression" && node.operator === "AND") {
4191
- return node.operands.flatMap((operand) => this.flattenAnd(operand));
4192
- }
4193
- return [node];
4104
+ astService(state) {
4105
+ return this.createQueryAstService(this.table, state);
4194
4106
  }
4195
- isExpressionSelfContained(expr, allowedTables) {
4196
- const collector = this.collectReferencedTables(expr);
4197
- if (collector.hasSubquery) return false;
4198
- if (collector.tables.size === 0) return true;
4199
- for (const table of collector.tables) {
4200
- if (!allowedTables.has(table)) {
4201
- return false;
4202
- }
4107
+ resolveTargetTableName(target, relation) {
4108
+ if (target.type === "Table") {
4109
+ return target.alias ?? target.name;
4203
4110
  }
4204
- return true;
4205
- }
4206
- collectReferencedTables(expr) {
4207
- const collector = {
4208
- tables: /* @__PURE__ */ new Set(),
4209
- hasSubquery: false
4210
- };
4211
- this.collectFromExpression(expr, collector);
4212
- return collector;
4213
- }
4214
- collectFromExpression(expr, collector) {
4215
- switch (expr.type) {
4216
- case "BinaryExpression":
4217
- this.collectFromOperand(expr.left, collector);
4218
- this.collectFromOperand(expr.right, collector);
4219
- break;
4220
- case "LogicalExpression":
4221
- expr.operands.forEach((operand) => this.collectFromExpression(operand, collector));
4222
- break;
4223
- case "NullExpression":
4224
- this.collectFromOperand(expr.left, collector);
4225
- break;
4226
- case "InExpression":
4227
- this.collectFromOperand(expr.left, collector);
4228
- if (Array.isArray(expr.right)) {
4229
- expr.right.forEach((value) => this.collectFromOperand(value, collector));
4230
- } else {
4231
- collector.hasSubquery = true;
4232
- }
4233
- break;
4234
- case "ExistsExpression":
4235
- collector.hasSubquery = true;
4236
- break;
4237
- case "BetweenExpression":
4238
- this.collectFromOperand(expr.left, collector);
4239
- this.collectFromOperand(expr.lower, collector);
4240
- this.collectFromOperand(expr.upper, collector);
4241
- break;
4242
- case "ArithmeticExpression":
4243
- case "BitwiseExpression":
4244
- this.collectFromOperand(expr.left, collector);
4245
- this.collectFromOperand(expr.right, collector);
4246
- break;
4247
- default:
4248
- break;
4111
+ if (target.type === "DerivedTable") {
4112
+ return target.alias;
4249
4113
  }
4250
- }
4251
- collectFromOperand(node, collector) {
4252
- switch (node.type) {
4253
- case "Column":
4254
- collector.tables.add(node.table);
4255
- break;
4256
- case "Function":
4257
- node.args.forEach((arg) => this.collectFromOperand(arg, collector));
4258
- if (node.separator) {
4259
- this.collectFromOperand(node.separator, collector);
4260
- }
4261
- if (node.orderBy) {
4262
- node.orderBy.forEach((order) => this.collectFromOrderingTerm(order.term, collector));
4263
- }
4264
- break;
4265
- case "JsonPath":
4266
- this.collectFromOperand(node.column, collector);
4267
- break;
4268
- case "ScalarSubquery":
4269
- collector.hasSubquery = true;
4270
- break;
4271
- case "CaseExpression":
4272
- node.conditions.forEach(({ when, then }) => {
4273
- this.collectFromExpression(when, collector);
4274
- this.collectFromOperand(then, collector);
4275
- });
4276
- if (node.else) {
4277
- this.collectFromOperand(node.else, collector);
4278
- }
4279
- break;
4280
- case "Cast":
4281
- this.collectFromOperand(node.expression, collector);
4282
- break;
4283
- case "WindowFunction":
4284
- node.args.forEach((arg) => this.collectFromOperand(arg, collector));
4285
- node.partitionBy?.forEach((part) => this.collectFromOperand(part, collector));
4286
- node.orderBy?.forEach((order) => this.collectFromOrderingTerm(order.term, collector));
4287
- break;
4288
- case "Collate":
4289
- this.collectFromOperand(node.expression, collector);
4290
- break;
4291
- case "ArithmeticExpression":
4292
- case "BitwiseExpression":
4293
- this.collectFromOperand(node.left, collector);
4294
- this.collectFromOperand(node.right, collector);
4295
- break;
4296
- case "Literal":
4297
- case "AliasRef":
4298
- break;
4299
- default:
4300
- break;
4114
+ if (target.type === "FunctionTable") {
4115
+ return target.alias ?? relation.target.name;
4301
4116
  }
4117
+ return relation.target.name;
4302
4118
  }
4303
- collectFromOrderingTerm(term, collector) {
4304
- if (isOperandNode(term)) {
4305
- this.collectFromOperand(term, collector);
4306
- return;
4307
- }
4308
- this.collectFromExpression(term, collector);
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;
4309
4126
  }
4310
- createFilteredRelationCte(state, relationName, relation, filters) {
4127
+ createFilteredRelationCte(state, relationName, relation, predicate) {
4311
4128
  const cteName = this.generateUniqueCteName(state, relationName);
4312
- const predicate = this.combineWithAnd(filters);
4313
4129
  if (!predicate) {
4314
4130
  throw new Error("Unable to build filter CTE without predicates.");
4315
4131
  }
@@ -4343,82 +4159,339 @@ var RelationService = class {
4343
4159
  }
4344
4160
  return candidate;
4345
4161
  }
4346
- resolveTargetTableName(target, relation) {
4347
- if (target.type === "Table") {
4348
- return target.alias ?? target.name;
4349
- }
4350
- if (target.type === "DerivedTable") {
4351
- return target.alias;
4352
- }
4353
- if (target.type === "FunctionTable") {
4354
- return target.alias ?? relation.target.name;
4355
- }
4356
- return relation.target.name;
4162
+ astService(state) {
4163
+ return this.createQueryAstService(this.table, state);
4357
4164
  }
4358
- /**
4359
- * Gets a relation definition by name
4360
- * @param relationName - Name of the relation
4361
- * @returns Relation definition
4362
- * @throws Error if relation is not found
4363
- */
4364
- getRelation(relationName) {
4365
- const relation = this.table.relations[relationName];
4366
- if (!relation) {
4367
- throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
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));
4368
4173
  }
4369
- return relation;
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);
4370
4183
  }
4371
- /**
4372
- * Creates a QueryAstService instance
4373
- * @param state - Current query state
4374
- * @returns QueryAstService instance
4375
- */
4376
- astService(state = this.state) {
4377
- return this.createQueryAstService(this.table, state);
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 };
4378
4190
  }
4379
- rootTableName() {
4380
- const from = this.state.ast.from;
4381
- if (from.type === "Table" && from.alias) return from.alias;
4382
- return this.table.name;
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 };
4383
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
4384
4271
  };
4385
4272
 
4386
- // src/query-builder/column-selector.ts
4387
- var ColumnSelector = class {
4273
+ // src/query-builder/relation-service.ts
4274
+ var RelationService = class {
4388
4275
  /**
4389
- * Creates a new ColumnSelector instance
4390
- * @param env - Query builder environment
4276
+ * Creates a new RelationService instance
4277
+ * @param table - Table definition
4278
+ * @param state - Current query state
4279
+ * @param hydration - Hydration manager
4391
4280
  */
4392
- constructor(env) {
4393
- this.env = env;
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);
4394
4292
  }
4293
+ projectionHelper;
4294
+ joinPlanner;
4295
+ cteBuilder;
4395
4296
  /**
4396
- * Selects columns for the query
4397
- * @param context - Current query context
4398
- * @param columns - Columns to select
4399
- * @returns Updated query context with selected columns
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
4400
4302
  */
4401
- select(context, columns) {
4402
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
4403
- const { state: nextState, addedColumns } = astService.select(columns);
4404
- return {
4405
- state: nextState,
4406
- hydration: context.hydration.onColumnsSelected(nextState, addedColumns)
4407
- };
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 };
4408
4314
  }
4409
4315
  /**
4410
- * Selects raw column expressions
4411
- * @param context - Current query context
4412
- * @param columns - Raw column expressions
4413
- * @returns Updated query context with raw column selections
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
4414
4320
  */
4415
- selectRaw(context, columns) {
4416
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
4417
- const nextState = astService.selectRaw(columns).state;
4418
- return { state: nextState, hydration: context.hydration };
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 };
4419
4328
  }
4420
4329
  /**
4421
- * Selects a subquery as a column
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,
4357
+ relation,
4358
+ predicate
4359
+ );
4360
+ state = cteInfo.state;
4361
+ tableSourceOverride = cteInfo.table;
4362
+ }
4363
+ if (!alreadyJoined) {
4364
+ state = this.joinPlanner.withJoin(
4365
+ state,
4366
+ relationName,
4367
+ relation,
4368
+ options?.joinKind ?? JOIN_KINDS.LEFT,
4369
+ joinCondition,
4370
+ tableSourceOverride
4371
+ );
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,
4379
+ state,
4380
+ hydration,
4381
+ relation,
4382
+ relationName,
4383
+ aliasPrefix,
4384
+ options,
4385
+ selectColumns: (nextState, nextHydration, columns) => this.selectColumns(nextState, nextHydration, columns)
4386
+ });
4387
+ return { state: result.state, hydration: result.hydration };
4388
+ }
4389
+ /**
4390
+ * Applies relation correlation to a query AST
4391
+ * @param relationName - Name of the relation
4392
+ * @param ast - Query AST to modify
4393
+ * @returns Modified query AST with relation correlation
4394
+ */
4395
+ applyRelationCorrelation(relationName, ast, additionalCorrelation) {
4396
+ const relation = this.getRelation(relationName);
4397
+ const rootAlias = this.state.ast.from.type === "Table" ? this.state.ast.from.alias : void 0;
4398
+ let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
4399
+ if (additionalCorrelation) {
4400
+ correlation = and(correlation, additionalCorrelation);
4401
+ }
4402
+ const whereInSubquery = ast.where ? and(correlation, ast.where) : correlation;
4403
+ return {
4404
+ ...ast,
4405
+ where: whereInSubquery
4406
+ };
4407
+ }
4408
+ /**
4409
+ * Selects columns for a relation
4410
+ * @param state - Current query state
4411
+ * @param hydration - Hydration manager
4412
+ * @param columns - Columns to select
4413
+ * @returns Relation result with updated state and hydration
4414
+ */
4415
+ selectColumns(state, hydration, columns) {
4416
+ const { state: nextState, addedColumns } = this.astService(state).select(columns);
4417
+ return {
4418
+ state: nextState,
4419
+ hydration: hydration.onColumnsSelected(nextState, addedColumns)
4420
+ };
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
+ }
4431
+ /**
4432
+ * Gets a relation definition by name
4433
+ * @param relationName - Name of the relation
4434
+ * @returns Relation definition
4435
+ * @throws Error if relation is not found
4436
+ */
4437
+ getRelation(relationName) {
4438
+ const relation = this.table.relations[relationName];
4439
+ if (!relation) {
4440
+ throw new Error(`Relation '${relationName}' not found on table '${this.table.name}'`);
4441
+ }
4442
+ return relation;
4443
+ }
4444
+ /**
4445
+ * Creates a QueryAstService instance
4446
+ * @param state - Current query state
4447
+ * @returns QueryAstService instance
4448
+ */
4449
+ astService(state = this.state) {
4450
+ return this.createQueryAstService(this.table, state);
4451
+ }
4452
+ rootTableName() {
4453
+ const from = this.state.ast.from;
4454
+ if (from.type === "Table" && from.alias) return from.alias;
4455
+ return this.table.name;
4456
+ }
4457
+ };
4458
+
4459
+ // src/query-builder/column-selector.ts
4460
+ var ColumnSelector = class {
4461
+ /**
4462
+ * Creates a new ColumnSelector instance
4463
+ * @param env - Query builder environment
4464
+ */
4465
+ constructor(env) {
4466
+ this.env = env;
4467
+ }
4468
+ /**
4469
+ * Selects columns for the query
4470
+ * @param context - Current query context
4471
+ * @param columns - Columns to select
4472
+ * @returns Updated query context with selected columns
4473
+ */
4474
+ select(context, columns) {
4475
+ const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
4476
+ const { state: nextState, addedColumns } = astService.select(columns);
4477
+ return {
4478
+ state: nextState,
4479
+ hydration: context.hydration.onColumnsSelected(nextState, addedColumns)
4480
+ };
4481
+ }
4482
+ /**
4483
+ * Selects raw column expressions
4484
+ * @param context - Current query context
4485
+ * @param columns - Raw column expressions
4486
+ * @returns Updated query context with raw column selections
4487
+ */
4488
+ selectRaw(context, columns) {
4489
+ const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
4490
+ const nextState = astService.selectRaw(columns).state;
4491
+ return { state: nextState, hydration: context.hydration };
4492
+ }
4493
+ /**
4494
+ * Selects a subquery as a column
4422
4495
  * @param context - Current query context
4423
4496
  * @param alias - Alias for the subquery
4424
4497
  * @param query - Subquery to select
@@ -4643,8 +4716,52 @@ var hasEntityMeta = (entity) => {
4643
4716
  return Boolean(getEntityMeta(entity));
4644
4717
  };
4645
4718
 
4646
- // src/orm/relations/has-many.ts
4719
+ // src/orm/entity-hydration.ts
4647
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);
4648
4765
  var hideInternal = (obj, keys) => {
4649
4766
  for (const key of keys) {
4650
4767
  Object.defineProperty(obj, key, {
@@ -4678,13 +4795,13 @@ var DefaultHasManyCollection = class {
4678
4795
  this.loader = loader;
4679
4796
  this.createEntity = createEntity;
4680
4797
  this.localKey = localKey;
4681
- this.loaded = false;
4682
- this.items = [];
4683
- this.added = /* @__PURE__ */ new Set();
4684
- this.removed = /* @__PURE__ */ new Set();
4685
4798
  hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
4686
4799
  this.hydrateFromCache();
4687
4800
  }
4801
+ loaded = false;
4802
+ items = [];
4803
+ added = /* @__PURE__ */ new Set();
4804
+ removed = /* @__PURE__ */ new Set();
4688
4805
  /**
4689
4806
  * Loads the related entities if not already loaded.
4690
4807
  * @returns Promise resolving to the array of child entities
@@ -4692,7 +4809,7 @@ var DefaultHasManyCollection = class {
4692
4809
  async load() {
4693
4810
  if (this.loaded) return this.items;
4694
4811
  const map = await this.loader();
4695
- const key = toKey2(this.root[this.localKey]);
4812
+ const key = toKey3(this.root[this.localKey]);
4696
4813
  const rows = map.get(key) ?? [];
4697
4814
  this.items = rows.map((row) => this.createEntity(row));
4698
4815
  this.loaded = true;
@@ -4804,7 +4921,7 @@ var DefaultHasManyCollection = class {
4804
4921
  };
4805
4922
 
4806
4923
  // src/orm/relations/has-one.ts
4807
- var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
4924
+ var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
4808
4925
  var hideInternal2 = (obj, keys) => {
4809
4926
  for (const key of keys) {
4810
4927
  Object.defineProperty(obj, key, {
@@ -4837,8 +4954,6 @@ var DefaultHasOneReference = class {
4837
4954
  this.loader = loader;
4838
4955
  this.createEntity = createEntity;
4839
4956
  this.localKey = localKey;
4840
- this.loaded = false;
4841
- this.current = null;
4842
4957
  hideInternal2(this, [
4843
4958
  "ctx",
4844
4959
  "meta",
@@ -4852,6 +4967,8 @@ var DefaultHasOneReference = class {
4852
4967
  ]);
4853
4968
  this.populateFromHydrationCache();
4854
4969
  }
4970
+ loaded = false;
4971
+ current = null;
4855
4972
  async load() {
4856
4973
  if (this.loaded) return this.current;
4857
4974
  const map = await this.loader();
@@ -4860,7 +4977,7 @@ var DefaultHasOneReference = class {
4860
4977
  this.loaded = true;
4861
4978
  return this.current;
4862
4979
  }
4863
- const row = map.get(toKey3(keyValue));
4980
+ const row = map.get(toKey4(keyValue));
4864
4981
  this.current = row ? this.createEntity(row) : null;
4865
4982
  this.loaded = true;
4866
4983
  return this.current;
@@ -4932,7 +5049,7 @@ var DefaultHasOneReference = class {
4932
5049
  };
4933
5050
 
4934
5051
  // src/orm/relations/belongs-to.ts
4935
- var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
5052
+ var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
4936
5053
  var hideInternal3 = (obj, keys) => {
4937
5054
  for (const key of keys) {
4938
5055
  Object.defineProperty(obj, key, {
@@ -4965,11 +5082,11 @@ var DefaultBelongsToReference = class {
4965
5082
  this.loader = loader;
4966
5083
  this.createEntity = createEntity;
4967
5084
  this.targetKey = targetKey;
4968
- this.loaded = false;
4969
- this.current = null;
4970
5085
  hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
4971
5086
  this.populateFromHydrationCache();
4972
5087
  }
5088
+ loaded = false;
5089
+ current = null;
4973
5090
  async load() {
4974
5091
  if (this.loaded) return this.current;
4975
5092
  const map = await this.loader();
@@ -4977,7 +5094,7 @@ var DefaultBelongsToReference = class {
4977
5094
  if (fkValue === null || fkValue === void 0) {
4978
5095
  this.current = null;
4979
5096
  } else {
4980
- const row = map.get(toKey4(fkValue));
5097
+ const row = map.get(toKey5(fkValue));
4981
5098
  this.current = row ? this.createEntity(row) : null;
4982
5099
  }
4983
5100
  this.loaded = true;
@@ -5034,7 +5151,7 @@ var DefaultBelongsToReference = class {
5034
5151
  };
5035
5152
 
5036
5153
  // src/orm/relations/many-to-many.ts
5037
- var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
5154
+ var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
5038
5155
  var hideInternal4 = (obj, keys) => {
5039
5156
  for (const key of keys) {
5040
5157
  Object.defineProperty(obj, key, {
@@ -5067,11 +5184,11 @@ var DefaultManyToManyCollection = class {
5067
5184
  this.loader = loader;
5068
5185
  this.createEntity = createEntity;
5069
5186
  this.localKey = localKey;
5070
- this.loaded = false;
5071
- this.items = [];
5072
5187
  hideInternal4(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
5073
5188
  this.hydrateFromCache();
5074
5189
  }
5190
+ loaded = false;
5191
+ items = [];
5075
5192
  /**
5076
5193
  * Loads the collection items if not already loaded.
5077
5194
  * @returns A promise that resolves to the array of target entities.
@@ -5079,7 +5196,7 @@ var DefaultManyToManyCollection = class {
5079
5196
  async load() {
5080
5197
  if (this.loaded) return this.items;
5081
5198
  const map = await this.loader();
5082
- const key = toKey5(this.root[this.localKey]);
5199
+ const key = toKey6(this.root[this.localKey]);
5083
5200
  const rows = map.get(key) ?? [];
5084
5201
  this.items = rows.map((row) => {
5085
5202
  const entity = this.createEntity(row);
@@ -5161,15 +5278,15 @@ var DefaultManyToManyCollection = class {
5161
5278
  */
5162
5279
  async syncByIds(ids) {
5163
5280
  await this.load();
5164
- const normalized = new Set(ids.map((id) => toKey5(id)));
5165
- 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))));
5166
5283
  for (const id of normalized) {
5167
5284
  if (!currentIds.has(id)) {
5168
5285
  this.attach(id);
5169
5286
  }
5170
5287
  }
5171
5288
  for (const item of [...this.items]) {
5172
- const itemId = toKey5(this.extractId(item));
5289
+ const itemId = toKey6(this.extractId(item));
5173
5290
  if (!normalized.has(itemId)) {
5174
5291
  this.detach(item);
5175
5292
  }
@@ -5216,7 +5333,7 @@ var DefaultManyToManyCollection = class {
5216
5333
  }
5217
5334
  };
5218
5335
 
5219
- // src/orm/lazy-batch.ts
5336
+ // src/orm/lazy-batch/shared.ts
5220
5337
  var hasColumns = (columns) => Boolean(columns && columns.length > 0);
5221
5338
  var buildColumnSelection = (table, columns, missingMsg) => {
5222
5339
  return columns.reduce((acc, column) => {
@@ -5257,7 +5374,7 @@ var executeQuery = async (ctx, qb) => {
5257
5374
  const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
5258
5375
  return rowsFromResults(results);
5259
5376
  };
5260
- var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
5377
+ var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
5261
5378
  var collectKeysFromRoots = (roots, key) => {
5262
5379
  const collected = /* @__PURE__ */ new Set();
5263
5380
  for (const tracked of roots) {
@@ -5282,7 +5399,7 @@ var groupRowsByMany = (rows, keyColumn) => {
5282
5399
  for (const row of rows) {
5283
5400
  const value = row[keyColumn];
5284
5401
  if (value === null || value === void 0) continue;
5285
- const key = toKey6(value);
5402
+ const key = toKey7(value);
5286
5403
  const bucket = grouped.get(key) ?? [];
5287
5404
  bucket.push(row);
5288
5405
  grouped.set(key, bucket);
@@ -5294,13 +5411,15 @@ var groupRowsByUnique = (rows, keyColumn) => {
5294
5411
  for (const row of rows) {
5295
5412
  const value = row[keyColumn];
5296
5413
  if (value === null || value === void 0) continue;
5297
- const key = toKey6(value);
5414
+ const key = toKey7(value);
5298
5415
  if (!lookup.has(key)) {
5299
5416
  lookup.set(key, row);
5300
5417
  }
5301
5418
  }
5302
5419
  return lookup;
5303
5420
  };
5421
+
5422
+ // src/orm/lazy-batch/has-many.ts
5304
5423
  var loadHasManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5305
5424
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5306
5425
  const roots = ctx.getEntitiesForTable(rootTable);
@@ -5333,6 +5452,8 @@ var loadHasManyRelation = async (ctx, rootTable, relationName, relation, options
5333
5452
  }
5334
5453
  return filtered;
5335
5454
  };
5455
+
5456
+ // src/orm/lazy-batch/has-one.ts
5336
5457
  var loadHasOneRelation = async (ctx, rootTable, relationName, relation, options) => {
5337
5458
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5338
5459
  const roots = ctx.getEntitiesForTable(rootTable);
@@ -5365,6 +5486,8 @@ var loadHasOneRelation = async (ctx, rootTable, relationName, relation, options)
5365
5486
  }
5366
5487
  return filtered;
5367
5488
  };
5489
+
5490
+ // src/orm/lazy-batch/belongs-to.ts
5368
5491
  var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, options) => {
5369
5492
  const roots = ctx.getEntitiesForTable(rootTable);
5370
5493
  const getForeignKeys = () => collectKeysFromRoots(roots, relation.foreignKey);
@@ -5433,6 +5556,8 @@ var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, optio
5433
5556
  }
5434
5557
  return filtered;
5435
5558
  };
5559
+
5560
+ // src/orm/lazy-batch/belongs-to-many.ts
5436
5561
  var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5437
5562
  const rootKey = relation.localKey || findPrimaryKey(rootTable);
5438
5563
  const roots = ctx.getEntitiesForTable(rootTable);
@@ -5471,12 +5596,12 @@ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, o
5471
5596
  if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
5472
5597
  continue;
5473
5598
  }
5474
- const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
5599
+ const bucket = rootLookup.get(toKey7(rootValue)) ?? [];
5475
5600
  bucket.push({
5476
5601
  targetId: targetValue,
5477
5602
  pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
5478
5603
  });
5479
- rootLookup.set(toKey6(rootValue), bucket);
5604
+ rootLookup.set(toKey7(rootValue), bucket);
5480
5605
  targetIds.add(targetValue);
5481
5606
  }
5482
5607
  if (!targetIds.size) {
@@ -5495,14 +5620,21 @@ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, o
5495
5620
  targetSelectedColumns,
5496
5621
  (column) => `Column '${column}' not found on relation '${relationName}'`
5497
5622
  );
5498
- const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds, targetSelection, options?.filter);
5623
+ const targetRows = await fetchRowsForKeys(
5624
+ ctx,
5625
+ relation.target,
5626
+ targetPkColumn,
5627
+ targetIds,
5628
+ targetSelection,
5629
+ options?.filter
5630
+ );
5499
5631
  const targetMap = groupRowsByUnique(targetRows, targetKey);
5500
5632
  const targetVisibleColumns = new Set(targetSelectedColumns);
5501
5633
  const result = /* @__PURE__ */ new Map();
5502
5634
  for (const [rootId, entries] of rootLookup.entries()) {
5503
5635
  const bucket = [];
5504
5636
  for (const entry of entries) {
5505
- const targetRow = targetMap.get(toKey6(entry.targetId));
5637
+ const targetRow = targetMap.get(toKey7(entry.targetId));
5506
5638
  if (!targetRow) continue;
5507
5639
  bucket.push({
5508
5640
  ...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
@@ -5514,7 +5646,7 @@ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, o
5514
5646
  return result;
5515
5647
  };
5516
5648
 
5517
- // src/orm/entity.ts
5649
+ // src/orm/entity-relation-cache.ts
5518
5650
  var relationLoaderCache = (meta, relationName, factory) => {
5519
5651
  if (meta.relationCache.has(relationName)) {
5520
5652
  return meta.relationCache.get(relationName);
@@ -5535,118 +5667,16 @@ var relationLoaderCache = (meta, relationName, factory) => {
5535
5667
  }
5536
5668
  return promise;
5537
5669
  };
5538
- var createEntityProxy = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
5539
- const target = { ...row };
5540
- const meta = {
5541
- ctx,
5542
- table,
5543
- lazyRelations: [...lazyRelations],
5544
- lazyRelationOptions: new Map(lazyRelationOptions),
5545
- relationCache: /* @__PURE__ */ new Map(),
5546
- relationHydration: /* @__PURE__ */ new Map(),
5547
- relationWrappers: /* @__PURE__ */ new Map()
5548
- };
5549
- Object.defineProperty(target, ENTITY_META, {
5550
- value: meta,
5551
- enumerable: false,
5552
- writable: false
5553
- });
5554
- const handler = {
5555
- get(targetObj, prop, receiver) {
5556
- if (prop === ENTITY_META) {
5557
- return meta;
5558
- }
5559
- if (prop === "$load") {
5560
- return async (relationName) => {
5561
- const wrapper = getRelationWrapper(meta, relationName, receiver);
5562
- if (wrapper && typeof wrapper.load === "function") {
5563
- return wrapper.load();
5564
- }
5565
- return void 0;
5566
- };
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);
5567
5677
  }
5568
- if (typeof prop === "string" && table.relations[prop]) {
5569
- return getRelationWrapper(meta, prop, receiver);
5570
- }
5571
- return Reflect.get(targetObj, prop, receiver);
5572
- },
5573
- set(targetObj, prop, value, receiver) {
5574
- const result = Reflect.set(targetObj, prop, value, receiver);
5575
- if (typeof prop === "string" && table.columns[prop]) {
5576
- ctx.markDirty(receiver);
5577
- }
5578
- return result;
5579
- }
5580
- };
5581
- const proxy = new Proxy(target, handler);
5582
- populateHydrationCache(proxy, row, meta);
5583
- return proxy;
5584
- };
5585
- var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
5586
- const pkName = findPrimaryKey(table);
5587
- const pkValue = row[pkName];
5588
- if (pkValue !== void 0 && pkValue !== null) {
5589
- const tracked = ctx.getEntity(table, pkValue);
5590
- if (tracked) return tracked;
5591
- }
5592
- const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
5593
- if (pkValue !== void 0 && pkValue !== null) {
5594
- ctx.trackManaged(table, pkValue, entity);
5595
- } else {
5596
- ctx.trackNew(table, entity);
5597
- }
5598
- return entity;
5599
- };
5600
- var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
5601
- var populateHydrationCache = (entity, row, meta) => {
5602
- for (const relationName of Object.keys(meta.table.relations)) {
5603
- const relation = meta.table.relations[relationName];
5604
- const data = row[relationName];
5605
- if (relation.type === RelationKinds.HasOne) {
5606
- const localKey = relation.localKey || findPrimaryKey(meta.table);
5607
- const rootValue = entity[localKey];
5608
- if (rootValue === void 0 || rootValue === null) continue;
5609
- if (!data || typeof data !== "object") continue;
5610
- const cache = /* @__PURE__ */ new Map();
5611
- cache.set(toKey7(rootValue), data);
5612
- meta.relationHydration.set(relationName, cache);
5613
- meta.relationCache.set(relationName, Promise.resolve(cache));
5614
- continue;
5615
- }
5616
- if (!Array.isArray(data)) continue;
5617
- if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
5618
- const localKey = relation.localKey || findPrimaryKey(meta.table);
5619
- const rootValue = entity[localKey];
5620
- if (rootValue === void 0 || rootValue === null) continue;
5621
- const cache = /* @__PURE__ */ new Map();
5622
- cache.set(toKey7(rootValue), data);
5623
- meta.relationHydration.set(relationName, cache);
5624
- meta.relationCache.set(relationName, Promise.resolve(cache));
5625
- continue;
5626
- }
5627
- if (relation.type === RelationKinds.BelongsTo) {
5628
- const targetKey = relation.localKey || findPrimaryKey(relation.target);
5629
- const cache = /* @__PURE__ */ new Map();
5630
- for (const item of data) {
5631
- const pkValue = item[targetKey];
5632
- if (pkValue === void 0 || pkValue === null) continue;
5633
- cache.set(toKey7(pkValue), item);
5634
- }
5635
- if (cache.size) {
5636
- meta.relationHydration.set(relationName, cache);
5637
- meta.relationCache.set(relationName, Promise.resolve(cache));
5638
- }
5639
- }
5640
- }
5641
- };
5642
- var proxifyRelationWrapper = (wrapper) => {
5643
- return new Proxy(wrapper, {
5644
- get(target, prop, receiver) {
5645
- if (typeof prop === "symbol") {
5646
- return Reflect.get(target, prop, receiver);
5647
- }
5648
- if (prop in target) {
5649
- return Reflect.get(target, prop, receiver);
5678
+ if (prop in target) {
5679
+ return Reflect.get(target, prop, receiver);
5650
5680
  }
5651
5681
  const getItems = target.getItems;
5652
5682
  if (typeof getItems === "function") {
@@ -5691,98 +5721,93 @@ var proxifyRelationWrapper = (wrapper) => {
5691
5721
  }
5692
5722
  });
5693
5723
  };
5694
- var getRelationWrapper = (meta, relationName, owner) => {
5695
- if (meta.relationWrappers.has(relationName)) {
5696
- 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);
5697
5728
  }
5698
- const relation = meta.table.relations[relationName];
5729
+ const relation = meta.table.relations[relationKey];
5699
5730
  if (!relation) return void 0;
5700
- const wrapper = instantiateWrapper(meta, relationName, relation, owner);
5731
+ const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
5701
5732
  if (!wrapper) return void 0;
5702
5733
  const proxied = proxifyRelationWrapper(wrapper);
5703
- meta.relationWrappers.set(relationName, proxied);
5734
+ meta.relationWrappers.set(relationKey, proxied);
5704
5735
  return proxied;
5705
5736
  };
5706
- var instantiateWrapper = (meta, relationName, relation, owner) => {
5737
+ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) => {
5738
+ const metaBase = meta;
5707
5739
  const lazyOptions = meta.lazyRelationOptions.get(relationName);
5740
+ const loadCached = (factory) => relationLoaderCache(metaBase, relationName, factory);
5708
5741
  switch (relation.type) {
5709
5742
  case RelationKinds.HasOne: {
5710
5743
  const hasOne2 = relation;
5711
5744
  const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
5712
- const loader = () => relationLoaderCache(
5713
- meta,
5714
- relationName,
5745
+ const loader = () => loadCached(
5715
5746
  () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, lazyOptions)
5716
5747
  );
5717
5748
  return new DefaultHasOneReference(
5718
5749
  meta.ctx,
5719
- meta,
5750
+ metaBase,
5720
5751
  owner,
5721
5752
  relationName,
5722
5753
  hasOne2,
5723
5754
  meta.table,
5724
5755
  loader,
5725
- (row) => createEntityFromRow(meta.ctx, hasOne2.target, row),
5756
+ (row) => createEntity(hasOne2.target, row),
5726
5757
  localKey
5727
5758
  );
5728
5759
  }
5729
5760
  case RelationKinds.HasMany: {
5730
5761
  const hasMany2 = relation;
5731
5762
  const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
5732
- const loader = () => relationLoaderCache(
5733
- meta,
5734
- relationName,
5763
+ const loader = () => loadCached(
5735
5764
  () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, lazyOptions)
5736
5765
  );
5737
5766
  return new DefaultHasManyCollection(
5738
5767
  meta.ctx,
5739
- meta,
5768
+ metaBase,
5740
5769
  owner,
5741
5770
  relationName,
5742
5771
  hasMany2,
5743
5772
  meta.table,
5744
5773
  loader,
5745
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
5774
+ (row) => createEntity(relation.target, row),
5746
5775
  localKey
5747
5776
  );
5748
5777
  }
5749
5778
  case RelationKinds.BelongsTo: {
5750
5779
  const belongsTo2 = relation;
5751
5780
  const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
5752
- const loader = () => relationLoaderCache(
5753
- meta,
5754
- relationName,
5781
+ const loader = () => loadCached(
5755
5782
  () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, lazyOptions)
5756
5783
  );
5757
5784
  return new DefaultBelongsToReference(
5758
5785
  meta.ctx,
5759
- meta,
5786
+ metaBase,
5760
5787
  owner,
5761
5788
  relationName,
5762
5789
  belongsTo2,
5763
5790
  meta.table,
5764
5791
  loader,
5765
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
5792
+ (row) => createEntity(relation.target, row),
5766
5793
  targetKey
5767
5794
  );
5768
5795
  }
5769
5796
  case RelationKinds.BelongsToMany: {
5770
5797
  const many = relation;
5771
5798
  const localKey = many.localKey || findPrimaryKey(meta.table);
5772
- const loader = () => relationLoaderCache(
5773
- meta,
5774
- relationName,
5799
+ const loader = () => loadCached(
5775
5800
  () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
5776
5801
  );
5777
5802
  return new DefaultManyToManyCollection(
5778
5803
  meta.ctx,
5779
- meta,
5804
+ metaBase,
5780
5805
  owner,
5781
5806
  relationName,
5782
5807
  many,
5783
5808
  meta.table,
5784
5809
  loader,
5785
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
5810
+ (row) => createEntity(relation.target, row),
5786
5811
  localKey
5787
5812
  );
5788
5813
  }
@@ -5791,103 +5816,570 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
5791
5816
  }
5792
5817
  };
5793
5818
 
5794
- // src/orm/execute.ts
5795
- var flattenResults = (results) => {
5796
- const rows = [];
5797
- for (const result of results) {
5798
- const { columns, values } = result;
5799
- for (const valueRow of values) {
5800
- const row = {};
5801
- columns.forEach((column, idx) => {
5802
- row[column] = valueRow[idx];
5803
- });
5804
- rows.push(row);
5805
- }
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;
6274
+ }
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 };
6288
+ }
6289
+ };
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;
5806
6301
  }
5807
- return rows;
5808
- };
5809
- var executeWithContexts = async (execCtx, entityCtx, qb) => {
5810
- const ast = qb.getAST();
5811
- const compiled = execCtx.dialect.compileSelect(ast);
5812
- const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
5813
- const rows = flattenResults(executed);
5814
- const lazyRelations = qb.getLazyRelations();
5815
- const lazyRelationOptions = qb.getLazyRelationOptions();
5816
- if (ast.setOps && ast.setOps.length > 0) {
5817
- const proxies = rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
5818
- await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
5819
- return proxies;
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 };
5820
6313
  }
5821
- const hydrated = hydrateRows(rows, qb.getHydrationPlan());
5822
- const entities = hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
5823
- await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
5824
- return entities;
5825
6314
  };
5826
- async function executeHydrated(session, qb) {
5827
- return executeWithContexts(session.getExecutionContext(), session, qb);
5828
- }
5829
- async function executeHydratedWithContexts(execCtx, hydCtx, qb) {
5830
- const entityCtx = hydCtx.entityContext;
5831
- if (!entityCtx) {
5832
- throw new Error("Hydration context is missing an EntityContext");
6315
+
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;
5833
6324
  }
5834
- return executeWithContexts(execCtx, entityCtx, qb);
5835
- }
5836
- var loadLazyRelationsForTable = async (ctx, table, lazyRelations, lazyRelationOptions) => {
5837
- if (!lazyRelations.length) return;
5838
- const tracked = ctx.getEntitiesForTable(table);
5839
- if (!tracked.length) return;
5840
- const meta = getEntityMeta(tracked[0].entity);
5841
- if (!meta) return;
5842
- for (const relationName of lazyRelations) {
5843
- const relation = table.relations[relationName];
5844
- if (!relation) continue;
5845
- const key = relationName;
5846
- const options = lazyRelationOptions.get(key);
5847
- if (!options) {
5848
- continue;
5849
- }
5850
- switch (relation.type) {
5851
- case RelationKinds.HasOne:
5852
- await relationLoaderCache(
5853
- meta,
5854
- key,
5855
- () => loadHasOneRelation(ctx, table, key, relation, options)
5856
- );
5857
- break;
5858
- case RelationKinds.HasMany:
5859
- await relationLoaderCache(
5860
- meta,
5861
- key,
5862
- () => loadHasManyRelation(ctx, table, key, relation, options)
5863
- );
5864
- break;
5865
- case RelationKinds.BelongsTo:
5866
- await relationLoaderCache(
5867
- meta,
5868
- key,
5869
- () => loadBelongsToRelation(ctx, table, key, relation, options)
5870
- );
5871
- break;
5872
- case RelationKinds.BelongsToMany:
5873
- await relationLoaderCache(
5874
- meta,
5875
- key,
5876
- () => loadBelongsToManyRelation(ctx, table, key, relation, options)
5877
- );
5878
- break;
5879
- }
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);
5880
6366
  }
5881
6367
  };
5882
6368
 
5883
- // src/query-builder/query-resolution.ts
5884
- function resolveSelectQuery(query) {
5885
- const candidate = query;
5886
- return typeof candidate.getAST === "function" && candidate.getAST ? candidate.getAST() : query;
5887
- }
5888
-
5889
6369
  // src/query-builder/select.ts
5890
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;
5891
6383
  /**
5892
6384
  * Creates a new SelectQueryBuilder instance
5893
6385
  * @param table - Table definition to query
@@ -5898,6 +6390,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5898
6390
  constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions) {
5899
6391
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
5900
6392
  this.env = { table, deps };
6393
+ const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
5901
6394
  const initialState = state ?? deps.createState(table);
5902
6395
  const initialHydration = hydration ?? deps.createHydration(table);
5903
6396
  this.context = {
@@ -5907,7 +6400,14 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5907
6400
  this.lazyRelations = new Set(lazyRelations ?? []);
5908
6401
  this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
5909
6402
  this.columnSelector = deps.createColumnSelector(this.env);
5910
- 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);
5911
6411
  }
5912
6412
  /**
5913
6413
  * Creates a new SelectQueryBuilder instance with updated context and lazy relations
@@ -5930,12 +6430,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5930
6430
  * @param alias - Alias to apply
5931
6431
  */
5932
6432
  as(alias) {
5933
- const from = this.context.state.ast.from;
5934
- if (from.type !== "Table") {
5935
- throw new Error("Cannot alias non-table FROM sources");
5936
- }
5937
- const nextFrom = { ...from, alias };
5938
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(nextFrom));
6433
+ const nextContext = this.fromFacet.as(this.context, alias);
5939
6434
  return this.clone(nextContext);
5940
6435
  }
5941
6436
  /**
@@ -5960,29 +6455,6 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5960
6455
  createChildBuilder(table) {
5961
6456
  return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
5962
6457
  }
5963
- /**
5964
- * Applies an AST mutation using the query AST service
5965
- * @param context - Current query context
5966
- * @param mutator - Function that mutates the AST
5967
- * @returns Updated query context
5968
- */
5969
- applyAst(context, mutator) {
5970
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
5971
- const nextState = mutator(astService);
5972
- return { state: nextState, hydration: context.hydration };
5973
- }
5974
- /**
5975
- * Applies a join to the query context
5976
- * @param context - Current query context
5977
- * @param table - Table to join
5978
- * @param condition - Join condition
5979
- * @param kind - Join kind
5980
- * @returns Updated query context with join applied
5981
- */
5982
- applyJoin(context, table, condition, kind) {
5983
- const joinNode = createJoinNode(kind, { type: "Table", name: table.name, schema: table.schema }, condition);
5984
- return this.applyAst(context, (service) => service.withJoin(joinNode));
5985
- }
5986
6458
  /**
5987
6459
  * Applies a set operation to the query
5988
6460
  * @param operator - Set operation kind
@@ -5991,12 +6463,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5991
6463
  */
5992
6464
  applySetOperation(operator, query) {
5993
6465
  const subAst = resolveSelectQuery(query);
5994
- return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
6466
+ return this.setOpFacet.applySetOperation(this.context, operator, subAst);
5995
6467
  }
5996
6468
  select(...args) {
5997
6469
  if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && typeof args[0] !== "string") {
5998
6470
  const columns = args[0];
5999
- return this.clone(this.columnSelector.select(this.context, columns));
6471
+ return this.clone(this.projectionFacet.select(this.context, columns));
6000
6472
  }
6001
6473
  const cols = args;
6002
6474
  const selection = {};
@@ -6007,7 +6479,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6007
6479
  }
6008
6480
  selection[key] = col2;
6009
6481
  }
6010
- return this.clone(this.columnSelector.select(this.context, selection));
6482
+ return this.clone(this.projectionFacet.select(this.context, selection));
6011
6483
  }
6012
6484
  /**
6013
6485
  * Selects raw column expressions
@@ -6015,7 +6487,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6015
6487
  * @returns New query builder instance with raw column selections
6016
6488
  */
6017
6489
  selectRaw(...cols) {
6018
- return this.clone(this.columnSelector.selectRaw(this.context, cols));
6490
+ return this.clone(this.projectionFacet.selectRaw(this.context, cols));
6019
6491
  }
6020
6492
  /**
6021
6493
  * Adds a Common Table Expression (CTE) to the query
@@ -6026,7 +6498,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6026
6498
  */
6027
6499
  with(name, query, columns) {
6028
6500
  const subAst = resolveSelectQuery(query);
6029
- 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);
6030
6502
  return this.clone(nextContext);
6031
6503
  }
6032
6504
  /**
@@ -6038,7 +6510,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6038
6510
  */
6039
6511
  withRecursive(name, query, columns) {
6040
6512
  const subAst = resolveSelectQuery(query);
6041
- 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);
6042
6514
  return this.clone(nextContext);
6043
6515
  }
6044
6516
  /**
@@ -6050,8 +6522,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6050
6522
  */
6051
6523
  fromSubquery(subquery, alias, columnAliases) {
6052
6524
  const subAst = resolveSelectQuery(subquery);
6053
- const fromNode = derivedTable(subAst, alias, columnAliases);
6054
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(fromNode));
6525
+ const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
6055
6526
  return this.clone(nextContext);
6056
6527
  }
6057
6528
  /**
@@ -6062,8 +6533,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6062
6533
  * @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
6063
6534
  */
6064
6535
  fromFunctionTable(name, args = [], alias, options) {
6065
- const functionTable = fnTable(name, args, alias, options);
6066
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(functionTable));
6536
+ const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
6067
6537
  return this.clone(nextContext);
6068
6538
  }
6069
6539
  /**
@@ -6074,7 +6544,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6074
6544
  */
6075
6545
  selectSubquery(alias, sub2) {
6076
6546
  const query = resolveSelectQuery(sub2);
6077
- return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
6547
+ return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
6078
6548
  }
6079
6549
  /**
6080
6550
  * Adds a JOIN against a derived table (subquery with alias)
@@ -6087,8 +6557,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6087
6557
  */
6088
6558
  joinSubquery(subquery, alias, condition, joinKind = JOIN_KINDS.INNER, columnAliases) {
6089
6559
  const subAst = resolveSelectQuery(subquery);
6090
- const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
6091
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
6560
+ const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
6092
6561
  return this.clone(nextContext);
6093
6562
  }
6094
6563
  /**
@@ -6101,9 +6570,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6101
6570
  * @param options - Optional metadata (lateral, ordinality, column aliases, schema)
6102
6571
  */
6103
6572
  joinFunctionTable(name, args = [], alias, condition, joinKind = JOIN_KINDS.INNER, options) {
6104
- const functionTable = fnTable(name, args, alias, options);
6105
- const joinNode = createJoinNode(joinKind, functionTable, condition);
6106
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
6573
+ const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
6107
6574
  return this.clone(nextContext);
6108
6575
  }
6109
6576
  /**
@@ -6113,7 +6580,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6113
6580
  * @returns New query builder instance with the INNER JOIN
6114
6581
  */
6115
6582
  innerJoin(table, condition) {
6116
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
6583
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
6117
6584
  return this.clone(nextContext);
6118
6585
  }
6119
6586
  /**
@@ -6123,7 +6590,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6123
6590
  * @returns New query builder instance with the LEFT JOIN
6124
6591
  */
6125
6592
  leftJoin(table, condition) {
6126
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
6593
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
6127
6594
  return this.clone(nextContext);
6128
6595
  }
6129
6596
  /**
@@ -6133,7 +6600,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6133
6600
  * @returns New query builder instance with the RIGHT JOIN
6134
6601
  */
6135
6602
  rightJoin(table, condition) {
6136
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
6603
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
6137
6604
  return this.clone(nextContext);
6138
6605
  }
6139
6606
  /**
@@ -6143,7 +6610,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6143
6610
  * @returns New query builder instance with the relationship match
6144
6611
  */
6145
6612
  match(relationName, predicate) {
6146
- const nextContext = this.relationManager.match(this.context, relationName, predicate);
6613
+ const nextContext = this.relationFacet.match(this.context, relationName, predicate);
6147
6614
  return this.clone(nextContext);
6148
6615
  }
6149
6616
  /**
@@ -6154,7 +6621,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6154
6621
  * @returns New query builder instance with the relationship join
6155
6622
  */
6156
6623
  joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
6157
- const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
6624
+ const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
6158
6625
  return this.clone(nextContext);
6159
6626
  }
6160
6627
  /**
@@ -6164,7 +6631,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6164
6631
  * @returns New query builder instance with the relationship inclusion
6165
6632
  */
6166
6633
  include(relationName, options) {
6167
- const nextContext = this.relationManager.include(this.context, relationName, options);
6634
+ const nextContext = this.relationFacet.include(this.context, relationName, options);
6168
6635
  return this.clone(nextContext);
6169
6636
  }
6170
6637
  /**
@@ -6203,7 +6670,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6203
6670
  * Convenience alias for including only specific columns from a relation.
6204
6671
  */
6205
6672
  includePick(relationName, cols) {
6206
- return this.include(relationName, { columns: cols });
6673
+ const options = { columns: cols };
6674
+ return this.include(relationName, options);
6207
6675
  }
6208
6676
  /**
6209
6677
  * Selects columns for the root table and relations from an array of entries
@@ -6216,7 +6684,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6216
6684
  if (entry.type === "root") {
6217
6685
  currBuilder = currBuilder.select(...entry.columns);
6218
6686
  } else {
6219
- currBuilder = currBuilder.include(entry.relationName, { columns: entry.columns });
6687
+ const options = { columns: entry.columns };
6688
+ currBuilder = currBuilder.include(entry.relationName, options);
6220
6689
  }
6221
6690
  }
6222
6691
  return currBuilder;
@@ -6250,51 +6719,23 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6250
6719
  async execute(ctx) {
6251
6720
  return executeHydrated(ctx, this);
6252
6721
  }
6253
- withAst(ast) {
6254
- const nextState = new SelectQueryState(this.env.table, ast);
6255
- const nextContext = {
6256
- ...this.context,
6257
- state: nextState
6258
- };
6259
- return this.clone(nextContext);
6260
- }
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
+ */
6261
6728
  async count(session) {
6262
- const unpagedAst = {
6263
- ...this.context.state.ast,
6264
- orderBy: void 0,
6265
- limit: void 0,
6266
- offset: void 0
6267
- };
6268
- const subAst = this.withAst(unpagedAst).getAST();
6269
- const countQuery = {
6270
- type: "SelectQuery",
6271
- from: derivedTable(subAst, "__metal_count"),
6272
- columns: [{ type: "Function", name: "COUNT", args: [], alias: "total" }],
6273
- joins: []
6274
- };
6275
- const execCtx = session.getExecutionContext();
6276
- const compiled = execCtx.dialect.compileSelect(countQuery);
6277
- const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
6278
- const value = results[0]?.values?.[0]?.[0];
6279
- if (typeof value === "number") return value;
6280
- if (typeof value === "bigint") return Number(value);
6281
- if (typeof value === "string") return Number(value);
6282
- return value === null || value === void 0 ? 0 : Number(value);
6729
+ return executeCount(this.context, this.env, session);
6283
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
+ */
6284
6737
  async executePaged(session, options) {
6285
- const { page, pageSize } = options;
6286
- if (!Number.isInteger(page) || page < 1) {
6287
- throw new Error("executePaged: page must be an integer >= 1");
6288
- }
6289
- if (!Number.isInteger(pageSize) || pageSize < 1) {
6290
- throw new Error("executePaged: pageSize must be an integer >= 1");
6291
- }
6292
- const offset = (page - 1) * pageSize;
6293
- const [items, totalItems] = await Promise.all([
6294
- this.limit(pageSize).offset(offset).execute(session),
6295
- this.count(session)
6296
- ]);
6297
- return { items, totalItems };
6738
+ return executePagedQuery(this, session, options, (sess) => this.count(sess));
6298
6739
  }
6299
6740
  /**
6300
6741
  * Executes the query with provided execution and hydration contexts
@@ -6311,7 +6752,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6311
6752
  * @returns New query builder instance with the WHERE condition
6312
6753
  */
6313
6754
  where(expr) {
6314
- const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
6755
+ const nextContext = this.predicateFacet.where(this.context, expr);
6315
6756
  return this.clone(nextContext);
6316
6757
  }
6317
6758
  /**
@@ -6320,7 +6761,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6320
6761
  * @returns New query builder instance with the GROUP BY clause
6321
6762
  */
6322
6763
  groupBy(term) {
6323
- const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(term));
6764
+ const nextContext = this.predicateFacet.groupBy(this.context, term);
6324
6765
  return this.clone(nextContext);
6325
6766
  }
6326
6767
  /**
@@ -6329,7 +6770,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6329
6770
  * @returns New query builder instance with the HAVING condition
6330
6771
  */
6331
6772
  having(expr) {
6332
- const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
6773
+ const nextContext = this.predicateFacet.having(this.context, expr);
6333
6774
  return this.clone(nextContext);
6334
6775
  }
6335
6776
  /**
@@ -6337,14 +6778,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6337
6778
  * @param term - Column definition or ordering term to order by
6338
6779
  * @param directionOrOptions - Order direction or options (defaults to ASC)
6339
6780
  * @returns New query builder instance with the ORDER BY clause
6781
+ *
6782
+ * @example
6783
+ * qb.orderBy(userTable.columns.createdAt, 'DESC');
6340
6784
  */
6341
6785
  orderBy(term, directionOrOptions = ORDER_DIRECTIONS.ASC) {
6342
- const options = typeof directionOrOptions === "string" ? { direction: directionOrOptions } : directionOrOptions;
6343
- const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
6344
- const nextContext = this.applyAst(
6345
- this.context,
6346
- (service) => service.withOrderBy(term, dir, options.nulls, options.collation)
6347
- );
6786
+ const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
6348
6787
  return this.clone(nextContext);
6349
6788
  }
6350
6789
  /**
@@ -6353,7 +6792,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6353
6792
  * @returns New query builder instance with the DISTINCT clause
6354
6793
  */
6355
6794
  distinct(...cols) {
6356
- return this.clone(this.columnSelector.distinct(this.context, cols));
6795
+ return this.clone(this.projectionFacet.distinct(this.context, cols));
6357
6796
  }
6358
6797
  /**
6359
6798
  * Adds a LIMIT clause to the query
@@ -6361,7 +6800,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6361
6800
  * @returns New query builder instance with the LIMIT clause
6362
6801
  */
6363
6802
  limit(n) {
6364
- const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
6803
+ const nextContext = this.predicateFacet.limit(this.context, n);
6365
6804
  return this.clone(nextContext);
6366
6805
  }
6367
6806
  /**
@@ -6370,7 +6809,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6370
6809
  * @returns New query builder instance with the OFFSET clause
6371
6810
  */
6372
6811
  offset(n) {
6373
- const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
6812
+ const nextContext = this.predicateFacet.offset(this.context, n);
6374
6813
  return this.clone(nextContext);
6375
6814
  }
6376
6815
  /**
@@ -6430,42 +6869,44 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6430
6869
  * @param relationName - Name of the relationship to check
6431
6870
  * @param callback - Optional callback to modify the relationship query
6432
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)));
6433
6875
  */
6434
6876
  whereHas(relationName, callbackOrOptions, maybeOptions) {
6435
- const relation = this.env.table.relations[relationName];
6436
- if (!relation) {
6437
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
6438
- }
6439
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6440
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6441
- let subQb = this.createChildBuilder(relation.target);
6442
- if (callback) {
6443
- subQb = callback(subQb);
6444
- }
6445
- const subAst = subQb.getAST();
6446
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
6447
- 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);
6448
6888
  }
6449
6889
  /**
6450
6890
  * Adds a WHERE NOT EXISTS condition based on a relationship
6451
6891
  * @param relationName - Name of the relationship to check
6452
6892
  * @param callback - Optional callback to modify the relationship query
6453
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)));
6454
6897
  */
6455
6898
  whereHasNot(relationName, callbackOrOptions, maybeOptions) {
6456
- const relation = this.env.table.relations[relationName];
6457
- if (!relation) {
6458
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
6459
- }
6460
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6461
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6462
- let subQb = this.createChildBuilder(relation.target);
6463
- if (callback) {
6464
- subQb = callback(subQb);
6465
- }
6466
- const subAst = subQb.getAST();
6467
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
6468
- 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);
6469
6910
  }
6470
6911
  /**
6471
6912
  * Compiles the query to SQL for a specific dialect
@@ -6597,23 +7038,44 @@ var resolveTableTarget = (target, tableMap) => {
6597
7038
  }
6598
7039
  return table;
6599
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
+ };
6600
7060
  var buildRelationDefinitions = (meta, tableMap) => {
6601
7061
  const relations = {};
6602
7062
  for (const [name, relation] of Object.entries(meta.relations)) {
6603
7063
  switch (relation.kind) {
6604
7064
  case RelationKinds.HasOne: {
7065
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6605
7066
  relations[name] = hasOne(
6606
7067
  resolveTableTarget(relation.target, tableMap),
6607
- relation.foreignKey,
7068
+ foreignKey,
6608
7069
  relation.localKey,
6609
7070
  relation.cascade
6610
7071
  );
6611
7072
  break;
6612
7073
  }
6613
7074
  case RelationKinds.HasMany: {
7075
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6614
7076
  relations[name] = hasMany(
6615
7077
  resolveTableTarget(relation.target, tableMap),
6616
- relation.foreignKey,
7078
+ foreignKey,
6617
7079
  relation.localKey,
6618
7080
  relation.cascade
6619
7081
  );
@@ -6629,12 +7091,14 @@ var buildRelationDefinitions = (meta, tableMap) => {
6629
7091
  break;
6630
7092
  }
6631
7093
  case RelationKinds.BelongsToMany: {
7094
+ const pivotForeignKeyToRoot = relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
7095
+ const pivotForeignKeyToTarget = relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
6632
7096
  relations[name] = belongsToMany(
6633
7097
  resolveTableTarget(relation.target, tableMap),
6634
7098
  resolveTableTarget(relation.pivotTable, tableMap),
6635
7099
  {
6636
- pivotForeignKeyToRoot: relation.pivotForeignKeyToRoot,
6637
- pivotForeignKeyToTarget: relation.pivotForeignKeyToTarget,
7100
+ pivotForeignKeyToRoot,
7101
+ pivotForeignKeyToTarget,
6638
7102
  localKey: relation.localKey,
6639
7103
  targetKey: relation.targetKey,
6640
7104
  pivotPrimaryKey: relation.pivotPrimaryKey,
@@ -6715,6 +7179,8 @@ function esel(entity, ...props) {
6715
7179
 
6716
7180
  // src/query-builder/insert-query-state.ts
6717
7181
  var InsertQueryState = class _InsertQueryState {
7182
+ table;
7183
+ ast;
6718
7184
  /**
6719
7185
  * Creates a new InsertQueryState instance
6720
7186
  * @param table - The table definition for the INSERT query
@@ -6835,6 +7301,8 @@ var InsertQueryState = class _InsertQueryState {
6835
7301
 
6836
7302
  // src/query-builder/insert.ts
6837
7303
  var InsertQueryBuilder = class _InsertQueryBuilder {
7304
+ table;
7305
+ state;
6838
7306
  /**
6839
7307
  * Creates a new InsertQueryBuilder instance
6840
7308
  * @param table - The table definition for the INSERT query
@@ -6934,6 +7402,8 @@ var isUpdateValue = (value) => {
6934
7402
  }
6935
7403
  };
6936
7404
  var UpdateQueryState = class _UpdateQueryState {
7405
+ table;
7406
+ ast;
6937
7407
  /**
6938
7408
  * Creates a new UpdateQueryState instance
6939
7409
  * @param table - Table definition for the update
@@ -7044,6 +7514,8 @@ var UpdateQueryState = class _UpdateQueryState {
7044
7514
 
7045
7515
  // src/query-builder/update.ts
7046
7516
  var UpdateQueryBuilder = class _UpdateQueryBuilder {
7517
+ table;
7518
+ state;
7047
7519
  /**
7048
7520
  * Creates a new UpdateQueryBuilder instance
7049
7521
  * @param table - The table definition for the UPDATE query
@@ -7161,6 +7633,8 @@ var isTableSourceNode = (source) => typeof source.type === "string";
7161
7633
 
7162
7634
  // src/query-builder/delete-query-state.ts
7163
7635
  var DeleteQueryState = class _DeleteQueryState {
7636
+ table;
7637
+ ast;
7164
7638
  /**
7165
7639
  * Creates a new DeleteQueryState instance
7166
7640
  * @param table - The table definition for the DELETE query
@@ -7239,6 +7713,8 @@ var DeleteQueryState = class _DeleteQueryState {
7239
7713
 
7240
7714
  // src/query-builder/delete.ts
7241
7715
  var DeleteQueryBuilder = class _DeleteQueryBuilder {
7716
+ table;
7717
+ state;
7242
7718
  /**
7243
7719
  * Creates a new DeleteQueryBuilder instance
7244
7720
  * @param table - The table definition for the DELETE query
@@ -7366,7 +7842,8 @@ var renderColumnDefinition = (table, col2, dialect, options = {}) => {
7366
7842
  if (col2.default !== void 0) {
7367
7843
  parts.push(`DEFAULT ${dialect.renderDefault(col2.default, col2)}`);
7368
7844
  }
7369
- 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) {
7370
7847
  parts.push("PRIMARY KEY");
7371
7848
  }
7372
7849
  if (col2.check) {
@@ -7424,6 +7901,16 @@ var generateSchemaSql = (tables, dialect) => {
7424
7901
  });
7425
7902
  return statements;
7426
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
+ };
7427
7914
  var orderTablesByDependencies = (tables) => {
7428
7915
  const map = /* @__PURE__ */ new Map();
7429
7916
  tables.forEach((t) => map.set(t.name, t));
@@ -9330,6 +9817,7 @@ var arrayAppend = (array, value) => fn7("ARRAY_APPEND", [array, value]);
9330
9817
 
9331
9818
  // src/orm/als.ts
9332
9819
  var AsyncLocalStorage = class {
9820
+ store;
9333
9821
  /**
9334
9822
  * Executes a callback function within a context containing the specified store value.
9335
9823
  * The store value is only available during the callback's execution and is automatically
@@ -9826,9 +10314,7 @@ var TypeScriptGenerator = class {
9826
10314
 
9827
10315
  // src/orm/identity-map.ts
9828
10316
  var IdentityMap = class {
9829
- constructor() {
9830
- this.buckets = /* @__PURE__ */ new Map();
9831
- }
10317
+ buckets = /* @__PURE__ */ new Map();
9832
10318
  get bucketsMap() {
9833
10319
  return this.buckets;
9834
10320
  }
@@ -9898,8 +10384,8 @@ var UnitOfWork = class {
9898
10384
  this.executor = executor;
9899
10385
  this.identityMap = identityMap;
9900
10386
  this.hookContext = hookContext;
9901
- this.trackedEntities = /* @__PURE__ */ new Map();
9902
10387
  }
10388
+ trackedEntities = /* @__PURE__ */ new Map();
9903
10389
  /**
9904
10390
  * Gets the identity buckets map.
9905
10391
  */
@@ -10229,12 +10715,12 @@ var UnitOfWork = class {
10229
10715
 
10230
10716
  // src/orm/domain-event-bus.ts
10231
10717
  var DomainEventBus = class {
10718
+ handlers = /* @__PURE__ */ new Map();
10232
10719
  /**
10233
10720
  * Creates a new DomainEventBus instance.
10234
10721
  * @param initialHandlers - Optional initial event handlers
10235
10722
  */
10236
10723
  constructor(initialHandlers) {
10237
- this.handlers = /* @__PURE__ */ new Map();
10238
10724
  if (initialHandlers) {
10239
10725
  for (const key in initialHandlers) {
10240
10726
  const type = key;
@@ -10303,8 +10789,8 @@ var RelationChangeProcessor = class {
10303
10789
  this.unitOfWork = unitOfWork;
10304
10790
  this.dialect = dialect;
10305
10791
  this.executor = executor;
10306
- this.relationChanges = [];
10307
10792
  }
10793
+ relationChanges = [];
10308
10794
  /**
10309
10795
  * Registers a relation change for processing.
10310
10796
  * @param entry - The relation change entry
@@ -10738,25 +11224,24 @@ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
10738
11224
 
10739
11225
  // src/orm/orm-session.ts
10740
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;
10741
11240
  /**
10742
11241
  * Creates a new OrmSession instance.
10743
11242
  * @param opts - Session options
10744
11243
  */
10745
11244
  constructor(opts) {
10746
- /**
10747
- * Registers a relation change.
10748
- * @param root - The root entity
10749
- * @param relationKey - The relation key
10750
- * @param rootTable - The root table definition
10751
- * @param relationName - The relation name
10752
- * @param relation - The relation definition
10753
- * @param change - The relation change
10754
- */
10755
- this.registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
10756
- this.relationChanges.registerChange(
10757
- buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
10758
- );
10759
- };
10760
11245
  this.orm = opts.orm;
10761
11246
  this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
10762
11247
  this.interceptors = [...opts.interceptors ?? []];
@@ -10845,6 +11330,20 @@ var OrmSession = class {
10845
11330
  markRemoved(entity) {
10846
11331
  this.unitOfWork.markRemoved(entity);
10847
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
+ };
10848
11347
  /**
10849
11348
  * Gets all tracked entities for a specific table.
10850
11349
  * @param table - The table definition
@@ -11050,9 +11549,7 @@ var buildRelationChangeEntry = (root, relationKey, rootTable, relationName, rela
11050
11549
 
11051
11550
  // src/orm/interceptor-pipeline.ts
11052
11551
  var InterceptorPipeline = class {
11053
- constructor() {
11054
- this.interceptors = [];
11055
- }
11552
+ interceptors = [];
11056
11553
  use(interceptor) {
11057
11554
  this.interceptors.push(interceptor);
11058
11555
  }
@@ -11071,6 +11568,13 @@ var InterceptorPipeline = class {
11071
11568
 
11072
11569
  // src/orm/orm.ts
11073
11570
  var Orm = class {
11571
+ /** The database dialect */
11572
+ dialect;
11573
+ /** The interceptors pipeline */
11574
+ interceptors;
11575
+ /** The naming strategy */
11576
+ namingStrategy;
11577
+ executorFactory;
11074
11578
  /**
11075
11579
  * Creates a new ORM instance.
11076
11580
  * @param opts - ORM options
@@ -11127,17 +11631,13 @@ var jsonify = (value) => {
11127
11631
 
11128
11632
  // src/decorators/decorator-metadata.ts
11129
11633
  var METADATA_KEY = "metal-orm:decorators";
11130
- var isStandardDecoratorContext = (value) => {
11131
- return typeof value === "object" && value !== null && "kind" in value;
11132
- };
11133
11634
  var getOrCreateMetadataBag = (context) => {
11134
11635
  const metadata = context.metadata || (context.metadata = {});
11135
- const existing = metadata[METADATA_KEY];
11136
- if (existing) {
11137
- return existing;
11636
+ let bag = metadata[METADATA_KEY];
11637
+ if (!bag) {
11638
+ bag = { columns: [], relations: [] };
11639
+ metadata[METADATA_KEY] = bag;
11138
11640
  }
11139
- const bag = { columns: [], relations: [] };
11140
- metadata[METADATA_KEY] = bag;
11141
11641
  return bag;
11142
11642
  };
11143
11643
  var readMetadataBag = (context) => {
@@ -11152,57 +11652,50 @@ var readMetadataBagFromConstructor = (ctor) => {
11152
11652
  var getDecoratorMetadata = (ctor) => readMetadataBagFromConstructor(ctor);
11153
11653
 
11154
11654
  // src/decorators/entity.ts
11155
- var toSnakeCase = (value) => {
11655
+ var toSnakeCase2 = (value) => {
11156
11656
  return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
11157
11657
  };
11158
11658
  var deriveTableNameFromConstructor = (ctor) => {
11159
11659
  const fallback = "unknown";
11160
11660
  const rawName = ctor.name || fallback;
11161
11661
  const strippedName = rawName.replace(/Entity$/i, "");
11162
- const normalized = toSnakeCase(strippedName || rawName);
11662
+ const normalized = toSnakeCase2(strippedName || rawName);
11163
11663
  if (!normalized) {
11164
11664
  return fallback;
11165
11665
  }
11166
11666
  return normalized.endsWith("s") ? normalized : `${normalized}s`;
11167
11667
  };
11168
11668
  function Entity(options = {}) {
11169
- const decorator = (value) => {
11170
- const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
11171
- setEntityTableName(value, tableName, options.hooks);
11172
- return value;
11173
- };
11174
- const decoratorWithContext = (value, context) => {
11669
+ return function(value, context) {
11175
11670
  const ctor = value;
11176
- decorator(ctor);
11177
- if (context && isStandardDecoratorContext(context)) {
11178
- const bag = readMetadataBag(context);
11179
- if (bag) {
11180
- const meta = ensureEntityMetadata(ctor);
11181
- for (const entry of bag.columns) {
11182
- if (meta.columns[entry.propertyName]) {
11183
- throw new Error(
11184
- `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11185
- );
11186
- }
11187
- 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
+ );
11188
11681
  }
11189
- for (const entry of bag.relations) {
11190
- if (meta.relations[entry.propertyName]) {
11191
- throw new Error(
11192
- `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11193
- );
11194
- }
11195
- const relationCopy = entry.relation.kind === RelationKinds.BelongsToMany ? {
11196
- ...entry.relation,
11197
- defaultPivotColumns: entry.relation.defaultPivotColumns ? [...entry.relation.defaultPivotColumns] : void 0
11198
- } : { ...entry.relation };
11199
- 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
+ );
11200
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);
11201
11695
  }
11202
11696
  }
11203
11697
  return ctor;
11204
11698
  };
11205
- return decoratorWithContext;
11206
11699
  }
11207
11700
 
11208
11701
  // src/decorators/column-decorator.ts
@@ -11235,26 +11728,13 @@ var normalizePropertyName = (name) => {
11235
11728
  }
11236
11729
  return name;
11237
11730
  };
11238
- var resolveConstructor = (target) => {
11239
- if (typeof target === "function") {
11240
- return target;
11241
- }
11242
- if (target && typeof target.constructor === "function") {
11243
- return target.constructor;
11244
- }
11245
- return void 0;
11246
- };
11247
- var registerColumn = (ctor, propertyName, column) => {
11248
- const meta = ensureEntityMetadata(ctor);
11249
- if (meta.columns[propertyName]) {
11250
- return;
11251
- }
11252
- addColumnMetadata(ctor, propertyName, column);
11253
- };
11254
11731
  var registerColumnFromContext = (context, column) => {
11255
11732
  if (!context.name) {
11256
11733
  throw new Error("Column decorator requires a property name");
11257
11734
  }
11735
+ if (context.private) {
11736
+ throw new Error("Column decorator does not support private fields");
11737
+ }
11258
11738
  const propertyName = normalizePropertyName(context.name);
11259
11739
  const bag = getOrCreateMetadataBag(context);
11260
11740
  if (!bag.columns.some((entry) => entry.propertyName === propertyName)) {
@@ -11263,19 +11743,9 @@ var registerColumnFromContext = (context, column) => {
11263
11743
  };
11264
11744
  function Column(definition) {
11265
11745
  const normalized = normalizeColumnInput(definition);
11266
- const decorator = (targetOrValue, propertyKeyOrContext) => {
11267
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
11268
- registerColumnFromContext(propertyKeyOrContext, normalized);
11269
- return;
11270
- }
11271
- const propertyName = normalizePropertyName(propertyKeyOrContext);
11272
- const ctor = resolveConstructor(targetOrValue);
11273
- if (!ctor) {
11274
- throw new Error("Unable to resolve constructor when registering column metadata");
11275
- }
11276
- registerColumn(ctor, propertyName, { ...normalized });
11746
+ return function(_value, context) {
11747
+ registerColumnFromContext(context, normalized);
11277
11748
  };
11278
- return decorator;
11279
11749
  }
11280
11750
  function PrimaryKey(definition) {
11281
11751
  const normalized = normalizeColumnInput(definition);
@@ -11290,41 +11760,21 @@ var normalizePropertyName2 = (name) => {
11290
11760
  }
11291
11761
  return name;
11292
11762
  };
11293
- var resolveConstructor2 = (instanceOrCtor) => {
11294
- if (typeof instanceOrCtor === "function") {
11295
- return instanceOrCtor;
11296
- }
11297
- if (instanceOrCtor && typeof instanceOrCtor.constructor === "function") {
11298
- return instanceOrCtor.constructor;
11299
- }
11300
- return void 0;
11301
- };
11302
- var registerRelation = (ctor, propertyName, metadata) => {
11303
- addRelationMetadata(ctor, propertyName, metadata);
11304
- };
11305
11763
  var createFieldDecorator = (metadataFactory) => {
11306
- const decorator = (targetOrValue, propertyKeyOrContext) => {
11307
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
11308
- const ctx = propertyKeyOrContext;
11309
- if (!ctx.name) {
11310
- throw new Error("Relation decorator requires a property name");
11311
- }
11312
- const propertyName2 = normalizePropertyName2(ctx.name);
11313
- const bag = getOrCreateMetadataBag(ctx);
11314
- const relationMetadata = metadataFactory(propertyName2);
11315
- if (!bag.relations.some((entry) => entry.propertyName === propertyName2)) {
11316
- bag.relations.push({ propertyName: propertyName2, relation: relationMetadata });
11317
- }
11318
- return;
11764
+ return function(_value, context) {
11765
+ if (!context.name) {
11766
+ throw new Error("Relation decorator requires a property name");
11319
11767
  }
11320
- const propertyName = normalizePropertyName2(propertyKeyOrContext);
11321
- const ctor = resolveConstructor2(targetOrValue);
11322
- if (!ctor) {
11323
- 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 });
11324
11776
  }
11325
- registerRelation(ctor, propertyName, metadataFactory(propertyName));
11326
11777
  };
11327
- return decorator;
11328
11778
  };
11329
11779
  function HasMany(options) {
11330
11780
  return createFieldDecorator((propertyName) => ({
@@ -11351,7 +11801,7 @@ function BelongsTo(options) {
11351
11801
  kind: RelationKinds.BelongsTo,
11352
11802
  propertyKey: propertyName,
11353
11803
  target: options.target,
11354
- foreignKey: options.foreignKey,
11804
+ foreignKey: options.foreignKey ?? `${propertyName}_id`,
11355
11805
  localKey: options.localKey,
11356
11806
  cascade: options.cascade
11357
11807
  }));
@@ -11427,13 +11877,15 @@ var deferred = () => {
11427
11877
  return { promise, resolve, reject };
11428
11878
  };
11429
11879
  var Pool = class {
11880
+ adapter;
11881
+ options;
11882
+ destroyed = false;
11883
+ creating = 0;
11884
+ leased = 0;
11885
+ idle = [];
11886
+ waiters = [];
11887
+ reapTimer = null;
11430
11888
  constructor(adapter, options) {
11431
- this.destroyed = false;
11432
- this.creating = 0;
11433
- this.leased = 0;
11434
- this.idle = [];
11435
- this.waiters = [];
11436
- this.reapTimer = null;
11437
11889
  if (!Number.isFinite(options.max) || options.max <= 0) {
11438
11890
  throw new Error("Pool options.max must be a positive number");
11439
11891
  }
@@ -11995,6 +12447,8 @@ export {
11995
12447
  esel,
11996
12448
  executeHydrated,
11997
12449
  executeHydratedWithContexts,
12450
+ executeSchemaSql,
12451
+ executeSchemaSqlFor,
11998
12452
  exists,
11999
12453
  exp,
12000
12454
  extract,
@@ -12003,6 +12457,7 @@ export {
12003
12457
  fromUnixTime,
12004
12458
  generateCreateTableSql,
12005
12459
  generateSchemaSql,
12460
+ generateSchemaSqlFor,
12006
12461
  getColumn,
12007
12462
  getDecoratorMetadata,
12008
12463
  getSchemaIntrospector,
@@ -12108,6 +12563,7 @@ export {
12108
12563
  second,
12109
12564
  sel,
12110
12565
  selectFromEntity,
12566
+ setRelations,
12111
12567
  sha1,
12112
12568
  sha2,
12113
12569
  shiftLeft,