@prisma/orm-family-sql 8.0.0-rc.16-dev.6 → 8.0.0-rc.16-dev.8
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/{control-DxvOSmwF.mjs → control-BRbds4vn.mjs} +627 -123
- package/dist/control-BRbds4vn.mjs.map +1 -0
- package/dist/{control-DCt08EpW.d.mts → control-BkyDhucB.d.mts} +262 -56
- package/dist/control-BkyDhucB.d.mts.map +1 -0
- package/dist/{diff-3tfqwWS7.d.mts → diff-DMOqiT2h.d.mts} +2 -2
- package/dist/{diff-3tfqwWS7.d.mts.map → diff-DMOqiT2h.d.mts.map} +1 -1
- package/dist/family.d.mts +5 -5
- package/dist/family.mjs +3 -3
- package/dist/family__control.d.mts +3 -3
- package/dist/family__control.mjs +2 -2
- package/dist/family__diff.d.mts +1 -1
- package/dist/family__migration.d.mts +1 -1
- package/dist/family__migration.mjs +1 -1
- package/dist/{migration-D6NJlA6-.d.mts → migration-C5HGygKM.d.mts} +3 -3
- package/dist/{migration-D6NJlA6-.d.mts.map → migration-C5HGygKM.d.mts.map} +1 -1
- package/dist/{migration-CK4oePIo.mjs → migration-Dy_4Wmiy.mjs} +3 -3
- package/dist/{migration-CK4oePIo.mjs.map → migration-Dy_4Wmiy.mjs.map} +1 -1
- package/dist/orm-client.d.mts +120 -120
- package/dist/orm-client.d.mts.map +1 -1
- package/dist/orm-client.mjs +1268 -1268
- package/dist/orm-client.mjs.map +1 -1
- package/dist/{types-CvuS68sy-ReNXbgpE.d.mts → types-CzxOObSY-CNWyrf87.d.mts} +12 -8
- package/dist/{types-CvuS68sy-ReNXbgpE.d.mts.map → types-CzxOObSY-CNWyrf87.d.mts.map} +1 -1
- package/package.json +18 -18
- package/dist/control-DCt08EpW.d.mts.map +0 -1
- package/dist/control-DxvOSmwF.mjs.map +0 -1
package/dist/orm-client.mjs
CHANGED
|
@@ -2784,1389 +2784,1389 @@ function assertFieldHasEqualityTrait(context, namespaceId, modelName, fieldName)
|
|
|
2784
2784
|
trait: "equality"
|
|
2785
2785
|
} });
|
|
2786
2786
|
}
|
|
2787
|
-
function
|
|
2788
|
-
return
|
|
2787
|
+
function nullability(nullable) {
|
|
2788
|
+
return nullable ? "may be null" : "is never null";
|
|
2789
2789
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
}
|
|
2816
|
-
};
|
|
2817
|
-
var ModelAccessorScope = class ModelAccessorScope {
|
|
2818
|
-
current;
|
|
2819
|
-
#visibleBindings;
|
|
2820
|
-
#aliasCounter;
|
|
2821
|
-
constructor(current, visibleBindings, aliasCounter) {
|
|
2822
|
-
this.current = current;
|
|
2823
|
-
this.#visibleBindings = Object.freeze([...visibleBindings]);
|
|
2824
|
-
this.#aliasCounter = aliasCounter;
|
|
2825
|
-
Object.freeze(this);
|
|
2826
|
-
}
|
|
2827
|
-
static root(namespaceId, tableName) {
|
|
2828
|
-
const binding = SqlTableBinding.unaliased({
|
|
2829
|
-
namespaceId,
|
|
2830
|
-
tableName
|
|
2790
|
+
function isFieldBuilder(value) {
|
|
2791
|
+
return "build" in value && typeof value.build === "function";
|
|
2792
|
+
}
|
|
2793
|
+
function isListMultiplicity(value) {
|
|
2794
|
+
return typeof value === "object" && value !== null && "elementNullable" in value && typeof value.elementNullable === "boolean";
|
|
2795
|
+
}
|
|
2796
|
+
function isMultiplicity(value) {
|
|
2797
|
+
return value === void 0 || value === false || isListMultiplicity(value);
|
|
2798
|
+
}
|
|
2799
|
+
function isFieldSpec(value) {
|
|
2800
|
+
return "codecId" in value && typeof value.codecId === "string" && "nullable" in value && typeof value.nullable === "boolean" && (!("many" in value) || isMultiplicity(value.many));
|
|
2801
|
+
}
|
|
2802
|
+
function multiplicity(many) {
|
|
2803
|
+
return isListMultiplicity(many) ? { elementNullable: many.elementNullable } : false;
|
|
2804
|
+
}
|
|
2805
|
+
const FIELD_DECLARATION_FIX = "Declare each field with a field builder, such as field.temporal.timestamptz().optional(), or with { codecId, nullable }. Declare a list with .many() or many: { elementNullable: false }, or, when its elements may be null, with .many({ elementsNullable: true }) or many: { elementNullable: true }.";
|
|
2806
|
+
function declaredFieldSpec(name, declaration) {
|
|
2807
|
+
if (typeof declaration === "object" && declaration !== null && isFieldBuilder(declaration)) {
|
|
2808
|
+
const built = declaration.build();
|
|
2809
|
+
const descriptor = typeof built === "object" && built !== null && "descriptor" in built ? built.descriptor : void 0;
|
|
2810
|
+
const nullable = typeof built === "object" && built !== null && "nullable" in built ? built.nullable : void 0;
|
|
2811
|
+
if (typeof descriptor !== "object" || descriptor === null || !("codecId" in descriptor) || typeof descriptor.codecId !== "string" || typeof nullable !== "boolean") throw ormError("ORM.ARGUMENT_INVALID", `Cannot define the fragment: the field builder for ${name} names no column type`, {
|
|
2812
|
+
why: "A fragment for any model matches each declared field by its codec and nullability, and this builder refers to a named type instead of a column type.",
|
|
2813
|
+
fix: "Declare the field with a builder that has a column type, such as field.text() or field.column(textColumn), or with { codecId, nullable }.",
|
|
2814
|
+
meta: { field: name }
|
|
2831
2815
|
});
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
}
|
|
2838
|
-
forManyToManyRelation(childNamespaceId, childTableName, junctionNamespaceId, junctionTableName) {
|
|
2839
|
-
const initialChildScope = this.forRelation(childNamespaceId, childTableName);
|
|
2840
|
-
const junctionBinding = initialChildScope.#allocateBinding(junctionNamespaceId, junctionTableName, "junction");
|
|
2841
|
-
return {
|
|
2842
|
-
childScope: new ModelAccessorScope(initialChildScope.current, [...initialChildScope.#visibleBindings, junctionBinding], this.#aliasCounter),
|
|
2843
|
-
junctionBinding
|
|
2844
|
-
};
|
|
2845
|
-
}
|
|
2846
|
-
forJoinedSource(namespaceId, tableName) {
|
|
2847
|
-
if (this.current.isStoredAt(namespaceId, tableName)) return this;
|
|
2848
|
-
const binding = SqlTableBinding.unaliased({
|
|
2849
|
-
namespaceId,
|
|
2850
|
-
tableName
|
|
2816
|
+
const many = typeof built === "object" && built !== null && "many" in built && built.many;
|
|
2817
|
+
if (!isMultiplicity(many)) throw ormError("ORM.ARGUMENT_INVALID", `Cannot define the fragment: the field builder for ${name} builds a many that is not false or { elementNullable }`, {
|
|
2818
|
+
why: `A field builder's build() returns many as false or undefined for one value, or as { elementNullable } with a boolean elementNullable for a list; received ${describeReceived(many)} for ${name}.`,
|
|
2819
|
+
fix: FIELD_DECLARATION_FIX,
|
|
2820
|
+
meta: { field: name }
|
|
2851
2821
|
});
|
|
2852
|
-
return
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
namespaceId,
|
|
2857
|
-
tableName
|
|
2822
|
+
return {
|
|
2823
|
+
codecId: descriptor.codecId,
|
|
2824
|
+
nullable,
|
|
2825
|
+
many: multiplicity(many)
|
|
2858
2826
|
};
|
|
2859
|
-
if (!this.#visibleBindings.some((binding) => binding.isReferencedAs(tableName))) return SqlTableBinding.unaliased(storage);
|
|
2860
|
-
return SqlTableBinding.aliased(storage, this.#allocateAlias(aliasKind));
|
|
2861
|
-
}
|
|
2862
|
-
#allocateAlias(kind) {
|
|
2863
|
-
while (true) {
|
|
2864
|
-
const alias = `__orm_${kind}_${this.#aliasCounter.nextId}`;
|
|
2865
|
-
this.#aliasCounter.nextId += 1;
|
|
2866
|
-
if (!this.#visibleBindings.some((binding) => binding.isReferencedAs(alias))) return alias;
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
};
|
|
2870
|
-
function createModelAccessor(context, namespaceId, modelName, variantName) {
|
|
2871
|
-
const tableName = resolveModelTableName(context.contract, namespaceId, modelName);
|
|
2872
|
-
return createModelAccessorInScope(context, namespaceId, modelName, variantName, ModelAccessorScope.root(namespaceId, tableName));
|
|
2873
|
-
}
|
|
2874
|
-
function createModelAccessorInScope(context, namespaceId, modelName, variantName, scope) {
|
|
2875
|
-
const contract = context.contract;
|
|
2876
|
-
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
2877
|
-
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
2878
|
-
const modelRelations = resolveModelRelations(contract, namespaceId, modelName);
|
|
2879
|
-
const variantFieldColumns = variantName ? resolveVariantFieldColumns(contract, namespaceId, modelName, variantName) : {};
|
|
2880
|
-
const variantCoordinates = variantName ? {
|
|
2881
|
-
name: variantName,
|
|
2882
|
-
relations: resolveModelRelations(contract, namespaceId, variantName),
|
|
2883
|
-
tableName: resolvePolymorphismInfo(contract, namespaceId, modelName)?.variants.get(variantName)?.table ?? tableName
|
|
2884
|
-
} : void 0;
|
|
2885
|
-
const opsByCodecId = /* @__PURE__ */ new Map();
|
|
2886
|
-
function registerOp(codecId, op) {
|
|
2887
|
-
let existing = opsByCodecId.get(codecId);
|
|
2888
|
-
if (!existing) {
|
|
2889
|
-
existing = [];
|
|
2890
|
-
opsByCodecId.set(codecId, existing);
|
|
2891
|
-
}
|
|
2892
|
-
existing.push(op);
|
|
2893
|
-
}
|
|
2894
|
-
for (const [name, entry] of Object.entries(context.queryOperations.entries())) {
|
|
2895
|
-
const op = [name, entry];
|
|
2896
|
-
const self = entry.self;
|
|
2897
|
-
if (!self) continue;
|
|
2898
|
-
if (self.codecId !== void 0) registerOp(self.codecId, op);
|
|
2899
|
-
else if (self.traits !== void 0) for (const descriptor of context.codecDescriptors.values()) {
|
|
2900
|
-
const descriptorTraits = descriptor.traits;
|
|
2901
|
-
if (self.traits.every((t) => descriptorTraits.includes(t))) registerOp(descriptor.codecId, op);
|
|
2902
|
-
}
|
|
2903
2827
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
const fieldBinding = scope.forJoinedSource(namespaceId, resolvedTable).current;
|
|
2915
|
-
const columnName = variantField?.column ?? fieldToColumn[prop] ?? prop;
|
|
2916
|
-
const column = resolveColumn(contract, namespaceId, resolvedTable, columnName);
|
|
2917
|
-
if (!column) return;
|
|
2918
|
-
const traits = codecTraits(context, column.codecId);
|
|
2919
|
-
const operations = opsByCodecId.get(column.codecId) ?? [];
|
|
2920
|
-
const codec = codecRefForStorageColumn(contract.storage, namespaceId, resolvedTable, columnName);
|
|
2921
|
-
return createScalarFieldAccessor(fieldBinding, columnName, column.codecId, column.nullable, codec, traits, operations, context);
|
|
2922
|
-
} }));
|
|
2828
|
+
if (typeof declaration === "object" && declaration !== null && isFieldSpec(declaration)) return {
|
|
2829
|
+
codecId: declaration.codecId,
|
|
2830
|
+
nullable: declaration.nullable,
|
|
2831
|
+
many: multiplicity(declaration.many)
|
|
2832
|
+
};
|
|
2833
|
+
throw ormError("ORM.ARGUMENT_INVALID", `Cannot define the fragment: the declaration of field ${name} is not a field builder or { codecId, nullable }`, {
|
|
2834
|
+
why: `Each field of a fragment is declared with a field builder or with an object that has a string codecId, a boolean nullable and, for a list, many: { elementNullable } with a boolean elementNullable; received ${describeReceived(declaration)} for ${name}.`,
|
|
2835
|
+
fix: FIELD_DECLARATION_FIX,
|
|
2836
|
+
meta: { field: name }
|
|
2837
|
+
});
|
|
2923
2838
|
}
|
|
2924
|
-
function
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
comparisonEntries.push([name, meta.create(column, codec)]);
|
|
2930
|
-
}
|
|
2931
|
-
const accessor = blindCast({
|
|
2932
|
-
returnType: {
|
|
2933
|
-
codecId,
|
|
2934
|
-
nullable,
|
|
2935
|
-
codec
|
|
2936
|
-
},
|
|
2937
|
-
buildAst: () => column,
|
|
2938
|
-
...Object.fromEntries(comparisonEntries)
|
|
2839
|
+
function declaredFieldSpecs(declarations) {
|
|
2840
|
+
if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations)) throw ormError("ORM.ARGUMENT_INVALID", "Cannot define the fragment: the fields are not an object", {
|
|
2841
|
+
why: `The first argument of fragment maps the name of each field the fragment needs to its declaration; received ${describeReceived(declarations)}.`,
|
|
2842
|
+
fix: FIELD_DECLARATION_FIX,
|
|
2843
|
+
meta: { argument: "fields" }
|
|
2939
2844
|
});
|
|
2940
|
-
|
|
2941
|
-
return blindCast(accessor);
|
|
2845
|
+
return Object.entries(declarations).map(([name, declaration]) => [name, declaredFieldSpec(name, declaration)]);
|
|
2942
2846
|
}
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
const returnCodec = { codecId: returnCodecId };
|
|
2951
|
-
const methods = {};
|
|
2952
|
-
for (const [resultMethodName, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
2953
|
-
if (meta.traits.some((t) => !returnTraits.includes(t))) continue;
|
|
2954
|
-
methods[resultMethodName] = meta.create(resultAst, returnCodec);
|
|
2955
|
-
}
|
|
2956
|
-
return methods;
|
|
2957
|
-
};
|
|
2847
|
+
/** Throws `ORM.ARGUMENT_INVALID` unless `body`, the body of a fragment, is a function. */
|
|
2848
|
+
function assertFragmentBody(body) {
|
|
2849
|
+
if (typeof body !== "function") throw ormError("ORM.ARGUMENT_INVALID", "Cannot define the fragment: the body is not a function", {
|
|
2850
|
+
why: `The body of a fragment is a function that receives a collection and returns one; received ${describeReceived(body)}.`,
|
|
2851
|
+
fix: "Pass a function, such as (rows) => rows.where(...).",
|
|
2852
|
+
meta: { argument: "body" }
|
|
2853
|
+
});
|
|
2958
2854
|
}
|
|
2959
|
-
|
|
2960
|
-
"
|
|
2961
|
-
"every",
|
|
2962
|
-
"none",
|
|
2963
|
-
"count"
|
|
2964
|
-
]);
|
|
2965
|
-
function createRelationFilterAccessor(context, parentNamespaceId, parentModelName, parentScope, relation) {
|
|
2966
|
-
const relatedTableName = resolveModelTableName(context.contract, relation.toNamespace, relation.to);
|
|
2967
|
-
const correlate = () => correlateRelatedRows(context, parentNamespaceId, parentModelName, parentScope, relatedTableName, relation);
|
|
2968
|
-
const filters = {
|
|
2969
|
-
some: (predicate) => buildExistsExpr(context, relation, correlate(), "some", predicate),
|
|
2970
|
-
every: (predicate) => buildExistsExpr(context, relation, correlate(), "every", predicate),
|
|
2971
|
-
none: (predicate) => buildExistsExpr(context, relation, correlate(), "none", predicate)
|
|
2972
|
-
};
|
|
2973
|
-
if (isToOneCardinality(relation.cardinality)) return new Proxy(filters, { get(target, prop) {
|
|
2974
|
-
if (typeof prop !== "string") return void 0;
|
|
2975
|
-
if (Object.hasOwn(target, prop)) return Reflect.get(target, prop);
|
|
2976
|
-
if (RELATION_ACCESSOR_METHOD_NAMES.has(prop)) return void 0;
|
|
2977
|
-
return relatedOrderableField(context, relation, relatedTableName, correlate, prop);
|
|
2978
|
-
} });
|
|
2979
|
-
return {
|
|
2980
|
-
...filters,
|
|
2981
|
-
count: (predicate) => createOrderable(() => buildRelationCountExpr(context, relation, correlate(), predicate))
|
|
2982
|
-
};
|
|
2855
|
+
function hasContract(ctx) {
|
|
2856
|
+
return "context" in ctx && typeof ctx.context === "object" && ctx.context !== null && "contract" in ctx.context && typeof ctx.context.contract === "object" && ctx.context.contract !== null;
|
|
2983
2857
|
}
|
|
2984
|
-
function
|
|
2985
|
-
return
|
|
2986
|
-
asc: (options) => checkedOrderByItem("asc", buildExpr(), options),
|
|
2987
|
-
desc: (options) => checkedOrderByItem("desc", buildExpr(), options)
|
|
2988
|
-
};
|
|
2858
|
+
function isModelCollection(value) {
|
|
2859
|
+
return typeof value === "object" && value !== null && "ctx" in value && typeof value.ctx === "object" && value.ctx !== null && hasContract(value.ctx) && "modelName" in value && typeof value.modelName === "string" && "namespaceId" in value && typeof value.namespaceId === "string";
|
|
2989
2860
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
if (!
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
if (!column || !hasTrait(context, column.codecId, "order")) return void 0;
|
|
2997
|
-
return createOrderable(() => {
|
|
2998
|
-
const rows = correlate();
|
|
2999
|
-
return SubqueryExpr.of(rows.source.withProjection([ProjectionItem.of(columnName, rows.childScope.current.column(columnName))]).withWhere(rows.correlation));
|
|
2861
|
+
/** Throws `ORM.ARGUMENT_INVALID` unless `value`, what a fragment is applied to, is a collection. */
|
|
2862
|
+
function assertFragmentReceiver(value) {
|
|
2863
|
+
if (!isModelCollection(value)) throw ormError("ORM.ARGUMENT_INVALID", "Cannot apply the fragment: it was not given a collection", {
|
|
2864
|
+
why: `A fragment is applied to a collection, such as db.orm.public.Post; received ${describeReceived(value)}.`,
|
|
2865
|
+
fix: "Pass the fragment to with on a collection: collection.with(fragment).",
|
|
2866
|
+
meta: { argument: "collection" }
|
|
3000
2867
|
});
|
|
3001
2868
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
2869
|
+
/** Throws `ORM.ARGUMENT_INVALID` unless `value` is a collection of the model and namespace a fragment for one model was made from. */
|
|
2870
|
+
function assertModelFragmentReceiver(source, value) {
|
|
2871
|
+
assertFragmentReceiver(value);
|
|
2872
|
+
if (value.modelName !== source.modelName || value.namespaceId !== source.namespaceId) {
|
|
2873
|
+
const made = `${source.namespaceId}.${source.modelName}`;
|
|
2874
|
+
const given = `${value.namespaceId}.${value.modelName}`;
|
|
2875
|
+
throw ormError("ORM.ARGUMENT_INVALID", `Cannot apply a fragment for ${made} to a collection of ${given}`, {
|
|
2876
|
+
why: `The fragment was made with ${made}.fragment(...), and its body was written for that model.`,
|
|
2877
|
+
fix: `Apply the fragment to a collection of ${made}, or make a fragment from ${given}.`,
|
|
2878
|
+
meta: {
|
|
2879
|
+
model: source.modelName,
|
|
2880
|
+
namespace: source.namespaceId,
|
|
2881
|
+
receivedModel: value.modelName,
|
|
2882
|
+
receivedNamespace: value.namespaceId
|
|
2883
|
+
}
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
3005
2886
|
}
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
2887
|
+
/** The model as error messages name it: with its namespace when the contract has more than one. */
|
|
2888
|
+
function modelLabel(collection) {
|
|
2889
|
+
return Object.keys(collection.ctx.context.contract.domain.namespaces).length > 1 ? `${collection.namespaceId}.${collection.modelName}` : collection.modelName;
|
|
2890
|
+
}
|
|
2891
|
+
/** Throws `ORM.ARGUMENT_INVALID` unless `result`, what a fragment's body returned, is a collection of the receiver's model, namespace and class. */
|
|
2892
|
+
function assertFragmentResult(receiver, result) {
|
|
2893
|
+
if (!(isModelCollection(result) && result.modelName === receiver.modelName && result.namespaceId === receiver.namespaceId && result instanceof receiver.constructor)) {
|
|
2894
|
+
const label = modelLabel(receiver);
|
|
2895
|
+
throw ormError("ORM.ARGUMENT_INVALID", `Cannot apply the fragment to ${label}: its body did not return a collection of ${label}`, {
|
|
2896
|
+
why: `The body of a fragment for any model returns the collection it received, after where, orderBy, limit or offset; it returned ${isModelCollection(result) ? `a collection of ${modelLabel(result)}` : describeReceived(result)}.`,
|
|
2897
|
+
fix: "Return the collection the body receives, or one made from it by calling its methods.",
|
|
2898
|
+
meta: {
|
|
2899
|
+
model: receiver.modelName,
|
|
2900
|
+
namespace: receiver.namespaceId,
|
|
2901
|
+
returned: isModelCollection(result) ? result.modelName : describeReceived(result)
|
|
2902
|
+
}
|
|
2903
|
+
});
|
|
3018
2904
|
}
|
|
3019
|
-
const childScope = parentScope.forRelation(relation.toNamespace, relatedTableName);
|
|
3020
|
-
return {
|
|
3021
|
-
childScope,
|
|
3022
|
-
source: SelectAst.from(childScope.current.tableSource(context.contract)),
|
|
3023
|
-
correlation: buildJoinWhere(context.contract, parentNamespaceId, parentModelName, parentScope.current, childScope.current, relation),
|
|
3024
|
-
keyColumn: firstTargetColumn(context.contract, relation) ?? "id"
|
|
3025
|
-
};
|
|
3026
2905
|
}
|
|
3027
|
-
function
|
|
3028
|
-
|
|
3029
|
-
const filterPlan = planRelationFilterMode(rows.correlation, childWhere, mode);
|
|
3030
|
-
if (filterPlan.kind === "constantTrue") return AndExpr.true();
|
|
3031
|
-
const subquery = rows.source.withProjection([ProjectionItem.of("_exists", rows.childScope.current.column(rows.keyColumn))]).withWhere(filterPlan.where);
|
|
3032
|
-
return filterPlan.notExists ? ExistsExpr.notExists(subquery) : ExistsExpr.exists(subquery);
|
|
2906
|
+
function elementNullability(elementNullable) {
|
|
2907
|
+
return elementNullable ? "may be null" : "are never null";
|
|
3033
2908
|
}
|
|
3034
|
-
function
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
2909
|
+
function sameMultiplicity(a, b) {
|
|
2910
|
+
return a === false || b === false ? a === b : a.elementNullable === b.elementNullable;
|
|
2911
|
+
}
|
|
2912
|
+
function fieldShape(many) {
|
|
2913
|
+
return many === false ? "holds one value" : `is a list whose elements ${elementNullability(many.elementNullable)}`;
|
|
2914
|
+
}
|
|
2915
|
+
function declarationFor(many) {
|
|
2916
|
+
if (many === false) return "as one value, without .many() or many";
|
|
2917
|
+
return many.elementNullable ? "as a list whose elements may be null, with .many({ elementsNullable: true }) or many: { elementNullable: true }" : "as a list whose elements are never null, with .many() or many: { elementNullable: false }";
|
|
2918
|
+
}
|
|
2919
|
+
function describeField(field) {
|
|
2920
|
+
const value = `with codec ${field.codecId} that ${nullability(field.nullable)}`;
|
|
2921
|
+
return field.many === false ? value : `${value} and whose elements ${elementNullability(field.many.elementNullable)}`;
|
|
2922
|
+
}
|
|
2923
|
+
function assertFragmentFields(collection, fields) {
|
|
2924
|
+
const { contract } = collection.ctx.context;
|
|
2925
|
+
const { modelName, namespaceId } = collection;
|
|
2926
|
+
const label = modelLabel(collection);
|
|
2927
|
+
const model = modelOf(contract, namespaceId, modelName);
|
|
2928
|
+
for (const [name, spec] of fields) {
|
|
2929
|
+
const declared = `The fragment was declared for models that have a ${spec.many === false ? "field" : "list field"} ${name} ${describeField(spec)}.`;
|
|
2930
|
+
const meta = {
|
|
2931
|
+
model: modelName,
|
|
2932
|
+
namespace: namespaceId,
|
|
2933
|
+
field: name,
|
|
2934
|
+
codecId: spec.codecId,
|
|
2935
|
+
nullable: spec.nullable,
|
|
2936
|
+
many: spec.many
|
|
3041
2937
|
};
|
|
2938
|
+
const modelFields = model?.fields ?? {};
|
|
2939
|
+
if (!Object.hasOwn(modelFields, name)) throw ormError("ORM.FIELD_UNKNOWN", `Cannot apply a fragment to ${label}: it has no field ${name}`, {
|
|
2940
|
+
why: declared,
|
|
2941
|
+
fix: `Apply the fragment to a model that has a field ${name}, or remove ${name} from the declaration in the fragment.`,
|
|
2942
|
+
meta
|
|
2943
|
+
});
|
|
2944
|
+
const fieldMany = multiplicity(modelFields[name]?.many);
|
|
2945
|
+
const column = resolveColumn(contract, namespaceId, collection.tableName, resolveFieldToColumn(contract, namespaceId, modelName, name));
|
|
2946
|
+
const actual = column === void 0 ? `${label}.${name} has no column.` : fieldMany === false ? `${label}.${name} has codec ${column.codecId} and ${nullability(column.nullable)}.` : `${label}.${name} is a list ${describeField({
|
|
2947
|
+
codecId: column.codecId,
|
|
2948
|
+
nullable: column.nullable,
|
|
2949
|
+
many: fieldMany
|
|
2950
|
+
})}.`;
|
|
2951
|
+
const sameCodecAndNullability = column?.codecId === spec.codecId && column.nullable === spec.nullable;
|
|
2952
|
+
if (!sameCodecAndNullability || !sameMultiplicity(fieldMany, spec.many)) throw ormError("ORM.FIELD_UNKNOWN", `Cannot apply a fragment to ${label}: its field ${name} does not match the declaration`, {
|
|
2953
|
+
why: `${declared} ${actual}`,
|
|
2954
|
+
fix: sameCodecAndNullability ? `Apply the fragment to a model whose ${name} field ${fieldShape(spec.many)}, or declare ${name} ${declarationFor(fieldMany)}.` : `Apply the fragment to a model whose ${name} field has that codec and nullability, or change the declaration in the fragment.`,
|
|
2955
|
+
meta
|
|
2956
|
+
});
|
|
3042
2957
|
}
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Define a fragment for any model that has the declared fields. Reached as the `fragment` method of the client `orm()` returns.
|
|
2961
|
+
*/
|
|
2962
|
+
function defineFieldFragment(declarations, body) {
|
|
2963
|
+
const fields = declaredFieldSpecs(declarations);
|
|
2964
|
+
assertFragmentBody(body);
|
|
2965
|
+
return blindCast((collection) => {
|
|
2966
|
+
const receiver = collection;
|
|
2967
|
+
assertFragmentReceiver(receiver);
|
|
2968
|
+
assertFragmentFields(receiver, fields);
|
|
2969
|
+
const result = body(blindCast(collection));
|
|
2970
|
+
assertFragmentResult(receiver, result);
|
|
2971
|
+
return result;
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
const SHOWN_LENGTH = 64;
|
|
2975
|
+
function shown(value) {
|
|
2976
|
+
const characters = Array.from(value);
|
|
2977
|
+
const cut = characters.length > SHOWN_LENGTH;
|
|
3048
2978
|
return {
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
where: childWhere ? and(joinWhere, childWhere) : joinWhere
|
|
2979
|
+
text: JSON.stringify(cut ? `${characters.slice(0, SHOWN_LENGTH).join("")}…` : value),
|
|
2980
|
+
cut
|
|
3052
2981
|
};
|
|
3053
2982
|
}
|
|
3054
|
-
function
|
|
3055
|
-
|
|
3056
|
-
if (
|
|
3057
|
-
|
|
2983
|
+
function describeReceived(value) {
|
|
2984
|
+
if (value === void 0 || value === null) return String(value);
|
|
2985
|
+
if (Array.isArray(value)) return "an array";
|
|
2986
|
+
const kind = typeof value;
|
|
2987
|
+
return kind === "object" ? "an object" : `a ${kind}`;
|
|
3058
2988
|
}
|
|
3059
|
-
function
|
|
3060
|
-
|
|
3061
|
-
if (leftColumns.length === 0) throw new InternalError("Relation metadata is missing join columns");
|
|
3062
|
-
const exprs = [];
|
|
3063
|
-
for (let i = 0; i < leftColumns.length; i++) {
|
|
3064
|
-
const left = leftColumns[i];
|
|
3065
|
-
const right = rightColumns[i];
|
|
3066
|
-
if (!left || !right) throw new InternalError(`Relation metadata is missing a join column pair at index ${i}`);
|
|
3067
|
-
exprs.push(BinaryExpr.eq(leftTable.column(left), rightTable.column(right)));
|
|
3068
|
-
}
|
|
3069
|
-
if (exprs.length === 1 && exprs[0]) return exprs[0];
|
|
3070
|
-
return and(...exprs);
|
|
2989
|
+
function cutNote(subject, value) {
|
|
2990
|
+
return value.cut ? ` The ${subject} above is cut to its first ${SHOWN_LENGTH} characters.` : "";
|
|
3071
2991
|
}
|
|
3072
|
-
function
|
|
3073
|
-
|
|
3074
|
-
const
|
|
3075
|
-
|
|
3076
|
-
const exprs = [];
|
|
3077
|
-
for (const [fieldName, value] of Object.entries(predicate)) {
|
|
3078
|
-
if (value === void 0) continue;
|
|
3079
|
-
const fieldAccessor = blindCast(accessor)[fieldName];
|
|
3080
|
-
if (!fieldAccessor) throw ormError("ORM.FIELD_UNKNOWN", `Shorthand filter on "${relatedModelName}.${fieldName}": field is not defined on the model`, { meta: {
|
|
3081
|
-
model: relatedModelName,
|
|
3082
|
-
field: fieldName
|
|
3083
|
-
} });
|
|
3084
|
-
if (value === null) {
|
|
3085
|
-
if (!fieldAccessor.isNull) throw new InternalError(`Shorthand filter on "${relatedModelName}.${fieldName}": isNull is unexpectedly missing — this is a bug in trait gating`);
|
|
3086
|
-
exprs.push(fieldAccessor.isNull());
|
|
3087
|
-
continue;
|
|
3088
|
-
}
|
|
3089
|
-
if (!fieldAccessor.eq) throw ormError("ORM.FILTER_UNSUPPORTED", `Shorthand filter on "${relatedModelName}.${fieldName}": field does not support equality comparisons`, { meta: {
|
|
3090
|
-
model: relatedModelName,
|
|
3091
|
-
field: fieldName,
|
|
3092
|
-
trait: "equality"
|
|
3093
|
-
} });
|
|
3094
|
-
exprs.push(fieldAccessor.eq(value));
|
|
3095
|
-
}
|
|
3096
|
-
if (exprs.length === 0) return;
|
|
3097
|
-
return exprs.length === 1 ? exprs[0] : and(...exprs);
|
|
2992
|
+
function fieldCodecId(collection, field) {
|
|
2993
|
+
const { contract } = collection.ctx.context;
|
|
2994
|
+
const column = resolveFieldToColumn(contract, collection.namespaceId, collection.modelName, field);
|
|
2995
|
+
return resolveColumn(contract, collection.namespaceId, collection.tableName, column)?.codecId;
|
|
3098
2996
|
}
|
|
3099
|
-
function
|
|
3100
|
-
const
|
|
3101
|
-
|
|
3102
|
-
const joinExprs = [];
|
|
3103
|
-
const count = Math.min(localFields.length, targetFields.length);
|
|
3104
|
-
for (let i = 0; i < count; i++) {
|
|
3105
|
-
const localField = localFields[i];
|
|
3106
|
-
const targetField = targetFields[i];
|
|
3107
|
-
if (!localField || !targetField) continue;
|
|
3108
|
-
const localColumn = resolveFieldToColumn(contract, parentNamespaceId, parentModelName, localField);
|
|
3109
|
-
const targetColumn = resolveFieldToColumn(contract, relation.toNamespace, relation.to, targetField);
|
|
3110
|
-
joinExprs.push(BinaryExpr.eq(relatedTable.column(targetColumn), parentTable.column(localColumn)));
|
|
3111
|
-
}
|
|
3112
|
-
if (joinExprs.length === 0) throw new InternalError("Relation metadata is missing join columns");
|
|
3113
|
-
const firstExpr = joinExprs[0];
|
|
3114
|
-
if (joinExprs.length === 1 && firstExpr !== void 0) return firstExpr;
|
|
3115
|
-
return and(...joinExprs);
|
|
2997
|
+
function canOrder(collection, field) {
|
|
2998
|
+
const codecId = fieldCodecId(collection, field);
|
|
2999
|
+
return codecId !== void 0 && hasTrait(collection.ctx.context, codecId, "order");
|
|
3116
3000
|
}
|
|
3117
|
-
function
|
|
3118
|
-
const
|
|
3119
|
-
|
|
3120
|
-
return resolveFieldToColumn(contract, relation.toNamespace, relation.to, firstField);
|
|
3001
|
+
function orderableFieldNames(collection) {
|
|
3002
|
+
const model = modelOf(collection.ctx.context.contract, collection.namespaceId, collection.modelName);
|
|
3003
|
+
return Object.keys(model?.fields ?? {}).filter((field) => canOrder(collection, field));
|
|
3121
3004
|
}
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
contract;
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
}
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
}
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
const nextOrders = selectors.map((selector) => selector(accessor));
|
|
3175
|
-
return this.#clone({ postGroup: {
|
|
3176
|
-
...this.postGroup,
|
|
3177
|
-
orderBy: [...this.postGroup.orderBy, ...nextOrders]
|
|
3178
|
-
} });
|
|
3005
|
+
function whyNotOrderable(collection, name) {
|
|
3006
|
+
const { modelName } = collection;
|
|
3007
|
+
const model = modelOf(collection.ctx.context.contract, collection.namespaceId, modelName);
|
|
3008
|
+
const quoted = shown(name).text;
|
|
3009
|
+
if (Object.hasOwn(model?.relations ?? {}, name)) return `${quoted} is a relation of ${modelName}, not a field.`;
|
|
3010
|
+
if (!Object.hasOwn(model?.fields ?? {}, name)) return `${modelName} has no field ${quoted}.`;
|
|
3011
|
+
return canOrder(collection, name) ? `${quoted} is not one of the fields allowed for ordering.` : `The codec ${fieldCodecId(collection, name)} of ${modelName}.${name} cannot be ordered.`;
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* An `orderBy` selector for a field named by a string, such as an order parameter of a request. `allowed` lists the fields a request may order by. A name that is not in `allowed` or not an orderable field of the collection's model, and a direction other than `asc` or `desc`, throw `ORM.ARGUMENT_INVALID`. An undefined direction is `asc`.
|
|
3015
|
+
*
|
|
3016
|
+
* ```ts
|
|
3017
|
+
* db.Post.orderBy(orderByField(db.Post, input.orderBy, input.direction, ['title', 'createdAt']));
|
|
3018
|
+
* ```
|
|
3019
|
+
*/
|
|
3020
|
+
function orderByField(collection, name, direction, allowed) {
|
|
3021
|
+
const receiver = collection;
|
|
3022
|
+
if (!isModelCollection(receiver)) throw ormError("ORM.ARGUMENT_INVALID", "Cannot order: orderByField was not given a collection", {
|
|
3023
|
+
why: `orderByField takes the collection it orders, such as db.orm.public.Post; received ${describeReceived(receiver)}.`,
|
|
3024
|
+
fix: "Pass the collection the selector is for as the first argument.",
|
|
3025
|
+
meta: { argument: "collection" }
|
|
3026
|
+
});
|
|
3027
|
+
const { modelName } = collection;
|
|
3028
|
+
const requestedName = name;
|
|
3029
|
+
const requestedDirection = direction === void 0 ? "asc" : direction;
|
|
3030
|
+
const allowedList = allowed;
|
|
3031
|
+
if (!Array.isArray(allowedList) || allowedList.some((field) => typeof field !== "string")) throw ormError("ORM.ARGUMENT_INVALID", `Cannot order ${modelName}: the allowed fields are not a list of names`, {
|
|
3032
|
+
why: `orderByField takes the list of fields a request may order by; received ${describeReceived(allowedList)}.`,
|
|
3033
|
+
fix: `Pass an array of field names of ${modelName}, such as ['title', 'createdAt'].`,
|
|
3034
|
+
meta: {
|
|
3035
|
+
model: modelName,
|
|
3036
|
+
argument: "allowed"
|
|
3037
|
+
}
|
|
3038
|
+
});
|
|
3039
|
+
if (typeof requestedDirection !== "string") throw ormError("ORM.ARGUMENT_INVALID", `Cannot order ${modelName}: the direction is not a string`, {
|
|
3040
|
+
why: `An order direction is "asc" or "desc"; received ${describeReceived(requestedDirection)}.`,
|
|
3041
|
+
fix: "Pass \"asc\" or \"desc\".",
|
|
3042
|
+
meta: {
|
|
3043
|
+
model: modelName,
|
|
3044
|
+
direction: requestedDirection
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
3047
|
+
if (!isOrderByDirection(requestedDirection)) {
|
|
3048
|
+
const shownDirection = shown(requestedDirection);
|
|
3049
|
+
throw ormError("ORM.ARGUMENT_INVALID", `Cannot order ${modelName} in direction ${shownDirection.text}`, {
|
|
3050
|
+
why: "An order direction is \"asc\" or \"desc\".",
|
|
3051
|
+
fix: `Pass "asc" or "desc".${cutNote("direction", shownDirection)}`,
|
|
3052
|
+
meta: {
|
|
3053
|
+
model: modelName,
|
|
3054
|
+
direction: requestedDirection
|
|
3055
|
+
}
|
|
3056
|
+
});
|
|
3179
3057
|
}
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
}
|
|
3058
|
+
const orderable = orderableFieldNames(collection);
|
|
3059
|
+
const names = allowedList.filter((field) => orderable.includes(field));
|
|
3060
|
+
const orderByOneOf = names.length === 0 ? `Pass an allowed list that names at least one field of ${modelName} that can be ordered.` : `Order by one of: ${names.join(", ")}.`;
|
|
3061
|
+
if (typeof requestedName !== "string") throw ormError("ORM.ARGUMENT_INVALID", `Cannot order ${modelName}: the field name is not a string`, {
|
|
3062
|
+
why: `An order field name is a string; received ${describeReceived(requestedName)}.`,
|
|
3063
|
+
fix: orderByOneOf,
|
|
3064
|
+
meta: {
|
|
3065
|
+
model: modelName,
|
|
3066
|
+
field: requestedName
|
|
3067
|
+
}
|
|
3068
|
+
});
|
|
3069
|
+
if (!names.includes(requestedName)) {
|
|
3070
|
+
const shownName = shown(requestedName);
|
|
3071
|
+
throw ormError("ORM.ARGUMENT_INVALID", `Cannot order ${modelName} by ${shownName.text}`, {
|
|
3072
|
+
why: whyNotOrderable(collection, requestedName),
|
|
3073
|
+
fix: `${orderByOneOf}${cutNote("name", shownName)}`,
|
|
3074
|
+
meta: {
|
|
3075
|
+
model: modelName,
|
|
3076
|
+
field: requestedName
|
|
3077
|
+
}
|
|
3078
|
+
});
|
|
3190
3079
|
}
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3080
|
+
const field = blindCast(requestedName);
|
|
3081
|
+
const order = requestedDirection;
|
|
3082
|
+
return (row) => row[field][order]();
|
|
3083
|
+
}
|
|
3084
|
+
function hasThrough$1(relation) {
|
|
3085
|
+
return relation.through !== void 0;
|
|
3086
|
+
}
|
|
3087
|
+
var SqlTableBinding = class SqlTableBinding {
|
|
3088
|
+
#storage;
|
|
3089
|
+
#reference;
|
|
3090
|
+
constructor(storage, reference) {
|
|
3091
|
+
this.#storage = Object.freeze({ ...storage });
|
|
3092
|
+
this.#reference = reference;
|
|
3093
|
+
Object.freeze(this);
|
|
3201
3094
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
*
|
|
3205
|
-
* Accepts an optional `configure` callback that receives a
|
|
3206
|
-
* `MetaBuilder<'read'>` for attaching typed annotations.
|
|
3207
|
-
* Annotations are merged into the compiled plan's `meta.annotations`.
|
|
3208
|
-
*/
|
|
3209
|
-
get prepared() {
|
|
3210
|
-
return { aggregate: (fn, configure) => this.#describeAggregate(fn, configure) };
|
|
3095
|
+
static unaliased(storage) {
|
|
3096
|
+
return new SqlTableBinding(storage, storage.tableName);
|
|
3211
3097
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
return description.consume(queryPlanRows(this.ctx.runtime, description.plan));
|
|
3098
|
+
static aliased(storage, alias) {
|
|
3099
|
+
return new SqlTableBinding(storage, alias);
|
|
3215
3100
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3101
|
+
column(columnName) {
|
|
3102
|
+
return ColumnRef.of(this.#reference, columnName);
|
|
3103
|
+
}
|
|
3104
|
+
tableSource(contract) {
|
|
3105
|
+
return tableSourceForContract(contract, this.#storage.namespaceId, this.#storage.tableName, this.#reference);
|
|
3106
|
+
}
|
|
3107
|
+
isReferencedAs(candidate) {
|
|
3108
|
+
return this.#reference === candidate;
|
|
3109
|
+
}
|
|
3110
|
+
isStoredAt(namespaceId, tableName) {
|
|
3111
|
+
return this.#storage.namespaceId === namespaceId && this.#storage.tableName === tableName;
|
|
3112
|
+
}
|
|
3113
|
+
};
|
|
3114
|
+
var ModelAccessorScope = class ModelAccessorScope {
|
|
3115
|
+
current;
|
|
3116
|
+
#visibleBindings;
|
|
3117
|
+
#aliasCounter;
|
|
3118
|
+
constructor(current, visibleBindings, aliasCounter) {
|
|
3119
|
+
this.current = current;
|
|
3120
|
+
this.#visibleBindings = Object.freeze([...visibleBindings]);
|
|
3121
|
+
this.#aliasCounter = aliasCounter;
|
|
3122
|
+
Object.freeze(this);
|
|
3123
|
+
}
|
|
3124
|
+
static root(namespaceId, tableName) {
|
|
3125
|
+
const binding = SqlTableBinding.unaliased({
|
|
3126
|
+
namespaceId,
|
|
3127
|
+
tableName
|
|
3128
|
+
});
|
|
3129
|
+
return new ModelAccessorScope(binding, [binding], { nextId: 1 });
|
|
3130
|
+
}
|
|
3131
|
+
forRelation(namespaceId, tableName) {
|
|
3132
|
+
const binding = this.#allocateBinding(namespaceId, tableName, "rel");
|
|
3133
|
+
return new ModelAccessorScope(binding, [...this.#visibleBindings, binding], this.#aliasCounter);
|
|
3134
|
+
}
|
|
3135
|
+
forManyToManyRelation(childNamespaceId, childTableName, junctionNamespaceId, junctionTableName) {
|
|
3136
|
+
const initialChildScope = this.forRelation(childNamespaceId, childTableName);
|
|
3137
|
+
const junctionBinding = initialChildScope.#allocateBinding(junctionNamespaceId, junctionTableName, "junction");
|
|
3236
3138
|
return {
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
return (await source.toArray()).map((row) => {
|
|
3240
|
-
const mapped = mapRow(row);
|
|
3241
|
-
for (const [alias] of aggregateEntries) mapped[alias] = row[alias];
|
|
3242
|
-
return blindCast(mapped);
|
|
3243
|
-
});
|
|
3244
|
-
}
|
|
3139
|
+
childScope: new ModelAccessorScope(initialChildScope.current, [...initialChildScope.#visibleBindings, junctionBinding], this.#aliasCounter),
|
|
3140
|
+
junctionBinding
|
|
3245
3141
|
};
|
|
3246
3142
|
}
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
* inside the database, so only an operation's plain `AggregateExpr` form is
|
|
3253
|
-
* sound here: an operation outside the SQL aggregate alphabet exists only in
|
|
3254
|
-
* its descriptor-lowered form — a rendering for the driver boundary — and is
|
|
3255
|
-
* refused. The typed surface already excludes it; the runtime refusal covers
|
|
3256
|
-
* dynamic invocation.
|
|
3257
|
-
*/
|
|
3258
|
-
function createHavingBuilder(contract, aggregates, namespaceId, modelName, tableName) {
|
|
3259
|
-
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
3260
|
-
const builder = {};
|
|
3261
|
-
for (const operation of aggregateOperationNames(aggregates)) builder[operation] = (field) => {
|
|
3262
|
-
if (!isAggregateFn(operation)) throw ormError("ORM.AGGREGATE_PROJECTION_ONLY", `Aggregate operation '${operation}' is projection-only: it has no plain SQL form for HAVING, ORDER BY, or comparison positions.`, {
|
|
3263
|
-
why: "An operation outside the SQL aggregate alphabet reaches SQL only through its descriptor's lowering hook — a rendering for the driver boundary. HAVING and ORDER BY compare the value inside the database, where that rendering would change SQL semantics.",
|
|
3264
|
-
fix: `Project '${operation}' in a select and filter or order on the projected value, or use an operation from the SQL aggregate alphabet.`,
|
|
3265
|
-
meta: { operation }
|
|
3143
|
+
forJoinedSource(namespaceId, tableName) {
|
|
3144
|
+
if (this.current.isStoredAt(namespaceId, tableName)) return this;
|
|
3145
|
+
const binding = SqlTableBinding.unaliased({
|
|
3146
|
+
namespaceId,
|
|
3147
|
+
tableName
|
|
3266
3148
|
});
|
|
3267
|
-
return
|
|
3268
|
-
|
|
3269
|
-
|
|
3149
|
+
return new ModelAccessorScope(binding, [...this.#visibleBindings, binding], this.#aliasCounter);
|
|
3150
|
+
}
|
|
3151
|
+
#allocateBinding(namespaceId, tableName, aliasKind) {
|
|
3152
|
+
const storage = {
|
|
3270
3153
|
namespaceId,
|
|
3271
|
-
tableName
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
}
|
|
3276
|
-
|
|
3154
|
+
tableName
|
|
3155
|
+
};
|
|
3156
|
+
if (!this.#visibleBindings.some((binding) => binding.isReferencedAs(tableName))) return SqlTableBinding.unaliased(storage);
|
|
3157
|
+
return SqlTableBinding.aliased(storage, this.#allocateAlias(aliasKind));
|
|
3158
|
+
}
|
|
3159
|
+
#allocateAlias(kind) {
|
|
3160
|
+
while (true) {
|
|
3161
|
+
const alias = `__orm_${kind}_${this.#aliasCounter.nextId}`;
|
|
3162
|
+
this.#aliasCounter.nextId += 1;
|
|
3163
|
+
if (!this.#visibleBindings.some((binding) => binding.isReferencedAs(alias))) return alias;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
3167
|
+
function createModelAccessor(context, namespaceId, modelName, variantName) {
|
|
3168
|
+
const tableName = resolveModelTableName(context.contract, namespaceId, modelName);
|
|
3169
|
+
return createModelAccessorInScope(context, namespaceId, modelName, variantName, ModelAccessorScope.root(namespaceId, tableName));
|
|
3277
3170
|
}
|
|
3278
|
-
function
|
|
3279
|
-
const
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
};
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3171
|
+
function createModelAccessorInScope(context, namespaceId, modelName, variantName, scope) {
|
|
3172
|
+
const contract = context.contract;
|
|
3173
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
3174
|
+
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
3175
|
+
const modelRelations = resolveModelRelations(contract, namespaceId, modelName);
|
|
3176
|
+
const variantFieldColumns = variantName ? resolveVariantFieldColumns(contract, namespaceId, modelName, variantName) : {};
|
|
3177
|
+
const variantCoordinates = variantName ? {
|
|
3178
|
+
name: variantName,
|
|
3179
|
+
relations: resolveModelRelations(contract, namespaceId, variantName),
|
|
3180
|
+
tableName: resolvePolymorphismInfo(contract, namespaceId, modelName)?.variants.get(variantName)?.table ?? tableName
|
|
3181
|
+
} : void 0;
|
|
3182
|
+
const opsByCodecId = /* @__PURE__ */ new Map();
|
|
3183
|
+
function registerOp(codecId, op) {
|
|
3184
|
+
let existing = opsByCodecId.get(codecId);
|
|
3185
|
+
if (!existing) {
|
|
3186
|
+
existing = [];
|
|
3187
|
+
opsByCodecId.set(codecId, existing);
|
|
3188
|
+
}
|
|
3189
|
+
existing.push(op);
|
|
3190
|
+
}
|
|
3191
|
+
for (const [name, entry] of Object.entries(context.queryOperations.entries())) {
|
|
3192
|
+
const op = [name, entry];
|
|
3193
|
+
const self = entry.self;
|
|
3194
|
+
if (!self) continue;
|
|
3195
|
+
if (self.codecId !== void 0) registerOp(self.codecId, op);
|
|
3196
|
+
else if (self.traits !== void 0) for (const descriptor of context.codecDescriptors.values()) {
|
|
3197
|
+
const descriptorTraits = descriptor.traits;
|
|
3198
|
+
if (self.traits.every((t) => descriptorTraits.includes(t))) registerOp(descriptor.codecId, op);
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
return blindCast(new Proxy({}, { get(_target, prop) {
|
|
3202
|
+
if (typeof prop !== "string") return;
|
|
3203
|
+
if (variantCoordinates) {
|
|
3204
|
+
const variantRelation = variantCoordinates.relations[prop];
|
|
3205
|
+
if (variantRelation) return createRelationFilterAccessor(context, namespaceId, variantCoordinates.name, scope.forJoinedSource(namespaceId, variantCoordinates.tableName), variantRelation);
|
|
3206
|
+
}
|
|
3207
|
+
const relation = modelRelations[prop];
|
|
3208
|
+
if (relation) return createRelationFilterAccessor(context, namespaceId, modelName, scope, relation);
|
|
3209
|
+
const variantField = variantFieldColumns[prop];
|
|
3210
|
+
const resolvedTable = variantField?.table ?? tableName;
|
|
3211
|
+
const fieldBinding = scope.forJoinedSource(namespaceId, resolvedTable).current;
|
|
3212
|
+
const columnName = variantField?.column ?? fieldToColumn[prop] ?? prop;
|
|
3213
|
+
const column = resolveColumn(contract, namespaceId, resolvedTable, columnName);
|
|
3214
|
+
if (!column) return;
|
|
3215
|
+
const traits = codecTraits(context, column.codecId);
|
|
3216
|
+
const operations = opsByCodecId.get(column.codecId) ?? [];
|
|
3217
|
+
const codec = codecRefForStorageColumn(contract.storage, namespaceId, resolvedTable, columnName);
|
|
3218
|
+
return createScalarFieldAccessor(fieldBinding, columnName, column.codecId, column.nullable, codec, traits, operations, context);
|
|
3219
|
+
} }));
|
|
3220
|
+
}
|
|
3221
|
+
function createScalarFieldAccessor(tableBinding, columnName, codecId, nullable, codec, traits, operations, context) {
|
|
3222
|
+
const column = tableBinding.column(columnName);
|
|
3223
|
+
const comparisonEntries = [];
|
|
3224
|
+
for (const [name, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
3225
|
+
if (meta.traits.some((t) => !traits.includes(t))) continue;
|
|
3226
|
+
comparisonEntries.push([name, meta.create(column, codec)]);
|
|
3227
|
+
}
|
|
3228
|
+
const accessor = blindCast({
|
|
3229
|
+
returnType: {
|
|
3230
|
+
codecId,
|
|
3231
|
+
nullable,
|
|
3232
|
+
codec
|
|
3299
3233
|
},
|
|
3300
|
-
|
|
3301
|
-
|
|
3234
|
+
buildAst: () => column,
|
|
3235
|
+
...Object.fromEntries(comparisonEntries)
|
|
3236
|
+
});
|
|
3237
|
+
for (const [name, entry] of operations) accessor[name] = createExtensionMethodFactory(accessor, entry, context);
|
|
3238
|
+
return blindCast(accessor);
|
|
3239
|
+
}
|
|
3240
|
+
function createExtensionMethodFactory(selfExpr, entry, context) {
|
|
3241
|
+
return (...args) => {
|
|
3242
|
+
const result = blindCast(entry.impl)(selfExpr, ...args);
|
|
3243
|
+
const returnCodecId = result.returnType.codecId;
|
|
3244
|
+
const returnTraits = codecTraits(context, returnCodecId);
|
|
3245
|
+
if (returnTraits.includes("boolean")) return result.buildAst();
|
|
3246
|
+
const resultAst = result.buildAst();
|
|
3247
|
+
const returnCodec = { codecId: returnCodecId };
|
|
3248
|
+
const methods = {};
|
|
3249
|
+
for (const [resultMethodName, meta] of Object.entries(COMPARISON_METHODS_META)) {
|
|
3250
|
+
if (meta.traits.some((t) => !returnTraits.includes(t))) continue;
|
|
3251
|
+
methods[resultMethodName] = meta.create(resultAst, returnCodec);
|
|
3302
3252
|
}
|
|
3253
|
+
return methods;
|
|
3303
3254
|
};
|
|
3304
3255
|
}
|
|
3305
|
-
|
|
3256
|
+
const RELATION_ACCESSOR_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
3257
|
+
"some",
|
|
3258
|
+
"every",
|
|
3259
|
+
"none",
|
|
3260
|
+
"count"
|
|
3261
|
+
]);
|
|
3262
|
+
function createRelationFilterAccessor(context, parentNamespaceId, parentModelName, parentScope, relation) {
|
|
3263
|
+
const relatedTableName = resolveModelTableName(context.contract, relation.toNamespace, relation.to);
|
|
3264
|
+
const correlate = () => correlateRelatedRows(context, parentNamespaceId, parentModelName, parentScope, relatedTableName, relation);
|
|
3265
|
+
const filters = {
|
|
3266
|
+
some: (predicate) => buildExistsExpr(context, relation, correlate(), "some", predicate),
|
|
3267
|
+
every: (predicate) => buildExistsExpr(context, relation, correlate(), "every", predicate),
|
|
3268
|
+
none: (predicate) => buildExistsExpr(context, relation, correlate(), "none", predicate)
|
|
3269
|
+
};
|
|
3270
|
+
if (isToOneCardinality(relation.cardinality)) return new Proxy(filters, { get(target, prop) {
|
|
3271
|
+
if (typeof prop !== "string") return void 0;
|
|
3272
|
+
if (Object.hasOwn(target, prop)) return Reflect.get(target, prop);
|
|
3273
|
+
if (RELATION_ACCESSOR_METHOD_NAMES.has(prop)) return void 0;
|
|
3274
|
+
return relatedOrderableField(context, relation, relatedTableName, correlate, prop);
|
|
3275
|
+
} });
|
|
3306
3276
|
return {
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
state,
|
|
3310
|
-
...column !== void 0 ? { column } : {}
|
|
3277
|
+
...filters,
|
|
3278
|
+
count: (predicate) => createOrderable(() => buildRelationCountExpr(context, relation, correlate(), predicate))
|
|
3311
3279
|
};
|
|
3312
3280
|
}
|
|
3313
|
-
function
|
|
3281
|
+
function createOrderable(buildExpr) {
|
|
3314
3282
|
return {
|
|
3315
|
-
|
|
3316
|
-
|
|
3283
|
+
asc: (options) => checkedOrderByItem("asc", buildExpr(), options),
|
|
3284
|
+
desc: (options) => checkedOrderByItem("desc", buildExpr(), options)
|
|
3317
3285
|
};
|
|
3318
3286
|
}
|
|
3319
|
-
function
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
if (
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
}
|
|
3331
|
-
function isCollectionStateCarrier(value) {
|
|
3332
|
-
if (typeof value !== "object" || value === null) return false;
|
|
3333
|
-
return isCollectionState(value.state);
|
|
3287
|
+
function relatedOrderableField(context, relation, relatedTableName, correlate, fieldName) {
|
|
3288
|
+
const fieldToColumn = getFieldToColumnMap(context.contract, relation.toNamespace, relation.to);
|
|
3289
|
+
if (!Object.hasOwn(fieldToColumn, fieldName)) return void 0;
|
|
3290
|
+
const columnName = fieldToColumn[fieldName];
|
|
3291
|
+
if (columnName === void 0) return void 0;
|
|
3292
|
+
const column = resolveColumn(context.contract, relation.toNamespace, relatedTableName, columnName);
|
|
3293
|
+
if (!column || !hasTrait(context, column.codecId, "order")) return void 0;
|
|
3294
|
+
return createOrderable(() => {
|
|
3295
|
+
const rows = correlate();
|
|
3296
|
+
return SubqueryExpr.of(rows.source.withProjection([ProjectionItem.of(columnName, rows.childScope.current.column(columnName))]).withWhere(rows.correlation));
|
|
3297
|
+
});
|
|
3334
3298
|
}
|
|
3335
|
-
function
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
return Array.isArray(candidate.filters) && Array.isArray(candidate.includes);
|
|
3299
|
+
function buildRelationCountExpr(context, relation, rows, predicate) {
|
|
3300
|
+
const childWhere = toRelationWhereExpr(context, relation.toNamespace, relation.to, predicate, rows.childScope);
|
|
3301
|
+
return SubqueryExpr.of(rows.source.withProjection([ProjectionItem.of("count", plainAggregateExpr("count", void 0))]).withWhere(childWhere ? and(rows.correlation, childWhere) : rows.correlation));
|
|
3339
3302
|
}
|
|
3340
|
-
function
|
|
3303
|
+
function correlateRelatedRows(context, parentNamespaceId, parentModelName, parentScope, relatedTableName, relation) {
|
|
3304
|
+
if (hasThrough$1(relation)) {
|
|
3305
|
+
const { through } = relation;
|
|
3306
|
+
const { childScope, junctionBinding } = parentScope.forManyToManyRelation(relation.toNamespace, relatedTableName, through.namespaceId, through.table);
|
|
3307
|
+
const junctionJoinOn = buildPairedColumnExprs(junctionBinding, through.childColumns, childScope.current, through.targetColumns);
|
|
3308
|
+
const parentLocalColumns = relation.on.localFields.map((field) => resolveFieldToColumn(context.contract, parentNamespaceId, parentModelName, field));
|
|
3309
|
+
return {
|
|
3310
|
+
childScope,
|
|
3311
|
+
source: SelectAst.from(childScope.current.tableSource(context.contract)).withJoins([JoinAst.inner(junctionBinding.tableSource(context.contract), junctionJoinOn)]),
|
|
3312
|
+
correlation: buildPairedColumnExprs(junctionBinding, through.parentColumns, parentScope.current, parentLocalColumns),
|
|
3313
|
+
keyColumn: firstJoinColumn(through.targetColumns, "targetColumns")
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
const childScope = parentScope.forRelation(relation.toNamespace, relatedTableName);
|
|
3341
3317
|
return {
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
};
|
|
3347
|
-
},
|
|
3348
|
-
connect(criteria) {
|
|
3349
|
-
return {
|
|
3350
|
-
kind: "connect",
|
|
3351
|
-
criteria: Array.isArray(criteria) ? [...criteria] : [criteria]
|
|
3352
|
-
};
|
|
3353
|
-
},
|
|
3354
|
-
disconnect(criteria) {
|
|
3355
|
-
if (!criteria) return { kind: "disconnect" };
|
|
3356
|
-
return {
|
|
3357
|
-
kind: "disconnect",
|
|
3358
|
-
criteria: [...criteria]
|
|
3359
|
-
};
|
|
3360
|
-
}
|
|
3318
|
+
childScope,
|
|
3319
|
+
source: SelectAst.from(childScope.current.tableSource(context.contract)),
|
|
3320
|
+
correlation: buildJoinWhere(context.contract, parentNamespaceId, parentModelName, parentScope.current, childScope.current, relation),
|
|
3321
|
+
keyColumn: firstTargetColumn(context.contract, relation) ?? "id"
|
|
3361
3322
|
};
|
|
3362
3323
|
}
|
|
3363
|
-
function
|
|
3364
|
-
|
|
3365
|
-
const
|
|
3366
|
-
if (
|
|
3367
|
-
|
|
3324
|
+
function buildExistsExpr(context, relation, rows, mode, predicate) {
|
|
3325
|
+
const childWhere = toRelationWhereExpr(context, relation.toNamespace, relation.to, predicate, rows.childScope);
|
|
3326
|
+
const filterPlan = planRelationFilterMode(rows.correlation, childWhere, mode);
|
|
3327
|
+
if (filterPlan.kind === "constantTrue") return AndExpr.true();
|
|
3328
|
+
const subquery = rows.source.withProjection([ProjectionItem.of("_exists", rows.childScope.current.column(rows.keyColumn))]).withWhere(filterPlan.where);
|
|
3329
|
+
return filterPlan.notExists ? ExistsExpr.notExists(subquery) : ExistsExpr.exists(subquery);
|
|
3368
3330
|
}
|
|
3369
|
-
function
|
|
3370
|
-
|
|
3331
|
+
function planRelationFilterMode(joinWhere, childWhere, mode) {
|
|
3332
|
+
if (mode === "every") {
|
|
3333
|
+
if (!childWhere) return { kind: "constantTrue" };
|
|
3334
|
+
return {
|
|
3335
|
+
kind: "exists",
|
|
3336
|
+
notExists: true,
|
|
3337
|
+
where: and(joinWhere, not(childWhere))
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
if (mode === "none") return {
|
|
3341
|
+
kind: "exists",
|
|
3342
|
+
notExists: true,
|
|
3343
|
+
where: childWhere ? and(joinWhere, childWhere) : joinWhere
|
|
3344
|
+
};
|
|
3345
|
+
return {
|
|
3346
|
+
kind: "exists",
|
|
3347
|
+
notExists: false,
|
|
3348
|
+
where: childWhere ? and(joinWhere, childWhere) : joinWhere
|
|
3349
|
+
};
|
|
3371
3350
|
}
|
|
3372
|
-
function
|
|
3373
|
-
|
|
3351
|
+
function firstJoinColumn(columns, label) {
|
|
3352
|
+
const first = columns[0];
|
|
3353
|
+
if (!first) throw new InternalError(`Relation metadata is missing ${label}`);
|
|
3354
|
+
return first;
|
|
3374
3355
|
}
|
|
3375
|
-
function
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3356
|
+
function buildPairedColumnExprs(leftTable, leftColumns, rightTable, rightColumns) {
|
|
3357
|
+
if (leftColumns.length !== rightColumns.length) throw new InternalError(`Relation metadata has mismatched join column counts: ${leftColumns.length} left column(s), ${rightColumns.length} right column(s)`);
|
|
3358
|
+
if (leftColumns.length === 0) throw new InternalError("Relation metadata is missing join columns");
|
|
3359
|
+
const exprs = [];
|
|
3360
|
+
for (let i = 0; i < leftColumns.length; i++) {
|
|
3361
|
+
const left = leftColumns[i];
|
|
3362
|
+
const right = rightColumns[i];
|
|
3363
|
+
if (!left || !right) throw new InternalError(`Relation metadata is missing a join column pair at index ${i}`);
|
|
3364
|
+
exprs.push(BinaryExpr.eq(leftTable.column(left), rightTable.column(right)));
|
|
3380
3365
|
}
|
|
3381
|
-
return
|
|
3366
|
+
if (exprs.length === 1 && exprs[0]) return exprs[0];
|
|
3367
|
+
return and(...exprs);
|
|
3382
3368
|
}
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
const value = row[fieldName];
|
|
3400
|
-
if (value === void 0) throw new InternalError(`Missing identity field "${fieldName}" while reloading model "${modelName}"`);
|
|
3401
|
-
filter[fieldName] = value;
|
|
3402
|
-
}
|
|
3403
|
-
return filter;
|
|
3404
|
-
}
|
|
3405
|
-
async function withMutationScope(runtime, run) {
|
|
3406
|
-
if (typeof runtime.transaction === "function") return runInTransaction(await runtime.transaction(), run);
|
|
3407
|
-
if (typeof runtime.connection === "function") {
|
|
3408
|
-
const connection = await runtime.connection();
|
|
3409
|
-
try {
|
|
3410
|
-
if (typeof connection.transaction === "function") return await runInTransaction(await connection.transaction(), run);
|
|
3411
|
-
return await run(connection);
|
|
3412
|
-
} finally {
|
|
3413
|
-
await connection.release?.();
|
|
3369
|
+
function toRelationWhereExpr(context, relatedNamespaceId, relatedModelName, predicate, scope) {
|
|
3370
|
+
if (!predicate) return;
|
|
3371
|
+
const accessor = createModelAccessorInScope(context, relatedNamespaceId, relatedModelName, void 0, scope);
|
|
3372
|
+
if (typeof predicate === "function") return predicate(accessor);
|
|
3373
|
+
const exprs = [];
|
|
3374
|
+
for (const [fieldName, value] of Object.entries(predicate)) {
|
|
3375
|
+
if (value === void 0) continue;
|
|
3376
|
+
const fieldAccessor = blindCast(accessor)[fieldName];
|
|
3377
|
+
if (!fieldAccessor) throw ormError("ORM.FIELD_UNKNOWN", `Shorthand filter on "${relatedModelName}.${fieldName}": field is not defined on the model`, { meta: {
|
|
3378
|
+
model: relatedModelName,
|
|
3379
|
+
field: fieldName
|
|
3380
|
+
} });
|
|
3381
|
+
if (value === null) {
|
|
3382
|
+
if (!fieldAccessor.isNull) throw new InternalError(`Shorthand filter on "${relatedModelName}.${fieldName}": isNull is unexpectedly missing — this is a bug in trait gating`);
|
|
3383
|
+
exprs.push(fieldAccessor.isNull());
|
|
3384
|
+
continue;
|
|
3414
3385
|
}
|
|
3386
|
+
if (!fieldAccessor.eq) throw ormError("ORM.FILTER_UNSUPPORTED", `Shorthand filter on "${relatedModelName}.${fieldName}": field does not support equality comparisons`, { meta: {
|
|
3387
|
+
model: relatedModelName,
|
|
3388
|
+
field: fieldName,
|
|
3389
|
+
trait: "equality"
|
|
3390
|
+
} });
|
|
3391
|
+
exprs.push(fieldAccessor.eq(value));
|
|
3415
3392
|
}
|
|
3416
|
-
|
|
3393
|
+
if (exprs.length === 0) return;
|
|
3394
|
+
return exprs.length === 1 ? exprs[0] : and(...exprs);
|
|
3417
3395
|
}
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3396
|
+
function buildJoinWhere(contract, parentNamespaceId, parentModelName, parentTable, relatedTable, relation) {
|
|
3397
|
+
const localFields = relation.on?.localFields ?? [];
|
|
3398
|
+
const targetFields = relation.on?.targetFields ?? [];
|
|
3399
|
+
const joinExprs = [];
|
|
3400
|
+
const count = Math.min(localFields.length, targetFields.length);
|
|
3401
|
+
for (let i = 0; i < count; i++) {
|
|
3402
|
+
const localField = localFields[i];
|
|
3403
|
+
const targetField = targetFields[i];
|
|
3404
|
+
if (!localField || !targetField) continue;
|
|
3405
|
+
const localColumn = resolveFieldToColumn(contract, parentNamespaceId, parentModelName, localField);
|
|
3406
|
+
const targetColumn = resolveFieldToColumn(contract, relation.toNamespace, relation.to, targetField);
|
|
3407
|
+
joinExprs.push(BinaryExpr.eq(relatedTable.column(targetColumn), parentTable.column(localColumn)));
|
|
3426
3408
|
}
|
|
3409
|
+
if (joinExprs.length === 0) throw new InternalError("Relation metadata is missing join columns");
|
|
3410
|
+
const firstExpr = joinExprs[0];
|
|
3411
|
+
if (joinExprs.length === 1 && firstExpr !== void 0) return firstExpr;
|
|
3412
|
+
return and(...joinExprs);
|
|
3427
3413
|
}
|
|
3428
|
-
|
|
3429
|
-
const
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3414
|
+
function firstTargetColumn(contract, relation) {
|
|
3415
|
+
const firstField = (relation.on?.targetFields)?.[0];
|
|
3416
|
+
if (!firstField) return;
|
|
3417
|
+
return resolveFieldToColumn(contract, relation.toNamespace, relation.to, firstField);
|
|
3418
|
+
}
|
|
3419
|
+
var GroupedCollection = class GroupedCollection {
|
|
3420
|
+
ctx;
|
|
3421
|
+
contract;
|
|
3422
|
+
modelName;
|
|
3423
|
+
tableName;
|
|
3424
|
+
namespaceId;
|
|
3425
|
+
preGroupState;
|
|
3426
|
+
groupByFields;
|
|
3427
|
+
groupByColumns;
|
|
3428
|
+
havingFilters;
|
|
3429
|
+
postGroup;
|
|
3430
|
+
constructor(ctx, modelName, options) {
|
|
3431
|
+
this.ctx = ctx;
|
|
3432
|
+
this.contract = ctx.context.contract;
|
|
3433
|
+
this.modelName = modelName;
|
|
3434
|
+
this.tableName = options.tableName;
|
|
3435
|
+
this.namespaceId = blindCast(options.namespaceId);
|
|
3436
|
+
this.preGroupState = options.preGroupState;
|
|
3437
|
+
this.groupByFields = options.groupByFields;
|
|
3438
|
+
this.groupByColumns = options.groupByColumns;
|
|
3439
|
+
this.havingFilters = options.havingFilters;
|
|
3440
|
+
this.postGroup = options.postGroup;
|
|
3441
|
+
}
|
|
3442
|
+
#clone(overrides) {
|
|
3443
|
+
return blindCast(new GroupedCollection(this.ctx, this.modelName, {
|
|
3444
|
+
tableName: this.tableName,
|
|
3445
|
+
namespaceId: this.namespaceId,
|
|
3446
|
+
preGroupState: this.preGroupState,
|
|
3447
|
+
groupByFields: this.groupByFields,
|
|
3448
|
+
groupByColumns: this.groupByColumns,
|
|
3449
|
+
havingFilters: this.havingFilters,
|
|
3450
|
+
postGroup: this.postGroup,
|
|
3451
|
+
...overrides
|
|
3452
|
+
}));
|
|
3453
|
+
}
|
|
3454
|
+
having(predicate) {
|
|
3455
|
+
const havingExpr = predicate(createHavingBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName, this.tableName));
|
|
3456
|
+
return this.#clone({ havingFilters: [...this.havingFilters, havingExpr] });
|
|
3457
|
+
}
|
|
3458
|
+
/**
|
|
3459
|
+
* Append an `ORDER BY` clause on the grouped rows themselves — orders by
|
|
3460
|
+
* group key. Ordering by an aggregate alias needs a builder surface over
|
|
3461
|
+
* the aliases and isn't supported here. Unlocks post-group `limit(...)` /
|
|
3462
|
+
* `offset(...)`, which page a group order that would otherwise be undefined.
|
|
3463
|
+
*/
|
|
3464
|
+
orderBy(selection) {
|
|
3465
|
+
const selectors = Array.isArray(selection) ? selection : [selection];
|
|
3466
|
+
if (selectors.length === 0) throw ormError("ORM.ARGUMENT_INVALID", `orderBy() for model "${this.modelName}" requires at least one selector`, { meta: {
|
|
3467
|
+
method: "orderBy",
|
|
3468
|
+
model: this.modelName
|
|
3469
|
+
} });
|
|
3470
|
+
const accessor = createModelAccessor(this.ctx.context, this.namespaceId, this.modelName);
|
|
3471
|
+
const nextOrders = selectors.map((selector) => selector(accessor));
|
|
3472
|
+
return this.#clone({ postGroup: {
|
|
3473
|
+
...this.postGroup,
|
|
3474
|
+
orderBy: [...this.postGroup.orderBy, ...nextOrders]
|
|
3437
3475
|
} });
|
|
3438
|
-
await applyParentOwnedMutation(scope, context, namespaceId, modelName, scalarData, relationMutation.relation, relationMutation.mutation);
|
|
3439
3476
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3477
|
+
/**
|
|
3478
|
+
* Apply `LIMIT n` to the grouped rows. Replaces any previous post-group
|
|
3479
|
+
* limit. Requires a prior `orderBy(...)` — a database may return groups in
|
|
3480
|
+
* any order, so "the first n groups" is undefined without one.
|
|
3481
|
+
*/
|
|
3482
|
+
limit(n) {
|
|
3483
|
+
return this.#clone({ postGroup: {
|
|
3484
|
+
...this.postGroup,
|
|
3485
|
+
limit: typeof n === "number" ? n : toExpr(n)
|
|
3444
3486
|
} });
|
|
3445
|
-
await preflightJunctionOwnedCreateMutation(scope, context, relationMutation);
|
|
3446
3487
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3488
|
+
/**
|
|
3489
|
+
* Apply `OFFSET n` to the grouped rows. Replaces any previous post-group
|
|
3490
|
+
* offset. Requires a prior `orderBy(...)`, same as `limit(...)` — both
|
|
3491
|
+
* pagination methods need a deterministic group order.
|
|
3492
|
+
*/
|
|
3493
|
+
offset(n) {
|
|
3494
|
+
return this.#clone({ postGroup: {
|
|
3495
|
+
...this.postGroup,
|
|
3496
|
+
offset: typeof n === "number" ? n : toExpr(n)
|
|
3452
3497
|
} });
|
|
3453
|
-
await applyChildOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
3454
3498
|
}
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
const scalarData = { ...parsed.scalarData };
|
|
3465
|
-
for (const relationMutation of parentOwned) await applyParentOwnedMutation(scope, context, namespaceId, modelName, scalarData, relationMutation.relation, relationMutation.mutation);
|
|
3466
|
-
for (const relationMutation of junctionOwned) await preflightJunctionOwnedCreateMutation(scope, context, relationMutation);
|
|
3467
|
-
let parentRow = existingRow;
|
|
3468
|
-
const mappedUpdateData = mapModelDataToStorageRow(contract, namespaceId, modelName, scalarData);
|
|
3469
|
-
if (Object.keys(mappedUpdateData).length > 0) {
|
|
3470
|
-
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
3471
|
-
const appliedUpdateDefaults = context.applyMutationDefaults({
|
|
3472
|
-
op: "update",
|
|
3473
|
-
entry: tableName,
|
|
3474
|
-
namespace: namespaceId,
|
|
3475
|
-
values: mappedUpdateData
|
|
3476
|
-
});
|
|
3477
|
-
for (const def of appliedUpdateDefaults) mappedUpdateData[def.field] = def.value;
|
|
3478
|
-
const identityWhere = shorthandToWhereExpr(context, namespaceId, modelName, castAs(buildRowIdentityFilterFromRow(contract, namespaceId, modelName, existingRow)));
|
|
3479
|
-
if (!identityWhere) throw new InternalError(`Failed to build row identity filter for model "${modelName}"`);
|
|
3480
|
-
const updatedRaw = (await queryPlanRows(scope, compileUpdateReturning(contract, namespaceId, tableName, mappedUpdateData, [identityWhere], void 0)).toArray())[0];
|
|
3481
|
-
if (updatedRaw) parentRow = mapStorageRowToModelFields(contract, namespaceId, modelName, updatedRaw);
|
|
3499
|
+
/**
|
|
3500
|
+
* Read terminal: run a grouped aggregate query.
|
|
3501
|
+
*
|
|
3502
|
+
* Accepts an optional `configure` callback that receives a
|
|
3503
|
+
* `MetaBuilder<'read'>` for attaching typed annotations.
|
|
3504
|
+
* Annotations are merged into the compiled plan's `meta.annotations`.
|
|
3505
|
+
*/
|
|
3506
|
+
get prepared() {
|
|
3507
|
+
return { aggregate: (fn, configure) => this.#describeAggregate(fn, configure) };
|
|
3482
3508
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
}
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
if (!relation) {
|
|
3494
|
-
scalarData[fieldName] = value;
|
|
3495
|
-
continue;
|
|
3496
|
-
}
|
|
3497
|
-
if (!isRelationMutationCallback(value)) throw ormError("ORM.RELATION_MUTATION_INVALID", `Relation field "${fieldName}" on model "${modelName}" expects a mutator callback`, { meta: {
|
|
3498
|
-
relation: fieldName,
|
|
3499
|
-
model: modelName,
|
|
3500
|
-
problem: "missing-callback"
|
|
3509
|
+
async aggregate(fn, configure) {
|
|
3510
|
+
const description = this.#describeAggregate(fn, configure);
|
|
3511
|
+
return description.consume(queryPlanRows(this.ctx.runtime, description.plan));
|
|
3512
|
+
}
|
|
3513
|
+
#describeAggregate(fn, configure) {
|
|
3514
|
+
const aggregateSpec = fn(createAggregateBuilder(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.modelName));
|
|
3515
|
+
const aggregateEntries = Object.entries(aggregateSpec);
|
|
3516
|
+
if (aggregateEntries.length === 0) throw ormError("ORM.AGGREGATE_SELECTOR_MISSING", "groupBy().aggregate() requires at least one aggregation selector", { meta: {
|
|
3517
|
+
method: "groupBy.aggregate",
|
|
3518
|
+
model: this.modelName
|
|
3501
3519
|
} });
|
|
3502
|
-
const
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
problem: "invalid-descriptor"
|
|
3520
|
+
for (const [alias, selector] of aggregateEntries) if (!isAggregateSelector(selector)) throw ormError("ORM.AGGREGATE_SELECTOR_INVALID", `groupBy().aggregate() selector "${alias}" is invalid`, { meta: {
|
|
3521
|
+
method: "groupBy.aggregate",
|
|
3522
|
+
model: this.modelName,
|
|
3523
|
+
alias
|
|
3507
3524
|
} });
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
return {
|
|
3514
|
-
scalarData,
|
|
3515
|
-
relationMutations
|
|
3516
|
-
};
|
|
3517
|
-
}
|
|
3518
|
-
function partitionByOwnership(relationMutations) {
|
|
3519
|
-
const parentOwned = [];
|
|
3520
|
-
const childOwned = [];
|
|
3521
|
-
const junctionOwned = [];
|
|
3522
|
-
for (const relationMutation of relationMutations) {
|
|
3523
|
-
if (hasThrough(relationMutation.relation)) {
|
|
3524
|
-
junctionOwned.push({
|
|
3525
|
-
relation: relationMutation.relation,
|
|
3526
|
-
mutation: relationMutation.mutation
|
|
3527
|
-
});
|
|
3528
|
-
continue;
|
|
3529
|
-
}
|
|
3530
|
-
if (relationMutation.relation.cardinality === "N:1") {
|
|
3531
|
-
parentOwned.push(relationMutation);
|
|
3532
|
-
continue;
|
|
3525
|
+
let annotationsMap;
|
|
3526
|
+
if (configure !== void 0) {
|
|
3527
|
+
const meta = createMetaBuilder("read", "groupBy.aggregate");
|
|
3528
|
+
configure(meta);
|
|
3529
|
+
if (meta.annotations.size > 0) annotationsMap = meta.annotations;
|
|
3533
3530
|
}
|
|
3534
|
-
|
|
3531
|
+
const compiled = mergeAnnotations(compileGroupedAggregate(this.contract, this.ctx.context.aggregateDescriptors, this.namespaceId, this.tableName, this.preGroupState, this.groupByColumns, aggregateSpec, combineWhereExprs(this.havingFilters), this.modelName, this.postGroup), annotationsMap);
|
|
3532
|
+
const mapRow = createStorageRowMapper(this.contract, this.namespaceId, this.modelName);
|
|
3533
|
+
return {
|
|
3534
|
+
plan: compiled,
|
|
3535
|
+
async consume(source) {
|
|
3536
|
+
return (await source.toArray()).map((row) => {
|
|
3537
|
+
const mapped = mapRow(row);
|
|
3538
|
+
for (const [alias] of aggregateEntries) mapped[alias] = row[alias];
|
|
3539
|
+
return blindCast(mapped);
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
};
|
|
3535
3543
|
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3544
|
+
};
|
|
3545
|
+
/**
|
|
3546
|
+
* The having metric methods, one per operation the registry contributes —
|
|
3547
|
+
* the runtime mirror of the contract's emitted aggregate map, which is what
|
|
3548
|
+
* types the surface as {@link HavingBuilder}. HAVING compares the value
|
|
3549
|
+
* inside the database, so only an operation's plain `AggregateExpr` form is
|
|
3550
|
+
* sound here: an operation outside the SQL aggregate alphabet exists only in
|
|
3551
|
+
* its descriptor-lowered form — a rendering for the driver boundary — and is
|
|
3552
|
+
* refused. The typed surface already excludes it; the runtime refusal covers
|
|
3553
|
+
* dynamic invocation.
|
|
3554
|
+
*/
|
|
3555
|
+
function createHavingBuilder(contract, aggregates, namespaceId, modelName, tableName) {
|
|
3556
|
+
const fieldToColumn = getFieldToColumnMap(contract, namespaceId, modelName);
|
|
3557
|
+
const builder = {};
|
|
3558
|
+
for (const operation of aggregateOperationNames(aggregates)) builder[operation] = (field) => {
|
|
3559
|
+
if (!isAggregateFn(operation)) throw ormError("ORM.AGGREGATE_PROJECTION_ONLY", `Aggregate operation '${operation}' is projection-only: it has no plain SQL form for HAVING, ORDER BY, or comparison positions.`, {
|
|
3560
|
+
why: "An operation outside the SQL aggregate alphabet reaches SQL only through its descriptor's lowering hook — a rendering for the driver boundary. HAVING and ORDER BY compare the value inside the database, where that rendering would change SQL semantics.",
|
|
3561
|
+
fix: `Project '${operation}' in a select and filter or order on the projected value, or use an operation from the SQL aggregate alphabet.`,
|
|
3562
|
+
meta: { operation }
|
|
3563
|
+
});
|
|
3564
|
+
return createHavingComparisonMethods(new AggregateExpr(operation, field === void 0 ? void 0 : ColumnRef.of(tableName, fieldToColumn[field] ?? field)), () => resolveAggregate({
|
|
3565
|
+
aggregates,
|
|
3566
|
+
contract,
|
|
3567
|
+
namespaceId,
|
|
3568
|
+
tableName,
|
|
3569
|
+
fn: operation,
|
|
3570
|
+
column: field === void 0 ? void 0 : fieldToColumn[field] ?? field
|
|
3571
|
+
}).codec.codecId);
|
|
3540
3572
|
};
|
|
3573
|
+
return blindCast(builder);
|
|
3541
3574
|
}
|
|
3542
|
-
|
|
3543
|
-
const
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
}
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
} });
|
|
3567
|
-
const relatedRow = await findRowByCriterion(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
3568
|
-
if (!relatedRow) throw ormError("ORM.RELATION_ROW_MISSING", `connect() nested mutation for relation "${relation.relationName}" did not find a matching row`, { meta: {
|
|
3569
|
-
kind: "connect",
|
|
3570
|
-
relation: relation.relationName
|
|
3571
|
-
} });
|
|
3572
|
-
copyRelatedValuesToParent(contract, parentNamespaceId, parentModelName, relation, scalarData, relatedRow);
|
|
3573
|
-
}
|
|
3574
|
-
function copyRelatedValuesToParent(contract, parentNamespaceId, parentModelName, relation, scalarData, relatedRow) {
|
|
3575
|
-
for (let i = 0; i < relation.localColumns.length; i++) {
|
|
3576
|
-
const localColumn = relation.localColumns[i];
|
|
3577
|
-
const targetColumn = relation.targetColumns[i];
|
|
3578
|
-
if (!localColumn || !targetColumn) continue;
|
|
3579
|
-
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, localColumn);
|
|
3580
|
-
scalarData[parentFieldName] = relatedRow[toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, targetColumn)];
|
|
3581
|
-
}
|
|
3582
|
-
}
|
|
3583
|
-
async function applyChildOwnedMutation(scope, context, parentNamespaceId, parentModelName, parentRow, relation, mutation) {
|
|
3584
|
-
const contract = context.contract;
|
|
3585
|
-
const parentValues = readParentColumnValues(contract, parentNamespaceId, parentModelName, relation, parentRow);
|
|
3586
|
-
if (mutation.kind === "create") {
|
|
3587
|
-
for (const childInput of mutation.data) {
|
|
3588
|
-
const payload = { ...castAs(childInput) };
|
|
3589
|
-
for (const [childColumn, parentValue] of parentValues.entries()) {
|
|
3590
|
-
const childFieldName = toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, childColumn);
|
|
3591
|
-
payload[childFieldName] = parentValue;
|
|
3592
|
-
}
|
|
3593
|
-
await createGraph(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(payload));
|
|
3594
|
-
}
|
|
3595
|
-
return;
|
|
3596
|
-
}
|
|
3597
|
-
if (mutation.kind === "connect") {
|
|
3598
|
-
for (const criterion of mutation.criteria) {
|
|
3599
|
-
const criterionWhere = shorthandToWhereExpr(context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
3600
|
-
if (!criterionWhere) throw ormError("ORM.RELATION_MUTATION_INVALID", `connect() nested mutation for relation "${relation.relationName}" requires non-empty criterion`, { meta: {
|
|
3601
|
-
kind: "connect",
|
|
3602
|
-
relation: relation.relationName,
|
|
3603
|
-
problem: "empty-criterion"
|
|
3604
|
-
} });
|
|
3605
|
-
const setValues = {};
|
|
3606
|
-
for (const [childColumn, parentValue] of parentValues.entries()) setValues[childColumn] = parentValue;
|
|
3607
|
-
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [criterionWhere]);
|
|
3575
|
+
function createHavingComparisonMethods(metric, outputCodecId) {
|
|
3576
|
+
const buildBinaryExpr = (op, value) => {
|
|
3577
|
+
const expression = predicateExpression(value);
|
|
3578
|
+
if (expression !== void 0 && (expression.kind !== "prepared-param-ref" || expression.codec.codecId !== outputCodecId())) throw ormError("ORM.HAVING_EXPRESSION_UNSUPPORTED", "HAVING comparands require a prepared parameter with the aggregate output codec", { meta: { kind: expression.kind } });
|
|
3579
|
+
return predicateComparison(op, metric, expression ?? LiteralExpr.of(value));
|
|
3580
|
+
};
|
|
3581
|
+
return {
|
|
3582
|
+
eq(value) {
|
|
3583
|
+
return buildBinaryExpr("eq", value);
|
|
3584
|
+
},
|
|
3585
|
+
neq(value) {
|
|
3586
|
+
return buildBinaryExpr("neq", value);
|
|
3587
|
+
},
|
|
3588
|
+
gt(value) {
|
|
3589
|
+
return buildBinaryExpr("gt", value);
|
|
3590
|
+
},
|
|
3591
|
+
lt(value) {
|
|
3592
|
+
return buildBinaryExpr("lt", value);
|
|
3593
|
+
},
|
|
3594
|
+
gte(value) {
|
|
3595
|
+
return buildBinaryExpr("gte", value);
|
|
3596
|
+
},
|
|
3597
|
+
lte(value) {
|
|
3598
|
+
return buildBinaryExpr("lte", value);
|
|
3608
3599
|
}
|
|
3609
|
-
|
|
3610
|
-
}
|
|
3611
|
-
const setValues = {};
|
|
3612
|
-
for (const childColumn of parentValues.keys()) setValues[childColumn] = null;
|
|
3613
|
-
if (!mutation.criteria || mutation.criteria.length === 0) {
|
|
3614
|
-
const parentJoinWhere = buildChildJoinWhere(relation, parentValues);
|
|
3615
|
-
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [parentJoinWhere]);
|
|
3616
|
-
return;
|
|
3617
|
-
}
|
|
3618
|
-
for (const criterion of mutation.criteria) {
|
|
3619
|
-
const criterionWhere = shorthandToWhereExpr(context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
3620
|
-
if (!criterionWhere) throw ormError("ORM.RELATION_MUTATION_INVALID", `disconnect() nested mutation for relation "${relation.relationName}" requires non-empty criterion`, { meta: {
|
|
3621
|
-
kind: "disconnect",
|
|
3622
|
-
relation: relation.relationName,
|
|
3623
|
-
problem: "empty-criterion"
|
|
3624
|
-
} });
|
|
3625
|
-
const parentJoinWhere = buildChildJoinWhere(relation, parentValues);
|
|
3626
|
-
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [and(parentJoinWhere, criterionWhere)]);
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
async function applyJunctionOwnedMutation(scope, context, parentNamespaceId, parentModelName, parentRow, relation, mutation) {
|
|
3630
|
-
const contract = context.contract;
|
|
3631
|
-
const parentPkValues = readJunctionParentValues(contract, parentNamespaceId, parentModelName, relation, parentRow);
|
|
3632
|
-
assertJunctionPayloadWritable(relation, mutation.kind);
|
|
3633
|
-
if (mutation.kind === "create") {
|
|
3634
|
-
for (const childInput of mutation.data) await insertJunctionLink(scope, context, relation, parentPkValues, readJunctionTargetValues(contract, relation, await createGraph(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(childInput))), "create");
|
|
3635
|
-
return;
|
|
3636
|
-
}
|
|
3637
|
-
if (mutation.kind === "connect") {
|
|
3638
|
-
for (const criterion of mutation.criteria) await insertJunctionLink(scope, context, relation, parentPkValues, await resolveJunctionTargetValues(scope, context, relation, "connect", criterion), "connect");
|
|
3639
|
-
return;
|
|
3640
|
-
}
|
|
3641
|
-
if (!mutation.criteria || mutation.criteria.length === 0) throw ormError("ORM.RELATION_MUTATION_INVALID", `disconnect() nested mutation for relation "${relation.relationName}" requires criterion`, { meta: {
|
|
3642
|
-
kind: "disconnect",
|
|
3643
|
-
relation: relation.relationName,
|
|
3644
|
-
problem: "missing-criterion"
|
|
3645
|
-
} });
|
|
3646
|
-
for (const criterion of mutation.criteria) await deleteJunctionLink(scope, context, relation, parentPkValues, await resolveJunctionTargetValues(scope, context, relation, "disconnect", criterion));
|
|
3647
|
-
}
|
|
3648
|
-
async function preflightJunctionOwnedCreateMutation(scope, context, relationMutation) {
|
|
3649
|
-
const { relation, mutation } = relationMutation;
|
|
3650
|
-
assertJunctionMetadataShape(relation);
|
|
3651
|
-
assertJunctionPayloadWritable(relation, mutation.kind);
|
|
3652
|
-
if (mutation.kind !== "connect") return;
|
|
3653
|
-
const seenTargetKeys = /* @__PURE__ */ new Set();
|
|
3654
|
-
for (const criterion of mutation.criteria) {
|
|
3655
|
-
const targetValues = await resolveJunctionTargetValues(scope, context, relation, "connect", criterion);
|
|
3656
|
-
const targetKey = JSON.stringify([...targetValues.entries()]);
|
|
3657
|
-
if (seenTargetKeys.has(targetKey)) throw ormError("ORM.RELATION_MUTATION_INVALID", `connect() nested mutation for relation "${relation.relationName}" resolved duplicate junction link targets; remove the duplicate criteria`, { meta: {
|
|
3658
|
-
kind: "connect",
|
|
3659
|
-
relation: relation.relationName,
|
|
3660
|
-
problem: "duplicate-criteria"
|
|
3661
|
-
} });
|
|
3662
|
-
seenTargetKeys.add(targetKey);
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
function assertJunctionPayloadWritable(relation, mutationKind) {
|
|
3666
|
-
const through = relation.through;
|
|
3667
|
-
if (mutationKind !== "create" && mutationKind !== "connect" || through.requiredPayloadColumns.length === 0) return;
|
|
3668
|
-
const cols = through.requiredPayloadColumns.map((c) => `\`${c}\``).join(", ");
|
|
3669
|
-
throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", `Cannot \`${mutationKind}\` on relation \`${relation.relationName}\`: its junction \`${through.table}\` has required column(s) ${cols} the relation API can't populate. Write the \`${through.table}\` junction directly or use the SQL builder.`, { meta: {
|
|
3670
|
-
kind: mutationKind,
|
|
3671
|
-
relation: relation.relationName,
|
|
3672
|
-
reason: "junction-required-columns",
|
|
3673
|
-
junction: through.table
|
|
3674
|
-
} });
|
|
3675
|
-
}
|
|
3676
|
-
async function resolveJunctionTargetValues(scope, context, relation, kind, criterion) {
|
|
3677
|
-
const relatedRow = await findRowByCriterion(scope, context, relation.relatedNamespaceId, relation.relatedModelName, criterion);
|
|
3678
|
-
if (!relatedRow) throw ormError("ORM.RELATION_ROW_MISSING", `${kind}() nested mutation for relation "${relation.relationName}" did not find a matching row`, { meta: {
|
|
3679
|
-
kind,
|
|
3680
|
-
relation: relation.relationName
|
|
3681
|
-
} });
|
|
3682
|
-
return readJunctionTargetValues(context.contract, relation, relatedRow);
|
|
3683
|
-
}
|
|
3684
|
-
function readJunctionParentValues(contract, parentNamespaceId, parentModelName, relation, parentRow) {
|
|
3685
|
-
const values = /* @__PURE__ */ new Map();
|
|
3686
|
-
assertJunctionParentMetadataLength(relation);
|
|
3687
|
-
for (let i = 0; i < relation.through.parentColumns.length; i++) {
|
|
3688
|
-
const junctionColumn = relation.through.parentColumns[i];
|
|
3689
|
-
const parentColumn = relation.localColumns[i];
|
|
3690
|
-
if (junctionColumn === void 0 || parentColumn === void 0) throw new InternalError(`Relation "${relation.relationName}" has incomplete junction metadata for parent columns`);
|
|
3691
|
-
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, parentColumn);
|
|
3692
|
-
const parentValue = parentRow[parentFieldName];
|
|
3693
|
-
if (parentValue === void 0) throw new InternalError(`Nested mutation requires parent field "${parentFieldName}" to be present in returned row`);
|
|
3694
|
-
values.set(junctionColumn, parentValue);
|
|
3695
|
-
}
|
|
3696
|
-
return values;
|
|
3697
|
-
}
|
|
3698
|
-
function readJunctionTargetValues(contract, relation, relatedRow) {
|
|
3699
|
-
const values = /* @__PURE__ */ new Map();
|
|
3700
|
-
assertJunctionTargetMetadataLength(relation);
|
|
3701
|
-
for (let i = 0; i < relation.through.childColumns.length; i++) {
|
|
3702
|
-
const junctionColumn = relation.through.childColumns[i];
|
|
3703
|
-
const targetColumn = relation.through.targetColumns[i];
|
|
3704
|
-
if (junctionColumn === void 0 || targetColumn === void 0) throw new InternalError(`Relation "${relation.relationName}" has incomplete junction metadata for target columns`);
|
|
3705
|
-
const targetFieldName = toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, targetColumn);
|
|
3706
|
-
const targetValue = relatedRow[targetFieldName];
|
|
3707
|
-
if (targetValue === void 0) throw new InternalError(`Nested mutation requires target field "${targetFieldName}" to be present in returned row`);
|
|
3708
|
-
values.set(junctionColumn, targetValue);
|
|
3709
|
-
}
|
|
3710
|
-
return values;
|
|
3711
|
-
}
|
|
3712
|
-
function assertJunctionMetadataLength(relation, throughColumnName, throughColumns, pairedColumnName, pairedColumns) {
|
|
3713
|
-
if (throughColumns.length === pairedColumns.length) return;
|
|
3714
|
-
throw new InternalError(`Relation "${relation.relationName}" has invalid junction metadata: ${throughColumnName} has ${throughColumns.length} column(s), but ${pairedColumnName} has ${pairedColumns.length}`);
|
|
3715
|
-
}
|
|
3716
|
-
function assertJunctionParentMetadataLength(relation) {
|
|
3717
|
-
assertJunctionMetadataLength(relation, "parentColumns", relation.through.parentColumns, "localColumns", relation.localColumns);
|
|
3718
|
-
}
|
|
3719
|
-
function assertJunctionTargetMetadataLength(relation) {
|
|
3720
|
-
assertJunctionMetadataLength(relation, "childColumns", relation.through.childColumns, "targetColumns", relation.through.targetColumns);
|
|
3600
|
+
};
|
|
3721
3601
|
}
|
|
3722
|
-
function
|
|
3723
|
-
|
|
3724
|
-
|
|
3602
|
+
function createIncludeScalar(fn, state, column) {
|
|
3603
|
+
return {
|
|
3604
|
+
kind: "includeScalar",
|
|
3605
|
+
fn,
|
|
3606
|
+
state,
|
|
3607
|
+
...column !== void 0 ? { column } : {}
|
|
3608
|
+
};
|
|
3725
3609
|
}
|
|
3726
|
-
function
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
} });
|
|
3732
|
-
junctionRow[column] = value;
|
|
3610
|
+
function createIncludeCombine(branches) {
|
|
3611
|
+
return {
|
|
3612
|
+
kind: "includeCombine",
|
|
3613
|
+
branches
|
|
3614
|
+
};
|
|
3733
3615
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
const
|
|
3737
|
-
|
|
3738
|
-
for (const [column, value] of targetPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
3739
|
-
const applied = context.applyMutationDefaults({
|
|
3740
|
-
op: "create",
|
|
3741
|
-
entry: through.table,
|
|
3742
|
-
namespace: through.namespaceId,
|
|
3743
|
-
values: junctionRow
|
|
3744
|
-
});
|
|
3745
|
-
for (const def of applied) junctionRow[def.field] = def.value;
|
|
3746
|
-
const compiled = compileInsertCount(context.contract, through.namespaceId, through.table, [junctionRow]);
|
|
3747
|
-
try {
|
|
3748
|
-
await scope.execute(compiled);
|
|
3749
|
-
} catch (error) {
|
|
3750
|
-
if (mutationKind === "connect" && isUniqueConstraintViolation(error)) throw ormError("ORM.RELATION_LINK_DUPLICATE", `connect() nested mutation for relation "${relation.relationName}" violated a unique constraint on junction "${through.table}"; the junction link may already be present`, {
|
|
3751
|
-
meta: {
|
|
3752
|
-
relation: relation.relationName,
|
|
3753
|
-
junction: through.table
|
|
3754
|
-
},
|
|
3755
|
-
cause: error
|
|
3756
|
-
});
|
|
3757
|
-
throw error;
|
|
3758
|
-
}
|
|
3616
|
+
function isIncludeScalar(value) {
|
|
3617
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3618
|
+
const candidate = value;
|
|
3619
|
+
return candidate.kind === "includeScalar" && typeof candidate.fn === "string" && isCollectionState(candidate.state);
|
|
3759
3620
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
const
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
for (const [column, value] of Object.entries(junctionRow)) exprs.push(BinaryExpr.eq(ColumnRef.of(through.table, column), LiteralExpr.of(value)));
|
|
3767
|
-
const first = exprs[0];
|
|
3768
|
-
const where = exprs.length === 1 && first !== void 0 ? first : and(...exprs);
|
|
3769
|
-
const compiled = compileDeleteCount(context.contract, through.namespaceId, through.table, [where]);
|
|
3770
|
-
await scope.execute(compiled);
|
|
3621
|
+
function isIncludeCombine(value) {
|
|
3622
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3623
|
+
const candidate = value;
|
|
3624
|
+
if (candidate.kind !== "includeCombine") return false;
|
|
3625
|
+
if (typeof candidate.branches !== "object" || candidate.branches === null) return false;
|
|
3626
|
+
return true;
|
|
3771
3627
|
}
|
|
3772
|
-
function
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
const localColumn = relation.localColumns[i];
|
|
3776
|
-
const targetColumn = relation.targetColumns[i];
|
|
3777
|
-
if (!localColumn || !targetColumn) continue;
|
|
3778
|
-
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, localColumn);
|
|
3779
|
-
const parentValue = parentRow[parentFieldName];
|
|
3780
|
-
if (parentValue === void 0) throw new InternalError(`Nested mutation requires parent field "${parentFieldName}" to be present in returned row`);
|
|
3781
|
-
values.set(targetColumn, parentValue);
|
|
3782
|
-
}
|
|
3783
|
-
return values;
|
|
3628
|
+
function isCollectionStateCarrier(value) {
|
|
3629
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3630
|
+
return isCollectionState(value.state);
|
|
3784
3631
|
}
|
|
3785
|
-
function
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
if (exprs.length === 1 && first !== void 0) return first;
|
|
3790
|
-
return and(...exprs);
|
|
3632
|
+
function isCollectionState(value) {
|
|
3633
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3634
|
+
const candidate = value;
|
|
3635
|
+
return Array.isArray(candidate.filters) && Array.isArray(candidate.includes);
|
|
3791
3636
|
}
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3637
|
+
function createRelationMutator() {
|
|
3638
|
+
return {
|
|
3639
|
+
create(data) {
|
|
3640
|
+
return {
|
|
3641
|
+
kind: "create",
|
|
3642
|
+
data: Array.isArray(data) ? [...data] : [data]
|
|
3643
|
+
};
|
|
3644
|
+
},
|
|
3645
|
+
connect(criteria) {
|
|
3646
|
+
return {
|
|
3647
|
+
kind: "connect",
|
|
3648
|
+
criteria: Array.isArray(criteria) ? [...criteria] : [criteria]
|
|
3649
|
+
};
|
|
3650
|
+
},
|
|
3651
|
+
disconnect(criteria) {
|
|
3652
|
+
if (!criteria) return { kind: "disconnect" };
|
|
3653
|
+
return {
|
|
3654
|
+
kind: "disconnect",
|
|
3655
|
+
criteria: [...criteria]
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
};
|
|
3810
3659
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
const
|
|
3814
|
-
if (
|
|
3815
|
-
|
|
3816
|
-
model: modelName,
|
|
3817
|
-
problem: "empty-criterion"
|
|
3818
|
-
} });
|
|
3819
|
-
const firstRow = (await queryPlanRows(scope, compileSelect(contract, namespaceId, resolveModelTableName(contract, namespaceId, modelName), {
|
|
3820
|
-
...emptyState(),
|
|
3821
|
-
filters: [whereExpr],
|
|
3822
|
-
limit: 1
|
|
3823
|
-
})).toArray())[0];
|
|
3824
|
-
if (!firstRow) return null;
|
|
3825
|
-
return mapStorageRowToModelFields(contract, namespaceId, modelName, firstRow);
|
|
3660
|
+
function isRelationMutationDescriptor(value) {
|
|
3661
|
+
if (!value || typeof value !== "object") return false;
|
|
3662
|
+
const candidate = value;
|
|
3663
|
+
if (candidate.kind !== "create" && candidate.kind !== "connect" && candidate.kind !== "disconnect") return false;
|
|
3664
|
+
return true;
|
|
3826
3665
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
...emptyState(),
|
|
3830
|
-
filters,
|
|
3831
|
-
limit: 1
|
|
3832
|
-
})).toArray())[0];
|
|
3833
|
-
if (!firstRow) return null;
|
|
3834
|
-
return mapStorageRowToModelFields(contract, namespaceId, modelName, firstRow);
|
|
3666
|
+
function isRelationMutationCallback(value) {
|
|
3667
|
+
return typeof value === "function";
|
|
3835
3668
|
}
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
await scope.execute(compiled);
|
|
3669
|
+
function hasThrough(relation) {
|
|
3670
|
+
return relation.through !== void 0;
|
|
3839
3671
|
}
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
relationDefsCache.set(contract, perContract);
|
|
3672
|
+
function hasNestedMutationCallbacks(contract, namespaceId, modelName, data) {
|
|
3673
|
+
const relationNames = new Set(Object.keys(resolveModelRelations(contract, namespaceId, modelName)));
|
|
3674
|
+
for (const [fieldName, value] of Object.entries(data)) {
|
|
3675
|
+
if (!relationNames.has(fieldName)) continue;
|
|
3676
|
+
if (isRelationMutationCallback(value)) return true;
|
|
3846
3677
|
}
|
|
3847
|
-
|
|
3848
|
-
const cached = perContract.get(cacheKey);
|
|
3849
|
-
if (cached) return cached;
|
|
3850
|
-
const relations = resolveModelRelations(contract, namespaceId, modelName);
|
|
3851
|
-
const definitions = Object.entries(relations).map(([relationName, relation]) => ({
|
|
3852
|
-
relationName,
|
|
3853
|
-
relatedModelName: relation.to,
|
|
3854
|
-
relatedNamespaceId: relation.toNamespace,
|
|
3855
|
-
relatedTableName: resolveModelTableName(contract, relation.toNamespace, relation.to),
|
|
3856
|
-
cardinality: relation.cardinality,
|
|
3857
|
-
localColumns: relation.on.localFields.map((f) => resolveFieldToColumn(contract, namespaceId, modelName, f)),
|
|
3858
|
-
targetColumns: relation.on.targetFields.map((f) => resolveFieldToColumn(contract, relation.toNamespace, relation.to, f)),
|
|
3859
|
-
through: relation.through ? {
|
|
3860
|
-
table: relation.through.table,
|
|
3861
|
-
namespaceId: relation.through.namespaceId,
|
|
3862
|
-
parentColumns: relation.through.parentColumns,
|
|
3863
|
-
childColumns: relation.through.childColumns,
|
|
3864
|
-
targetColumns: relation.through.targetColumns,
|
|
3865
|
-
requiredPayloadColumns: relation.through.requiredPayloadColumns
|
|
3866
|
-
} : void 0
|
|
3867
|
-
}));
|
|
3868
|
-
perContract.set(cacheKey, definitions);
|
|
3869
|
-
return definitions;
|
|
3870
|
-
}
|
|
3871
|
-
function toFieldName(contract, namespaceId, modelName, columnName) {
|
|
3872
|
-
return getColumnToFieldMap(contract, namespaceId, modelName)[columnName] ?? columnName;
|
|
3678
|
+
return false;
|
|
3873
3679
|
}
|
|
3874
|
-
function
|
|
3875
|
-
return
|
|
3680
|
+
async function executeNestedCreateMutation(options) {
|
|
3681
|
+
return withMutationScope(options.runtime, async (scope) => createGraph(scope, options.context, options.namespaceId, options.modelName, options.data));
|
|
3876
3682
|
}
|
|
3877
|
-
function
|
|
3878
|
-
return
|
|
3683
|
+
async function executeNestedUpdateMutation(options) {
|
|
3684
|
+
return withMutationScope(options.runtime, async (scope) => updateFirstGraph(scope, options.context, options.namespaceId, options.modelName, options.filters, options.data));
|
|
3879
3685
|
}
|
|
3880
|
-
function
|
|
3881
|
-
|
|
3686
|
+
function buildRowIdentityFilterFromRow(contract, namespaceId, modelName, row) {
|
|
3687
|
+
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
3688
|
+
const identityColumns = resolveRowIdentityColumns(contract, namespaceId, tableName);
|
|
3689
|
+
if (identityColumns.length === 0) throw ormError("ORM.ROW_IDENTITY_MISSING", `Nested mutations on model "${modelName}" require table "${tableName}" to have a primary key or unique constraint`, { meta: {
|
|
3690
|
+
model: modelName,
|
|
3691
|
+
table: tableName
|
|
3692
|
+
} });
|
|
3693
|
+
const filter = {};
|
|
3694
|
+
for (const column of identityColumns) {
|
|
3695
|
+
const fieldName = toFieldName(contract, namespaceId, modelName, column);
|
|
3696
|
+
const value = row[fieldName];
|
|
3697
|
+
if (value === void 0) throw new InternalError(`Missing identity field "${fieldName}" while reloading model "${modelName}"`);
|
|
3698
|
+
filter[fieldName] = value;
|
|
3699
|
+
}
|
|
3700
|
+
return filter;
|
|
3882
3701
|
}
|
|
3883
|
-
function
|
|
3884
|
-
|
|
3702
|
+
async function withMutationScope(runtime, run) {
|
|
3703
|
+
if (typeof runtime.transaction === "function") return runInTransaction(await runtime.transaction(), run);
|
|
3704
|
+
if (typeof runtime.connection === "function") {
|
|
3705
|
+
const connection = await runtime.connection();
|
|
3706
|
+
try {
|
|
3707
|
+
if (typeof connection.transaction === "function") return await runInTransaction(await connection.transaction(), run);
|
|
3708
|
+
return await run(connection);
|
|
3709
|
+
} finally {
|
|
3710
|
+
await connection.release?.();
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
return run(runtime);
|
|
3885
3714
|
}
|
|
3886
|
-
function
|
|
3887
|
-
|
|
3715
|
+
async function runInTransaction(transaction, run) {
|
|
3716
|
+
try {
|
|
3717
|
+
const result = await run(transaction);
|
|
3718
|
+
if (typeof transaction.commit === "function") await transaction.commit();
|
|
3719
|
+
return result;
|
|
3720
|
+
} catch (error) {
|
|
3721
|
+
if (typeof transaction.rollback === "function") await transaction.rollback();
|
|
3722
|
+
throw error;
|
|
3723
|
+
}
|
|
3888
3724
|
}
|
|
3889
|
-
function
|
|
3890
|
-
|
|
3725
|
+
async function createGraph(scope, context, namespaceId, modelName, input) {
|
|
3726
|
+
const contract = context.contract;
|
|
3727
|
+
const parsed = parseMutationInput(contract, namespaceId, modelName, input);
|
|
3728
|
+
const { parentOwned, childOwned, junctionOwned } = partitionByOwnership(parsed.relationMutations);
|
|
3729
|
+
const scalarData = { ...parsed.scalarData };
|
|
3730
|
+
for (const relationMutation of parentOwned) {
|
|
3731
|
+
if (relationMutation.mutation.kind === "disconnect") throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", "disconnect() is only supported in update() nested mutations", { meta: {
|
|
3732
|
+
kind: "disconnect",
|
|
3733
|
+
relation: relationMutation.relation.relationName
|
|
3734
|
+
} });
|
|
3735
|
+
await applyParentOwnedMutation(scope, context, namespaceId, modelName, scalarData, relationMutation.relation, relationMutation.mutation);
|
|
3736
|
+
}
|
|
3737
|
+
for (const relationMutation of junctionOwned) {
|
|
3738
|
+
if (relationMutation.mutation.kind === "disconnect") throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", "disconnect() is only supported in update() nested mutations", { meta: {
|
|
3739
|
+
kind: "disconnect",
|
|
3740
|
+
relation: relationMutation.relation.relationName
|
|
3741
|
+
} });
|
|
3742
|
+
await preflightJunctionOwnedCreateMutation(scope, context, relationMutation);
|
|
3743
|
+
}
|
|
3744
|
+
const parentRow = await insertSingleRow(scope, context, namespaceId, modelName, scalarData);
|
|
3745
|
+
for (const relationMutation of childOwned) {
|
|
3746
|
+
if (relationMutation.mutation.kind === "disconnect") throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", "disconnect() is only supported in update() nested mutations", { meta: {
|
|
3747
|
+
kind: "disconnect",
|
|
3748
|
+
relation: relationMutation.relation.relationName
|
|
3749
|
+
} });
|
|
3750
|
+
await applyChildOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
3751
|
+
}
|
|
3752
|
+
for (const relationMutation of junctionOwned) await applyJunctionOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
3753
|
+
return parentRow;
|
|
3891
3754
|
}
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3755
|
+
async function updateFirstGraph(scope, context, namespaceId, modelName, filters, input) {
|
|
3756
|
+
const contract = context.contract;
|
|
3757
|
+
const existingRow = await findFirstByFilters(scope, contract, namespaceId, modelName, filters);
|
|
3758
|
+
if (!existingRow) return null;
|
|
3759
|
+
const parsed = parseMutationInput(contract, namespaceId, modelName, input);
|
|
3760
|
+
const { parentOwned, childOwned, junctionOwned } = partitionByOwnership(parsed.relationMutations);
|
|
3761
|
+
const scalarData = { ...parsed.scalarData };
|
|
3762
|
+
for (const relationMutation of parentOwned) await applyParentOwnedMutation(scope, context, namespaceId, modelName, scalarData, relationMutation.relation, relationMutation.mutation);
|
|
3763
|
+
for (const relationMutation of junctionOwned) await preflightJunctionOwnedCreateMutation(scope, context, relationMutation);
|
|
3764
|
+
let parentRow = existingRow;
|
|
3765
|
+
const mappedUpdateData = mapModelDataToStorageRow(contract, namespaceId, modelName, scalarData);
|
|
3766
|
+
if (Object.keys(mappedUpdateData).length > 0) {
|
|
3767
|
+
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
3768
|
+
const appliedUpdateDefaults = context.applyMutationDefaults({
|
|
3769
|
+
op: "update",
|
|
3770
|
+
entry: tableName,
|
|
3771
|
+
namespace: namespaceId,
|
|
3772
|
+
values: mappedUpdateData
|
|
3902
3773
|
});
|
|
3903
|
-
const
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3774
|
+
for (const def of appliedUpdateDefaults) mappedUpdateData[def.field] = def.value;
|
|
3775
|
+
const identityWhere = shorthandToWhereExpr(context, namespaceId, modelName, castAs(buildRowIdentityFilterFromRow(contract, namespaceId, modelName, existingRow)));
|
|
3776
|
+
if (!identityWhere) throw new InternalError(`Failed to build row identity filter for model "${modelName}"`);
|
|
3777
|
+
const updatedRaw = (await queryPlanRows(scope, compileUpdateReturning(contract, namespaceId, tableName, mappedUpdateData, [identityWhere], void 0)).toArray())[0];
|
|
3778
|
+
if (updatedRaw) parentRow = mapStorageRowToModelFields(contract, namespaceId, modelName, updatedRaw);
|
|
3779
|
+
}
|
|
3780
|
+
for (const relationMutation of childOwned) await applyChildOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
3781
|
+
for (const relationMutation of junctionOwned) await applyJunctionOwnedMutation(scope, context, namespaceId, modelName, parentRow, relationMutation.relation, relationMutation.mutation);
|
|
3782
|
+
return parentRow;
|
|
3783
|
+
}
|
|
3784
|
+
function parseMutationInput(contract, namespaceId, modelName, input) {
|
|
3785
|
+
const scalarData = {};
|
|
3786
|
+
const relationDefinitions = new Map(getRelationDefinitions(contract, namespaceId, modelName).map((relation) => [relation.relationName, relation]));
|
|
3787
|
+
const relationMutations = [];
|
|
3788
|
+
for (const [fieldName, value] of Object.entries(input)) {
|
|
3789
|
+
const relation = relationDefinitions.get(fieldName);
|
|
3790
|
+
if (!relation) {
|
|
3791
|
+
scalarData[fieldName] = value;
|
|
3792
|
+
continue;
|
|
3793
|
+
}
|
|
3794
|
+
if (!isRelationMutationCallback(value)) throw ormError("ORM.RELATION_MUTATION_INVALID", `Relation field "${fieldName}" on model "${modelName}" expects a mutator callback`, { meta: {
|
|
3795
|
+
relation: fieldName,
|
|
3796
|
+
model: modelName,
|
|
3797
|
+
problem: "missing-callback"
|
|
3798
|
+
} });
|
|
3799
|
+
const mutation = value(createRelationMutator());
|
|
3800
|
+
if (!isRelationMutationDescriptor(mutation)) throw ormError("ORM.RELATION_MUTATION_INVALID", `Relation field "${fieldName}" on model "${modelName}" returned an invalid mutation descriptor`, { meta: {
|
|
3801
|
+
relation: fieldName,
|
|
3802
|
+
model: modelName,
|
|
3803
|
+
problem: "invalid-descriptor"
|
|
3804
|
+
} });
|
|
3805
|
+
relationMutations.push({
|
|
3806
|
+
relation,
|
|
3807
|
+
mutation
|
|
3908
3808
|
});
|
|
3909
|
-
return {
|
|
3910
|
-
codecId: descriptor.codecId,
|
|
3911
|
-
nullable,
|
|
3912
|
-
many: multiplicity(many)
|
|
3913
|
-
};
|
|
3914
3809
|
}
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3810
|
+
return {
|
|
3811
|
+
scalarData,
|
|
3812
|
+
relationMutations
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
function partitionByOwnership(relationMutations) {
|
|
3816
|
+
const parentOwned = [];
|
|
3817
|
+
const childOwned = [];
|
|
3818
|
+
const junctionOwned = [];
|
|
3819
|
+
for (const relationMutation of relationMutations) {
|
|
3820
|
+
if (hasThrough(relationMutation.relation)) {
|
|
3821
|
+
junctionOwned.push({
|
|
3822
|
+
relation: relationMutation.relation,
|
|
3823
|
+
mutation: relationMutation.mutation
|
|
3824
|
+
});
|
|
3825
|
+
continue;
|
|
3826
|
+
}
|
|
3827
|
+
if (relationMutation.relation.cardinality === "N:1") {
|
|
3828
|
+
parentOwned.push(relationMutation);
|
|
3829
|
+
continue;
|
|
3830
|
+
}
|
|
3831
|
+
childOwned.push(relationMutation);
|
|
3832
|
+
}
|
|
3833
|
+
return {
|
|
3834
|
+
parentOwned,
|
|
3835
|
+
childOwned,
|
|
3836
|
+
junctionOwned
|
|
3919
3837
|
};
|
|
3920
|
-
throw ormError("ORM.ARGUMENT_INVALID", `Cannot define the scope: the declaration of field ${name} is not a field builder or { codecId, nullable }`, {
|
|
3921
|
-
why: `Each field of a scope is declared with a field builder or with an object that has a string codecId, a boolean nullable and, for a list, many: { elementNullable } with a boolean elementNullable; received ${describeReceived(declaration)} for ${name}.`,
|
|
3922
|
-
fix: FIELD_DECLARATION_FIX,
|
|
3923
|
-
meta: { field: name }
|
|
3924
|
-
});
|
|
3925
|
-
}
|
|
3926
|
-
function declaredFieldSpecs(declarations) {
|
|
3927
|
-
if (typeof declarations !== "object" || declarations === null || Array.isArray(declarations)) throw ormError("ORM.ARGUMENT_INVALID", "Cannot define the scope: the fields are not an object", {
|
|
3928
|
-
why: `The first argument of scope maps the name of each field the scope needs to its declaration; received ${describeReceived(declarations)}.`,
|
|
3929
|
-
fix: FIELD_DECLARATION_FIX,
|
|
3930
|
-
meta: { argument: "fields" }
|
|
3931
|
-
});
|
|
3932
|
-
return Object.entries(declarations).map(([name, declaration]) => [name, declaredFieldSpec(name, declaration)]);
|
|
3933
|
-
}
|
|
3934
|
-
/** Throws `ORM.ARGUMENT_INVALID` unless `body`, the body of a scope, is a function. */
|
|
3935
|
-
function assertScopeBody(body) {
|
|
3936
|
-
if (typeof body !== "function") throw ormError("ORM.ARGUMENT_INVALID", "Cannot define the scope: the body is not a function", {
|
|
3937
|
-
why: `The body of a scope is a function that receives a collection and returns one; received ${describeReceived(body)}.`,
|
|
3938
|
-
fix: "Pass a function, such as (rows) => rows.where(...).",
|
|
3939
|
-
meta: { argument: "body" }
|
|
3940
|
-
});
|
|
3941
|
-
}
|
|
3942
|
-
function hasContract(ctx) {
|
|
3943
|
-
return "context" in ctx && typeof ctx.context === "object" && ctx.context !== null && "contract" in ctx.context && typeof ctx.context.contract === "object" && ctx.context.contract !== null;
|
|
3944
|
-
}
|
|
3945
|
-
function isModelCollection(value) {
|
|
3946
|
-
return typeof value === "object" && value !== null && "ctx" in value && typeof value.ctx === "object" && value.ctx !== null && hasContract(value.ctx) && "modelName" in value && typeof value.modelName === "string" && "namespaceId" in value && typeof value.namespaceId === "string";
|
|
3947
3838
|
}
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
if (
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
model: source.modelName,
|
|
3967
|
-
namespace: source.namespaceId,
|
|
3968
|
-
receivedModel: value.modelName,
|
|
3969
|
-
receivedNamespace: value.namespaceId
|
|
3970
|
-
}
|
|
3971
|
-
});
|
|
3839
|
+
async function applyParentOwnedMutation(scope, context, parentNamespaceId, parentModelName, scalarData, relation, mutation) {
|
|
3840
|
+
const contract = context.contract;
|
|
3841
|
+
if (mutation.kind === "disconnect") {
|
|
3842
|
+
for (const localColumn of relation.localColumns) {
|
|
3843
|
+
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, localColumn);
|
|
3844
|
+
scalarData[parentFieldName] = null;
|
|
3845
|
+
}
|
|
3846
|
+
return;
|
|
3847
|
+
}
|
|
3848
|
+
if (mutation.kind === "create") {
|
|
3849
|
+
const row = mutation.data[0];
|
|
3850
|
+
if (!row) throw ormError("ORM.RELATION_MUTATION_INVALID", `create() nested mutation for relation "${relation.relationName}" requires data`, { meta: {
|
|
3851
|
+
kind: "create",
|
|
3852
|
+
relation: relation.relationName,
|
|
3853
|
+
problem: "missing-data"
|
|
3854
|
+
} });
|
|
3855
|
+
copyRelatedValuesToParent(contract, parentNamespaceId, parentModelName, relation, scalarData, await createGraph(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(row)));
|
|
3856
|
+
return;
|
|
3972
3857
|
}
|
|
3858
|
+
const criterion = mutation.criteria[0];
|
|
3859
|
+
if (!criterion) throw ormError("ORM.RELATION_MUTATION_INVALID", `connect() nested mutation for relation "${relation.relationName}" requires criterion`, { meta: {
|
|
3860
|
+
kind: "connect",
|
|
3861
|
+
relation: relation.relationName,
|
|
3862
|
+
problem: "missing-criterion"
|
|
3863
|
+
} });
|
|
3864
|
+
const relatedRow = await findRowByCriterion(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
3865
|
+
if (!relatedRow) throw ormError("ORM.RELATION_ROW_MISSING", `connect() nested mutation for relation "${relation.relationName}" did not find a matching row`, { meta: {
|
|
3866
|
+
kind: "connect",
|
|
3867
|
+
relation: relation.relationName
|
|
3868
|
+
} });
|
|
3869
|
+
copyRelatedValuesToParent(contract, parentNamespaceId, parentModelName, relation, scalarData, relatedRow);
|
|
3973
3870
|
}
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3871
|
+
function copyRelatedValuesToParent(contract, parentNamespaceId, parentModelName, relation, scalarData, relatedRow) {
|
|
3872
|
+
for (let i = 0; i < relation.localColumns.length; i++) {
|
|
3873
|
+
const localColumn = relation.localColumns[i];
|
|
3874
|
+
const targetColumn = relation.targetColumns[i];
|
|
3875
|
+
if (!localColumn || !targetColumn) continue;
|
|
3876
|
+
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, localColumn);
|
|
3877
|
+
scalarData[parentFieldName] = relatedRow[toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, targetColumn)];
|
|
3878
|
+
}
|
|
3977
3879
|
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
namespace: receiver.namespaceId,
|
|
3988
|
-
returned: isModelCollection(result) ? result.modelName : describeReceived(result)
|
|
3880
|
+
async function applyChildOwnedMutation(scope, context, parentNamespaceId, parentModelName, parentRow, relation, mutation) {
|
|
3881
|
+
const contract = context.contract;
|
|
3882
|
+
const parentValues = readParentColumnValues(contract, parentNamespaceId, parentModelName, relation, parentRow);
|
|
3883
|
+
if (mutation.kind === "create") {
|
|
3884
|
+
for (const childInput of mutation.data) {
|
|
3885
|
+
const payload = { ...castAs(childInput) };
|
|
3886
|
+
for (const [childColumn, parentValue] of parentValues.entries()) {
|
|
3887
|
+
const childFieldName = toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, childColumn);
|
|
3888
|
+
payload[childFieldName] = parentValue;
|
|
3989
3889
|
}
|
|
3990
|
-
|
|
3890
|
+
await createGraph(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(payload));
|
|
3891
|
+
}
|
|
3892
|
+
return;
|
|
3893
|
+
}
|
|
3894
|
+
if (mutation.kind === "connect") {
|
|
3895
|
+
for (const criterion of mutation.criteria) {
|
|
3896
|
+
const criterionWhere = shorthandToWhereExpr(context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
3897
|
+
if (!criterionWhere) throw ormError("ORM.RELATION_MUTATION_INVALID", `connect() nested mutation for relation "${relation.relationName}" requires non-empty criterion`, { meta: {
|
|
3898
|
+
kind: "connect",
|
|
3899
|
+
relation: relation.relationName,
|
|
3900
|
+
problem: "empty-criterion"
|
|
3901
|
+
} });
|
|
3902
|
+
const setValues = {};
|
|
3903
|
+
for (const [childColumn, parentValue] of parentValues.entries()) setValues[childColumn] = parentValue;
|
|
3904
|
+
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [criterionWhere]);
|
|
3905
|
+
}
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
const setValues = {};
|
|
3909
|
+
for (const childColumn of parentValues.keys()) setValues[childColumn] = null;
|
|
3910
|
+
if (!mutation.criteria || mutation.criteria.length === 0) {
|
|
3911
|
+
const parentJoinWhere = buildChildJoinWhere(relation, parentValues);
|
|
3912
|
+
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [parentJoinWhere]);
|
|
3913
|
+
return;
|
|
3914
|
+
}
|
|
3915
|
+
for (const criterion of mutation.criteria) {
|
|
3916
|
+
const criterionWhere = shorthandToWhereExpr(context, relation.relatedNamespaceId, relation.relatedModelName, castAs(criterion));
|
|
3917
|
+
if (!criterionWhere) throw ormError("ORM.RELATION_MUTATION_INVALID", `disconnect() nested mutation for relation "${relation.relationName}" requires non-empty criterion`, { meta: {
|
|
3918
|
+
kind: "disconnect",
|
|
3919
|
+
relation: relation.relationName,
|
|
3920
|
+
problem: "empty-criterion"
|
|
3921
|
+
} });
|
|
3922
|
+
const parentJoinWhere = buildChildJoinWhere(relation, parentValues);
|
|
3923
|
+
await executeUpdateCount(scope, contract, relation.relatedNamespaceId, relation.relatedTableName, setValues, [and(parentJoinWhere, criterionWhere)]);
|
|
3991
3924
|
}
|
|
3992
3925
|
}
|
|
3993
|
-
function
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
}
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
3926
|
+
async function applyJunctionOwnedMutation(scope, context, parentNamespaceId, parentModelName, parentRow, relation, mutation) {
|
|
3927
|
+
const contract = context.contract;
|
|
3928
|
+
const parentPkValues = readJunctionParentValues(contract, parentNamespaceId, parentModelName, relation, parentRow);
|
|
3929
|
+
assertJunctionPayloadWritable(relation, mutation.kind);
|
|
3930
|
+
if (mutation.kind === "create") {
|
|
3931
|
+
for (const childInput of mutation.data) await insertJunctionLink(scope, context, relation, parentPkValues, readJunctionTargetValues(contract, relation, await createGraph(scope, context, relation.relatedNamespaceId, relation.relatedModelName, castAs(childInput))), "create");
|
|
3932
|
+
return;
|
|
3933
|
+
}
|
|
3934
|
+
if (mutation.kind === "connect") {
|
|
3935
|
+
for (const criterion of mutation.criteria) await insertJunctionLink(scope, context, relation, parentPkValues, await resolveJunctionTargetValues(scope, context, relation, "connect", criterion), "connect");
|
|
3936
|
+
return;
|
|
3937
|
+
}
|
|
3938
|
+
if (!mutation.criteria || mutation.criteria.length === 0) throw ormError("ORM.RELATION_MUTATION_INVALID", `disconnect() nested mutation for relation "${relation.relationName}" requires criterion`, { meta: {
|
|
3939
|
+
kind: "disconnect",
|
|
3940
|
+
relation: relation.relationName,
|
|
3941
|
+
problem: "missing-criterion"
|
|
3942
|
+
} });
|
|
3943
|
+
for (const criterion of mutation.criteria) await deleteJunctionLink(scope, context, relation, parentPkValues, await resolveJunctionTargetValues(scope, context, relation, "disconnect", criterion));
|
|
4009
3944
|
}
|
|
4010
|
-
function
|
|
4011
|
-
const {
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
const
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
const modelFields = model?.fields ?? {};
|
|
4026
|
-
if (!Object.hasOwn(modelFields, name)) throw ormError("ORM.FIELD_UNKNOWN", `Cannot apply a scope to ${label}: it has no field ${name}`, {
|
|
4027
|
-
why: declared,
|
|
4028
|
-
fix: `Apply the scope to a model that has a field ${name}, or remove ${name} from the declaration in the scope.`,
|
|
4029
|
-
meta
|
|
4030
|
-
});
|
|
4031
|
-
const fieldMany = multiplicity(modelFields[name]?.many);
|
|
4032
|
-
const column = resolveColumn(contract, namespaceId, collection.tableName, resolveFieldToColumn(contract, namespaceId, modelName, name));
|
|
4033
|
-
const actual = column === void 0 ? `${label}.${name} has no column.` : fieldMany === false ? `${label}.${name} has codec ${column.codecId} and ${nullability(column.nullable)}.` : `${label}.${name} is a list ${describeField({
|
|
4034
|
-
codecId: column.codecId,
|
|
4035
|
-
nullable: column.nullable,
|
|
4036
|
-
many: fieldMany
|
|
4037
|
-
})}.`;
|
|
4038
|
-
const sameCodecAndNullability = column?.codecId === spec.codecId && column.nullable === spec.nullable;
|
|
4039
|
-
if (!sameCodecAndNullability || !sameMultiplicity(fieldMany, spec.many)) throw ormError("ORM.FIELD_UNKNOWN", `Cannot apply a scope to ${label}: its field ${name} does not match the declaration`, {
|
|
4040
|
-
why: `${declared} ${actual}`,
|
|
4041
|
-
fix: sameCodecAndNullability ? `Apply the scope to a model whose ${name} field ${fieldShape(spec.many)}, or declare ${name} ${declarationFor(fieldMany)}.` : `Apply the scope to a model whose ${name} field has that codec and nullability, or change the declaration in the scope.`,
|
|
4042
|
-
meta
|
|
4043
|
-
});
|
|
3945
|
+
async function preflightJunctionOwnedCreateMutation(scope, context, relationMutation) {
|
|
3946
|
+
const { relation, mutation } = relationMutation;
|
|
3947
|
+
assertJunctionMetadataShape(relation);
|
|
3948
|
+
assertJunctionPayloadWritable(relation, mutation.kind);
|
|
3949
|
+
if (mutation.kind !== "connect") return;
|
|
3950
|
+
const seenTargetKeys = /* @__PURE__ */ new Set();
|
|
3951
|
+
for (const criterion of mutation.criteria) {
|
|
3952
|
+
const targetValues = await resolveJunctionTargetValues(scope, context, relation, "connect", criterion);
|
|
3953
|
+
const targetKey = JSON.stringify([...targetValues.entries()]);
|
|
3954
|
+
if (seenTargetKeys.has(targetKey)) throw ormError("ORM.RELATION_MUTATION_INVALID", `connect() nested mutation for relation "${relation.relationName}" resolved duplicate junction link targets; remove the duplicate criteria`, { meta: {
|
|
3955
|
+
kind: "connect",
|
|
3956
|
+
relation: relation.relationName,
|
|
3957
|
+
problem: "duplicate-criteria"
|
|
3958
|
+
} });
|
|
3959
|
+
seenTargetKeys.add(targetKey);
|
|
4044
3960
|
}
|
|
4045
3961
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
const result = body(blindCast(collection));
|
|
4057
|
-
assertScopeResult(receiver, result);
|
|
4058
|
-
return result;
|
|
4059
|
-
});
|
|
3962
|
+
function assertJunctionPayloadWritable(relation, mutationKind) {
|
|
3963
|
+
const through = relation.through;
|
|
3964
|
+
if (mutationKind !== "create" && mutationKind !== "connect" || through.requiredPayloadColumns.length === 0) return;
|
|
3965
|
+
const cols = through.requiredPayloadColumns.map((c) => `\`${c}\``).join(", ");
|
|
3966
|
+
throw ormError("ORM.RELATION_MUTATION_UNSUPPORTED", `Cannot \`${mutationKind}\` on relation \`${relation.relationName}\`: its junction \`${through.table}\` has required column(s) ${cols} the relation API can't populate. Write the \`${through.table}\` junction directly or use the SQL builder.`, { meta: {
|
|
3967
|
+
kind: mutationKind,
|
|
3968
|
+
relation: relation.relationName,
|
|
3969
|
+
reason: "junction-required-columns",
|
|
3970
|
+
junction: through.table
|
|
3971
|
+
} });
|
|
4060
3972
|
}
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
3973
|
+
async function resolveJunctionTargetValues(scope, context, relation, kind, criterion) {
|
|
3974
|
+
const relatedRow = await findRowByCriterion(scope, context, relation.relatedNamespaceId, relation.relatedModelName, criterion);
|
|
3975
|
+
if (!relatedRow) throw ormError("ORM.RELATION_ROW_MISSING", `${kind}() nested mutation for relation "${relation.relationName}" did not find a matching row`, { meta: {
|
|
3976
|
+
kind,
|
|
3977
|
+
relation: relation.relationName
|
|
3978
|
+
} });
|
|
3979
|
+
return readJunctionTargetValues(context.contract, relation, relatedRow);
|
|
3980
|
+
}
|
|
3981
|
+
function readJunctionParentValues(contract, parentNamespaceId, parentModelName, relation, parentRow) {
|
|
3982
|
+
const values = /* @__PURE__ */ new Map();
|
|
3983
|
+
assertJunctionParentMetadataLength(relation);
|
|
3984
|
+
for (let i = 0; i < relation.through.parentColumns.length; i++) {
|
|
3985
|
+
const junctionColumn = relation.through.parentColumns[i];
|
|
3986
|
+
const parentColumn = relation.localColumns[i];
|
|
3987
|
+
if (junctionColumn === void 0 || parentColumn === void 0) throw new InternalError(`Relation "${relation.relationName}" has incomplete junction metadata for parent columns`);
|
|
3988
|
+
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, parentColumn);
|
|
3989
|
+
const parentValue = parentRow[parentFieldName];
|
|
3990
|
+
if (parentValue === void 0) throw new InternalError(`Nested mutation requires parent field "${parentFieldName}" to be present in returned row`);
|
|
3991
|
+
values.set(junctionColumn, parentValue);
|
|
3992
|
+
}
|
|
3993
|
+
return values;
|
|
4069
3994
|
}
|
|
4070
|
-
function
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
3995
|
+
function readJunctionTargetValues(contract, relation, relatedRow) {
|
|
3996
|
+
const values = /* @__PURE__ */ new Map();
|
|
3997
|
+
assertJunctionTargetMetadataLength(relation);
|
|
3998
|
+
for (let i = 0; i < relation.through.childColumns.length; i++) {
|
|
3999
|
+
const junctionColumn = relation.through.childColumns[i];
|
|
4000
|
+
const targetColumn = relation.through.targetColumns[i];
|
|
4001
|
+
if (junctionColumn === void 0 || targetColumn === void 0) throw new InternalError(`Relation "${relation.relationName}" has incomplete junction metadata for target columns`);
|
|
4002
|
+
const targetFieldName = toFieldName(contract, relation.relatedNamespaceId, relation.relatedModelName, targetColumn);
|
|
4003
|
+
const targetValue = relatedRow[targetFieldName];
|
|
4004
|
+
if (targetValue === void 0) throw new InternalError(`Nested mutation requires target field "${targetFieldName}" to be present in returned row`);
|
|
4005
|
+
values.set(junctionColumn, targetValue);
|
|
4006
|
+
}
|
|
4007
|
+
return values;
|
|
4075
4008
|
}
|
|
4076
|
-
function
|
|
4077
|
-
|
|
4009
|
+
function assertJunctionMetadataLength(relation, throughColumnName, throughColumns, pairedColumnName, pairedColumns) {
|
|
4010
|
+
if (throughColumns.length === pairedColumns.length) return;
|
|
4011
|
+
throw new InternalError(`Relation "${relation.relationName}" has invalid junction metadata: ${throughColumnName} has ${throughColumns.length} column(s), but ${pairedColumnName} has ${pairedColumns.length}`);
|
|
4078
4012
|
}
|
|
4079
|
-
function
|
|
4080
|
-
|
|
4081
|
-
const column = resolveFieldToColumn(contract, collection.namespaceId, collection.modelName, field);
|
|
4082
|
-
return resolveColumn(contract, collection.namespaceId, collection.tableName, column)?.codecId;
|
|
4013
|
+
function assertJunctionParentMetadataLength(relation) {
|
|
4014
|
+
assertJunctionMetadataLength(relation, "parentColumns", relation.through.parentColumns, "localColumns", relation.localColumns);
|
|
4083
4015
|
}
|
|
4084
|
-
function
|
|
4085
|
-
|
|
4086
|
-
return codecId !== void 0 && hasTrait(collection.ctx.context, codecId, "order");
|
|
4016
|
+
function assertJunctionTargetMetadataLength(relation) {
|
|
4017
|
+
assertJunctionMetadataLength(relation, "childColumns", relation.through.childColumns, "targetColumns", relation.through.targetColumns);
|
|
4087
4018
|
}
|
|
4088
|
-
function
|
|
4089
|
-
|
|
4090
|
-
|
|
4019
|
+
function assertJunctionMetadataShape(relation) {
|
|
4020
|
+
assertJunctionParentMetadataLength(relation);
|
|
4021
|
+
assertJunctionTargetMetadataLength(relation);
|
|
4091
4022
|
}
|
|
4092
|
-
function
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4023
|
+
function writeJunctionColumn(junctionRow, through, column, value, relationName) {
|
|
4024
|
+
if (Object.hasOwn(junctionRow, column) && !Object.is(junctionRow[column], value)) throw ormError("ORM.RELATION_MUTATION_INVALID", `Cannot write junction "${through.table}": conflicting values for junction column "${column}"`, { meta: {
|
|
4025
|
+
relation: relationName,
|
|
4026
|
+
junction: through.table,
|
|
4027
|
+
column
|
|
4028
|
+
} });
|
|
4029
|
+
junctionRow[column] = value;
|
|
4099
4030
|
}
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
why: `orderByField takes the collection it orders, such as db.orm.public.Post; received ${describeReceived(receiver)}.`,
|
|
4111
|
-
fix: "Pass the collection the selector is for as the first argument.",
|
|
4112
|
-
meta: { argument: "collection" }
|
|
4113
|
-
});
|
|
4114
|
-
const { modelName } = collection;
|
|
4115
|
-
const requestedName = name;
|
|
4116
|
-
const requestedDirection = direction === void 0 ? "asc" : direction;
|
|
4117
|
-
const allowedList = allowed;
|
|
4118
|
-
if (!Array.isArray(allowedList) || allowedList.some((field) => typeof field !== "string")) throw ormError("ORM.ARGUMENT_INVALID", `Cannot order ${modelName}: the allowed fields are not a list of names`, {
|
|
4119
|
-
why: `orderByField takes the list of fields a request may order by; received ${describeReceived(allowedList)}.`,
|
|
4120
|
-
fix: `Pass an array of field names of ${modelName}, such as ['title', 'createdAt'].`,
|
|
4121
|
-
meta: {
|
|
4122
|
-
model: modelName,
|
|
4123
|
-
argument: "allowed"
|
|
4124
|
-
}
|
|
4125
|
-
});
|
|
4126
|
-
if (typeof requestedDirection !== "string") throw ormError("ORM.ARGUMENT_INVALID", `Cannot order ${modelName}: the direction is not a string`, {
|
|
4127
|
-
why: `An order direction is "asc" or "desc"; received ${describeReceived(requestedDirection)}.`,
|
|
4128
|
-
fix: "Pass \"asc\" or \"desc\".",
|
|
4129
|
-
meta: {
|
|
4130
|
-
model: modelName,
|
|
4131
|
-
direction: requestedDirection
|
|
4132
|
-
}
|
|
4031
|
+
async function insertJunctionLink(scope, context, relation, parentPkValues, targetPkValues, mutationKind) {
|
|
4032
|
+
const through = relation.through;
|
|
4033
|
+
const junctionRow = {};
|
|
4034
|
+
for (const [column, value] of parentPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
4035
|
+
for (const [column, value] of targetPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
4036
|
+
const applied = context.applyMutationDefaults({
|
|
4037
|
+
op: "create",
|
|
4038
|
+
entry: through.table,
|
|
4039
|
+
namespace: through.namespaceId,
|
|
4040
|
+
values: junctionRow
|
|
4133
4041
|
});
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4042
|
+
for (const def of applied) junctionRow[def.field] = def.value;
|
|
4043
|
+
const compiled = compileInsertCount(context.contract, through.namespaceId, through.table, [junctionRow]);
|
|
4044
|
+
try {
|
|
4045
|
+
await scope.execute(compiled);
|
|
4046
|
+
} catch (error) {
|
|
4047
|
+
if (mutationKind === "connect" && isUniqueConstraintViolation(error)) throw ormError("ORM.RELATION_LINK_DUPLICATE", `connect() nested mutation for relation "${relation.relationName}" violated a unique constraint on junction "${through.table}"; the junction link may already be present`, {
|
|
4139
4048
|
meta: {
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
}
|
|
4049
|
+
relation: relation.relationName,
|
|
4050
|
+
junction: through.table
|
|
4051
|
+
},
|
|
4052
|
+
cause: error
|
|
4143
4053
|
});
|
|
4054
|
+
throw error;
|
|
4144
4055
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
const
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4056
|
+
}
|
|
4057
|
+
async function deleteJunctionLink(scope, context, relation, parentPkValues, targetPkValues) {
|
|
4058
|
+
const through = relation.through;
|
|
4059
|
+
const junctionRow = {};
|
|
4060
|
+
for (const [column, value] of parentPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
4061
|
+
for (const [column, value] of targetPkValues.entries()) writeJunctionColumn(junctionRow, through, column, value, relation.relationName);
|
|
4062
|
+
const exprs = [];
|
|
4063
|
+
for (const [column, value] of Object.entries(junctionRow)) exprs.push(BinaryExpr.eq(ColumnRef.of(through.table, column), LiteralExpr.of(value)));
|
|
4064
|
+
const first = exprs[0];
|
|
4065
|
+
const where = exprs.length === 1 && first !== void 0 ? first : and(...exprs);
|
|
4066
|
+
const compiled = compileDeleteCount(context.contract, through.namespaceId, through.table, [where]);
|
|
4067
|
+
await scope.execute(compiled);
|
|
4068
|
+
}
|
|
4069
|
+
function readParentColumnValues(contract, parentNamespaceId, parentModelName, relation, parentRow) {
|
|
4070
|
+
const values = /* @__PURE__ */ new Map();
|
|
4071
|
+
for (let i = 0; i < relation.localColumns.length; i++) {
|
|
4072
|
+
const localColumn = relation.localColumns[i];
|
|
4073
|
+
const targetColumn = relation.targetColumns[i];
|
|
4074
|
+
if (!localColumn || !targetColumn) continue;
|
|
4075
|
+
const parentFieldName = toFieldName(contract, parentNamespaceId, parentModelName, localColumn);
|
|
4076
|
+
const parentValue = parentRow[parentFieldName];
|
|
4077
|
+
if (parentValue === void 0) throw new InternalError(`Nested mutation requires parent field "${parentFieldName}" to be present in returned row`);
|
|
4078
|
+
values.set(targetColumn, parentValue);
|
|
4079
|
+
}
|
|
4080
|
+
return values;
|
|
4081
|
+
}
|
|
4082
|
+
function buildChildJoinWhere(relation, childValues) {
|
|
4083
|
+
const exprs = [];
|
|
4084
|
+
for (const [childColumn, parentValue] of childValues.entries()) exprs.push(BinaryExpr.eq(ColumnRef.of(relation.relatedTableName, childColumn), LiteralExpr.of(parentValue)));
|
|
4085
|
+
const first = exprs[0];
|
|
4086
|
+
if (exprs.length === 1 && first !== void 0) return first;
|
|
4087
|
+
return and(...exprs);
|
|
4088
|
+
}
|
|
4089
|
+
async function insertSingleRow(scope, context, namespaceId, modelName, data) {
|
|
4090
|
+
const contract = context.contract;
|
|
4091
|
+
const tableName = resolveModelTableName(contract, namespaceId, modelName);
|
|
4092
|
+
const mappedData = mapModelDataToStorageRow(contract, namespaceId, modelName, data);
|
|
4093
|
+
const applied = context.applyMutationDefaults({
|
|
4094
|
+
op: "create",
|
|
4095
|
+
entry: tableName,
|
|
4096
|
+
namespace: namespaceId,
|
|
4097
|
+
values: mappedData
|
|
4155
4098
|
});
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4099
|
+
for (const def of applied) mappedData[def.field] = def.value;
|
|
4100
|
+
const firstRow = (await queryPlanRows(scope, compileInsertReturning(contract, namespaceId, tableName, [mappedData], void 0)).toArray())[0];
|
|
4101
|
+
if (!firstRow) throw ormError("ORM.MUTATION_ROW_MISSING", `Nested create for model "${modelName}" did not return a row`, { meta: {
|
|
4102
|
+
operation: "create",
|
|
4103
|
+
model: modelName,
|
|
4104
|
+
phase: "nested"
|
|
4105
|
+
} });
|
|
4106
|
+
return mapStorageRowToModelFields(contract, namespaceId, modelName, firstRow);
|
|
4107
|
+
}
|
|
4108
|
+
async function findRowByCriterion(scope, context, namespaceId, modelName, criterion) {
|
|
4109
|
+
const contract = context.contract;
|
|
4110
|
+
const whereExpr = shorthandToWhereExpr(context, namespaceId, modelName, castAs(criterion));
|
|
4111
|
+
if (!whereExpr) throw ormError("ORM.RELATION_MUTATION_INVALID", `Nested connect for model "${modelName}" requires non-empty criterion`, { meta: {
|
|
4112
|
+
kind: "connect",
|
|
4113
|
+
model: modelName,
|
|
4114
|
+
problem: "empty-criterion"
|
|
4115
|
+
} });
|
|
4116
|
+
const firstRow = (await queryPlanRows(scope, compileSelect(contract, namespaceId, resolveModelTableName(contract, namespaceId, modelName), {
|
|
4117
|
+
...emptyState(),
|
|
4118
|
+
filters: [whereExpr],
|
|
4119
|
+
limit: 1
|
|
4120
|
+
})).toArray())[0];
|
|
4121
|
+
if (!firstRow) return null;
|
|
4122
|
+
return mapStorageRowToModelFields(contract, namespaceId, modelName, firstRow);
|
|
4123
|
+
}
|
|
4124
|
+
async function findFirstByFilters(scope, contract, namespaceId, modelName, filters) {
|
|
4125
|
+
const firstRow = (await queryPlanRows(scope, compileSelect(contract, namespaceId, resolveModelTableName(contract, namespaceId, modelName), {
|
|
4126
|
+
...emptyState(),
|
|
4127
|
+
filters,
|
|
4128
|
+
limit: 1
|
|
4129
|
+
})).toArray())[0];
|
|
4130
|
+
if (!firstRow) return null;
|
|
4131
|
+
return mapStorageRowToModelFields(contract, namespaceId, modelName, firstRow);
|
|
4132
|
+
}
|
|
4133
|
+
async function executeUpdateCount(scope, contract, namespaceId, tableName, setValues, filters) {
|
|
4134
|
+
const compiled = compileUpdateCount(contract, namespaceId, tableName, setValues, filters);
|
|
4135
|
+
await scope.execute(compiled);
|
|
4136
|
+
}
|
|
4137
|
+
const relationDefsCache = /* @__PURE__ */ new WeakMap();
|
|
4138
|
+
function getRelationDefinitions(contract, namespaceId, modelName) {
|
|
4139
|
+
let perContract = relationDefsCache.get(contract);
|
|
4140
|
+
if (!perContract) {
|
|
4141
|
+
perContract = /* @__PURE__ */ new Map();
|
|
4142
|
+
relationDefsCache.set(contract, perContract);
|
|
4166
4143
|
}
|
|
4167
|
-
const
|
|
4168
|
-
const
|
|
4169
|
-
|
|
4144
|
+
const cacheKey = JSON.stringify([namespaceId, modelName]);
|
|
4145
|
+
const cached = perContract.get(cacheKey);
|
|
4146
|
+
if (cached) return cached;
|
|
4147
|
+
const relations = resolveModelRelations(contract, namespaceId, modelName);
|
|
4148
|
+
const definitions = Object.entries(relations).map(([relationName, relation]) => ({
|
|
4149
|
+
relationName,
|
|
4150
|
+
relatedModelName: relation.to,
|
|
4151
|
+
relatedNamespaceId: relation.toNamespace,
|
|
4152
|
+
relatedTableName: resolveModelTableName(contract, relation.toNamespace, relation.to),
|
|
4153
|
+
cardinality: relation.cardinality,
|
|
4154
|
+
localColumns: relation.on.localFields.map((f) => resolveFieldToColumn(contract, namespaceId, modelName, f)),
|
|
4155
|
+
targetColumns: relation.on.targetFields.map((f) => resolveFieldToColumn(contract, relation.toNamespace, relation.to, f)),
|
|
4156
|
+
through: relation.through ? {
|
|
4157
|
+
table: relation.through.table,
|
|
4158
|
+
namespaceId: relation.through.namespaceId,
|
|
4159
|
+
parentColumns: relation.through.parentColumns,
|
|
4160
|
+
childColumns: relation.through.childColumns,
|
|
4161
|
+
targetColumns: relation.through.targetColumns,
|
|
4162
|
+
requiredPayloadColumns: relation.through.requiredPayloadColumns
|
|
4163
|
+
} : void 0
|
|
4164
|
+
}));
|
|
4165
|
+
perContract.set(cacheKey, definitions);
|
|
4166
|
+
return definitions;
|
|
4167
|
+
}
|
|
4168
|
+
function toFieldName(contract, namespaceId, modelName, columnName) {
|
|
4169
|
+
return getColumnToFieldMap(contract, namespaceId, modelName)[columnName] ?? columnName;
|
|
4170
4170
|
}
|
|
4171
4171
|
function normalizeWhereArg(arg, options) {
|
|
4172
4172
|
if (arg === void 0) return;
|
|
@@ -4218,7 +4218,7 @@ function listed(items, conjunction) {
|
|
|
4218
4218
|
const last = items.at(-1) ?? "";
|
|
4219
4219
|
return items.length > 1 ? `${items.slice(0, -1).join(", ")} ${conjunction} ${last}` : last;
|
|
4220
4220
|
}
|
|
4221
|
-
const
|
|
4221
|
+
const HIDDEN_BY_A_FRAGMENT = "A fragment passed to with can add one without showing it at the call site.";
|
|
4222
4222
|
/** Throws `ORM.ARGUMENT_INVALID` when the collection has a limit, an offset, a cursor or a distinct selection, which `method`, a write of every matching row, would ignore. */
|
|
4223
4223
|
function assertBulkWriteIgnoresNothing(state, modelName, method) {
|
|
4224
4224
|
const { nouns, calls } = presentParts(state, [
|
|
@@ -4231,7 +4231,7 @@ function assertBulkWriteIgnoresNothing(state, modelName, method) {
|
|
|
4231
4231
|
if (nouns.length === 0) return;
|
|
4232
4232
|
const { limit, offset, cursor, distinct, distinctOn } = state;
|
|
4233
4233
|
throw ormError("ORM.ARGUMENT_INVALID", `Cannot ${method} ${modelName}: the collection has ${listed(nouns, "and")}`, {
|
|
4234
|
-
why: `${method} changes every row that matches the filter. The statement it runs cannot apply ${listed(nouns, "or")}, so it would change more rows than the chain asks for. ${
|
|
4234
|
+
why: `${method} changes every row that matches the filter. The statement it runs cannot apply ${listed(nouns, "or")}, so it would change more rows than the chain asks for. ${HIDDEN_BY_A_FRAGMENT}`,
|
|
4235
4235
|
fix: `Remove ${listed(calls, "and")} before ${method}, or read the rows first and change them by their ids.`,
|
|
4236
4236
|
meta: {
|
|
4237
4237
|
model: modelName,
|
|
@@ -4257,7 +4257,7 @@ function assertRelationUpdateIgnoresNothing(state, modelName) {
|
|
|
4257
4257
|
if (nouns.length === 0) return;
|
|
4258
4258
|
const { orderBy, limit, offset, cursor, distinct, distinctOn } = state;
|
|
4259
4259
|
throw ormError("ORM.ARGUMENT_INVALID", `Cannot update ${modelName} with a relation mutation: the collection has ${listed(nouns, "and")}`, {
|
|
4260
|
-
why: `An update that changes a relation finds its row by the filter alone. It would ignore ${listed(nouns, "and")}, and could change another row than first() returns. ${
|
|
4260
|
+
why: `An update that changes a relation finds its row by the filter alone. It would ignore ${listed(nouns, "and")}, and could change another row than first() returns. ${HIDDEN_BY_A_FRAGMENT}`,
|
|
4261
4261
|
fix: `Remove ${listed(calls, "and")} before update, or filter to the one row, such as by its id.`,
|
|
4262
4262
|
meta: {
|
|
4263
4263
|
model: modelName,
|
|
@@ -4393,21 +4393,21 @@ var CollectionBase = class CollectionBase {
|
|
|
4393
4393
|
return fn(this);
|
|
4394
4394
|
}
|
|
4395
4395
|
/**
|
|
4396
|
-
* Define a
|
|
4396
|
+
* Define a query fragment for this collection's model, such as a shared `select` and `include`. The body is typed once, against the model's plain collection. The fragment accepts any collection of the model that `select` and `variant` have not narrowed.
|
|
4397
4397
|
*
|
|
4398
4398
|
* ```ts
|
|
4399
|
-
* const summary = db.Post.
|
|
4399
|
+
* const summary = db.Post.fragment((posts) => posts.select('id', 'title').include('user'));
|
|
4400
4400
|
* db.User.include('posts', (posts) => posts.with(summary));
|
|
4401
4401
|
* ```
|
|
4402
4402
|
*/
|
|
4403
|
-
|
|
4404
|
-
|
|
4403
|
+
fragment(body) {
|
|
4404
|
+
assertFragmentBody(body);
|
|
4405
4405
|
const source = {
|
|
4406
4406
|
modelName: this.modelName,
|
|
4407
4407
|
namespaceId: this.namespaceId
|
|
4408
4408
|
};
|
|
4409
4409
|
return (collection) => {
|
|
4410
|
-
|
|
4410
|
+
assertModelFragmentReceiver(source, collection);
|
|
4411
4411
|
return body(blindCast(collection));
|
|
4412
4412
|
};
|
|
4413
4413
|
}
|
|
@@ -5438,7 +5438,7 @@ function orm(options) {
|
|
|
5438
5438
|
return new Proxy({}, { get(_target, prop) {
|
|
5439
5439
|
if (typeof prop !== "string") return;
|
|
5440
5440
|
if (Object.hasOwn(contract.domain.namespaces, prop)) return namespaceFacet(prop);
|
|
5441
|
-
return prop === "
|
|
5441
|
+
return prop === "fragment" ? defineFieldFragment : void 0;
|
|
5442
5442
|
} });
|
|
5443
5443
|
}
|
|
5444
5444
|
function createCollectionRegistry(contract, collections) {
|