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.cjs CHANGED
@@ -134,6 +134,7 @@ __export(index_exports, {
134
134
  dayOfWeek: () => dayOfWeek,
135
135
  defineTable: () => defineTable,
136
136
  degrees: () => degrees,
137
+ deleteFrom: () => deleteFrom,
137
138
  denseRank: () => denseRank,
138
139
  diffSchema: () => diffSchema,
139
140
  div: () => div,
@@ -143,6 +144,8 @@ __export(index_exports, {
143
144
  esel: () => esel,
144
145
  executeHydrated: () => executeHydrated,
145
146
  executeHydratedWithContexts: () => executeHydratedWithContexts,
147
+ executeSchemaSql: () => executeSchemaSql,
148
+ executeSchemaSqlFor: () => executeSchemaSqlFor,
146
149
  exists: () => exists,
147
150
  exp: () => exp,
148
151
  extract: () => extract,
@@ -151,6 +154,7 @@ __export(index_exports, {
151
154
  fromUnixTime: () => fromUnixTime,
152
155
  generateCreateTableSql: () => generateCreateTableSql,
153
156
  generateSchemaSql: () => generateSchemaSql,
157
+ generateSchemaSqlFor: () => generateSchemaSqlFor,
154
158
  getColumn: () => getColumn,
155
159
  getDecoratorMetadata: () => getDecoratorMetadata,
156
160
  getSchemaIntrospector: () => getSchemaIntrospector,
@@ -167,6 +171,7 @@ __export(index_exports, {
167
171
  inList: () => inList,
168
172
  inSubquery: () => inSubquery,
169
173
  initcap: () => initcap,
174
+ insertInto: () => insertInto,
170
175
  instr: () => instr,
171
176
  introspectSchema: () => introspectSchema,
172
177
  isCaseExpressionNode: () => isCaseExpressionNode,
@@ -255,7 +260,9 @@ __export(index_exports, {
255
260
  rtrim: () => rtrim,
256
261
  second: () => second,
257
262
  sel: () => sel,
263
+ selectFrom: () => selectFrom,
258
264
  selectFromEntity: () => selectFromEntity,
265
+ setRelations: () => setRelations,
259
266
  sha1: () => sha1,
260
267
  sha2: () => sha2,
261
268
  shiftLeft: () => shiftLeft,
@@ -277,6 +284,7 @@ __export(index_exports, {
277
284
  trunc: () => trunc,
278
285
  truncate: () => truncate,
279
286
  unixTimestamp: () => unixTimestamp,
287
+ update: () => update,
280
288
  upper: () => upper,
281
289
  utcNow: () => utcNow,
282
290
  valueToOperand: () => valueToOperand,
@@ -311,6 +319,9 @@ var defineTable = (name, columns, relations = {}, hooks, options = {}) => {
311
319
  collation: options.collation
312
320
  };
313
321
  };
322
+ function setRelations(table, relations) {
323
+ table.relations = relations;
324
+ }
314
325
  var TABLE_REF_CACHE = /* @__PURE__ */ new WeakMap();
315
326
  var withColumnProps = (table) => {
316
327
  const cached = TABLE_REF_CACHE.get(table);
@@ -954,6 +965,7 @@ var variance = buildAggregate("VARIANCE");
954
965
 
955
966
  // src/core/ast/expression-visitor.ts
956
967
  var DispatcherRegistry = class _DispatcherRegistry {
968
+ dispatchers;
957
969
  constructor(dispatchers = /* @__PURE__ */ new Map()) {
958
970
  this.dispatchers = dispatchers;
959
971
  }
@@ -1087,61 +1099,9 @@ var toTableRef = (table) => ({
1087
1099
  alias: hasAlias(table) ? table.alias : void 0
1088
1100
  });
1089
1101
 
1090
- // src/core/ast/builders.ts
1091
- var isColumnNode = (col2) => "type" in col2 && col2.type === "Column";
1092
- var resolveTableName = (def, table) => {
1093
- if (!def.table) {
1094
- return table.alias || table.name;
1095
- }
1096
- if (table.alias && def.table === table.name) {
1097
- return table.alias;
1098
- }
1099
- return def.table;
1100
- };
1101
- var buildColumnNode = (table, column) => {
1102
- if (isColumnNode(column)) {
1103
- return column;
1104
- }
1105
- const def = column;
1106
- const baseTable = resolveTableName(def, table);
1107
- return {
1108
- type: "Column",
1109
- table: baseTable,
1110
- name: def.name
1111
- };
1112
- };
1113
- var buildColumnNodes = (table, names) => names.map((name) => ({
1114
- type: "Column",
1115
- table: table.alias || table.name,
1116
- name
1117
- }));
1118
- var createTableNode = (table) => ({
1119
- type: "Table",
1120
- name: table.name,
1121
- schema: table.schema
1122
- });
1123
- var fnTable = (name, args = [], alias, opts) => ({
1124
- type: "FunctionTable",
1125
- name,
1126
- args,
1127
- alias,
1128
- lateral: opts?.lateral,
1129
- withOrdinality: opts?.withOrdinality,
1130
- columnAliases: opts?.columnAliases,
1131
- schema: opts?.schema
1132
- });
1133
- var derivedTable = (query, alias, columnAliases) => ({
1134
- type: "DerivedTable",
1135
- query,
1136
- alias,
1137
- columnAliases
1138
- });
1139
-
1140
1102
  // src/core/functions/function-registry.ts
1141
1103
  var FunctionRegistry = class {
1142
- constructor() {
1143
- this.renderers = /* @__PURE__ */ new Map();
1144
- }
1104
+ renderers = /* @__PURE__ */ new Map();
1145
1105
  /**
1146
1106
  * Registers or overrides a renderer for the given function name.
1147
1107
  */
@@ -1436,6 +1396,7 @@ function renderStandardGroupConcat(ctx) {
1436
1396
 
1437
1397
  // src/core/functions/standard-strategy.ts
1438
1398
  var StandardFunctionStrategy = class {
1399
+ registry;
1439
1400
  /**
1440
1401
  * Creates a new StandardFunctionStrategy and registers standard functions.
1441
1402
  */
@@ -1501,17 +1462,13 @@ var StandardFunctionStrategy = class {
1501
1462
  getGroupConcatSeparatorOperand(ctx) {
1502
1463
  return getGroupConcatSeparatorOperand(ctx);
1503
1464
  }
1504
- static {
1505
- /** Default separator for GROUP_CONCAT, a comma. */
1506
- this.DEFAULT_GROUP_CONCAT_SEPARATOR = DEFAULT_GROUP_CONCAT_SEPARATOR;
1507
- }
1465
+ /** Default separator for GROUP_CONCAT, a comma. */
1466
+ static DEFAULT_GROUP_CONCAT_SEPARATOR = DEFAULT_GROUP_CONCAT_SEPARATOR;
1508
1467
  };
1509
1468
 
1510
1469
  // src/core/functions/standard-table-strategy.ts
1511
1470
  var StandardTableFunctionStrategy = class {
1512
- constructor() {
1513
- this.renderers = /* @__PURE__ */ new Map();
1514
- }
1471
+ renderers = /* @__PURE__ */ new Map();
1515
1472
  add(key, renderer) {
1516
1473
  this.renderers.set(key, renderer);
1517
1474
  }
@@ -1690,6 +1647,10 @@ var Dialect = class _Dialect {
1690
1647
  const combinedCtes = [...normalized.ctes ?? [], ...hoistedCtes];
1691
1648
  return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
1692
1649
  }
1650
+ expressionCompilers;
1651
+ operandCompilers;
1652
+ functionStrategy;
1653
+ tableFunctionStrategy;
1693
1654
  constructor(functionStrategy, tableFunctionStrategy) {
1694
1655
  this.expressionCompilers = /* @__PURE__ */ new Map();
1695
1656
  this.operandCompilers = /* @__PURE__ */ new Map();
@@ -1705,10 +1666,7 @@ var Dialect = class _Dialect {
1705
1666
  */
1706
1667
  static create(functionStrategy, tableFunctionStrategy) {
1707
1668
  class TestDialect extends _Dialect {
1708
- constructor() {
1709
- super(...arguments);
1710
- this.dialect = "sqlite";
1711
- }
1669
+ dialect = "sqlite";
1712
1670
  quoteIdentifier(id) {
1713
1671
  return `"${id}"`;
1714
1672
  }
@@ -2148,11 +2106,8 @@ var OrderByCompiler = class {
2148
2106
 
2149
2107
  // src/core/dialect/base/sql-dialect.ts
2150
2108
  var SqlDialectBase = class extends Dialect {
2151
- constructor() {
2152
- super(...arguments);
2153
- this.paginationStrategy = new StandardLimitOffsetPagination();
2154
- this.returningStrategy = new NoReturningStrategy();
2155
- }
2109
+ paginationStrategy = new StandardLimitOffsetPagination();
2110
+ returningStrategy = new NoReturningStrategy();
2156
2111
  compileSelectAst(ast, ctx) {
2157
2112
  const hasSetOps = !!(ast.setOps && ast.setOps.length);
2158
2113
  const ctes = CteCompiler.compileCtes(
@@ -2540,12 +2495,12 @@ var PostgresTableFunctionStrategy = class extends StandardTableFunctionStrategy
2540
2495
 
2541
2496
  // src/core/dialect/postgres/index.ts
2542
2497
  var PostgresDialect = class extends SqlDialectBase {
2498
+ dialect = "postgres";
2543
2499
  /**
2544
2500
  * Creates a new PostgresDialect instance
2545
2501
  */
2546
2502
  constructor() {
2547
2503
  super(new PostgresFunctionStrategy(), new PostgresTableFunctionStrategy());
2548
- this.dialect = "postgres";
2549
2504
  this.registerExpressionCompiler("BitwiseExpression", (node, ctx) => {
2550
2505
  const left2 = this.compileOperand(node.left, ctx);
2551
2506
  const right2 = this.compileOperand(node.right, ctx);
@@ -2679,12 +2634,12 @@ var MysqlFunctionStrategy = class extends StandardFunctionStrategy {
2679
2634
 
2680
2635
  // src/core/dialect/mysql/index.ts
2681
2636
  var MySqlDialect = class extends SqlDialectBase {
2637
+ dialect = "mysql";
2682
2638
  /**
2683
2639
  * Creates a new MySqlDialect instance
2684
2640
  */
2685
2641
  constructor() {
2686
2642
  super(new MysqlFunctionStrategy());
2687
- this.dialect = "mysql";
2688
2643
  }
2689
2644
  /**
2690
2645
  * Quotes an identifier using MySQL backtick syntax
@@ -2835,12 +2790,12 @@ var SqliteFunctionStrategy = class extends StandardFunctionStrategy {
2835
2790
 
2836
2791
  // src/core/dialect/sqlite/index.ts
2837
2792
  var SqliteDialect = class extends SqlDialectBase {
2793
+ dialect = "sqlite";
2838
2794
  /**
2839
2795
  * Creates a new SqliteDialect instance
2840
2796
  */
2841
2797
  constructor() {
2842
2798
  super(new SqliteFunctionStrategy());
2843
- this.dialect = "sqlite";
2844
2799
  this.registerExpressionCompiler("BitwiseExpression", (node, ctx) => {
2845
2800
  const left2 = this.compileOperand(node.left, ctx);
2846
2801
  const right2 = this.compileOperand(node.right, ctx);
@@ -3013,12 +2968,12 @@ var MssqlFunctionStrategy = class extends StandardFunctionStrategy {
3013
2968
 
3014
2969
  // src/core/dialect/mssql/index.ts
3015
2970
  var SqlServerDialect = class extends SqlDialectBase {
2971
+ dialect = "mssql";
3016
2972
  /**
3017
2973
  * Creates a new SqlServerDialect instance
3018
2974
  */
3019
2975
  constructor() {
3020
2976
  super(new MssqlFunctionStrategy());
3021
- this.dialect = "mssql";
3022
2977
  }
3023
2978
  /**
3024
2979
  * Quotes an identifier using SQL Server bracket syntax
@@ -3145,12 +3100,8 @@ var SqlServerDialect = class extends SqlDialectBase {
3145
3100
 
3146
3101
  // src/core/dialect/dialect-factory.ts
3147
3102
  var DialectFactory = class {
3148
- static {
3149
- this.registry = /* @__PURE__ */ new Map();
3150
- }
3151
- static {
3152
- this.defaultsInitialized = false;
3153
- }
3103
+ static registry = /* @__PURE__ */ new Map();
3104
+ static defaultsInitialized = false;
3154
3105
  static ensureDefaults() {
3155
3106
  if (this.defaultsInitialized) return;
3156
3107
  this.defaultsInitialized = true;
@@ -3209,8 +3160,66 @@ var resolveDialectInput = (dialect) => {
3209
3160
  return dialect;
3210
3161
  };
3211
3162
 
3163
+ // src/core/ast/builders.ts
3164
+ var isColumnNode = (col2) => "type" in col2 && col2.type === "Column";
3165
+ var resolveTableName = (def, table) => {
3166
+ if (!def.table) {
3167
+ return table.alias || table.name;
3168
+ }
3169
+ if (table.alias && def.table === table.name) {
3170
+ return table.alias;
3171
+ }
3172
+ return def.table;
3173
+ };
3174
+ var buildColumnNode = (table, column) => {
3175
+ if (isColumnNode(column)) {
3176
+ return column;
3177
+ }
3178
+ const def = column;
3179
+ const baseTable = resolveTableName(def, table);
3180
+ return {
3181
+ type: "Column",
3182
+ table: baseTable,
3183
+ name: def.name
3184
+ };
3185
+ };
3186
+ var buildColumnNodes = (table, names) => names.map((name) => ({
3187
+ type: "Column",
3188
+ table: table.alias || table.name,
3189
+ name
3190
+ }));
3191
+ var createTableNode = (table) => ({
3192
+ type: "Table",
3193
+ name: table.name,
3194
+ schema: table.schema
3195
+ });
3196
+ var fnTable = (name, args = [], alias, opts) => ({
3197
+ type: "FunctionTable",
3198
+ name,
3199
+ args,
3200
+ alias,
3201
+ lateral: opts?.lateral,
3202
+ withOrdinality: opts?.withOrdinality,
3203
+ columnAliases: opts?.columnAliases,
3204
+ schema: opts?.schema
3205
+ });
3206
+ var derivedTable = (query, alias, columnAliases) => ({
3207
+ type: "DerivedTable",
3208
+ query,
3209
+ alias,
3210
+ columnAliases
3211
+ });
3212
+
3212
3213
  // src/query-builder/select-query-state.ts
3213
3214
  var SelectQueryState = class _SelectQueryState {
3215
+ /**
3216
+ * Table definition for the query
3217
+ */
3218
+ table;
3219
+ /**
3220
+ * Abstract Syntax Tree (AST) representation of the query
3221
+ */
3222
+ ast;
3214
3223
  /**
3215
3224
  * Creates a new SelectQueryState instance
3216
3225
  * @param table - Table definition
@@ -4180,199 +4189,150 @@ var buildRelationCorrelation = (root, relation, rootAlias, targetTableName) => {
4180
4189
  // src/core/ast/join-metadata.ts
4181
4190
  var getJoinRelationName = (join) => join.meta?.relationName;
4182
4191
 
4183
- // src/query-builder/relation-service.ts
4184
- var hasRelationForeignKey = (relation) => relation.type !== RelationKinds.BelongsToMany;
4185
- var RelationService = class {
4186
- /**
4187
- * Creates a new RelationService instance
4188
- * @param table - Table definition
4189
- * @param state - Current query state
4190
- * @param hydration - Hydration manager
4191
- */
4192
- constructor(table, state, hydration, createQueryAstService) {
4193
- this.table = table;
4194
- this.state = state;
4195
- this.hydration = hydration;
4196
- this.createQueryAstService = createQueryAstService;
4197
- this.projectionHelper = new RelationProjectionHelper(
4198
- table,
4199
- (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
4200
- );
4201
- }
4202
- /**
4203
- * Joins a relation to the query
4204
- * @param relationName - Name of the relation to join
4205
- * @param joinKind - Type of join to use
4206
- * @param extraCondition - Additional join condition
4207
- * @returns Relation result with updated state and hydration
4208
- */
4209
- joinRelation(relationName, joinKind, extraCondition, tableSource) {
4210
- const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition, tableSource);
4211
- return { state: nextState, hydration: this.hydration };
4192
+ // src/query-builder/relation-filter-utils.ts
4193
+ var splitFilterExpressions = (filter, allowedTables) => {
4194
+ const terms = flattenAnd(filter);
4195
+ const selfFilters = [];
4196
+ const crossFilters = [];
4197
+ for (const term of terms) {
4198
+ if (isExpressionSelfContained(term, allowedTables)) {
4199
+ selfFilters.push(term);
4200
+ } else {
4201
+ crossFilters.push(term);
4202
+ }
4212
4203
  }
4213
- /**
4214
- * Matches records based on a relation with an optional predicate
4215
- * @param relationName - Name of the relation to match
4216
- * @param predicate - Optional predicate expression
4217
- * @returns Relation result with updated state and hydration
4218
- */
4219
- match(relationName, predicate) {
4220
- const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
4221
- const pk = findPrimaryKey(this.table);
4222
- const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
4223
- const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
4224
- const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
4225
- return { state: nextState, hydration: joined.hydration };
4204
+ return { selfFilters, crossFilters };
4205
+ };
4206
+ var flattenAnd = (node) => {
4207
+ if (!node) return [];
4208
+ if (node.type === "LogicalExpression" && node.operator === "AND") {
4209
+ return node.operands.flatMap((operand) => flattenAnd(operand));
4226
4210
  }
4227
- /**
4228
- * Includes a relation in the query result
4229
- * @param relationName - Name of the relation to include
4230
- * @param options - Options for relation inclusion
4231
- * @returns Relation result with updated state and hydration
4232
- */
4233
- include(relationName, options) {
4234
- let state = this.state;
4235
- let hydration = this.hydration;
4236
- const relation = this.getRelation(relationName);
4237
- const aliasPrefix = options?.aliasPrefix ?? relationName;
4238
- const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
4239
- const { selfFilters, crossFilters } = this.splitFilterExpressions(
4240
- options?.filter,
4241
- /* @__PURE__ */ new Set([relation.target.name])
4242
- );
4243
- const canUseCte = !alreadyJoined && selfFilters.length > 0;
4244
- const joinFilters = [...crossFilters];
4245
- if (!canUseCte) {
4246
- joinFilters.push(...selfFilters);
4247
- }
4248
- const joinCondition = this.combineWithAnd(joinFilters);
4249
- let tableSourceOverride;
4250
- if (canUseCte) {
4251
- const cteInfo = this.createFilteredRelationCte(state, relationName, relation, selfFilters);
4252
- state = cteInfo.state;
4253
- tableSourceOverride = cteInfo.table;
4254
- }
4255
- if (!alreadyJoined) {
4256
- state = this.withJoin(
4257
- state,
4258
- relationName,
4259
- options?.joinKind ?? JOIN_KINDS.LEFT,
4260
- joinCondition,
4261
- tableSourceOverride
4262
- );
4211
+ return [node];
4212
+ };
4213
+ var isExpressionSelfContained = (expr, allowedTables) => {
4214
+ const collector = collectReferencedTables(expr);
4215
+ if (collector.hasSubquery) return false;
4216
+ if (collector.tables.size === 0) return true;
4217
+ for (const table of collector.tables) {
4218
+ if (!allowedTables.has(table)) {
4219
+ return false;
4263
4220
  }
4264
- const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
4265
- state = projectionResult.state;
4266
- hydration = projectionResult.hydration;
4267
- if (hasRelationForeignKey(relation)) {
4268
- const fkColumn = this.table.columns[relation.foreignKey];
4269
- if (fkColumn) {
4270
- const hasForeignKeySelected = state.ast.columns.some((col2) => {
4271
- if (col2.type !== "Column") return false;
4272
- const node = col2;
4273
- const alias = node.alias ?? node.name;
4274
- return alias === relation.foreignKey;
4275
- });
4276
- if (!hasForeignKeySelected) {
4277
- const fkSelectionResult = this.selectColumns(state, hydration, {
4278
- [relation.foreignKey]: fkColumn
4279
- });
4280
- state = fkSelectionResult.state;
4281
- hydration = fkSelectionResult.hydration;
4282
- }
4221
+ }
4222
+ return true;
4223
+ };
4224
+ var collectReferencedTables = (expr) => {
4225
+ const collector = {
4226
+ tables: /* @__PURE__ */ new Set(),
4227
+ hasSubquery: false
4228
+ };
4229
+ collectFromExpression(expr, collector);
4230
+ return collector;
4231
+ };
4232
+ var collectFromExpression = (expr, collector) => {
4233
+ switch (expr.type) {
4234
+ case "BinaryExpression":
4235
+ collectFromOperand(expr.left, collector);
4236
+ collectFromOperand(expr.right, collector);
4237
+ break;
4238
+ case "LogicalExpression":
4239
+ expr.operands.forEach((operand) => collectFromExpression(operand, collector));
4240
+ break;
4241
+ case "NullExpression":
4242
+ collectFromOperand(expr.left, collector);
4243
+ break;
4244
+ case "InExpression":
4245
+ collectFromOperand(expr.left, collector);
4246
+ if (Array.isArray(expr.right)) {
4247
+ expr.right.forEach((value) => collectFromOperand(value, collector));
4248
+ } else {
4249
+ collector.hasSubquery = true;
4283
4250
  }
4284
- }
4285
- const requestedColumns = options?.columns?.length ? [...options.columns] : Object.keys(relation.target.columns);
4286
- const targetPrimaryKey = findPrimaryKey(relation.target);
4287
- if (!requestedColumns.includes(targetPrimaryKey)) {
4288
- requestedColumns.push(targetPrimaryKey);
4289
- }
4290
- const targetColumns = requestedColumns;
4291
- const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
4292
- return keys.reduce((acc, key) => {
4293
- const def = columns[key];
4294
- if (!def) {
4295
- throw new Error(missingMsg(key));
4296
- }
4297
- acc[makeRelationAlias(prefix, key)] = def;
4298
- return acc;
4299
- }, {});
4300
- };
4301
- const targetSelection = buildTypedSelection(
4302
- relation.target.columns,
4303
- aliasPrefix,
4304
- targetColumns,
4305
- (key) => `Column '${key}' not found on relation '${relationName}'`
4306
- );
4307
- if (relation.type !== RelationKinds.BelongsToMany) {
4308
- const relationSelectionResult2 = this.selectColumns(state, hydration, targetSelection);
4309
- state = relationSelectionResult2.state;
4310
- hydration = relationSelectionResult2.hydration;
4311
- hydration = hydration.onRelationIncluded(
4312
- state,
4313
- relation,
4314
- relationName,
4315
- aliasPrefix,
4316
- targetColumns
4317
- );
4318
- return { state, hydration };
4319
- }
4320
- const many = relation;
4321
- const pivotAliasPrefix = options?.pivot?.aliasPrefix ?? `${aliasPrefix}_pivot`;
4322
- const pivotPk = many.pivotPrimaryKey || findPrimaryKey(many.pivotTable);
4323
- const pivotColumns = options?.pivot?.columns ?? many.defaultPivotColumns ?? buildDefaultPivotColumns(many, pivotPk);
4324
- const pivotSelection = buildTypedSelection(
4325
- many.pivotTable.columns,
4326
- pivotAliasPrefix,
4327
- pivotColumns,
4328
- (key) => `Column '${key}' not found on pivot table '${many.pivotTable.name}'`
4329
- );
4330
- const combinedSelection = {
4331
- ...targetSelection,
4332
- ...pivotSelection
4333
- };
4334
- const relationSelectionResult = this.selectColumns(state, hydration, combinedSelection);
4335
- state = relationSelectionResult.state;
4336
- hydration = relationSelectionResult.hydration;
4337
- hydration = hydration.onRelationIncluded(
4338
- state,
4339
- relation,
4340
- relationName,
4341
- aliasPrefix,
4342
- targetColumns,
4343
- { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
4344
- );
4345
- return { state, hydration };
4251
+ break;
4252
+ case "ExistsExpression":
4253
+ collector.hasSubquery = true;
4254
+ break;
4255
+ case "BetweenExpression":
4256
+ collectFromOperand(expr.left, collector);
4257
+ collectFromOperand(expr.lower, collector);
4258
+ collectFromOperand(expr.upper, collector);
4259
+ break;
4260
+ case "ArithmeticExpression":
4261
+ case "BitwiseExpression":
4262
+ collectFromOperand(expr.left, collector);
4263
+ collectFromOperand(expr.right, collector);
4264
+ break;
4265
+ default:
4266
+ break;
4346
4267
  }
4347
- /**
4348
- * Applies relation correlation to a query AST
4349
- * @param relationName - Name of the relation
4350
- * @param ast - Query AST to modify
4351
- * @returns Modified query AST with relation correlation
4352
- */
4353
- applyRelationCorrelation(relationName, ast, additionalCorrelation) {
4354
- const relation = this.getRelation(relationName);
4355
- const rootAlias = this.state.ast.from.type === "Table" ? this.state.ast.from.alias : void 0;
4356
- let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
4357
- if (additionalCorrelation) {
4358
- correlation = and(correlation, additionalCorrelation);
4359
- }
4360
- const whereInSubquery = ast.where ? and(correlation, ast.where) : correlation;
4361
- return {
4362
- ...ast,
4363
- where: whereInSubquery
4364
- };
4268
+ };
4269
+ var collectFromOperand = (node, collector) => {
4270
+ switch (node.type) {
4271
+ case "Column":
4272
+ collector.tables.add(node.table);
4273
+ break;
4274
+ case "Function":
4275
+ node.args.forEach((arg) => collectFromOperand(arg, collector));
4276
+ if (node.separator) {
4277
+ collectFromOperand(node.separator, collector);
4278
+ }
4279
+ if (node.orderBy) {
4280
+ node.orderBy.forEach((order) => collectFromOrderingTerm(order.term, collector));
4281
+ }
4282
+ break;
4283
+ case "JsonPath":
4284
+ collectFromOperand(node.column, collector);
4285
+ break;
4286
+ case "ScalarSubquery":
4287
+ collector.hasSubquery = true;
4288
+ break;
4289
+ case "CaseExpression":
4290
+ node.conditions.forEach(({ when, then }) => {
4291
+ collectFromExpression(when, collector);
4292
+ collectFromOperand(then, collector);
4293
+ });
4294
+ if (node.else) {
4295
+ collectFromOperand(node.else, collector);
4296
+ }
4297
+ break;
4298
+ case "Cast":
4299
+ collectFromOperand(node.expression, collector);
4300
+ break;
4301
+ case "WindowFunction":
4302
+ node.args.forEach((arg) => collectFromOperand(arg, collector));
4303
+ node.partitionBy?.forEach((part) => collectFromOperand(part, collector));
4304
+ node.orderBy?.forEach((order) => collectFromOrderingTerm(order.term, collector));
4305
+ break;
4306
+ case "Collate":
4307
+ collectFromOperand(node.expression, collector);
4308
+ break;
4309
+ case "ArithmeticExpression":
4310
+ case "BitwiseExpression":
4311
+ collectFromOperand(node.left, collector);
4312
+ collectFromOperand(node.right, collector);
4313
+ break;
4314
+ case "Literal":
4315
+ case "AliasRef":
4316
+ break;
4317
+ default:
4318
+ break;
4365
4319
  }
4366
- /**
4367
- * Creates a join node for a relation
4368
- * @param state - Current query state
4369
- * @param relationName - Name of the relation
4370
- * @param joinKind - Type of join to use
4371
- * @param extraCondition - Additional join condition
4372
- * @returns Updated query state with join
4373
- */
4374
- withJoin(state, relationName, joinKind, extraCondition, tableSource) {
4375
- const relation = this.getRelation(relationName);
4320
+ };
4321
+ var collectFromOrderingTerm = (term, collector) => {
4322
+ if (isOperandNode(term)) {
4323
+ collectFromOperand(term, collector);
4324
+ return;
4325
+ }
4326
+ collectFromExpression(term, collector);
4327
+ };
4328
+
4329
+ // src/query-builder/relation-join-planner.ts
4330
+ var RelationJoinPlanner = class {
4331
+ constructor(table, createQueryAstService) {
4332
+ this.table = table;
4333
+ this.createQueryAstService = createQueryAstService;
4334
+ }
4335
+ withJoin(state, relationName, relation, joinKind, extraCondition, tableSource) {
4376
4336
  const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4377
4337
  if (relation.type === RelationKinds.BelongsToMany) {
4378
4338
  const targetTableSource = tableSource ?? {
@@ -4409,167 +4369,31 @@ var RelationService = class {
4409
4369
  const joinNode = createJoinNode(joinKind, targetTable, condition, relationName);
4410
4370
  return this.astService(state).withJoin(joinNode);
4411
4371
  }
4412
- /**
4413
- * Selects columns for a relation
4414
- * @param state - Current query state
4415
- * @param hydration - Hydration manager
4416
- * @param columns - Columns to select
4417
- * @returns Relation result with updated state and hydration
4418
- */
4419
- selectColumns(state, hydration, columns) {
4420
- const { state: nextState, addedColumns } = this.astService(state).select(columns);
4421
- return {
4422
- state: nextState,
4423
- hydration: hydration.onColumnsSelected(nextState, addedColumns)
4424
- };
4425
- }
4426
- combineWithAnd(expressions) {
4427
- if (expressions.length === 0) return void 0;
4428
- if (expressions.length === 1) return expressions[0];
4429
- return {
4430
- type: "LogicalExpression",
4431
- operator: "AND",
4432
- operands: expressions
4433
- };
4434
- }
4435
- splitFilterExpressions(filter, allowedTables) {
4436
- const terms = this.flattenAnd(filter);
4437
- const selfFilters = [];
4438
- const crossFilters = [];
4439
- for (const term of terms) {
4440
- if (this.isExpressionSelfContained(term, allowedTables)) {
4441
- selfFilters.push(term);
4442
- } else {
4443
- crossFilters.push(term);
4444
- }
4445
- }
4446
- return { selfFilters, crossFilters };
4447
- }
4448
- flattenAnd(node) {
4449
- if (!node) return [];
4450
- if (node.type === "LogicalExpression" && node.operator === "AND") {
4451
- return node.operands.flatMap((operand) => this.flattenAnd(operand));
4452
- }
4453
- return [node];
4372
+ astService(state) {
4373
+ return this.createQueryAstService(this.table, state);
4454
4374
  }
4455
- isExpressionSelfContained(expr, allowedTables) {
4456
- const collector = this.collectReferencedTables(expr);
4457
- if (collector.hasSubquery) return false;
4458
- if (collector.tables.size === 0) return true;
4459
- for (const table of collector.tables) {
4460
- if (!allowedTables.has(table)) {
4461
- return false;
4462
- }
4375
+ resolveTargetTableName(target, relation) {
4376
+ if (target.type === "Table") {
4377
+ return target.alias ?? target.name;
4463
4378
  }
4464
- return true;
4465
- }
4466
- collectReferencedTables(expr) {
4467
- const collector = {
4468
- tables: /* @__PURE__ */ new Set(),
4469
- hasSubquery: false
4470
- };
4471
- this.collectFromExpression(expr, collector);
4472
- return collector;
4473
- }
4474
- collectFromExpression(expr, collector) {
4475
- switch (expr.type) {
4476
- case "BinaryExpression":
4477
- this.collectFromOperand(expr.left, collector);
4478
- this.collectFromOperand(expr.right, collector);
4479
- break;
4480
- case "LogicalExpression":
4481
- expr.operands.forEach((operand) => this.collectFromExpression(operand, collector));
4482
- break;
4483
- case "NullExpression":
4484
- this.collectFromOperand(expr.left, collector);
4485
- break;
4486
- case "InExpression":
4487
- this.collectFromOperand(expr.left, collector);
4488
- if (Array.isArray(expr.right)) {
4489
- expr.right.forEach((value) => this.collectFromOperand(value, collector));
4490
- } else {
4491
- collector.hasSubquery = true;
4492
- }
4493
- break;
4494
- case "ExistsExpression":
4495
- collector.hasSubquery = true;
4496
- break;
4497
- case "BetweenExpression":
4498
- this.collectFromOperand(expr.left, collector);
4499
- this.collectFromOperand(expr.lower, collector);
4500
- this.collectFromOperand(expr.upper, collector);
4501
- break;
4502
- case "ArithmeticExpression":
4503
- case "BitwiseExpression":
4504
- this.collectFromOperand(expr.left, collector);
4505
- this.collectFromOperand(expr.right, collector);
4506
- break;
4507
- default:
4508
- break;
4379
+ if (target.type === "DerivedTable") {
4380
+ return target.alias;
4509
4381
  }
4510
- }
4511
- collectFromOperand(node, collector) {
4512
- switch (node.type) {
4513
- case "Column":
4514
- collector.tables.add(node.table);
4515
- break;
4516
- case "Function":
4517
- node.args.forEach((arg) => this.collectFromOperand(arg, collector));
4518
- if (node.separator) {
4519
- this.collectFromOperand(node.separator, collector);
4520
- }
4521
- if (node.orderBy) {
4522
- node.orderBy.forEach((order) => this.collectFromOrderingTerm(order.term, collector));
4523
- }
4524
- break;
4525
- case "JsonPath":
4526
- this.collectFromOperand(node.column, collector);
4527
- break;
4528
- case "ScalarSubquery":
4529
- collector.hasSubquery = true;
4530
- break;
4531
- case "CaseExpression":
4532
- node.conditions.forEach(({ when, then }) => {
4533
- this.collectFromExpression(when, collector);
4534
- this.collectFromOperand(then, collector);
4535
- });
4536
- if (node.else) {
4537
- this.collectFromOperand(node.else, collector);
4538
- }
4539
- break;
4540
- case "Cast":
4541
- this.collectFromOperand(node.expression, collector);
4542
- break;
4543
- case "WindowFunction":
4544
- node.args.forEach((arg) => this.collectFromOperand(arg, collector));
4545
- node.partitionBy?.forEach((part) => this.collectFromOperand(part, collector));
4546
- node.orderBy?.forEach((order) => this.collectFromOrderingTerm(order.term, collector));
4547
- break;
4548
- case "Collate":
4549
- this.collectFromOperand(node.expression, collector);
4550
- break;
4551
- case "ArithmeticExpression":
4552
- case "BitwiseExpression":
4553
- this.collectFromOperand(node.left, collector);
4554
- this.collectFromOperand(node.right, collector);
4555
- break;
4556
- case "Literal":
4557
- case "AliasRef":
4558
- break;
4559
- default:
4560
- break;
4382
+ if (target.type === "FunctionTable") {
4383
+ return target.alias ?? relation.target.name;
4561
4384
  }
4385
+ return relation.target.name;
4562
4386
  }
4563
- collectFromOrderingTerm(term, collector) {
4564
- if (isOperandNode(term)) {
4565
- this.collectFromOperand(term, collector);
4566
- return;
4567
- }
4568
- this.collectFromExpression(term, collector);
4387
+ };
4388
+
4389
+ // src/query-builder/relation-cte-builder.ts
4390
+ var RelationCteBuilder = class {
4391
+ constructor(table, createQueryAstService) {
4392
+ this.table = table;
4393
+ this.createQueryAstService = createQueryAstService;
4569
4394
  }
4570
- createFilteredRelationCte(state, relationName, relation, filters) {
4395
+ createFilteredRelationCte(state, relationName, relation, predicate) {
4571
4396
  const cteName = this.generateUniqueCteName(state, relationName);
4572
- const predicate = this.combineWithAnd(filters);
4573
4397
  if (!predicate) {
4574
4398
  throw new Error("Unable to build filter CTE without predicates.");
4575
4399
  }
@@ -4603,17 +4427,274 @@ var RelationService = class {
4603
4427
  }
4604
4428
  return candidate;
4605
4429
  }
4606
- resolveTargetTableName(target, relation) {
4607
- if (target.type === "Table") {
4608
- return target.alias ?? target.name;
4430
+ astService(state) {
4431
+ return this.createQueryAstService(this.table, state);
4432
+ }
4433
+ };
4434
+
4435
+ // src/query-builder/relation-include-strategies.ts
4436
+ var buildTypedSelection = (columns, prefix, keys, missingMsg) => {
4437
+ return keys.reduce((acc, key) => {
4438
+ const def = columns[key];
4439
+ if (!def) {
4440
+ throw new Error(missingMsg(key));
4609
4441
  }
4610
- if (target.type === "DerivedTable") {
4611
- return target.alias;
4442
+ acc[makeRelationAlias(prefix, key)] = def;
4443
+ return acc;
4444
+ }, {});
4445
+ };
4446
+ var resolveTargetColumns = (relation, options) => {
4447
+ const requestedColumns = options?.columns?.length ? [...options.columns] : Object.keys(relation.target.columns);
4448
+ const targetPrimaryKey = findPrimaryKey(relation.target);
4449
+ if (!requestedColumns.includes(targetPrimaryKey)) {
4450
+ requestedColumns.push(targetPrimaryKey);
4451
+ }
4452
+ return requestedColumns;
4453
+ };
4454
+ var ensureRootForeignKeySelected = (context, relation) => {
4455
+ const fkColumn = context.rootTable.columns[relation.foreignKey];
4456
+ if (!fkColumn) {
4457
+ return { state: context.state, hydration: context.hydration };
4458
+ }
4459
+ const hasForeignKeySelected = context.state.ast.columns.some((col2) => {
4460
+ if (col2.type !== "Column") return false;
4461
+ const node = col2;
4462
+ const alias = node.alias ?? node.name;
4463
+ return alias === relation.foreignKey;
4464
+ });
4465
+ if (hasForeignKeySelected) {
4466
+ return { state: context.state, hydration: context.hydration };
4467
+ }
4468
+ return context.selectColumns(context.state, context.hydration, {
4469
+ [relation.foreignKey]: fkColumn
4470
+ });
4471
+ };
4472
+ var standardIncludeStrategy = (context) => {
4473
+ const relation = context.relation;
4474
+ let { state, hydration } = context;
4475
+ const fkSelectionResult = ensureRootForeignKeySelected(context, relation);
4476
+ state = fkSelectionResult.state;
4477
+ hydration = fkSelectionResult.hydration;
4478
+ const targetColumns = resolveTargetColumns(relation, context.options);
4479
+ const targetSelection = buildTypedSelection(
4480
+ relation.target.columns,
4481
+ context.aliasPrefix,
4482
+ targetColumns,
4483
+ (key) => `Column '${key}' not found on relation '${context.relationName}'`
4484
+ );
4485
+ const relationSelectionResult = context.selectColumns(state, hydration, targetSelection);
4486
+ state = relationSelectionResult.state;
4487
+ hydration = relationSelectionResult.hydration;
4488
+ hydration = hydration.onRelationIncluded(
4489
+ state,
4490
+ relation,
4491
+ context.relationName,
4492
+ context.aliasPrefix,
4493
+ targetColumns
4494
+ );
4495
+ return { state, hydration };
4496
+ };
4497
+ var belongsToManyStrategy = (context) => {
4498
+ const relation = context.relation;
4499
+ let { state, hydration } = context;
4500
+ const targetColumns = resolveTargetColumns(relation, context.options);
4501
+ const targetSelection = buildTypedSelection(
4502
+ relation.target.columns,
4503
+ context.aliasPrefix,
4504
+ targetColumns,
4505
+ (key) => `Column '${key}' not found on relation '${context.relationName}'`
4506
+ );
4507
+ const pivotAliasPrefix = context.options?.pivot?.aliasPrefix ?? `${context.aliasPrefix}_pivot`;
4508
+ const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
4509
+ const defaultPivotColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
4510
+ const pivotColumns = context.options?.pivot?.columns ? [...context.options.pivot.columns] : [...defaultPivotColumns];
4511
+ const pivotSelection = buildTypedSelection(
4512
+ relation.pivotTable.columns,
4513
+ pivotAliasPrefix,
4514
+ pivotColumns,
4515
+ (key) => `Column '${key}' not found on pivot table '${relation.pivotTable.name}'`
4516
+ );
4517
+ const combinedSelection = {
4518
+ ...targetSelection,
4519
+ ...pivotSelection
4520
+ };
4521
+ const relationSelectionResult = context.selectColumns(state, hydration, combinedSelection);
4522
+ state = relationSelectionResult.state;
4523
+ hydration = relationSelectionResult.hydration;
4524
+ hydration = hydration.onRelationIncluded(
4525
+ state,
4526
+ relation,
4527
+ context.relationName,
4528
+ context.aliasPrefix,
4529
+ targetColumns,
4530
+ { aliasPrefix: pivotAliasPrefix, columns: pivotColumns }
4531
+ );
4532
+ return { state, hydration };
4533
+ };
4534
+ var relationIncludeStrategies = {
4535
+ [RelationKinds.HasMany]: standardIncludeStrategy,
4536
+ [RelationKinds.HasOne]: standardIncludeStrategy,
4537
+ [RelationKinds.BelongsTo]: standardIncludeStrategy,
4538
+ [RelationKinds.BelongsToMany]: belongsToManyStrategy
4539
+ };
4540
+
4541
+ // src/query-builder/relation-service.ts
4542
+ var RelationService = class {
4543
+ /**
4544
+ * Creates a new RelationService instance
4545
+ * @param table - Table definition
4546
+ * @param state - Current query state
4547
+ * @param hydration - Hydration manager
4548
+ */
4549
+ constructor(table, state, hydration, createQueryAstService) {
4550
+ this.table = table;
4551
+ this.state = state;
4552
+ this.hydration = hydration;
4553
+ this.createQueryAstService = createQueryAstService;
4554
+ this.projectionHelper = new RelationProjectionHelper(
4555
+ table,
4556
+ (state2, hydration2, columns) => this.selectColumns(state2, hydration2, columns)
4557
+ );
4558
+ this.joinPlanner = new RelationJoinPlanner(table, createQueryAstService);
4559
+ this.cteBuilder = new RelationCteBuilder(table, createQueryAstService);
4560
+ }
4561
+ projectionHelper;
4562
+ joinPlanner;
4563
+ cteBuilder;
4564
+ /**
4565
+ * Joins a relation to the query
4566
+ * @param relationName - Name of the relation to join
4567
+ * @param joinKind - Type of join to use
4568
+ * @param extraCondition - Additional join condition
4569
+ * @returns Relation result with updated state and hydration
4570
+ */
4571
+ joinRelation(relationName, joinKind, extraCondition, tableSource) {
4572
+ const relation = this.getRelation(relationName);
4573
+ const nextState = this.joinPlanner.withJoin(
4574
+ this.state,
4575
+ relationName,
4576
+ relation,
4577
+ joinKind,
4578
+ extraCondition,
4579
+ tableSource
4580
+ );
4581
+ return { state: nextState, hydration: this.hydration };
4582
+ }
4583
+ /**
4584
+ * Matches records based on a relation with an optional predicate
4585
+ * @param relationName - Name of the relation to match
4586
+ * @param predicate - Optional predicate expression
4587
+ * @returns Relation result with updated state and hydration
4588
+ */
4589
+ match(relationName, predicate) {
4590
+ const joined = this.joinRelation(relationName, JOIN_KINDS.INNER, predicate);
4591
+ const pk = findPrimaryKey(this.table);
4592
+ const distinctCols = [{ type: "Column", table: this.rootTableName(), name: pk }];
4593
+ const existingDistinct = joined.state.ast.distinct ? joined.state.ast.distinct : [];
4594
+ const nextState = this.astService(joined.state).withDistinct([...existingDistinct, ...distinctCols]);
4595
+ return { state: nextState, hydration: joined.hydration };
4596
+ }
4597
+ /**
4598
+ * Includes a relation in the query result
4599
+ * @param relationName - Name of the relation to include
4600
+ * @param options - Options for relation inclusion
4601
+ * @returns Relation result with updated state and hydration
4602
+ */
4603
+ include(relationName, options) {
4604
+ let state = this.state;
4605
+ let hydration = this.hydration;
4606
+ const relation = this.getRelation(relationName);
4607
+ const aliasPrefix = options?.aliasPrefix ?? relationName;
4608
+ const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
4609
+ const { selfFilters, crossFilters } = splitFilterExpressions(
4610
+ options?.filter,
4611
+ /* @__PURE__ */ new Set([relation.target.name])
4612
+ );
4613
+ const canUseCte = !alreadyJoined && selfFilters.length > 0;
4614
+ const joinFilters = [...crossFilters];
4615
+ if (!canUseCte) {
4616
+ joinFilters.push(...selfFilters);
4612
4617
  }
4613
- if (target.type === "FunctionTable") {
4614
- return target.alias ?? relation.target.name;
4618
+ const joinCondition = this.combineWithAnd(joinFilters);
4619
+ let tableSourceOverride;
4620
+ if (canUseCte) {
4621
+ const predicate = this.combineWithAnd(selfFilters);
4622
+ const cteInfo = this.cteBuilder.createFilteredRelationCte(
4623
+ state,
4624
+ relationName,
4625
+ relation,
4626
+ predicate
4627
+ );
4628
+ state = cteInfo.state;
4629
+ tableSourceOverride = cteInfo.table;
4615
4630
  }
4616
- return relation.target.name;
4631
+ if (!alreadyJoined) {
4632
+ state = this.joinPlanner.withJoin(
4633
+ state,
4634
+ relationName,
4635
+ relation,
4636
+ options?.joinKind ?? JOIN_KINDS.LEFT,
4637
+ joinCondition,
4638
+ tableSourceOverride
4639
+ );
4640
+ }
4641
+ const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
4642
+ state = projectionResult.state;
4643
+ hydration = projectionResult.hydration;
4644
+ const strategy = relationIncludeStrategies[relation.type];
4645
+ const result = strategy({
4646
+ rootTable: this.table,
4647
+ state,
4648
+ hydration,
4649
+ relation,
4650
+ relationName,
4651
+ aliasPrefix,
4652
+ options,
4653
+ selectColumns: (nextState, nextHydration, columns) => this.selectColumns(nextState, nextHydration, columns)
4654
+ });
4655
+ return { state: result.state, hydration: result.hydration };
4656
+ }
4657
+ /**
4658
+ * Applies relation correlation to a query AST
4659
+ * @param relationName - Name of the relation
4660
+ * @param ast - Query AST to modify
4661
+ * @returns Modified query AST with relation correlation
4662
+ */
4663
+ applyRelationCorrelation(relationName, ast, additionalCorrelation) {
4664
+ const relation = this.getRelation(relationName);
4665
+ const rootAlias = this.state.ast.from.type === "Table" ? this.state.ast.from.alias : void 0;
4666
+ let correlation = buildRelationCorrelation(this.table, relation, rootAlias);
4667
+ if (additionalCorrelation) {
4668
+ correlation = and(correlation, additionalCorrelation);
4669
+ }
4670
+ const whereInSubquery = ast.where ? and(correlation, ast.where) : correlation;
4671
+ return {
4672
+ ...ast,
4673
+ where: whereInSubquery
4674
+ };
4675
+ }
4676
+ /**
4677
+ * Selects columns for a relation
4678
+ * @param state - Current query state
4679
+ * @param hydration - Hydration manager
4680
+ * @param columns - Columns to select
4681
+ * @returns Relation result with updated state and hydration
4682
+ */
4683
+ selectColumns(state, hydration, columns) {
4684
+ const { state: nextState, addedColumns } = this.astService(state).select(columns);
4685
+ return {
4686
+ state: nextState,
4687
+ hydration: hydration.onColumnsSelected(nextState, addedColumns)
4688
+ };
4689
+ }
4690
+ combineWithAnd(expressions) {
4691
+ if (expressions.length === 0) return void 0;
4692
+ if (expressions.length === 1) return expressions[0];
4693
+ return {
4694
+ type: "LogicalExpression",
4695
+ operator: "AND",
4696
+ operands: expressions
4697
+ };
4617
4698
  }
4618
4699
  /**
4619
4700
  * Gets a relation definition by name
@@ -4903,8 +4984,52 @@ var hasEntityMeta = (entity) => {
4903
4984
  return Boolean(getEntityMeta(entity));
4904
4985
  };
4905
4986
 
4906
- // src/orm/relations/has-many.ts
4987
+ // src/orm/entity-hydration.ts
4907
4988
  var toKey2 = (value) => value === null || value === void 0 ? "" : String(value);
4989
+ var populateHydrationCache = (entity, row, meta) => {
4990
+ for (const relationName of Object.keys(meta.table.relations)) {
4991
+ const relation = meta.table.relations[relationName];
4992
+ const data = row[relationName];
4993
+ if (relation.type === RelationKinds.HasOne) {
4994
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
4995
+ const rootValue = entity[localKey];
4996
+ if (rootValue === void 0 || rootValue === null) continue;
4997
+ if (!data || typeof data !== "object") continue;
4998
+ const cache = /* @__PURE__ */ new Map();
4999
+ cache.set(toKey2(rootValue), data);
5000
+ meta.relationHydration.set(relationName, cache);
5001
+ meta.relationCache.set(relationName, Promise.resolve(cache));
5002
+ continue;
5003
+ }
5004
+ if (!Array.isArray(data)) continue;
5005
+ if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
5006
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
5007
+ const rootValue = entity[localKey];
5008
+ if (rootValue === void 0 || rootValue === null) continue;
5009
+ const cache = /* @__PURE__ */ new Map();
5010
+ cache.set(toKey2(rootValue), data);
5011
+ meta.relationHydration.set(relationName, cache);
5012
+ meta.relationCache.set(relationName, Promise.resolve(cache));
5013
+ continue;
5014
+ }
5015
+ if (relation.type === RelationKinds.BelongsTo) {
5016
+ const targetKey = relation.localKey || findPrimaryKey(relation.target);
5017
+ const cache = /* @__PURE__ */ new Map();
5018
+ for (const item of data) {
5019
+ const pkValue = item[targetKey];
5020
+ if (pkValue === void 0 || pkValue === null) continue;
5021
+ cache.set(toKey2(pkValue), item);
5022
+ }
5023
+ if (cache.size) {
5024
+ meta.relationHydration.set(relationName, cache);
5025
+ meta.relationCache.set(relationName, Promise.resolve(cache));
5026
+ }
5027
+ }
5028
+ }
5029
+ };
5030
+
5031
+ // src/orm/relations/has-many.ts
5032
+ var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
4908
5033
  var hideInternal = (obj, keys) => {
4909
5034
  for (const key of keys) {
4910
5035
  Object.defineProperty(obj, key, {
@@ -4938,13 +5063,13 @@ var DefaultHasManyCollection = class {
4938
5063
  this.loader = loader;
4939
5064
  this.createEntity = createEntity;
4940
5065
  this.localKey = localKey;
4941
- this.loaded = false;
4942
- this.items = [];
4943
- this.added = /* @__PURE__ */ new Set();
4944
- this.removed = /* @__PURE__ */ new Set();
4945
5066
  hideInternal(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
4946
5067
  this.hydrateFromCache();
4947
5068
  }
5069
+ loaded = false;
5070
+ items = [];
5071
+ added = /* @__PURE__ */ new Set();
5072
+ removed = /* @__PURE__ */ new Set();
4948
5073
  /**
4949
5074
  * Loads the related entities if not already loaded.
4950
5075
  * @returns Promise resolving to the array of child entities
@@ -4952,7 +5077,7 @@ var DefaultHasManyCollection = class {
4952
5077
  async load() {
4953
5078
  if (this.loaded) return this.items;
4954
5079
  const map = await this.loader();
4955
- const key = toKey2(this.root[this.localKey]);
5080
+ const key = toKey3(this.root[this.localKey]);
4956
5081
  const rows = map.get(key) ?? [];
4957
5082
  this.items = rows.map((row) => this.createEntity(row));
4958
5083
  this.loaded = true;
@@ -5064,7 +5189,7 @@ var DefaultHasManyCollection = class {
5064
5189
  };
5065
5190
 
5066
5191
  // src/orm/relations/has-one.ts
5067
- var toKey3 = (value) => value === null || value === void 0 ? "" : String(value);
5192
+ var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
5068
5193
  var hideInternal2 = (obj, keys) => {
5069
5194
  for (const key of keys) {
5070
5195
  Object.defineProperty(obj, key, {
@@ -5097,8 +5222,6 @@ var DefaultHasOneReference = class {
5097
5222
  this.loader = loader;
5098
5223
  this.createEntity = createEntity;
5099
5224
  this.localKey = localKey;
5100
- this.loaded = false;
5101
- this.current = null;
5102
5225
  hideInternal2(this, [
5103
5226
  "ctx",
5104
5227
  "meta",
@@ -5112,6 +5235,8 @@ var DefaultHasOneReference = class {
5112
5235
  ]);
5113
5236
  this.populateFromHydrationCache();
5114
5237
  }
5238
+ loaded = false;
5239
+ current = null;
5115
5240
  async load() {
5116
5241
  if (this.loaded) return this.current;
5117
5242
  const map = await this.loader();
@@ -5120,7 +5245,7 @@ var DefaultHasOneReference = class {
5120
5245
  this.loaded = true;
5121
5246
  return this.current;
5122
5247
  }
5123
- const row = map.get(toKey3(keyValue));
5248
+ const row = map.get(toKey4(keyValue));
5124
5249
  this.current = row ? this.createEntity(row) : null;
5125
5250
  this.loaded = true;
5126
5251
  return this.current;
@@ -5192,7 +5317,7 @@ var DefaultHasOneReference = class {
5192
5317
  };
5193
5318
 
5194
5319
  // src/orm/relations/belongs-to.ts
5195
- var toKey4 = (value) => value === null || value === void 0 ? "" : String(value);
5320
+ var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
5196
5321
  var hideInternal3 = (obj, keys) => {
5197
5322
  for (const key of keys) {
5198
5323
  Object.defineProperty(obj, key, {
@@ -5225,11 +5350,11 @@ var DefaultBelongsToReference = class {
5225
5350
  this.loader = loader;
5226
5351
  this.createEntity = createEntity;
5227
5352
  this.targetKey = targetKey;
5228
- this.loaded = false;
5229
- this.current = null;
5230
5353
  hideInternal3(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "targetKey"]);
5231
5354
  this.populateFromHydrationCache();
5232
5355
  }
5356
+ loaded = false;
5357
+ current = null;
5233
5358
  async load() {
5234
5359
  if (this.loaded) return this.current;
5235
5360
  const map = await this.loader();
@@ -5237,7 +5362,7 @@ var DefaultBelongsToReference = class {
5237
5362
  if (fkValue === null || fkValue === void 0) {
5238
5363
  this.current = null;
5239
5364
  } else {
5240
- const row = map.get(toKey4(fkValue));
5365
+ const row = map.get(toKey5(fkValue));
5241
5366
  this.current = row ? this.createEntity(row) : null;
5242
5367
  }
5243
5368
  this.loaded = true;
@@ -5294,7 +5419,7 @@ var DefaultBelongsToReference = class {
5294
5419
  };
5295
5420
 
5296
5421
  // src/orm/relations/many-to-many.ts
5297
- var toKey5 = (value) => value === null || value === void 0 ? "" : String(value);
5422
+ var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
5298
5423
  var hideInternal4 = (obj, keys) => {
5299
5424
  for (const key of keys) {
5300
5425
  Object.defineProperty(obj, key, {
@@ -5327,11 +5452,11 @@ var DefaultManyToManyCollection = class {
5327
5452
  this.loader = loader;
5328
5453
  this.createEntity = createEntity;
5329
5454
  this.localKey = localKey;
5330
- this.loaded = false;
5331
- this.items = [];
5332
5455
  hideInternal4(this, ["ctx", "meta", "root", "relationName", "relation", "rootTable", "loader", "createEntity", "localKey"]);
5333
5456
  this.hydrateFromCache();
5334
5457
  }
5458
+ loaded = false;
5459
+ items = [];
5335
5460
  /**
5336
5461
  * Loads the collection items if not already loaded.
5337
5462
  * @returns A promise that resolves to the array of target entities.
@@ -5339,7 +5464,7 @@ var DefaultManyToManyCollection = class {
5339
5464
  async load() {
5340
5465
  if (this.loaded) return this.items;
5341
5466
  const map = await this.loader();
5342
- const key = toKey5(this.root[this.localKey]);
5467
+ const key = toKey6(this.root[this.localKey]);
5343
5468
  const rows = map.get(key) ?? [];
5344
5469
  this.items = rows.map((row) => {
5345
5470
  const entity = this.createEntity(row);
@@ -5421,15 +5546,15 @@ var DefaultManyToManyCollection = class {
5421
5546
  */
5422
5547
  async syncByIds(ids) {
5423
5548
  await this.load();
5424
- const normalized = new Set(ids.map((id) => toKey5(id)));
5425
- const currentIds = new Set(this.items.map((item) => toKey5(this.extractId(item))));
5549
+ const normalized = new Set(ids.map((id) => toKey6(id)));
5550
+ const currentIds = new Set(this.items.map((item) => toKey6(this.extractId(item))));
5426
5551
  for (const id of normalized) {
5427
5552
  if (!currentIds.has(id)) {
5428
5553
  this.attach(id);
5429
5554
  }
5430
5555
  }
5431
5556
  for (const item of [...this.items]) {
5432
- const itemId = toKey5(this.extractId(item));
5557
+ const itemId = toKey6(this.extractId(item));
5433
5558
  if (!normalized.has(itemId)) {
5434
5559
  this.detach(item);
5435
5560
  }
@@ -5476,7 +5601,7 @@ var DefaultManyToManyCollection = class {
5476
5601
  }
5477
5602
  };
5478
5603
 
5479
- // src/orm/lazy-batch.ts
5604
+ // src/orm/lazy-batch/shared.ts
5480
5605
  var hasColumns = (columns) => Boolean(columns && columns.length > 0);
5481
5606
  var buildColumnSelection = (table, columns, missingMsg) => {
5482
5607
  return columns.reduce((acc, column) => {
@@ -5517,7 +5642,7 @@ var executeQuery = async (ctx, qb) => {
5517
5642
  const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
5518
5643
  return rowsFromResults(results);
5519
5644
  };
5520
- var toKey6 = (value) => value === null || value === void 0 ? "" : String(value);
5645
+ var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
5521
5646
  var collectKeysFromRoots = (roots, key) => {
5522
5647
  const collected = /* @__PURE__ */ new Set();
5523
5648
  for (const tracked of roots) {
@@ -5542,7 +5667,7 @@ var groupRowsByMany = (rows, keyColumn) => {
5542
5667
  for (const row of rows) {
5543
5668
  const value = row[keyColumn];
5544
5669
  if (value === null || value === void 0) continue;
5545
- const key = toKey6(value);
5670
+ const key = toKey7(value);
5546
5671
  const bucket = grouped.get(key) ?? [];
5547
5672
  bucket.push(row);
5548
5673
  grouped.set(key, bucket);
@@ -5554,13 +5679,15 @@ var groupRowsByUnique = (rows, keyColumn) => {
5554
5679
  for (const row of rows) {
5555
5680
  const value = row[keyColumn];
5556
5681
  if (value === null || value === void 0) continue;
5557
- const key = toKey6(value);
5682
+ const key = toKey7(value);
5558
5683
  if (!lookup.has(key)) {
5559
5684
  lookup.set(key, row);
5560
5685
  }
5561
5686
  }
5562
5687
  return lookup;
5563
5688
  };
5689
+
5690
+ // src/orm/lazy-batch/has-many.ts
5564
5691
  var loadHasManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5565
5692
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5566
5693
  const roots = ctx.getEntitiesForTable(rootTable);
@@ -5593,6 +5720,8 @@ var loadHasManyRelation = async (ctx, rootTable, relationName, relation, options
5593
5720
  }
5594
5721
  return filtered;
5595
5722
  };
5723
+
5724
+ // src/orm/lazy-batch/has-one.ts
5596
5725
  var loadHasOneRelation = async (ctx, rootTable, relationName, relation, options) => {
5597
5726
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5598
5727
  const roots = ctx.getEntitiesForTable(rootTable);
@@ -5625,6 +5754,8 @@ var loadHasOneRelation = async (ctx, rootTable, relationName, relation, options)
5625
5754
  }
5626
5755
  return filtered;
5627
5756
  };
5757
+
5758
+ // src/orm/lazy-batch/belongs-to.ts
5628
5759
  var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, options) => {
5629
5760
  const roots = ctx.getEntitiesForTable(rootTable);
5630
5761
  const getForeignKeys = () => collectKeysFromRoots(roots, relation.foreignKey);
@@ -5693,6 +5824,8 @@ var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, optio
5693
5824
  }
5694
5825
  return filtered;
5695
5826
  };
5827
+
5828
+ // src/orm/lazy-batch/belongs-to-many.ts
5696
5829
  var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5697
5830
  const rootKey = relation.localKey || findPrimaryKey(rootTable);
5698
5831
  const roots = ctx.getEntitiesForTable(rootTable);
@@ -5731,12 +5864,12 @@ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, o
5731
5864
  if (rootValue === null || rootValue === void 0 || targetValue === null || targetValue === void 0) {
5732
5865
  continue;
5733
5866
  }
5734
- const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
5867
+ const bucket = rootLookup.get(toKey7(rootValue)) ?? [];
5735
5868
  bucket.push({
5736
5869
  targetId: targetValue,
5737
5870
  pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
5738
5871
  });
5739
- rootLookup.set(toKey6(rootValue), bucket);
5872
+ rootLookup.set(toKey7(rootValue), bucket);
5740
5873
  targetIds.add(targetValue);
5741
5874
  }
5742
5875
  if (!targetIds.size) {
@@ -5749,156 +5882,61 @@ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, o
5749
5882
  const targetSelectedColumns = targetRequestedColumns ? [...targetRequestedColumns] : Object.keys(relation.target.columns);
5750
5883
  if (!targetSelectedColumns.includes(targetKey)) {
5751
5884
  targetSelectedColumns.push(targetKey);
5752
- }
5753
- const targetSelection = buildColumnSelection(
5754
- relation.target,
5755
- targetSelectedColumns,
5756
- (column) => `Column '${column}' not found on relation '${relationName}'`
5757
- );
5758
- const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds, targetSelection, options?.filter);
5759
- const targetMap = groupRowsByUnique(targetRows, targetKey);
5760
- const targetVisibleColumns = new Set(targetSelectedColumns);
5761
- const result = /* @__PURE__ */ new Map();
5762
- for (const [rootId, entries] of rootLookup.entries()) {
5763
- const bucket = [];
5764
- for (const entry of entries) {
5765
- const targetRow = targetMap.get(toKey6(entry.targetId));
5766
- if (!targetRow) continue;
5767
- bucket.push({
5768
- ...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
5769
- _pivot: entry.pivot
5770
- });
5771
- }
5772
- result.set(rootId, bucket);
5773
- }
5774
- return result;
5775
- };
5776
-
5777
- // src/orm/entity.ts
5778
- var relationLoaderCache = (meta, relationName, factory) => {
5779
- if (meta.relationCache.has(relationName)) {
5780
- return meta.relationCache.get(relationName);
5781
- }
5782
- const promise = factory().then((value) => {
5783
- for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
5784
- const otherMeta = getEntityMeta(tracked.entity);
5785
- if (!otherMeta) continue;
5786
- otherMeta.relationHydration.set(relationName, value);
5787
- }
5788
- return value;
5789
- });
5790
- meta.relationCache.set(relationName, promise);
5791
- for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
5792
- const otherMeta = getEntityMeta(tracked.entity);
5793
- if (!otherMeta) continue;
5794
- otherMeta.relationCache.set(relationName, promise);
5795
- }
5796
- return promise;
5797
- };
5798
- var createEntityProxy = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
5799
- const target = { ...row };
5800
- const meta = {
5801
- ctx,
5802
- table,
5803
- lazyRelations: [...lazyRelations],
5804
- lazyRelationOptions: new Map(lazyRelationOptions),
5805
- relationCache: /* @__PURE__ */ new Map(),
5806
- relationHydration: /* @__PURE__ */ new Map(),
5807
- relationWrappers: /* @__PURE__ */ new Map()
5808
- };
5809
- Object.defineProperty(target, ENTITY_META, {
5810
- value: meta,
5811
- enumerable: false,
5812
- writable: false
5813
- });
5814
- const handler = {
5815
- get(targetObj, prop, receiver) {
5816
- if (prop === ENTITY_META) {
5817
- return meta;
5818
- }
5819
- if (prop === "$load") {
5820
- return async (relationName) => {
5821
- const wrapper = getRelationWrapper(meta, relationName, receiver);
5822
- if (wrapper && typeof wrapper.load === "function") {
5823
- return wrapper.load();
5824
- }
5825
- return void 0;
5826
- };
5827
- }
5828
- if (typeof prop === "string" && table.relations[prop]) {
5829
- return getRelationWrapper(meta, prop, receiver);
5830
- }
5831
- return Reflect.get(targetObj, prop, receiver);
5832
- },
5833
- set(targetObj, prop, value, receiver) {
5834
- const result = Reflect.set(targetObj, prop, value, receiver);
5835
- if (typeof prop === "string" && table.columns[prop]) {
5836
- ctx.markDirty(receiver);
5837
- }
5838
- return result;
5839
- }
5840
- };
5841
- const proxy = new Proxy(target, handler);
5842
- populateHydrationCache(proxy, row, meta);
5843
- return proxy;
5844
- };
5845
- var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
5846
- const pkName = findPrimaryKey(table);
5847
- const pkValue = row[pkName];
5848
- if (pkValue !== void 0 && pkValue !== null) {
5849
- const tracked = ctx.getEntity(table, pkValue);
5850
- if (tracked) return tracked;
5851
- }
5852
- const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
5853
- if (pkValue !== void 0 && pkValue !== null) {
5854
- ctx.trackManaged(table, pkValue, entity);
5855
- } else {
5856
- ctx.trackNew(table, entity);
5857
- }
5858
- return entity;
5859
- };
5860
- var toKey7 = (value) => value === null || value === void 0 ? "" : String(value);
5861
- var populateHydrationCache = (entity, row, meta) => {
5862
- for (const relationName of Object.keys(meta.table.relations)) {
5863
- const relation = meta.table.relations[relationName];
5864
- const data = row[relationName];
5865
- if (relation.type === RelationKinds.HasOne) {
5866
- const localKey = relation.localKey || findPrimaryKey(meta.table);
5867
- const rootValue = entity[localKey];
5868
- if (rootValue === void 0 || rootValue === null) continue;
5869
- if (!data || typeof data !== "object") continue;
5870
- const cache = /* @__PURE__ */ new Map();
5871
- cache.set(toKey7(rootValue), data);
5872
- meta.relationHydration.set(relationName, cache);
5873
- meta.relationCache.set(relationName, Promise.resolve(cache));
5874
- continue;
5875
- }
5876
- if (!Array.isArray(data)) continue;
5877
- if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
5878
- const localKey = relation.localKey || findPrimaryKey(meta.table);
5879
- const rootValue = entity[localKey];
5880
- if (rootValue === void 0 || rootValue === null) continue;
5881
- const cache = /* @__PURE__ */ new Map();
5882
- cache.set(toKey7(rootValue), data);
5883
- meta.relationHydration.set(relationName, cache);
5884
- meta.relationCache.set(relationName, Promise.resolve(cache));
5885
- continue;
5885
+ }
5886
+ const targetSelection = buildColumnSelection(
5887
+ relation.target,
5888
+ targetSelectedColumns,
5889
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5890
+ );
5891
+ const targetRows = await fetchRowsForKeys(
5892
+ ctx,
5893
+ relation.target,
5894
+ targetPkColumn,
5895
+ targetIds,
5896
+ targetSelection,
5897
+ options?.filter
5898
+ );
5899
+ const targetMap = groupRowsByUnique(targetRows, targetKey);
5900
+ const targetVisibleColumns = new Set(targetSelectedColumns);
5901
+ const result = /* @__PURE__ */ new Map();
5902
+ for (const [rootId, entries] of rootLookup.entries()) {
5903
+ const bucket = [];
5904
+ for (const entry of entries) {
5905
+ const targetRow = targetMap.get(toKey7(entry.targetId));
5906
+ if (!targetRow) continue;
5907
+ bucket.push({
5908
+ ...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
5909
+ _pivot: entry.pivot
5910
+ });
5886
5911
  }
5887
- if (relation.type === RelationKinds.BelongsTo) {
5888
- const targetKey = relation.localKey || findPrimaryKey(relation.target);
5889
- const cache = /* @__PURE__ */ new Map();
5890
- for (const item of data) {
5891
- const pkValue = item[targetKey];
5892
- if (pkValue === void 0 || pkValue === null) continue;
5893
- cache.set(toKey7(pkValue), item);
5894
- }
5895
- if (cache.size) {
5896
- meta.relationHydration.set(relationName, cache);
5897
- meta.relationCache.set(relationName, Promise.resolve(cache));
5898
- }
5912
+ result.set(rootId, bucket);
5913
+ }
5914
+ return result;
5915
+ };
5916
+
5917
+ // src/orm/entity-relation-cache.ts
5918
+ var relationLoaderCache = (meta, relationName, factory) => {
5919
+ if (meta.relationCache.has(relationName)) {
5920
+ return meta.relationCache.get(relationName);
5921
+ }
5922
+ const promise = factory().then((value) => {
5923
+ for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
5924
+ const otherMeta = getEntityMeta(tracked.entity);
5925
+ if (!otherMeta) continue;
5926
+ otherMeta.relationHydration.set(relationName, value);
5899
5927
  }
5928
+ return value;
5929
+ });
5930
+ meta.relationCache.set(relationName, promise);
5931
+ for (const tracked of meta.ctx.getEntitiesForTable(meta.table)) {
5932
+ const otherMeta = getEntityMeta(tracked.entity);
5933
+ if (!otherMeta) continue;
5934
+ otherMeta.relationCache.set(relationName, promise);
5900
5935
  }
5936
+ return promise;
5901
5937
  };
5938
+
5939
+ // src/orm/entity-relations.ts
5902
5940
  var proxifyRelationWrapper = (wrapper) => {
5903
5941
  return new Proxy(wrapper, {
5904
5942
  get(target, prop, receiver) {
@@ -5951,98 +5989,93 @@ var proxifyRelationWrapper = (wrapper) => {
5951
5989
  }
5952
5990
  });
5953
5991
  };
5954
- var getRelationWrapper = (meta, relationName, owner) => {
5955
- if (meta.relationWrappers.has(relationName)) {
5956
- return meta.relationWrappers.get(relationName);
5992
+ var getRelationWrapper = (meta, relationName, owner, createEntity) => {
5993
+ const relationKey = relationName;
5994
+ if (meta.relationWrappers.has(relationKey)) {
5995
+ return meta.relationWrappers.get(relationKey);
5957
5996
  }
5958
- const relation = meta.table.relations[relationName];
5997
+ const relation = meta.table.relations[relationKey];
5959
5998
  if (!relation) return void 0;
5960
- const wrapper = instantiateWrapper(meta, relationName, relation, owner);
5999
+ const wrapper = instantiateWrapper(meta, relationKey, relation, owner, createEntity);
5961
6000
  if (!wrapper) return void 0;
5962
6001
  const proxied = proxifyRelationWrapper(wrapper);
5963
- meta.relationWrappers.set(relationName, proxied);
6002
+ meta.relationWrappers.set(relationKey, proxied);
5964
6003
  return proxied;
5965
6004
  };
5966
- var instantiateWrapper = (meta, relationName, relation, owner) => {
6005
+ var instantiateWrapper = (meta, relationName, relation, owner, createEntity) => {
6006
+ const metaBase = meta;
5967
6007
  const lazyOptions = meta.lazyRelationOptions.get(relationName);
6008
+ const loadCached = (factory) => relationLoaderCache(metaBase, relationName, factory);
5968
6009
  switch (relation.type) {
5969
6010
  case RelationKinds.HasOne: {
5970
6011
  const hasOne2 = relation;
5971
6012
  const localKey = hasOne2.localKey || findPrimaryKey(meta.table);
5972
- const loader = () => relationLoaderCache(
5973
- meta,
5974
- relationName,
6013
+ const loader = () => loadCached(
5975
6014
  () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, lazyOptions)
5976
6015
  );
5977
6016
  return new DefaultHasOneReference(
5978
6017
  meta.ctx,
5979
- meta,
6018
+ metaBase,
5980
6019
  owner,
5981
6020
  relationName,
5982
6021
  hasOne2,
5983
6022
  meta.table,
5984
6023
  loader,
5985
- (row) => createEntityFromRow(meta.ctx, hasOne2.target, row),
6024
+ (row) => createEntity(hasOne2.target, row),
5986
6025
  localKey
5987
6026
  );
5988
6027
  }
5989
6028
  case RelationKinds.HasMany: {
5990
6029
  const hasMany2 = relation;
5991
6030
  const localKey = hasMany2.localKey || findPrimaryKey(meta.table);
5992
- const loader = () => relationLoaderCache(
5993
- meta,
5994
- relationName,
6031
+ const loader = () => loadCached(
5995
6032
  () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, lazyOptions)
5996
6033
  );
5997
6034
  return new DefaultHasManyCollection(
5998
6035
  meta.ctx,
5999
- meta,
6036
+ metaBase,
6000
6037
  owner,
6001
6038
  relationName,
6002
6039
  hasMany2,
6003
6040
  meta.table,
6004
6041
  loader,
6005
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
6042
+ (row) => createEntity(relation.target, row),
6006
6043
  localKey
6007
6044
  );
6008
6045
  }
6009
6046
  case RelationKinds.BelongsTo: {
6010
6047
  const belongsTo2 = relation;
6011
6048
  const targetKey = belongsTo2.localKey || findPrimaryKey(belongsTo2.target);
6012
- const loader = () => relationLoaderCache(
6013
- meta,
6014
- relationName,
6049
+ const loader = () => loadCached(
6015
6050
  () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, lazyOptions)
6016
6051
  );
6017
6052
  return new DefaultBelongsToReference(
6018
6053
  meta.ctx,
6019
- meta,
6054
+ metaBase,
6020
6055
  owner,
6021
6056
  relationName,
6022
6057
  belongsTo2,
6023
6058
  meta.table,
6024
6059
  loader,
6025
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
6060
+ (row) => createEntity(relation.target, row),
6026
6061
  targetKey
6027
6062
  );
6028
6063
  }
6029
6064
  case RelationKinds.BelongsToMany: {
6030
6065
  const many = relation;
6031
6066
  const localKey = many.localKey || findPrimaryKey(meta.table);
6032
- const loader = () => relationLoaderCache(
6033
- meta,
6034
- relationName,
6067
+ const loader = () => loadCached(
6035
6068
  () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
6036
6069
  );
6037
6070
  return new DefaultManyToManyCollection(
6038
6071
  meta.ctx,
6039
- meta,
6072
+ metaBase,
6040
6073
  owner,
6041
6074
  relationName,
6042
6075
  many,
6043
6076
  meta.table,
6044
6077
  loader,
6045
- (row) => createEntityFromRow(meta.ctx, relation.target, row),
6078
+ (row) => createEntity(relation.target, row),
6046
6079
  localKey
6047
6080
  );
6048
6081
  }
@@ -6051,6 +6084,71 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
6051
6084
  }
6052
6085
  };
6053
6086
 
6087
+ // src/orm/entity.ts
6088
+ var createEntityProxy = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
6089
+ const target = { ...row };
6090
+ const meta = {
6091
+ ctx,
6092
+ table,
6093
+ lazyRelations: [...lazyRelations],
6094
+ lazyRelationOptions: new Map(lazyRelationOptions),
6095
+ relationCache: /* @__PURE__ */ new Map(),
6096
+ relationHydration: /* @__PURE__ */ new Map(),
6097
+ relationWrappers: /* @__PURE__ */ new Map()
6098
+ };
6099
+ const createRelationEntity = (relationTable, relationRow) => createEntityFromRow(meta.ctx, relationTable, relationRow);
6100
+ Object.defineProperty(target, ENTITY_META, {
6101
+ value: meta,
6102
+ enumerable: false,
6103
+ writable: false
6104
+ });
6105
+ const handler = {
6106
+ get(targetObj, prop, receiver) {
6107
+ if (prop === ENTITY_META) {
6108
+ return meta;
6109
+ }
6110
+ if (prop === "$load") {
6111
+ return async (relationName) => {
6112
+ const wrapper = getRelationWrapper(meta, relationName, receiver, createRelationEntity);
6113
+ if (wrapper && typeof wrapper.load === "function") {
6114
+ return wrapper.load();
6115
+ }
6116
+ return void 0;
6117
+ };
6118
+ }
6119
+ if (typeof prop === "string" && table.relations[prop]) {
6120
+ return getRelationWrapper(meta, prop, receiver, createRelationEntity);
6121
+ }
6122
+ return Reflect.get(targetObj, prop, receiver);
6123
+ },
6124
+ set(targetObj, prop, value, receiver) {
6125
+ const result = Reflect.set(targetObj, prop, value, receiver);
6126
+ if (typeof prop === "string" && table.columns[prop]) {
6127
+ ctx.markDirty(receiver);
6128
+ }
6129
+ return result;
6130
+ }
6131
+ };
6132
+ const proxy = new Proxy(target, handler);
6133
+ populateHydrationCache(proxy, row, meta);
6134
+ return proxy;
6135
+ };
6136
+ var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
6137
+ const pkName = findPrimaryKey(table);
6138
+ const pkValue = row[pkName];
6139
+ if (pkValue !== void 0 && pkValue !== null) {
6140
+ const tracked = ctx.getEntity(table, pkValue);
6141
+ if (tracked) return tracked;
6142
+ }
6143
+ const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
6144
+ if (pkValue !== void 0 && pkValue !== null) {
6145
+ ctx.trackManaged(table, pkValue, entity);
6146
+ } else {
6147
+ ctx.trackNew(table, entity);
6148
+ }
6149
+ return entity;
6150
+ };
6151
+
6054
6152
  // src/orm/execute.ts
6055
6153
  var flattenResults = (results) => {
6056
6154
  const rows = [];
@@ -6140,14 +6238,416 @@ var loadLazyRelationsForTable = async (ctx, table, lazyRelations, lazyRelationOp
6140
6238
  }
6141
6239
  };
6142
6240
 
6143
- // src/query-builder/query-resolution.ts
6144
- function resolveSelectQuery(query) {
6145
- const candidate = query;
6146
- return typeof candidate.getAST === "function" && candidate.getAST ? candidate.getAST() : query;
6147
- }
6241
+ // src/query-builder/query-resolution.ts
6242
+ function resolveSelectQuery(query) {
6243
+ const candidate = query;
6244
+ return typeof candidate.getAST === "function" && candidate.getAST ? candidate.getAST() : query;
6245
+ }
6246
+
6247
+ // src/query-builder/select/select-operations.ts
6248
+ function applyOrderBy(context, predicateFacet, term, directionOrOptions) {
6249
+ const options = typeof directionOrOptions === "string" ? { direction: directionOrOptions } : directionOrOptions;
6250
+ const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
6251
+ return predicateFacet.orderBy(context, term, dir, options.nulls, options.collation);
6252
+ }
6253
+ async function executeCount(context, env, session) {
6254
+ const unpagedAst = {
6255
+ ...context.state.ast,
6256
+ orderBy: void 0,
6257
+ limit: void 0,
6258
+ offset: void 0
6259
+ };
6260
+ const nextState = new SelectQueryState(env.table, unpagedAst);
6261
+ const nextContext = {
6262
+ ...context,
6263
+ state: nextState
6264
+ };
6265
+ const subAst = nextContext.hydration.applyToAst(nextState.ast);
6266
+ const countQuery = {
6267
+ type: "SelectQuery",
6268
+ from: derivedTable(subAst, "__metal_count"),
6269
+ columns: [{ type: "Function", name: "COUNT", args: [], alias: "total" }],
6270
+ joins: []
6271
+ };
6272
+ const execCtx = session.getExecutionContext();
6273
+ const compiled = execCtx.dialect.compileSelect(countQuery);
6274
+ const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
6275
+ const value = results[0]?.values?.[0]?.[0];
6276
+ if (typeof value === "number") return value;
6277
+ if (typeof value === "bigint") return Number(value);
6278
+ if (typeof value === "string") return Number(value);
6279
+ return value === null || value === void 0 ? 0 : Number(value);
6280
+ }
6281
+ async function executePagedQuery(builder, session, options, countCallback) {
6282
+ const { page, pageSize } = options;
6283
+ if (!Number.isInteger(page) || page < 1) {
6284
+ throw new Error("executePaged: page must be an integer >= 1");
6285
+ }
6286
+ if (!Number.isInteger(pageSize) || pageSize < 1) {
6287
+ throw new Error("executePaged: pageSize must be an integer >= 1");
6288
+ }
6289
+ const offset = (page - 1) * pageSize;
6290
+ const [items, totalItems] = await Promise.all([
6291
+ builder.limit(pageSize).offset(offset).execute(session),
6292
+ countCallback(session)
6293
+ ]);
6294
+ return { items, totalItems };
6295
+ }
6296
+ function buildWhereHasPredicate(env, context, relationFacet, createChildBuilder, relationName, callbackOrOptions, maybeOptions, negate = false) {
6297
+ const relation = env.table.relations[relationName];
6298
+ if (!relation) {
6299
+ throw new Error(`Relation '${relationName}' not found on table '${env.table.name}'`);
6300
+ }
6301
+ const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6302
+ const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6303
+ let subQb = createChildBuilder(relation.target);
6304
+ if (callback) {
6305
+ subQb = callback(subQb);
6306
+ }
6307
+ const subAst = subQb.getAST();
6308
+ const finalSubAst = relationFacet.applyRelationCorrelation(
6309
+ context,
6310
+ relationName,
6311
+ subAst,
6312
+ options?.correlate
6313
+ );
6314
+ return negate ? notExists(finalSubAst) : exists(finalSubAst);
6315
+ }
6316
+
6317
+ // src/query-builder/select/from-facet.ts
6318
+ var SelectFromFacet = class {
6319
+ /**
6320
+ * Creates a new SelectFromFacet instance
6321
+ * @param env - Query builder environment
6322
+ * @param createAstService - Function to create AST service
6323
+ */
6324
+ constructor(env, createAstService) {
6325
+ this.env = env;
6326
+ this.createAstService = createAstService;
6327
+ }
6328
+ /**
6329
+ * Applies an alias to the FROM table
6330
+ * @param context - Current query context
6331
+ * @param alias - Alias to apply
6332
+ * @returns Updated query context with aliased FROM
6333
+ */
6334
+ as(context, alias) {
6335
+ const from = context.state.ast.from;
6336
+ if (from.type !== "Table") {
6337
+ throw new Error("Cannot alias non-table FROM sources");
6338
+ }
6339
+ const nextFrom = { ...from, alias };
6340
+ const astService = this.createAstService(context.state);
6341
+ const nextState = astService.withFrom(nextFrom);
6342
+ return { state: nextState, hydration: context.hydration };
6343
+ }
6344
+ /**
6345
+ * Sets the FROM clause to a subquery
6346
+ * @param context - Current query context
6347
+ * @param subAst - Subquery AST
6348
+ * @param alias - Alias for the subquery
6349
+ * @param columnAliases - Optional column aliases
6350
+ * @returns Updated query context with subquery FROM
6351
+ */
6352
+ fromSubquery(context, subAst, alias, columnAliases) {
6353
+ const fromNode = derivedTable(subAst, alias, columnAliases);
6354
+ const astService = this.createAstService(context.state);
6355
+ const nextState = astService.withFrom(fromNode);
6356
+ return { state: nextState, hydration: context.hydration };
6357
+ }
6358
+ /**
6359
+ * Sets the FROM clause to a function table
6360
+ * @param context - Current query context
6361
+ * @param name - Function name
6362
+ * @param args - Function arguments
6363
+ * @param alias - Optional alias for the function table
6364
+ * @param options - Optional function table options
6365
+ * @returns Updated query context with function table FROM
6366
+ */
6367
+ fromFunctionTable(context, name, args, alias, options) {
6368
+ const functionTable = fnTable(name, args, alias, options);
6369
+ const astService = this.createAstService(context.state);
6370
+ const nextState = astService.withFrom(functionTable);
6371
+ return { state: nextState, hydration: context.hydration };
6372
+ }
6373
+ };
6374
+
6375
+ // src/query-builder/select/join-facet.ts
6376
+ var SelectJoinFacet = class {
6377
+ constructor(env, createAstService) {
6378
+ this.env = env;
6379
+ this.createAstService = createAstService;
6380
+ }
6381
+ applyJoin(context, table, condition, kind) {
6382
+ const joinNode = createJoinNode(kind, { type: "Table", name: table.name, schema: table.schema }, condition);
6383
+ const astService = this.createAstService(context.state);
6384
+ const nextState = astService.withJoin(joinNode);
6385
+ return { state: nextState, hydration: context.hydration };
6386
+ }
6387
+ joinSubquery(context, subAst, alias, condition, joinKind, columnAliases) {
6388
+ const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
6389
+ const astService = this.createAstService(context.state);
6390
+ const nextState = astService.withJoin(joinNode);
6391
+ return { state: nextState, hydration: context.hydration };
6392
+ }
6393
+ joinFunctionTable(context, name, args, alias, condition, joinKind, options) {
6394
+ const functionTable = fnTable(name, args, alias, options);
6395
+ const joinNode = createJoinNode(joinKind, functionTable, condition);
6396
+ const astService = this.createAstService(context.state);
6397
+ const nextState = astService.withJoin(joinNode);
6398
+ return { state: nextState, hydration: context.hydration };
6399
+ }
6400
+ };
6401
+
6402
+ // src/query-builder/select/projection-facet.ts
6403
+ var SelectProjectionFacet = class {
6404
+ /**
6405
+ * Creates a new SelectProjectionFacet instance
6406
+ * @param columnSelector - Column selector dependency
6407
+ */
6408
+ constructor(columnSelector) {
6409
+ this.columnSelector = columnSelector;
6410
+ }
6411
+ /**
6412
+ * Selects columns for the query
6413
+ * @param context - Current query context
6414
+ * @param columns - Columns to select
6415
+ * @returns Updated query context with selected columns
6416
+ */
6417
+ select(context, columns) {
6418
+ return this.columnSelector.select(context, columns);
6419
+ }
6420
+ /**
6421
+ * Selects raw column expressions
6422
+ * @param context - Current query context
6423
+ * @param cols - Raw column expressions
6424
+ * @returns Updated query context with raw column selections
6425
+ */
6426
+ selectRaw(context, cols) {
6427
+ return this.columnSelector.selectRaw(context, cols);
6428
+ }
6429
+ /**
6430
+ * Selects a subquery as a column
6431
+ * @param context - Current query context
6432
+ * @param alias - Alias for the subquery
6433
+ * @param query - Subquery to select
6434
+ * @returns Updated query context with subquery selection
6435
+ */
6436
+ selectSubquery(context, alias, query) {
6437
+ return this.columnSelector.selectSubquery(context, alias, query);
6438
+ }
6439
+ /**
6440
+ * Adds DISTINCT clause to the query
6441
+ * @param context - Current query context
6442
+ * @param cols - Columns to make distinct
6443
+ * @returns Updated query context with DISTINCT clause
6444
+ */
6445
+ distinct(context, cols) {
6446
+ return this.columnSelector.distinct(context, cols);
6447
+ }
6448
+ };
6449
+
6450
+ // src/query-builder/select/predicate-facet.ts
6451
+ var SelectPredicateFacet = class {
6452
+ /**
6453
+ * Creates a new SelectPredicateFacet instance
6454
+ * @param env - Query builder environment
6455
+ * @param createAstService - Function to create AST service
6456
+ */
6457
+ constructor(env, createAstService) {
6458
+ this.env = env;
6459
+ this.createAstService = createAstService;
6460
+ }
6461
+ /**
6462
+ * Adds a WHERE condition to the query
6463
+ * @param context - Current query context
6464
+ * @param expr - WHERE expression
6465
+ * @returns Updated query context with WHERE condition
6466
+ */
6467
+ where(context, expr) {
6468
+ const astService = this.createAstService(context.state);
6469
+ const nextState = astService.withWhere(expr);
6470
+ return { state: nextState, hydration: context.hydration };
6471
+ }
6472
+ /**
6473
+ * Adds a GROUP BY clause to the query
6474
+ * @param context - Current query context
6475
+ * @param term - Column or ordering term to group by
6476
+ * @returns Updated query context with GROUP BY clause
6477
+ */
6478
+ groupBy(context, term) {
6479
+ const astService = this.createAstService(context.state);
6480
+ const nextState = astService.withGroupBy(term);
6481
+ return { state: nextState, hydration: context.hydration };
6482
+ }
6483
+ /**
6484
+ * Adds a HAVING condition to the query
6485
+ * @param context - Current query context
6486
+ * @param expr - HAVING expression
6487
+ * @returns Updated query context with HAVING condition
6488
+ */
6489
+ having(context, expr) {
6490
+ const astService = this.createAstService(context.state);
6491
+ const nextState = astService.withHaving(expr);
6492
+ return { state: nextState, hydration: context.hydration };
6493
+ }
6494
+ /**
6495
+ * Adds an ORDER BY clause to the query
6496
+ * @param context - Current query context
6497
+ * @param term - Column or ordering term to order by
6498
+ * @param direction - Order direction
6499
+ * @param nulls - Nulls ordering
6500
+ * @param collation - Collation
6501
+ * @returns Updated query context with ORDER BY clause
6502
+ */
6503
+ orderBy(context, term, direction, nulls, collation) {
6504
+ const astService = this.createAstService(context.state);
6505
+ const nextState = astService.withOrderBy(term, direction, nulls, collation);
6506
+ return { state: nextState, hydration: context.hydration };
6507
+ }
6508
+ /**
6509
+ * Adds a LIMIT clause to the query
6510
+ * @param context - Current query context
6511
+ * @param n - Maximum number of rows
6512
+ * @returns Updated query context with LIMIT clause
6513
+ */
6514
+ limit(context, n) {
6515
+ const astService = this.createAstService(context.state);
6516
+ const nextState = astService.withLimit(n);
6517
+ return { state: nextState, hydration: context.hydration };
6518
+ }
6519
+ /**
6520
+ * Adds an OFFSET clause to the query
6521
+ * @param context - Current query context
6522
+ * @param n - Number of rows to skip
6523
+ * @returns Updated query context with OFFSET clause
6524
+ */
6525
+ offset(context, n) {
6526
+ const astService = this.createAstService(context.state);
6527
+ const nextState = astService.withOffset(n);
6528
+ return { state: nextState, hydration: context.hydration };
6529
+ }
6530
+ };
6531
+
6532
+ // src/query-builder/select/cte-facet.ts
6533
+ var SelectCTEFacet = class {
6534
+ /**
6535
+ * Creates a new SelectCTEFacet instance
6536
+ * @param env - Query builder environment
6537
+ * @param createAstService - Function to create AST service
6538
+ */
6539
+ constructor(env, createAstService) {
6540
+ this.env = env;
6541
+ this.createAstService = createAstService;
6542
+ }
6543
+ /**
6544
+ * Adds a Common Table Expression to the query
6545
+ * @param context - Current query context
6546
+ * @param name - CTE name
6547
+ * @param subAst - CTE query AST
6548
+ * @param columns - Optional column names
6549
+ * @param recursive - Whether the CTE is recursive
6550
+ * @returns Updated query context with CTE
6551
+ */
6552
+ withCTE(context, name, subAst, columns, recursive) {
6553
+ const astService = this.createAstService(context.state);
6554
+ const nextState = astService.withCte(name, subAst, columns, recursive);
6555
+ return { state: nextState, hydration: context.hydration };
6556
+ }
6557
+ };
6558
+
6559
+ // src/query-builder/select/setop-facet.ts
6560
+ var SelectSetOpFacet = class {
6561
+ /**
6562
+ * Creates a new SelectSetOpFacet instance
6563
+ * @param env - Query builder environment
6564
+ * @param createAstService - Function to create AST service
6565
+ */
6566
+ constructor(env, createAstService) {
6567
+ this.env = env;
6568
+ this.createAstService = createAstService;
6569
+ }
6570
+ /**
6571
+ * Applies a set operation to the query
6572
+ * @param context - Current query context
6573
+ * @param operator - Set operation kind
6574
+ * @param subAst - Subquery AST to combine
6575
+ * @returns Updated query context with set operation
6576
+ */
6577
+ applySetOperation(context, operator, subAst) {
6578
+ const astService = this.createAstService(context.state);
6579
+ const nextState = astService.withSetOperation(operator, subAst);
6580
+ return { state: nextState, hydration: context.hydration };
6581
+ }
6582
+ };
6583
+
6584
+ // src/query-builder/select/relation-facet.ts
6585
+ var SelectRelationFacet = class {
6586
+ /**
6587
+ * Creates a new SelectRelationFacet instance
6588
+ * @param relationManager - Relation manager dependency
6589
+ */
6590
+ constructor(relationManager) {
6591
+ this.relationManager = relationManager;
6592
+ }
6593
+ /**
6594
+ * Matches records based on a relationship
6595
+ * @param context - Current query context
6596
+ * @param relationName - Name of the relationship
6597
+ * @param predicate - Optional predicate
6598
+ * @returns Updated query context with relation match
6599
+ */
6600
+ match(context, relationName, predicate) {
6601
+ return this.relationManager.match(context, relationName, predicate);
6602
+ }
6603
+ /**
6604
+ * Joins a related table
6605
+ * @param context - Current query context
6606
+ * @param relationName - Name of the relationship
6607
+ * @param joinKind - Type of join
6608
+ * @param extraCondition - Optional additional condition
6609
+ * @returns Updated query context with relation join
6610
+ */
6611
+ joinRelation(context, relationName, joinKind, extraCondition) {
6612
+ return this.relationManager.joinRelation(context, relationName, joinKind, extraCondition);
6613
+ }
6614
+ /**
6615
+ * Includes related data in the query results
6616
+ * @param context - Current query context
6617
+ * @param relationName - Name of the relationship to include
6618
+ * @param options - Optional include options
6619
+ * @returns Updated query context with relation inclusion
6620
+ */
6621
+ include(context, relationName, options) {
6622
+ return this.relationManager.include(context, relationName, options);
6623
+ }
6624
+ /**
6625
+ * Applies correlation for relation-based subqueries
6626
+ * @param context - Current query context
6627
+ * @param relationName - Name of the relationship
6628
+ * @param subAst - Subquery AST
6629
+ * @param extraCorrelate - Optional additional correlation
6630
+ * @returns Modified subquery AST with correlation
6631
+ */
6632
+ applyRelationCorrelation(context, relationName, subAst, extraCorrelate) {
6633
+ return this.relationManager.applyRelationCorrelation(context, relationName, subAst, extraCorrelate);
6634
+ }
6635
+ };
6148
6636
 
6149
6637
  // src/query-builder/select.ts
6150
6638
  var SelectQueryBuilder = class _SelectQueryBuilder {
6639
+ env;
6640
+ context;
6641
+ columnSelector;
6642
+ fromFacet;
6643
+ joinFacet;
6644
+ projectionFacet;
6645
+ predicateFacet;
6646
+ cteFacet;
6647
+ setOpFacet;
6648
+ relationFacet;
6649
+ lazyRelations;
6650
+ lazyRelationOptions;
6151
6651
  /**
6152
6652
  * Creates a new SelectQueryBuilder instance
6153
6653
  * @param table - Table definition to query
@@ -6158,6 +6658,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6158
6658
  constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions) {
6159
6659
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
6160
6660
  this.env = { table, deps };
6661
+ const createAstService = (nextState) => deps.createQueryAstService(table, nextState);
6161
6662
  const initialState = state ?? deps.createState(table);
6162
6663
  const initialHydration = hydration ?? deps.createHydration(table);
6163
6664
  this.context = {
@@ -6167,7 +6668,14 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6167
6668
  this.lazyRelations = new Set(lazyRelations ?? []);
6168
6669
  this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
6169
6670
  this.columnSelector = deps.createColumnSelector(this.env);
6170
- this.relationManager = deps.createRelationManager(this.env);
6671
+ const relationManager = deps.createRelationManager(this.env);
6672
+ this.fromFacet = new SelectFromFacet(this.env, createAstService);
6673
+ this.joinFacet = new SelectJoinFacet(this.env, createAstService);
6674
+ this.projectionFacet = new SelectProjectionFacet(this.columnSelector);
6675
+ this.predicateFacet = new SelectPredicateFacet(this.env, createAstService);
6676
+ this.cteFacet = new SelectCTEFacet(this.env, createAstService);
6677
+ this.setOpFacet = new SelectSetOpFacet(this.env, createAstService);
6678
+ this.relationFacet = new SelectRelationFacet(relationManager);
6171
6679
  }
6172
6680
  /**
6173
6681
  * Creates a new SelectQueryBuilder instance with updated context and lazy relations
@@ -6188,14 +6696,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6188
6696
  /**
6189
6697
  * Applies an alias to the root FROM table.
6190
6698
  * @param alias - Alias to apply
6699
+ * @example
6700
+ * const qb = new SelectQueryBuilder(userTable).as('u');
6191
6701
  */
6192
6702
  as(alias) {
6193
- const from = this.context.state.ast.from;
6194
- if (from.type !== "Table") {
6195
- throw new Error("Cannot alias non-table FROM sources");
6196
- }
6197
- const nextFrom = { ...from, alias };
6198
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(nextFrom));
6703
+ const nextContext = this.fromFacet.as(this.context, alias);
6199
6704
  return this.clone(nextContext);
6200
6705
  }
6201
6706
  /**
@@ -6220,29 +6725,6 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6220
6725
  createChildBuilder(table) {
6221
6726
  return new _SelectQueryBuilder(table, void 0, void 0, this.env.deps);
6222
6727
  }
6223
- /**
6224
- * Applies an AST mutation using the query AST service
6225
- * @param context - Current query context
6226
- * @param mutator - Function that mutates the AST
6227
- * @returns Updated query context
6228
- */
6229
- applyAst(context, mutator) {
6230
- const astService = this.env.deps.createQueryAstService(this.env.table, context.state);
6231
- const nextState = mutator(astService);
6232
- return { state: nextState, hydration: context.hydration };
6233
- }
6234
- /**
6235
- * Applies a join to the query context
6236
- * @param context - Current query context
6237
- * @param table - Table to join
6238
- * @param condition - Join condition
6239
- * @param kind - Join kind
6240
- * @returns Updated query context with join applied
6241
- */
6242
- applyJoin(context, table, condition, kind) {
6243
- const joinNode = createJoinNode(kind, { type: "Table", name: table.name, schema: table.schema }, condition);
6244
- return this.applyAst(context, (service) => service.withJoin(joinNode));
6245
- }
6246
6728
  /**
6247
6729
  * Applies a set operation to the query
6248
6730
  * @param operator - Set operation kind
@@ -6251,12 +6733,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6251
6733
  */
6252
6734
  applySetOperation(operator, query) {
6253
6735
  const subAst = resolveSelectQuery(query);
6254
- return this.applyAst(this.context, (service) => service.withSetOperation(operator, subAst));
6736
+ return this.setOpFacet.applySetOperation(this.context, operator, subAst);
6255
6737
  }
6256
6738
  select(...args) {
6257
6739
  if (args.length === 1 && typeof args[0] === "object" && args[0] !== null && typeof args[0] !== "string") {
6258
6740
  const columns = args[0];
6259
- return this.clone(this.columnSelector.select(this.context, columns));
6741
+ return this.clone(this.projectionFacet.select(this.context, columns));
6260
6742
  }
6261
6743
  const cols = args;
6262
6744
  const selection = {};
@@ -6267,15 +6749,17 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6267
6749
  }
6268
6750
  selection[key] = col2;
6269
6751
  }
6270
- return this.clone(this.columnSelector.select(this.context, selection));
6752
+ return this.clone(this.projectionFacet.select(this.context, selection));
6271
6753
  }
6272
6754
  /**
6273
6755
  * Selects raw column expressions
6274
6756
  * @param cols - Column expressions as strings
6275
6757
  * @returns New query builder instance with raw column selections
6758
+ * @example
6759
+ * qb.selectRaw('COUNT(*) as total', 'UPPER(name) as upper_name');
6276
6760
  */
6277
6761
  selectRaw(...cols) {
6278
- return this.clone(this.columnSelector.selectRaw(this.context, cols));
6762
+ return this.clone(this.projectionFacet.selectRaw(this.context, cols));
6279
6763
  }
6280
6764
  /**
6281
6765
  * Adds a Common Table Expression (CTE) to the query
@@ -6283,10 +6767,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6283
6767
  * @param query - Query builder or query node for the CTE
6284
6768
  * @param columns - Optional column names for the CTE
6285
6769
  * @returns New query builder instance with the CTE
6770
+ * @example
6771
+ * const recentUsers = new SelectQueryBuilder(userTable)
6772
+ * .where(gt(userTable.columns.createdAt, subDays(now(), 30)));
6773
+ * const qb = new SelectQueryBuilder(userTable)
6774
+ * .with('recent_users', recentUsers)
6775
+ * .from('recent_users');
6286
6776
  */
6287
6777
  with(name, query, columns) {
6288
6778
  const subAst = resolveSelectQuery(query);
6289
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, false));
6779
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, false);
6290
6780
  return this.clone(nextContext);
6291
6781
  }
6292
6782
  /**
@@ -6295,10 +6785,23 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6295
6785
  * @param query - Query builder or query node for the CTE
6296
6786
  * @param columns - Optional column names for the CTE
6297
6787
  * @returns New query builder instance with the recursive CTE
6788
+ * @example
6789
+ * // Base case: select root nodes
6790
+ * const baseQuery = new SelectQueryBuilder(orgTable)
6791
+ * .where(eq(orgTable.columns.parentId, 1));
6792
+ * // Recursive case: join with the CTE itself
6793
+ * const recursiveQuery = new SelectQueryBuilder(orgTable)
6794
+ * .join('org_hierarchy', 'oh', eq(orgTable.columns.parentId, col('oh.id')));
6795
+ * // Combine base and recursive parts
6796
+ * const orgHierarchy = baseQuery.union(recursiveQuery);
6797
+ * // Use in main query
6798
+ * const qb = new SelectQueryBuilder(orgTable)
6799
+ * .withRecursive('org_hierarchy', orgHierarchy)
6800
+ * .from('org_hierarchy');
6298
6801
  */
6299
6802
  withRecursive(name, query, columns) {
6300
6803
  const subAst = resolveSelectQuery(query);
6301
- const nextContext = this.applyAst(this.context, (service) => service.withCte(name, subAst, columns, true));
6804
+ const nextContext = this.cteFacet.withCTE(this.context, name, subAst, columns, true);
6302
6805
  return this.clone(nextContext);
6303
6806
  }
6304
6807
  /**
@@ -6307,11 +6810,15 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6307
6810
  * @param alias - Alias for the derived table
6308
6811
  * @param columnAliases - Optional column alias list
6309
6812
  * @returns New query builder instance with updated FROM
6813
+ * @example
6814
+ * const subquery = new SelectQueryBuilder(userTable)
6815
+ * .select('id', 'name')
6816
+ * .where(gt(userTable.columns.score, 100));
6817
+ * qb.fromSubquery(subquery, 'high_scorers', ['userId', 'userName']);
6310
6818
  */
6311
6819
  fromSubquery(subquery, alias, columnAliases) {
6312
6820
  const subAst = resolveSelectQuery(subquery);
6313
- const fromNode = derivedTable(subAst, alias, columnAliases);
6314
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(fromNode));
6821
+ const nextContext = this.fromFacet.fromSubquery(this.context, subAst, alias, columnAliases);
6315
6822
  return this.clone(nextContext);
6316
6823
  }
6317
6824
  /**
@@ -6320,10 +6827,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6320
6827
  * @param args - Optional function arguments
6321
6828
  * @param alias - Optional alias for the function table
6322
6829
  * @param options - Optional function-table metadata (lateral, ordinality, column aliases, schema)
6830
+ * @example
6831
+ * qb.fromFunctionTable(
6832
+ * 'generate_series',
6833
+ * [literal(1), literal(10), literal(1)],
6834
+ * 'series',
6835
+ * { columnAliases: ['value'] }
6836
+ * );
6323
6837
  */
6324
6838
  fromFunctionTable(name, args = [], alias, options) {
6325
- const functionTable = fnTable(name, args, alias, options);
6326
- const nextContext = this.applyAst(this.context, (service) => service.withFrom(functionTable));
6839
+ const nextContext = this.fromFacet.fromFunctionTable(this.context, name, args, alias, options);
6327
6840
  return this.clone(nextContext);
6328
6841
  }
6329
6842
  /**
@@ -6331,10 +6844,16 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6331
6844
  * @param alias - Alias for the subquery column
6332
6845
  * @param sub - Query builder or query node for the subquery
6333
6846
  * @returns New query builder instance with the subquery selection
6847
+ * @example
6848
+ * const postCount = new SelectQueryBuilder(postTable)
6849
+ * .select(count(postTable.columns.id))
6850
+ * .where(eq(postTable.columns.userId, col('u.id')));
6851
+ * qb.select('id', 'name')
6852
+ * .selectSubquery('postCount', postCount);
6334
6853
  */
6335
6854
  selectSubquery(alias, sub2) {
6336
6855
  const query = resolveSelectQuery(sub2);
6337
- return this.clone(this.columnSelector.selectSubquery(this.context, alias, query));
6856
+ return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
6338
6857
  }
6339
6858
  /**
6340
6859
  * Adds a JOIN against a derived table (subquery with alias)
@@ -6344,11 +6863,19 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6344
6863
  * @param joinKind - Join kind (defaults to INNER)
6345
6864
  * @param columnAliases - Optional column alias list for the derived table
6346
6865
  * @returns New query builder instance with the derived-table join
6866
+ * @example
6867
+ * const activeUsers = new SelectQueryBuilder(userTable)
6868
+ * .where(eq(userTable.columns.active, true));
6869
+ * qb.joinSubquery(
6870
+ * activeUsers,
6871
+ * 'au',
6872
+ * eq(col('t.userId'), col('au.id')),
6873
+ * JOIN_KINDS.LEFT
6874
+ * );
6347
6875
  */
6348
6876
  joinSubquery(subquery, alias, condition, joinKind = JOIN_KINDS.INNER, columnAliases) {
6349
6877
  const subAst = resolveSelectQuery(subquery);
6350
- const joinNode = createJoinNode(joinKind, derivedTable(subAst, alias, columnAliases), condition);
6351
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
6878
+ const nextContext = this.joinFacet.joinSubquery(this.context, subAst, alias, condition, joinKind, columnAliases);
6352
6879
  return this.clone(nextContext);
6353
6880
  }
6354
6881
  /**
@@ -6359,11 +6886,18 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6359
6886
  * @param condition - Join condition expression
6360
6887
  * @param joinKind - Kind of join (defaults to INNER)
6361
6888
  * @param options - Optional metadata (lateral, ordinality, column aliases, schema)
6889
+ * @example
6890
+ * qb.joinFunctionTable(
6891
+ * 'generate_series',
6892
+ * [literal(1), literal(10)],
6893
+ * 'gs',
6894
+ * eq(col('t.value'), col('gs.value')),
6895
+ * JOIN_KINDS.INNER,
6896
+ * { columnAliases: ['value'] }
6897
+ * );
6362
6898
  */
6363
6899
  joinFunctionTable(name, args = [], alias, condition, joinKind = JOIN_KINDS.INNER, options) {
6364
- const functionTable = fnTable(name, args, alias, options);
6365
- const joinNode = createJoinNode(joinKind, functionTable, condition);
6366
- const nextContext = this.applyAst(this.context, (service) => service.withJoin(joinNode));
6900
+ const nextContext = this.joinFacet.joinFunctionTable(this.context, name, args, alias, condition, joinKind, options);
6367
6901
  return this.clone(nextContext);
6368
6902
  }
6369
6903
  /**
@@ -6371,9 +6905,14 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6371
6905
  * @param table - Table to join
6372
6906
  * @param condition - Join condition expression
6373
6907
  * @returns New query builder instance with the INNER JOIN
6908
+ * @example
6909
+ * qb.innerJoin(
6910
+ * postTable,
6911
+ * eq(userTable.columns.id, postTable.columns.userId)
6912
+ * );
6374
6913
  */
6375
6914
  innerJoin(table, condition) {
6376
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
6915
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.INNER);
6377
6916
  return this.clone(nextContext);
6378
6917
  }
6379
6918
  /**
@@ -6381,9 +6920,14 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6381
6920
  * @param table - Table to join
6382
6921
  * @param condition - Join condition expression
6383
6922
  * @returns New query builder instance with the LEFT JOIN
6923
+ * @example
6924
+ * qb.leftJoin(
6925
+ * postTable,
6926
+ * eq(userTable.columns.id, postTable.columns.userId)
6927
+ * );
6384
6928
  */
6385
6929
  leftJoin(table, condition) {
6386
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
6930
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.LEFT);
6387
6931
  return this.clone(nextContext);
6388
6932
  }
6389
6933
  /**
@@ -6391,9 +6935,14 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6391
6935
  * @param table - Table to join
6392
6936
  * @param condition - Join condition expression
6393
6937
  * @returns New query builder instance with the RIGHT JOIN
6938
+ * @example
6939
+ * qb.rightJoin(
6940
+ * postTable,
6941
+ * eq(userTable.columns.id, postTable.columns.userId)
6942
+ * );
6394
6943
  */
6395
6944
  rightJoin(table, condition) {
6396
- const nextContext = this.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
6945
+ const nextContext = this.joinFacet.applyJoin(this.context, table, condition, JOIN_KINDS.RIGHT);
6397
6946
  return this.clone(nextContext);
6398
6947
  }
6399
6948
  /**
@@ -6401,9 +6950,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6401
6950
  * @param relationName - Name of the relationship to match
6402
6951
  * @param predicate - Optional predicate expression
6403
6952
  * @returns New query builder instance with the relationship match
6953
+ * @example
6954
+ * qb.match('posts', eq(postTable.columns.published, true));
6404
6955
  */
6405
6956
  match(relationName, predicate) {
6406
- const nextContext = this.relationManager.match(this.context, relationName, predicate);
6957
+ const nextContext = this.relationFacet.match(this.context, relationName, predicate);
6407
6958
  return this.clone(nextContext);
6408
6959
  }
6409
6960
  /**
@@ -6412,9 +6963,13 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6412
6963
  * @param joinKind - Type of join (defaults to INNER)
6413
6964
  * @param extraCondition - Optional additional join condition
6414
6965
  * @returns New query builder instance with the relationship join
6966
+ * @example
6967
+ * qb.joinRelation('posts', JOIN_KINDS.LEFT);
6968
+ * @example
6969
+ * qb.joinRelation('posts', JOIN_KINDS.INNER, eq(postTable.columns.published, true));
6415
6970
  */
6416
6971
  joinRelation(relationName, joinKind = JOIN_KINDS.INNER, extraCondition) {
6417
- const nextContext = this.relationManager.joinRelation(this.context, relationName, joinKind, extraCondition);
6972
+ const nextContext = this.relationFacet.joinRelation(this.context, relationName, joinKind, extraCondition);
6418
6973
  return this.clone(nextContext);
6419
6974
  }
6420
6975
  /**
@@ -6422,9 +6977,18 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6422
6977
  * @param relationName - Name of the relationship to include
6423
6978
  * @param options - Optional include options
6424
6979
  * @returns New query builder instance with the relationship inclusion
6980
+ * @example
6981
+ * qb.include('posts');
6982
+ * @example
6983
+ * qb.include('posts', { columns: ['id', 'title', 'published'] });
6984
+ * @example
6985
+ * qb.include('posts', {
6986
+ * columns: ['id', 'title'],
6987
+ * where: eq(postTable.columns.published, true)
6988
+ * });
6425
6989
  */
6426
6990
  include(relationName, options) {
6427
- const nextContext = this.relationManager.include(this.context, relationName, options);
6991
+ const nextContext = this.relationFacet.include(this.context, relationName, options);
6428
6992
  return this.clone(nextContext);
6429
6993
  }
6430
6994
  /**
@@ -6432,6 +6996,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6432
6996
  * @param relationName - Name of the relation to include lazily
6433
6997
  * @param options - Optional include options for lazy loading
6434
6998
  * @returns New query builder instance with lazy relation inclusion
6999
+ * @example
7000
+ * const qb = new SelectQueryBuilder(userTable).includeLazy('posts');
7001
+ * const users = await qb.execute(session);
7002
+ * // Access posts later - they will be loaded on demand
7003
+ * const posts = await users[0].posts;
6435
7004
  */
6436
7005
  includeLazy(relationName, options) {
6437
7006
  let nextContext = this.context;
@@ -6461,14 +7030,22 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6461
7030
  }
6462
7031
  /**
6463
7032
  * Convenience alias for including only specific columns from a relation.
7033
+ * @example
7034
+ * qb.includePick('posts', ['id', 'title', 'createdAt']);
6464
7035
  */
6465
7036
  includePick(relationName, cols) {
6466
- return this.include(relationName, { columns: cols });
7037
+ const options = { columns: cols };
7038
+ return this.include(relationName, options);
6467
7039
  }
6468
7040
  /**
6469
7041
  * Selects columns for the root table and relations from an array of entries
6470
7042
  * @param config - Configuration array for deep column selection
6471
7043
  * @returns New query builder instance with deep column selections
7044
+ * @example
7045
+ * qb.selectColumnsDeep([
7046
+ * { type: 'root', columns: ['id', 'name'] },
7047
+ * { type: 'relation', relationName: 'posts', columns: ['id', 'title'] }
7048
+ * ]);
6472
7049
  */
6473
7050
  selectColumnsDeep(config) {
6474
7051
  let currBuilder = this;
@@ -6476,7 +7053,8 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6476
7053
  if (entry.type === "root") {
6477
7054
  currBuilder = currBuilder.select(...entry.columns);
6478
7055
  } else {
6479
- currBuilder = currBuilder.include(entry.relationName, { columns: entry.columns });
7056
+ const options = { columns: entry.columns };
7057
+ currBuilder = currBuilder.include(entry.relationName, options);
6480
7058
  }
6481
7059
  }
6482
7060
  return currBuilder;
@@ -6506,61 +7084,41 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6506
7084
  * Executes the query and returns hydrated results
6507
7085
  * @param ctx - ORM session context
6508
7086
  * @returns Promise of entity instances
7087
+ * @example
7088
+ * const users = await qb.select('id', 'name')
7089
+ * .where(eq(userTable.columns.active, true))
7090
+ * .execute(session);
6509
7091
  */
6510
7092
  async execute(ctx) {
6511
7093
  return executeHydrated(ctx, this);
6512
7094
  }
6513
- withAst(ast) {
6514
- const nextState = new SelectQueryState(this.env.table, ast);
6515
- const nextContext = {
6516
- ...this.context,
6517
- state: nextState
6518
- };
6519
- return this.clone(nextContext);
6520
- }
7095
+ /**
7096
+ * Executes a count query for the current builder without LIMIT/OFFSET clauses.
7097
+ *
7098
+ * @example
7099
+ * const total = await qb.count(session);
7100
+ */
6521
7101
  async count(session) {
6522
- const unpagedAst = {
6523
- ...this.context.state.ast,
6524
- orderBy: void 0,
6525
- limit: void 0,
6526
- offset: void 0
6527
- };
6528
- const subAst = this.withAst(unpagedAst).getAST();
6529
- const countQuery = {
6530
- type: "SelectQuery",
6531
- from: derivedTable(subAst, "__metal_count"),
6532
- columns: [{ type: "Function", name: "COUNT", args: [], alias: "total" }],
6533
- joins: []
6534
- };
6535
- const execCtx = session.getExecutionContext();
6536
- const compiled = execCtx.dialect.compileSelect(countQuery);
6537
- const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
6538
- const value = results[0]?.values?.[0]?.[0];
6539
- if (typeof value === "number") return value;
6540
- if (typeof value === "bigint") return Number(value);
6541
- if (typeof value === "string") return Number(value);
6542
- return value === null || value === void 0 ? 0 : Number(value);
7102
+ return executeCount(this.context, this.env, session);
6543
7103
  }
7104
+ /**
7105
+ * Executes the query and returns both the paged items and the total.
7106
+ *
7107
+ * @example
7108
+ * const { items, totalItems } = await qb.executePaged(session, { page: 1, pageSize: 20 });
7109
+ */
6544
7110
  async executePaged(session, options) {
6545
- const { page, pageSize } = options;
6546
- if (!Number.isInteger(page) || page < 1) {
6547
- throw new Error("executePaged: page must be an integer >= 1");
6548
- }
6549
- if (!Number.isInteger(pageSize) || pageSize < 1) {
6550
- throw new Error("executePaged: pageSize must be an integer >= 1");
6551
- }
6552
- const offset = (page - 1) * pageSize;
6553
- const [items, totalItems] = await Promise.all([
6554
- this.limit(pageSize).offset(offset).execute(session),
6555
- this.count(session)
6556
- ]);
6557
- return { items, totalItems };
7111
+ return executePagedQuery(this, session, options, (sess) => this.count(sess));
6558
7112
  }
6559
7113
  /**
6560
7114
  * Executes the query with provided execution and hydration contexts
6561
7115
  * @param execCtx - Execution context
6562
7116
  * @param hydCtx - Hydration context
6563
7117
  * @returns Promise of entity instances
7118
+ * @example
7119
+ * const execCtx = new ExecutionContext(session);
7120
+ * const hydCtx = new HydrationContext();
7121
+ * const users = await qb.executeWithContexts(execCtx, hydCtx);
6564
7122
  */
6565
7123
  async executeWithContexts(execCtx, hydCtx) {
6566
7124
  return executeHydratedWithContexts(execCtx, hydCtx, this);
@@ -6569,27 +7127,41 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6569
7127
  * Adds a WHERE condition to the query
6570
7128
  * @param expr - Expression for the WHERE clause
6571
7129
  * @returns New query builder instance with the WHERE condition
7130
+ * @example
7131
+ * qb.where(eq(userTable.columns.id, 1));
7132
+ * @example
7133
+ * qb.where(and(
7134
+ * eq(userTable.columns.active, true),
7135
+ * gt(userTable.columns.createdAt, subDays(now(), 30))
7136
+ * ));
6572
7137
  */
6573
7138
  where(expr) {
6574
- const nextContext = this.applyAst(this.context, (service) => service.withWhere(expr));
7139
+ const nextContext = this.predicateFacet.where(this.context, expr);
6575
7140
  return this.clone(nextContext);
6576
7141
  }
6577
7142
  /**
6578
7143
  * Adds a GROUP BY clause to the query
6579
7144
  * @param term - Column definition or ordering term to group by
6580
7145
  * @returns New query builder instance with the GROUP BY clause
7146
+ * @example
7147
+ * qb.select('departmentId', count(userTable.columns.id))
7148
+ * .groupBy(userTable.columns.departmentId);
6581
7149
  */
6582
7150
  groupBy(term) {
6583
- const nextContext = this.applyAst(this.context, (service) => service.withGroupBy(term));
7151
+ const nextContext = this.predicateFacet.groupBy(this.context, term);
6584
7152
  return this.clone(nextContext);
6585
7153
  }
6586
7154
  /**
6587
7155
  * Adds a HAVING condition to the query
6588
7156
  * @param expr - Expression for the HAVING clause
6589
7157
  * @returns New query builder instance with the HAVING condition
7158
+ * @example
7159
+ * qb.select('departmentId', count(userTable.columns.id))
7160
+ * .groupBy(userTable.columns.departmentId)
7161
+ * .having(gt(count(userTable.columns.id), 5));
6590
7162
  */
6591
7163
  having(expr) {
6592
- const nextContext = this.applyAst(this.context, (service) => service.withHaving(expr));
7164
+ const nextContext = this.predicateFacet.having(this.context, expr);
6593
7165
  return this.clone(nextContext);
6594
7166
  }
6595
7167
  /**
@@ -6597,46 +7169,62 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6597
7169
  * @param term - Column definition or ordering term to order by
6598
7170
  * @param directionOrOptions - Order direction or options (defaults to ASC)
6599
7171
  * @returns New query builder instance with the ORDER BY clause
7172
+ *
7173
+ * @example
7174
+ * qb.orderBy(userTable.columns.createdAt, 'DESC');
6600
7175
  */
6601
7176
  orderBy(term, directionOrOptions = ORDER_DIRECTIONS.ASC) {
6602
- const options = typeof directionOrOptions === "string" ? { direction: directionOrOptions } : directionOrOptions;
6603
- const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
6604
- const nextContext = this.applyAst(
6605
- this.context,
6606
- (service) => service.withOrderBy(term, dir, options.nulls, options.collation)
6607
- );
7177
+ const nextContext = applyOrderBy(this.context, this.predicateFacet, term, directionOrOptions);
6608
7178
  return this.clone(nextContext);
6609
7179
  }
6610
7180
  /**
6611
7181
  * Adds a DISTINCT clause to the query
6612
7182
  * @param cols - Columns to make distinct
6613
7183
  * @returns New query builder instance with the DISTINCT clause
7184
+ * @example
7185
+ * qb.distinct(userTable.columns.email);
7186
+ * @example
7187
+ * qb.distinct(userTable.columns.firstName, userTable.columns.lastName);
6614
7188
  */
6615
7189
  distinct(...cols) {
6616
- return this.clone(this.columnSelector.distinct(this.context, cols));
7190
+ return this.clone(this.projectionFacet.distinct(this.context, cols));
6617
7191
  }
6618
7192
  /**
6619
7193
  * Adds a LIMIT clause to the query
6620
7194
  * @param n - Maximum number of rows to return
6621
7195
  * @returns New query builder instance with the LIMIT clause
7196
+ * @example
7197
+ * qb.limit(10);
7198
+ * @example
7199
+ * qb.limit(20).offset(40); // Pagination: page 3 with 20 items per page
6622
7200
  */
6623
7201
  limit(n) {
6624
- const nextContext = this.applyAst(this.context, (service) => service.withLimit(n));
7202
+ const nextContext = this.predicateFacet.limit(this.context, n);
6625
7203
  return this.clone(nextContext);
6626
7204
  }
6627
7205
  /**
6628
7206
  * Adds an OFFSET clause to the query
6629
7207
  * @param n - Number of rows to skip
6630
7208
  * @returns New query builder instance with the OFFSET clause
7209
+ * @example
7210
+ * qb.offset(10);
7211
+ * @example
7212
+ * qb.limit(20).offset(40); // Pagination: page 3 with 20 items per page
6631
7213
  */
6632
7214
  offset(n) {
6633
- const nextContext = this.applyAst(this.context, (service) => service.withOffset(n));
7215
+ const nextContext = this.predicateFacet.offset(this.context, n);
6634
7216
  return this.clone(nextContext);
6635
7217
  }
6636
7218
  /**
6637
7219
  * Combines this query with another using UNION
6638
7220
  * @param query - Query to union with
6639
7221
  * @returns New query builder instance with the set operation
7222
+ * @example
7223
+ * const activeUsers = new SelectQueryBuilder(userTable)
7224
+ * .where(eq(userTable.columns.active, true));
7225
+ * const inactiveUsers = new SelectQueryBuilder(userTable)
7226
+ * .where(eq(userTable.columns.active, false));
7227
+ * qb.union(activeUsers).union(inactiveUsers);
6640
7228
  */
6641
7229
  union(query) {
6642
7230
  return this.clone(this.applySetOperation("UNION", query));
@@ -6645,6 +7233,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6645
7233
  * Combines this query with another using UNION ALL
6646
7234
  * @param query - Query to union with
6647
7235
  * @returns New query builder instance with the set operation
7236
+ * @example
7237
+ * const q1 = new SelectQueryBuilder(userTable).where(gt(userTable.columns.score, 80));
7238
+ * const q2 = new SelectQueryBuilder(userTable).where(lt(userTable.columns.score, 20));
7239
+ * qb.unionAll(q1).unionAll(q2);
6648
7240
  */
6649
7241
  unionAll(query) {
6650
7242
  return this.clone(this.applySetOperation("UNION ALL", query));
@@ -6653,6 +7245,12 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6653
7245
  * Combines this query with another using INTERSECT
6654
7246
  * @param query - Query to intersect with
6655
7247
  * @returns New query builder instance with the set operation
7248
+ * @example
7249
+ * const activeUsers = new SelectQueryBuilder(userTable)
7250
+ * .where(eq(userTable.columns.active, true));
7251
+ * const premiumUsers = new SelectQueryBuilder(userTable)
7252
+ * .where(eq(userTable.columns.premium, true));
7253
+ * qb.intersect(activeUsers).intersect(premiumUsers);
6656
7254
  */
6657
7255
  intersect(query) {
6658
7256
  return this.clone(this.applySetOperation("INTERSECT", query));
@@ -6661,6 +7259,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6661
7259
  * Combines this query with another using EXCEPT
6662
7260
  * @param query - Query to subtract
6663
7261
  * @returns New query builder instance with the set operation
7262
+ * @example
7263
+ * const allUsers = new SelectQueryBuilder(userTable);
7264
+ * const inactiveUsers = new SelectQueryBuilder(userTable)
7265
+ * .where(eq(userTable.columns.active, false));
7266
+ * qb.except(allUsers).except(inactiveUsers); // Only active users
6664
7267
  */
6665
7268
  except(query) {
6666
7269
  return this.clone(this.applySetOperation("EXCEPT", query));
@@ -6669,6 +7272,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6669
7272
  * Adds a WHERE EXISTS condition to the query
6670
7273
  * @param subquery - Subquery to check for existence
6671
7274
  * @returns New query builder instance with the WHERE EXISTS condition
7275
+ * @example
7276
+ * const postsQuery = new SelectQueryBuilder(postTable)
7277
+ * .where(eq(postTable.columns.userId, col('u.id')));
7278
+ * qb.whereExists(postsQuery);
6672
7279
  */
6673
7280
  whereExists(subquery, correlate) {
6674
7281
  const subAst = resolveSelectQuery(subquery);
@@ -6679,6 +7286,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6679
7286
  * Adds a WHERE NOT EXISTS condition to the query
6680
7287
  * @param subquery - Subquery to check for non-existence
6681
7288
  * @returns New query builder instance with the WHERE NOT EXISTS condition
7289
+ * @example
7290
+ * const postsQuery = new SelectQueryBuilder(postTable)
7291
+ * .where(eq(postTable.columns.userId, col('u.id')));
7292
+ * qb.whereNotExists(postsQuery); // Users without posts
6682
7293
  */
6683
7294
  whereNotExists(subquery, correlate) {
6684
7295
  const subAst = resolveSelectQuery(subquery);
@@ -6690,47 +7301,54 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6690
7301
  * @param relationName - Name of the relationship to check
6691
7302
  * @param callback - Optional callback to modify the relationship query
6692
7303
  * @returns New query builder instance with the relationship existence check
7304
+ *
7305
+ * @example
7306
+ * qb.whereHas('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
6693
7307
  */
6694
7308
  whereHas(relationName, callbackOrOptions, maybeOptions) {
6695
- const relation = this.env.table.relations[relationName];
6696
- if (!relation) {
6697
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
6698
- }
6699
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6700
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6701
- let subQb = this.createChildBuilder(relation.target);
6702
- if (callback) {
6703
- subQb = callback(subQb);
6704
- }
6705
- const subAst = subQb.getAST();
6706
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
6707
- return this.where(exists(finalSubAst));
7309
+ const predicate = buildWhereHasPredicate(
7310
+ this.env,
7311
+ this.context,
7312
+ this.relationFacet,
7313
+ (table) => this.createChildBuilder(table),
7314
+ relationName,
7315
+ callbackOrOptions,
7316
+ maybeOptions,
7317
+ false
7318
+ );
7319
+ return this.where(predicate);
6708
7320
  }
6709
7321
  /**
6710
7322
  * Adds a WHERE NOT EXISTS condition based on a relationship
6711
7323
  * @param relationName - Name of the relationship to check
6712
7324
  * @param callback - Optional callback to modify the relationship query
6713
7325
  * @returns New query builder instance with the relationship non-existence check
7326
+ *
7327
+ * @example
7328
+ * qb.whereHasNot('posts', postQb => postQb.where(eq(postTable.columns.published, true)));
6714
7329
  */
6715
7330
  whereHasNot(relationName, callbackOrOptions, maybeOptions) {
6716
- const relation = this.env.table.relations[relationName];
6717
- if (!relation) {
6718
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
6719
- }
6720
- const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : void 0;
6721
- const options = typeof callbackOrOptions === "function" ? maybeOptions : callbackOrOptions;
6722
- let subQb = this.createChildBuilder(relation.target);
6723
- if (callback) {
6724
- subQb = callback(subQb);
6725
- }
6726
- const subAst = subQb.getAST();
6727
- const finalSubAst = this.relationManager.applyRelationCorrelation(this.context, relationName, subAst, options?.correlate);
6728
- return this.where(notExists(finalSubAst));
7331
+ const predicate = buildWhereHasPredicate(
7332
+ this.env,
7333
+ this.context,
7334
+ this.relationFacet,
7335
+ (table) => this.createChildBuilder(table),
7336
+ relationName,
7337
+ callbackOrOptions,
7338
+ maybeOptions,
7339
+ true
7340
+ );
7341
+ return this.where(predicate);
6729
7342
  }
6730
7343
  /**
6731
7344
  * Compiles the query to SQL for a specific dialect
6732
7345
  * @param dialect - Database dialect to compile for
6733
7346
  * @returns Compiled query with SQL and parameters
7347
+ * @example
7348
+ * const compiled = qb.select('id', 'name')
7349
+ * .where(eq(userTable.columns.active, true))
7350
+ * .compile('postgres');
7351
+ * console.log(compiled.sql); // SELECT "id", "name" FROM "users" WHERE "active" = true
6734
7352
  */
6735
7353
  compile(dialect) {
6736
7354
  const resolved = resolveDialectInput(dialect);
@@ -6740,6 +7358,11 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6740
7358
  * Converts the query to SQL string for a specific dialect
6741
7359
  * @param dialect - Database dialect to generate SQL for
6742
7360
  * @returns SQL string representation of the query
7361
+ * @example
7362
+ * const sql = qb.select('id', 'name')
7363
+ * .where(eq(userTable.columns.active, true))
7364
+ * .toSql('postgres');
7365
+ * console.log(sql); // SELECT "id", "name" FROM "users" WHERE "active" = true
6743
7366
  */
6744
7367
  toSql(dialect) {
6745
7368
  return this.compile(dialect).sql;
@@ -6747,6 +7370,9 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6747
7370
  /**
6748
7371
  * Gets the hydration plan for the query
6749
7372
  * @returns Hydration plan or undefined if none exists
7373
+ * @example
7374
+ * const plan = qb.include('posts').getHydrationPlan();
7375
+ * console.log(plan?.relations); // Information about included relations
6750
7376
  */
6751
7377
  getHydrationPlan() {
6752
7378
  return this.context.hydration.getPlan();
@@ -6754,6 +7380,10 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
6754
7380
  /**
6755
7381
  * Gets the Abstract Syntax Tree (AST) representation of the query
6756
7382
  * @returns Query AST with hydration applied
7383
+ * @example
7384
+ * const ast = qb.select('id', 'name').getAST();
7385
+ * console.log(ast.columns); // Array of column nodes
7386
+ * console.log(ast.from); // From clause information
6757
7387
  */
6758
7388
  getAST() {
6759
7389
  return this.context.hydration.applyToAst(this.context.state.ast);
@@ -6857,23 +7487,44 @@ var resolveTableTarget = (target, tableMap) => {
6857
7487
  }
6858
7488
  return table;
6859
7489
  };
7490
+ var toSnakeCase = (value) => {
7491
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
7492
+ };
7493
+ var normalizeEntityName = (value) => {
7494
+ const stripped = value.replace(/Entity$/i, "");
7495
+ const normalized = toSnakeCase(stripped || value);
7496
+ return normalized || "unknown";
7497
+ };
7498
+ var getPivotKeyBaseFromTarget = (target) => {
7499
+ const resolved = unwrapTarget(target);
7500
+ if (isTableDef(resolved)) {
7501
+ return toSnakeCase(resolved.name || "unknown");
7502
+ }
7503
+ const ctor = resolved;
7504
+ return normalizeEntityName(ctor.name || "unknown");
7505
+ };
7506
+ var getPivotKeyBaseFromRoot = (meta) => {
7507
+ return normalizeEntityName(meta.target.name || meta.tableName || "unknown");
7508
+ };
6860
7509
  var buildRelationDefinitions = (meta, tableMap) => {
6861
7510
  const relations = {};
6862
7511
  for (const [name, relation] of Object.entries(meta.relations)) {
6863
7512
  switch (relation.kind) {
6864
7513
  case RelationKinds.HasOne: {
7514
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6865
7515
  relations[name] = hasOne(
6866
7516
  resolveTableTarget(relation.target, tableMap),
6867
- relation.foreignKey,
7517
+ foreignKey,
6868
7518
  relation.localKey,
6869
7519
  relation.cascade
6870
7520
  );
6871
7521
  break;
6872
7522
  }
6873
7523
  case RelationKinds.HasMany: {
7524
+ const foreignKey = relation.foreignKey ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
6874
7525
  relations[name] = hasMany(
6875
7526
  resolveTableTarget(relation.target, tableMap),
6876
- relation.foreignKey,
7527
+ foreignKey,
6877
7528
  relation.localKey,
6878
7529
  relation.cascade
6879
7530
  );
@@ -6889,12 +7540,14 @@ var buildRelationDefinitions = (meta, tableMap) => {
6889
7540
  break;
6890
7541
  }
6891
7542
  case RelationKinds.BelongsToMany: {
7543
+ const pivotForeignKeyToRoot = relation.pivotForeignKeyToRoot ?? `${getPivotKeyBaseFromRoot(meta)}_id`;
7544
+ const pivotForeignKeyToTarget = relation.pivotForeignKeyToTarget ?? `${getPivotKeyBaseFromTarget(relation.target)}_id`;
6892
7545
  relations[name] = belongsToMany(
6893
7546
  resolveTableTarget(relation.target, tableMap),
6894
7547
  resolveTableTarget(relation.pivotTable, tableMap),
6895
7548
  {
6896
- pivotForeignKeyToRoot: relation.pivotForeignKeyToRoot,
6897
- pivotForeignKeyToTarget: relation.pivotForeignKeyToTarget,
7549
+ pivotForeignKeyToRoot,
7550
+ pivotForeignKeyToTarget,
6898
7551
  localKey: relation.localKey,
6899
7552
  targetKey: relation.targetKey,
6900
7553
  pivotPrimaryKey: relation.pivotPrimaryKey,
@@ -6975,6 +7628,8 @@ function esel(entity, ...props) {
6975
7628
 
6976
7629
  // src/query-builder/insert-query-state.ts
6977
7630
  var InsertQueryState = class _InsertQueryState {
7631
+ table;
7632
+ ast;
6978
7633
  /**
6979
7634
  * Creates a new InsertQueryState instance
6980
7635
  * @param table - The table definition for the INSERT query
@@ -7095,6 +7750,8 @@ var InsertQueryState = class _InsertQueryState {
7095
7750
 
7096
7751
  // src/query-builder/insert.ts
7097
7752
  var InsertQueryBuilder = class _InsertQueryBuilder {
7753
+ table;
7754
+ state;
7098
7755
  /**
7099
7756
  * Creates a new InsertQueryBuilder instance
7100
7757
  * @param table - The table definition for the INSERT query
@@ -7194,6 +7851,8 @@ var isUpdateValue = (value) => {
7194
7851
  }
7195
7852
  };
7196
7853
  var UpdateQueryState = class _UpdateQueryState {
7854
+ table;
7855
+ ast;
7197
7856
  /**
7198
7857
  * Creates a new UpdateQueryState instance
7199
7858
  * @param table - Table definition for the update
@@ -7304,6 +7963,8 @@ var UpdateQueryState = class _UpdateQueryState {
7304
7963
 
7305
7964
  // src/query-builder/update.ts
7306
7965
  var UpdateQueryBuilder = class _UpdateQueryBuilder {
7966
+ table;
7967
+ state;
7307
7968
  /**
7308
7969
  * Creates a new UpdateQueryBuilder instance
7309
7970
  * @param table - The table definition for the UPDATE query
@@ -7421,6 +8082,8 @@ var isTableSourceNode = (source) => typeof source.type === "string";
7421
8082
 
7422
8083
  // src/query-builder/delete-query-state.ts
7423
8084
  var DeleteQueryState = class _DeleteQueryState {
8085
+ table;
8086
+ ast;
7424
8087
  /**
7425
8088
  * Creates a new DeleteQueryState instance
7426
8089
  * @param table - The table definition for the DELETE query
@@ -7499,6 +8162,8 @@ var DeleteQueryState = class _DeleteQueryState {
7499
8162
 
7500
8163
  // src/query-builder/delete.ts
7501
8164
  var DeleteQueryBuilder = class _DeleteQueryBuilder {
8165
+ table;
8166
+ state;
7502
8167
  /**
7503
8168
  * Creates a new DeleteQueryBuilder instance
7504
8169
  * @param table - The table definition for the DELETE query
@@ -7605,6 +8270,39 @@ var DeleteQueryBuilder = class _DeleteQueryBuilder {
7605
8270
  };
7606
8271
  var isTableSourceNode2 = (source) => typeof source.type === "string";
7607
8272
 
8273
+ // src/query/target.ts
8274
+ var resolveEntityTarget = (ctor) => {
8275
+ const table = getTableDefFromEntity(ctor);
8276
+ if (!table) {
8277
+ throw new Error(`Entity '${ctor.name}' is not registered with decorators`);
8278
+ }
8279
+ return table;
8280
+ };
8281
+ var resolveTable = (target) => {
8282
+ if (isTableDef(target)) {
8283
+ return target;
8284
+ }
8285
+ return resolveEntityTarget(target);
8286
+ };
8287
+
8288
+ // src/query/index.ts
8289
+ var selectFrom = (target) => {
8290
+ const table = resolveTable(target);
8291
+ return new SelectQueryBuilder(table);
8292
+ };
8293
+ var insertInto = (target) => {
8294
+ const table = resolveTable(target);
8295
+ return new InsertQueryBuilder(table);
8296
+ };
8297
+ var update = (target) => {
8298
+ const table = resolveTable(target);
8299
+ return new UpdateQueryBuilder(table);
8300
+ };
8301
+ var deleteFrom = (target) => {
8302
+ const table = resolveTable(target);
8303
+ return new DeleteQueryBuilder(table);
8304
+ };
8305
+
7608
8306
  // src/core/ddl/sql-writing.ts
7609
8307
  var resolvePrimaryKey = (table) => {
7610
8308
  if (Array.isArray(table.primaryKey) && table.primaryKey.length > 0) {
@@ -7626,7 +8324,8 @@ var renderColumnDefinition = (table, col2, dialect, options = {}) => {
7626
8324
  if (col2.default !== void 0) {
7627
8325
  parts.push(`DEFAULT ${dialect.renderDefault(col2.default, col2)}`);
7628
8326
  }
7629
- if (options.includePrimary && col2.primary) {
8327
+ const autoIncIncludesPrimary = typeof autoInc === "string" && /\bPRIMARY\s+KEY\b/i.test(autoInc);
8328
+ if (options.includePrimary && col2.primary && !autoIncIncludesPrimary) {
7630
8329
  parts.push("PRIMARY KEY");
7631
8330
  }
7632
8331
  if (col2.check) {
@@ -7684,6 +8383,16 @@ var generateSchemaSql = (tables, dialect) => {
7684
8383
  });
7685
8384
  return statements;
7686
8385
  };
8386
+ var generateSchemaSqlFor = (dialect, ...tables) => generateSchemaSql(tables, dialect);
8387
+ var executeSchemaSql = async (executor, tables, dialect) => {
8388
+ const statements = generateSchemaSql(tables, dialect);
8389
+ for (const sql of statements) {
8390
+ await executor.executeSql(sql);
8391
+ }
8392
+ };
8393
+ var executeSchemaSqlFor = async (executor, dialect, ...tables) => {
8394
+ await executeSchemaSql(executor, tables, dialect);
8395
+ };
7687
8396
  var orderTablesByDependencies = (tables) => {
7688
8397
  const map = /* @__PURE__ */ new Map();
7689
8398
  tables.forEach((t) => map.set(t.name, t));
@@ -9590,6 +10299,7 @@ var arrayAppend = (array, value) => fn7("ARRAY_APPEND", [array, value]);
9590
10299
 
9591
10300
  // src/orm/als.ts
9592
10301
  var AsyncLocalStorage = class {
10302
+ store;
9593
10303
  /**
9594
10304
  * Executes a callback function within a context containing the specified store value.
9595
10305
  * The store value is only available during the callback's execution and is automatically
@@ -10086,9 +10796,7 @@ var TypeScriptGenerator = class {
10086
10796
 
10087
10797
  // src/orm/identity-map.ts
10088
10798
  var IdentityMap = class {
10089
- constructor() {
10090
- this.buckets = /* @__PURE__ */ new Map();
10091
- }
10799
+ buckets = /* @__PURE__ */ new Map();
10092
10800
  get bucketsMap() {
10093
10801
  return this.buckets;
10094
10802
  }
@@ -10158,8 +10866,8 @@ var UnitOfWork = class {
10158
10866
  this.executor = executor;
10159
10867
  this.identityMap = identityMap;
10160
10868
  this.hookContext = hookContext;
10161
- this.trackedEntities = /* @__PURE__ */ new Map();
10162
10869
  }
10870
+ trackedEntities = /* @__PURE__ */ new Map();
10163
10871
  /**
10164
10872
  * Gets the identity buckets map.
10165
10873
  */
@@ -10489,12 +11197,12 @@ var UnitOfWork = class {
10489
11197
 
10490
11198
  // src/orm/domain-event-bus.ts
10491
11199
  var DomainEventBus = class {
11200
+ handlers = /* @__PURE__ */ new Map();
10492
11201
  /**
10493
11202
  * Creates a new DomainEventBus instance.
10494
11203
  * @param initialHandlers - Optional initial event handlers
10495
11204
  */
10496
11205
  constructor(initialHandlers) {
10497
- this.handlers = /* @__PURE__ */ new Map();
10498
11206
  if (initialHandlers) {
10499
11207
  for (const key in initialHandlers) {
10500
11208
  const type = key;
@@ -10563,8 +11271,8 @@ var RelationChangeProcessor = class {
10563
11271
  this.unitOfWork = unitOfWork;
10564
11272
  this.dialect = dialect;
10565
11273
  this.executor = executor;
10566
- this.relationChanges = [];
10567
11274
  }
11275
+ relationChanges = [];
10568
11276
  /**
10569
11277
  * Registers a relation change for processing.
10570
11278
  * @param entry - The relation change entry
@@ -10998,25 +11706,24 @@ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
10998
11706
 
10999
11707
  // src/orm/orm-session.ts
11000
11708
  var OrmSession = class {
11709
+ /** The ORM instance */
11710
+ orm;
11711
+ /** The database executor */
11712
+ executor;
11713
+ /** The identity map for tracking entity instances */
11714
+ identityMap;
11715
+ /** The unit of work for tracking entity changes */
11716
+ unitOfWork;
11717
+ /** The domain event bus */
11718
+ domainEvents;
11719
+ /** The relation change processor */
11720
+ relationChanges;
11721
+ interceptors;
11001
11722
  /**
11002
11723
  * Creates a new OrmSession instance.
11003
11724
  * @param opts - Session options
11004
11725
  */
11005
11726
  constructor(opts) {
11006
- /**
11007
- * Registers a relation change.
11008
- * @param root - The root entity
11009
- * @param relationKey - The relation key
11010
- * @param rootTable - The root table definition
11011
- * @param relationName - The relation name
11012
- * @param relation - The relation definition
11013
- * @param change - The relation change
11014
- */
11015
- this.registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
11016
- this.relationChanges.registerChange(
11017
- buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
11018
- );
11019
- };
11020
11727
  this.orm = opts.orm;
11021
11728
  this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
11022
11729
  this.interceptors = [...opts.interceptors ?? []];
@@ -11105,6 +11812,20 @@ var OrmSession = class {
11105
11812
  markRemoved(entity) {
11106
11813
  this.unitOfWork.markRemoved(entity);
11107
11814
  }
11815
+ /**
11816
+ * Registers a relation change.
11817
+ * @param root - The root entity
11818
+ * @param relationKey - The relation key
11819
+ * @param rootTable - The root table definition
11820
+ * @param relationName - The relation name
11821
+ * @param relation - The relation definition
11822
+ * @param change - The relation change
11823
+ */
11824
+ registerRelationChange = (root, relationKey, rootTable, relationName, relation, change) => {
11825
+ this.relationChanges.registerChange(
11826
+ buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
11827
+ );
11828
+ };
11108
11829
  /**
11109
11830
  * Gets all tracked entities for a specific table.
11110
11831
  * @param table - The table definition
@@ -11310,9 +12031,7 @@ var buildRelationChangeEntry = (root, relationKey, rootTable, relationName, rela
11310
12031
 
11311
12032
  // src/orm/interceptor-pipeline.ts
11312
12033
  var InterceptorPipeline = class {
11313
- constructor() {
11314
- this.interceptors = [];
11315
- }
12034
+ interceptors = [];
11316
12035
  use(interceptor) {
11317
12036
  this.interceptors.push(interceptor);
11318
12037
  }
@@ -11331,6 +12050,13 @@ var InterceptorPipeline = class {
11331
12050
 
11332
12051
  // src/orm/orm.ts
11333
12052
  var Orm = class {
12053
+ /** The database dialect */
12054
+ dialect;
12055
+ /** The interceptors pipeline */
12056
+ interceptors;
12057
+ /** The naming strategy */
12058
+ namingStrategy;
12059
+ executorFactory;
11334
12060
  /**
11335
12061
  * Creates a new ORM instance.
11336
12062
  * @param opts - ORM options
@@ -11387,17 +12113,13 @@ var jsonify = (value) => {
11387
12113
 
11388
12114
  // src/decorators/decorator-metadata.ts
11389
12115
  var METADATA_KEY = "metal-orm:decorators";
11390
- var isStandardDecoratorContext = (value) => {
11391
- return typeof value === "object" && value !== null && "kind" in value;
11392
- };
11393
12116
  var getOrCreateMetadataBag = (context) => {
11394
12117
  const metadata = context.metadata || (context.metadata = {});
11395
- const existing = metadata[METADATA_KEY];
11396
- if (existing) {
11397
- return existing;
12118
+ let bag = metadata[METADATA_KEY];
12119
+ if (!bag) {
12120
+ bag = { columns: [], relations: [] };
12121
+ metadata[METADATA_KEY] = bag;
11398
12122
  }
11399
- const bag = { columns: [], relations: [] };
11400
- metadata[METADATA_KEY] = bag;
11401
12123
  return bag;
11402
12124
  };
11403
12125
  var readMetadataBag = (context) => {
@@ -11412,57 +12134,50 @@ var readMetadataBagFromConstructor = (ctor) => {
11412
12134
  var getDecoratorMetadata = (ctor) => readMetadataBagFromConstructor(ctor);
11413
12135
 
11414
12136
  // src/decorators/entity.ts
11415
- var toSnakeCase = (value) => {
12137
+ var toSnakeCase2 = (value) => {
11416
12138
  return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_]+/gi, "_").replace(/__+/g, "_").replace(/^_|_$/g, "").toLowerCase();
11417
12139
  };
11418
12140
  var deriveTableNameFromConstructor = (ctor) => {
11419
12141
  const fallback = "unknown";
11420
12142
  const rawName = ctor.name || fallback;
11421
12143
  const strippedName = rawName.replace(/Entity$/i, "");
11422
- const normalized = toSnakeCase(strippedName || rawName);
12144
+ const normalized = toSnakeCase2(strippedName || rawName);
11423
12145
  if (!normalized) {
11424
12146
  return fallback;
11425
12147
  }
11426
12148
  return normalized.endsWith("s") ? normalized : `${normalized}s`;
11427
12149
  };
11428
12150
  function Entity(options = {}) {
11429
- const decorator = (value) => {
11430
- const tableName = options.tableName ?? deriveTableNameFromConstructor(value);
11431
- setEntityTableName(value, tableName, options.hooks);
11432
- return value;
11433
- };
11434
- const decoratorWithContext = (value, context) => {
12151
+ return function(value, context) {
11435
12152
  const ctor = value;
11436
- decorator(ctor);
11437
- if (context && isStandardDecoratorContext(context)) {
11438
- const bag = readMetadataBag(context);
11439
- if (bag) {
11440
- const meta = ensureEntityMetadata(ctor);
11441
- for (const entry of bag.columns) {
11442
- if (meta.columns[entry.propertyName]) {
11443
- throw new Error(
11444
- `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11445
- );
11446
- }
11447
- addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
12153
+ const tableName = options.tableName ?? deriveTableNameFromConstructor(ctor);
12154
+ setEntityTableName(ctor, tableName, options.hooks);
12155
+ const bag = readMetadataBag(context);
12156
+ if (bag) {
12157
+ const meta = ensureEntityMetadata(ctor);
12158
+ for (const entry of bag.columns) {
12159
+ if (meta.columns[entry.propertyName]) {
12160
+ throw new Error(
12161
+ `Column '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
12162
+ );
11448
12163
  }
11449
- for (const entry of bag.relations) {
11450
- if (meta.relations[entry.propertyName]) {
11451
- throw new Error(
11452
- `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
11453
- );
11454
- }
11455
- const relationCopy = entry.relation.kind === RelationKinds.BelongsToMany ? {
11456
- ...entry.relation,
11457
- defaultPivotColumns: entry.relation.defaultPivotColumns ? [...entry.relation.defaultPivotColumns] : void 0
11458
- } : { ...entry.relation };
11459
- addRelationMetadata(ctor, entry.propertyName, relationCopy);
12164
+ addColumnMetadata(ctor, entry.propertyName, { ...entry.column });
12165
+ }
12166
+ for (const entry of bag.relations) {
12167
+ if (meta.relations[entry.propertyName]) {
12168
+ throw new Error(
12169
+ `Relation '${entry.propertyName}' is already defined on entity '${ctor.name}'.`
12170
+ );
11460
12171
  }
12172
+ const relationCopy = entry.relation.kind === RelationKinds.BelongsToMany ? {
12173
+ ...entry.relation,
12174
+ defaultPivotColumns: entry.relation.defaultPivotColumns ? [...entry.relation.defaultPivotColumns] : void 0
12175
+ } : { ...entry.relation };
12176
+ addRelationMetadata(ctor, entry.propertyName, relationCopy);
11461
12177
  }
11462
12178
  }
11463
12179
  return ctor;
11464
12180
  };
11465
- return decoratorWithContext;
11466
12181
  }
11467
12182
 
11468
12183
  // src/decorators/column-decorator.ts
@@ -11495,26 +12210,13 @@ var normalizePropertyName = (name) => {
11495
12210
  }
11496
12211
  return name;
11497
12212
  };
11498
- var resolveConstructor = (target) => {
11499
- if (typeof target === "function") {
11500
- return target;
11501
- }
11502
- if (target && typeof target.constructor === "function") {
11503
- return target.constructor;
11504
- }
11505
- return void 0;
11506
- };
11507
- var registerColumn = (ctor, propertyName, column) => {
11508
- const meta = ensureEntityMetadata(ctor);
11509
- if (meta.columns[propertyName]) {
11510
- return;
11511
- }
11512
- addColumnMetadata(ctor, propertyName, column);
11513
- };
11514
12213
  var registerColumnFromContext = (context, column) => {
11515
12214
  if (!context.name) {
11516
12215
  throw new Error("Column decorator requires a property name");
11517
12216
  }
12217
+ if (context.private) {
12218
+ throw new Error("Column decorator does not support private fields");
12219
+ }
11518
12220
  const propertyName = normalizePropertyName(context.name);
11519
12221
  const bag = getOrCreateMetadataBag(context);
11520
12222
  if (!bag.columns.some((entry) => entry.propertyName === propertyName)) {
@@ -11523,19 +12225,9 @@ var registerColumnFromContext = (context, column) => {
11523
12225
  };
11524
12226
  function Column(definition) {
11525
12227
  const normalized = normalizeColumnInput(definition);
11526
- const decorator = (targetOrValue, propertyKeyOrContext) => {
11527
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
11528
- registerColumnFromContext(propertyKeyOrContext, normalized);
11529
- return;
11530
- }
11531
- const propertyName = normalizePropertyName(propertyKeyOrContext);
11532
- const ctor = resolveConstructor(targetOrValue);
11533
- if (!ctor) {
11534
- throw new Error("Unable to resolve constructor when registering column metadata");
11535
- }
11536
- registerColumn(ctor, propertyName, { ...normalized });
12228
+ return function(_value, context) {
12229
+ registerColumnFromContext(context, normalized);
11537
12230
  };
11538
- return decorator;
11539
12231
  }
11540
12232
  function PrimaryKey(definition) {
11541
12233
  const normalized = normalizeColumnInput(definition);
@@ -11550,41 +12242,21 @@ var normalizePropertyName2 = (name) => {
11550
12242
  }
11551
12243
  return name;
11552
12244
  };
11553
- var resolveConstructor2 = (instanceOrCtor) => {
11554
- if (typeof instanceOrCtor === "function") {
11555
- return instanceOrCtor;
11556
- }
11557
- if (instanceOrCtor && typeof instanceOrCtor.constructor === "function") {
11558
- return instanceOrCtor.constructor;
11559
- }
11560
- return void 0;
11561
- };
11562
- var registerRelation = (ctor, propertyName, metadata) => {
11563
- addRelationMetadata(ctor, propertyName, metadata);
11564
- };
11565
12245
  var createFieldDecorator = (metadataFactory) => {
11566
- const decorator = (targetOrValue, propertyKeyOrContext) => {
11567
- if (isStandardDecoratorContext(propertyKeyOrContext)) {
11568
- const ctx = propertyKeyOrContext;
11569
- if (!ctx.name) {
11570
- throw new Error("Relation decorator requires a property name");
11571
- }
11572
- const propertyName2 = normalizePropertyName2(ctx.name);
11573
- const bag = getOrCreateMetadataBag(ctx);
11574
- const relationMetadata = metadataFactory(propertyName2);
11575
- if (!bag.relations.some((entry) => entry.propertyName === propertyName2)) {
11576
- bag.relations.push({ propertyName: propertyName2, relation: relationMetadata });
11577
- }
11578
- return;
12246
+ return function(_value, context) {
12247
+ if (!context.name) {
12248
+ throw new Error("Relation decorator requires a property name");
11579
12249
  }
11580
- const propertyName = normalizePropertyName2(propertyKeyOrContext);
11581
- const ctor = resolveConstructor2(targetOrValue);
11582
- if (!ctor) {
11583
- throw new Error("Unable to resolve constructor when registering relation metadata");
12250
+ if (context.private) {
12251
+ throw new Error("Relation decorator does not support private fields");
12252
+ }
12253
+ const propertyName = normalizePropertyName2(context.name);
12254
+ const bag = getOrCreateMetadataBag(context);
12255
+ const relationMetadata = metadataFactory(propertyName);
12256
+ if (!bag.relations.some((entry) => entry.propertyName === propertyName)) {
12257
+ bag.relations.push({ propertyName, relation: relationMetadata });
11584
12258
  }
11585
- registerRelation(ctor, propertyName, metadataFactory(propertyName));
11586
12259
  };
11587
- return decorator;
11588
12260
  };
11589
12261
  function HasMany(options) {
11590
12262
  return createFieldDecorator((propertyName) => ({
@@ -11611,7 +12283,7 @@ function BelongsTo(options) {
11611
12283
  kind: RelationKinds.BelongsTo,
11612
12284
  propertyKey: propertyName,
11613
12285
  target: options.target,
11614
- foreignKey: options.foreignKey,
12286
+ foreignKey: options.foreignKey ?? `${propertyName}_id`,
11615
12287
  localKey: options.localKey,
11616
12288
  cascade: options.cascade
11617
12289
  }));
@@ -11687,13 +12359,15 @@ var deferred = () => {
11687
12359
  return { promise, resolve, reject };
11688
12360
  };
11689
12361
  var Pool = class {
12362
+ adapter;
12363
+ options;
12364
+ destroyed = false;
12365
+ creating = 0;
12366
+ leased = 0;
12367
+ idle = [];
12368
+ waiters = [];
12369
+ reapTimer = null;
11690
12370
  constructor(adapter, options) {
11691
- this.destroyed = false;
11692
- this.creating = 0;
11693
- this.leased = 0;
11694
- this.idle = [];
11695
- this.waiters = [];
11696
- this.reapTimer = null;
11697
12371
  if (!Number.isFinite(options.max) || options.max <= 0) {
11698
12372
  throw new Error("Pool options.max must be a positive number");
11699
12373
  }
@@ -12247,6 +12921,7 @@ function createPooledExecutorFactory(opts) {
12247
12921
  dayOfWeek,
12248
12922
  defineTable,
12249
12923
  degrees,
12924
+ deleteFrom,
12250
12925
  denseRank,
12251
12926
  diffSchema,
12252
12927
  div,
@@ -12256,6 +12931,8 @@ function createPooledExecutorFactory(opts) {
12256
12931
  esel,
12257
12932
  executeHydrated,
12258
12933
  executeHydratedWithContexts,
12934
+ executeSchemaSql,
12935
+ executeSchemaSqlFor,
12259
12936
  exists,
12260
12937
  exp,
12261
12938
  extract,
@@ -12264,6 +12941,7 @@ function createPooledExecutorFactory(opts) {
12264
12941
  fromUnixTime,
12265
12942
  generateCreateTableSql,
12266
12943
  generateSchemaSql,
12944
+ generateSchemaSqlFor,
12267
12945
  getColumn,
12268
12946
  getDecoratorMetadata,
12269
12947
  getSchemaIntrospector,
@@ -12280,6 +12958,7 @@ function createPooledExecutorFactory(opts) {
12280
12958
  inList,
12281
12959
  inSubquery,
12282
12960
  initcap,
12961
+ insertInto,
12283
12962
  instr,
12284
12963
  introspectSchema,
12285
12964
  isCaseExpressionNode,
@@ -12368,7 +13047,9 @@ function createPooledExecutorFactory(opts) {
12368
13047
  rtrim,
12369
13048
  second,
12370
13049
  sel,
13050
+ selectFrom,
12371
13051
  selectFromEntity,
13052
+ setRelations,
12372
13053
  sha1,
12373
13054
  sha2,
12374
13055
  shiftLeft,
@@ -12390,6 +13071,7 @@ function createPooledExecutorFactory(opts) {
12390
13071
  trunc,
12391
13072
  truncate,
12392
13073
  unixTimestamp,
13074
+ update,
12393
13075
  upper,
12394
13076
  utcNow,
12395
13077
  valueToOperand,