Skip to content

Backport v16 commits #4388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
434cc98
Add redirect for /api (#4327)
JoviDeCroock Jan 15, 2025
beb9fa7
Increase print/visit performance (#4312)
JoviDeCroock May 4, 2025
d5c1e8d
fix eslint errors with website
yaacovCR Dec 3, 2024
26c7de8
fix sidebar for documentation and `/api-v16` (#4331)
dimaMachina Jan 29, 2025
31a88a3
Add cspell exception (#4335)
JoviDeCroock Jan 29, 2025
5c18922
Improve flow of documentation around GraphiQL (#4340)
benjie Feb 5, 2025
466e16c
typofix: removes extra parenthesis from getting started code snippet …
rabahalishah Feb 12, 2025
ac7ad7a
First draft for upgrade guide to v17 (#4310)
JoviDeCroock Feb 19, 2025
1d0cc8f
docs(getting-started): promises current links (#4352)
guspan-tanadi Mar 4, 2025
3805bea
fixed wrong variable name (#4351)
fto-dev Mar 8, 2025
5a169fe
fix(coerce-input-value): input object coercion rejects arrays (#4367)
JoviDeCroock May 4, 2025
b3879c8
feat(execution): add max coercion errors option to execution context …
cristunaranjo Apr 10, 2025
7662a00
Correct some syntax (#4369)
JoviDeCroock Apr 10, 2025
0f4a254
Update docs for execution options (#4368)
JoviDeCroock Apr 10, 2025
e00e83a
docs: Update getting-started.mdx (#4373)
Shubhdeep12 Apr 24, 2025
dd6f507
Refactor every code-first example to leverage resolve (#4372)
JoviDeCroock Apr 24, 2025
eb41fba
Change to gqlConf 2025 (#4378)
JoviDeCroock Apr 25, 2025
85e5e11
Add missing parenthesis (#4379)
benjie Apr 25, 2025
8b73a88
docs: anatomy of a resolver (#4381)
sarahxsanders May 3, 2025
28ace28
docs: understanding graphql errors (#4382)
sarahxsanders May 3, 2025
5460ae5
Fix tests
JoviDeCroock May 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![GraphQLConf 2024 Banner: September 10-12, San Francisco. Hosted by the GraphQL Foundation](https://github.com/user-attachments/assets/2d048502-e5b2-4e9d-a02a-50b841824de6)](https://graphql.org/conf/2024/?utm_source=github&utm_medium=graphql_js&utm_campaign=readme)
[![GraphQLConf 2025 Banner: September 08-10, Amsterdam. Hosted by the GraphQL Foundation](./assets/graphql-conf-2025.png)](https://graphql.org/conf/2025/?utm_source=github&utm_medium=graphql_js&utm_campaign=readme)

# GraphQL.js

Expand Down
Binary file added assets/graphql-conf-2025.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions benchmark/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ export const bigSchemaSDL = fs.readFileSync(
export const bigSchemaIntrospectionResult = JSON.parse(
fs.readFileSync(new URL('github-schema.json', import.meta.url), 'utf8'),
);

export const bigDocumentSDL = fs.readFileSync(
new URL('kitchen-sink.graphql', import.meta.url),
'utf8',
);
65 changes: 65 additions & 0 deletions benchmark/kitchen-sink.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
whoever123is: node(id: [123, 456]) {
id
... on User @onInlineFragment {
field2 {
id
alias: field1(first: 10, after: $foo) @include(if: $foo) {
id
...frag @onFragmentSpread
}
}
}
... @skip(unless: $foo) {
id
}
... {
id
}
}
}

mutation likeStory @onMutation {
like(story: 123) @onField {
story {
id @onField
}
}
}

subscription StoryLikeSubscription(
$input: StoryLikeSubscribeInput @onVariableDefinition
) @onSubscription {
storyLikeSubscribe(input: $input) {
story {
likers {
count
}
likeSentence {
text
}
}
}
}

fragment frag on Friend @onFragmentDefinition {
foo(
size: $size
bar: $b
obj: {
key: "value"
block: """
block string uses \"""
"""
}
)
}

{
unnamed(truthy: true, falsy: false, nullish: null)
query
}

query {
__typename
}
14 changes: 14 additions & 0 deletions benchmark/printer-benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { parse } from 'graphql/language/parser.js';
import { print } from 'graphql/language/printer.js';

import { bigDocumentSDL } from './fixtures.js';

const document = parse(bigDocumentSDL);

export const benchmark = {
name: 'Print kitchen sink document',
count: 1000,
measure() {
print(document);
},
};
1 change: 1 addition & 0 deletions cspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ words:
- svgr
- ruru
- oneof
- vercel

# used as href anchors
- graphqlerror
Expand Down
71 changes: 71 additions & 0 deletions src/execution/__tests__/executor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Kind } from '../../language/kinds.js';
import { parse } from '../../language/parser.js';

import {
GraphQLInputObjectType,
GraphQLInterfaceType,
GraphQLList,
GraphQLNonNull,
Expand Down Expand Up @@ -1380,4 +1381,74 @@ describe('Execute: Handles basic execution tasks', () => {
expect(result).to.deep.equal({ data: { foo: { bar: 'bar' } } });
expect(possibleTypes).to.deep.equal([fooObject]);
});

it('uses a different number of max coercion errors', () => {
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
dummy: { type: GraphQLString },
},
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: {
updateUser: {
type: GraphQLString,
args: {
data: {
type: new GraphQLInputObjectType({
name: 'User',
fields: {
email: { type: new GraphQLNonNull(GraphQLString) },
},
}),
},
},
},
},
}),
});

const document = parse(`
mutation ($data: User) {
updateUser(data: $data)
}
`);

const options = {
maxCoercionErrors: 1,
};

const result = executeSync({
schema,
document,
variableValues: {
data: {
email: '',
wrongArg: 'wrong',
wrongArg2: 'wrong',
wrongArg3: 'wrong',
},
},
options,
});

// Returns at least 2 errors, one for the first 'wrongArg', and one for coercion limit
expect(result.errors).to.have.lengthOf(options.maxCoercionErrors + 1);

expectJSON(result).toDeepEqual({
errors: [
{
message:
'Variable "$data" has invalid value: Expected value of type "User" not to include unknown field "wrongArg", found: { email: "", wrongArg: "wrong", wrongArg2: "wrong", wrongArg3: "wrong" }.',
locations: [{ line: 2, column: 17 }],
},
{
message:
'Too many errors processing variables, error limit reached. Execution aborted.',
},
],
});
});
});
11 changes: 7 additions & 4 deletions src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ export interface ExecutionArgs {
enableEarlyExecution?: Maybe<boolean>;
hideSuggestions?: Maybe<boolean>;
abortSignal?: Maybe<AbortSignal>;
/** Additional execution options. */
options?: {
/** Set the maximum number of errors allowed for coercing (defaults to 50). */
maxCoercionErrors?: number;
};
}

export interface StreamUsage {
Expand Down Expand Up @@ -473,6 +478,7 @@ export function validateExecutionArgs(
perEventExecutor,
enableEarlyExecution,
abortSignal,
options,
} = args;

if (abortSignal?.aborted) {
Expand Down Expand Up @@ -534,10 +540,7 @@ export function validateExecutionArgs(
schema,
variableDefinitions,
rawVariableValues ?? {},
{
maxErrors: 50,
hideSuggestions,
},
{ maxErrors: options?.maxCoercionErrors ?? 50, hideSuggestions },
);

if (variableValuesOrErrors.errors) {
Expand Down
5 changes: 1 addition & 4 deletions src/language/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,7 @@ export function visit(
}
}
} else {
node = Object.defineProperties(
{},
Object.getOwnPropertyDescriptors(node),
);
node = { ...node };
for (const [editKey, editValue] of edits) {
node[editKey] = editValue;
}
Expand Down
16 changes: 16 additions & 0 deletions src/utilities/__tests__/coerceInputValue-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,19 @@ describe('coerceInputValue', () => {
});

describe('for GraphQLInputObject', () => {
const DeepObject = new GraphQLInputObjectType({
name: 'DeepObject',
fields: {
foo: { type: new GraphQLNonNull(GraphQLInt) },
bar: { type: GraphQLInt },
},
});
const TestInputObject = new GraphQLInputObjectType({
name: 'TestInputObject',
fields: {
foo: { type: new GraphQLNonNull(GraphQLInt) },
bar: { type: GraphQLInt },
deepObject: { type: DeepObject },
},
});

Expand All @@ -153,6 +161,14 @@ describe('coerceInputValue', () => {
it('invalid for an unknown field', () => {
test({ foo: 123, unknownField: 123 }, TestInputObject, undefined);
});

it('invalid for an array type', () => {
test([{ foo: 1 }, { bar: 1 }], TestInputObject, undefined);
});

it('invalid for an array type on a nested field', () => {
test({ foo: 1, deepObject: [1, 2, 3] }, TestInputObject, undefined);
});
});

describe('for GraphQLInputObject that isOneOf', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/utilities/coerceInputValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export function coerceInputValue(
}

if (isInputObjectType(type)) {
if (!isObjectLike(inputValue)) {
return; // Invalid: intentionally return no value.
if (!isObjectLike(inputValue) || Array.isArray(inputValue)) {
return;
}

const coercedValue: any = {};
Expand Down
5 changes: 5 additions & 0 deletions website/next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/* eslint-disable camelcase */
import path from 'node:path';
import fs from 'node:fs';

const fileContents = fs.readFileSync('./vercel.json', 'utf-8');
const vercel = JSON.parse(fileContents);

import nextra from 'nextra';

Expand Down Expand Up @@ -29,6 +33,7 @@ export default withNextra({
});
return config;
},
redirects: async () => vercel.redirects,
output: 'export',
images: {
loader: 'custom',
Expand Down
35 changes: 12 additions & 23 deletions website/pages/_meta.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
const meta = {
index: '',
'-- 1': {
type: 'separator',
title: 'GraphQL.JS Tutorial',
docs: {
type: 'page',
title: 'Documentation',
},
'getting-started': '',
'running-an-express-graphql-server': '',
'graphql-clients': '',
'basic-types': '',
'passing-arguments': '',
'object-types': '',
'mutations-and-input-types': '',
'authentication-and-express-middleware': '',
'-- 2': {
type: 'separator',
title: 'Advanced Guides',
},
'constructing-types': '',
'oneof-input-objects': 'OneOf input objects',
'defer-stream': '',
'-- 3': {
type: 'separator',
title: 'FAQ',
'upgrade-guides': {
type: 'menu',
title: 'Upgrade Guides',
items: {
'v16-v17': {
title: 'v16 to v17',
href: '/upgrade-guides/v16-v17',
},
},
},
'going-to-production': '',
'api-v16': {
type: 'menu',
title: 'API',
Expand Down
2 changes: 1 addition & 1 deletion website/pages/api-v16/error.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class GraphQLError extends Error {
source?: Source,
positions?: number[],
originalError?: Error,
extensions?: { [key: string]: mixed },
extensions?: Record<string, unknown>,
);
}
```
Expand Down
Loading
Loading