metal-orm 1.0.58 → 1.0.60

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 (41) hide show
  1. package/README.md +34 -31
  2. package/dist/index.cjs +1583 -901
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +400 -129
  5. package/dist/index.d.ts +400 -129
  6. package/dist/index.js +1575 -901
  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/index.ts +7 -7
  16. package/src/orm/entity-hydration.ts +72 -0
  17. package/src/orm/entity-meta.ts +13 -11
  18. package/src/orm/entity-metadata.ts +240 -238
  19. package/src/orm/entity-relation-cache.ts +39 -0
  20. package/src/orm/entity-relations.ts +207 -0
  21. package/src/orm/entity.ts +124 -410
  22. package/src/orm/execute.ts +4 -4
  23. package/src/orm/lazy-batch/belongs-to-many.ts +134 -0
  24. package/src/orm/lazy-batch/belongs-to.ts +108 -0
  25. package/src/orm/lazy-batch/has-many.ts +69 -0
  26. package/src/orm/lazy-batch/has-one.ts +68 -0
  27. package/src/orm/lazy-batch/shared.ts +125 -0
  28. package/src/orm/lazy-batch.ts +4 -492
  29. package/src/orm/relations/many-to-many.ts +2 -1
  30. package/src/query-builder/relation-cte-builder.ts +63 -0
  31. package/src/query-builder/relation-filter-utils.ts +159 -0
  32. package/src/query-builder/relation-include-strategies.ts +177 -0
  33. package/src/query-builder/relation-join-planner.ts +80 -0
  34. package/src/query-builder/relation-service.ts +119 -479
  35. package/src/query-builder/relation-types.ts +41 -10
  36. package/src/query-builder/select/projection-facet.ts +23 -23
  37. package/src/query-builder/select/select-operations.ts +145 -0
  38. package/src/query-builder/select.ts +329 -221
  39. package/src/schema/relation.ts +22 -18
  40. package/src/schema/table.ts +22 -9
  41. 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,17 +4159,274 @@ 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;
4162
+ astService(state) {
4163
+ return this.createQueryAstService(this.table, state);
4164
+ }
4165
+ };
4166
+
4167
+ // src/query-builder/relation-include-strategies.ts
4168
+ var buildTypedSelection = (columns, prefix, keys, missingMsg) => {
4169
+ return keys.reduce((acc, key) => {
4170
+ const def = columns[key];
4171
+ if (!def) {
4172
+ throw new Error(missingMsg(key));
4349
4173
  }
4350
- if (target.type === "DerivedTable") {
4351
- return target.alias;
4174
+ acc[makeRelationAlias(prefix, key)] = def;
4175
+ return acc;
4176
+ }, {});
4177
+ };
4178
+ var resolveTargetColumns = (relation, options) => {
4179
+ const requestedColumns = options?.columns?.length ? [...options.columns] : Object.keys(relation.target.columns);
4180
+ const targetPrimaryKey = findPrimaryKey(relation.target);
4181
+ if (!requestedColumns.includes(targetPrimaryKey)) {
4182
+ requestedColumns.push(targetPrimaryKey);
4183
+ }
4184
+ return requestedColumns;
4185
+ };
4186
+ var ensureRootForeignKeySelected = (context, relation) => {
4187
+ const fkColumn = context.rootTable.columns[relation.foreignKey];
4188
+ if (!fkColumn) {
4189
+ return { state: context.state, hydration: context.hydration };
4190
+ }
4191
+ const hasForeignKeySelected = context.state.ast.columns.some((col2) => {
4192
+ if (col2.type !== "Column") return false;
4193
+ const node = col2;
4194
+ const alias = node.alias ?? node.name;
4195
+ return alias === relation.foreignKey;
4196
+ });
4197
+ if (hasForeignKeySelected) {
4198
+ return { state: context.state, hydration: context.hydration };
4199
+ }
4200
+ return context.selectColumns(context.state, context.hydration, {
4201
+ [relation.foreignKey]: fkColumn
4202
+ });
4203
+ };
4204
+ var standardIncludeStrategy = (context) => {
4205
+ const relation = context.relation;
4206
+ let { state, hydration } = context;
4207
+ const fkSelectionResult = ensureRootForeignKeySelected(context, relation);
4208
+ state = fkSelectionResult.state;
4209
+ hydration = fkSelectionResult.hydration;
4210
+ const targetColumns = resolveTargetColumns(relation, context.options);
4211
+ const targetSelection = buildTypedSelection(
4212
+ relation.target.columns,
4213
+ context.aliasPrefix,
4214
+ targetColumns,
4215
+ (key) => `Column '${key}' not found on relation '${context.relationName}'`
4216
+ );
4217
+ const relationSelectionResult = context.selectColumns(state, hydration, targetSelection);
4218
+ state = relationSelectionResult.state;
4219
+ hydration = relationSelectionResult.hydration;
4220
+ hydration = hydration.onRelationIncluded(
4221
+ state,
4222
+ relation,
4223
+ context.relationName,
4224
+ context.aliasPrefix,
4225
+ targetColumns
4226
+ );
4227
+ return { state, hydration };
4228
+ };
4229
+ var belongsToManyStrategy = (context) => {
4230
+ const relation = context.relation;
4231
+ let { state, hydration } = context;
4232
+ const targetColumns = resolveTargetColumns(relation, context.options);
4233
+ const targetSelection = buildTypedSelection(
4234
+ relation.target.columns,
4235
+ context.aliasPrefix,
4236
+ targetColumns,
4237
+ (key) => `Column '${key}' not found on relation '${context.relationName}'`
4238
+ );
4239
+ const pivotAliasPrefix = context.options?.pivot?.aliasPrefix ?? `${context.aliasPrefix}_pivot`;
4240
+ const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
4241
+ const defaultPivotColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
4242
+ const pivotColumns = context.options?.pivot?.columns ? [...context.options.pivot.columns] : [...defaultPivotColumns];
4243
+ const pivotSelection = buildTypedSelection(
4244
+ relation.pivotTable.columns,
4245
+ pivotAliasPrefix,
4246
+ pivotColumns,
4247
+ (key) => `Column '${key}' not found on pivot table '${relation.pivotTable.name}'`
4248
+ );
4249
+ const combinedSelection = {
4250
+ ...targetSelection,
4251
+ ...pivotSelection
4252
+ };
4253
+ const relationSelectionResult = context.selectColumns(state, hydration, combinedSelection);
4254
+ state = relationSelectionResult.state;
4255
+ hydration = relationSelectionResult.hydration;
4256
+ hydration = hydration.onRelationIncluded(
4257
+ state,
4258
+ relation,
4259
+ context.relationName,
4260
+ context.aliasPrefix,
4261
+ targetColumns,
4262
+ { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
4263
+ );
4264
+ return { state, hydration };
4265
+ };
4266
+ var relationIncludeStrategies = {
4267
+ [RelationKinds.HasMany]: standardIncludeStrategy,
4268
+ [RelationKinds.HasOne]: standardIncludeStrategy,
4269
+ [RelationKinds.BelongsTo]: standardIncludeStrategy,
4270
+ [RelationKinds.BelongsToMany]: belongsToManyStrategy
4271
+ };
4272
+
4273
+ // src/query-builder/relation-service.ts
4274
+ var RelationService = class {
4275
+ /**
4276
+ * Creates a new RelationService instance
4277
+ * @param table - Table definition
4278
+ * @param state - Current query state
4279
+ * @param hydration - Hydration manager
4280
+ */
4281
+ constructor(table, state, hydration, createQueryAstService) {
4282
+ this.table = table;
4283
+ this.state = state;
4284
+ this.hydration = hydration;
4285
+ this.createQueryAstService = createQueryAstService;
4286
+ this.projectionHelper = new RelationProjectionHelper(
4287
+ table,
4288
+ (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
4289
+ );
4290
+ this.joinPlanner = new RelationJoinPlanner(table, createQueryAstService);
4291
+ this.cteBuilder = new RelationCteBuilder(table, createQueryAstService);
4292
+ }
4293
+ projectionHelper;
4294
+ joinPlanner;
4295
+ cteBuilder;
4296
+ /**
4297
+ * Joins a relation to the query
4298
+ * @param relationName - Name of the relation to join
4299
+ * @param joinKind - Type of join to use
4300
+ * @param extraCondition - Additional join condition
4301
+ * @returns Relation result with updated state and hydration
4302
+ */
4303
+ joinRelation(relationName, joinKind, extraCondition, tableSource) {
4304
+ const relation = this.getRelation(relationName);
4305
+ const nextState = this.joinPlanner.withJoin(
4306
+ this.state,
4307
+ relationName,
4308
+ relation,
4309
+ joinKind,
4310
+ extraCondition,
4311
+ tableSource
4312
+ );
4313
+ return { state: nextState, hydration: this.hydration };
4314
+ }
4315
+ /**
4316
+ * Matches records based on a relation with an optional predicate
4317
+ * @param relationName - Name of the relation to match
4318
+ * @param predicate - Optional predicate expression
4319
+ * @returns Relation result with updated state and hydration
4320
+ */
4321
+ match(relationName, predicate) {
4322
+ const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
4323
+ const pk = findPrimaryKey(this.table);
4324
+ const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
4325
+ const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
4326
+ const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
4327
+ return { state: nextState, hydration: joined.hydration };
4328
+ }
4329
+ /**
4330
+ * Includes a relation in the query result
4331
+ * @param relationName - Name of the relation to include
4332
+ * @param options - Options for relation inclusion
4333
+ * @returns Relation result with updated state and hydration
4334
+ */
4335
+ include(relationName, options) {
4336
+ let state = this.state;
4337
+ let hydration = this.hydration;
4338
+ const relation = this.getRelation(relationName);
4339
+ const aliasPrefix = options?.aliasPrefix ?? relationName;
4340
+ const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
4341
+ const { selfFilters, crossFilters } = splitFilterExpressions(
4342
+ options?.filter,
4343
+ /* @__PURE__ */ new Set([relation.target.name])
4344
+ );
4345
+ const canUseCte = !alreadyJoined && selfFilters.length > 0;
4346
+ const joinFilters = [...crossFilters];
4347
+ if (!canUseCte) {
4348
+ joinFilters.push(...selfFilters);
4352
4349
  }
4353
- if (target.type === "FunctionTable") {
4354
- return target.alias ?? relation.target.name;
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;
4355
4362
  }
4356
- return relation.target.name;
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
+ };
4357
4430
  }
4358
4431
  /**
4359
4432
  * Gets a relation definition by name
@@ -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) {
@@ -5489,156 +5614,61 @@ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, o
5489
5614
  const targetSelectedColumns = targetRequestedColumns ? [...targetRequestedColumns] : Object.keys(relation.target.columns);
5490
5615
  if (!targetSelectedColumns.includes(targetKey)) {
5491
5616
  targetSelectedColumns.push(targetKey);
5492
- }
5493
- const targetSelection = buildColumnSelection(
5494
- relation.target,
5495
- targetSelectedColumns,
5496
- (column) => `Column '${column}' not found on relation '${relationName}'`
5497
- );
5498
- const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds, targetSelection, options?.filter);
5499
- const targetMap = groupRowsByUnique(targetRows, targetKey);
5500
- const targetVisibleColumns = new Set(targetSelectedColumns);
5501
- const result = /* @__PURE__ */ new Map();
5502
- for (const [rootId, entries] of rootLookup.entries()) {
5503
- const bucket = [];
5504
- for (const entry of entries) {
5505
- const targetRow = targetMap.get(toKey6(entry.targetId));
5506
- if (!targetRow) continue;
5507
- bucket.push({
5508
- ...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
5509
- _pivot: entry.pivot
5510
- });
5511
- }
5512
- result.set(rootId, bucket);
5513
- }
5514
- return result;
5515
- };
5516
-
5517
- // src/orm/entity.ts
5518
- var relationLoaderCache = (meta, relationName, factory) => {
5519
- if (meta.relationCache.has(relationName)) {
5520
- return meta.relationCache.get(relationName);
5521
- }
5522
- const promise = factory().then((value) => {
5523
- for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
5524
- const otherMeta = getEntityMeta(tracked.entity);
5525
- if (!otherMeta) continue;
5526
- otherMeta.relationHydration.set(relationName, value);
5527
- }
5528
- return value;
5529
- });
5530
- meta.relationCache.set(relationName, promise);
5531
- for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
5532
- const otherMeta = getEntityMeta(tracked.entity);
5533
- if (!otherMeta) continue;
5534
- otherMeta.relationCache.set(relationName, promise);
5535
- }
5536
- return promise;
5537
- };
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
- };
5567
- }
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;
5617
+ }
5618
+ const targetSelection = buildColumnSelection(
5619
+ relation.target,
5620
+ targetSelectedColumns,
5621
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5622
+ );
5623
+ const targetRows = await fetchRowsForKeys(
5624
+ ctx,
5625
+ relation.target,
5626
+ targetPkColumn,
5627
+ targetIds,
5628
+ targetSelection,
5629
+ options?.filter
5630
+ );
5631
+ const targetMap = groupRowsByUnique(targetRows, targetKey);
5632
+ const targetVisibleColumns = new Set(targetSelectedColumns);
5633
+ const result = /* @__PURE__ */ new Map();
5634
+ for (const [rootId, entries] of rootLookup.entries()) {
5635
+ const bucket = [];
5636
+ for (const entry of entries) {
5637
+ const targetRow = targetMap.get(toKey7(entry.targetId));
5638
+ if (!targetRow) continue;
5639
+ bucket.push({
5640
+ ...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
5641
+ _pivot: entry.pivot
5642
+ });
5626
5643
  }
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
- }
5644
+ result.set(rootId, bucket);
5645
+ }
5646
+ return result;
5647
+ };
5648
+
5649
+ // src/orm/entity-relation-cache.ts
5650
+ var relationLoaderCache = (meta, relationName, factory) => {
5651
+ if (meta.relationCache.has(relationName)) {
5652
+ return meta.relationCache.get(relationName);
5653
+ }
5654
+ const promise = factory().then((value) => {
5655
+ for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
5656
+ const otherMeta = getEntityMeta(tracked.entity);
5657
+ if (!otherMeta) continue;
5658
+ otherMeta.relationHydration.set(relationName, value);
5639
5659
  }
5660
+ return value;
5661
+ });
5662
+ meta.relationCache.set(relationName, promise);
5663
+ for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
5664
+ const otherMeta = getEntityMeta(tracked.entity);
5665
+ if (!otherMeta) continue;
5666
+ otherMeta.relationCache.set(relationName, promise);
5640
5667
  }
5668
+ return promise;
5641
5669
  };
5670
+
5671
+ // src/orm/entity-relations.ts
5642
5672
  var proxifyRelationWrapper = (wrapper) => {
5643
5673
  return new Proxy(wrapper, {
5644
5674
  get(target, prop, receiver) {
@@ -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,6 +5816,71 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
5791
5816
  }
5792
5817
  };
5793
5818
 
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
+
5794
5884
  // src/orm/execute.ts
5795
5885
  var flattenResults = (results) => {
5796
5886
  const rows = [];
@@ -5880,14 +5970,416 @@ var loadLazyRelationsForTable = async (ctx, table, lazyRelations, lazyRelationOp
5880
5970
  }
5881
5971
  };
5882
5972
 
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
- }
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;
6301
+ }
6302
+ /**
6303
+ * Applies a set operation to the query
6304
+ * @param context - Current query context
6305
+ * @param operator - Set operation kind
6306
+ * @param subAst - Subquery AST to combine
6307
+ * @returns Updated query context with set operation
6308
+ */
6309
+ applySetOperation(context, operator, subAst) {
6310
+ const astService = this.createAstService(context.state);
6311
+ const nextState = astService.withSetOperation(operator, subAst);
6312
+ return { state: nextState, hydration: context.hydration };
6313
+ }
6314
+ };
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;
6324
+ }
6325
+ /**
6326
+ * Matches records based on a relationship
6327
+ * @param context - Current query context
6328
+ * @param relationName - Name of the relationship
6329
+ * @param predicate - Optional predicate
6330
+ * @returns Updated query context with relation match
6331
+ */
6332
+ match(context, relationName, predicate) {
6333
+ return this.relationManager.match(context, relationName, predicate);
6334
+ }
6335
+ /**
6336
+ * Joins a related table
6337
+ * @param context - Current query context
6338
+ * @param relationName - Name of the relationship
6339
+ * @param joinKind - Type of join
6340
+ * @param extraCondition - Optional additional condition
6341
+ * @returns Updated query context with relation join
6342
+ */
6343
+ joinRelation(context, relationName, joinKind, extraCondition) {
6344
+ return this.relationManager.joinRelation(context, relationName, joinKind, extraCondition);
6345
+ }
6346
+ /**
6347
+ * Includes related data in the query results
6348
+ * @param context - Current query context
6349
+ * @param relationName - Name of the relationship to include
6350
+ * @param options - Optional include options
6351
+ * @returns Updated query context with relation inclusion
6352
+ */
6353
+ include(context, relationName, options) {
6354
+ return this.relationManager.include(context, relationName, options);
6355
+ }
6356
+ /**
6357
+ * Applies correlation for relation-based subqueries
6358
+ * @param context - Current query context
6359
+ * @param relationName - Name of the relationship
6360
+ * @param subAst - Subquery AST
6361
+ * @param extraCorrelate - Optional additional correlation
6362
+ * @returns Modified subquery AST with correlation
6363
+ */
6364
+ applyRelationCorrelation(context, relationName, subAst, extraCorrelate) {
6365
+ return this.relationManager.applyRelationCorrelation(context, relationName, subAst, extraCorrelate);
6366
+ }
6367
+ };
5888
6368
 
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
@@ -5928,14 +6428,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5928
6428
  /**
5929
6429
  * Applies an alias to the root FROM table.
5930
6430
  * @param alias - Alias to apply
6431
+ * @example
6432
+ * const qb = new SelectQueryBuilder(userTable).as('u');
5931
6433
  */
5932
6434
  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));
6435
+ const nextContext = this.fromFacet.as(this.context, alias);
5939
6436
  return this.clone(nextContext);
5940
6437
  }
5941
6438
  /**
@@ -5960,29 +6457,6 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5960
6457
  createChildBuilder(table) {
5961
6458
  return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
5962
6459
  }
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
6460
  /**
5987
6461
  * Applies a set operation to the query
5988
6462
  * @param operator - Set operation kind
@@ -5991,12 +6465,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5991
6465
  */
5992
6466
  applySetOperation(operator, query) {
5993
6467
  const subAst = resolveSelectQuery(query);
5994
- return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
6468
+ return this.setOpFacet.applySetOperation(this.context, operator, subAst);
5995
6469
  }
5996
6470
  select(...args) {
5997
6471
  if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && typeof args[0] !== "string") {
5998
6472
  const columns = args[0];
5999
- return this.clone(this.columnSelector.select(this.context, columns));
6473
+ return this.clone(this.projectionFacet.select(this.context, columns));
6000
6474
  }
6001
6475
  const cols = args;
6002
6476
  const selection = {};
@@ -6007,15 +6481,17 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6007
6481
  }
6008
6482
  selection[key] = col2;
6009
6483
  }
6010
- return this.clone(this.columnSelector.select(this.context, selection));
6484
+ return this.clone(this.projectionFacet.select(this.context, selection));
6011
6485
  }
6012
6486
  /**
6013
6487
  * Selects raw column expressions
6014
6488
  * @param cols - Column expressions as strings
6015
6489
  * @returns New query builder instance with raw column selections
6490
+ * @example
6491
+ * qb.selectRaw('COUNT(*) as total', 'UPPER(name) as upper_name');
6016
6492
  */
6017
6493
  selectRaw(...cols) {
6018
- return this.clone(this.columnSelector.selectRaw(this.context, cols));
6494
+ return this.clone(this.projectionFacet.selectRaw(this.context, cols));
6019
6495
  }
6020
6496
  /**
6021
6497
  * Adds a Common Table Expression (CTE) to the query
@@ -6023,10 +6499,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6023
6499
  * @param query - Query builder or query node for the CTE
6024
6500
  * @param columns - Optional column names for the CTE
6025
6501
  * @returns New query builder instance with the CTE
6502
+ * @example
6503
+ * const recentUsers = new SelectQueryBuilder(userTable)
6504
+ * .where(gt(userTable.columns.createdAt, subDays(now(), 30)));
6505
+ * const qb = new SelectQueryBuilder(userTable)
6506
+ * .with('recent_users', recentUsers)
6507
+ * .from('recent_users');
6026
6508
  */
6027
6509
  with(name, query, columns) {
6028
6510
  const subAst = resolveSelectQuery(query);
6029
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
6511
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, false);
6030
6512
  return this.clone(nextContext);
6031
6513
  }
6032
6514
  /**
@@ -6035,10 +6517,23 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6035
6517
  * @param query - Query builder or query node for the CTE
6036
6518
  * @param columns - Optional column names for the CTE
6037
6519
  * @returns New query builder instance with the recursive CTE
6520
+ * @example
6521
+ * // Base case: select root nodes
6522
+ * const baseQuery = new SelectQueryBuilder(orgTable)
6523
+ * .where(eq(orgTable.columns.parentId, 1));
6524
+ * // Recursive case: join with the CTE itself
6525
+ * const recursiveQuery = new SelectQueryBuilder(orgTable)
6526
+ * .join('org_hierarchy', 'oh', eq(orgTable.columns.parentId, col('oh.id')));
6527
+ * // Combine base and recursive parts
6528
+ * const orgHierarchy = baseQuery.union(recursiveQuery);
6529
+ * // Use in main query
6530
+ * const qb = new SelectQueryBuilder(orgTable)
6531
+ * .withRecursive('org_hierarchy', orgHierarchy)
6532
+ * .from('org_hierarchy');
6038
6533
  */
6039
6534
  withRecursive(name, query, columns) {
6040
6535
  const subAst = resolveSelectQuery(query);
6041
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
6536
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, true);
6042
6537
  return this.clone(nextContext);
6043
6538
  }
6044
6539
  /**
@@ -6047,11 +6542,15 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6047
6542
  * @param alias - Alias for the derived table
6048
6543
  * @param columnAliases - Optional column alias list
6049
6544
  * @returns New query builder instance with updated FROM
6545
+ * @example
6546
+ * const subquery = new SelectQueryBuilder(userTable)
6547
+ * .select('id', 'name')
6548
+ * .where(gt(userTable.columns.score, 100));
6549
+ * qb.fromSubquery(subquery, 'high_scorers', ['userId', 'userName']);
6050
6550
  */
6051
6551
  fromSubquery(subquery, alias, columnAliases) {
6052
6552
  const subAst = resolveSelectQuery(subquery);
6053
- const fromNode = derivedTable(subAst, alias, columnAliases);
6054
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(fromNode));
6553
+ const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
6055
6554
  return this.clone(nextContext);
6056
6555
  }
6057
6556
  /**
@@ -6060,10 +6559,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6060
6559
  * @param args - Optional function arguments
6061
6560
  * @param alias - Optional alias for the function table
6062
6561
  * @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
6562
+ * @example
6563
+ * qb.fromFunctionTable(
6564
+ * 'generate_series',
6565
+ * [literal(1), literal(10), literal(1)],
6566
+ * 'series',
6567
+ * { columnAliases: ['value'] }
6568
+ * );
6063
6569
  */
6064
6570
  fromFunctionTable(name, args = [], alias, options) {
6065
- const functionTable = fnTable(name, args, alias, options);
6066
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(functionTable));
6571
+ const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
6067
6572
  return this.clone(nextContext);
6068
6573
  }
6069
6574
  /**
@@ -6071,10 +6576,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6071
6576
  * @param alias - Alias for the subquery column
6072
6577
  * @param sub - Query builder or query node for the subquery
6073
6578
  * @returns New query builder instance with the subquery selection
6579
+ * @example
6580
+ * const postCount = new SelectQueryBuilder(postTable)
6581
+ * .select(count(postTable.columns.id))
6582
+ * .where(eq(postTable.columns.userId, col('u.id')));
6583
+ * qb.select('id', 'name')
6584
+ * .selectSubquery('postCount', postCount);
6074
6585
  */
6075
6586
  selectSubquery(alias, sub2) {
6076
6587
  const query = resolveSelectQuery(sub2);
6077
- return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
6588
+ return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
6078
6589
  }
6079
6590
  /**
6080
6591
  * Adds a JOIN against a derived table (subquery with alias)
@@ -6084,11 +6595,19 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6084
6595
  * @param joinKind - Join kind (defaults to INNER)
6085
6596
  * @param columnAliases - Optional column alias list for the derived table
6086
6597
  * @returns New query builder instance with the derived-table join
6598
+ * @example
6599
+ * const activeUsers = new SelectQueryBuilder(userTable)
6600
+ * .where(eq(userTable.columns.active, true));
6601
+ * qb.joinSubquery(
6602
+ * activeUsers,
6603
+ * 'au',
6604
+ * eq(col('t.userId'), col('au.id')),
6605
+ * JOIN_KINDS.LEFT
6606
+ * );
6087
6607
  */
6088
6608
  joinSubquery(subquery, alias, condition, joinKind = JOIN_KINDS.INNER, columnAliases) {
6089
6609
  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));
6610
+ const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
6092
6611
  return this.clone(nextContext);
6093
6612
  }
6094
6613
  /**
@@ -6099,11 +6618,18 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6099
6618
  * @param condition - Join condition expression
6100
6619
  * @param joinKind - Kind of join (defaults to INNER)
6101
6620
  * @param options - Optional metadata (lateral, ordinality, column aliases, schema)
6621
+ * @example
6622
+ * qb.joinFunctionTable(
6623
+ * 'generate_series',
6624
+ * [literal(1), literal(10)],
6625
+ * 'gs',
6626
+ * eq(col('t.value'), col('gs.value')),
6627
+ * JOIN_KINDS.INNER,
6628
+ * { columnAliases: ['value'] }
6629
+ * );
6102
6630
  */
6103
6631
  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));
6632
+ const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
6107
6633
  return this.clone(nextContext);
6108
6634
  }
6109
6635
  /**
@@ -6111,9 +6637,14 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6111
6637
  * @param table - Table to join
6112
6638
  * @param condition - Join condition expression
6113
6639
  * @returns New query builder instance with the INNER JOIN
6640
+ * @example
6641
+ * qb.innerJoin(
6642
+ * postTable,
6643
+ * eq(userTable.columns.id, postTable.columns.userId)
6644
+ * );
6114
6645
  */
6115
6646
  innerJoin(table, condition) {
6116
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
6647
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
6117
6648
  return this.clone(nextContext);
6118
6649
  }
6119
6650
  /**
@@ -6121,9 +6652,14 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6121
6652
  * @param table - Table to join
6122
6653
  * @param condition - Join condition expression
6123
6654
  * @returns New query builder instance with the LEFT JOIN
6655
+ * @example
6656
+ * qb.leftJoin(
6657
+ * postTable,
6658
+ * eq(userTable.columns.id, postTable.columns.userId)
6659
+ * );
6124
6660
  */
6125
6661
  leftJoin(table, condition) {
6126
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
6662
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
6127
6663
  return this.clone(nextContext);
6128
6664
  }
6129
6665
  /**
@@ -6131,9 +6667,14 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6131
6667
  * @param table - Table to join
6132
6668
  * @param condition - Join condition expression
6133
6669
  * @returns New query builder instance with the RIGHT JOIN
6670
+ * @example
6671
+ * qb.rightJoin(
6672
+ * postTable,
6673
+ * eq(userTable.columns.id, postTable.columns.userId)
6674
+ * );
6134
6675
  */
6135
6676
  rightJoin(table, condition) {
6136
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
6677
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
6137
6678
  return this.clone(nextContext);
6138
6679
  }
6139
6680
  /**
@@ -6141,9 +6682,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6141
6682
  * @param relationName - Name of the relationship to match
6142
6683
  * @param predicate - Optional predicate expression
6143
6684
  * @returns New query builder instance with the relationship match
6685
+ * @example
6686
+ * qb.match('posts', eq(postTable.columns.published, true));
6144
6687
  */
6145
6688
  match(relationName, predicate) {
6146
- const nextContext = this.relationManager.match(this.context, relationName, predicate);
6689
+ const nextContext = this.relationFacet.match(this.context, relationName, predicate);
6147
6690
  return this.clone(nextContext);
6148
6691
  }
6149
6692
  /**
@@ -6152,9 +6695,13 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6152
6695
  * @param joinKind - Type of join (defaults to INNER)
6153
6696
  * @param extraCondition - Optional additional join condition
6154
6697
  * @returns New query builder instance with the relationship join
6698
+ * @example
6699
+ * qb.joinRelation('posts', JOIN_KINDS.LEFT);
6700
+ * @example
6701
+ * qb.joinRelation('posts', JOIN_KINDS.INNER, eq(postTable.columns.published, true));
6155
6702
  */
6156
6703
  joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
6157
- const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
6704
+ const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
6158
6705
  return this.clone(nextContext);
6159
6706
  }
6160
6707
  /**
@@ -6162,9 +6709,18 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6162
6709
  * @param relationName - Name of the relationship to include
6163
6710
  * @param options - Optional include options
6164
6711
  * @returns New query builder instance with the relationship inclusion
6712
+ * @example
6713
+ * qb.include('posts');
6714
+ * @example
6715
+ * qb.include('posts', { columns: ['id', 'title', 'published'] });
6716
+ * @example
6717
+ * qb.include('posts', {
6718
+ * columns: ['id', 'title'],
6719
+ * where: eq(postTable.columns.published, true)
6720
+ * });
6165
6721
  */
6166
6722
  include(relationName, options) {
6167
- const nextContext = this.relationManager.include(this.context, relationName, options);
6723
+ const nextContext = this.relationFacet.include(this.context, relationName, options);
6168
6724
  return this.clone(nextContext);
6169
6725
  }
6170
6726
  /**
@@ -6172,6 +6728,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6172
6728
  * @param relationName - Name of the relation to include lazily
6173
6729
  * @param options - Optional include options for lazy loading
6174
6730
  * @returns New query builder instance with lazy relation inclusion
6731
+ * @example
6732
+ * const qb = new SelectQueryBuilder(userTable).includeLazy('posts');
6733
+ * const users = await qb.execute(session);
6734
+ * // Access posts later - they will be loaded on demand
6735
+ * const posts = await users[0].posts;
6175
6736
  */
6176
6737
  includeLazy(relationName, options) {
6177
6738
  let nextContext = this.context;
@@ -6201,14 +6762,22 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6201
6762
  }
6202
6763
  /**
6203
6764
  * Convenience alias for including only specific columns from a relation.
6765
+ * @example
6766
+ * qb.includePick('posts', ['id', 'title', 'createdAt']);
6204
6767
  */
6205
6768
  includePick(relationName, cols) {
6206
- return this.include(relationName, { columns: cols });
6769
+ const options = { columns: cols };
6770
+ return this.include(relationName, options);
6207
6771
  }
6208
6772
  /**
6209
6773
  * Selects columns for the root table and relations from an array of entries
6210
6774
  * @param config - Configuration array for deep column selection
6211
6775
  * @returns New query builder instance with deep column selections
6776
+ * @example
6777
+ * qb.selectColumnsDeep([
6778
+ * { type: 'root', columns: ['id', 'name'] },
6779
+ * { type: 'relation', relationName: 'posts', columns: ['id', 'title'] }
6780
+ * ]);
6212
6781
  */
6213
6782
  selectColumnsDeep(config) {
6214
6783
  let currBuilder = this;
@@ -6216,7 +6785,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6216
6785
  if (entry.type === "root") {
6217
6786
  currBuilder = currBuilder.select(...entry.columns);
6218
6787
  } else {
6219
- currBuilder = currBuilder.include(entry.relationName, { columns: entry.columns });
6788
+ const options = { columns: entry.columns };
6789
+ currBuilder = currBuilder.include(entry.relationName, options);
6220
6790
  }
6221
6791
  }
6222
6792
  return currBuilder;
@@ -6246,61 +6816,41 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6246
6816
  * Executes the query and returns hydrated results
6247
6817
  * @param ctx - ORM session context
6248
6818
  * @returns Promise of entity instances
6819
+ * @example
6820
+ * const users = await qb.select('id', 'name')
6821
+ * .where(eq(userTable.columns.active, true))
6822
+ * .execute(session);
6249
6823
  */
6250
6824
  async execute(ctx) {
6251
6825
  return executeHydrated(ctx, this);
6252
6826
  }
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
- }
6827
+ /**
6828
+ * Executes a count query for the current builder without LIMIT/OFFSET clauses.
6829
+ *
6830
+ * @example
6831
+ * const total = await qb.count(session);
6832
+ */
6261
6833
  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);
6834
+ return executeCount(this.context, this.env, session);
6283
6835
  }
6836
+ /**
6837
+ * Executes the query and returns both the paged items and the total.
6838
+ *
6839
+ * @example
6840
+ * const { items, totalItems } = await qb.executePaged(session, { page: 1, pageSize: 20 });
6841
+ */
6284
6842
  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 };
6843
+ return executePagedQuery(this, session, options, (sess) => this.count(sess));
6298
6844
  }
6299
6845
  /**
6300
6846
  * Executes the query with provided execution and hydration contexts
6301
6847
  * @param execCtx - Execution context
6302
6848
  * @param hydCtx - Hydration context
6303
6849
  * @returns Promise of entity instances
6850
+ * @example
6851
+ * const execCtx = new ExecutionContext(session);
6852
+ * const hydCtx = new HydrationContext();
6853
+ * const users = await qb.executeWithContexts(execCtx, hydCtx);
6304
6854
  */
6305
6855
  async executeWithContexts(execCtx, hydCtx) {
6306
6856
  return executeHydratedWithContexts(execCtx, hydCtx, this);
@@ -6309,27 +6859,41 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6309
6859
  * Adds a WHERE condition to the query
6310
6860
  * @param expr - Expression for the WHERE clause
6311
6861
  * @returns New query builder instance with the WHERE condition
6862
+ * @example
6863
+ * qb.where(eq(userTable.columns.id, 1));
6864
+ * @example
6865
+ * qb.where(and(
6866
+ * eq(userTable.columns.active, true),
6867
+ * gt(userTable.columns.createdAt, subDays(now(), 30))
6868
+ * ));
6312
6869
  */
6313
6870
  where(expr) {
6314
- const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
6871
+ const nextContext = this.predicateFacet.where(this.context, expr);
6315
6872
  return this.clone(nextContext);
6316
6873
  }
6317
6874
  /**
6318
6875
  * Adds a GROUP BY clause to the query
6319
6876
  * @param term - Column definition or ordering term to group by
6320
6877
  * @returns New query builder instance with the GROUP BY clause
6878
+ * @example
6879
+ * qb.select('departmentId', count(userTable.columns.id))
6880
+ * .groupBy(userTable.columns.departmentId);
6321
6881
  */
6322
6882
  groupBy(term) {
6323
- const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(term));
6883
+ const nextContext = this.predicateFacet.groupBy(this.context, term);
6324
6884
  return this.clone(nextContext);
6325
6885
  }
6326
6886
  /**
6327
6887
  * Adds a HAVING condition to the query
6328
6888
  * @param expr - Expression for the HAVING clause
6329
6889
  * @returns New query builder instance with the HAVING condition
6890
+ * @example
6891
+ * qb.select('departmentId', count(userTable.columns.id))
6892
+ * .groupBy(userTable.columns.departmentId)
6893
+ * .having(gt(count(userTable.columns.id), 5));
6330
6894
  */
6331
6895
  having(expr) {
6332
- const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
6896
+ const nextContext = this.predicateFacet.having(this.context, expr);
6333
6897
  return this.clone(nextContext);
6334
6898
  }
6335
6899
  /**
@@ -6337,46 +6901,62 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6337
6901
  * @param term - Column definition or ordering term to order by
6338
6902
  * @param directionOrOptions - Order direction or options (defaults to ASC)
6339
6903
  * @returns New query builder instance with the ORDER BY clause
6904
+ *
6905
+ * @example
6906
+ * qb.orderBy(userTable.columns.createdAt, 'DESC');
6340
6907
  */
6341
6908
  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
- );
6909
+ const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
6348
6910
  return this.clone(nextContext);
6349
6911
  }
6350
6912
  /**
6351
6913
  * Adds a DISTINCT clause to the query
6352
6914
  * @param cols - Columns to make distinct
6353
6915
  * @returns New query builder instance with the DISTINCT clause
6916
+ * @example
6917
+ * qb.distinct(userTable.columns.email);
6918
+ * @example
6919
+ * qb.distinct(userTable.columns.firstName, userTable.columns.lastName);
6354
6920
  */
6355
6921
  distinct(...cols) {
6356
- return this.clone(this.columnSelector.distinct(this.context, cols));
6922
+ return this.clone(this.projectionFacet.distinct(this.context, cols));
6357
6923
  }
6358
6924
  /**
6359
6925
  * Adds a LIMIT clause to the query
6360
6926
  * @param n - Maximum number of rows to return
6361
6927
  * @returns New query builder instance with the LIMIT clause
6928
+ * @example
6929
+ * qb.limit(10);
6930
+ * @example
6931
+ * qb.limit(20).offset(40); // Pagination: page 3 with 20 items per page
6362
6932
  */
6363
6933
  limit(n) {
6364
- const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
6934
+ const nextContext = this.predicateFacet.limit(this.context, n);
6365
6935
  return this.clone(nextContext);
6366
6936
  }
6367
6937
  /**
6368
6938
  * Adds an OFFSET clause to the query
6369
6939
  * @param n - Number of rows to skip
6370
6940
  * @returns New query builder instance with the OFFSET clause
6941
+ * @example
6942
+ * qb.offset(10);
6943
+ * @example
6944
+ * qb.limit(20).offset(40); // Pagination: page 3 with 20 items per page
6371
6945
  */
6372
6946
  offset(n) {
6373
- const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
6947
+ const nextContext = this.predicateFacet.offset(this.context, n);
6374
6948
  return this.clone(nextContext);
6375
6949
  }
6376
6950
  /**
6377
6951
  * Combines this query with another using UNION
6378
6952
  * @param query - Query to union with
6379
6953
  * @returns New query builder instance with the set operation
6954
+ * @example
6955
+ * const activeUsers = new SelectQueryBuilder(userTable)
6956
+ * .where(eq(userTable.columns.active, true));
6957
+ * const inactiveUsers = new SelectQueryBuilder(userTable)
6958
+ * .where(eq(userTable.columns.active, false));
6959
+ * qb.union(activeUsers).union(inactiveUsers);
6380
6960
  */
6381
6961
  union(query) {
6382
6962
  return this.clone(this.applySetOperation("UNION", query));
@@ -6385,6 +6965,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6385
6965
  * Combines this query with another using UNION ALL
6386
6966
  * @param query - Query to union with
6387
6967
  * @returns New query builder instance with the set operation
6968
+ * @example
6969
+ * const q1 = new SelectQueryBuilder(userTable).where(gt(userTable.columns.score, 80));
6970
+ * const q2 = new SelectQueryBuilder(userTable).where(lt(userTable.columns.score, 20));
6971
+ * qb.unionAll(q1).unionAll(q2);
6388
6972
  */
6389
6973
  unionAll(query) {
6390
6974
  return this.clone(this.applySetOperation("UNION ALL", query));
@@ -6393,6 +6977,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6393
6977
  * Combines this query with another using INTERSECT
6394
6978
  * @param query - Query to intersect with
6395
6979
  * @returns New query builder instance with the set operation
6980
+ * @example
6981
+ * const activeUsers = new SelectQueryBuilder(userTable)
6982
+ * .where(eq(userTable.columns.active, true));
6983
+ * const premiumUsers = new SelectQueryBuilder(userTable)
6984
+ * .where(eq(userTable.columns.premium, true));
6985
+ * qb.intersect(activeUsers).intersect(premiumUsers);
6396
6986
  */
6397
6987
  intersect(query) {
6398
6988
  return this.clone(this.applySetOperation("INTERSECT", query));
@@ -6401,6 +6991,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6401
6991
  * Combines this query with another using EXCEPT
6402
6992
  * @param query - Query to subtract
6403
6993
  * @returns New query builder instance with the set operation
6994
+ * @example
6995
+ * const allUsers = new SelectQueryBuilder(userTable);
6996
+ * const inactiveUsers = new SelectQueryBuilder(userTable)
6997
+ * .where(eq(userTable.columns.active, false));
6998
+ * qb.except(allUsers).except(inactiveUsers); // Only active users
6404
6999
  */
6405
7000
  except(query) {
6406
7001
  return this.clone(this.applySetOperation("EXCEPT", query));
@@ -6409,6 +7004,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6409
7004
  * Adds a WHERE EXISTS condition to the query
6410
7005
  * @param subquery - Subquery to check for existence
6411
7006
  * @returns New query builder instance with the WHERE EXISTS condition
7007
+ * @example
7008
+ * const postsQuery = new SelectQueryBuilder(postTable)
7009
+ * .where(eq(postTable.columns.userId, col('u.id')));
7010
+ * qb.whereExists(postsQuery);
6412
7011
  */
6413
7012
  whereExists(subquery, correlate) {
6414
7013
  const subAst = resolveSelectQuery(subquery);
@@ -6419,6 +7018,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6419
7018
  * Adds a WHERE NOT EXISTS condition to the query
6420
7019
  * @param subquery - Subquery to check for non-existence
6421
7020
  * @returns New query builder instance with the WHERE NOT EXISTS condition
7021
+ * @example
7022
+ * const postsQuery = new SelectQueryBuilder(postTable)
7023
+ * .where(eq(postTable.columns.userId, col('u.id')));
7024
+ * qb.whereNotExists(postsQuery); // Users without posts
6422
7025
  */
6423
7026
  whereNotExists(subquery, correlate) {
6424
7027
  const subAst = resolveSelectQuery(subquery);
@@ -6430,47 +7033,54 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6430
7033
  * @param relationName - Name of the relationship to check
6431
7034
  * @param callback - Optional callback to modify the relationship query
6432
7035
  * @returns New query builder instance with the relationship existence check
7036
+ *
7037
+ * @example
7038
+ * qb.whereHas('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
6433
7039
  */
6434
7040
  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));
7041
+ const predicate = buildWhereHasPredicate(
7042
+ this.env,
7043
+ this.context,
7044
+ this.relationFacet,
7045
+ (table) => this.createChildBuilder(table),
7046
+ relationName,
7047
+ callbackOrOptions,
7048
+ maybeOptions,
7049
+ false
7050
+ );
7051
+ return this.where(predicate);
6448
7052
  }
6449
7053
  /**
6450
7054
  * Adds a WHERE NOT EXISTS condition based on a relationship
6451
7055
  * @param relationName - Name of the relationship to check
6452
7056
  * @param callback - Optional callback to modify the relationship query
6453
7057
  * @returns New query builder instance with the relationship non-existence check
7058
+ *
7059
+ * @example
7060
+ * qb.whereHasNot('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
6454
7061
  */
6455
7062
  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));
7063
+ const predicate = buildWhereHasPredicate(
7064
+ this.env,
7065
+ this.context,
7066
+ this.relationFacet,
7067
+ (table) => this.createChildBuilder(table),
7068
+ relationName,
7069
+ callbackOrOptions,
7070
+ maybeOptions,
7071
+ true
7072
+ );
7073
+ return this.where(predicate);
6469
7074
  }
6470
7075
  /**
6471
7076
  * Compiles the query to SQL for a specific dialect
6472
7077
  * @param dialect - Database dialect to compile for
6473
7078
  * @returns Compiled query with SQL and parameters
7079
+ * @example
7080
+ * const compiled = qb.select('id', 'name')
7081
+ * .where(eq(userTable.columns.active, true))
7082
+ * .compile('postgres');
7083
+ * console.log(compiled.sql); // SELECT "id", "name" FROM "users" WHERE "active" = true
6474
7084
  */
6475
7085
  compile(dialect) {
6476
7086
  const resolved = resolveDialectInput(dialect);
@@ -6480,6 +7090,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6480
7090
  * Converts the query to SQL string for a specific dialect
6481
7091
  * @param dialect - Database dialect to generate SQL for
6482
7092
  * @returns SQL string representation of the query
7093
+ * @example
7094
+ * const sql = qb.select('id', 'name')
7095
+ * .where(eq(userTable.columns.active, true))
7096
+ * .toSql('postgres');
7097
+ * console.log(sql); // SELECT "id", "name" FROM "users" WHERE "active" = true
6483
7098
  */
6484
7099
  toSql(dialect) {
6485
7100
  return this.compile(dialect).sql;
@@ -6487,6 +7102,9 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6487
7102
  /**
6488
7103
  * Gets the hydration plan for the query
6489
7104
  * @returns Hydration plan or undefined if none exists
7105
+ * @example
7106
+ * const plan = qb.include('posts').getHydrationPlan();
7107
+ * console.log(plan?.relations); // Information about included relations
6490
7108
  */
6491
7109
  getHydrationPlan() {
6492
7110
  return this.context.hydration.getPlan();
@@ -6494,6 +7112,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6494
7112
  /**
6495
7113
  * Gets the Abstract Syntax Tree (AST) representation of the query
6496
7114
  * @returns Query AST with hydration applied
7115
+ * @example
7116
+ * const ast = qb.select('id', 'name').getAST();
7117
+ * console.log(ast.columns); // Array of column nodes
7118
+ * console.log(ast.from); // From clause information
6497
7119
  */
6498
7120
  getAST() {
6499
7121
  return this.context.hydration.applyToAst(this.context.state.ast);
@@ -6597,23 +7219,44 @@ var resolveTableTarget = (target, tableMap) => {
6597
7219
  }
6598
7220
  return table;
6599
7221
  };
7222
+ var toSnakeCase = (value) => {
7223
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
7224
+ };
7225
+ var normalizeEntityName = (value) => {
7226
+ const stripped = value.replace(/Entity$/i, "");
7227
+ const normalized = toSnakeCase(stripped || value);
7228
+ return normalized || "unknown";
7229
+ };
7230
+ var getPivotKeyBaseFromTarget = (target) => {
7231
+ const resolved = unwrapTarget(target);
7232
+ if (isTableDef(resolved)) {
7233
+ return toSnakeCase(resolved.name || "unknown");
7234
+ }
7235
+ const ctor = resolved;
7236
+ return normalizeEntityName(ctor.name || "unknown");
7237
+ };
7238
+ var getPivotKeyBaseFromRoot = (meta) => {
7239
+ return normalizeEntityName(meta.target.name || meta.tableName || "unknown");
7240
+ };
6600
7241
  var buildRelationDefinitions = (meta, tableMap) => {
6601
7242
  const relations = {};
6602
7243
  for (const [name, relation] of Object.entries(meta.relations)) {
6603
7244
  switch (relation.kind) {
6604
7245
  case RelationKinds.HasOne: {
7246
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6605
7247
  relations[name] = hasOne(
6606
7248
  resolveTableTarget(relation.target, tableMap),
6607
- relation.foreignKey,
7249
+ foreignKey,
6608
7250
  relation.localKey,
6609
7251
  relation.cascade
6610
7252
  );
6611
7253
  break;
6612
7254
  }
6613
7255
  case RelationKinds.HasMany: {
7256
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6614
7257
  relations[name] = hasMany(
6615
7258
  resolveTableTarget(relation.target, tableMap),
6616
- relation.foreignKey,
7259
+ foreignKey,
6617
7260
  relation.localKey,
6618
7261
  relation.cascade
6619
7262
  );
@@ -6629,12 +7272,14 @@ var buildRelationDefinitions = (meta, tableMap) => {
6629
7272
  break;
6630
7273
  }
6631
7274
  case RelationKinds.BelongsToMany: {
7275
+ const pivotForeignKeyToRoot = relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
7276
+ const pivotForeignKeyToTarget = relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
6632
7277
  relations[name] = belongsToMany(
6633
7278
  resolveTableTarget(relation.target, tableMap),
6634
7279
  resolveTableTarget(relation.pivotTable, tableMap),
6635
7280
  {
6636
- pivotForeignKeyToRoot: relation.pivotForeignKeyToRoot,
6637
- pivotForeignKeyToTarget: relation.pivotForeignKeyToTarget,
7281
+ pivotForeignKeyToRoot,
7282
+ pivotForeignKeyToTarget,
6638
7283
  localKey: relation.localKey,
6639
7284
  targetKey: relation.targetKey,
6640
7285
  pivotPrimaryKey: relation.pivotPrimaryKey,
@@ -6715,6 +7360,8 @@ function esel(entity, ...props) {
6715
7360
 
6716
7361
  // src/query-builder/insert-query-state.ts
6717
7362
  var InsertQueryState = class _InsertQueryState {
7363
+ table;
7364
+ ast;
6718
7365
  /**
6719
7366
  * Creates a new InsertQueryState instance
6720
7367
  * @param table - The table definition for the INSERT query
@@ -6835,6 +7482,8 @@ var InsertQueryState = class _InsertQueryState {
6835
7482
 
6836
7483
  // src/query-builder/insert.ts
6837
7484
  var InsertQueryBuilder = class _InsertQueryBuilder {
7485
+ table;
7486
+ state;
6838
7487
  /**
6839
7488
  * Creates a new InsertQueryBuilder instance
6840
7489
  * @param table - The table definition for the INSERT query
@@ -6934,6 +7583,8 @@ var isUpdateValue = (value) => {
6934
7583
  }
6935
7584
  };
6936
7585
  var UpdateQueryState = class _UpdateQueryState {
7586
+ table;
7587
+ ast;
6937
7588
  /**
6938
7589
  * Creates a new UpdateQueryState instance
6939
7590
  * @param table - Table definition for the update
@@ -7044,6 +7695,8 @@ var UpdateQueryState = class _UpdateQueryState {
7044
7695
 
7045
7696
  // src/query-builder/update.ts
7046
7697
  var UpdateQueryBuilder = class _UpdateQueryBuilder {
7698
+ table;
7699
+ state;
7047
7700
  /**
7048
7701
  * Creates a new UpdateQueryBuilder instance
7049
7702
  * @param table - The table definition for the UPDATE query
@@ -7161,6 +7814,8 @@ var isTableSourceNode = (source) => typeof source.type === "string";
7161
7814
 
7162
7815
  // src/query-builder/delete-query-state.ts
7163
7816
  var DeleteQueryState = class _DeleteQueryState {
7817
+ table;
7818
+ ast;
7164
7819
  /**
7165
7820
  * Creates a new DeleteQueryState instance
7166
7821
  * @param table - The table definition for the DELETE query
@@ -7239,6 +7894,8 @@ var DeleteQueryState = class _DeleteQueryState {
7239
7894
 
7240
7895
  // src/query-builder/delete.ts
7241
7896
  var DeleteQueryBuilder = class _DeleteQueryBuilder {
7897
+ table;
7898
+ state;
7242
7899
  /**
7243
7900
  * Creates a new DeleteQueryBuilder instance
7244
7901
  * @param table - The table definition for the DELETE query
@@ -7345,6 +8002,39 @@ var DeleteQueryBuilder = class _DeleteQueryBuilder {
7345
8002
  };
7346
8003
  var isTableSourceNode2 = (source) => typeof source.type === "string";
7347
8004
 
8005
+ // src/query/target.ts
8006
+ var resolveEntityTarget = (ctor) => {
8007
+ const table = getTableDefFromEntity(ctor);
8008
+ if (!table) {
8009
+ throw new Error(`Entity '${ctor.name}' is not registered with decorators`);
8010
+ }
8011
+ return table;
8012
+ };
8013
+ var resolveTable = (target) => {
8014
+ if (isTableDef(target)) {
8015
+ return target;
8016
+ }
8017
+ return resolveEntityTarget(target);
8018
+ };
8019
+
8020
+ // src/query/index.ts
8021
+ var selectFrom = (target) => {
8022
+ const table = resolveTable(target);
8023
+ return new SelectQueryBuilder(table);
8024
+ };
8025
+ var insertInto = (target) => {
8026
+ const table = resolveTable(target);
8027
+ return new InsertQueryBuilder(table);
8028
+ };
8029
+ var update = (target) => {
8030
+ const table = resolveTable(target);
8031
+ return new UpdateQueryBuilder(table);
8032
+ };
8033
+ var deleteFrom = (target) => {
8034
+ const table = resolveTable(target);
8035
+ return new DeleteQueryBuilder(table);
8036
+ };
8037
+
7348
8038
  // src/core/ddl/sql-writing.ts
7349
8039
  var resolvePrimaryKey = (table) => {
7350
8040
  if (Array.isArray(table.primaryKey) && table.primaryKey.length > 0) {
@@ -7366,7 +8056,8 @@ var renderColumnDefinition = (table, col2, dialect, options = {}) => {
7366
8056
  if (col2.default !== void 0) {
7367
8057
  parts.push(`DEFAULT ${dialect.renderDefault(col2.default, col2)}`);
7368
8058
  }
7369
- if (options.includePrimary && col2.primary) {
8059
+ const autoIncIncludesPrimary = typeof autoInc === "string" && /\bPRIMARY\s+KEY\b/i.test(autoInc);
8060
+ if (options.includePrimary && col2.primary && !autoIncIncludesPrimary) {
7370
8061
  parts.push("PRIMARY KEY");
7371
8062
  }
7372
8063
  if (col2.check) {
@@ -7424,6 +8115,16 @@ var generateSchemaSql = (tables, dialect) => {
7424
8115
  });
7425
8116
  return statements;
7426
8117
  };
8118
+ var generateSchemaSqlFor = (dialect, ...tables) => generateSchemaSql(tables, dialect);
8119
+ var executeSchemaSql = async (executor, tables, dialect) => {
8120
+ const statements = generateSchemaSql(tables, dialect);
8121
+ for (const sql of statements) {
8122
+ await executor.executeSql(sql);
8123
+ }
8124
+ };
8125
+ var executeSchemaSqlFor = async (executor, dialect, ...tables) => {
8126
+ await executeSchemaSql(executor, tables, dialect);
8127
+ };
7427
8128
  var orderTablesByDependencies = (tables) => {
7428
8129
  const map = /* @__PURE__ */ new Map();
7429
8130
  tables.forEach((t) => map.set(t.name, t));
@@ -9330,6 +10031,7 @@ var arrayAppend = (array, value) => fn7("ARRAY_APPEND", [array, value]);
9330
10031
 
9331
10032
  // src/orm/als.ts
9332
10033
  var AsyncLocalStorage = class {
10034
+ store;
9333
10035
  /**
9334
10036
  * Executes a callback function within a context containing the specified store value.
9335
10037
  * The store value is only available during the callback's execution and is automatically
@@ -9826,9 +10528,7 @@ var TypeScriptGenerator = class {
9826
10528
 
9827
10529
  // src/orm/identity-map.ts
9828
10530
  var IdentityMap = class {
9829
- constructor() {
9830
- this.buckets = /* @__PURE__ */ new Map();
9831
- }
10531
+ buckets = /* @__PURE__ */ new Map();
9832
10532
  get bucketsMap() {
9833
10533
  return this.buckets;
9834
10534
  }
@@ -9898,8 +10598,8 @@ var UnitOfWork = class {
9898
10598
  this.executor = executor;
9899
10599
  this.identityMap = identityMap;
9900
10600
  this.hookContext = hookContext;
9901
- this.trackedEntities = /* @__PURE__ */ new Map();
9902
10601
  }
10602
+ trackedEntities = /* @__PURE__ */ new Map();
9903
10603
  /**
9904
10604
  * Gets the identity buckets map.
9905
10605
  */
@@ -10229,12 +10929,12 @@ var UnitOfWork = class {
10229
10929
 
10230
10930
  // src/orm/domain-event-bus.ts
10231
10931
  var DomainEventBus = class {
10932
+ handlers = /* @__PURE__ */ new Map();
10232
10933
  /**
10233
10934
  * Creates a new DomainEventBus instance.
10234
10935
  * @param initialHandlers - Optional initial event handlers
10235
10936
  */
10236
10937
  constructor(initialHandlers) {
10237
- this.handlers = /* @__PURE__ */ new Map();
10238
10938
  if (initialHandlers) {
10239
10939
  for (const key in initialHandlers) {
10240
10940
  const type = key;
@@ -10303,8 +11003,8 @@ var RelationChangeProcessor = class {
10303
11003
  this.unitOfWork = unitOfWork;
10304
11004
  this.dialect = dialect;
10305
11005
  this.executor = executor;
10306
- this.relationChanges = [];
10307
11006
  }
11007
+ relationChanges = [];
10308
11008
  /**
10309
11009
  * Registers a relation change for processing.
10310
11010
  * @param entry - The relation change entry
@@ -10738,25 +11438,24 @@ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
10738
11438
 
10739
11439
  // src/orm/orm-session.ts
10740
11440
  var OrmSession = class {
11441
+ /** The ORM instance */
11442
+ orm;
11443
+ /** The database executor */
11444
+ executor;
11445
+ /** The identity map for tracking entity instances */
11446
+ identityMap;
11447
+ /** The unit of work for tracking entity changes */
11448
+ unitOfWork;
11449
+ /** The domain event bus */
11450
+ domainEvents;
11451
+ /** The relation change processor */
11452
+ relationChanges;
11453
+ interceptors;
10741
11454
  /**
10742
11455
  * Creates a new OrmSession instance.
10743
11456
  * @param opts - Session options
10744
11457
  */
10745
11458
  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
11459
  this.orm = opts.orm;
10761
11460
  this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
10762
11461
  this.interceptors = [...opts.interceptors ?? []];
@@ -10845,6 +11544,20 @@ var OrmSession = class {
10845
11544
  markRemoved(entity) {
10846
11545
  this.unitOfWork.markRemoved(entity);
10847
11546
  }
11547
+ /**
11548
+ * Registers a relation change.
11549
+ * @param root - The root entity
11550
+ * @param relationKey - The relation key
11551
+ * @param rootTable - The root table definition
11552
+ * @param relationName - The relation name
11553
+ * @param relation - The relation definition
11554
+ * @param change - The relation change
11555
+ */
11556
+ registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
11557
+ this.relationChanges.registerChange(
11558
+ buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
11559
+ );
11560
+ };
10848
11561
  /**
10849
11562
  * Gets all tracked entities for a specific table.
10850
11563
  * @param table - The table definition
@@ -11050,9 +11763,7 @@ var buildRelationChangeEntry = (root, relationKey, rootTable, relationName, rela
11050
11763
 
11051
11764
  // src/orm/interceptor-pipeline.ts
11052
11765
  var InterceptorPipeline = class {
11053
- constructor() {
11054
- this.interceptors = [];
11055
- }
11766
+ interceptors = [];
11056
11767
  use(interceptor) {
11057
11768
  this.interceptors.push(interceptor);
11058
11769
  }
@@ -11071,6 +11782,13 @@ var InterceptorPipeline = class {
11071
11782
 
11072
11783
  // src/orm/orm.ts
11073
11784
  var Orm = class {
11785
+ /** The database dialect */
11786
+ dialect;
11787
+ /** The interceptors pipeline */
11788
+ interceptors;
11789
+ /** The naming strategy */
11790
+ namingStrategy;
11791
+ executorFactory;
11074
11792
  /**
11075
11793
  * Creates a new ORM instance.
11076
11794
  * @param opts - ORM options
@@ -11127,17 +11845,13 @@ var jsonify = (value) => {
11127
11845
 
11128
11846
  // src/decorators/decorator-metadata.ts
11129
11847
  var METADATA_KEY = "metal-orm:decorators";
11130
- var isStandardDecoratorContext = (value) => {
11131
- return typeof value === "object" && value !== null && "kind" in value;
11132
- };
11133
11848
  var getOrCreateMetadataBag = (context) => {
11134
11849
  const metadata = context.metadata || (context.metadata = {});
11135
- const existing = metadata[METADATA_KEY];
11136
- if (existing) {
11137
- return existing;
11850
+ let bag = metadata[METADATA_KEY];
11851
+ if (!bag) {
11852
+ bag = { columns: [], relations: [] };
11853
+ metadata[METADATA_KEY] = bag;
11138
11854
  }
11139
- const bag = { columns: [], relations: [] };
11140
- metadata[METADATA_KEY] = bag;
11141
11855
  return bag;
11142
11856
  };
11143
11857
  var readMetadataBag = (context) => {
@@ -11152,57 +11866,50 @@ var readMetadataBagFromConstructor = (ctor) => {
11152
11866
  var getDecoratorMetadata = (ctor) => readMetadataBagFromConstructor(ctor);
11153
11867
 
11154
11868
  // src/decorators/entity.ts
11155
- var toSnakeCase = (value) => {
11869
+ var toSnakeCase2 = (value) => {
11156
11870
  return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
11157
11871
  };
11158
11872
  var deriveTableNameFromConstructor = (ctor) => {
11159
11873
  const fallback = "unknown";
11160
11874
  const rawName = ctor.name || fallback;
11161
11875
  const strippedName = rawName.replace(/Entity$/i, "");
11162
- const normalized = toSnakeCase(strippedName || rawName);
11876
+ const normalized = toSnakeCase2(strippedName || rawName);
11163
11877
  if (!normalized) {
11164
11878
  return fallback;
11165
11879
  }
11166
11880
  return normalized.endsWith("s") ? normalized : `${normalized}s`;
11167
11881
  };
11168
11882
  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) => {
11883
+ return function(value, context) {
11175
11884
  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 });
11885
+ const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
11886
+ setEntityTableName(ctor, tableName, options.hooks);
11887
+ const bag = readMetadataBag(context);
11888
+ if (bag) {
11889
+ const meta = ensureEntityMetadata(ctor);
11890
+ for (const entry of bag.columns) {
11891
+ if (meta.columns[entry.propertyName]) {
11892
+ throw new Error(
11893
+ `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11894
+ );
11188
11895
  }
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);
11896
+ addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
11897
+ }
11898
+ for (const entry of bag.relations) {
11899
+ if (meta.relations[entry.propertyName]) {
11900
+ throw new Error(
11901
+ `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11902
+ );
11200
11903
  }
11904
+ const relationCopy = entry.relation.kind === RelationKinds.BelongsToMany ? {
11905
+ ...entry.relation,
11906
+ defaultPivotColumns: entry.relation.defaultPivotColumns ? [...entry.relation.defaultPivotColumns] : void 0
11907
+ } : { ...entry.relation };
11908
+ addRelationMetadata(ctor, entry.propertyName, relationCopy);
11201
11909
  }
11202
11910
  }
11203
11911
  return ctor;
11204
11912
  };
11205
- return decoratorWithContext;
11206
11913
  }
11207
11914
 
11208
11915
  // src/decorators/column-decorator.ts
@@ -11235,26 +11942,13 @@ var normalizePropertyName = (name) => {
11235
11942
  }
11236
11943
  return name;
11237
11944
  };
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
11945
  var registerColumnFromContext = (context, column) => {
11255
11946
  if (!context.name) {
11256
11947
  throw new Error("Column decorator requires a property name");
11257
11948
  }
11949
+ if (context.private) {
11950
+ throw new Error("Column decorator does not support private fields");
11951
+ }
11258
11952
  const propertyName = normalizePropertyName(context.name);
11259
11953
  const bag = getOrCreateMetadataBag(context);
11260
11954
  if (!bag.columns.some((entry) => entry.propertyName === propertyName)) {
@@ -11263,19 +11957,9 @@ var registerColumnFromContext = (context, column) => {
11263
11957
  };
11264
11958
  function Column(definition) {
11265
11959
  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 });
11960
+ return function(_value, context) {
11961
+ registerColumnFromContext(context, normalized);
11277
11962
  };
11278
- return decorator;
11279
11963
  }
11280
11964
  function PrimaryKey(definition) {
11281
11965
  const normalized = normalizeColumnInput(definition);
@@ -11290,41 +11974,21 @@ var normalizePropertyName2 = (name) => {
11290
11974
  }
11291
11975
  return name;
11292
11976
  };
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
11977
  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;
11978
+ return function(_value, context) {
11979
+ if (!context.name) {
11980
+ throw new Error("Relation decorator requires a property name");
11319
11981
  }
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");
11982
+ if (context.private) {
11983
+ throw new Error("Relation decorator does not support private fields");
11984
+ }
11985
+ const propertyName = normalizePropertyName2(context.name);
11986
+ const bag = getOrCreateMetadataBag(context);
11987
+ const relationMetadata = metadataFactory(propertyName);
11988
+ if (!bag.relations.some((entry) => entry.propertyName === propertyName)) {
11989
+ bag.relations.push({ propertyName, relation: relationMetadata });
11324
11990
  }
11325
- registerRelation(ctor, propertyName, metadataFactory(propertyName));
11326
11991
  };
11327
- return decorator;
11328
11992
  };
11329
11993
  function HasMany(options) {
11330
11994
  return createFieldDecorator((propertyName) => ({
@@ -11351,7 +12015,7 @@ function BelongsTo(options) {
11351
12015
  kind: RelationKinds.BelongsTo,
11352
12016
  propertyKey: propertyName,
11353
12017
  target: options.target,
11354
- foreignKey: options.foreignKey,
12018
+ foreignKey: options.foreignKey ?? `${propertyName}_id`,
11355
12019
  localKey: options.localKey,
11356
12020
  cascade: options.cascade
11357
12021
  }));
@@ -11427,13 +12091,15 @@ var deferred = () => {
11427
12091
  return { promise, resolve, reject };
11428
12092
  };
11429
12093
  var Pool = class {
12094
+ adapter;
12095
+ options;
12096
+ destroyed = false;
12097
+ creating = 0;
12098
+ leased = 0;
12099
+ idle = [];
12100
+ waiters = [];
12101
+ reapTimer = null;
11430
12102
  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
12103
  if (!Number.isFinite(options.max) || options.max <= 0) {
11438
12104
  throw new Error("Pool options.max must be a positive number");
11439
12105
  }
@@ -11986,6 +12652,7 @@ export {
11986
12652
  dayOfWeek,
11987
12653
  defineTable,
11988
12654
  degrees,
12655
+ deleteFrom,
11989
12656
  denseRank,
11990
12657
  diffSchema,
11991
12658
  div,
@@ -11995,6 +12662,8 @@ export {
11995
12662
  esel,
11996
12663
  executeHydrated,
11997
12664
  executeHydratedWithContexts,
12665
+ executeSchemaSql,
12666
+ executeSchemaSqlFor,
11998
12667
  exists,
11999
12668
  exp,
12000
12669
  extract,
@@ -12003,6 +12672,7 @@ export {
12003
12672
  fromUnixTime,
12004
12673
  generateCreateTableSql,
12005
12674
  generateSchemaSql,
12675
+ generateSchemaSqlFor,
12006
12676
  getColumn,
12007
12677
  getDecoratorMetadata,
12008
12678
  getSchemaIntrospector,
@@ -12019,6 +12689,7 @@ export {
12019
12689
  inList,
12020
12690
  inSubquery,
12021
12691
  initcap,
12692
+ insertInto,
12022
12693
  instr,
12023
12694
  introspectSchema,
12024
12695
  isCaseExpressionNode,
@@ -12107,7 +12778,9 @@ export {
12107
12778
  rtrim,
12108
12779
  second,
12109
12780
  sel,
12781
+ selectFrom,
12110
12782
  selectFromEntity,
12783
+ setRelations,
12111
12784
  sha1,
12112
12785
  sha2,
12113
12786
  shiftLeft,
@@ -12129,6 +12802,7 @@ export {
12129
12802
  trunc,
12130
12803
  truncate,
12131
12804
  unixTimestamp,
12805
+ update,
12132
12806
  upper,
12133
12807
  utcNow,
12134
12808
  valueToOperand,