metal-orm 1.0.57 → 1.0.58

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.
package/dist/index.js CHANGED
@@ -3852,20 +3852,21 @@ var RelationProjectionHelper = class {
3852
3852
  var assertNever = (value) => {
3853
3853
  throw new Error(`Unhandled relation type: ${JSON.stringify(value)}`);
3854
3854
  };
3855
- var baseRelationCondition = (root, relation, rootAlias) => {
3855
+ var baseRelationCondition = (root, relation, rootAlias, targetTableName) => {
3856
3856
  const rootTable = rootAlias || root.name;
3857
+ const targetTable = targetTableName ?? relation.target.name;
3857
3858
  const defaultLocalKey = relation.type === RelationKinds.HasMany || relation.type === RelationKinds.HasOne ? findPrimaryKey(root) : findPrimaryKey(relation.target);
3858
3859
  const localKey = relation.localKey || defaultLocalKey;
3859
3860
  switch (relation.type) {
3860
3861
  case RelationKinds.HasMany:
3861
3862
  case RelationKinds.HasOne:
3862
3863
  return eq(
3863
- { type: "Column", table: relation.target.name, name: relation.foreignKey },
3864
+ { type: "Column", table: targetTable, name: relation.foreignKey },
3864
3865
  { type: "Column", table: rootTable, name: localKey }
3865
3866
  );
3866
3867
  case RelationKinds.BelongsTo:
3867
3868
  return eq(
3868
- { type: "Column", table: relation.target.name, name: localKey },
3869
+ { type: "Column", table: targetTable, name: localKey },
3869
3870
  { type: "Column", table: rootTable, name: relation.foreignKey }
3870
3871
  );
3871
3872
  case RelationKinds.BelongsToMany:
@@ -3874,7 +3875,7 @@ var baseRelationCondition = (root, relation, rootAlias) => {
3874
3875
  return assertNever(relation);
3875
3876
  }
3876
3877
  };
3877
- var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias) => {
3878
+ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, rootAlias, targetTable, targetTableName) => {
3878
3879
  const rootKey = relation.localKey || findPrimaryKey(root);
3879
3880
  const targetKey = relation.targetKey || findPrimaryKey(relation.target);
3880
3881
  const rootTable = rootAlias || root.name;
@@ -3887,8 +3888,14 @@ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, ro
3887
3888
  { type: "Table", name: relation.pivotTable.name, schema: relation.pivotTable.schema },
3888
3889
  pivotCondition
3889
3890
  );
3891
+ const targetSource = targetTable ?? {
3892
+ type: "Table",
3893
+ name: relation.target.name,
3894
+ schema: relation.target.schema
3895
+ };
3896
+ const effectiveTargetName = targetTableName ?? relation.target.name;
3890
3897
  let targetCondition = eq(
3891
- { type: "Column", table: relation.target.name, name: targetKey },
3898
+ { type: "Column", table: effectiveTargetName, name: targetKey },
3892
3899
  { type: "Column", table: relation.pivotTable.name, name: relation.pivotForeignKeyToTarget }
3893
3900
  );
3894
3901
  if (extra) {
@@ -3896,24 +3903,25 @@ var buildBelongsToManyJoins = (root, relationName, relation, joinKind, extra, ro
3896
3903
  }
3897
3904
  const targetJoin = createJoinNode(
3898
3905
  joinKind,
3899
- { type: "Table", name: relation.target.name, schema: relation.target.schema },
3906
+ targetSource,
3900
3907
  targetCondition,
3901
3908
  relationName
3902
3909
  );
3903
3910
  return [pivotJoin, targetJoin];
3904
3911
  };
3905
- var buildRelationJoinCondition = (root, relation, extra, rootAlias) => {
3906
- const base = baseRelationCondition(root, relation, rootAlias);
3912
+ var buildRelationJoinCondition = (root, relation, extra, rootAlias, targetTableName) => {
3913
+ const base = baseRelationCondition(root, relation, rootAlias, targetTableName);
3907
3914
  return extra ? and(base, extra) : base;
3908
3915
  };
3909
- var buildRelationCorrelation = (root, relation, rootAlias) => {
3910
- return baseRelationCondition(root, relation, rootAlias);
3916
+ var buildRelationCorrelation = (root, relation, rootAlias, targetTableName) => {
3917
+ return baseRelationCondition(root, relation, rootAlias, targetTableName);
3911
3918
  };
3912
3919
 
3913
3920
  // src/core/ast/join-metadata.ts
3914
3921
  var getJoinRelationName = (join) => join.meta?.relationName;
3915
3922
 
3916
3923
  // src/query-builder/relation-service.ts
3924
+ var hasRelationForeignKey = (relation) => relation.type !== RelationKinds.BelongsToMany;
3917
3925
  var RelationService = class {
3918
3926
  /**
3919
3927
  * Creates a new RelationService instance
@@ -3938,8 +3946,8 @@ var RelationService = class {
3938
3946
  * @param extraCondition - Additional join condition
3939
3947
  * @returns Relation result with updated state and hydration
3940
3948
  */
3941
- joinRelation(relationName, joinKind, extraCondition) {
3942
- const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition);
3949
+ joinRelation(relationName, joinKind, extraCondition, tableSource) {
3950
+ const nextState = this.withJoin(this.state, relationName, joinKind, extraCondition, tableSource);
3943
3951
  return { state: nextState, hydration: this.hydration };
3944
3952
  }
3945
3953
  /**
@@ -3968,14 +3976,58 @@ var RelationService = class {
3968
3976
  const relation = this.getRelation(relationName);
3969
3977
  const aliasPrefix = options?.aliasPrefix ?? relationName;
3970
3978
  const alreadyJoined = state.ast.joins.some((j) => getJoinRelationName(j) === relationName);
3979
+ const { selfFilters, crossFilters } = this.splitFilterExpressions(
3980
+ options?.filter,
3981
+ /* @__PURE__ */ new Set([relation.target.name])
3982
+ );
3983
+ const canUseCte = !alreadyJoined && selfFilters.length > 0;
3984
+ const joinFilters = [...crossFilters];
3985
+ if (!canUseCte) {
3986
+ joinFilters.push(...selfFilters);
3987
+ }
3988
+ const joinCondition = this.combineWithAnd(joinFilters);
3989
+ let tableSourceOverride;
3990
+ if (canUseCte) {
3991
+ const cteInfo = this.createFilteredRelationCte(state, relationName, relation, selfFilters);
3992
+ state = cteInfo.state;
3993
+ tableSourceOverride = cteInfo.table;
3994
+ }
3971
3995
  if (!alreadyJoined) {
3972
- const joined = this.joinRelation(relationName, options?.joinKind ?? JOIN_KINDS.LEFT, options?.filter);
3973
- state = joined.state;
3996
+ state = this.withJoin(
3997
+ state,
3998
+ relationName,
3999
+ options?.joinKind ?? JOIN_KINDS.LEFT,
4000
+ joinCondition,
4001
+ tableSourceOverride
4002
+ );
3974
4003
  }
3975
4004
  const projectionResult = this.projectionHelper.ensureBaseProjection(state, hydration);
3976
4005
  state = projectionResult.state;
3977
4006
  hydration = projectionResult.hydration;
3978
- const targetColumns = options?.columns?.length ? options.columns : Object.keys(relation.target.columns);
4007
+ if (hasRelationForeignKey(relation)) {
4008
+ const fkColumn = this.table.columns[relation.foreignKey];
4009
+ if (fkColumn) {
4010
+ const hasForeignKeySelected = state.ast.columns.some((col2) => {
4011
+ if (col2.type !== "Column") return false;
4012
+ const node = col2;
4013
+ const alias = node.alias ?? node.name;
4014
+ return alias === relation.foreignKey;
4015
+ });
4016
+ if (!hasForeignKeySelected) {
4017
+ const fkSelectionResult = this.selectColumns(state, hydration, {
4018
+ [relation.foreignKey]: fkColumn
4019
+ });
4020
+ state = fkSelectionResult.state;
4021
+ hydration = fkSelectionResult.hydration;
4022
+ }
4023
+ }
4024
+ }
4025
+ const requestedColumns = options?.columns?.length ? [...options.columns] : Object.keys(relation.target.columns);
4026
+ const targetPrimaryKey = findPrimaryKey(relation.target);
4027
+ if (!requestedColumns.includes(targetPrimaryKey)) {
4028
+ requestedColumns.push(targetPrimaryKey);
4029
+ }
4030
+ const targetColumns = requestedColumns;
3979
4031
  const buildTypedSelection = (columns, prefix, keys, missingMsg) => {
3980
4032
  return keys.reduce((acc, key) => {
3981
4033
  const def = columns[key];
@@ -4059,27 +4111,42 @@ var RelationService = class {
4059
4111
  * @param extraCondition - Additional join condition
4060
4112
  * @returns Updated query state with join
4061
4113
  */
4062
- withJoin(state, relationName, joinKind, extraCondition) {
4114
+ withJoin(state, relationName, joinKind, extraCondition, tableSource) {
4063
4115
  const relation = this.getRelation(relationName);
4064
4116
  const rootAlias = state.ast.from.type === "Table" ? state.ast.from.alias : void 0;
4065
4117
  if (relation.type === RelationKinds.BelongsToMany) {
4118
+ const targetTableSource = tableSource ?? {
4119
+ type: "Table",
4120
+ name: relation.target.name,
4121
+ schema: relation.target.schema
4122
+ };
4123
+ const targetName2 = this.resolveTargetTableName(targetTableSource, relation);
4066
4124
  const joins = buildBelongsToManyJoins(
4067
4125
  this.table,
4068
4126
  relationName,
4069
4127
  relation,
4070
4128
  joinKind,
4071
4129
  extraCondition,
4072
- rootAlias
4130
+ rootAlias,
4131
+ targetTableSource,
4132
+ targetName2
4073
4133
  );
4074
4134
  return joins.reduce((current, join) => this.astService(current).withJoin(join), state);
4075
4135
  }
4076
- const condition = buildRelationJoinCondition(this.table, relation, extraCondition, rootAlias);
4077
- const joinNode = createJoinNode(
4078
- joinKind,
4079
- { type: "Table", name: relation.target.name, schema: relation.target.schema },
4080
- condition,
4081
- relationName
4136
+ const targetTable = tableSource ?? {
4137
+ type: "Table",
4138
+ name: relation.target.name,
4139
+ schema: relation.target.schema
4140
+ };
4141
+ const targetName = this.resolveTargetTableName(targetTable, relation);
4142
+ const condition = buildRelationJoinCondition(
4143
+ this.table,
4144
+ relation,
4145
+ extraCondition,
4146
+ rootAlias,
4147
+ targetName
4082
4148
  );
4149
+ const joinNode = createJoinNode(joinKind, targetTable, condition, relationName);
4083
4150
  return this.astService(state).withJoin(joinNode);
4084
4151
  }
4085
4152
  /**
@@ -4096,6 +4163,198 @@ var RelationService = class {
4096
4163
  hydration: hydration.onColumnsSelected(nextState, addedColumns)
4097
4164
  };
4098
4165
  }
4166
+ combineWithAnd(expressions) {
4167
+ if (expressions.length === 0) return void 0;
4168
+ if (expressions.length === 1) return expressions[0];
4169
+ return {
4170
+ type: "LogicalExpression",
4171
+ operator: "AND",
4172
+ operands: expressions
4173
+ };
4174
+ }
4175
+ splitFilterExpressions(filter, allowedTables) {
4176
+ const terms = this.flattenAnd(filter);
4177
+ const selfFilters = [];
4178
+ const crossFilters = [];
4179
+ for (const term of terms) {
4180
+ if (this.isExpressionSelfContained(term, allowedTables)) {
4181
+ selfFilters.push(term);
4182
+ } else {
4183
+ crossFilters.push(term);
4184
+ }
4185
+ }
4186
+ return { selfFilters, crossFilters };
4187
+ }
4188
+ flattenAnd(node) {
4189
+ if (!node) return [];
4190
+ if (node.type === "LogicalExpression" && node.operator === "AND") {
4191
+ return node.operands.flatMap((operand) => this.flattenAnd(operand));
4192
+ }
4193
+ return [node];
4194
+ }
4195
+ isExpressionSelfContained(expr, allowedTables) {
4196
+ const collector = this.collectReferencedTables(expr);
4197
+ if (collector.hasSubquery) return false;
4198
+ if (collector.tables.size === 0) return true;
4199
+ for (const table of collector.tables) {
4200
+ if (!allowedTables.has(table)) {
4201
+ return false;
4202
+ }
4203
+ }
4204
+ return true;
4205
+ }
4206
+ collectReferencedTables(expr) {
4207
+ const collector = {
4208
+ tables: /* @__PURE__ */ new Set(),
4209
+ hasSubquery: false
4210
+ };
4211
+ this.collectFromExpression(expr, collector);
4212
+ return collector;
4213
+ }
4214
+ collectFromExpression(expr, collector) {
4215
+ switch (expr.type) {
4216
+ case "BinaryExpression":
4217
+ this.collectFromOperand(expr.left, collector);
4218
+ this.collectFromOperand(expr.right, collector);
4219
+ break;
4220
+ case "LogicalExpression":
4221
+ expr.operands.forEach((operand) => this.collectFromExpression(operand, collector));
4222
+ break;
4223
+ case "NullExpression":
4224
+ this.collectFromOperand(expr.left, collector);
4225
+ break;
4226
+ case "InExpression":
4227
+ this.collectFromOperand(expr.left, collector);
4228
+ if (Array.isArray(expr.right)) {
4229
+ expr.right.forEach((value) => this.collectFromOperand(value, collector));
4230
+ } else {
4231
+ collector.hasSubquery = true;
4232
+ }
4233
+ break;
4234
+ case "ExistsExpression":
4235
+ collector.hasSubquery = true;
4236
+ break;
4237
+ case "BetweenExpression":
4238
+ this.collectFromOperand(expr.left, collector);
4239
+ this.collectFromOperand(expr.lower, collector);
4240
+ this.collectFromOperand(expr.upper, collector);
4241
+ break;
4242
+ case "ArithmeticExpression":
4243
+ case "BitwiseExpression":
4244
+ this.collectFromOperand(expr.left, collector);
4245
+ this.collectFromOperand(expr.right, collector);
4246
+ break;
4247
+ default:
4248
+ break;
4249
+ }
4250
+ }
4251
+ collectFromOperand(node, collector) {
4252
+ switch (node.type) {
4253
+ case "Column":
4254
+ collector.tables.add(node.table);
4255
+ break;
4256
+ case "Function":
4257
+ node.args.forEach((arg) => this.collectFromOperand(arg, collector));
4258
+ if (node.separator) {
4259
+ this.collectFromOperand(node.separator, collector);
4260
+ }
4261
+ if (node.orderBy) {
4262
+ node.orderBy.forEach((order) => this.collectFromOrderingTerm(order.term, collector));
4263
+ }
4264
+ break;
4265
+ case "JsonPath":
4266
+ this.collectFromOperand(node.column, collector);
4267
+ break;
4268
+ case "ScalarSubquery":
4269
+ collector.hasSubquery = true;
4270
+ break;
4271
+ case "CaseExpression":
4272
+ node.conditions.forEach(({ when, then }) => {
4273
+ this.collectFromExpression(when, collector);
4274
+ this.collectFromOperand(then, collector);
4275
+ });
4276
+ if (node.else) {
4277
+ this.collectFromOperand(node.else, collector);
4278
+ }
4279
+ break;
4280
+ case "Cast":
4281
+ this.collectFromOperand(node.expression, collector);
4282
+ break;
4283
+ case "WindowFunction":
4284
+ node.args.forEach((arg) => this.collectFromOperand(arg, collector));
4285
+ node.partitionBy?.forEach((part) => this.collectFromOperand(part, collector));
4286
+ node.orderBy?.forEach((order) => this.collectFromOrderingTerm(order.term, collector));
4287
+ break;
4288
+ case "Collate":
4289
+ this.collectFromOperand(node.expression, collector);
4290
+ break;
4291
+ case "ArithmeticExpression":
4292
+ case "BitwiseExpression":
4293
+ this.collectFromOperand(node.left, collector);
4294
+ this.collectFromOperand(node.right, collector);
4295
+ break;
4296
+ case "Literal":
4297
+ case "AliasRef":
4298
+ break;
4299
+ default:
4300
+ break;
4301
+ }
4302
+ }
4303
+ collectFromOrderingTerm(term, collector) {
4304
+ if (isOperandNode(term)) {
4305
+ this.collectFromOperand(term, collector);
4306
+ return;
4307
+ }
4308
+ this.collectFromExpression(term, collector);
4309
+ }
4310
+ createFilteredRelationCte(state, relationName, relation, filters) {
4311
+ const cteName = this.generateUniqueCteName(state, relationName);
4312
+ const predicate = this.combineWithAnd(filters);
4313
+ if (!predicate) {
4314
+ throw new Error("Unable to build filter CTE without predicates.");
4315
+ }
4316
+ const columns = Object.keys(relation.target.columns).map((name) => ({
4317
+ type: "Column",
4318
+ table: relation.target.name,
4319
+ name
4320
+ }));
4321
+ const cteQuery = {
4322
+ type: "SelectQuery",
4323
+ from: { type: "Table", name: relation.target.name, schema: relation.target.schema },
4324
+ columns,
4325
+ joins: [],
4326
+ where: predicate
4327
+ };
4328
+ const nextState = this.astService(state).withCte(cteName, cteQuery);
4329
+ const tableNode3 = {
4330
+ type: "Table",
4331
+ name: cteName,
4332
+ alias: relation.target.name
4333
+ };
4334
+ return { state: nextState, table: tableNode3 };
4335
+ }
4336
+ generateUniqueCteName(state, relationName) {
4337
+ const existing = new Set((state.ast.ctes ?? []).map((cte) => cte.name));
4338
+ let candidate = `${relationName}__filtered`;
4339
+ let suffix = 1;
4340
+ while (existing.has(candidate)) {
4341
+ candidate = `${relationName}__filtered_${suffix}`;
4342
+ suffix += 1;
4343
+ }
4344
+ return candidate;
4345
+ }
4346
+ resolveTargetTableName(target, relation) {
4347
+ if (target.type === "Table") {
4348
+ return target.alias ?? target.name;
4349
+ }
4350
+ if (target.type === "DerivedTable") {
4351
+ return target.alias;
4352
+ }
4353
+ if (target.type === "FunctionTable") {
4354
+ return target.alias ?? relation.target.name;
4355
+ }
4356
+ return relation.target.name;
4357
+ }
4099
4358
  /**
4100
4359
  * Gets a relation definition by name
4101
4360
  * @param relationName - Name of the relation
@@ -4446,6 +4705,18 @@ var DefaultHasManyCollection = class {
4446
4705
  getItems() {
4447
4706
  return this.items;
4448
4707
  }
4708
+ /**
4709
+ * Array-compatible length for testing frameworks.
4710
+ */
4711
+ get length() {
4712
+ return this.items.length;
4713
+ }
4714
+ /**
4715
+ * Enables iteration over the collection like an array.
4716
+ */
4717
+ [Symbol.iterator]() {
4718
+ return this.items[Symbol.iterator]();
4719
+ }
4449
4720
  /**
4450
4721
  * Adds a new child entity to the collection.
4451
4722
  * @param data - Partial data for the new entity
@@ -4827,6 +5098,18 @@ var DefaultManyToManyCollection = class {
4827
5098
  getItems() {
4828
5099
  return this.items;
4829
5100
  }
5101
+ /**
5102
+ * Array-compatible length for testing frameworks.
5103
+ */
5104
+ get length() {
5105
+ return this.items.length;
5106
+ }
5107
+ /**
5108
+ * Enables iteration over the collection like an array.
5109
+ */
5110
+ [Symbol.iterator]() {
5111
+ return this.items[Symbol.iterator]();
5112
+ }
4830
5113
  /**
4831
5114
  * Attaches an entity to the collection.
4832
5115
  * Registers an 'attach' change in the entity context.
@@ -4934,10 +5217,27 @@ var DefaultManyToManyCollection = class {
4934
5217
  };
4935
5218
 
4936
5219
  // src/orm/lazy-batch.ts
4937
- var selectAllColumns = (table) => Object.entries(table.columns).reduce((acc, [name, def]) => {
4938
- acc[name] = def;
4939
- return acc;
4940
- }, {});
5220
+ var hasColumns = (columns) => Boolean(columns && columns.length > 0);
5221
+ var buildColumnSelection = (table, columns, missingMsg) => {
5222
+ return columns.reduce((acc, column) => {
5223
+ const def = table.columns[column];
5224
+ if (!def) {
5225
+ throw new Error(missingMsg(column));
5226
+ }
5227
+ acc[column] = def;
5228
+ return acc;
5229
+ }, {});
5230
+ };
5231
+ var filterRow = (row, columns) => {
5232
+ const filtered = {};
5233
+ for (const column of columns) {
5234
+ if (column in row) {
5235
+ filtered[column] = row[column];
5236
+ }
5237
+ }
5238
+ return filtered;
5239
+ };
5240
+ var filterRows = (rows, columns) => rows.map((row) => filterRow(row, columns));
4941
5241
  var rowsFromResults = (results) => {
4942
5242
  const rows = [];
4943
5243
  for (const result of results) {
@@ -4969,9 +5269,12 @@ var collectKeysFromRoots = (roots, key) => {
4969
5269
  return collected;
4970
5270
  };
4971
5271
  var buildInListValues = (keys) => Array.from(keys);
4972
- var fetchRowsForKeys = async (ctx, table, column, keys) => {
4973
- const qb = new SelectQueryBuilder(table).select(selectAllColumns(table));
4974
- qb.where(inList(column, buildInListValues(keys)));
5272
+ var fetchRowsForKeys = async (ctx, table, column, keys, selection, filter) => {
5273
+ let qb = new SelectQueryBuilder(table).select(selection);
5274
+ qb = qb.where(inList(column, buildInListValues(keys)));
5275
+ if (filter) {
5276
+ qb = qb.where(filter);
5277
+ }
4975
5278
  return executeQuery(ctx, qb);
4976
5279
  };
4977
5280
  var groupRowsByMany = (rows, keyColumn) => {
@@ -4998,7 +5301,7 @@ var groupRowsByUnique = (rows, keyColumn) => {
4998
5301
  }
4999
5302
  return lookup;
5000
5303
  };
5001
- var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
5304
+ var loadHasManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5002
5305
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5003
5306
  const roots = ctx.getEntitiesForTable(rootTable);
5004
5307
  const keys = collectKeysFromRoots(roots, localKey);
@@ -5007,10 +5310,30 @@ var loadHasManyRelation = async (ctx, rootTable, _relationName, relation) => {
5007
5310
  }
5008
5311
  const fkColumn = relation.target.columns[relation.foreignKey];
5009
5312
  if (!fkColumn) return /* @__PURE__ */ new Map();
5010
- const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys);
5011
- return groupRowsByMany(rows, relation.foreignKey);
5313
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5314
+ const targetPrimaryKey = findPrimaryKey(relation.target);
5315
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5316
+ if (!selectedColumns.includes(targetPrimaryKey)) {
5317
+ selectedColumns.push(targetPrimaryKey);
5318
+ }
5319
+ const queryColumns = new Set(selectedColumns);
5320
+ queryColumns.add(relation.foreignKey);
5321
+ const selection = buildColumnSelection(
5322
+ relation.target,
5323
+ Array.from(queryColumns),
5324
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5325
+ );
5326
+ const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys, selection, options?.filter);
5327
+ const grouped = groupRowsByMany(rows, relation.foreignKey);
5328
+ if (!requestedColumns) return grouped;
5329
+ const visibleColumns = new Set(selectedColumns);
5330
+ const filtered = /* @__PURE__ */ new Map();
5331
+ for (const [key, bucket] of grouped.entries()) {
5332
+ filtered.set(key, filterRows(bucket, visibleColumns));
5333
+ }
5334
+ return filtered;
5012
5335
  };
5013
- var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
5336
+ var loadHasOneRelation = async (ctx, rootTable, relationName, relation, options) => {
5014
5337
  const localKey = relation.localKey || findPrimaryKey(rootTable);
5015
5338
  const roots = ctx.getEntitiesForTable(rootTable);
5016
5339
  const keys = collectKeysFromRoots(roots, localKey);
@@ -5019,22 +5342,98 @@ var loadHasOneRelation = async (ctx, rootTable, _relationName, relation) => {
5019
5342
  }
5020
5343
  const fkColumn = relation.target.columns[relation.foreignKey];
5021
5344
  if (!fkColumn) return /* @__PURE__ */ new Map();
5022
- const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys);
5023
- return groupRowsByUnique(rows, relation.foreignKey);
5345
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5346
+ const targetPrimaryKey = findPrimaryKey(relation.target);
5347
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5348
+ if (!selectedColumns.includes(targetPrimaryKey)) {
5349
+ selectedColumns.push(targetPrimaryKey);
5350
+ }
5351
+ const queryColumns = new Set(selectedColumns);
5352
+ queryColumns.add(relation.foreignKey);
5353
+ const selection = buildColumnSelection(
5354
+ relation.target,
5355
+ Array.from(queryColumns),
5356
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5357
+ );
5358
+ const rows = await fetchRowsForKeys(ctx, relation.target, fkColumn, keys, selection, options?.filter);
5359
+ const grouped = groupRowsByUnique(rows, relation.foreignKey);
5360
+ if (!requestedColumns) return grouped;
5361
+ const visibleColumns = new Set(selectedColumns);
5362
+ const filtered = /* @__PURE__ */ new Map();
5363
+ for (const [key, row] of grouped.entries()) {
5364
+ filtered.set(key, filterRow(row, visibleColumns));
5365
+ }
5366
+ return filtered;
5024
5367
  };
5025
- var loadBelongsToRelation = async (ctx, rootTable, _relationName, relation) => {
5368
+ var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, options) => {
5026
5369
  const roots = ctx.getEntitiesForTable(rootTable);
5027
- const foreignKeys = collectKeysFromRoots(roots, relation.foreignKey);
5370
+ const getForeignKeys = () => collectKeysFromRoots(roots, relation.foreignKey);
5371
+ let foreignKeys = getForeignKeys();
5372
+ if (!foreignKeys.size) {
5373
+ const pkName = findPrimaryKey(rootTable);
5374
+ const pkColumn2 = rootTable.columns[pkName];
5375
+ const fkColumn = rootTable.columns[relation.foreignKey];
5376
+ if (pkColumn2 && fkColumn) {
5377
+ const missingKeys = /* @__PURE__ */ new Set();
5378
+ const entityByPk = /* @__PURE__ */ new Map();
5379
+ for (const tracked of roots) {
5380
+ const entity = tracked.entity;
5381
+ const pkValue = entity[pkName];
5382
+ if (pkValue === void 0 || pkValue === null) continue;
5383
+ const fkValue = entity[relation.foreignKey];
5384
+ if (fkValue === void 0 || fkValue === null) {
5385
+ missingKeys.add(pkValue);
5386
+ entityByPk.set(pkValue, entity);
5387
+ }
5388
+ }
5389
+ if (missingKeys.size) {
5390
+ const selection2 = buildColumnSelection(
5391
+ rootTable,
5392
+ [pkName, relation.foreignKey],
5393
+ (column) => `Column '${column}' not found on table '${rootTable.name}'`
5394
+ );
5395
+ const keyRows = await fetchRowsForKeys(ctx, rootTable, pkColumn2, missingKeys, selection2);
5396
+ for (const row of keyRows) {
5397
+ const pkValue = row[pkName];
5398
+ if (pkValue === void 0 || pkValue === null) continue;
5399
+ const entity = entityByPk.get(pkValue);
5400
+ if (!entity) continue;
5401
+ const fkValue = row[relation.foreignKey];
5402
+ if (fkValue !== void 0 && fkValue !== null) {
5403
+ entity[relation.foreignKey] = fkValue;
5404
+ }
5405
+ }
5406
+ foreignKeys = getForeignKeys();
5407
+ }
5408
+ }
5409
+ }
5028
5410
  if (!foreignKeys.size) {
5029
5411
  return /* @__PURE__ */ new Map();
5030
5412
  }
5031
5413
  const targetKey = relation.localKey || findPrimaryKey(relation.target);
5032
5414
  const pkColumn = relation.target.columns[targetKey];
5033
5415
  if (!pkColumn) return /* @__PURE__ */ new Map();
5034
- const rows = await fetchRowsForKeys(ctx, relation.target, pkColumn, foreignKeys);
5035
- return groupRowsByUnique(rows, targetKey);
5416
+ const requestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5417
+ const selectedColumns = requestedColumns ? [...requestedColumns] : Object.keys(relation.target.columns);
5418
+ if (!selectedColumns.includes(targetKey)) {
5419
+ selectedColumns.push(targetKey);
5420
+ }
5421
+ const selection = buildColumnSelection(
5422
+ relation.target,
5423
+ selectedColumns,
5424
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5425
+ );
5426
+ const rows = await fetchRowsForKeys(ctx, relation.target, pkColumn, foreignKeys, selection, options?.filter);
5427
+ const grouped = groupRowsByUnique(rows, targetKey);
5428
+ if (!requestedColumns) return grouped;
5429
+ const visibleColumns = new Set(selectedColumns);
5430
+ const filtered = /* @__PURE__ */ new Map();
5431
+ for (const [key, row] of grouped.entries()) {
5432
+ filtered.set(key, filterRow(row, visibleColumns));
5433
+ }
5434
+ return filtered;
5036
5435
  };
5037
- var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation) => {
5436
+ var loadBelongsToManyRelation = async (ctx, rootTable, relationName, relation, options) => {
5038
5437
  const rootKey = relation.localKey || findPrimaryKey(rootTable);
5039
5438
  const roots = ctx.getEntitiesForTable(rootTable);
5040
5439
  const rootIds = collectKeysFromRoots(roots, rootKey);
@@ -5043,9 +5442,29 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5043
5442
  }
5044
5443
  const pivotColumn = relation.pivotTable.columns[relation.pivotForeignKeyToRoot];
5045
5444
  if (!pivotColumn) return /* @__PURE__ */ new Map();
5046
- const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds);
5445
+ const pivotColumnsRequested = hasColumns(options?.pivot?.columns) ? [...options.pivot.columns] : void 0;
5446
+ const useIncludeDefaults = options !== void 0;
5447
+ let pivotSelectedColumns;
5448
+ if (pivotColumnsRequested) {
5449
+ pivotSelectedColumns = [...pivotColumnsRequested];
5450
+ } else if (useIncludeDefaults) {
5451
+ const pivotPk = relation.pivotPrimaryKey || findPrimaryKey(relation.pivotTable);
5452
+ pivotSelectedColumns = relation.defaultPivotColumns ?? buildDefaultPivotColumns(relation, pivotPk);
5453
+ } else {
5454
+ pivotSelectedColumns = Object.keys(relation.pivotTable.columns);
5455
+ }
5456
+ const pivotQueryColumns = new Set(pivotSelectedColumns);
5457
+ pivotQueryColumns.add(relation.pivotForeignKeyToRoot);
5458
+ pivotQueryColumns.add(relation.pivotForeignKeyToTarget);
5459
+ const pivotSelection = buildColumnSelection(
5460
+ relation.pivotTable,
5461
+ Array.from(pivotQueryColumns),
5462
+ (column) => `Column '${column}' not found on pivot table '${relation.pivotTable.name}'`
5463
+ );
5464
+ const pivotRows = await fetchRowsForKeys(ctx, relation.pivotTable, pivotColumn, rootIds, pivotSelection);
5047
5465
  const rootLookup = /* @__PURE__ */ new Map();
5048
5466
  const targetIds = /* @__PURE__ */ new Set();
5467
+ const pivotVisibleColumns = new Set(pivotSelectedColumns);
5049
5468
  for (const pivot of pivotRows) {
5050
5469
  const rootValue = pivot[relation.pivotForeignKeyToRoot];
5051
5470
  const targetValue = pivot[relation.pivotForeignKeyToTarget];
@@ -5055,7 +5474,7 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5055
5474
  const bucket = rootLookup.get(toKey6(rootValue)) ?? [];
5056
5475
  bucket.push({
5057
5476
  targetId: targetValue,
5058
- pivot: { ...pivot }
5477
+ pivot: pivotVisibleColumns.size ? filterRow(pivot, pivotVisibleColumns) : {}
5059
5478
  });
5060
5479
  rootLookup.set(toKey6(rootValue), bucket);
5061
5480
  targetIds.add(targetValue);
@@ -5066,8 +5485,19 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5066
5485
  const targetKey = relation.targetKey || findPrimaryKey(relation.target);
5067
5486
  const targetPkColumn = relation.target.columns[targetKey];
5068
5487
  if (!targetPkColumn) return /* @__PURE__ */ new Map();
5069
- const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds);
5488
+ const targetRequestedColumns = hasColumns(options?.columns) ? [...options.columns] : void 0;
5489
+ const targetSelectedColumns = targetRequestedColumns ? [...targetRequestedColumns] : Object.keys(relation.target.columns);
5490
+ if (!targetSelectedColumns.includes(targetKey)) {
5491
+ targetSelectedColumns.push(targetKey);
5492
+ }
5493
+ const targetSelection = buildColumnSelection(
5494
+ relation.target,
5495
+ targetSelectedColumns,
5496
+ (column) => `Column '${column}' not found on relation '${relationName}'`
5497
+ );
5498
+ const targetRows = await fetchRowsForKeys(ctx, relation.target, targetPkColumn, targetIds, targetSelection, options?.filter);
5070
5499
  const targetMap = groupRowsByUnique(targetRows, targetKey);
5500
+ const targetVisibleColumns = new Set(targetSelectedColumns);
5071
5501
  const result = /* @__PURE__ */ new Map();
5072
5502
  for (const [rootId, entries] of rootLookup.entries()) {
5073
5503
  const bucket = [];
@@ -5075,7 +5505,7 @@ var loadBelongsToManyRelation = async (ctx, rootTable, _relationName, relation)
5075
5505
  const targetRow = targetMap.get(toKey6(entry.targetId));
5076
5506
  if (!targetRow) continue;
5077
5507
  bucket.push({
5078
- ...targetRow,
5508
+ ...targetRequestedColumns ? filterRow(targetRow, targetVisibleColumns) : targetRow,
5079
5509
  _pivot: entry.pivot
5080
5510
  });
5081
5511
  }
@@ -5105,12 +5535,13 @@ var relationLoaderCache = (meta, relationName, factory) => {
5105
5535
  }
5106
5536
  return promise;
5107
5537
  };
5108
- var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
5538
+ var createEntityProxy = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
5109
5539
  const target = { ...row };
5110
5540
  const meta = {
5111
5541
  ctx,
5112
5542
  table,
5113
5543
  lazyRelations: [...lazyRelations],
5544
+ lazyRelationOptions: new Map(lazyRelationOptions),
5114
5545
  relationCache: /* @__PURE__ */ new Map(),
5115
5546
  relationHydration: /* @__PURE__ */ new Map(),
5116
5547
  relationWrappers: /* @__PURE__ */ new Map()
@@ -5151,14 +5582,14 @@ var createEntityProxy = (ctx, table, row, lazyRelations = []) => {
5151
5582
  populateHydrationCache(proxy, row, meta);
5152
5583
  return proxy;
5153
5584
  };
5154
- var createEntityFromRow = (ctx, table, row, lazyRelations = []) => {
5585
+ var createEntityFromRow = (ctx, table, row, lazyRelations = [], lazyRelationOptions = /* @__PURE__ */ new Map()) => {
5155
5586
  const pkName = findPrimaryKey(table);
5156
5587
  const pkValue = row[pkName];
5157
5588
  if (pkValue !== void 0 && pkValue !== null) {
5158
5589
  const tracked = ctx.getEntity(table, pkValue);
5159
5590
  if (tracked) return tracked;
5160
5591
  }
5161
- const entity = createEntityProxy(ctx, table, row, lazyRelations);
5592
+ const entity = createEntityProxy(ctx, table, row, lazyRelations, lazyRelationOptions);
5162
5593
  if (pkValue !== void 0 && pkValue !== null) {
5163
5594
  ctx.trackManaged(table, pkValue, entity);
5164
5595
  } else {
@@ -5208,6 +5639,58 @@ var populateHydrationCache = (entity, row, meta) => {
5208
5639
  }
5209
5640
  }
5210
5641
  };
5642
+ var proxifyRelationWrapper = (wrapper) => {
5643
+ return new Proxy(wrapper, {
5644
+ get(target, prop, receiver) {
5645
+ if (typeof prop === "symbol") {
5646
+ return Reflect.get(target, prop, receiver);
5647
+ }
5648
+ if (prop in target) {
5649
+ return Reflect.get(target, prop, receiver);
5650
+ }
5651
+ const getItems = target.getItems;
5652
+ if (typeof getItems === "function") {
5653
+ const items = getItems.call(target);
5654
+ if (items && prop in items) {
5655
+ const propName = prop;
5656
+ const value = items[propName];
5657
+ return typeof value === "function" ? value.bind(items) : value;
5658
+ }
5659
+ }
5660
+ const getRef = target.get;
5661
+ if (typeof getRef === "function") {
5662
+ const current = getRef.call(target);
5663
+ if (current && prop in current) {
5664
+ const propName = prop;
5665
+ const value = current[propName];
5666
+ return typeof value === "function" ? value.bind(current) : value;
5667
+ }
5668
+ }
5669
+ return void 0;
5670
+ },
5671
+ set(target, prop, value, receiver) {
5672
+ if (typeof prop === "symbol") {
5673
+ return Reflect.set(target, prop, value, receiver);
5674
+ }
5675
+ if (prop in target) {
5676
+ return Reflect.set(target, prop, value, receiver);
5677
+ }
5678
+ const getRef = target.get;
5679
+ if (typeof getRef === "function") {
5680
+ const current = getRef.call(target);
5681
+ if (current && typeof current === "object") {
5682
+ return Reflect.set(current, prop, value);
5683
+ }
5684
+ }
5685
+ const getItems = target.getItems;
5686
+ if (typeof getItems === "function") {
5687
+ const items = getItems.call(target);
5688
+ return Reflect.set(items, prop, value);
5689
+ }
5690
+ return Reflect.set(target, prop, value, receiver);
5691
+ }
5692
+ });
5693
+ };
5211
5694
  var getRelationWrapper = (meta, relationName, owner) => {
5212
5695
  if (meta.relationWrappers.has(relationName)) {
5213
5696
  return meta.relationWrappers.get(relationName);
@@ -5215,12 +5698,13 @@ var getRelationWrapper = (meta, relationName, owner) => {
5215
5698
  const relation = meta.table.relations[relationName];
5216
5699
  if (!relation) return void 0;
5217
5700
  const wrapper = instantiateWrapper(meta, relationName, relation, owner);
5218
- if (wrapper) {
5219
- meta.relationWrappers.set(relationName, wrapper);
5220
- }
5221
- return wrapper;
5701
+ if (!wrapper) return void 0;
5702
+ const proxied = proxifyRelationWrapper(wrapper);
5703
+ meta.relationWrappers.set(relationName, proxied);
5704
+ return proxied;
5222
5705
  };
5223
5706
  var instantiateWrapper = (meta, relationName, relation, owner) => {
5707
+ const lazyOptions = meta.lazyRelationOptions.get(relationName);
5224
5708
  switch (relation.type) {
5225
5709
  case RelationKinds.HasOne: {
5226
5710
  const hasOne2 = relation;
@@ -5228,7 +5712,7 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
5228
5712
  const loader = () => relationLoaderCache(
5229
5713
  meta,
5230
5714
  relationName,
5231
- () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2)
5715
+ () => loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne2, lazyOptions)
5232
5716
  );
5233
5717
  return new DefaultHasOneReference(
5234
5718
  meta.ctx,
@@ -5248,7 +5732,7 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
5248
5732
  const loader = () => relationLoaderCache(
5249
5733
  meta,
5250
5734
  relationName,
5251
- () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2)
5735
+ () => loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany2, lazyOptions)
5252
5736
  );
5253
5737
  return new DefaultHasManyCollection(
5254
5738
  meta.ctx,
@@ -5268,7 +5752,7 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
5268
5752
  const loader = () => relationLoaderCache(
5269
5753
  meta,
5270
5754
  relationName,
5271
- () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2)
5755
+ () => loadBelongsToRelation(meta.ctx, meta.table, relationName, belongsTo2, lazyOptions)
5272
5756
  );
5273
5757
  return new DefaultBelongsToReference(
5274
5758
  meta.ctx,
@@ -5288,7 +5772,7 @@ var instantiateWrapper = (meta, relationName, relation, owner) => {
5288
5772
  const loader = () => relationLoaderCache(
5289
5773
  meta,
5290
5774
  relationName,
5291
- () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many)
5775
+ () => loadBelongsToManyRelation(meta.ctx, meta.table, relationName, many, lazyOptions)
5292
5776
  );
5293
5777
  return new DefaultManyToManyCollection(
5294
5778
  meta.ctx,
@@ -5327,11 +5811,17 @@ var executeWithContexts = async (execCtx, entityCtx, qb) => {
5327
5811
  const compiled = execCtx.dialect.compileSelect(ast);
5328
5812
  const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
5329
5813
  const rows = flattenResults(executed);
5814
+ const lazyRelations = qb.getLazyRelations();
5815
+ const lazyRelationOptions = qb.getLazyRelationOptions();
5330
5816
  if (ast.setOps && ast.setOps.length > 0) {
5331
- return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
5817
+ const proxies = rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
5818
+ await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
5819
+ return proxies;
5332
5820
  }
5333
5821
  const hydrated = hydrateRows(rows, qb.getHydrationPlan());
5334
- return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
5822
+ const entities = hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, lazyRelations, lazyRelationOptions));
5823
+ await loadLazyRelationsForTable(entityCtx, qb.getTable(), lazyRelations, lazyRelationOptions);
5824
+ return entities;
5335
5825
  };
5336
5826
  async function executeHydrated(session, qb) {
5337
5827
  return executeWithContexts(session.getExecutionContext(), session, qb);
@@ -5343,6 +5833,52 @@ async function executeHydratedWithContexts(execCtx, hydCtx, qb) {
5343
5833
  }
5344
5834
  return executeWithContexts(execCtx, entityCtx, qb);
5345
5835
  }
5836
+ var loadLazyRelationsForTable = async (ctx, table, lazyRelations, lazyRelationOptions) => {
5837
+ if (!lazyRelations.length) return;
5838
+ const tracked = ctx.getEntitiesForTable(table);
5839
+ if (!tracked.length) return;
5840
+ const meta = getEntityMeta(tracked[0].entity);
5841
+ if (!meta) return;
5842
+ for (const relationName of lazyRelations) {
5843
+ const relation = table.relations[relationName];
5844
+ if (!relation) continue;
5845
+ const key = relationName;
5846
+ const options = lazyRelationOptions.get(key);
5847
+ if (!options) {
5848
+ continue;
5849
+ }
5850
+ switch (relation.type) {
5851
+ case RelationKinds.HasOne:
5852
+ await relationLoaderCache(
5853
+ meta,
5854
+ key,
5855
+ () => loadHasOneRelation(ctx, table, key, relation, options)
5856
+ );
5857
+ break;
5858
+ case RelationKinds.HasMany:
5859
+ await relationLoaderCache(
5860
+ meta,
5861
+ key,
5862
+ () => loadHasManyRelation(ctx, table, key, relation, options)
5863
+ );
5864
+ break;
5865
+ case RelationKinds.BelongsTo:
5866
+ await relationLoaderCache(
5867
+ meta,
5868
+ key,
5869
+ () => loadBelongsToRelation(ctx, table, key, relation, options)
5870
+ );
5871
+ break;
5872
+ case RelationKinds.BelongsToMany:
5873
+ await relationLoaderCache(
5874
+ meta,
5875
+ key,
5876
+ () => loadBelongsToManyRelation(ctx, table, key, relation, options)
5877
+ );
5878
+ break;
5879
+ }
5880
+ }
5881
+ };
5346
5882
 
5347
5883
  // src/query-builder/query-resolution.ts
5348
5884
  function resolveSelectQuery(query) {
@@ -5359,7 +5895,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5359
5895
  * @param hydration - Optional hydration manager
5360
5896
  * @param dependencies - Optional query builder dependencies
5361
5897
  */
5362
- constructor(table, state, hydration, dependencies, lazyRelations) {
5898
+ constructor(table, state, hydration, dependencies, lazyRelations, lazyRelationOptions) {
5363
5899
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
5364
5900
  this.env = { table, deps };
5365
5901
  const initialState = state ?? deps.createState(table);
@@ -5369,6 +5905,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5369
5905
  hydration: initialHydration
5370
5906
  };
5371
5907
  this.lazyRelations = new Set(lazyRelations ?? []);
5908
+ this.lazyRelationOptions = new Map(lazyRelationOptions ?? []);
5372
5909
  this.columnSelector = deps.createColumnSelector(this.env);
5373
5910
  this.relationManager = deps.createRelationManager(this.env);
5374
5911
  }
@@ -5378,8 +5915,15 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5378
5915
  * @param lazyRelations - Updated lazy relations set
5379
5916
  * @returns New SelectQueryBuilder instance
5380
5917
  */
5381
- clone(context = this.context, lazyRelations = new Set(this.lazyRelations)) {
5382
- return new _SelectQueryBuilder(this.env.table, context.state, context.hydration, this.env.deps, lazyRelations);
5918
+ clone(context = this.context, lazyRelations = new Set(this.lazyRelations), lazyRelationOptions = new Map(this.lazyRelationOptions)) {
5919
+ return new _SelectQueryBuilder(
5920
+ this.env.table,
5921
+ context.state,
5922
+ context.hydration,
5923
+ this.env.deps,
5924
+ lazyRelations,
5925
+ lazyRelationOptions
5926
+ );
5383
5927
  }
5384
5928
  /**
5385
5929
  * Applies an alias to the root FROM table.
@@ -5626,36 +6170,40 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5626
6170
  /**
5627
6171
  * Includes a relation lazily in the query results
5628
6172
  * @param relationName - Name of the relation to include lazily
6173
+ * @param options - Optional include options for lazy loading
5629
6174
  * @returns New query builder instance with lazy relation inclusion
5630
6175
  */
5631
- includeLazy(relationName) {
5632
- const nextLazy = new Set(this.lazyRelations);
5633
- nextLazy.add(relationName);
5634
- return this.clone(this.context, nextLazy);
5635
- }
5636
- /**
5637
- * Selects columns for a related table in a single hop.
5638
- */
5639
- selectRelationColumns(relationName, ...cols) {
6176
+ includeLazy(relationName, options) {
6177
+ let nextContext = this.context;
5640
6178
  const relation = this.env.table.relations[relationName];
5641
- if (!relation) {
5642
- throw new Error(`Relation '${relationName}' not found on table '${this.env.table.name}'`);
5643
- }
5644
- const target = relation.target;
5645
- for (const col2 of cols) {
5646
- if (!target.columns[col2]) {
5647
- throw new Error(
5648
- `Column '${col2}' not found on related table '${target.name}' for relation '${relationName}'`
5649
- );
6179
+ if (relation?.type === RelationKinds.BelongsTo) {
6180
+ const foreignKey = relation.foreignKey;
6181
+ const fkColumn = this.env.table.columns[foreignKey];
6182
+ if (fkColumn) {
6183
+ const hasAlias2 = nextContext.state.ast.columns.some((col2) => {
6184
+ const node = col2;
6185
+ return (node.alias ?? node.name) === foreignKey;
6186
+ });
6187
+ if (!hasAlias2) {
6188
+ nextContext = this.columnSelector.select(nextContext, { [foreignKey]: fkColumn });
6189
+ }
5650
6190
  }
5651
6191
  }
5652
- return this.include(relationName, { columns: cols });
6192
+ const nextLazy = new Set(this.lazyRelations);
6193
+ nextLazy.add(relationName);
6194
+ const nextOptions = new Map(this.lazyRelationOptions);
6195
+ if (options) {
6196
+ nextOptions.set(relationName, options);
6197
+ } else {
6198
+ nextOptions.delete(relationName);
6199
+ }
6200
+ return this.clone(nextContext, nextLazy, nextOptions);
5653
6201
  }
5654
6202
  /**
5655
- * Convenience alias for selecting specific columns from a relation.
6203
+ * Convenience alias for including only specific columns from a relation.
5656
6204
  */
5657
6205
  includePick(relationName, cols) {
5658
- return this.selectRelationColumns(relationName, ...cols);
6206
+ return this.include(relationName, { columns: cols });
5659
6207
  }
5660
6208
  /**
5661
6209
  * Selects columns for the root table and relations from an array of entries
@@ -5668,7 +6216,7 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5668
6216
  if (entry.type === "root") {
5669
6217
  currBuilder = currBuilder.select(...entry.columns);
5670
6218
  } else {
5671
- currBuilder = currBuilder.selectRelationColumns(entry.relationName, ...entry.columns);
6219
+ currBuilder = currBuilder.include(entry.relationName, { columns: entry.columns });
5672
6220
  }
5673
6221
  }
5674
6222
  return currBuilder;
@@ -5680,6 +6228,13 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
5680
6228
  getLazyRelations() {
5681
6229
  return Array.from(this.lazyRelations);
5682
6230
  }
6231
+ /**
6232
+ * Gets lazy relation include options
6233
+ * @returns Map of relation names to include options
6234
+ */
6235
+ getLazyRelationOptions() {
6236
+ return new Map(this.lazyRelationOptions);
6237
+ }
5683
6238
  /**
5684
6239
  * Gets the table definition for this query builder
5685
6240
  * @returns Table definition
@@ -11538,6 +12093,7 @@ export {
11538
12093
  registerExpressionDispatcher,
11539
12094
  registerOperandDispatcher,
11540
12095
  registerSchemaIntrospector,
12096
+ relationLoaderCache,
11541
12097
  renderColumnDefinition,
11542
12098
  renderTypeWithArgs,
11543
12099
  repeat,