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/README.md +22 -15
- package/dist/index.cjs +640 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -45
- package/dist/index.d.ts +113 -45
- package/dist/index.js +639 -83
- package/dist/index.js.map +1 -1
- package/package.json +69 -69
- package/src/decorators/bootstrap.ts +39 -3
- package/src/orm/entity-meta.ts +6 -3
- package/src/orm/entity.ts +81 -14
- package/src/orm/execute.ts +87 -20
- package/src/orm/lazy-batch.ts +237 -54
- package/src/orm/relations/belongs-to.ts +2 -2
- package/src/orm/relations/has-many.ts +23 -9
- package/src/orm/relations/has-one.ts +2 -2
- package/src/orm/relations/many-to-many.ts +27 -13
- package/src/orm/save-graph-types.ts +2 -2
- package/src/orm/save-graph.ts +18 -18
- package/src/query-builder/relation-conditions.ts +80 -59
- package/src/query-builder/relation-service.ts +399 -95
- package/src/query-builder/relation-types.ts +2 -2
- package/src/query-builder/select.ts +58 -40
- package/src/schema/types.ts +106 -89
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
3973
|
-
|
|
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
|
-
|
|
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
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
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
|
|
4938
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
5011
|
-
|
|
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,
|
|
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
|
|
5023
|
-
|
|
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,
|
|
5368
|
+
var loadBelongsToRelation = async (ctx, rootTable, relationName, relation, options) => {
|
|
5026
5369
|
const roots = ctx.getEntitiesForTable(rootTable);
|
|
5027
|
-
const
|
|
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
|
|
5035
|
-
|
|
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,
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
5220
|
-
|
|
5221
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
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
|
-
|
|
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
|
|
6203
|
+
* Convenience alias for including only specific columns from a relation.
|
|
5656
6204
|
*/
|
|
5657
6205
|
includePick(relationName, cols) {
|
|
5658
|
-
return this.
|
|
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.
|
|
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,
|