@vantreeseba/drizzle-graphql 11.0.1 → 11.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +188 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +188 -158
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3768,6 +3768,154 @@ const identifyRows = (field, args) => {
|
|
|
3768
3768
|
};
|
|
3769
3769
|
};
|
|
3770
3770
|
//#endregion
|
|
3771
|
+
//#region src/util/parse-resolve-info.ts
|
|
3772
|
+
/** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
|
|
3773
|
+
const namedTypeOf = (type) => {
|
|
3774
|
+
let current = type;
|
|
3775
|
+
while (current?.ofType) current = current.ofType;
|
|
3776
|
+
return current;
|
|
3777
|
+
};
|
|
3778
|
+
/**
|
|
3779
|
+
* Whether a field's named output type has sub-selections. Object types and interfaces expose
|
|
3780
|
+
* `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
|
|
3781
|
+
* also expose `getFields`, but an input type can never be a field's output type, so there is
|
|
3782
|
+
* nothing here for that to collide with.
|
|
3783
|
+
*/
|
|
3784
|
+
const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
|
|
3785
|
+
/**
|
|
3786
|
+
* The fields declared on a type, or `undefined` when it has none to declare. A union is the
|
|
3787
|
+
* case that matters: it has no fields of its own, so a selection directly against one resolves
|
|
3788
|
+
* to nothing and is dropped — only the inline fragments underneath contribute.
|
|
3789
|
+
*/
|
|
3790
|
+
const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
|
|
3791
|
+
/**
|
|
3792
|
+
* The runtime variable values, in the plain-map form the directive check wants. graphql 17
|
|
3793
|
+
* wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
|
|
3794
|
+
*/
|
|
3795
|
+
const coercedVariables = (info) => {
|
|
3796
|
+
const values = info.variableValues;
|
|
3797
|
+
if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
|
|
3798
|
+
return values ?? {};
|
|
3799
|
+
};
|
|
3800
|
+
/** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
|
|
3801
|
+
const directiveIfValue = (info, value) => {
|
|
3802
|
+
if (value.kind === Kind.VARIABLE) return coercedVariables(info)[value.name.value];
|
|
3803
|
+
if (value.kind === Kind.BOOLEAN) return value.value;
|
|
3804
|
+
};
|
|
3805
|
+
/**
|
|
3806
|
+
* Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
|
|
3807
|
+
* spreads and inline fragments alike, which is where graphql puts them.
|
|
3808
|
+
*/
|
|
3809
|
+
const isExcluded = (info, node) => {
|
|
3810
|
+
for (const directive of node.directives ?? []) {
|
|
3811
|
+
const directiveName = directive.name.value;
|
|
3812
|
+
if (directiveName !== "skip" && directiveName !== "include") continue;
|
|
3813
|
+
const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
|
|
3814
|
+
if (!ifArgument) continue;
|
|
3815
|
+
const value = directiveIfValue(info, ifArgument.value);
|
|
3816
|
+
if (directiveName === "skip" ? !!value : !value) return true;
|
|
3817
|
+
}
|
|
3818
|
+
return false;
|
|
3819
|
+
};
|
|
3820
|
+
/** The type a fragment's `on Type` condition names, looked up in the executing schema. */
|
|
3821
|
+
const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
|
|
3822
|
+
/**
|
|
3823
|
+
* Introspection meta-fields are not schema fields and never reach the database. `__typename` is
|
|
3824
|
+
* the one that turns up in practice; `__id` is excluded from the rule because a schema is free
|
|
3825
|
+
* to declare a field by that name.
|
|
3826
|
+
*/
|
|
3827
|
+
const isMetaField = (name) => name.startsWith("__") && name !== "__id";
|
|
3828
|
+
/**
|
|
3829
|
+
* Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
|
|
3830
|
+
* level, this owns the entries under `parentType`'s name.
|
|
3831
|
+
*
|
|
3832
|
+
* `activeFragments` is the set of fragment names on the current spread path. A document that
|
|
3833
|
+
* passed validation cannot contain a fragment cycle, but this parser also runs on trees a
|
|
3834
|
+
* caller assembled by hand, and an unbounded walk would hang rather than fail.
|
|
3835
|
+
*/
|
|
3836
|
+
const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
|
|
3837
|
+
const parentTypeName = parentType?.name;
|
|
3838
|
+
if (!parentTypeName) return tree;
|
|
3839
|
+
let fields = tree[parentTypeName];
|
|
3840
|
+
if (!fields) {
|
|
3841
|
+
fields = {};
|
|
3842
|
+
tree[parentTypeName] = fields;
|
|
3843
|
+
}
|
|
3844
|
+
const parentFields = fieldsOf(parentType);
|
|
3845
|
+
for (const selection of selections) {
|
|
3846
|
+
if (isExcluded(info, selection)) continue;
|
|
3847
|
+
if (selection.kind === Kind.FIELD) {
|
|
3848
|
+
const name = selection.name.value;
|
|
3849
|
+
if (isMetaField(name)) continue;
|
|
3850
|
+
const fieldDef = parentFields?.[name];
|
|
3851
|
+
if (!fieldDef) continue;
|
|
3852
|
+
const fieldType = namedTypeOf(fieldDef.type);
|
|
3853
|
+
if (!fieldType) continue;
|
|
3854
|
+
const composite = isCompositeLike(fieldType);
|
|
3855
|
+
const alias = selection.alias?.value || name;
|
|
3856
|
+
let node = fields[alias];
|
|
3857
|
+
if (!node) {
|
|
3858
|
+
node = {
|
|
3859
|
+
name,
|
|
3860
|
+
alias,
|
|
3861
|
+
args: { ...getArgumentValues(fieldDef, selection, info.variableValues) },
|
|
3862
|
+
fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
|
|
3863
|
+
};
|
|
3864
|
+
fields[alias] = node;
|
|
3865
|
+
}
|
|
3866
|
+
if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
|
|
3867
|
+
continue;
|
|
3868
|
+
}
|
|
3869
|
+
if (!deep) continue;
|
|
3870
|
+
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
|
3871
|
+
const fragmentName = selection.name.value;
|
|
3872
|
+
const fragment = info.fragments?.[fragmentName];
|
|
3873
|
+
if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
|
|
3874
|
+
if (activeFragments.has(fragmentName)) continue;
|
|
3875
|
+
const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
|
|
3876
|
+
if (!isCompositeLike(fragmentType)) continue;
|
|
3877
|
+
activeFragments.add(fragmentName);
|
|
3878
|
+
try {
|
|
3879
|
+
collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3880
|
+
} finally {
|
|
3881
|
+
activeFragments.delete(fragmentName);
|
|
3882
|
+
}
|
|
3883
|
+
continue;
|
|
3884
|
+
}
|
|
3885
|
+
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
3886
|
+
const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
|
|
3887
|
+
if (!isCompositeLike(fragmentType)) continue;
|
|
3888
|
+
collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
return tree;
|
|
3892
|
+
};
|
|
3893
|
+
/** The first key of an object, without materializing the whole key array to discard it. */
|
|
3894
|
+
const firstKey = (object) => {
|
|
3895
|
+
for (const key in object) return key;
|
|
3896
|
+
};
|
|
3897
|
+
/**
|
|
3898
|
+
* Reads the selection under a resolver's own field out of its `info`, as a tree of
|
|
3899
|
+
* {@link ResolveTree} nodes.
|
|
3900
|
+
*
|
|
3901
|
+
* ```ts
|
|
3902
|
+
* const parsed = parseResolveInfo(info, { deep: true })!;
|
|
3903
|
+
* const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
|
|
3904
|
+
* ```
|
|
3905
|
+
*
|
|
3906
|
+
* Returns `undefined` when the resolver's field produced no node at all, which in practice
|
|
3907
|
+
* means `info` carried no field nodes.
|
|
3908
|
+
*/
|
|
3909
|
+
const parseResolveInfo = (info, options = {}) => {
|
|
3910
|
+
const fieldNodes = info.fieldNodes;
|
|
3911
|
+
if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
|
|
3912
|
+
const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
|
|
3913
|
+
const typeKey = firstKey(tree);
|
|
3914
|
+
const fields = typeKey === void 0 ? void 0 : tree[typeKey];
|
|
3915
|
+
const fieldKey = fields && firstKey(fields);
|
|
3916
|
+
return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
|
|
3917
|
+
};
|
|
3918
|
+
//#endregion
|
|
3771
3919
|
//#region src/util/builders/common/relation-resolvers.ts
|
|
3772
3920
|
/**
|
|
3773
3921
|
* Fetches a to-many relation with per-parent limit/offset for ALL parents in a
|
|
@@ -3780,8 +3928,8 @@ const identifyRows = (field, args) => {
|
|
|
3780
3928
|
* Window functions require PostgreSQL, MySQL >= 8.0, or SQLite >= 3.25.
|
|
3781
3929
|
* Returns raw rows (NOT remapped); the caller groups + remaps them.
|
|
3782
3930
|
*/
|
|
3783
|
-
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx, whereArgs) => {
|
|
3784
|
-
const cols = getColumns(targetTable);
|
|
3931
|
+
const batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, columns, orderCtx, whereArgs) => {
|
|
3932
|
+
const cols = columns ?? getColumns(targetTable);
|
|
3785
3933
|
const orderExprs = [...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [], ...primaryKeyOrderExprs(targetTable, pkNames)];
|
|
3786
3934
|
const orderClause = orderExprs.length ? sql` order by ${sql.join(orderExprs, sql`, `)}` : sql``;
|
|
3787
3935
|
const RN = "__drizzle_graphql_rn";
|
|
@@ -3827,12 +3975,39 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3827
3975
|
operation: "relation",
|
|
3828
3976
|
relation: relationName
|
|
3829
3977
|
};
|
|
3978
|
+
/**
|
|
3979
|
+
* The columns one batch reads: the ones the selection names, plus the ones the batch needs
|
|
3980
|
+
* whatever the client asked for — the key it groups the rows by, and the ordering columns a
|
|
3981
|
+
* row cursor is encoded from. Nested relation fields contribute their own join column
|
|
3982
|
+
* through the selection context, the same way the root select path narrows.
|
|
3983
|
+
*
|
|
3984
|
+
* `undefined` means "every column", which is what a resolver with no readable resolve info
|
|
3985
|
+
* has to fall back to.
|
|
3986
|
+
*/
|
|
3987
|
+
const selectedColumns = (info, cursorEntries) => {
|
|
3988
|
+
const parsed = info ? parseResolveInfo(info) : void 0;
|
|
3989
|
+
if (!parsed) return;
|
|
3990
|
+
const tree = Object.assign({}, ...Object.values(parsed.fieldsByTypeName));
|
|
3991
|
+
const columns = extractSelectedColumnsFromTreeSQLFormat(tree, targetTable, {
|
|
3992
|
+
tableName: targetTableName,
|
|
3993
|
+
relationMap: filterCtx?.relationMap ?? {},
|
|
3994
|
+
tables,
|
|
3995
|
+
allRelations: filterCtx?.relationMap
|
|
3996
|
+
});
|
|
3997
|
+
const targetColumns = getColumns(targetTable);
|
|
3998
|
+
columns[foreignColPropName] = foreignCol;
|
|
3999
|
+
for (const [columnName] of cursorEntries ?? []) {
|
|
4000
|
+
const column = targetColumns[columnName];
|
|
4001
|
+
if (column) columns[columnName] = column;
|
|
4002
|
+
}
|
|
4003
|
+
return columns;
|
|
4004
|
+
};
|
|
3830
4005
|
const resolve = async (parent, args, context, info) => {
|
|
3831
4006
|
if (parent[relationName] !== void 0) return parent[relationName];
|
|
3832
4007
|
const localValue = parent[localColPropName];
|
|
3833
4008
|
if (localValue == null) return isOne ? null : [];
|
|
3834
4009
|
const { where: whereArg, limit: requestedLimit, offset, after, deleted } = args ?? {};
|
|
3835
|
-
const { orderByArg, limit, distinct, cursorEntries, cursorValues, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
4010
|
+
const { orderByArg, limit, distinct, cursorEntries, cursorValues, columns, loaderKey } = getOrCreateRequestValue(context, info?.fieldNodes?.[0], `relation:${tableName}::${relationName}`, () => {
|
|
3836
4011
|
const orderByArg = isOne ? args?.orderBy : withDefaultOrderBy(args ?? {}, targetTableName, policies?.defaultOrderBy).orderBy;
|
|
3837
4012
|
const limit = applyLimitPolicy(requestedLimit, limitPolicy, errorCtx);
|
|
3838
4013
|
const distinct = (args ?? {}).distinct?.length ? (args ?? {}).distinct : void 0;
|
|
@@ -3847,6 +4022,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3847
4022
|
} else cursorEntries = cursorOrderingEntries(orderByArg, targetPkNames);
|
|
3848
4023
|
}
|
|
3849
4024
|
const cursorValues = after != null && cursorEntries ? decodeCursor(after, cursorEntries) : void 0;
|
|
4025
|
+
const columns = selectedColumns(info, cursorEntries);
|
|
3850
4026
|
const argsKey = JSON.stringify({
|
|
3851
4027
|
where: whereArg ?? null,
|
|
3852
4028
|
orderBy: orderByArg ?? null,
|
|
@@ -3855,7 +4031,8 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3855
4031
|
deleted: deleted ?? defaultDeleted ?? null,
|
|
3856
4032
|
after: after ?? null,
|
|
3857
4033
|
distinct: distinct ?? null,
|
|
3858
|
-
cursor: cursorEntries ? 1 : 0
|
|
4034
|
+
cursor: cursorEntries ? 1 : 0,
|
|
4035
|
+
columns: columns ? Object.keys(columns).sort() : null
|
|
3859
4036
|
});
|
|
3860
4037
|
return {
|
|
3861
4038
|
orderByArg,
|
|
@@ -3863,6 +4040,7 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3863
4040
|
distinct,
|
|
3864
4041
|
cursorEntries,
|
|
3865
4042
|
cursorValues,
|
|
4043
|
+
columns,
|
|
3866
4044
|
loaderKey: `${tableName}::${relationName}::${argsKey}`
|
|
3867
4045
|
};
|
|
3868
4046
|
});
|
|
@@ -3886,9 +4064,9 @@ const createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limi
|
|
|
3886
4064
|
whereCondition = and(whereCondition, primaryKeyRestriction(targetTable, targetPkNames, keys));
|
|
3887
4065
|
}
|
|
3888
4066
|
let rows;
|
|
3889
|
-
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, relationFilterCtx(filterCtx, targetTableName), whereArg);
|
|
4067
|
+
if (limit != null || offset != null) rows = await batchedPaginatedRelationQuery(executor, targetTable, foreignCol, whereCondition, orderByArg, limit ?? null, offset ?? null, targetPkNames, columns, relationFilterCtx(filterCtx, targetTableName), whereArg);
|
|
3890
4068
|
else {
|
|
3891
|
-
let q = executor.select().from(targetTable).where(whereCondition);
|
|
4069
|
+
let q = (columns ? executor.select(columns) : executor.select()).from(targetTable).where(whereCondition);
|
|
3892
4070
|
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
3893
4071
|
if (orderExprs.length) q = q.orderBy(...orderExprs);
|
|
3894
4072
|
rows = await q;
|
|
@@ -4903,154 +5081,6 @@ const resolveBuildConfig = (config, schema) => {
|
|
|
4903
5081
|
};
|
|
4904
5082
|
};
|
|
4905
5083
|
//#endregion
|
|
4906
|
-
//#region src/util/parse-resolve-info.ts
|
|
4907
|
-
/** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
|
|
4908
|
-
const namedTypeOf = (type) => {
|
|
4909
|
-
let current = type;
|
|
4910
|
-
while (current?.ofType) current = current.ofType;
|
|
4911
|
-
return current;
|
|
4912
|
-
};
|
|
4913
|
-
/**
|
|
4914
|
-
* Whether a field's named output type has sub-selections. Object types and interfaces expose
|
|
4915
|
-
* `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
|
|
4916
|
-
* also expose `getFields`, but an input type can never be a field's output type, so there is
|
|
4917
|
-
* nothing here for that to collide with.
|
|
4918
|
-
*/
|
|
4919
|
-
const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
|
|
4920
|
-
/**
|
|
4921
|
-
* The fields declared on a type, or `undefined` when it has none to declare. A union is the
|
|
4922
|
-
* case that matters: it has no fields of its own, so a selection directly against one resolves
|
|
4923
|
-
* to nothing and is dropped — only the inline fragments underneath contribute.
|
|
4924
|
-
*/
|
|
4925
|
-
const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
|
|
4926
|
-
/**
|
|
4927
|
-
* The runtime variable values, in the plain-map form the directive check wants. graphql 17
|
|
4928
|
-
* wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
|
|
4929
|
-
*/
|
|
4930
|
-
const coercedVariables = (info) => {
|
|
4931
|
-
const values = info.variableValues;
|
|
4932
|
-
if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
|
|
4933
|
-
return values ?? {};
|
|
4934
|
-
};
|
|
4935
|
-
/** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
|
|
4936
|
-
const directiveIfValue = (info, value) => {
|
|
4937
|
-
if (value.kind === Kind.VARIABLE) return coercedVariables(info)[value.name.value];
|
|
4938
|
-
if (value.kind === Kind.BOOLEAN) return value.value;
|
|
4939
|
-
};
|
|
4940
|
-
/**
|
|
4941
|
-
* Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
|
|
4942
|
-
* spreads and inline fragments alike, which is where graphql puts them.
|
|
4943
|
-
*/
|
|
4944
|
-
const isExcluded = (info, node) => {
|
|
4945
|
-
for (const directive of node.directives ?? []) {
|
|
4946
|
-
const directiveName = directive.name.value;
|
|
4947
|
-
if (directiveName !== "skip" && directiveName !== "include") continue;
|
|
4948
|
-
const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
|
|
4949
|
-
if (!ifArgument) continue;
|
|
4950
|
-
const value = directiveIfValue(info, ifArgument.value);
|
|
4951
|
-
if (directiveName === "skip" ? !!value : !value) return true;
|
|
4952
|
-
}
|
|
4953
|
-
return false;
|
|
4954
|
-
};
|
|
4955
|
-
/** The type a fragment's `on Type` condition names, looked up in the executing schema. */
|
|
4956
|
-
const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
|
|
4957
|
-
/**
|
|
4958
|
-
* Introspection meta-fields are not schema fields and never reach the database. `__typename` is
|
|
4959
|
-
* the one that turns up in practice; `__id` is excluded from the rule because a schema is free
|
|
4960
|
-
* to declare a field by that name.
|
|
4961
|
-
*/
|
|
4962
|
-
const isMetaField = (name) => name.startsWith("__") && name !== "__id";
|
|
4963
|
-
/**
|
|
4964
|
-
* Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
|
|
4965
|
-
* level, this owns the entries under `parentType`'s name.
|
|
4966
|
-
*
|
|
4967
|
-
* `activeFragments` is the set of fragment names on the current spread path. A document that
|
|
4968
|
-
* passed validation cannot contain a fragment cycle, but this parser also runs on trees a
|
|
4969
|
-
* caller assembled by hand, and an unbounded walk would hang rather than fail.
|
|
4970
|
-
*/
|
|
4971
|
-
const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
|
|
4972
|
-
const parentTypeName = parentType?.name;
|
|
4973
|
-
if (!parentTypeName) return tree;
|
|
4974
|
-
let fields = tree[parentTypeName];
|
|
4975
|
-
if (!fields) {
|
|
4976
|
-
fields = {};
|
|
4977
|
-
tree[parentTypeName] = fields;
|
|
4978
|
-
}
|
|
4979
|
-
const parentFields = fieldsOf(parentType);
|
|
4980
|
-
for (const selection of selections) {
|
|
4981
|
-
if (isExcluded(info, selection)) continue;
|
|
4982
|
-
if (selection.kind === Kind.FIELD) {
|
|
4983
|
-
const name = selection.name.value;
|
|
4984
|
-
if (isMetaField(name)) continue;
|
|
4985
|
-
const fieldDef = parentFields?.[name];
|
|
4986
|
-
if (!fieldDef) continue;
|
|
4987
|
-
const fieldType = namedTypeOf(fieldDef.type);
|
|
4988
|
-
if (!fieldType) continue;
|
|
4989
|
-
const composite = isCompositeLike(fieldType);
|
|
4990
|
-
const alias = selection.alias?.value || name;
|
|
4991
|
-
let node = fields[alias];
|
|
4992
|
-
if (!node) {
|
|
4993
|
-
node = {
|
|
4994
|
-
name,
|
|
4995
|
-
alias,
|
|
4996
|
-
args: { ...getArgumentValues(fieldDef, selection, info.variableValues) },
|
|
4997
|
-
fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
|
|
4998
|
-
};
|
|
4999
|
-
fields[alias] = node;
|
|
5000
|
-
}
|
|
5001
|
-
if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
|
|
5002
|
-
continue;
|
|
5003
|
-
}
|
|
5004
|
-
if (!deep) continue;
|
|
5005
|
-
if (selection.kind === Kind.FRAGMENT_SPREAD) {
|
|
5006
|
-
const fragmentName = selection.name.value;
|
|
5007
|
-
const fragment = info.fragments?.[fragmentName];
|
|
5008
|
-
if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
|
|
5009
|
-
if (activeFragments.has(fragmentName)) continue;
|
|
5010
|
-
const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
|
|
5011
|
-
if (!isCompositeLike(fragmentType)) continue;
|
|
5012
|
-
activeFragments.add(fragmentName);
|
|
5013
|
-
try {
|
|
5014
|
-
collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
5015
|
-
} finally {
|
|
5016
|
-
activeFragments.delete(fragmentName);
|
|
5017
|
-
}
|
|
5018
|
-
continue;
|
|
5019
|
-
}
|
|
5020
|
-
if (selection.kind === Kind.INLINE_FRAGMENT) {
|
|
5021
|
-
const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
|
|
5022
|
-
if (!isCompositeLike(fragmentType)) continue;
|
|
5023
|
-
collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
|
|
5024
|
-
}
|
|
5025
|
-
}
|
|
5026
|
-
return tree;
|
|
5027
|
-
};
|
|
5028
|
-
/** The first key of an object, without materializing the whole key array to discard it. */
|
|
5029
|
-
const firstKey = (object) => {
|
|
5030
|
-
for (const key in object) return key;
|
|
5031
|
-
};
|
|
5032
|
-
/**
|
|
5033
|
-
* Reads the selection under a resolver's own field out of its `info`, as a tree of
|
|
5034
|
-
* {@link ResolveTree} nodes.
|
|
5035
|
-
*
|
|
5036
|
-
* ```ts
|
|
5037
|
-
* const parsed = parseResolveInfo(info, { deep: true })!;
|
|
5038
|
-
* const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
|
|
5039
|
-
* ```
|
|
5040
|
-
*
|
|
5041
|
-
* Returns `undefined` when the resolver's field produced no node at all, which in practice
|
|
5042
|
-
* means `info` carried no field nodes.
|
|
5043
|
-
*/
|
|
5044
|
-
const parseResolveInfo = (info, options = {}) => {
|
|
5045
|
-
const fieldNodes = info.fieldNodes;
|
|
5046
|
-
if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
|
|
5047
|
-
const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
|
|
5048
|
-
const typeKey = firstKey(tree);
|
|
5049
|
-
const fields = typeKey === void 0 ? void 0 : tree[typeKey];
|
|
5050
|
-
const fieldKey = fields && firstKey(fields);
|
|
5051
|
-
return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
|
|
5052
|
-
};
|
|
5053
|
-
//#endregion
|
|
5054
5084
|
//#region src/util/builders/aggregates.ts
|
|
5055
5085
|
/** Operations that aggregate over a set of column values. `count` is handled separately (whole rows). */
|
|
5056
5086
|
const AGGREGATE_OPS = [
|
|
@@ -5935,7 +5965,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
5935
5965
|
};
|
|
5936
5966
|
/** The values the junction stores for the target rows one operand selects. */
|
|
5937
5967
|
const selectedTargetKeys = async (executor, plan, filter, operation, scope) => {
|
|
5938
|
-
return (await executor.select().from(plan.targetTable).where(conditionOf(plan, filter, operation, scope))).map((row) => row[plan.foreignColPropName]).filter((value) => value !== void 0 && value !== null);
|
|
5968
|
+
return (await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, filter, operation, scope))).map((row) => row[plan.foreignColPropName]).filter((value) => value !== void 0 && value !== null);
|
|
5939
5969
|
};
|
|
5940
5970
|
/**
|
|
5941
5971
|
* The link half of a many-to-many write: junction rows are inserted and deleted, and both
|
|
@@ -5950,7 +5980,7 @@ const createNestedWriteRuntime = (params) => {
|
|
|
5950
5980
|
const through = plan.through;
|
|
5951
5981
|
const unlinkAll = () => executor.delete(through.table).where(eq(through.sourceCol, key));
|
|
5952
5982
|
const link = async (operands, operation) => {
|
|
5953
|
-
const existing = new Set((await executor.select().from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
5983
|
+
const existing = new Set((await executor.select({ [through.targetColPropName]: through.targetCol }).from(through.table).where(eq(through.sourceCol, key))).map((row) => row[through.targetColPropName]));
|
|
5954
5984
|
const rows = [];
|
|
5955
5985
|
for (const filter of operands) for (const targetKey of await selectedTargetKeys(executor, plan, filter, operation, scope)) if (!existing.has(targetKey)) {
|
|
5956
5986
|
existing.add(targetKey);
|
|
@@ -6011,11 +6041,11 @@ const createNestedWriteRuntime = (params) => {
|
|
|
6011
6041
|
assertSingleOperation(plan, op);
|
|
6012
6042
|
if (op["create"] !== void 0) {
|
|
6013
6043
|
const values = applyContextValues(remapFromGraphQLSingleInput({ ...op["create"] }, plan.targetTable), contextValues?.(plan.targetTableName), context);
|
|
6014
|
-
const created = (await executor.insert(plan.targetTable).values(values).returning())[0];
|
|
6044
|
+
const created = (await executor.insert(plan.targetTable).values(values).returning({ [plan.foreignColPropName]: plan.foreignCol }))[0];
|
|
6015
6045
|
if (!created) throw drizzleError(`Drizzle-GraphQL Error: 'create' on '${relationName}' inserted no row, so there is nothing to attach.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6016
6046
|
patch[plan.localColPropName] = created[plan.foreignColPropName];
|
|
6017
6047
|
} else if (op["connect"] !== void 0) {
|
|
6018
|
-
const rows = await executor.select().from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
6048
|
+
const rows = await executor.select({ [plan.foreignColPropName]: plan.foreignCol }).from(plan.targetTable).where(conditionOf(plan, op["connect"], "connect", scope));
|
|
6019
6049
|
if (rows.length !== 1) throw drizzleError(`Drizzle-GraphQL Error: 'connect' on '${relationName}' matched ${rows.length} rows — it attaches a single row, so its filter must match exactly one.`, { code: "DRIZZLE_NESTED_WRITE_INVALID" });
|
|
6020
6050
|
patch[plan.localColPropName] = rows[0][plan.foreignColPropName];
|
|
6021
6051
|
} else if (op["disconnect"] === true) patch[plan.localColPropName] = null;
|