@vantreeseba/drizzle-graphql 9.1.1 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1831,6 +1831,19 @@ image, a stored value the scalar transporting it cannot represent, is
1831
1831
  `DRIZZLE_UNREPRESENTABLE_VALUE`; it says the data is out of range, not that the request was
1832
1832
  wrong.
1833
1833
 
1834
+ An `update`'s `set` giving a `NOT NULL` column an explicit `null` is `DRIZZLE_NOT_NULL`, and the
1835
+ error names the column. Every field of an update input is nullable — that is what makes a partial
1836
+ `set` possible — so the nullability says nothing about the column, and an update has no default to
1837
+ fall back on: `null` there can only mean "write null". Nothing is written, including the rest of
1838
+ the `set`. To leave a column alone, omit it.
1839
+
1840
+ An **insert** reads the same `null` the other way, and is unchanged. A `NOT NULL` column's field
1841
+ on a create input is non-null unless the database, drizzle, or a nested `create` can fill it in,
1842
+ so the only nulls that reach a resolver are ones the schema offered as "I am not supplying this" —
1843
+ and they let the default apply, which is what makes `id: $id` with a null variable insert a new
1844
+ row. A `NOT NULL` column with nothing to fill it is rejected while GraphQL coerces the request.
1845
+ Upserts take the create input, so they follow the insert rule.
1846
+
1834
1847
  The `drizzle` block is only on errors raised from inside a field. An argument rejected while
1835
1848
  GraphQL coerces the request — a `BigInt` variable that is not an integer, say — is rejected
1836
1849
  before any resolver runs, so it carries its `code` but no `drizzle` and no `path`.
package/dist/index.cjs CHANGED
@@ -29,7 +29,6 @@ let graphql = require("graphql");
29
29
  let pluralize = require("pluralize");
30
30
  pluralize = __toESM(pluralize, 1);
31
31
  let graphql_scalars = require("graphql-scalars");
32
- let graphql_parse_resolve_info = require("graphql-parse-resolve-info");
33
32
  //#region src/util/builders/common/errors.ts
34
33
  /**
35
34
  * A `GraphQLError` carrying its context as data. The message says what went wrong, in prose
@@ -588,12 +587,13 @@ const remapFromGraphQLCore = (value, column, columnName) => {
588
587
  return value;
589
588
  }
590
589
  };
591
- const remapFromGraphQLSingleInput = (queryInput, table) => {
590
+ const remapFromGraphQLSingleInput = (queryInput, table, operation = "insert") => {
592
591
  for (const [key, value] of Object.entries(queryInput)) if (value === void 0) delete queryInput[key];
593
592
  else {
594
593
  const column = (0, drizzle_orm.getTableColumns)(table)[key];
595
594
  if (!column) throw drizzleError(`Unknown column: ${key}`, { code: "DRIZZLE_UNKNOWN_COLUMN" });
596
595
  if (value === null && column.notNull) {
596
+ if (operation === "update") throw drizzleError(`Column '${key}' cannot be set to null.`, { code: "DRIZZLE_NOT_NULL" });
597
597
  delete queryInput[key];
598
598
  continue;
599
599
  }
@@ -601,8 +601,8 @@ const remapFromGraphQLSingleInput = (queryInput, table) => {
601
601
  }
602
602
  return queryInput;
603
603
  };
604
- const remapFromGraphQLArrayInput = (queryInput, table) => {
605
- for (const entry of queryInput) remapFromGraphQLSingleInput(entry, table);
604
+ const remapFromGraphQLArrayInput = (queryInput, table, operation = "insert") => {
605
+ for (const entry of queryInput) remapFromGraphQLSingleInput(entry, table, operation);
606
606
  return queryInput;
607
607
  };
608
608
  //#endregion
@@ -3004,10 +3004,14 @@ const splitFieldUpdateOperations = (values, table, tableName) => {
3004
3004
  *
3005
3005
  * Safe to call on every update path regardless of `features.fieldUpdateOperations` — see the
3006
3006
  * shape check in {@link splitFieldUpdateOperations}.
3007
+ *
3008
+ * This is the single funnel every update goes through — the plural and `Single` mutations,
3009
+ * `updateMany`, the `Count` mutations and the MySQL builder's own — which is why it is the
3010
+ * one place that has to tell the remapper it is serving an update rather than an insert.
3007
3011
  */
3008
3012
  const remapUpdateInput = (values, table, tableName) => {
3009
3013
  const { columns, expressions } = splitFieldUpdateOperations(values, table, tableName);
3010
- const remapped = remapFromGraphQLSingleInput(columns, table);
3014
+ const remapped = remapFromGraphQLSingleInput(columns, table, "update");
3011
3015
  return Object.keys(expressions).length ? {
3012
3016
  ...remapped,
3013
3017
  ...expressions
@@ -4764,6 +4768,150 @@ const resolveBuildConfig = (config, schema) => {
4764
4768
  };
4765
4769
  };
4766
4770
  //#endregion
4771
+ //#region src/util/parse-resolve-info.ts
4772
+ /** Unwraps `[T]!`-style wrappers down to the named type. `getNamedType` without the realm tie. */
4773
+ const namedTypeOf = (type) => {
4774
+ let current = type;
4775
+ while (current?.ofType) current = current.ofType;
4776
+ return current;
4777
+ };
4778
+ /**
4779
+ * Whether a field's named output type has sub-selections. Object types and interfaces expose
4780
+ * `getFields`, unions expose `getTypes`; scalars and enums expose neither. Input object types
4781
+ * also expose `getFields`, but an input type can never be a field's output type, so there is
4782
+ * nothing here for that to collide with.
4783
+ */
4784
+ const isCompositeLike = (type) => !!type && (typeof type.getFields === "function" || typeof type.getTypes === "function");
4785
+ /**
4786
+ * The fields declared on a type, or `undefined` when it has none to declare. A union is the
4787
+ * case that matters: it has no fields of its own, so a selection directly against one resolves
4788
+ * to nothing and is dropped — only the inline fragments underneath contribute.
4789
+ */
4790
+ const fieldsOf = (type) => typeof type?.getFields === "function" ? type.getFields() : void 0;
4791
+ /**
4792
+ * The runtime variable values, in the plain-map form the directive check wants. graphql 17
4793
+ * wraps them in `{ sources, coerced }`; graphql 16 hands over the map itself.
4794
+ */
4795
+ const coercedVariables = (info) => {
4796
+ const values = info.variableValues;
4797
+ if (values && typeof values === "object" && "coerced" in values) return values.coerced ?? {};
4798
+ return values ?? {};
4799
+ };
4800
+ /** The value of a `@skip` / `@include` `if:` argument — a literal or a variable reference. */
4801
+ const directiveIfValue = (info, value) => {
4802
+ if (value.kind === graphql.Kind.VARIABLE) return coercedVariables(info)[value.name.value];
4803
+ if (value.kind === graphql.Kind.BOOLEAN) return value.value;
4804
+ };
4805
+ /**
4806
+ * Whether `@skip` / `@include` on this selection exclude it. Applies to fields, fragment
4807
+ * spreads and inline fragments alike, which is where graphql puts them.
4808
+ */
4809
+ const isExcluded = (info, node) => {
4810
+ for (const directive of node.directives ?? []) {
4811
+ const directiveName = directive.name.value;
4812
+ if (directiveName !== "skip" && directiveName !== "include") continue;
4813
+ const ifArgument = directive.arguments?.find((argument) => argument.name.value === "if");
4814
+ if (!ifArgument) continue;
4815
+ const value = directiveIfValue(info, ifArgument.value);
4816
+ if (directiveName === "skip" ? !!value : !value) return true;
4817
+ }
4818
+ return false;
4819
+ };
4820
+ /** The type a fragment's `on Type` condition names, looked up in the executing schema. */
4821
+ const typeFromCondition = (info, condition) => info.schema.getType(condition.name.value);
4822
+ /**
4823
+ * Introspection meta-fields are not schema fields and never reach the database. `__typename` is
4824
+ * the one that turns up in practice; `__id` is excluded from the rule because a schema is free
4825
+ * to declare a field by that name.
4826
+ */
4827
+ const isMetaField = (name) => name.startsWith("__") && name !== "__id";
4828
+ /**
4829
+ * Merges one selection set into `tree`, which is a `fieldsByTypeName` map — the caller owns the
4830
+ * level, this owns the entries under `parentType`'s name.
4831
+ *
4832
+ * `activeFragments` is the set of fragment names on the current spread path. A document that
4833
+ * passed validation cannot contain a fragment cycle, but this parser also runs on trees a
4834
+ * caller assembled by hand, and an unbounded walk would hang rather than fail.
4835
+ */
4836
+ const collectSelections = (selections, info, tree, parentType, deep, activeFragments) => {
4837
+ const parentTypeName = parentType?.name;
4838
+ if (!parentTypeName) return tree;
4839
+ let fields = tree[parentTypeName];
4840
+ if (!fields) {
4841
+ fields = {};
4842
+ tree[parentTypeName] = fields;
4843
+ }
4844
+ for (const selection of selections) {
4845
+ if (isExcluded(info, selection)) continue;
4846
+ if (selection.kind === graphql.Kind.FIELD) {
4847
+ const name = selection.name.value;
4848
+ if (isMetaField(name)) continue;
4849
+ const fieldDef = fieldsOf(parentType)?.[name];
4850
+ if (!fieldDef) continue;
4851
+ const fieldType = namedTypeOf(fieldDef.type);
4852
+ if (!fieldType) continue;
4853
+ const composite = isCompositeLike(fieldType);
4854
+ const alias = selection.alias?.value || name;
4855
+ let node = fields[alias];
4856
+ if (!node) {
4857
+ node = {
4858
+ name,
4859
+ alias,
4860
+ args: { ...(0, graphql.getArgumentValues)(fieldDef, selection, info.variableValues) },
4861
+ fieldsByTypeName: composite && fieldType.name ? { [fieldType.name]: {} } : {}
4862
+ };
4863
+ fields[alias] = node;
4864
+ }
4865
+ if (selection.selectionSet && deep && composite) collectSelections(selection.selectionSet.selections, info, node.fieldsByTypeName, fieldType, deep, activeFragments);
4866
+ continue;
4867
+ }
4868
+ if (!deep) continue;
4869
+ if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
4870
+ const fragmentName = selection.name.value;
4871
+ const fragment = info.fragments?.[fragmentName];
4872
+ if (!fragment) throw new Error(`Drizzle-GraphQL Error: unknown fragment '${fragmentName}' — it is spread in the query but not defined in the document.`);
4873
+ if (activeFragments.has(fragmentName)) continue;
4874
+ const fragmentType = fragment.typeCondition ? typeFromCondition(info, fragment.typeCondition) : parentType;
4875
+ if (!isCompositeLike(fragmentType)) continue;
4876
+ activeFragments.add(fragmentName);
4877
+ try {
4878
+ collectSelections(fragment.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
4879
+ } finally {
4880
+ activeFragments.delete(fragmentName);
4881
+ }
4882
+ continue;
4883
+ }
4884
+ if (selection.kind === graphql.Kind.INLINE_FRAGMENT) {
4885
+ const fragmentType = selection.typeCondition ? typeFromCondition(info, selection.typeCondition) : parentType;
4886
+ if (!isCompositeLike(fragmentType)) continue;
4887
+ collectSelections(selection.selectionSet.selections, info, tree, fragmentType, deep, activeFragments);
4888
+ }
4889
+ }
4890
+ return tree;
4891
+ };
4892
+ const firstKey = (object) => Object.keys(object)[0];
4893
+ /**
4894
+ * Reads the selection under a resolver's own field out of its `info`, as a tree of
4895
+ * {@link ResolveTree} nodes.
4896
+ *
4897
+ * ```ts
4898
+ * const parsed = parseResolveInfo(info, { deep: true })!;
4899
+ * const selected = parsed.fieldsByTypeName['Users']; // keyed by response key
4900
+ * ```
4901
+ *
4902
+ * Returns `undefined` when the resolver's field produced no node at all, which in practice
4903
+ * means `info` carried no field nodes.
4904
+ */
4905
+ const parseResolveInfo = (info, options = {}) => {
4906
+ const fieldNodes = info.fieldNodes;
4907
+ if (!fieldNodes) throw new Error("Drizzle-GraphQL Error: resolve info carries no fieldNodes, so no selection could be read from it.");
4908
+ const tree = collectSelections(fieldNodes, info, {}, info.parentType, options.deep ?? true, /* @__PURE__ */ new Set());
4909
+ const typeKey = firstKey(tree);
4910
+ const fields = typeKey === void 0 ? void 0 : tree[typeKey];
4911
+ const fieldKey = fields && firstKey(fields);
4912
+ return fieldKey === void 0 ? void 0 : fields?.[fieldKey];
4913
+ };
4914
+ //#endregion
4767
4915
  //#region src/util/builders/aggregates.ts
4768
4916
  /** Operations that aggregate over a set of column values. `count` is handled separately (whole rows). */
4769
4917
  const AGGREGATE_OPS = [
@@ -4905,7 +5053,7 @@ const aggregateTarget = (table, tableName, typeName, resolveName) => {
4905
5053
  * client asked for nothing runnable (`__typename` only, or empty sub-selections).
4906
5054
  */
4907
5055
  const parseAggregateRequest = (info, target, rootTypeName = target.aggregateTypeName) => {
4908
- const selectionTree = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true }).fieldsByTypeName[rootTypeName] ?? {};
5056
+ const selectionTree = parseResolveInfo(info, { deep: true }).fieldsByTypeName[rootTypeName] ?? {};
4909
5057
  const request = {
4910
5058
  count: false,
4911
5059
  ops: {
@@ -5833,7 +5981,7 @@ const createSelectGenerators = (primaryKeyPropNames, nullOrdering) => {
5833
5981
  resolver: async (_source, rawArgs, context, info) => {
5834
5982
  const args = withDefaultOrderBy(rawArgs, tableName, policies?.defaultOrderBy);
5835
5983
  try {
5836
- const parsedInfo = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
5984
+ const parsedInfo = parseResolveInfo(info, { deep: true });
5837
5985
  const { executor, queryBase: requestQueryBase } = resolveQueryExecutor(db, context, tableName, queryBase);
5838
5986
  return await runRelationalSelect({
5839
5987
  queryBase: requestQueryBase,
@@ -5879,7 +6027,7 @@ const createSelectGenerators = (primaryKeyPropNames, nullOrdering) => {
5879
6027
  resolver: async (_source, rawArgs, context, info) => {
5880
6028
  const args = withDefaultOrderBy(rawArgs, tableName, policies?.defaultOrderBy);
5881
6029
  try {
5882
- const parsedInfo = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
6030
+ const parsedInfo = parseResolveInfo(info, { deep: true });
5883
6031
  const { executor, queryBase: requestQueryBase } = resolveQueryExecutor(db, context, tableName, queryBase);
5884
6032
  return await runRelationalSelect({
5885
6033
  queryBase: requestQueryBase,
@@ -6543,7 +6691,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6543
6691
  const contextColumns = policies?.contextValues?.(tableName);
6544
6692
  const scope = policies?.scope?.(context);
6545
6693
  const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : args.values, table), contextColumns, context);
6546
- const parsedInfo = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
6694
+ const parsedInfo = parseResolveInfo(info, { deep: true });
6547
6695
  const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6548
6696
  relationMap,
6549
6697
  tables,
@@ -6598,7 +6746,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6598
6746
  const contextColumns = policies?.contextValues?.(tableName);
6599
6747
  const scope = policies?.scope?.(context);
6600
6748
  const input = nestedEntry ? {} : applyContextValues(remapFromGraphQLSingleInput(entry ? entry.columns : args.values, table), contextColumns, context);
6601
- const parsedInfo = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
6749
+ const parsedInfo = parseResolveInfo(info, { deep: true });
6602
6750
  const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6603
6751
  relationMap,
6604
6752
  tables,
@@ -6671,7 +6819,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6671
6819
  const contextColumns = policies?.contextValues?.(tableName);
6672
6820
  const scope = policies?.scope?.(context);
6673
6821
  const input = nestedEntries ? [] : applyContextValuesAll(remapFromGraphQLArrayInput(entries ? entries.map((entry) => entry.columns) : supplied, table), contextColumns, context);
6674
- const parsedInfo = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
6822
+ const parsedInfo = parseResolveInfo(info, { deep: true });
6675
6823
  const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6676
6824
  relationMap,
6677
6825
  tables,
@@ -6742,7 +6890,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6742
6890
  const { where, set } = args;
6743
6891
  const scope = policies?.scope?.(context);
6744
6892
  await before();
6745
- const parsedInfo = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
6893
+ const parsedInfo = parseResolveInfo(info, { deep: true });
6746
6894
  const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
6747
6895
  relationMap,
6748
6896
  tables,
@@ -6823,7 +6971,7 @@ const buildWriteResolvers = (primaryKeyPropNames) => {
6823
6971
  const hard = canHardDelete && args.hard === true;
6824
6972
  const scope = policies?.scope?.(context);
6825
6973
  await before();
6826
- const parsedInfo = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
6974
+ const parsedInfo = parseResolveInfo(info, { deep: true });
6827
6975
  const columns = extractSelectedColumnsFromTreeSQLFormat(parsedInfo.fieldsByTypeName[applyObjectTypeName(typeName, tableName, resolveName)], table, selectionCtx);
6828
6976
  const relationCtx = relationFilterCtx(filterCtx, tableName);
6829
6977
  const filters = withScope(scope, tableName, table, single || requireWhere ? extractRequiredFilters(table, tableName, where, relationCtx) : where ? extractFilters(table, tableName, where, relationCtx) : void 0, restore ? "ONLY" : hard ? "INCLUDE" : void 0);
@@ -7155,7 +7303,7 @@ const createUpdateManyGenerator = (primaryKeyPropNames, runBatch = runUpdateMany
7155
7303
  await before();
7156
7304
  const scope = policies?.scope?.(context);
7157
7305
  const contextColumns = policies?.contextValues?.(tableName);
7158
- const parsedInfo = (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
7306
+ const parsedInfo = parseResolveInfo(info, { deep: true });
7159
7307
  const { columns, hasRelations, withParams } = prepareMutationRelationColumns({
7160
7308
  relationMap,
7161
7309
  tables,
@@ -7341,7 +7489,7 @@ const namingRuleOf = (options) => resolveGeneratedTypeNames({
7341
7489
  */
7342
7490
  const parseInfo = (info) => {
7343
7491
  if ("fieldsByTypeName" in info) return info;
7344
- return (0, graphql_parse_resolve_info.parseResolveInfo)(info, { deep: true });
7492
+ return parseResolveInfo(info, { deep: true });
7345
7493
  };
7346
7494
  /**
7347
7495
  * Translates a GraphQL selection set into the relational query builder's `with` tree — the
@@ -7469,6 +7617,7 @@ exports.extractOrderBy = extractOrderBy;
7469
7617
  exports.extractRelationJoinColumns = extractRelationJoinColumns;
7470
7618
  exports.identifyRows = identifyRows;
7471
7619
  exports.isDrizzleFieldExtension = isDrizzleFieldExtension;
7620
+ exports.parseResolveInfo = parseResolveInfo;
7472
7621
  exports.resolveSelection = resolveSelection;
7473
7622
  exports.selectionToWith = selectionToWith;
7474
7623
  exports.singularizeMapper = singularizeMapper;