├── .babelrc-deno.json ├── .babelrc-npm.json ├── .babelrc.json ├── .c8rc.json ├── .eslintignore ├── .eslintrc.yml ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── workflows │ ├── ci.yml │ ├── deploy-artifact-as-branch.yml │ ├── pull_request.yml │ ├── pull_request_opened.yml │ └── push.yml ├── .gitignore ├── .mocharc.yml ├── .node-version ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── assets └── graphql-conf-2025.png ├── benchmark ├── benchmark.js ├── buildASTSchema-benchmark.js ├── buildClientSchema-benchmark.js ├── fixtures.js ├── github-schema.graphql ├── github-schema.json ├── introspectionFromSchema-benchmark.js ├── kitchen-sink.graphql ├── parser-benchmark.js ├── printer-benchmark.js ├── repeated-fields-benchmark.js ├── validateGQL-benchmark.js ├── validateInvalidGQL-benchmark.js ├── validateSDL-benchmark.js ├── visit-benchmark.js └── visitInParallel-benchmark.js ├── codecov.yml ├── cspell.yml ├── integrationTests ├── README.md ├── integration-test.js ├── node │ ├── index.js │ ├── package.json │ └── test.js ├── ts │ ├── TypedQueryDocumentNode-test.ts │ ├── basic-test.ts │ ├── extensions-test.ts │ ├── internalImports-test.ts │ ├── package.json │ ├── test.js │ └── tsconfig.json └── webpack │ ├── entry.js │ ├── package.json │ ├── test.js │ └── webpack.config.json ├── package-lock.json ├── package.json ├── resources ├── add-extension-to-import-paths.js ├── build-deno.js ├── build-npm.js ├── checkgit.sh ├── diff-npm-package.js ├── eslint-internal-rules │ ├── README.md │ ├── index.js │ ├── no-dir-import.js │ ├── only-ascii.js │ ├── package.json │ └── require-to-string-tag.js ├── gen-changelog.js ├── gen-version.js ├── inline-invariant.js ├── ts-register.js └── utils.js ├── src ├── README.md ├── __testUtils__ │ ├── __tests__ │ │ ├── dedent-test.ts │ │ ├── genFuzzStrings-test.ts │ │ ├── inspectStr-test.ts │ │ └── resolveOnNextTick-test.ts │ ├── dedent.ts │ ├── expectJSON.ts │ ├── genFuzzStrings.ts │ ├── inspectStr.ts │ ├── kitchenSinkQuery.ts │ ├── kitchenSinkSDL.ts │ └── resolveOnNextTick.ts ├── __tests__ │ ├── starWarsData.ts │ ├── starWarsIntrospection-test.ts │ ├── starWarsQuery-test.ts │ ├── starWarsSchema.ts │ ├── starWarsValidation-test.ts │ └── version-test.ts ├── error │ ├── GraphQLError.ts │ ├── README.md │ ├── __tests__ │ │ ├── GraphQLError-test.ts │ │ └── locatedError-test.ts │ ├── index.ts │ ├── locatedError.ts │ └── syntaxError.ts ├── execution │ ├── README.md │ ├── __tests__ │ │ ├── abstract-test.ts │ │ ├── directives-test.ts │ │ ├── executor-test.ts │ │ ├── lists-test.ts │ │ ├── mapAsyncIterator-test.ts │ │ ├── mutations-test.ts │ │ ├── nonnull-test.ts │ │ ├── oneof-test.ts │ │ ├── resolve-test.ts │ │ ├── schema-test.ts │ │ ├── simplePubSub-test.ts │ │ ├── simplePubSub.ts │ │ ├── subscribe-test.ts │ │ ├── sync-test.ts │ │ ├── union-interface-test.ts │ │ └── variables-test.ts │ ├── collectFields.ts │ ├── execute.ts │ ├── index.ts │ ├── mapAsyncIterator.ts │ ├── subscribe.ts │ └── values.ts ├── graphql.ts ├── index.ts ├── jsutils │ ├── Maybe.ts │ ├── ObjMap.ts │ ├── Path.ts │ ├── PromiseOrValue.ts │ ├── README.md │ ├── __tests__ │ │ ├── Path-test.ts │ │ ├── didYouMean-test.ts │ │ ├── identityFunc-test.ts │ │ ├── inspect-test.ts │ │ ├── instanceOf-test.ts │ │ ├── invariant-test.ts │ │ ├── isAsyncIterable-test.ts │ │ ├── isIterableObject-test.ts │ │ ├── isObjectLike-test.ts │ │ ├── naturalCompare-test.ts │ │ ├── suggestionList-test.ts │ │ └── toObjMap-test.ts │ ├── devAssert.ts │ ├── didYouMean.ts │ ├── groupBy.ts │ ├── identityFunc.ts │ ├── inspect.ts │ ├── instanceOf.ts │ ├── invariant.ts │ ├── isAsyncIterable.ts │ ├── isIterableObject.ts │ ├── isObjectLike.ts │ ├── isPromise.ts │ ├── keyMap.ts │ ├── keyValMap.ts │ ├── mapValue.ts │ ├── memoize3.ts │ ├── naturalCompare.ts │ ├── printPathArray.ts │ ├── promiseForObject.ts │ ├── promiseReduce.ts │ ├── suggestionList.ts │ ├── toError.ts │ └── toObjMap.ts ├── language │ ├── README.md │ ├── __tests__ │ │ ├── blockString-fuzz.ts │ │ ├── blockString-test.ts │ │ ├── lexer-test.ts │ │ ├── parser-test.ts │ │ ├── predicates-test.ts │ │ ├── printLocation-test.ts │ │ ├── printString-test.ts │ │ ├── printer-test.ts │ │ ├── schema-parser-test.ts │ │ ├── schema-printer-test.ts │ │ ├── source-test.ts │ │ └── visitor-test.ts │ ├── ast.ts │ ├── blockString.ts │ ├── characterClasses.ts │ ├── directiveLocation.ts │ ├── index.ts │ ├── kinds.ts │ ├── lexer.ts │ ├── location.ts │ ├── parser.ts │ ├── predicates.ts │ ├── printLocation.ts │ ├── printString.ts │ ├── printer.ts │ ├── source.ts │ ├── tokenKind.ts │ └── visitor.ts ├── subscription │ ├── README.md │ └── index.ts ├── type │ ├── README.md │ ├── __tests__ │ │ ├── assertName-test.ts │ │ ├── definition-test.ts │ │ ├── directive-test.ts │ │ ├── enumType-test.ts │ │ ├── extensions-test.ts │ │ ├── introspection-test.ts │ │ ├── predicate-test.ts │ │ ├── scalars-test.ts │ │ ├── schema-test.ts │ │ └── validation-test.ts │ ├── assertName.ts │ ├── definition.ts │ ├── directives.ts │ ├── index.ts │ ├── introspection.ts │ ├── scalars.ts │ ├── schema.ts │ └── validate.ts ├── utilities │ ├── README.md │ ├── TypeInfo.ts │ ├── __tests__ │ │ ├── TypeInfo-test.ts │ │ ├── astFromValue-test.ts │ │ ├── buildASTSchema-test.ts │ │ ├── buildClientSchema-test.ts │ │ ├── coerceInputValue-test.ts │ │ ├── concatAST-test.ts │ │ ├── extendSchema-test.ts │ │ ├── findBreakingChanges-test.ts │ │ ├── getIntrospectionQuery-test.ts │ │ ├── getOperationAST-test.ts │ │ ├── getOperationRootType-test.ts │ │ ├── introspectionFromSchema-test.ts │ │ ├── lexicographicSortSchema-test.ts │ │ ├── printSchema-test.ts │ │ ├── separateOperations-test.ts │ │ ├── sortValueNode-test.ts │ │ ├── stripIgnoredCharacters-fuzz.ts │ │ ├── stripIgnoredCharacters-test.ts │ │ ├── typeComparators-test.ts │ │ ├── valueFromAST-test.ts │ │ └── valueFromASTUntyped-test.ts │ ├── assertValidName.ts │ ├── astFromValue.ts │ ├── buildASTSchema.ts │ ├── buildClientSchema.ts │ ├── coerceInputValue.ts │ ├── concatAST.ts │ ├── extendSchema.ts │ ├── findBreakingChanges.ts │ ├── getIntrospectionQuery.ts │ ├── getOperationAST.ts │ ├── getOperationRootType.ts │ ├── index.ts │ ├── introspectionFromSchema.ts │ ├── lexicographicSortSchema.ts │ ├── printSchema.ts │ ├── separateOperations.ts │ ├── sortValueNode.ts │ ├── stripIgnoredCharacters.ts │ ├── typeComparators.ts │ ├── typeFromAST.ts │ ├── typedQueryDocumentNode.ts │ ├── valueFromAST.ts │ └── valueFromASTUntyped.ts ├── validation │ ├── README.md │ ├── ValidationContext.ts │ ├── __tests__ │ │ ├── ExecutableDefinitionsRule-test.ts │ │ ├── FieldsOnCorrectTypeRule-test.ts │ │ ├── FragmentsOnCompositeTypesRule-test.ts │ │ ├── KnownArgumentNamesRule-test.ts │ │ ├── KnownDirectivesRule-test.ts │ │ ├── KnownFragmentNamesRule-test.ts │ │ ├── KnownTypeNamesRule-test.ts │ │ ├── LoneAnonymousOperationRule-test.ts │ │ ├── LoneSchemaDefinitionRule-test.ts │ │ ├── MaxIntrospectionDepthRule-test.ts │ │ ├── NoDeprecatedCustomRule-test.ts │ │ ├── NoFragmentCyclesRule-test.ts │ │ ├── NoSchemaIntrospectionCustomRule-test.ts │ │ ├── NoUndefinedVariablesRule-test.ts │ │ ├── NoUnusedFragmentsRule-test.ts │ │ ├── NoUnusedVariablesRule-test.ts │ │ ├── OverlappingFieldsCanBeMergedRule-test.ts │ │ ├── PossibleFragmentSpreadsRule-test.ts │ │ ├── PossibleTypeExtensionsRule-test.ts │ │ ├── ProvidedRequiredArgumentsRule-test.ts │ │ ├── ScalarLeafsRule-test.ts │ │ ├── SingleFieldSubscriptionsRule-test.ts │ │ ├── UniqueArgumentDefinitionNamesRule-test.ts │ │ ├── UniqueArgumentNamesRule-test.ts │ │ ├── UniqueDirectiveNamesRule-test.ts │ │ ├── UniqueDirectivesPerLocationRule-test.ts │ │ ├── UniqueEnumValueNamesRule-test.ts │ │ ├── UniqueFieldDefinitionNamesRule-test.ts │ │ ├── UniqueFragmentNamesRule-test.ts │ │ ├── UniqueInputFieldNamesRule-test.ts │ │ ├── UniqueOperationNamesRule-test.ts │ │ ├── UniqueOperationTypesRule-test.ts │ │ ├── UniqueTypeNamesRule-test.ts │ │ ├── UniqueVariableNamesRule-test.ts │ │ ├── ValidationContext-test.ts │ │ ├── ValuesOfCorrectTypeRule-test.ts │ │ ├── VariablesAreInputTypesRule-test.ts │ │ ├── VariablesInAllowedPositionRule-test.ts │ │ ├── harness.ts │ │ └── validation-test.ts │ ├── index.ts │ ├── rules │ │ ├── ExecutableDefinitionsRule.ts │ │ ├── FieldsOnCorrectTypeRule.ts │ │ ├── FragmentsOnCompositeTypesRule.ts │ │ ├── KnownArgumentNamesRule.ts │ │ ├── KnownDirectivesRule.ts │ │ ├── KnownFragmentNamesRule.ts │ │ ├── KnownTypeNamesRule.ts │ │ ├── LoneAnonymousOperationRule.ts │ │ ├── LoneSchemaDefinitionRule.ts │ │ ├── MaxIntrospectionDepthRule.ts │ │ ├── NoFragmentCyclesRule.ts │ │ ├── NoUndefinedVariablesRule.ts │ │ ├── NoUnusedFragmentsRule.ts │ │ ├── NoUnusedVariablesRule.ts │ │ ├── OverlappingFieldsCanBeMergedRule.ts │ │ ├── PossibleFragmentSpreadsRule.ts │ │ ├── PossibleTypeExtensionsRule.ts │ │ ├── ProvidedRequiredArgumentsRule.ts │ │ ├── ScalarLeafsRule.ts │ │ ├── SingleFieldSubscriptionsRule.ts │ │ ├── UniqueArgumentDefinitionNamesRule.ts │ │ ├── UniqueArgumentNamesRule.ts │ │ ├── UniqueDirectiveNamesRule.ts │ │ ├── UniqueDirectivesPerLocationRule.ts │ │ ├── UniqueEnumValueNamesRule.ts │ │ ├── UniqueFieldDefinitionNamesRule.ts │ │ ├── UniqueFragmentNamesRule.ts │ │ ├── UniqueInputFieldNamesRule.ts │ │ ├── UniqueOperationNamesRule.ts │ │ ├── UniqueOperationTypesRule.ts │ │ ├── UniqueTypeNamesRule.ts │ │ ├── UniqueVariableNamesRule.ts │ │ ├── ValuesOfCorrectTypeRule.ts │ │ ├── VariablesAreInputTypesRule.ts │ │ ├── VariablesInAllowedPositionRule.ts │ │ └── custom │ │ │ ├── NoDeprecatedCustomRule.ts │ │ │ └── NoSchemaIntrospectionCustomRule.ts │ ├── specifiedRules.ts │ └── validate.ts └── version.ts ├── tsconfig.json └── website ├── .eslintignore ├── css └── globals.css ├── icons ├── discord.svg ├── github.svg ├── graphql-wordmark.svg ├── graphql.svg ├── index.ts ├── stackoverflow.svg └── twitter.svg ├── next-env.d.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── _meta.ts ├── api-v16 │ ├── _meta.ts │ ├── error.mdx │ ├── execution.mdx │ ├── graphql-http.mdx │ ├── graphql.mdx │ ├── language.mdx │ ├── type.mdx │ ├── utilities.mdx │ └── validation.mdx ├── docs │ ├── _meta.ts │ ├── abstract-types.mdx │ ├── advanced-custom-scalars.mdx │ ├── authentication-and-express-middleware.mdx │ ├── authorization-strategies.mdx │ ├── basic-types.mdx │ ├── caching-strategies.mdx │ ├── constructing-types.mdx │ ├── cursor-based-pagination.mdx │ ├── custom-scalars.mdx │ ├── defer-stream.mdx │ ├── getting-started.mdx │ ├── going-to-production.mdx │ ├── graphql-clients.mdx │ ├── graphql-errors.mdx │ ├── index.mdx │ ├── mutations-and-input-types.mdx │ ├── n1-dataloader.mdx │ ├── nullability.mdx │ ├── object-types.mdx │ ├── oneof-input-objects.mdx │ ├── operation-complexity-controls.mdx │ ├── passing-arguments.mdx │ ├── resolver-anatomy.mdx │ ├── running-an-express-graphql-server.mdx │ ├── scaling-graphql.mdx │ ├── subscriptions.mdx │ ├── type-generation.mdx │ └── using-directives.mdx └── upgrade-guides │ └── v16-v17.mdx ├── postcss.config.js ├── tailwind.config.js ├── theme.config.tsx ├── tsconfig.json └── vercel.json /.babelrc-deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-syntax-typescript", 4 | ["./resources/add-extension-to-import-paths", { "extension": "ts" }], 5 | "./resources/inline-invariant" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.babelrc-npm.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-transform-typescript", 4 | "./resources/inline-invariant" 5 | ], 6 | "env": { 7 | "cjs": { 8 | "presets": [ 9 | [ 10 | "@babel/preset-env", 11 | { "modules": "commonjs", "targets": { "node": "12" } } 12 | ] 13 | ], 14 | "plugins": [ 15 | ["./resources/add-extension-to-import-paths", { "extension": "js" }] 16 | ] 17 | }, 18 | "mjs": { 19 | "presets": [ 20 | ["@babel/preset-env", { "modules": false, "targets": { "node": "12" } }] 21 | ], 22 | "plugins": [ 23 | ["./resources/add-extension-to-import-paths", { "extension": "mjs" }] 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-transform-typescript"], 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { "bugfixes": true, "targets": { "node": "current" } } 7 | ] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.c8rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "include": ["src/"], 4 | "exclude": [ 5 | "src/**/index.ts", 6 | "src/**/*-fuzz.ts", 7 | "src/jsutils/Maybe.ts", 8 | "src/jsutils/ObjMap.ts", 9 | "src/jsutils/PromiseOrValue.ts", 10 | "src/utilities/assertValidName.ts", 11 | "src/utilities/typedQueryDocumentNode.ts", 12 | "src/**/__tests__/**/*.ts" 13 | ], 14 | "clean": true, 15 | "temp-directory": "coverage", 16 | "report-dir": "coverage", 17 | "skip-full": true, 18 | "reporter": ["json", "html", "text"], 19 | "check-coverage": true, 20 | "branches": 100, 21 | "lines": 100, 22 | "functions": 100, 23 | "statements": 100 24 | } 25 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Copied from '.gitignore', please keep it in sync. 2 | /.eslintcache 3 | /node_modules 4 | /coverage 5 | /npmDist 6 | /denoDist 7 | /websiteDist 8 | /website 9 | 10 | # Ignore TS files inside integration test 11 | /integrationTests/ts/*.ts 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @graphql/graphql-js-reviewers 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Questions regarding how to use GraphQL 2 | 3 | We want to keep signal strong in the GitHub issue tracker – to make sure that it remains the best place to track bugs and features that affect development. 4 | 5 | If you have a question on how to use GraphQL, please [post it to Stack Overflow](https://stackoverflow.com/questions/ask?tags=graphql) with the tag [#graphql](https://stackoverflow.com/questions/tagged/graphql). 6 | 7 | Please do not post general questions directly as GitHub issues. They may sit for weeks unanswered, or may be spontaneously closed without answer. 8 | 9 | # Reporting issues with GraphQL.js 10 | 11 | Before filing a new issue, make sure an issue for your problem doesn't already exist. 12 | 13 | The best way to get a bug fixed is to provide a _pull request_ with a simplified failing test case (or better yet, include a fix). 14 | 15 | # Feature requests 16 | 17 | GraphQL.js is a reference implementation of the [GraphQL specification](https://github.com/graphql/graphql-spec). To discuss new features which are not GraphQL.js specific and fundamentally change the way GraphQL works, open an issue against the specification. 18 | 19 | # Security bugs 20 | 21 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. With that in mind, please do not file public issues; go through the process outlined on that page. 22 | -------------------------------------------------------------------------------- /.github/workflows/deploy-artifact-as-branch.yml: -------------------------------------------------------------------------------- 1 | name: Deploy specified artifact as a branch 2 | on: 3 | workflow_call: 4 | inputs: 5 | environment: 6 | description: Environment to publish under 7 | required: true 8 | type: string 9 | artifact_name: 10 | description: Artifact name 11 | required: true 12 | type: string 13 | target_branch: 14 | description: Target branch 15 | required: true 16 | type: string 17 | commit_message: 18 | description: Commit message 19 | required: true 20 | type: string 21 | permissions: {} 22 | jobs: 23 | deploy-artifact-as-branch: 24 | environment: 25 | name: ${{ inputs.environment }} 26 | url: ${{ github.server_url }}/${{ github.repository }}/tree/${{ inputs.target_branch }} 27 | runs-on: ubuntu-latest 28 | permissions: 29 | contents: write # for actions/checkout and to push branch 30 | steps: 31 | - name: Checkout `${{ inputs.target_branch }}` branch 32 | uses: actions/checkout@v4 33 | with: 34 | ref: ${{ inputs.target_branch }} 35 | 36 | - name: Remove existing files first 37 | run: git rm -r . 38 | 39 | - uses: actions/download-artifact@v4 40 | with: 41 | name: ${{ inputs.artifact_name }} 42 | 43 | - name: Publish target branch 44 | run: | 45 | git add -A 46 | if git diff --staged --quiet; then 47 | echo 'Nothing to publish' 48 | else 49 | git config user.name 'GitHub Action Script' 50 | git config user.email 'please@open.issue' 51 | 52 | git commit -a -m "$COMMIT_MESSAGE" 53 | git push 54 | echo 'Pushed' 55 | fi 56 | env: 57 | TARGET_BRANCH: ${{ inputs.target_branch }} 58 | COMMIT_MESSAGE: ${{ inputs.commit_message }} 59 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: PullRequest 2 | on: pull_request 3 | permissions: {} 4 | jobs: 5 | ci: 6 | permissions: 7 | contents: read # for actions/checkout 8 | security-events: write # for codeql-action 9 | uses: ./.github/workflows/ci.yml 10 | secrets: 11 | codecov_token: ${{ secrets.CODECOV_TOKEN }} 12 | 13 | dependency-review: 14 | name: Security check of added dependencies 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read # for actions/checkout 18 | steps: 19 | - name: Checkout repo 20 | uses: actions/checkout@v4 21 | with: 22 | persist-credentials: false 23 | 24 | - name: Dependency review 25 | uses: actions/dependency-review-action@v2 26 | 27 | diff-npm-package: 28 | name: Diff content of NPM package 29 | runs-on: ubuntu-latest 30 | permissions: 31 | contents: read # for actions/checkout 32 | steps: 33 | - name: Checkout repo 34 | uses: actions/checkout@v4 35 | with: 36 | persist-credentials: false 37 | 38 | - name: Deepen cloned repo 39 | env: 40 | BASE_SHA: ${{ github.event.pull_request.base.sha }} 41 | run: 'git fetch --depth=1 origin "$BASE_SHA:refs/tags/BASE"' 42 | 43 | - name: Setup Node.js 44 | uses: actions/setup-node@v4 45 | with: 46 | cache: npm 47 | node-version-file: '.node-version' 48 | 49 | - name: Install Dependencies 50 | run: npm ci --ignore-scripts 51 | 52 | - name: Generate report 53 | run: 'node resources/diff-npm-package.js BASE HEAD' 54 | 55 | - name: Upload generated report 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: npm-dist-diff.html 59 | path: ./npm-dist-diff.html 60 | if-no-files-found: ignore 61 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_opened.yml: -------------------------------------------------------------------------------- 1 | name: PullRequestOpened 2 | on: 3 | pull_request: 4 | types: [opened] 5 | permissions: {} 6 | jobs: 7 | save-github-event: 8 | name: "Save `github.event` as an artifact to use in subsequent 'workflow_run' actions" 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Upload event.json 12 | uses: actions/upload-artifact@v4 13 | with: 14 | name: event.json 15 | path: ${{ github.event_path }} 16 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Push 2 | on: push 3 | permissions: {} 4 | jobs: 5 | ci: 6 | permissions: 7 | contents: read # for actions/checkout 8 | security-events: write 9 | uses: ./.github/workflows/ci.yml 10 | secrets: 11 | codecov_token: ${{ secrets.CODECOV_TOKEN }} 12 | deploy-to-npm-branch: 13 | name: Deploy to `npm` branch 14 | needs: ci 15 | if: github.ref == 'refs/heads/main' 16 | permissions: 17 | contents: write # for actions/checkout and to push branch 18 | uses: ./.github/workflows/deploy-artifact-as-branch.yml 19 | with: 20 | environment: npm-branch 21 | artifact_name: npmDist 22 | target_branch: npm 23 | commit_message: "Deploy ${{github.event.workflow_run.head_sha}} to 'npm' branch" 24 | 25 | deploy-to-deno-branch: 26 | name: Deploy to `deno` branch 27 | needs: ci 28 | if: github.ref == 'refs/heads/main' 29 | permissions: 30 | contents: write # for actions/checkout and to push branch 31 | uses: ./.github/workflows/deploy-artifact-as-branch.yml 32 | with: 33 | environment: deno-branch 34 | artifact_name: denoDist 35 | target_branch: deno 36 | commit_message: "Deploy ${{github.event.workflow_run.head_sha}} to 'deno' branch" 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore only ignores files specific to this repository. 2 | # If you see other files generated by your OS or tools you use, consider 3 | # creating a global .gitignore file. 4 | # 5 | # https://help.github.com/articles/ignoring-files/#create-a-global-gitignore 6 | # https://www.gitignore.io/ 7 | 8 | /diff-npm-package.html 9 | /.eslintcache 10 | /.cspellcache 11 | node_modules 12 | /coverage 13 | /npmDist 14 | /denoDist 15 | /websiteDist 16 | /website/.next 17 | /website/out 18 | -------------------------------------------------------------------------------- /.mocharc.yml: -------------------------------------------------------------------------------- 1 | fail-zero: true 2 | throw-deprecation: true 3 | check-leaks: true 4 | require: 5 | - 'resources/ts-register.js' 6 | extension: 7 | - 'ts' 8 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v17 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Copied from '.gitignore', please keep it in sync. 2 | /diff-npm-package.html 3 | /.eslintcache 4 | /node_modules 5 | /coverage 6 | /npmDist 7 | /denoDist 8 | /websiteDist 9 | /website/out 10 | /website/**/*.mdx 11 | .next 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) GraphQL Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/graphql-conf-2025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graphql/graphql-js/9874ed6e92429e74f9494777143ce215c501c7c5/assets/graphql-conf-2025.png -------------------------------------------------------------------------------- /benchmark/buildASTSchema-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { buildASTSchema } = require('graphql/utilities/buildASTSchema.js'); 5 | 6 | const { bigSchemaSDL } = require('./fixtures.js'); 7 | 8 | const schemaAST = parse(bigSchemaSDL); 9 | 10 | module.exports = { 11 | name: 'Build Schema from AST', 12 | count: 10, 13 | measure() { 14 | buildASTSchema(schemaAST, { assumeValid: true }); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /benchmark/buildClientSchema-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { buildClientSchema } = require('graphql/utilities/buildClientSchema.js'); 4 | 5 | const { bigSchemaIntrospectionResult } = require('./fixtures.js'); 6 | 7 | module.exports = { 8 | name: 'Build Schema from Introspection', 9 | count: 10, 10 | measure() { 11 | buildClientSchema(bigSchemaIntrospectionResult.data, { assumeValid: true }); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /benchmark/fixtures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | exports.bigSchemaSDL = fs.readFileSync( 7 | path.join(__dirname, 'github-schema.graphql'), 8 | 'utf8', 9 | ); 10 | 11 | exports.bigDocumentSDL = fs.readFileSync( 12 | path.join(__dirname, 'kitchen-sink.graphql'), 13 | 'utf8', 14 | ); 15 | 16 | exports.bigSchemaIntrospectionResult = JSON.parse( 17 | fs.readFileSync(path.join(__dirname, 'github-schema.json'), 'utf8'), 18 | ); 19 | -------------------------------------------------------------------------------- /benchmark/introspectionFromSchema-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { executeSync } = require('graphql/execution/execute.js'); 5 | const { buildSchema } = require('graphql/utilities/buildASTSchema.js'); 6 | const { 7 | getIntrospectionQuery, 8 | } = require('graphql/utilities/getIntrospectionQuery.js'); 9 | 10 | const { bigSchemaSDL } = require('./fixtures.js'); 11 | 12 | const schema = buildSchema(bigSchemaSDL, { assumeValid: true }); 13 | const document = parse(getIntrospectionQuery()); 14 | 15 | module.exports = { 16 | name: 'Execute Introspection Query', 17 | count: 10, 18 | measure() { 19 | executeSync({ schema, document }); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /benchmark/kitchen-sink.graphql: -------------------------------------------------------------------------------- 1 | query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery { 2 | whoever123is: node(id: [123, 456]) { 3 | id 4 | ... on User @onInlineFragment { 5 | field2 { 6 | id 7 | alias: field1(first: 10, after: $foo) @include(if: $foo) { 8 | id 9 | ...frag @onFragmentSpread 10 | } 11 | } 12 | } 13 | ... @skip(unless: $foo) { 14 | id 15 | } 16 | ... { 17 | id 18 | } 19 | } 20 | } 21 | 22 | mutation likeStory @onMutation { 23 | like(story: 123) @onField { 24 | story { 25 | id @onField 26 | } 27 | } 28 | } 29 | 30 | subscription StoryLikeSubscription( 31 | $input: StoryLikeSubscribeInput @onVariableDefinition 32 | ) @onSubscription { 33 | storyLikeSubscribe(input: $input) { 34 | story { 35 | likers { 36 | count 37 | } 38 | likeSentence { 39 | text 40 | } 41 | } 42 | } 43 | } 44 | 45 | fragment frag on Friend @onFragmentDefinition { 46 | foo( 47 | size: $size 48 | bar: $b 49 | obj: { 50 | key: "value" 51 | block: """ 52 | block string uses \""" 53 | """ 54 | } 55 | ) 56 | } 57 | 58 | { 59 | unnamed(truthy: true, falsy: false, nullish: null) 60 | query 61 | } 62 | 63 | query { 64 | __typename 65 | } 66 | -------------------------------------------------------------------------------- /benchmark/parser-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { 5 | getIntrospectionQuery, 6 | } = require('graphql/utilities/getIntrospectionQuery.js'); 7 | 8 | const introspectionQuery = getIntrospectionQuery(); 9 | 10 | module.exports = { 11 | name: 'Parse introspection query', 12 | count: 1000, 13 | measure() { 14 | parse(introspectionQuery); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /benchmark/printer-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { print } = require('graphql/language/printer.js'); 5 | 6 | const { bigDocumentSDL } = require('./fixtures.js'); 7 | 8 | const document = parse(bigDocumentSDL); 9 | 10 | module.exports = { 11 | name: 'Print kitchen sink document', 12 | count: 1000, 13 | measure() { 14 | print(document); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /benchmark/repeated-fields-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { graphqlSync } = require('graphql/graphql.js'); 4 | const { buildSchema } = require('graphql/utilities/buildASTSchema.js'); 5 | 6 | const schema = buildSchema('type Query { hello: String! }'); 7 | const source = `{ ${'hello '.repeat(250)}}`; 8 | 9 | module.exports = { 10 | name: 'Many repeated fields', 11 | count: 5, 12 | measure() { 13 | graphqlSync({ schema, source }); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /benchmark/validateGQL-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { validate } = require('graphql/validation/validate.js'); 5 | const { buildSchema } = require('graphql/utilities/buildASTSchema.js'); 6 | const { 7 | getIntrospectionQuery, 8 | } = require('graphql/utilities/getIntrospectionQuery.js'); 9 | 10 | const { bigSchemaSDL } = require('./fixtures.js'); 11 | 12 | const schema = buildSchema(bigSchemaSDL, { assumeValid: true }); 13 | const queryAST = parse(getIntrospectionQuery()); 14 | 15 | module.exports = { 16 | name: 'Validate Introspection Query', 17 | count: 50, 18 | measure() { 19 | validate(schema, queryAST); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /benchmark/validateInvalidGQL-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { validate } = require('graphql/validation/validate.js'); 5 | const { buildSchema } = require('graphql/utilities/buildASTSchema.js'); 6 | 7 | const { bigSchemaSDL } = require('./fixtures.js'); 8 | 9 | const schema = buildSchema(bigSchemaSDL, { assumeValid: true }); 10 | const queryAST = parse(` 11 | { 12 | unknownField 13 | ... on unknownType { 14 | anotherUnknownField 15 | ...unknownFragment 16 | } 17 | } 18 | 19 | fragment TestFragment on anotherUnknownType { 20 | yetAnotherUnknownField 21 | } 22 | `); 23 | 24 | module.exports = { 25 | name: 'Validate Invalid Query', 26 | count: 50, 27 | measure() { 28 | validate(schema, queryAST); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /benchmark/validateSDL-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { validateSDL } = require('graphql/validation/validate.js'); 5 | 6 | const { bigSchemaSDL } = require('./fixtures.js'); 7 | 8 | const sdlAST = parse(bigSchemaSDL); 9 | 10 | module.exports = { 11 | name: 'Validate SDL Document', 12 | count: 10, 13 | measure() { 14 | validateSDL(sdlAST); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /benchmark/visit-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { visit } = require('graphql/language/visitor.js'); 5 | 6 | const { bigSchemaSDL } = require('./fixtures.js'); 7 | 8 | const documentAST = parse(bigSchemaSDL); 9 | 10 | const visitor = { 11 | enter() { 12 | /* do nothing */ 13 | }, 14 | leave() { 15 | /* do nothing */ 16 | }, 17 | }; 18 | 19 | module.exports = { 20 | name: 'Visit all AST nodes', 21 | count: 10, 22 | measure() { 23 | visit(documentAST, visitor); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /benchmark/visitInParallel-benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { parse } = require('graphql/language/parser.js'); 4 | const { visit, visitInParallel } = require('graphql/language/visitor.js'); 5 | 6 | const { bigSchemaSDL } = require('./fixtures.js'); 7 | 8 | const documentAST = parse(bigSchemaSDL); 9 | 10 | const visitors = new Array(50).fill({ 11 | enter() { 12 | /* do nothing */ 13 | }, 14 | leave() { 15 | /* do nothing */ 16 | }, 17 | }); 18 | 19 | module.exports = { 20 | name: 'Visit all AST nodes in parallel', 21 | count: 10, 22 | measure() { 23 | visit(documentAST, visitInParallel(visitors)); 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: true 4 | 5 | parsers: 6 | javascript: 7 | enable_partials: true 8 | 9 | comment: false 10 | coverage: 11 | status: 12 | project: 13 | default: 14 | target: auto 15 | -------------------------------------------------------------------------------- /integrationTests/README.md: -------------------------------------------------------------------------------- 1 | # TBD 2 | -------------------------------------------------------------------------------- /integrationTests/integration-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const os = require('os'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const childProcess = require('child_process'); 7 | 8 | const { describe, it } = require('mocha'); 9 | 10 | function exec(command, options = {}) { 11 | const output = childProcess.execSync(command, { 12 | encoding: 'utf-8', 13 | ...options, 14 | }); 15 | return output && output.trimEnd(); 16 | } 17 | 18 | describe('Integration Tests', () => { 19 | const tmpDir = path.join(os.tmpdir(), 'graphql-js-integrationTmp'); 20 | fs.rmSync(tmpDir, { recursive: true, force: true }); 21 | fs.mkdirSync(tmpDir); 22 | 23 | const distDir = path.resolve('./npmDist'); 24 | const archiveName = exec(`npm --quiet pack ${distDir}`, { cwd: tmpDir }); 25 | fs.renameSync( 26 | path.join(tmpDir, archiveName), 27 | path.join(tmpDir, 'graphql.tgz'), 28 | ); 29 | 30 | function testOnNodeProject(projectName) { 31 | const projectPath = path.join(__dirname, projectName); 32 | 33 | const packageJSONPath = path.join(projectPath, 'package.json'); 34 | const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, 'utf-8')); 35 | 36 | it(packageJSON.description, () => { 37 | exec(`cp -R ${projectPath} ${tmpDir}`); 38 | 39 | const cwd = path.join(tmpDir, projectName); 40 | // TODO: figure out a way to run it with --ignore-scripts 41 | exec('npm --quiet install', { cwd, stdio: 'inherit' }); 42 | exec('npm --quiet test', { cwd, stdio: 'inherit' }); 43 | }).timeout(120000); 44 | } 45 | 46 | testOnNodeProject('ts'); 47 | testOnNodeProject('node'); 48 | testOnNodeProject('webpack'); 49 | }); 50 | -------------------------------------------------------------------------------- /integrationTests/node/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const { readFileSync } = require('fs'); 5 | 6 | const { version, graphqlSync } = require('graphql'); 7 | const { buildSchema } = require('graphql/utilities'); 8 | 9 | assert.deepStrictEqual( 10 | version, 11 | JSON.parse(readFileSync('./node_modules/graphql/package.json')).version, 12 | ); 13 | 14 | const schema = buildSchema('type Query { hello: String }'); 15 | 16 | const result = graphqlSync({ 17 | schema, 18 | source: '{ hello }', 19 | rootValue: { hello: 'world' }, 20 | }); 21 | 22 | assert.deepStrictEqual(result, { 23 | data: { 24 | __proto__: null, 25 | hello: 'world', 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /integrationTests/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "description": "graphql-js should work on all supported node versions", 4 | "scripts": { 5 | "test": "node test.js" 6 | }, 7 | "dependencies": { 8 | "graphql": "file:../graphql.tgz" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /integrationTests/node/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const childProcess = require('child_process'); 4 | 5 | const graphqlPackageJSON = require('graphql/package.json'); 6 | 7 | const nodeVersions = graphqlPackageJSON.engines.node 8 | .split(' || ') 9 | .map((version) => version.replace(/^(\^|>=)/, '')) 10 | .sort((a, b) => b.localeCompare(a)); 11 | 12 | for (const version of nodeVersions) { 13 | console.log(`Testing on node@${version} ...`); 14 | 15 | childProcess.execSync( 16 | `docker run --rm --volume "$PWD":/usr/src/app -w /usr/src/app node:${version}-slim node ./index.js`, 17 | { stdio: 'inherit' }, 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /integrationTests/ts/TypedQueryDocumentNode-test.ts: -------------------------------------------------------------------------------- 1 | import type { ExecutionResult } from 'graphql/execution'; 2 | import type { TypedQueryDocumentNode } from 'graphql/utilities'; 3 | 4 | import { parse } from 'graphql/language'; 5 | import { execute } from 'graphql/execution'; 6 | import { buildSchema } from 'graphql/utilities'; 7 | 8 | const schema = buildSchema(` 9 | type Query { 10 | test: String 11 | } 12 | `); 13 | 14 | // Tests for TS specific TypedQueryDocumentNode type 15 | const queryDocument = parse('{ test }'); 16 | 17 | type ResponseData = { test: string }; 18 | const typedQueryDocument = queryDocument as TypedQueryDocumentNode< 19 | ResponseData, 20 | {} 21 | >; 22 | 23 | // Supports conversion to DocumentNode 24 | execute({ schema, document: typedQueryDocument }); 25 | 26 | function wrappedExecute(document: TypedQueryDocumentNode) { 27 | return execute({ schema, document }) as ExecutionResult; 28 | } 29 | 30 | const response = wrappedExecute(typedQueryDocument); 31 | const responseData: ResponseData | undefined | null = response.data; 32 | 33 | declare function runQueryA( 34 | q: TypedQueryDocumentNode<{ output: string }, { input: string | null }>, 35 | ): void; 36 | 37 | // valid 38 | declare const optionalInputRequiredOutput: TypedQueryDocumentNode< 39 | { output: string }, 40 | { input: string | null } 41 | >; 42 | runQueryA(optionalInputRequiredOutput); 43 | 44 | declare function runQueryB( 45 | q: TypedQueryDocumentNode<{ output: string | null }, { input: string }>, 46 | ): void; 47 | 48 | // still valid: We still accept {output: string} as a valid result. 49 | // We're now passing in {input: string} which is still assignable to {input: string | null} 50 | runQueryB(optionalInputRequiredOutput); 51 | 52 | // valid: we now accept {output: null} as a valid Result 53 | declare const optionalInputOptionalOutput: TypedQueryDocumentNode< 54 | { output: string | null }, 55 | { input: string | null } 56 | >; 57 | runQueryB(optionalInputOptionalOutput); 58 | 59 | // valid: we now only pass {input: string} to the query 60 | declare const requiredInputRequiredOutput: TypedQueryDocumentNode< 61 | { output: string }, 62 | { input: string } 63 | >; 64 | runQueryB(requiredInputRequiredOutput); 65 | 66 | // valid: we now accept {output: null} as a valid Result AND 67 | // we now only pass {input: string} to the query 68 | declare const requiredInputOptionalOutput: TypedQueryDocumentNode< 69 | { output: null }, 70 | { input: string } 71 | >; 72 | runQueryB(requiredInputOptionalOutput); 73 | -------------------------------------------------------------------------------- /integrationTests/ts/basic-test.ts: -------------------------------------------------------------------------------- 1 | import type { ExecutionResult } from 'graphql/execution'; 2 | 3 | import { graphqlSync } from 'graphql'; 4 | import { GraphQLString, GraphQLSchema, GraphQLObjectType } from 'graphql/type'; 5 | 6 | const queryType: GraphQLObjectType = new GraphQLObjectType({ 7 | name: 'Query', 8 | fields: () => ({ 9 | sayHi: { 10 | type: GraphQLString, 11 | args: { 12 | who: { 13 | type: GraphQLString, 14 | defaultValue: 'World', 15 | }, 16 | }, 17 | resolve(_root, args: { who: string }) { 18 | return 'Hello ' + args.who; 19 | }, 20 | }, 21 | }), 22 | }); 23 | 24 | const schema: GraphQLSchema = new GraphQLSchema({ query: queryType }); 25 | 26 | const result: ExecutionResult = graphqlSync({ 27 | schema, 28 | source: ` 29 | query helloWho($who: String){ 30 | test(who: $who) 31 | } 32 | `, 33 | variableValues: { who: 'Dolly' }, 34 | }); 35 | -------------------------------------------------------------------------------- /integrationTests/ts/extensions-test.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from 'graphql/error'; 2 | import { GraphQLString, GraphQLObjectType } from 'graphql/type'; 3 | 4 | interface SomeExtension { 5 | meaningOfLife: 42; 6 | } 7 | 8 | declare module 'graphql' { 9 | interface GraphQLObjectTypeExtensions<_TSource, _TContext> { 10 | someObjectExtension?: SomeExtension; 11 | } 12 | 13 | interface GraphQLFieldExtensions<_TSource, _TContext, _TArgs> { 14 | someFieldExtension?: SomeExtension; 15 | } 16 | 17 | interface GraphQLArgumentExtensions { 18 | someArgumentExtension?: SomeExtension; 19 | } 20 | } 21 | 22 | const queryType: GraphQLObjectType = new GraphQLObjectType({ 23 | name: 'Query', 24 | fields: () => ({ 25 | sayHi: { 26 | type: GraphQLString, 27 | args: { 28 | who: { 29 | type: GraphQLString, 30 | extensions: { 31 | someArgumentExtension: { meaningOfLife: 42 }, 32 | }, 33 | }, 34 | }, 35 | resolve: (_root, args) => 'Hello ' + (args.who || 'World'), 36 | extensions: { 37 | someFieldExtension: { meaningOfLife: 42 }, 38 | }, 39 | }, 40 | }), 41 | extensions: { 42 | someObjectExtension: { meaningOfLife: 42 }, 43 | }, 44 | }); 45 | 46 | function checkExtensionTypes(_test: SomeExtension | null | undefined) {} 47 | 48 | checkExtensionTypes(queryType.extensions.someObjectExtension); 49 | 50 | const sayHiField = queryType.getFields().sayHi; 51 | checkExtensionTypes(sayHiField.extensions.someFieldExtension); 52 | 53 | checkExtensionTypes(sayHiField.args[0].extensions.someArgumentExtension); 54 | 55 | declare module 'graphql' { 56 | export interface GraphQLErrorExtensions { 57 | someErrorExtension?: SomeExtension; 58 | } 59 | } 60 | 61 | const error = new GraphQLError('foo'); 62 | checkExtensionTypes(error.extensions.someErrorExtension); 63 | -------------------------------------------------------------------------------- /integrationTests/ts/internalImports-test.ts: -------------------------------------------------------------------------------- 1 | import type { NameNode } from 'graphql/language'; 2 | 3 | // Parser class is internal API so so any changes to it are never considered breaking changes. 4 | // We just want to test that we are able to import it. 5 | import { Parser } from 'graphql/language/parser'; 6 | 7 | const parser = new Parser('foo'); 8 | const ast: NameNode = parser.parseName(); 9 | -------------------------------------------------------------------------------- /integrationTests/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "description": "graphql-js should compile with all supported TS versions", 4 | "scripts": { 5 | "test": "node test.js" 6 | }, 7 | "dependencies": { 8 | "graphql": "file:../graphql.tgz", 9 | "typescript-4.1": "npm:typescript@4.1.x", 10 | "typescript-4.2": "npm:typescript@4.2.x", 11 | "typescript-4.3": "npm:typescript@4.3.x", 12 | "typescript-4.4": "npm:typescript@4.4.x", 13 | "typescript-4.5": "npm:typescript@4.5.x", 14 | "typescript-4.6": "npm:typescript@4.6.x" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /integrationTests/ts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const childProcess = require('child_process'); 5 | 6 | const { dependencies } = require('./package.json'); 7 | 8 | const tsVersions = Object.keys(dependencies) 9 | .filter((pkg) => pkg.startsWith('typescript-')) 10 | .sort((a, b) => b.localeCompare(a)); 11 | 12 | for (const version of tsVersions) { 13 | console.log(`Testing on ${version} ...`); 14 | childProcess.execSync(tscPath(version), { stdio: 'inherit' }); 15 | } 16 | 17 | function tscPath(version) { 18 | return path.join(__dirname, 'node_modules', version, 'bin/tsc'); 19 | } 20 | -------------------------------------------------------------------------------- /integrationTests/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": ["es2019", "es2020.promise", "es2020.bigint", "es2020.string"], 5 | "strict": true, 6 | "noEmit": true, 7 | "types": [] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /integrationTests/webpack/entry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { buildSchema, graphqlSync } = require('graphql'); 4 | 5 | const schema = buildSchema('type Query { hello: String }'); 6 | 7 | const result = graphqlSync({ 8 | schema, 9 | source: '{ hello }', 10 | rootValue: { hello: 'world' }, 11 | }); 12 | 13 | module.exports = { result }; 14 | -------------------------------------------------------------------------------- /integrationTests/webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "description": "graphql-js should be compatible with Webpack", 4 | "scripts": { 5 | "test": "webpack && node test.js" 6 | }, 7 | "dependencies": { 8 | "graphql": "file:../graphql.tgz", 9 | "webpack": "5.x.x", 10 | "webpack-cli": "4.x.x" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /integrationTests/webpack/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | // eslint-disable-next-line node/no-missing-require 6 | const { result } = require('./dist/main.js'); 7 | 8 | assert.deepStrictEqual(result, { 9 | data: { 10 | __proto__: null, 11 | hello: 'world', 12 | }, 13 | }); 14 | console.log('Test script: Got correct result from Webpack bundle!'); 15 | -------------------------------------------------------------------------------- /integrationTests/webpack/webpack.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mode": "production", 3 | "entry": "./entry.js", 4 | "output": { 5 | "libraryTarget": "commonjs2" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /resources/add-extension-to-import-paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Adds extension to all paths imported inside MJS files 5 | * 6 | * Transforms: 7 | * 8 | * import { foo } from './bar'; 9 | * export { foo } from './bar'; 10 | * 11 | * to: 12 | * 13 | * import { foo } from './bar.mjs'; 14 | * export { foo } from './bar.mjs'; 15 | * 16 | */ 17 | module.exports = function addExtensionToImportPaths(context, { extension }) { 18 | const { types } = context; 19 | 20 | return { 21 | visitor: { 22 | ImportDeclaration: replaceImportPath, 23 | ExportNamedDeclaration: replaceImportPath, 24 | }, 25 | }; 26 | 27 | function replaceImportPath(path) { 28 | // bail if the declaration doesn't have a source, e.g. "export { foo };" 29 | if (!path.node.source) { 30 | return; 31 | } 32 | 33 | const source = path.node.source.value; 34 | if (source.startsWith('./') || source.startsWith('../')) { 35 | const newSourceNode = types.stringLiteral(source + '.' + extension); 36 | path.get('source').replaceWith(newSourceNode); 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /resources/build-deno.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const babel = require('@babel/core'); 7 | 8 | const { 9 | writeGeneratedFile, 10 | readdirRecursive, 11 | showDirStats, 12 | } = require('./utils.js'); 13 | 14 | if (require.main === module) { 15 | fs.rmSync('./denoDist', { recursive: true, force: true }); 16 | fs.mkdirSync('./denoDist'); 17 | 18 | const srcFiles = readdirRecursive('./src', { ignoreDir: /^__.*__$/ }); 19 | for (const filepath of srcFiles) { 20 | const srcPath = path.join('./src', filepath); 21 | const destPath = path.join('./denoDist', filepath); 22 | 23 | fs.mkdirSync(path.dirname(destPath), { recursive: true }); 24 | if (filepath.endsWith('.ts')) { 25 | const options = { babelrc: false, configFile: './.babelrc-deno.json' }; 26 | const output = babel.transformFileSync(srcPath, options).code + '\n'; 27 | writeGeneratedFile(destPath, output); 28 | } 29 | } 30 | 31 | fs.copyFileSync('./LICENSE', './denoDist/LICENSE'); 32 | fs.copyFileSync('./README.md', './denoDist/README.md'); 33 | 34 | showDirStats('./denoDist'); 35 | } 36 | -------------------------------------------------------------------------------- /resources/checkgit.sh: -------------------------------------------------------------------------------- 1 | # Exit immediately if any subcommand terminated 2 | set -e 3 | trap "exit 1" ERR 4 | 5 | # 6 | # This script determines if current git state is the up to date main. If so 7 | # it exits normally. If not it prompts for an explicit continue. This script 8 | # intends to protect from versioning for NPM without first pushing changes 9 | # and including any changes on main. 10 | # 11 | 12 | # Check that local copy has no modifications 13 | GIT_MODIFIED_FILES=$(git ls-files -dm 2> /dev/null); 14 | GIT_STAGED_FILES=$(git diff --cached --name-only 2> /dev/null); 15 | if [ "$GIT_MODIFIED_FILES" != "" -o "$GIT_STAGED_FILES" != "" ]; then 16 | read -p "Git has local modifications. Continue? (y|N) " yn; 17 | if [ "$yn" != "y" ]; then exit 1; fi; 18 | fi; 19 | 20 | # First fetch to ensure git is up to date. Fail-fast if this fails. 21 | git fetch; 22 | if [[ $? -ne 0 ]]; then exit 1; fi; 23 | 24 | # Extract useful information. 25 | GIT_BRANCH=$(git branch -v 2> /dev/null | sed '/^[^*]/d'); 26 | GIT_BRANCH_NAME=$(echo "$GIT_BRANCH" | sed 's/* \([A-Za-z0-9_\-]*\).*/\1/'); 27 | GIT_BRANCH_SYNC=$(echo "$GIT_BRANCH" | sed 's/* [^[]*.\([^]]*\).*/\1/'); 28 | 29 | # Check if main is checked out 30 | if [ "$GIT_BRANCH_NAME" != "main" ]; then 31 | read -p "Git not on main but $GIT_BRANCH_NAME. Continue? (y|N) " yn; 32 | if [ "$yn" != "y" ]; then exit 1; fi; 33 | fi; 34 | 35 | # Check if branch is synced with remote 36 | if [ "$GIT_BRANCH_SYNC" != "" ]; then 37 | read -p "Git not up to date but $GIT_BRANCH_SYNC. Continue? (y|N) " yn; 38 | if [ "$yn" != "y" ]; then exit 1; fi; 39 | fi; 40 | -------------------------------------------------------------------------------- /resources/eslint-internal-rules/README.md: -------------------------------------------------------------------------------- 1 | # Custom ESLint Rules 2 | 3 | This is a dummy npm package that allows us to treat it as an `eslint-plugin-graphql-internal`. 4 | It's not actually published, nor are the rules here useful for users of graphql. 5 | 6 | **If you modify this rule, you must re-run `npm install` for it to take effect.** 7 | -------------------------------------------------------------------------------- /resources/eslint-internal-rules/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const onlyASCII = require('./only-ascii.js'); 4 | const noDirImport = require('./no-dir-import.js'); 5 | const requireToStringTag = require('./require-to-string-tag.js'); 6 | 7 | module.exports = { 8 | rules: { 9 | 'only-ascii': onlyASCII, 10 | 'no-dir-import': noDirImport, 11 | 'require-to-string-tag': requireToStringTag, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /resources/eslint-internal-rules/no-dir-import.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | module.exports = function noDirImportRule(context) { 7 | return { 8 | ImportDeclaration: checkImportPath, 9 | ExportNamedDeclaration: checkImportPath, 10 | }; 11 | 12 | function checkImportPath(node) { 13 | const { source } = node; 14 | 15 | // bail if the declaration doesn't have a source, e.g. "export { foo };" 16 | if (!source) { 17 | return; 18 | } 19 | 20 | const importPath = source.value; 21 | if (importPath.startsWith('./') || importPath.startsWith('../')) { 22 | const baseDir = path.dirname(context.getFilename()); 23 | const resolvedPath = path.resolve(baseDir, importPath); 24 | 25 | if ( 26 | fs.existsSync(resolvedPath) && 27 | fs.statSync(resolvedPath).isDirectory() 28 | ) { 29 | context.report({ 30 | node: source, 31 | message: 'It is not allowed to import from directory', 32 | }); 33 | } 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /resources/eslint-internal-rules/only-ascii.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | meta: { 5 | schema: [ 6 | { 7 | type: 'object', 8 | properties: { 9 | allowEmoji: { 10 | type: 'boolean', 11 | }, 12 | }, 13 | additionalProperties: false, 14 | }, 15 | ], 16 | }, 17 | create: onlyASCII, 18 | }; 19 | 20 | function onlyASCII(context) { 21 | const regExp = 22 | context.options[0]?.allowEmoji === true 23 | ? /[^\p{ASCII}\p{Emoji}]+/gu 24 | : /\P{ASCII}+/gu; 25 | 26 | return { 27 | Program() { 28 | const sourceCode = context.getSourceCode(); 29 | const text = sourceCode.getText(); 30 | 31 | for (const match of text.matchAll(regExp)) { 32 | context.report({ 33 | loc: sourceCode.getLocFromIndex(match.index), 34 | message: `Non-ASCII character "${match[0]}" found.`, 35 | }); 36 | } 37 | }, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /resources/eslint-internal-rules/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-graphql-internal", 3 | "version": "0.0.0", 4 | "private": true, 5 | "engines": { 6 | "node": ">= 14.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /resources/eslint-internal-rules/require-to-string-tag.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function requireToStringTag(context) { 4 | const sourceCode = context.getSourceCode(); 5 | 6 | return { 7 | 'ExportNamedDeclaration > ClassDeclaration': (classNode) => { 8 | const properties = classNode.body.body; 9 | if (properties.some(isToStringTagProperty)) { 10 | return; 11 | } 12 | 13 | const jsDoc = context.getJSDocComment(classNode)?.value; 14 | // FIXME: use proper TSDoc parser instead of includes once we fix TSDoc comments 15 | if (jsDoc?.includes('@internal') === true) { 16 | return; 17 | } 18 | 19 | context.report({ 20 | node: classNode, 21 | message: 22 | 'All classes in public API required to have [Symbol.toStringTag] method', 23 | }); 24 | }, 25 | }; 26 | 27 | function isToStringTagProperty(propertyNode) { 28 | if ( 29 | propertyNode.type !== 'MethodDefinition' || 30 | propertyNode.kind !== 'get' 31 | ) { 32 | return false; 33 | } 34 | const keyText = sourceCode.getText(propertyNode.key); 35 | return keyText === 'Symbol.toStringTag'; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /resources/gen-version.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { version } = require('../package.json'); 4 | 5 | const { writeGeneratedFile } = require('./utils.js'); 6 | 7 | const versionMatch = /^(\d+)\.(\d+)\.(\d+)-?(.*)?$/.exec(version); 8 | if (!versionMatch) { 9 | throw new Error('Version does not match semver spec: ' + version); 10 | } 11 | 12 | const [, major, minor, patch, preReleaseTag] = versionMatch; 13 | 14 | const body = ` 15 | // Note: This file is autogenerated using "resources/gen-version.js" script and 16 | // automatically updated by "npm version" command. 17 | 18 | /** 19 | * A string containing the version of the GraphQL.js library 20 | */ 21 | export const version = '${version}' as string; 22 | 23 | /** 24 | * An object containing the components of the GraphQL.js version string 25 | */ 26 | export const versionInfo = Object.freeze({ 27 | major: ${major} as number, 28 | minor: ${minor} as number, 29 | patch: ${patch} as number, 30 | preReleaseTag: ${ 31 | preReleaseTag ? `'${preReleaseTag}'` : 'null' 32 | } as string | null, 33 | }); 34 | `; 35 | 36 | if (require.main === module) { 37 | writeGeneratedFile('./src/version.ts', body); 38 | } 39 | -------------------------------------------------------------------------------- /resources/inline-invariant.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Eliminates function call to `invariant` if the condition is met. 5 | * 6 | * Transforms: 7 | * 8 | * invariant(, ...) 9 | * 10 | * to: 11 | * 12 | * () || invariant(false ...) 13 | */ 14 | module.exports = function inlineInvariant(context) { 15 | const invariantTemplate = context.template(` 16 | (%%cond%%) || invariant(false, %%args%%) 17 | `); 18 | const assertTemplate = context.template(` 19 | (%%cond%%) || devAssert(false, %%args%%) 20 | `); 21 | 22 | return { 23 | visitor: { 24 | CallExpression(path) { 25 | const node = path.node; 26 | const parent = path.parent; 27 | 28 | if ( 29 | parent.type !== 'ExpressionStatement' || 30 | node.callee.type !== 'Identifier' || 31 | node.arguments.length === 0 32 | ) { 33 | return; 34 | } 35 | 36 | const calleeName = node.callee.name; 37 | if (calleeName === 'invariant') { 38 | const [cond, args] = node.arguments; 39 | 40 | path.replaceWith(invariantTemplate({ cond, args })); 41 | } else if (calleeName === 'devAssert') { 42 | const [cond, args] = node.arguments; 43 | path.replaceWith(assertTemplate({ cond, args })); 44 | } 45 | }, 46 | }, 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /resources/ts-register.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('@babel/register')({ extensions: ['.ts'] }); 4 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL JS 2 | 3 | The primary `graphql` module includes everything you need to define a GraphQL 4 | schema and fulfill GraphQL requests. 5 | 6 | ```js 7 | import { ... } from 'graphql'; // ES6 8 | var GraphQL = require('graphql'); // CommonJS 9 | ``` 10 | 11 | Each sub directory within is a sub-module of graphql-js: 12 | 13 | - [`graphql/language`](language/README.md): Parse and operate on the GraphQL 14 | language. 15 | - [`graphql/type`](type/README.md): Define GraphQL types and schema. 16 | - [`graphql/validation`](validation/README.md): The Validation phase of 17 | fulfilling a GraphQL result. 18 | - [`graphql/execution`](execution/README.md): The Execution phase of fulfilling 19 | a GraphQL request. 20 | - [`graphql/error`](error/README.md): Creating and formatting GraphQL errors. 21 | - [`graphql/utilities`](utilities/README.md): Common useful computations upon 22 | the GraphQL language and type objects. 23 | - [`graphql/subscription`](subscription/README.md): Subscribe to data updates. 24 | -------------------------------------------------------------------------------- /src/__testUtils__/__tests__/genFuzzStrings-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { genFuzzStrings } from '../genFuzzStrings'; 5 | 6 | function expectFuzzStrings(options: { 7 | allowedChars: ReadonlyArray; 8 | maxLength: number; 9 | }) { 10 | return expect([...genFuzzStrings(options)]); 11 | } 12 | 13 | describe('genFuzzStrings', () => { 14 | it('always provide empty string', () => { 15 | expectFuzzStrings({ allowedChars: [], maxLength: 0 }).to.deep.equal(['']); 16 | expectFuzzStrings({ allowedChars: [], maxLength: 1 }).to.deep.equal(['']); 17 | expectFuzzStrings({ allowedChars: ['a'], maxLength: 0 }).to.deep.equal([ 18 | '', 19 | ]); 20 | }); 21 | 22 | it('generate strings with single character', () => { 23 | expectFuzzStrings({ allowedChars: ['a'], maxLength: 1 }).to.deep.equal([ 24 | '', 25 | 'a', 26 | ]); 27 | 28 | expectFuzzStrings({ 29 | allowedChars: ['a', 'b', 'c'], 30 | maxLength: 1, 31 | }).to.deep.equal(['', 'a', 'b', 'c']); 32 | }); 33 | 34 | it('generate strings with multiple character', () => { 35 | expectFuzzStrings({ allowedChars: ['a'], maxLength: 2 }).to.deep.equal([ 36 | '', 37 | 'a', 38 | 'aa', 39 | ]); 40 | 41 | expectFuzzStrings({ 42 | allowedChars: ['a', 'b', 'c'], 43 | maxLength: 2, 44 | }).to.deep.equal([ 45 | '', 46 | 'a', 47 | 'b', 48 | 'c', 49 | 'aa', 50 | 'ab', 51 | 'ac', 52 | 'ba', 53 | 'bb', 54 | 'bc', 55 | 'ca', 56 | 'cb', 57 | 'cc', 58 | ]); 59 | }); 60 | 61 | it('generate strings longer than possible number of characters', () => { 62 | expectFuzzStrings({ 63 | allowedChars: ['a', 'b'], 64 | maxLength: 3, 65 | }).to.deep.equal([ 66 | '', 67 | 'a', 68 | 'b', 69 | 'aa', 70 | 'ab', 71 | 'ba', 72 | 'bb', 73 | 'aaa', 74 | 'aab', 75 | 'aba', 76 | 'abb', 77 | 'baa', 78 | 'bab', 79 | 'bba', 80 | 'bbb', 81 | ]); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /src/__testUtils__/__tests__/inspectStr-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { inspectStr } from '../inspectStr'; 5 | 6 | describe('inspectStr', () => { 7 | it('handles null and undefined values', () => { 8 | expect(inspectStr(null)).to.equal('null'); 9 | expect(inspectStr(undefined)).to.equal('null'); 10 | }); 11 | 12 | it('correctly print various strings', () => { 13 | expect(inspectStr('')).to.equal('``'); 14 | expect(inspectStr('a')).to.equal('`a`'); 15 | expect(inspectStr('"')).to.equal('`"`'); 16 | expect(inspectStr("'")).to.equal("`'`"); 17 | expect(inspectStr('\\"')).to.equal('`\\"`'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/__testUtils__/__tests__/resolveOnNextTick-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { resolveOnNextTick } from '../resolveOnNextTick'; 5 | 6 | describe('resolveOnNextTick', () => { 7 | it('resolves promise on the next tick', async () => { 8 | const output = []; 9 | 10 | const promise1 = resolveOnNextTick().then(() => { 11 | output.push('second'); 12 | }); 13 | const promise2 = resolveOnNextTick().then(() => { 14 | output.push('third'); 15 | }); 16 | output.push('first'); 17 | 18 | await Promise.all([promise1, promise2]); 19 | expect(output).to.deep.equal(['first', 'second', 'third']); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/__testUtils__/dedent.ts: -------------------------------------------------------------------------------- 1 | export function dedentString(string: string): string { 2 | const trimmedStr = string 3 | .replace(/^\n*/m, '') // remove leading newline 4 | .replace(/[ \t\n]*$/, ''); // remove trailing spaces and tabs 5 | 6 | // fixes indentation by removing leading spaces and tabs from each line 7 | let indent = ''; 8 | for (const char of trimmedStr) { 9 | if (char !== ' ' && char !== '\t') { 10 | break; 11 | } 12 | indent += char; 13 | } 14 | 15 | return trimmedStr.replace(RegExp('^' + indent, 'mg'), ''); // remove indent 16 | } 17 | 18 | /** 19 | * An ES6 string tag that fixes indentation and also trims string. 20 | * 21 | * Example usage: 22 | * ```ts 23 | * const str = dedent` 24 | * { 25 | * test 26 | * } 27 | * `; 28 | * str === "{\n test\n}"; 29 | * ``` 30 | */ 31 | export function dedent( 32 | strings: ReadonlyArray, 33 | ...values: ReadonlyArray 34 | ): string { 35 | let str = strings[0]; 36 | 37 | for (let i = 1; i < strings.length; ++i) { 38 | str += values[i - 1] + strings[i]; // interpolation 39 | } 40 | return dedentString(str); 41 | } 42 | -------------------------------------------------------------------------------- /src/__testUtils__/expectJSON.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { isObjectLike } from '../jsutils/isObjectLike'; 4 | import { mapValue } from '../jsutils/mapValue'; 5 | 6 | /** 7 | * Deeply transforms an arbitrary value to a JSON-safe value by calling toJSON 8 | * on any nested value which defines it. 9 | */ 10 | function toJSONDeep(value: unknown): unknown { 11 | if (!isObjectLike(value)) { 12 | return value; 13 | } 14 | 15 | if (typeof value.toJSON === 'function') { 16 | return value.toJSON(); 17 | } 18 | 19 | if (Array.isArray(value)) { 20 | return value.map(toJSONDeep); 21 | } 22 | 23 | return mapValue(value, toJSONDeep); 24 | } 25 | 26 | export function expectJSON(actual: unknown) { 27 | const actualJSON = toJSONDeep(actual); 28 | 29 | return { 30 | toDeepEqual(expected: unknown) { 31 | const expectedJSON = toJSONDeep(expected); 32 | expect(actualJSON).to.deep.equal(expectedJSON); 33 | }, 34 | toDeepNestedProperty(path: string, expected: unknown) { 35 | const expectedJSON = toJSONDeep(expected); 36 | expect(actualJSON).to.deep.nested.property(path, expectedJSON); 37 | }, 38 | }; 39 | } 40 | 41 | export function expectToThrowJSON(fn: () => unknown) { 42 | function mapException(): unknown { 43 | try { 44 | return fn(); 45 | } catch (error) { 46 | throw toJSONDeep(error); 47 | } 48 | } 49 | 50 | return expect(mapException).to.throw(); 51 | } 52 | -------------------------------------------------------------------------------- /src/__testUtils__/genFuzzStrings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generator that produces all possible combinations of allowed characters. 3 | */ 4 | export function* genFuzzStrings(options: { 5 | allowedChars: ReadonlyArray; 6 | maxLength: number; 7 | }): Generator { 8 | const { allowedChars, maxLength } = options; 9 | const numAllowedChars = allowedChars.length; 10 | 11 | let numCombinations = 0; 12 | for (let length = 1; length <= maxLength; ++length) { 13 | numCombinations += numAllowedChars ** length; 14 | } 15 | 16 | yield ''; // special case for empty string 17 | for (let combination = 0; combination < numCombinations; ++combination) { 18 | let permutation = ''; 19 | 20 | let leftOver = combination; 21 | while (leftOver >= 0) { 22 | const reminder = leftOver % numAllowedChars; 23 | permutation = allowedChars[reminder] + permutation; 24 | leftOver = (leftOver - reminder) / numAllowedChars - 1; 25 | } 26 | 27 | yield permutation; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/__testUtils__/inspectStr.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe } from '../jsutils/Maybe'; 2 | 3 | /** 4 | * Special inspect function to produce readable string literal for error messages in tests 5 | */ 6 | export function inspectStr(str: Maybe): string { 7 | if (str == null) { 8 | return 'null'; 9 | } 10 | return JSON.stringify(str) 11 | .replace(/^"|"$/g, '`') 12 | .replace(/\\"/g, '"') 13 | .replace(/\\\\/g, '\\'); 14 | } 15 | -------------------------------------------------------------------------------- /src/__testUtils__/kitchenSinkQuery.ts: -------------------------------------------------------------------------------- 1 | export const kitchenSinkQuery: string = String.raw` 2 | query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery { 3 | whoever123is: node(id: [123, 456]) { 4 | id 5 | ... on User @onInlineFragment { 6 | field2 { 7 | id 8 | alias: field1(first: 10, after: $foo) @include(if: $foo) { 9 | id 10 | ...frag @onFragmentSpread 11 | } 12 | } 13 | } 14 | ... @skip(unless: $foo) { 15 | id 16 | } 17 | ... { 18 | id 19 | } 20 | } 21 | } 22 | 23 | mutation likeStory @onMutation { 24 | like(story: 123) @onField { 25 | story { 26 | id @onField 27 | } 28 | } 29 | } 30 | 31 | subscription StoryLikeSubscription( 32 | $input: StoryLikeSubscribeInput @onVariableDefinition 33 | ) 34 | @onSubscription { 35 | storyLikeSubscribe(input: $input) { 36 | story { 37 | likers { 38 | count 39 | } 40 | likeSentence { 41 | text 42 | } 43 | } 44 | } 45 | } 46 | 47 | fragment frag on Friend @onFragmentDefinition { 48 | foo( 49 | size: $size 50 | bar: $b 51 | obj: { 52 | key: "value" 53 | block: """ 54 | block string uses \""" 55 | """ 56 | } 57 | ) 58 | } 59 | 60 | { 61 | unnamed(truthy: true, falsy: false, nullish: null) 62 | query 63 | } 64 | 65 | query { 66 | __typename 67 | } 68 | `; 69 | -------------------------------------------------------------------------------- /src/__testUtils__/resolveOnNextTick.ts: -------------------------------------------------------------------------------- 1 | export function resolveOnNextTick(): Promise { 2 | return Promise.resolve(undefined); 3 | } 4 | -------------------------------------------------------------------------------- /src/__tests__/version-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { version, versionInfo } from '../version'; 5 | 6 | describe('Version', () => { 7 | it('versionInfo', () => { 8 | expect(versionInfo).to.be.an('object'); 9 | expect(versionInfo).to.have.all.keys( 10 | 'major', 11 | 'minor', 12 | 'patch', 13 | 'preReleaseTag', 14 | ); 15 | 16 | const { major, minor, patch, preReleaseTag } = versionInfo; 17 | expect(major).to.be.a('number').at.least(0); 18 | expect(minor).to.be.a('number').at.least(0); 19 | expect(patch).to.be.a('number').at.least(0); 20 | 21 | // Can't be verified on all versions 22 | /* c8 ignore start */ 23 | switch (preReleaseTag?.split('.').length) { 24 | case undefined: 25 | break; 26 | case 2: 27 | expect(preReleaseTag).to.match( 28 | /^(alpha|beta|rc|experimental-[\w-]+)\.\d+/, 29 | ); 30 | break; 31 | case 4: 32 | expect(preReleaseTag).to.match( 33 | /^(alpha|beta|rc)\.\d+.experimental-[\w-]+\.\d+/, 34 | ); 35 | break; 36 | default: 37 | expect.fail('Invalid pre-release tag: ' + preReleaseTag); 38 | } 39 | /* c8 ignore stop */ 40 | }); 41 | 42 | it('version', () => { 43 | expect(version).to.be.a('string'); 44 | 45 | const { major, minor, patch, preReleaseTag } = versionInfo; 46 | expect(version).to.equal( 47 | // Can't be verified on all versions 48 | /* c8 ignore next 3 */ 49 | preReleaseTag === null 50 | ? `${major}.${minor}.${patch}` 51 | : `${major}.${minor}.${patch}-${preReleaseTag}`, 52 | ); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/error/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL Errors 2 | 3 | The `graphql/error` module is responsible for creating and formatting 4 | GraphQL errors. 5 | 6 | ```js 7 | import { ... } from 'graphql/error'; // ES6 8 | var GraphQLError = require('graphql/error'); // CommonJS 9 | ``` 10 | -------------------------------------------------------------------------------- /src/error/__tests__/locatedError-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { GraphQLError } from '../GraphQLError'; 5 | import { locatedError } from '../locatedError'; 6 | 7 | describe('locatedError', () => { 8 | it('passes GraphQLError through', () => { 9 | const e = new GraphQLError('msg', { path: ['path', 3, 'to', 'field'] }); 10 | 11 | expect(locatedError(e, [], [])).to.deep.equal(e); 12 | }); 13 | 14 | it('wraps non-errors', () => { 15 | const testObject = Object.freeze({}); 16 | const error = locatedError(testObject, [], []); 17 | 18 | expect(error).to.be.instanceOf(GraphQLError); 19 | expect(error.originalError).to.include({ 20 | name: 'NonErrorThrown', 21 | thrownValue: testObject, 22 | }); 23 | }); 24 | 25 | it('passes GraphQLError-ish through', () => { 26 | const e = new Error(); 27 | // @ts-expect-error 28 | e.locations = []; 29 | // @ts-expect-error 30 | e.path = []; 31 | // @ts-expect-error 32 | e.nodes = []; 33 | // @ts-expect-error 34 | e.source = null; 35 | // @ts-expect-error 36 | e.positions = []; 37 | e.name = 'GraphQLError'; 38 | 39 | expect(locatedError(e, [], [])).to.deep.equal(e); 40 | }); 41 | 42 | it('does not pass through elasticsearch-like errors', () => { 43 | const e = new Error('I am from elasticsearch'); 44 | // @ts-expect-error 45 | e.path = '/something/feed/_search'; 46 | 47 | expect(locatedError(e, [], [])).to.not.deep.equal(e); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/error/index.ts: -------------------------------------------------------------------------------- 1 | export { GraphQLError, printError, formatError } from './GraphQLError'; 2 | export type { 3 | GraphQLErrorOptions, 4 | GraphQLFormattedError, 5 | GraphQLErrorExtensions, 6 | GraphQLFormattedErrorExtensions, 7 | } from './GraphQLError'; 8 | 9 | export { syntaxError } from './syntaxError'; 10 | 11 | export { locatedError } from './locatedError'; 12 | -------------------------------------------------------------------------------- /src/error/locatedError.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe } from '../jsutils/Maybe'; 2 | import { toError } from '../jsutils/toError'; 3 | 4 | import type { ASTNode } from '../language/ast'; 5 | 6 | import { GraphQLError } from './GraphQLError'; 7 | 8 | /** 9 | * Given an arbitrary value, presumably thrown while attempting to execute a 10 | * GraphQL operation, produce a new GraphQLError aware of the location in the 11 | * document responsible for the original Error. 12 | */ 13 | export function locatedError( 14 | rawOriginalError: unknown, 15 | nodes: ASTNode | ReadonlyArray | undefined | null, 16 | path?: Maybe>, 17 | ): GraphQLError { 18 | const originalError = toError(rawOriginalError); 19 | 20 | // Note: this uses a brand-check to support GraphQL errors originating from other contexts. 21 | if (isLocatedGraphQLError(originalError)) { 22 | return originalError; 23 | } 24 | 25 | return new GraphQLError(originalError.message, { 26 | nodes: (originalError as GraphQLError).nodes ?? nodes, 27 | source: (originalError as GraphQLError).source, 28 | positions: (originalError as GraphQLError).positions, 29 | path, 30 | originalError, 31 | }); 32 | } 33 | 34 | function isLocatedGraphQLError(error: any): error is GraphQLError { 35 | return Array.isArray(error.path); 36 | } 37 | -------------------------------------------------------------------------------- /src/error/syntaxError.ts: -------------------------------------------------------------------------------- 1 | import type { Source } from '../language/source'; 2 | 3 | import { GraphQLError } from './GraphQLError'; 4 | 5 | /** 6 | * Produces a GraphQLError representing a syntax error, containing useful 7 | * descriptive information about the syntax error's position in the source. 8 | */ 9 | export function syntaxError( 10 | source: Source, 11 | position: number, 12 | description: string, 13 | ): GraphQLError { 14 | return new GraphQLError(`Syntax Error: ${description}`, { 15 | source, 16 | positions: [position], 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/execution/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL Execution 2 | 3 | The `graphql/execution` module is responsible for the execution phase of 4 | fulfilling a GraphQL request. 5 | 6 | ```js 7 | import { execute } from 'graphql/execution'; // ES6 8 | var GraphQLExecution = require('graphql/execution'); // CommonJS 9 | ``` 10 | -------------------------------------------------------------------------------- /src/execution/__tests__/simplePubSub-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { SimplePubSub } from './simplePubSub'; 5 | 6 | describe('SimplePubSub', () => { 7 | it('subscribe async-iterator mock', async () => { 8 | const pubsub = new SimplePubSub(); 9 | const iterator = pubsub.getSubscriber((x) => x); 10 | 11 | // Queue up publishes 12 | expect(pubsub.emit('Apple')).to.equal(true); 13 | expect(pubsub.emit('Banana')).to.equal(true); 14 | 15 | // Read payloads 16 | expect(await iterator.next()).to.deep.equal({ 17 | done: false, 18 | value: 'Apple', 19 | }); 20 | expect(await iterator.next()).to.deep.equal({ 21 | done: false, 22 | value: 'Banana', 23 | }); 24 | 25 | // Read ahead 26 | const i3 = iterator.next().then((x) => x); 27 | const i4 = iterator.next().then((x) => x); 28 | 29 | // Publish 30 | expect(pubsub.emit('Coconut')).to.equal(true); 31 | expect(pubsub.emit('Durian')).to.equal(true); 32 | 33 | // Await out of order to get correct results 34 | expect(await i4).to.deep.equal({ done: false, value: 'Durian' }); 35 | expect(await i3).to.deep.equal({ done: false, value: 'Coconut' }); 36 | 37 | // Read ahead 38 | const i5 = iterator.next().then((x) => x); 39 | 40 | // Terminate queue 41 | await iterator.return(); 42 | 43 | // Publish is not caught after terminate 44 | expect(pubsub.emit('Fig')).to.equal(false); 45 | 46 | // Find that cancelled read-ahead got a "done" result 47 | expect(await i5).to.deep.equal({ done: true, value: undefined }); 48 | 49 | // And next returns empty completion value 50 | expect(await iterator.next()).to.deep.equal({ 51 | done: true, 52 | value: undefined, 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /src/execution/__tests__/simplePubSub.ts: -------------------------------------------------------------------------------- 1 | import { invariant } from '../../jsutils/invariant'; 2 | 3 | /** 4 | * Create an AsyncIterator from an EventEmitter. Useful for mocking a 5 | * PubSub system for tests. 6 | */ 7 | export class SimplePubSub { 8 | private _subscribers: Set<(value: T) => void>; 9 | 10 | constructor() { 11 | this._subscribers = new Set(); 12 | } 13 | 14 | emit(event: T): boolean { 15 | for (const subscriber of this._subscribers) { 16 | subscriber(event); 17 | } 18 | return this._subscribers.size > 0; 19 | } 20 | 21 | getSubscriber(transform: (value: T) => R): AsyncGenerator { 22 | const pullQueue: Array<(result: IteratorResult) => void> = []; 23 | const pushQueue: Array = []; 24 | let listening = true; 25 | this._subscribers.add(pushValue); 26 | 27 | const emptyQueue = () => { 28 | listening = false; 29 | this._subscribers.delete(pushValue); 30 | for (const resolve of pullQueue) { 31 | resolve({ value: undefined, done: true }); 32 | } 33 | pullQueue.length = 0; 34 | pushQueue.length = 0; 35 | }; 36 | 37 | return { 38 | next(): Promise> { 39 | if (!listening) { 40 | return Promise.resolve({ value: undefined, done: true }); 41 | } 42 | 43 | if (pushQueue.length > 0) { 44 | const value = pushQueue[0]; 45 | pushQueue.shift(); 46 | return Promise.resolve({ value, done: false }); 47 | } 48 | return new Promise((resolve) => pullQueue.push(resolve)); 49 | }, 50 | return(): Promise> { 51 | emptyQueue(); 52 | return Promise.resolve({ value: undefined, done: true }); 53 | }, 54 | throw(error: unknown) { 55 | emptyQueue(); 56 | return Promise.reject(error); 57 | }, 58 | [Symbol.asyncIterator]() { 59 | return this; 60 | }, 61 | }; 62 | 63 | function pushValue(event: T): void { 64 | const value: R = transform(event); 65 | if (pullQueue.length > 0) { 66 | const receiver = pullQueue.shift(); 67 | invariant(receiver); 68 | receiver({ value, done: false }); 69 | } else { 70 | pushQueue.push(value); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/execution/index.ts: -------------------------------------------------------------------------------- 1 | export { pathToArray as responsePathAsArray } from '../jsutils/Path'; 2 | 3 | export { 4 | execute, 5 | executeSync, 6 | defaultFieldResolver, 7 | defaultTypeResolver, 8 | } from './execute'; 9 | 10 | export type { 11 | ExecutionArgs, 12 | ExecutionResult, 13 | FormattedExecutionResult, 14 | } from './execute'; 15 | 16 | export { subscribe, createSourceEventStream } from './subscribe'; 17 | 18 | export { 19 | getArgumentValues, 20 | getVariableValues, 21 | getDirectiveValues, 22 | } from './values'; 23 | -------------------------------------------------------------------------------- /src/execution/mapAsyncIterator.ts: -------------------------------------------------------------------------------- 1 | import type { PromiseOrValue } from '../jsutils/PromiseOrValue'; 2 | 3 | /** 4 | * Given an AsyncIterable and a callback function, return an AsyncIterator 5 | * which produces values mapped via calling the callback function. 6 | */ 7 | export function mapAsyncIterator( 8 | iterable: AsyncGenerator | AsyncIterable, 9 | callback: (value: T) => PromiseOrValue, 10 | ): AsyncGenerator { 11 | const iterator = iterable[Symbol.asyncIterator](); 12 | 13 | async function mapResult( 14 | result: IteratorResult, 15 | ): Promise> { 16 | if (result.done) { 17 | return result; 18 | } 19 | 20 | try { 21 | return { value: await callback(result.value), done: false }; 22 | } catch (error) { 23 | /* c8 ignore start */ 24 | // FIXME: add test case 25 | if (typeof iterator.return === 'function') { 26 | try { 27 | await iterator.return(); 28 | } catch (_e) { 29 | /* ignore error */ 30 | } 31 | } 32 | throw error; 33 | /* c8 ignore stop */ 34 | } 35 | } 36 | 37 | return { 38 | async next() { 39 | return mapResult(await iterator.next()); 40 | }, 41 | async return(): Promise> { 42 | // If iterator.return() does not exist, then type R must be undefined. 43 | return typeof iterator.return === 'function' 44 | ? mapResult(await iterator.return()) 45 | : { value: undefined as any, done: true }; 46 | }, 47 | async throw(error?: unknown) { 48 | if (typeof iterator.throw === 'function') { 49 | return mapResult(await iterator.throw(error)); 50 | } 51 | throw error; 52 | }, 53 | [Symbol.asyncIterator]() { 54 | return this; 55 | }, 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/jsutils/Maybe.ts: -------------------------------------------------------------------------------- 1 | /** Conveniently represents flow's "Maybe" type https://flow.org/en/docs/types/maybe/ */ 2 | export type Maybe = null | undefined | T; 3 | -------------------------------------------------------------------------------- /src/jsutils/ObjMap.ts: -------------------------------------------------------------------------------- 1 | export interface ObjMap { 2 | [key: string]: T; 3 | } 4 | 5 | export type ObjMapLike = ObjMap | { [key: string]: T }; 6 | 7 | export interface ReadOnlyObjMap { 8 | readonly [key: string]: T; 9 | } 10 | 11 | export type ReadOnlyObjMapLike = 12 | | ReadOnlyObjMap 13 | | { readonly [key: string]: T }; 14 | -------------------------------------------------------------------------------- /src/jsutils/Path.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe } from './Maybe'; 2 | 3 | export interface Path { 4 | readonly prev: Path | undefined; 5 | readonly key: string | number; 6 | readonly typename: string | undefined; 7 | } 8 | 9 | /** 10 | * Given a Path and a key, return a new Path containing the new key. 11 | */ 12 | export function addPath( 13 | prev: Readonly | undefined, 14 | key: string | number, 15 | typename: string | undefined, 16 | ): Path { 17 | return { prev, key, typename }; 18 | } 19 | 20 | /** 21 | * Given a Path, return an Array of the path keys. 22 | */ 23 | export function pathToArray( 24 | path: Maybe>, 25 | ): Array { 26 | const flattened = []; 27 | let curr = path; 28 | while (curr) { 29 | flattened.push(curr.key); 30 | curr = curr.prev; 31 | } 32 | return flattened.reverse(); 33 | } 34 | -------------------------------------------------------------------------------- /src/jsutils/PromiseOrValue.ts: -------------------------------------------------------------------------------- 1 | export type PromiseOrValue = Promise | T; 2 | -------------------------------------------------------------------------------- /src/jsutils/README.md: -------------------------------------------------------------------------------- 1 | ## JavaScript Utils 2 | 3 | This directory contains dependency-free JavaScript utility functions used 4 | throughout the codebase. 5 | 6 | Each utility should belong in its own file and be the default export. 7 | 8 | These functions are not part of the module interface and are subject to change. 9 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/Path-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { addPath, pathToArray } from '../Path'; 5 | 6 | describe('Path', () => { 7 | it('can create a Path', () => { 8 | const first = addPath(undefined, 1, 'First'); 9 | 10 | expect(first).to.deep.equal({ 11 | prev: undefined, 12 | key: 1, 13 | typename: 'First', 14 | }); 15 | }); 16 | 17 | it('can add a new key to an existing Path', () => { 18 | const first = addPath(undefined, 1, 'First'); 19 | const second = addPath(first, 'two', 'Second'); 20 | 21 | expect(second).to.deep.equal({ 22 | prev: first, 23 | key: 'two', 24 | typename: 'Second', 25 | }); 26 | }); 27 | 28 | it('can convert a Path to an array of its keys', () => { 29 | const root = addPath(undefined, 0, 'Root'); 30 | const first = addPath(root, 'one', 'First'); 31 | const second = addPath(first, 2, 'Second'); 32 | 33 | const path = pathToArray(second); 34 | expect(path).to.deep.equal([0, 'one', 2]); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/didYouMean-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { didYouMean } from '../didYouMean'; 5 | 6 | describe('didYouMean', () => { 7 | it('Does accept an empty list', () => { 8 | expect(didYouMean([])).to.equal(''); 9 | }); 10 | 11 | it('Handles single suggestion', () => { 12 | expect(didYouMean(['A'])).to.equal(' Did you mean "A"?'); 13 | }); 14 | 15 | it('Handles two suggestions', () => { 16 | expect(didYouMean(['A', 'B'])).to.equal(' Did you mean "A" or "B"?'); 17 | }); 18 | 19 | it('Handles multiple suggestions', () => { 20 | expect(didYouMean(['A', 'B', 'C'])).to.equal( 21 | ' Did you mean "A", "B", or "C"?', 22 | ); 23 | }); 24 | 25 | it('Limits to five suggestions', () => { 26 | expect(didYouMean(['A', 'B', 'C', 'D', 'E', 'F'])).to.equal( 27 | ' Did you mean "A", "B", "C", "D", or "E"?', 28 | ); 29 | }); 30 | 31 | it('Adds sub-message', () => { 32 | expect(didYouMean('the letter', ['A'])).to.equal( 33 | ' Did you mean the letter "A"?', 34 | ); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/identityFunc-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { identityFunc } from '../identityFunc'; 5 | 6 | describe('identityFunc', () => { 7 | it('returns the first argument it receives', () => { 8 | // @ts-expect-error (Expects an argument) 9 | expect(identityFunc()).to.equal(undefined); 10 | expect(identityFunc(undefined)).to.equal(undefined); 11 | expect(identityFunc(null)).to.equal(null); 12 | 13 | const obj = {}; 14 | expect(identityFunc(obj)).to.equal(obj); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/instanceOf-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { instanceOf } from '../instanceOf'; 5 | 6 | describe('instanceOf', () => { 7 | it('do not throw on values without prototype', () => { 8 | class Foo { 9 | get [Symbol.toStringTag]() { 10 | return 'Foo'; 11 | } 12 | } 13 | 14 | expect(instanceOf(true, Foo)).to.equal(false); 15 | expect(instanceOf(null, Foo)).to.equal(false); 16 | expect(instanceOf(Object.create(null), Foo)).to.equal(false); 17 | }); 18 | 19 | it('detect name clashes with older versions of this lib', () => { 20 | function oldVersion() { 21 | class Foo {} 22 | return Foo; 23 | } 24 | 25 | function newVersion() { 26 | class Foo { 27 | get [Symbol.toStringTag]() { 28 | return 'Foo'; 29 | } 30 | } 31 | return Foo; 32 | } 33 | 34 | const NewClass = newVersion(); 35 | const OldClass = oldVersion(); 36 | expect(instanceOf(new NewClass(), NewClass)).to.equal(true); 37 | expect(() => instanceOf(new OldClass(), NewClass)).to.throw(); 38 | }); 39 | 40 | it('allows instances to have share the same constructor name', () => { 41 | function getMinifiedClass(tag: string) { 42 | class SomeNameAfterMinification { 43 | get [Symbol.toStringTag]() { 44 | return tag; 45 | } 46 | } 47 | return SomeNameAfterMinification; 48 | } 49 | 50 | const Foo = getMinifiedClass('Foo'); 51 | const Bar = getMinifiedClass('Bar'); 52 | expect(instanceOf(new Foo(), Bar)).to.equal(false); 53 | expect(instanceOf(new Bar(), Foo)).to.equal(false); 54 | 55 | const DuplicateOfFoo = getMinifiedClass('Foo'); 56 | expect(() => instanceOf(new DuplicateOfFoo(), Foo)).to.throw(); 57 | expect(() => instanceOf(new Foo(), DuplicateOfFoo)).to.throw(); 58 | }); 59 | 60 | it('fails with descriptive error message', () => { 61 | function getFoo() { 62 | class Foo { 63 | get [Symbol.toStringTag]() { 64 | return 'Foo'; 65 | } 66 | } 67 | return Foo; 68 | } 69 | const Foo1 = getFoo(); 70 | const Foo2 = getFoo(); 71 | 72 | expect(() => instanceOf(new Foo1(), Foo2)).to.throw( 73 | /^Cannot use Foo "{}" from another module or realm./m, 74 | ); 75 | expect(() => instanceOf(new Foo2(), Foo1)).to.throw( 76 | /^Cannot use Foo "{}" from another module or realm./m, 77 | ); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/invariant-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { invariant } from '../invariant'; 5 | 6 | describe('invariant', () => { 7 | it('throws on false conditions', () => { 8 | expect(() => invariant(false, 'Oops!')).to.throw('Oops!'); 9 | }); 10 | 11 | it('use default error message', () => { 12 | expect(() => invariant(false)).to.throw('Unexpected invariant triggered.'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/isAsyncIterable-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { identityFunc } from '../identityFunc'; 5 | import { isAsyncIterable } from '../isAsyncIterable'; 6 | 7 | describe('isAsyncIterable', () => { 8 | it('should return `true` for AsyncIterable', () => { 9 | const asyncIterable = { [Symbol.asyncIterator]: identityFunc }; 10 | expect(isAsyncIterable(asyncIterable)).to.equal(true); 11 | 12 | async function* asyncGeneratorFunc() { 13 | /* do nothing */ 14 | } 15 | 16 | expect(isAsyncIterable(asyncGeneratorFunc())).to.equal(true); 17 | 18 | // But async generator function itself is not iterable 19 | expect(isAsyncIterable(asyncGeneratorFunc)).to.equal(false); 20 | }); 21 | 22 | it('should return `false` for all other values', () => { 23 | expect(isAsyncIterable(null)).to.equal(false); 24 | expect(isAsyncIterable(undefined)).to.equal(false); 25 | 26 | expect(isAsyncIterable('ABC')).to.equal(false); 27 | expect(isAsyncIterable('0')).to.equal(false); 28 | expect(isAsyncIterable('')).to.equal(false); 29 | 30 | expect(isAsyncIterable([])).to.equal(false); 31 | expect(isAsyncIterable(new Int8Array(1))).to.equal(false); 32 | 33 | expect(isAsyncIterable({})).to.equal(false); 34 | expect(isAsyncIterable({ iterable: true })).to.equal(false); 35 | 36 | const asyncIteratorWithoutSymbol = { next: identityFunc }; 37 | expect(isAsyncIterable(asyncIteratorWithoutSymbol)).to.equal(false); 38 | 39 | const nonAsyncIterable = { [Symbol.iterator]: identityFunc }; 40 | expect(isAsyncIterable(nonAsyncIterable)).to.equal(false); 41 | 42 | function* generatorFunc() { 43 | /* do nothing */ 44 | } 45 | expect(isAsyncIterable(generatorFunc())).to.equal(false); 46 | 47 | const invalidAsyncIterable = { 48 | [Symbol.asyncIterator]: { next: identityFunc }, 49 | }; 50 | expect(isAsyncIterable(invalidAsyncIterable)).to.equal(false); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/isObjectLike-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { identityFunc } from '../identityFunc'; 5 | import { isObjectLike } from '../isObjectLike'; 6 | 7 | describe('isObjectLike', () => { 8 | it('should return `true` for objects', () => { 9 | expect(isObjectLike({})).to.equal(true); 10 | expect(isObjectLike(Object.create(null))).to.equal(true); 11 | expect(isObjectLike(/a/)).to.equal(true); 12 | expect(isObjectLike([])).to.equal(true); 13 | }); 14 | 15 | it('should return `false` for non-objects', () => { 16 | expect(isObjectLike(undefined)).to.equal(false); 17 | expect(isObjectLike(null)).to.equal(false); 18 | expect(isObjectLike(true)).to.equal(false); 19 | expect(isObjectLike('')).to.equal(false); 20 | expect(isObjectLike(identityFunc)).to.equal(false); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/suggestionList-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { suggestionList } from '../suggestionList'; 5 | 6 | function expectSuggestions(input: string, options: ReadonlyArray) { 7 | return expect(suggestionList(input, options)); 8 | } 9 | 10 | describe('suggestionList', () => { 11 | it('Returns results when input is empty', () => { 12 | expectSuggestions('', ['a']).to.deep.equal(['a']); 13 | }); 14 | 15 | it('Returns empty array when there are no options', () => { 16 | expectSuggestions('input', []).to.deep.equal([]); 17 | }); 18 | 19 | it('Returns options with small lexical distance', () => { 20 | expectSuggestions('greenish', ['green']).to.deep.equal(['green']); 21 | expectSuggestions('green', ['greenish']).to.deep.equal(['greenish']); 22 | }); 23 | 24 | it('Rejects options with distance that exceeds threshold', () => { 25 | // spell-checker:disable 26 | expectSuggestions('aaaa', ['aaab']).to.deep.equal(['aaab']); 27 | expectSuggestions('aaaa', ['aabb']).to.deep.equal(['aabb']); 28 | expectSuggestions('aaaa', ['abbb']).to.deep.equal([]); 29 | // spell-checker:enable 30 | 31 | expectSuggestions('ab', ['ca']).to.deep.equal([]); 32 | }); 33 | 34 | it('Returns options with different case', () => { 35 | // cSpell:ignore verylongstring 36 | expectSuggestions('verylongstring', ['VERYLONGSTRING']).to.deep.equal([ 37 | 'VERYLONGSTRING', 38 | ]); 39 | 40 | expectSuggestions('VERYLONGSTRING', ['verylongstring']).to.deep.equal([ 41 | 'verylongstring', 42 | ]); 43 | 44 | expectSuggestions('VERYLONGSTRING', ['VeryLongString']).to.deep.equal([ 45 | 'VeryLongString', 46 | ]); 47 | }); 48 | 49 | it('Returns options with transpositions', () => { 50 | expectSuggestions('agr', ['arg']).to.deep.equal(['arg']); 51 | expectSuggestions('214365879', ['123456789']).to.deep.equal(['123456789']); 52 | }); 53 | 54 | it('Returns options sorted based on lexical distance', () => { 55 | expectSuggestions('abc', ['a', 'ab', 'abc']).to.deep.equal([ 56 | 'abc', 57 | 'ab', 58 | 'a', 59 | ]); 60 | }); 61 | 62 | it('Returns options with the same lexical distance sorted lexicographically', () => { 63 | expectSuggestions('a', ['az', 'ax', 'ay']).to.deep.equal([ 64 | 'ax', 65 | 'ay', 66 | 'az', 67 | ]); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/jsutils/__tests__/toObjMap-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import type { ObjMapLike } from '../ObjMap'; 5 | import { toObjMap } from '../toObjMap'; 6 | 7 | // Workaround to make both ESLint happy 8 | const __proto__ = '__proto__'; 9 | 10 | describe('toObjMap', () => { 11 | it('convert undefined to ObjMap', () => { 12 | const result = toObjMap(undefined); 13 | expect(result).to.deep.equal({}); 14 | expect(Object.getPrototypeOf(result)).to.equal(null); 15 | }); 16 | 17 | it('convert null to ObjMap', () => { 18 | const result = toObjMap(null); 19 | expect(result).to.deep.equal({}); 20 | expect(Object.getPrototypeOf(result)).to.equal(null); 21 | }); 22 | 23 | it('convert empty object to ObjMap', () => { 24 | const result = toObjMap({}); 25 | expect(result).to.deep.equal({}); 26 | expect(Object.getPrototypeOf(result)).to.equal(null); 27 | }); 28 | 29 | it('convert object with own properties to ObjMap', () => { 30 | const obj: ObjMapLike = Object.freeze({ foo: 'bar' }); 31 | 32 | const result = toObjMap(obj); 33 | expect(result).to.deep.equal(obj); 34 | expect(Object.getPrototypeOf(result)).to.equal(null); 35 | }); 36 | 37 | it('convert object with __proto__ property to ObjMap', () => { 38 | const protoObj = Object.freeze({ toString: false }); 39 | const obj = Object.create(null); 40 | obj[__proto__] = protoObj; 41 | Object.freeze(obj); 42 | 43 | const result = toObjMap(obj); 44 | expect(Object.keys(result)).to.deep.equal(['__proto__']); 45 | expect(Object.getPrototypeOf(result)).to.equal(null); 46 | expect(result[__proto__]).to.equal(protoObj); 47 | }); 48 | 49 | it('passthrough empty ObjMap', () => { 50 | const objMap = Object.create(null); 51 | expect(toObjMap(objMap)).to.deep.equal(objMap); 52 | }); 53 | 54 | it('passthrough ObjMap with properties', () => { 55 | const objMap = Object.freeze({ 56 | __proto__: null, 57 | foo: 'bar', 58 | }); 59 | expect(toObjMap(objMap)).to.deep.equal(objMap); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/jsutils/devAssert.ts: -------------------------------------------------------------------------------- 1 | export function devAssert(condition: unknown, message: string): void { 2 | const booleanCondition = Boolean(condition); 3 | if (!booleanCondition) { 4 | throw new Error(message); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/jsutils/didYouMean.ts: -------------------------------------------------------------------------------- 1 | const MAX_SUGGESTIONS = 5; 2 | 3 | /** 4 | * Given [ A, B, C ] return ' Did you mean A, B, or C?'. 5 | */ 6 | export function didYouMean(suggestions: ReadonlyArray): string; 7 | export function didYouMean( 8 | subMessage: string, 9 | suggestions: ReadonlyArray, 10 | ): string; 11 | export function didYouMean( 12 | firstArg: string | ReadonlyArray, 13 | secondArg?: ReadonlyArray, 14 | ) { 15 | const [subMessage, suggestionsArg] = secondArg 16 | ? [firstArg as string, secondArg] 17 | : [undefined, firstArg as ReadonlyArray]; 18 | 19 | let message = ' Did you mean '; 20 | if (subMessage) { 21 | message += subMessage + ' '; 22 | } 23 | 24 | const suggestions = suggestionsArg.map((x) => `"${x}"`); 25 | switch (suggestions.length) { 26 | case 0: 27 | return ''; 28 | case 1: 29 | return message + suggestions[0] + '?'; 30 | case 2: 31 | return message + suggestions[0] + ' or ' + suggestions[1] + '?'; 32 | } 33 | 34 | const selected = suggestions.slice(0, MAX_SUGGESTIONS); 35 | const lastItem = selected.pop(); 36 | return message + selected.join(', ') + ', or ' + lastItem + '?'; 37 | } 38 | -------------------------------------------------------------------------------- /src/jsutils/groupBy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Groups array items into a Map, given a function to produce grouping key. 3 | */ 4 | export function groupBy( 5 | list: ReadonlyArray, 6 | keyFn: (item: T) => K, 7 | ): Map> { 8 | const result = new Map>(); 9 | for (const item of list) { 10 | const key = keyFn(item); 11 | const group = result.get(key); 12 | if (group === undefined) { 13 | result.set(key, [item]); 14 | } else { 15 | group.push(item); 16 | } 17 | } 18 | return result; 19 | } 20 | -------------------------------------------------------------------------------- /src/jsutils/identityFunc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the first argument it receives. 3 | */ 4 | export function identityFunc(x: T): T { 5 | return x; 6 | } 7 | -------------------------------------------------------------------------------- /src/jsutils/instanceOf.ts: -------------------------------------------------------------------------------- 1 | import { inspect } from './inspect'; 2 | 3 | /* c8 ignore next 3 */ 4 | const isProduction = 5 | globalThis.process && 6 | // eslint-disable-next-line no-undef 7 | process.env.NODE_ENV === 'production'; 8 | 9 | /** 10 | * A replacement for instanceof which includes an error warning when multi-realm 11 | * constructors are detected. 12 | * See: https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production 13 | * See: https://webpack.js.org/guides/production/ 14 | */ 15 | export const instanceOf: (value: unknown, constructor: Constructor) => boolean = 16 | /* c8 ignore next 6 */ 17 | // FIXME: https://github.com/graphql/graphql-js/issues/2317 18 | isProduction 19 | ? function instanceOf(value: unknown, constructor: Constructor): boolean { 20 | return value instanceof constructor; 21 | } 22 | : function instanceOf(value: unknown, constructor: Constructor): boolean { 23 | if (value instanceof constructor) { 24 | return true; 25 | } 26 | if (typeof value === 'object' && value !== null) { 27 | // Prefer Symbol.toStringTag since it is immune to minification. 28 | const className = constructor.prototype[Symbol.toStringTag]; 29 | const valueClassName = 30 | // We still need to support constructor's name to detect conflicts with older versions of this library. 31 | Symbol.toStringTag in value 32 | ? // @ts-expect-error TS bug see, https://github.com/microsoft/TypeScript/issues/38009 33 | value[Symbol.toStringTag] 34 | : value.constructor?.name; 35 | if (className === valueClassName) { 36 | const stringifiedValue = inspect(value); 37 | throw new Error( 38 | `Cannot use ${className} "${stringifiedValue}" from another module or realm. 39 | 40 | Ensure that there is only one instance of "graphql" in the node_modules 41 | directory. If different versions of "graphql" are the dependencies of other 42 | relied on modules, use "resolutions" to ensure only one version is installed. 43 | 44 | https://yarnpkg.com/en/docs/selective-version-resolutions 45 | 46 | Duplicate "graphql" modules cannot be used at the same time since different 47 | versions may have different capabilities and behavior. The data from one 48 | version used in the function from another could produce confusing and 49 | spurious results.`, 50 | ); 51 | } 52 | } 53 | return false; 54 | }; 55 | 56 | interface Constructor extends Function { 57 | prototype: { 58 | [Symbol.toStringTag]: string; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/jsutils/invariant.ts: -------------------------------------------------------------------------------- 1 | export function invariant( 2 | condition: unknown, 3 | message?: string, 4 | ): asserts condition { 5 | const booleanCondition = Boolean(condition); 6 | if (!booleanCondition) { 7 | throw new Error( 8 | message != null ? message : 'Unexpected invariant triggered.', 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/jsutils/isAsyncIterable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the provided object implements the AsyncIterator protocol via 3 | * implementing a `Symbol.asyncIterator` method. 4 | */ 5 | export function isAsyncIterable( 6 | maybeAsyncIterable: any, 7 | ): maybeAsyncIterable is AsyncIterable { 8 | return typeof maybeAsyncIterable?.[Symbol.asyncIterator] === 'function'; 9 | } 10 | -------------------------------------------------------------------------------- /src/jsutils/isIterableObject.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the provided object is an Object (i.e. not a string literal) 3 | * and implements the Iterator protocol. 4 | * 5 | * This may be used in place of [Array.isArray()][isArray] to determine if 6 | * an object should be iterated-over e.g. Array, Map, Set, Int8Array, 7 | * TypedArray, etc. but excludes string literals. 8 | * 9 | * @example 10 | * ```ts 11 | * isIterableObject([ 1, 2, 3 ]) // true 12 | * isIterableObject(new Map()) // true 13 | * isIterableObject('ABC') // false 14 | * isIterableObject({ key: 'value' }) // false 15 | * isIterableObject({ length: 1, 0: 'Alpha' }) // false 16 | * ``` 17 | */ 18 | export function isIterableObject( 19 | maybeIterable: any, 20 | ): maybeIterable is Iterable { 21 | return ( 22 | typeof maybeIterable === 'object' && 23 | typeof maybeIterable?.[Symbol.iterator] === 'function' 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/jsutils/isObjectLike.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Return true if `value` is object-like. A value is object-like if it's not 3 | * `null` and has a `typeof` result of "object". 4 | */ 5 | export function isObjectLike( 6 | value: unknown, 7 | ): value is { [key: string]: unknown } { 8 | return typeof value == 'object' && value !== null; 9 | } 10 | -------------------------------------------------------------------------------- /src/jsutils/isPromise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the value acts like a Promise, i.e. has a "then" function, 3 | * otherwise returns false. 4 | */ 5 | export function isPromise(value: any): value is Promise { 6 | return typeof value?.then === 'function'; 7 | } 8 | -------------------------------------------------------------------------------- /src/jsutils/keyMap.ts: -------------------------------------------------------------------------------- 1 | import type { ObjMap } from './ObjMap'; 2 | 3 | /** 4 | * Creates a keyed JS object from an array, given a function to produce the keys 5 | * for each value in the array. 6 | * 7 | * This provides a convenient lookup for the array items if the key function 8 | * produces unique results. 9 | * ```ts 10 | * const phoneBook = [ 11 | * { name: 'Jon', num: '555-1234' }, 12 | * { name: 'Jenny', num: '867-5309' } 13 | * ] 14 | * 15 | * const entriesByName = keyMap( 16 | * phoneBook, 17 | * entry => entry.name 18 | * ) 19 | * 20 | * // { 21 | * // Jon: { name: 'Jon', num: '555-1234' }, 22 | * // Jenny: { name: 'Jenny', num: '867-5309' } 23 | * // } 24 | * 25 | * const jennyEntry = entriesByName['Jenny'] 26 | * 27 | * // { name: 'Jenny', num: '857-6309' } 28 | * ``` 29 | */ 30 | export function keyMap( 31 | list: ReadonlyArray, 32 | keyFn: (item: T) => string, 33 | ): ObjMap { 34 | const result = Object.create(null); 35 | for (const item of list) { 36 | result[keyFn(item)] = item; 37 | } 38 | return result; 39 | } 40 | -------------------------------------------------------------------------------- /src/jsutils/keyValMap.ts: -------------------------------------------------------------------------------- 1 | import type { ObjMap } from './ObjMap'; 2 | 3 | /** 4 | * Creates a keyed JS object from an array, given a function to produce the keys 5 | * and a function to produce the values from each item in the array. 6 | * ```ts 7 | * const phoneBook = [ 8 | * { name: 'Jon', num: '555-1234' }, 9 | * { name: 'Jenny', num: '867-5309' } 10 | * ] 11 | * 12 | * // { Jon: '555-1234', Jenny: '867-5309' } 13 | * const phonesByName = keyValMap( 14 | * phoneBook, 15 | * entry => entry.name, 16 | * entry => entry.num 17 | * ) 18 | * ``` 19 | */ 20 | export function keyValMap( 21 | list: ReadonlyArray, 22 | keyFn: (item: T) => string, 23 | valFn: (item: T) => V, 24 | ): ObjMap { 25 | const result = Object.create(null); 26 | for (const item of list) { 27 | result[keyFn(item)] = valFn(item); 28 | } 29 | return result; 30 | } 31 | -------------------------------------------------------------------------------- /src/jsutils/mapValue.ts: -------------------------------------------------------------------------------- 1 | import type { ObjMap, ReadOnlyObjMap } from './ObjMap'; 2 | 3 | /** 4 | * Creates an object map with the same keys as `map` and values generated by 5 | * running each value of `map` thru `fn`. 6 | */ 7 | export function mapValue( 8 | map: ReadOnlyObjMap, 9 | fn: (value: T, key: string) => V, 10 | ): ObjMap { 11 | const result = Object.create(null); 12 | 13 | for (const key of Object.keys(map)) { 14 | result[key] = fn(map[key], key); 15 | } 16 | return result; 17 | } 18 | -------------------------------------------------------------------------------- /src/jsutils/memoize3.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Memoizes the provided three-argument function. 3 | */ 4 | export function memoize3< 5 | A1 extends object, 6 | A2 extends object, 7 | A3 extends object, 8 | R, 9 | >(fn: (a1: A1, a2: A2, a3: A3) => R): (a1: A1, a2: A2, a3: A3) => R { 10 | let cache0: WeakMap>>; 11 | 12 | return function memoized(a1, a2, a3) { 13 | if (cache0 === undefined) { 14 | cache0 = new WeakMap(); 15 | } 16 | 17 | let cache1 = cache0.get(a1); 18 | if (cache1 === undefined) { 19 | cache1 = new WeakMap(); 20 | cache0.set(a1, cache1); 21 | } 22 | 23 | let cache2 = cache1.get(a2); 24 | if (cache2 === undefined) { 25 | cache2 = new WeakMap(); 26 | cache1.set(a2, cache2); 27 | } 28 | 29 | let fnResult = cache2.get(a3); 30 | if (fnResult === undefined) { 31 | fnResult = fn(a1, a2, a3); 32 | cache2.set(a3, fnResult); 33 | } 34 | 35 | return fnResult; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/jsutils/naturalCompare.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a number indicating whether a reference string comes before, or after, 3 | * or is the same as the given string in natural sort order. 4 | * 5 | * See: https://en.wikipedia.org/wiki/Natural_sort_order 6 | * 7 | */ 8 | export function naturalCompare(aStr: string, bStr: string): number { 9 | let aIndex = 0; 10 | let bIndex = 0; 11 | 12 | while (aIndex < aStr.length && bIndex < bStr.length) { 13 | let aChar = aStr.charCodeAt(aIndex); 14 | let bChar = bStr.charCodeAt(bIndex); 15 | 16 | if (isDigit(aChar) && isDigit(bChar)) { 17 | let aNum = 0; 18 | do { 19 | ++aIndex; 20 | aNum = aNum * 10 + aChar - DIGIT_0; 21 | aChar = aStr.charCodeAt(aIndex); 22 | } while (isDigit(aChar) && aNum > 0); 23 | 24 | let bNum = 0; 25 | do { 26 | ++bIndex; 27 | bNum = bNum * 10 + bChar - DIGIT_0; 28 | bChar = bStr.charCodeAt(bIndex); 29 | } while (isDigit(bChar) && bNum > 0); 30 | 31 | if (aNum < bNum) { 32 | return -1; 33 | } 34 | 35 | if (aNum > bNum) { 36 | return 1; 37 | } 38 | } else { 39 | if (aChar < bChar) { 40 | return -1; 41 | } 42 | if (aChar > bChar) { 43 | return 1; 44 | } 45 | ++aIndex; 46 | ++bIndex; 47 | } 48 | } 49 | 50 | return aStr.length - bStr.length; 51 | } 52 | 53 | const DIGIT_0 = 48; 54 | const DIGIT_9 = 57; 55 | 56 | function isDigit(code: number): boolean { 57 | return !isNaN(code) && DIGIT_0 <= code && code <= DIGIT_9; 58 | } 59 | -------------------------------------------------------------------------------- /src/jsutils/printPathArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Build a string describing the path. 3 | */ 4 | export function printPathArray(path: ReadonlyArray): string { 5 | return path 6 | .map((key) => 7 | typeof key === 'number' ? '[' + key.toString() + ']' : '.' + key, 8 | ) 9 | .join(''); 10 | } 11 | -------------------------------------------------------------------------------- /src/jsutils/promiseForObject.ts: -------------------------------------------------------------------------------- 1 | import type { ObjMap } from './ObjMap'; 2 | 3 | /** 4 | * This function transforms a JS object `ObjMap>` into 5 | * a `Promise>` 6 | * 7 | * This is akin to bluebird's `Promise.props`, but implemented only using 8 | * `Promise.all` so it will work with any implementation of ES6 promises. 9 | */ 10 | export function promiseForObject( 11 | object: ObjMap>, 12 | ): Promise> { 13 | return Promise.all(Object.values(object)).then((resolvedValues) => { 14 | const resolvedObject = Object.create(null); 15 | for (const [i, key] of Object.keys(object).entries()) { 16 | resolvedObject[key] = resolvedValues[i]; 17 | } 18 | return resolvedObject; 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/jsutils/promiseReduce.ts: -------------------------------------------------------------------------------- 1 | import { isPromise } from './isPromise'; 2 | import type { PromiseOrValue } from './PromiseOrValue'; 3 | 4 | /** 5 | * Similar to Array.prototype.reduce(), however the reducing callback may return 6 | * a Promise, in which case reduction will continue after each promise resolves. 7 | * 8 | * If the callback does not return a Promise, then this function will also not 9 | * return a Promise. 10 | */ 11 | export function promiseReduce( 12 | values: Iterable, 13 | callbackFn: (accumulator: U, currentValue: T) => PromiseOrValue, 14 | initialValue: PromiseOrValue, 15 | ): PromiseOrValue { 16 | let accumulator = initialValue; 17 | for (const value of values) { 18 | accumulator = isPromise(accumulator) 19 | ? accumulator.then((resolved) => callbackFn(resolved, value)) 20 | : callbackFn(accumulator, value); 21 | } 22 | return accumulator; 23 | } 24 | -------------------------------------------------------------------------------- /src/jsutils/toError.ts: -------------------------------------------------------------------------------- 1 | import { inspect } from './inspect'; 2 | 3 | /** 4 | * Sometimes a non-error is thrown, wrap it as an Error instance to ensure a consistent Error interface. 5 | */ 6 | export function toError(thrownValue: unknown): Error { 7 | return thrownValue instanceof Error 8 | ? thrownValue 9 | : new NonErrorThrown(thrownValue); 10 | } 11 | 12 | class NonErrorThrown extends Error { 13 | thrownValue: unknown; 14 | 15 | constructor(thrownValue: unknown) { 16 | super('Unexpected error value: ' + inspect(thrownValue)); 17 | this.name = 'NonErrorThrown'; 18 | this.thrownValue = thrownValue; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/jsutils/toObjMap.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe } from './Maybe'; 2 | import type { ReadOnlyObjMap, ReadOnlyObjMapLike } from './ObjMap'; 3 | 4 | export function toObjMap( 5 | obj: Maybe>, 6 | ): ReadOnlyObjMap { 7 | if (obj == null) { 8 | return Object.create(null); 9 | } 10 | 11 | if (Object.getPrototypeOf(obj) === null) { 12 | return obj; 13 | } 14 | 15 | const map = Object.create(null); 16 | for (const [key, value] of Object.entries(obj)) { 17 | map[key] = value; 18 | } 19 | return map; 20 | } 21 | -------------------------------------------------------------------------------- /src/language/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL Language 2 | 3 | The `graphql/language` module is responsible for parsing and operating on the 4 | GraphQL language. 5 | 6 | ```js 7 | import { ... } from 'graphql/language'; // ES6 8 | var GraphQLLanguage = require('graphql/language'); // CommonJS 9 | ``` 10 | -------------------------------------------------------------------------------- /src/language/__tests__/blockString-fuzz.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | 3 | import { dedent } from '../../__testUtils__/dedent'; 4 | import { genFuzzStrings } from '../../__testUtils__/genFuzzStrings'; 5 | import { inspectStr } from '../../__testUtils__/inspectStr'; 6 | 7 | import { invariant } from '../../jsutils/invariant'; 8 | 9 | import { isPrintableAsBlockString, printBlockString } from '../blockString'; 10 | import { Lexer } from '../lexer'; 11 | import { Source } from '../source'; 12 | 13 | function lexValue(str: string): string { 14 | const lexer = new Lexer(new Source(str)); 15 | const value = lexer.advance().value; 16 | 17 | invariant(typeof value === 'string'); 18 | invariant(lexer.advance().kind === '', 'Expected EOF'); 19 | return value; 20 | } 21 | 22 | function testPrintableBlockString( 23 | testValue: string, 24 | options?: { minimize: boolean }, 25 | ): void { 26 | const blockString = printBlockString(testValue, options); 27 | const printedValue = lexValue(blockString); 28 | invariant( 29 | testValue === printedValue, 30 | dedent` 31 | Expected lexValue(${inspectStr(blockString)}) 32 | to equal ${inspectStr(testValue)} 33 | but got ${inspectStr(printedValue)} 34 | `, 35 | ); 36 | } 37 | 38 | function testNonPrintableBlockString(testValue: string): void { 39 | const blockString = printBlockString(testValue); 40 | const printedValue = lexValue(blockString); 41 | invariant( 42 | testValue !== printedValue, 43 | dedent` 44 | Expected lexValue(${inspectStr(blockString)}) 45 | to not equal ${inspectStr(testValue)} 46 | `, 47 | ); 48 | } 49 | 50 | describe('printBlockString', () => { 51 | it('correctly print random strings', () => { 52 | // Testing with length >7 is taking exponentially more time. However it is 53 | // highly recommended to test with increased limit if you make any change. 54 | for (const fuzzStr of genFuzzStrings({ 55 | allowedChars: ['\n', '\t', ' ', '"', 'a', '\\'], 56 | maxLength: 7, 57 | })) { 58 | if (!isPrintableAsBlockString(fuzzStr)) { 59 | testNonPrintableBlockString(fuzzStr); 60 | continue; 61 | } 62 | 63 | testPrintableBlockString(fuzzStr); 64 | testPrintableBlockString(fuzzStr, { minimize: true }); 65 | } 66 | }).timeout(20000); 67 | }); 68 | -------------------------------------------------------------------------------- /src/language/__tests__/source-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { Source } from '../source'; 5 | 6 | describe('Source', () => { 7 | it('asserts that a body was provided', () => { 8 | // @ts-expect-error 9 | expect(() => new Source()).to.throw( 10 | 'Body must be a string. Received: undefined.', 11 | ); 12 | }); 13 | 14 | it('asserts that a valid body was provided', () => { 15 | // @ts-expect-error 16 | expect(() => new Source({})).to.throw( 17 | 'Body must be a string. Received: {}.', 18 | ); 19 | }); 20 | 21 | it('can be Object.toStringified', () => { 22 | const source = new Source(''); 23 | 24 | expect(Object.prototype.toString.call(source)).to.equal('[object Source]'); 25 | }); 26 | 27 | it('rejects invalid locationOffset', () => { 28 | function createSource(locationOffset: { line: number; column: number }) { 29 | return new Source('', '', locationOffset); 30 | } 31 | 32 | expect(() => createSource({ line: 0, column: 1 })).to.throw( 33 | 'line in locationOffset is 1-indexed and must be positive.', 34 | ); 35 | expect(() => createSource({ line: -1, column: 1 })).to.throw( 36 | 'line in locationOffset is 1-indexed and must be positive.', 37 | ); 38 | 39 | expect(() => createSource({ line: 1, column: 0 })).to.throw( 40 | 'column in locationOffset is 1-indexed and must be positive.', 41 | ); 42 | expect(() => createSource({ line: 1, column: -1 })).to.throw( 43 | 'column in locationOffset is 1-indexed and must be positive.', 44 | ); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/language/characterClasses.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ``` 3 | * WhiteSpace :: 4 | * - "Horizontal Tab (U+0009)" 5 | * - "Space (U+0020)" 6 | * ``` 7 | * @internal 8 | */ 9 | export function isWhiteSpace(code: number): boolean { 10 | return code === 0x0009 || code === 0x0020; 11 | } 12 | 13 | /** 14 | * ``` 15 | * Digit :: one of 16 | * - `0` `1` `2` `3` `4` `5` `6` `7` `8` `9` 17 | * ``` 18 | * @internal 19 | */ 20 | export function isDigit(code: number): boolean { 21 | return code >= 0x0030 && code <= 0x0039; 22 | } 23 | 24 | /** 25 | * ``` 26 | * Letter :: one of 27 | * - `A` `B` `C` `D` `E` `F` `G` `H` `I` `J` `K` `L` `M` 28 | * - `N` `O` `P` `Q` `R` `S` `T` `U` `V` `W` `X` `Y` `Z` 29 | * - `a` `b` `c` `d` `e` `f` `g` `h` `i` `j` `k` `l` `m` 30 | * - `n` `o` `p` `q` `r` `s` `t` `u` `v` `w` `x` `y` `z` 31 | * ``` 32 | * @internal 33 | */ 34 | export function isLetter(code: number): boolean { 35 | return ( 36 | (code >= 0x0061 && code <= 0x007a) || // A-Z 37 | (code >= 0x0041 && code <= 0x005a) // a-z 38 | ); 39 | } 40 | 41 | /** 42 | * ``` 43 | * NameStart :: 44 | * - Letter 45 | * - `_` 46 | * ``` 47 | * @internal 48 | */ 49 | export function isNameStart(code: number): boolean { 50 | return isLetter(code) || code === 0x005f; 51 | } 52 | 53 | /** 54 | * ``` 55 | * NameContinue :: 56 | * - Letter 57 | * - Digit 58 | * - `_` 59 | * ``` 60 | * @internal 61 | */ 62 | export function isNameContinue(code: number): boolean { 63 | return isLetter(code) || isDigit(code) || code === 0x005f; 64 | } 65 | -------------------------------------------------------------------------------- /src/language/directiveLocation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The set of allowed directive location values. 3 | */ 4 | enum DirectiveLocation { 5 | /** Request Definitions */ 6 | QUERY = 'QUERY', 7 | MUTATION = 'MUTATION', 8 | SUBSCRIPTION = 'SUBSCRIPTION', 9 | FIELD = 'FIELD', 10 | FRAGMENT_DEFINITION = 'FRAGMENT_DEFINITION', 11 | FRAGMENT_SPREAD = 'FRAGMENT_SPREAD', 12 | INLINE_FRAGMENT = 'INLINE_FRAGMENT', 13 | VARIABLE_DEFINITION = 'VARIABLE_DEFINITION', 14 | /** Type System Definitions */ 15 | SCHEMA = 'SCHEMA', 16 | SCALAR = 'SCALAR', 17 | OBJECT = 'OBJECT', 18 | FIELD_DEFINITION = 'FIELD_DEFINITION', 19 | ARGUMENT_DEFINITION = 'ARGUMENT_DEFINITION', 20 | INTERFACE = 'INTERFACE', 21 | UNION = 'UNION', 22 | ENUM = 'ENUM', 23 | ENUM_VALUE = 'ENUM_VALUE', 24 | INPUT_OBJECT = 'INPUT_OBJECT', 25 | INPUT_FIELD_DEFINITION = 'INPUT_FIELD_DEFINITION', 26 | } 27 | export { DirectiveLocation }; 28 | 29 | /** 30 | * The enum type representing the directive location values. 31 | * 32 | * @deprecated Please use `DirectiveLocation`. Will be remove in v17. 33 | */ 34 | export type DirectiveLocationEnum = typeof DirectiveLocation; 35 | -------------------------------------------------------------------------------- /src/language/kinds.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The set of allowed kind values for AST nodes. 3 | */ 4 | enum Kind { 5 | /** Name */ 6 | NAME = 'Name', 7 | 8 | /** Document */ 9 | DOCUMENT = 'Document', 10 | OPERATION_DEFINITION = 'OperationDefinition', 11 | VARIABLE_DEFINITION = 'VariableDefinition', 12 | SELECTION_SET = 'SelectionSet', 13 | FIELD = 'Field', 14 | ARGUMENT = 'Argument', 15 | 16 | /** Fragments */ 17 | FRAGMENT_SPREAD = 'FragmentSpread', 18 | INLINE_FRAGMENT = 'InlineFragment', 19 | FRAGMENT_DEFINITION = 'FragmentDefinition', 20 | 21 | /** Values */ 22 | VARIABLE = 'Variable', 23 | INT = 'IntValue', 24 | FLOAT = 'FloatValue', 25 | STRING = 'StringValue', 26 | BOOLEAN = 'BooleanValue', 27 | NULL = 'NullValue', 28 | ENUM = 'EnumValue', 29 | LIST = 'ListValue', 30 | OBJECT = 'ObjectValue', 31 | OBJECT_FIELD = 'ObjectField', 32 | 33 | /** Directives */ 34 | DIRECTIVE = 'Directive', 35 | 36 | /** Types */ 37 | NAMED_TYPE = 'NamedType', 38 | LIST_TYPE = 'ListType', 39 | NON_NULL_TYPE = 'NonNullType', 40 | 41 | /** Type System Definitions */ 42 | SCHEMA_DEFINITION = 'SchemaDefinition', 43 | OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition', 44 | 45 | /** Type Definitions */ 46 | SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition', 47 | OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition', 48 | FIELD_DEFINITION = 'FieldDefinition', 49 | INPUT_VALUE_DEFINITION = 'InputValueDefinition', 50 | INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition', 51 | UNION_TYPE_DEFINITION = 'UnionTypeDefinition', 52 | ENUM_TYPE_DEFINITION = 'EnumTypeDefinition', 53 | ENUM_VALUE_DEFINITION = 'EnumValueDefinition', 54 | INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition', 55 | 56 | /** Directive Definitions */ 57 | DIRECTIVE_DEFINITION = 'DirectiveDefinition', 58 | 59 | /** Type System Extensions */ 60 | SCHEMA_EXTENSION = 'SchemaExtension', 61 | 62 | /** Type Extensions */ 63 | SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension', 64 | OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension', 65 | INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension', 66 | UNION_TYPE_EXTENSION = 'UnionTypeExtension', 67 | ENUM_TYPE_EXTENSION = 'EnumTypeExtension', 68 | INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension', 69 | } 70 | export { Kind }; 71 | 72 | /** 73 | * The enum type representing the possible kind values of AST nodes. 74 | * 75 | * @deprecated Please use `Kind`. Will be remove in v17. 76 | */ 77 | export type KindEnum = typeof Kind; 78 | -------------------------------------------------------------------------------- /src/language/location.ts: -------------------------------------------------------------------------------- 1 | import { invariant } from '../jsutils/invariant'; 2 | 3 | import type { Source } from './source'; 4 | 5 | const LineRegExp = /\r\n|[\n\r]/g; 6 | 7 | /** 8 | * Represents a location in a Source. 9 | */ 10 | export interface SourceLocation { 11 | readonly line: number; 12 | readonly column: number; 13 | } 14 | 15 | /** 16 | * Takes a Source and a UTF-8 character offset, and returns the corresponding 17 | * line and column as a SourceLocation. 18 | */ 19 | export function getLocation(source: Source, position: number): SourceLocation { 20 | let lastLineStart = 0; 21 | let line = 1; 22 | 23 | for (const match of source.body.matchAll(LineRegExp)) { 24 | invariant(typeof match.index === 'number'); 25 | if (match.index >= position) { 26 | break; 27 | } 28 | lastLineStart = match.index + match[0].length; 29 | line += 1; 30 | } 31 | 32 | return { line, column: position + 1 - lastLineStart }; 33 | } 34 | -------------------------------------------------------------------------------- /src/language/printString.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints a string as a GraphQL StringValue literal. Replaces control characters 3 | * and excluded characters (" U+0022 and \\ U+005C) with escape sequences. 4 | */ 5 | export function printString(str: string): string { 6 | return `"${str.replace(escapedRegExp, escapedReplacer)}"`; 7 | } 8 | 9 | // eslint-disable-next-line no-control-regex 10 | const escapedRegExp = /[\x00-\x1f\x22\x5c\x7f-\x9f]/g; 11 | 12 | function escapedReplacer(str: string): string { 13 | return escapeSequences[str.charCodeAt(0)]; 14 | } 15 | 16 | // prettier-ignore 17 | const escapeSequences = [ 18 | '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004', '\\u0005', '\\u0006', '\\u0007', 19 | '\\b', '\\t', '\\n', '\\u000B', '\\f', '\\r', '\\u000E', '\\u000F', 20 | '\\u0010', '\\u0011', '\\u0012', '\\u0013', '\\u0014', '\\u0015', '\\u0016', '\\u0017', 21 | '\\u0018', '\\u0019', '\\u001A', '\\u001B', '\\u001C', '\\u001D', '\\u001E', '\\u001F', 22 | '', '', '\\"', '', '', '', '', '', 23 | '', '', '', '', '', '', '', '', // 2F 24 | '', '', '', '', '', '', '', '', 25 | '', '', '', '', '', '', '', '', // 3F 26 | '', '', '', '', '', '', '', '', 27 | '', '', '', '', '', '', '', '', // 4F 28 | '', '', '', '', '', '', '', '', 29 | '', '', '', '', '\\\\', '', '', '', // 5F 30 | '', '', '', '', '', '', '', '', 31 | '', '', '', '', '', '', '', '', // 6F 32 | '', '', '', '', '', '', '', '', 33 | '', '', '', '', '', '', '', '\\u007F', 34 | '\\u0080', '\\u0081', '\\u0082', '\\u0083', '\\u0084', '\\u0085', '\\u0086', '\\u0087', 35 | '\\u0088', '\\u0089', '\\u008A', '\\u008B', '\\u008C', '\\u008D', '\\u008E', '\\u008F', 36 | '\\u0090', '\\u0091', '\\u0092', '\\u0093', '\\u0094', '\\u0095', '\\u0096', '\\u0097', 37 | '\\u0098', '\\u0099', '\\u009A', '\\u009B', '\\u009C', '\\u009D', '\\u009E', '\\u009F', 38 | ]; 39 | -------------------------------------------------------------------------------- /src/language/source.ts: -------------------------------------------------------------------------------- 1 | import { devAssert } from '../jsutils/devAssert'; 2 | import { inspect } from '../jsutils/inspect'; 3 | import { instanceOf } from '../jsutils/instanceOf'; 4 | 5 | interface Location { 6 | line: number; 7 | column: number; 8 | } 9 | 10 | /** 11 | * A representation of source input to GraphQL. The `name` and `locationOffset` parameters are 12 | * optional, but they are useful for clients who store GraphQL documents in source files. 13 | * For example, if the GraphQL input starts at line 40 in a file named `Foo.graphql`, it might 14 | * be useful for `name` to be `"Foo.graphql"` and location to be `{ line: 40, column: 1 }`. 15 | * The `line` and `column` properties in `locationOffset` are 1-indexed. 16 | */ 17 | export class Source { 18 | body: string; 19 | name: string; 20 | locationOffset: Location; 21 | 22 | constructor( 23 | body: string, 24 | name: string = 'GraphQL request', 25 | locationOffset: Location = { line: 1, column: 1 }, 26 | ) { 27 | devAssert( 28 | typeof body === 'string', 29 | `Body must be a string. Received: ${inspect(body)}.`, 30 | ); 31 | 32 | this.body = body; 33 | this.name = name; 34 | this.locationOffset = locationOffset; 35 | devAssert( 36 | this.locationOffset.line > 0, 37 | 'line in locationOffset is 1-indexed and must be positive.', 38 | ); 39 | devAssert( 40 | this.locationOffset.column > 0, 41 | 'column in locationOffset is 1-indexed and must be positive.', 42 | ); 43 | } 44 | 45 | get [Symbol.toStringTag]() { 46 | return 'Source'; 47 | } 48 | } 49 | 50 | /** 51 | * Test if the given value is a Source object. 52 | * 53 | * @internal 54 | */ 55 | export function isSource(source: unknown): source is Source { 56 | return instanceOf(source, Source); 57 | } 58 | -------------------------------------------------------------------------------- /src/language/tokenKind.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An exported enum describing the different kinds of tokens that the 3 | * lexer emits. 4 | */ 5 | enum TokenKind { 6 | SOF = '', 7 | EOF = '', 8 | BANG = '!', 9 | DOLLAR = '$', 10 | AMP = '&', 11 | PAREN_L = '(', 12 | PAREN_R = ')', 13 | SPREAD = '...', 14 | COLON = ':', 15 | EQUALS = '=', 16 | AT = '@', 17 | BRACKET_L = '[', 18 | BRACKET_R = ']', 19 | BRACE_L = '{', 20 | PIPE = '|', 21 | BRACE_R = '}', 22 | NAME = 'Name', 23 | INT = 'Int', 24 | FLOAT = 'Float', 25 | STRING = 'String', 26 | BLOCK_STRING = 'BlockString', 27 | COMMENT = 'Comment', 28 | } 29 | export { TokenKind }; 30 | 31 | /** 32 | * The enum type representing the token kinds values. 33 | * 34 | * @deprecated Please use `TokenKind`. Will be remove in v17. 35 | */ 36 | export type TokenKindEnum = typeof TokenKind; 37 | -------------------------------------------------------------------------------- /src/subscription/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL Subscription 2 | 3 | NOTE: the `graphql/subscription` module has been deprecated with its exported functions integrated into the `graphql/execution` module, to better conform with the terminology of the GraphQL specification. For backwards compatibility, the `graphql/subscription` module currently re-exports the moved functions from the `graphql/execution` module. In the next major release, the `graphql/subscription` module will be dropped entirely. 4 | 5 | The `graphql/subscription` module is responsible for subscribing to updates on specific data. 6 | 7 | ```js 8 | import { subscribe, createSourceEventStream } from 'graphql/subscription'; // ES6 9 | var GraphQLSubscription = require('graphql/subscription'); // CommonJS 10 | ``` 11 | -------------------------------------------------------------------------------- /src/subscription/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * NOTE: the `graphql/subscription` module has been deprecated with its 3 | * exported functions integrated into the `graphql/execution` module, to 4 | * better conform with the terminology of the GraphQL specification. 5 | * 6 | * For backwards compatibility, the `graphql/subscription` module 7 | * currently re-exports the moved functions from the `graphql/execution` 8 | * module. In the next major release, the `graphql/subscription` module 9 | * will be dropped entirely. 10 | */ 11 | 12 | import type { ExecutionArgs } from '../execution/execute'; 13 | 14 | /** 15 | * @deprecated use ExecutionArgs instead. Will be removed in v17 16 | * 17 | * ExecutionArgs has been broadened to include all properties within SubscriptionArgs. 18 | * The SubscriptionArgs type is retained for backwards compatibility. 19 | */ 20 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 21 | export interface SubscriptionArgs extends ExecutionArgs {} 22 | 23 | export { subscribe, createSourceEventStream } from '../execution/subscribe'; 24 | -------------------------------------------------------------------------------- /src/type/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL Type System 2 | 3 | The `graphql/type` module is responsible for defining GraphQL types and schema. 4 | 5 | ```js 6 | import { ... } from 'graphql/type'; // ES6 7 | var GraphQLType = require('graphql/type'); // CommonJS 8 | ``` 9 | -------------------------------------------------------------------------------- /src/type/__tests__/assertName-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { assertEnumValueName, assertName } from '../assertName'; 5 | 6 | describe('assertName', () => { 7 | it('passthrough valid name', () => { 8 | expect(assertName('_ValidName123')).to.equal('_ValidName123'); 9 | }); 10 | 11 | it('throws for non-strings', () => { 12 | // @ts-expect-error 13 | expect(() => assertName({})).to.throw('Expected name to be a string.'); 14 | }); 15 | 16 | it('throws on empty strings', () => { 17 | expect(() => assertName('')).to.throw( 18 | 'Expected name to be a non-empty string.', 19 | ); 20 | }); 21 | 22 | it('throws for names with invalid characters', () => { 23 | expect(() => assertName('>--()-->')).to.throw( 24 | 'Names must only contain [_a-zA-Z0-9] but ">--()-->" does not.', 25 | ); 26 | }); 27 | 28 | it('throws for names starting with invalid characters', () => { 29 | expect(() => assertName('42MeaningsOfLife')).to.throw( 30 | 'Names must start with [_a-zA-Z] but "42MeaningsOfLife" does not.', 31 | ); 32 | }); 33 | }); 34 | 35 | describe('assertEnumValueName', () => { 36 | it('passthrough valid name', () => { 37 | expect(assertEnumValueName('_ValidName123')).to.equal('_ValidName123'); 38 | }); 39 | 40 | it('throws on empty strings', () => { 41 | expect(() => assertEnumValueName('')).to.throw( 42 | 'Expected name to be a non-empty string.', 43 | ); 44 | }); 45 | 46 | it('throws for names with invalid characters', () => { 47 | expect(() => assertEnumValueName('>--()-->')).to.throw( 48 | 'Names must only contain [_a-zA-Z0-9] but ">--()-->" does not.', 49 | ); 50 | }); 51 | 52 | it('throws for names starting with invalid characters', () => { 53 | expect(() => assertEnumValueName('42MeaningsOfLife')).to.throw( 54 | 'Names must start with [_a-zA-Z] but "42MeaningsOfLife" does not.', 55 | ); 56 | }); 57 | 58 | it('throws for restricted names', () => { 59 | expect(() => assertEnumValueName('true')).to.throw( 60 | 'Enum values cannot be named: true', 61 | ); 62 | expect(() => assertEnumValueName('false')).to.throw( 63 | 'Enum values cannot be named: false', 64 | ); 65 | expect(() => assertEnumValueName('null')).to.throw( 66 | 'Enum values cannot be named: null', 67 | ); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/type/assertName.ts: -------------------------------------------------------------------------------- 1 | import { devAssert } from '../jsutils/devAssert'; 2 | 3 | import { GraphQLError } from '../error/GraphQLError'; 4 | 5 | import { isNameContinue, isNameStart } from '../language/characterClasses'; 6 | 7 | /** 8 | * Upholds the spec rules about naming. 9 | */ 10 | export function assertName(name: string): string { 11 | devAssert(name != null, 'Must provide name.'); 12 | devAssert(typeof name === 'string', 'Expected name to be a string.'); 13 | 14 | if (name.length === 0) { 15 | throw new GraphQLError('Expected name to be a non-empty string.'); 16 | } 17 | 18 | for (let i = 1; i < name.length; ++i) { 19 | if (!isNameContinue(name.charCodeAt(i))) { 20 | throw new GraphQLError( 21 | `Names must only contain [_a-zA-Z0-9] but "${name}" does not.`, 22 | ); 23 | } 24 | } 25 | 26 | if (!isNameStart(name.charCodeAt(0))) { 27 | throw new GraphQLError( 28 | `Names must start with [_a-zA-Z] but "${name}" does not.`, 29 | ); 30 | } 31 | 32 | return name; 33 | } 34 | 35 | /** 36 | * Upholds the spec rules about naming enum values. 37 | * 38 | * @internal 39 | */ 40 | export function assertEnumValueName(name: string): string { 41 | if (name === 'true' || name === 'false' || name === 'null') { 42 | throw new GraphQLError(`Enum values cannot be named: ${name}`); 43 | } 44 | return assertName(name); 45 | } 46 | -------------------------------------------------------------------------------- /src/utilities/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL Utilities 2 | 3 | The `graphql/utilities` module contains common useful computations to use with 4 | the GraphQL language and type objects. 5 | 6 | ```js 7 | import { ... } from 'graphql/utilities'; // ES6 8 | var GraphQLUtilities = require('graphql/utilities'); // CommonJS 9 | ``` 10 | -------------------------------------------------------------------------------- /src/utilities/__tests__/concatAST-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { dedent } from '../../__testUtils__/dedent'; 5 | 6 | import { parse } from '../../language/parser'; 7 | import { print } from '../../language/printer'; 8 | import { Source } from '../../language/source'; 9 | 10 | import { concatAST } from '../concatAST'; 11 | 12 | describe('concatAST', () => { 13 | it('concatenates two ASTs together', () => { 14 | const sourceA = new Source(` 15 | { a, b, ...Frag } 16 | `); 17 | 18 | const sourceB = new Source(` 19 | fragment Frag on T { 20 | c 21 | } 22 | `); 23 | 24 | const astA = parse(sourceA); 25 | const astB = parse(sourceB); 26 | const astC = concatAST([astA, astB]); 27 | 28 | expect(print(astC)).to.equal(dedent` 29 | { 30 | a 31 | b 32 | ...Frag 33 | } 34 | 35 | fragment Frag on T { 36 | c 37 | } 38 | `); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/utilities/__tests__/getOperationAST-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { parse } from '../../language/parser'; 5 | 6 | import { getOperationAST } from '../getOperationAST'; 7 | 8 | describe('getOperationAST', () => { 9 | it('Gets an operation from a simple document', () => { 10 | const doc = parse('{ field }'); 11 | expect(getOperationAST(doc)).to.equal(doc.definitions[0]); 12 | }); 13 | 14 | it('Gets an operation from a document with named op (mutation)', () => { 15 | const doc = parse('mutation Test { field }'); 16 | expect(getOperationAST(doc)).to.equal(doc.definitions[0]); 17 | }); 18 | 19 | it('Gets an operation from a document with named op (subscription)', () => { 20 | const doc = parse('subscription Test { field }'); 21 | expect(getOperationAST(doc)).to.equal(doc.definitions[0]); 22 | }); 23 | 24 | it('Does not get missing operation', () => { 25 | const doc = parse('type Foo { field: String }'); 26 | expect(getOperationAST(doc)).to.equal(null); 27 | }); 28 | 29 | it('Does not get ambiguous unnamed operation', () => { 30 | const doc = parse(` 31 | { field } 32 | mutation Test { field } 33 | subscription TestSub { field } 34 | `); 35 | expect(getOperationAST(doc)).to.equal(null); 36 | }); 37 | 38 | it('Does not get ambiguous named operation', () => { 39 | const doc = parse(` 40 | query TestQ { field } 41 | mutation TestM { field } 42 | subscription TestS { field } 43 | `); 44 | expect(getOperationAST(doc)).to.equal(null); 45 | }); 46 | 47 | it('Does not get misnamed operation', () => { 48 | const doc = parse(` 49 | { field } 50 | 51 | query TestQ { field } 52 | mutation TestM { field } 53 | subscription TestS { field } 54 | `); 55 | expect(getOperationAST(doc, 'Unknown')).to.equal(null); 56 | }); 57 | 58 | it('Gets named operation', () => { 59 | const doc = parse(` 60 | query TestQ { field } 61 | mutation TestM { field } 62 | subscription TestS { field } 63 | `); 64 | expect(getOperationAST(doc, 'TestQ')).to.equal(doc.definitions[0]); 65 | expect(getOperationAST(doc, 'TestM')).to.equal(doc.definitions[1]); 66 | expect(getOperationAST(doc, 'TestS')).to.equal(doc.definitions[2]); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/utilities/__tests__/introspectionFromSchema-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { dedent } from '../../__testUtils__/dedent'; 5 | 6 | import { GraphQLObjectType } from '../../type/definition'; 7 | import { GraphQLString } from '../../type/scalars'; 8 | import { GraphQLSchema } from '../../type/schema'; 9 | 10 | import { buildClientSchema } from '../buildClientSchema'; 11 | import type { IntrospectionQuery } from '../getIntrospectionQuery'; 12 | import { introspectionFromSchema } from '../introspectionFromSchema'; 13 | import { printSchema } from '../printSchema'; 14 | 15 | function introspectionToSDL(introspection: IntrospectionQuery): string { 16 | return printSchema(buildClientSchema(introspection)); 17 | } 18 | 19 | describe('introspectionFromSchema', () => { 20 | const schema = new GraphQLSchema({ 21 | description: 'This is a simple schema', 22 | query: new GraphQLObjectType({ 23 | name: 'Simple', 24 | description: 'This is a simple type', 25 | fields: { 26 | string: { 27 | type: GraphQLString, 28 | description: 'This is a string field', 29 | }, 30 | }, 31 | }), 32 | }); 33 | 34 | it('converts a simple schema', () => { 35 | const introspection = introspectionFromSchema(schema); 36 | 37 | expect(introspectionToSDL(introspection)).to.deep.equal(dedent` 38 | """This is a simple schema""" 39 | schema { 40 | query: Simple 41 | } 42 | 43 | """This is a simple type""" 44 | type Simple { 45 | """This is a string field""" 46 | string: String 47 | } 48 | `); 49 | }); 50 | 51 | it('converts a simple schema without descriptions', () => { 52 | const introspection = introspectionFromSchema(schema, { 53 | descriptions: false, 54 | }); 55 | 56 | expect(introspectionToSDL(introspection)).to.deep.equal(dedent` 57 | schema { 58 | query: Simple 59 | } 60 | 61 | type Simple { 62 | string: String 63 | } 64 | `); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /src/utilities/__tests__/sortValueNode-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { parseValue } from '../../language/parser'; 5 | import { print } from '../../language/printer'; 6 | 7 | import { sortValueNode } from '../sortValueNode'; 8 | 9 | describe('sortValueNode', () => { 10 | function expectSortedValue(source: string) { 11 | return expect(print(sortValueNode(parseValue(source)))); 12 | } 13 | 14 | it('do not change non-object values', () => { 15 | expectSortedValue('1').to.equal('1'); 16 | expectSortedValue('3.14').to.equal('3.14'); 17 | expectSortedValue('null').to.equal('null'); 18 | expectSortedValue('true').to.equal('true'); 19 | expectSortedValue('false').to.equal('false'); 20 | expectSortedValue('"cba"').to.equal('"cba"'); 21 | expectSortedValue('"""cba"""').to.equal('"""cba"""'); 22 | expectSortedValue('[1, 3.14, null, false, "cba"]').to.equal( 23 | '[1, 3.14, null, false, "cba"]', 24 | ); 25 | expectSortedValue('[[1, 3.14, null, false, "cba"]]').to.equal( 26 | '[[1, 3.14, null, false, "cba"]]', 27 | ); 28 | }); 29 | 30 | it('sort input object fields', () => { 31 | expectSortedValue('{ b: 2, a: 1 }').to.equal('{a: 1, b: 2}'); 32 | expectSortedValue('{ a: { c: 3, b: 2 } }').to.equal('{a: {b: 2, c: 3}}'); 33 | expectSortedValue('[{ b: 2, a: 1 }, { d: 4, c: 3}]').to.equal( 34 | '[{a: 1, b: 2}, {c: 3, d: 4}]', 35 | ); 36 | expectSortedValue( 37 | '{ b: { g: 7, f: 6 }, c: 3 , a: { d: 4, e: 5 } }', 38 | ).to.equal('{a: {d: 4, e: 5}, b: {f: 6, g: 7}, c: 3}'); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/utilities/__tests__/stripIgnoredCharacters-fuzz.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | 3 | import { dedent } from '../../__testUtils__/dedent'; 4 | import { genFuzzStrings } from '../../__testUtils__/genFuzzStrings'; 5 | import { inspectStr } from '../../__testUtils__/inspectStr'; 6 | 7 | import { invariant } from '../../jsutils/invariant'; 8 | 9 | import { Lexer } from '../../language/lexer'; 10 | import { Source } from '../../language/source'; 11 | 12 | import { stripIgnoredCharacters } from '../stripIgnoredCharacters'; 13 | 14 | function lexValue(str: string) { 15 | const lexer = new Lexer(new Source(str)); 16 | const value = lexer.advance().value; 17 | 18 | invariant(lexer.advance().kind === '', 'Expected EOF'); 19 | return value; 20 | } 21 | 22 | describe('stripIgnoredCharacters', () => { 23 | it('strips ignored characters inside random block strings', () => { 24 | // Testing with length >7 is taking exponentially more time. However it is 25 | // highly recommended to test with increased limit if you make any change. 26 | for (const fuzzStr of genFuzzStrings({ 27 | allowedChars: ['\n', '\t', ' ', '"', 'a', '\\'], 28 | maxLength: 7, 29 | })) { 30 | const testStr = '"""' + fuzzStr + '"""'; 31 | 32 | let testValue; 33 | try { 34 | testValue = lexValue(testStr); 35 | } catch (e) { 36 | continue; // skip invalid values 37 | } 38 | 39 | const strippedValue = lexValue(stripIgnoredCharacters(testStr)); 40 | 41 | invariant( 42 | testValue === strippedValue, 43 | dedent` 44 | Expected lexValue(stripIgnoredCharacters(${inspectStr(testStr)})) 45 | to equal ${inspectStr(testValue)} 46 | but got ${inspectStr(strippedValue)} 47 | `, 48 | ); 49 | } 50 | }).timeout(20000); 51 | }); 52 | -------------------------------------------------------------------------------- /src/utilities/__tests__/valueFromASTUntyped-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import type { Maybe } from '../../jsutils/Maybe'; 5 | import type { ObjMap } from '../../jsutils/ObjMap'; 6 | 7 | import { parseValue } from '../../language/parser'; 8 | 9 | import { valueFromASTUntyped } from '../valueFromASTUntyped'; 10 | 11 | describe('valueFromASTUntyped', () => { 12 | function expectValueFrom( 13 | valueText: string, 14 | variables?: Maybe>, 15 | ) { 16 | const ast = parseValue(valueText); 17 | const value = valueFromASTUntyped(ast, variables); 18 | return expect(value); 19 | } 20 | 21 | it('parses simple values', () => { 22 | expectValueFrom('null').to.equal(null); 23 | expectValueFrom('true').to.equal(true); 24 | expectValueFrom('false').to.equal(false); 25 | expectValueFrom('123').to.equal(123); 26 | expectValueFrom('123.456').to.equal(123.456); 27 | expectValueFrom('"abc123"').to.equal('abc123'); 28 | }); 29 | 30 | it('parses lists of values', () => { 31 | expectValueFrom('[true, false]').to.deep.equal([true, false]); 32 | expectValueFrom('[true, 123.45]').to.deep.equal([true, 123.45]); 33 | expectValueFrom('[true, null]').to.deep.equal([true, null]); 34 | expectValueFrom('[true, ["foo", 1.2]]').to.deep.equal([true, ['foo', 1.2]]); 35 | }); 36 | 37 | it('parses input objects', () => { 38 | expectValueFrom('{ int: 123, bool: false }').to.deep.equal({ 39 | int: 123, 40 | bool: false, 41 | }); 42 | expectValueFrom('{ foo: [ { bar: "baz"} ] }').to.deep.equal({ 43 | foo: [{ bar: 'baz' }], 44 | }); 45 | }); 46 | 47 | it('parses enum values as plain strings', () => { 48 | expectValueFrom('TEST_ENUM_VALUE').to.equal('TEST_ENUM_VALUE'); 49 | expectValueFrom('[TEST_ENUM_VALUE]').to.deep.equal(['TEST_ENUM_VALUE']); 50 | }); 51 | 52 | it('parses variables', () => { 53 | expectValueFrom('$testVariable', { testVariable: 'foo' }).to.equal('foo'); 54 | expectValueFrom('[$testVariable]', { testVariable: 'foo' }).to.deep.equal([ 55 | 'foo', 56 | ]); 57 | expectValueFrom('{a:[$testVariable]}', { 58 | testVariable: 'foo', 59 | }).to.deep.equal({ a: ['foo'] }); 60 | expectValueFrom('$testVariable', { testVariable: null }).to.equal(null); 61 | expectValueFrom('$testVariable', { testVariable: NaN }).to.satisfy( 62 | Number.isNaN, 63 | ); 64 | expectValueFrom('$testVariable', {}).to.equal(undefined); 65 | expectValueFrom('$testVariable', null).to.equal(undefined); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /src/utilities/assertValidName.ts: -------------------------------------------------------------------------------- 1 | import { devAssert } from '../jsutils/devAssert'; 2 | 3 | import { GraphQLError } from '../error/GraphQLError'; 4 | 5 | import { assertName } from '../type/assertName'; 6 | 7 | /* c8 ignore start */ 8 | /** 9 | * Upholds the spec rules about naming. 10 | * @deprecated Please use `assertName` instead. Will be removed in v17 11 | */ 12 | export function assertValidName(name: string): string { 13 | const error = isValidNameError(name); 14 | if (error) { 15 | throw error; 16 | } 17 | return name; 18 | } 19 | 20 | /** 21 | * Returns an Error if a name is invalid. 22 | * @deprecated Please use `assertName` instead. Will be removed in v17 23 | */ 24 | export function isValidNameError(name: string): GraphQLError | undefined { 25 | devAssert(typeof name === 'string', 'Expected name to be a string.'); 26 | 27 | if (name.startsWith('__')) { 28 | return new GraphQLError( 29 | `Name "${name}" must not begin with "__", which is reserved by GraphQL introspection.`, 30 | ); 31 | } 32 | 33 | try { 34 | assertName(name); 35 | } catch (error) { 36 | return error; 37 | } 38 | } 39 | /* c8 ignore stop */ 40 | -------------------------------------------------------------------------------- /src/utilities/concatAST.ts: -------------------------------------------------------------------------------- 1 | import type { DefinitionNode, DocumentNode } from '../language/ast'; 2 | import { Kind } from '../language/kinds'; 3 | 4 | /** 5 | * Provided a collection of ASTs, presumably each from different files, 6 | * concatenate the ASTs together into batched AST, useful for validating many 7 | * GraphQL source files which together represent one conceptual application. 8 | */ 9 | export function concatAST( 10 | documents: ReadonlyArray, 11 | ): DocumentNode { 12 | const definitions: Array = []; 13 | for (const doc of documents) { 14 | definitions.push(...doc.definitions); 15 | } 16 | return { kind: Kind.DOCUMENT, definitions }; 17 | } 18 | -------------------------------------------------------------------------------- /src/utilities/getOperationAST.ts: -------------------------------------------------------------------------------- 1 | import type { Maybe } from '../jsutils/Maybe'; 2 | 3 | import type { DocumentNode, OperationDefinitionNode } from '../language/ast'; 4 | import { Kind } from '../language/kinds'; 5 | 6 | /** 7 | * Returns an operation AST given a document AST and optionally an operation 8 | * name. If a name is not provided, an operation is only returned if only one is 9 | * provided in the document. 10 | */ 11 | export function getOperationAST( 12 | documentAST: DocumentNode, 13 | operationName?: Maybe, 14 | ): Maybe { 15 | let operation = null; 16 | for (const definition of documentAST.definitions) { 17 | if (definition.kind === Kind.OPERATION_DEFINITION) { 18 | if (operationName == null) { 19 | // If no operation name was provided, only return an Operation if there 20 | // is one defined in the document. Upon encountering the second, return 21 | // null. 22 | if (operation) { 23 | return null; 24 | } 25 | operation = definition; 26 | } else if (definition.name?.value === operationName) { 27 | return definition; 28 | } 29 | } 30 | } 31 | return operation; 32 | } 33 | -------------------------------------------------------------------------------- /src/utilities/getOperationRootType.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../error/GraphQLError'; 2 | 3 | import type { 4 | OperationDefinitionNode, 5 | OperationTypeDefinitionNode, 6 | } from '../language/ast'; 7 | 8 | import type { GraphQLObjectType } from '../type/definition'; 9 | import type { GraphQLSchema } from '../type/schema'; 10 | 11 | /** 12 | * Extracts the root type of the operation from the schema. 13 | * 14 | * @deprecated Please use `GraphQLSchema.getRootType` instead. Will be removed in v17 15 | */ 16 | export function getOperationRootType( 17 | schema: GraphQLSchema, 18 | operation: OperationDefinitionNode | OperationTypeDefinitionNode, 19 | ): GraphQLObjectType { 20 | if (operation.operation === 'query') { 21 | const queryType = schema.getQueryType(); 22 | if (!queryType) { 23 | throw new GraphQLError( 24 | 'Schema does not define the required query root type.', 25 | { nodes: operation }, 26 | ); 27 | } 28 | return queryType; 29 | } 30 | 31 | if (operation.operation === 'mutation') { 32 | const mutationType = schema.getMutationType(); 33 | if (!mutationType) { 34 | throw new GraphQLError('Schema is not configured for mutations.', { 35 | nodes: operation, 36 | }); 37 | } 38 | return mutationType; 39 | } 40 | 41 | if (operation.operation === 'subscription') { 42 | const subscriptionType = schema.getSubscriptionType(); 43 | if (!subscriptionType) { 44 | throw new GraphQLError('Schema is not configured for subscriptions.', { 45 | nodes: operation, 46 | }); 47 | } 48 | return subscriptionType; 49 | } 50 | 51 | throw new GraphQLError( 52 | 'Can only have query, mutation and subscription operations.', 53 | { nodes: operation }, 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/utilities/introspectionFromSchema.ts: -------------------------------------------------------------------------------- 1 | import { invariant } from '../jsutils/invariant'; 2 | 3 | import { parse } from '../language/parser'; 4 | 5 | import type { GraphQLSchema } from '../type/schema'; 6 | 7 | import { executeSync } from '../execution/execute'; 8 | 9 | import type { 10 | IntrospectionOptions, 11 | IntrospectionQuery, 12 | } from './getIntrospectionQuery'; 13 | import { getIntrospectionQuery } from './getIntrospectionQuery'; 14 | 15 | /** 16 | * Build an IntrospectionQuery from a GraphQLSchema 17 | * 18 | * IntrospectionQuery is useful for utilities that care about type and field 19 | * relationships, but do not need to traverse through those relationships. 20 | * 21 | * This is the inverse of buildClientSchema. The primary use case is outside 22 | * of the server context, for instance when doing schema comparisons. 23 | */ 24 | export function introspectionFromSchema( 25 | schema: GraphQLSchema, 26 | options?: IntrospectionOptions, 27 | ): IntrospectionQuery { 28 | const optionsWithDefaults = { 29 | specifiedByUrl: true, 30 | directiveIsRepeatable: true, 31 | schemaDescription: true, 32 | inputValueDeprecation: true, 33 | oneOf: true, 34 | ...options, 35 | }; 36 | 37 | const document = parse(getIntrospectionQuery(optionsWithDefaults)); 38 | const result = executeSync({ schema, document }); 39 | invariant(!result.errors && result.data); 40 | return result.data as any; 41 | } 42 | -------------------------------------------------------------------------------- /src/utilities/sortValueNode.ts: -------------------------------------------------------------------------------- 1 | import { naturalCompare } from '../jsutils/naturalCompare'; 2 | 3 | import type { ObjectFieldNode, ValueNode } from '../language/ast'; 4 | import { Kind } from '../language/kinds'; 5 | 6 | /** 7 | * Sort ValueNode. 8 | * 9 | * This function returns a sorted copy of the given ValueNode. 10 | * 11 | * @internal 12 | */ 13 | export function sortValueNode(valueNode: ValueNode): ValueNode { 14 | switch (valueNode.kind) { 15 | case Kind.OBJECT: 16 | return { 17 | ...valueNode, 18 | fields: sortFields(valueNode.fields), 19 | }; 20 | case Kind.LIST: 21 | return { 22 | ...valueNode, 23 | values: valueNode.values.map(sortValueNode), 24 | }; 25 | case Kind.INT: 26 | case Kind.FLOAT: 27 | case Kind.STRING: 28 | case Kind.BOOLEAN: 29 | case Kind.NULL: 30 | case Kind.ENUM: 31 | case Kind.VARIABLE: 32 | return valueNode; 33 | } 34 | } 35 | 36 | function sortFields( 37 | fields: ReadonlyArray, 38 | ): Array { 39 | return fields 40 | .map((fieldNode) => ({ 41 | ...fieldNode, 42 | value: sortValueNode(fieldNode.value), 43 | })) 44 | .sort((fieldA, fieldB) => 45 | naturalCompare(fieldA.name.value, fieldB.name.value), 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/utilities/typeFromAST.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ListTypeNode, 3 | NamedTypeNode, 4 | NonNullTypeNode, 5 | TypeNode, 6 | } from '../language/ast'; 7 | import { Kind } from '../language/kinds'; 8 | 9 | import type { GraphQLNamedType, GraphQLType } from '../type/definition'; 10 | import { GraphQLList, GraphQLNonNull } from '../type/definition'; 11 | import type { GraphQLSchema } from '../type/schema'; 12 | 13 | /** 14 | * Given a Schema and an AST node describing a type, return a GraphQLType 15 | * definition which applies to that type. For example, if provided the parsed 16 | * AST node for `[User]`, a GraphQLList instance will be returned, containing 17 | * the type called "User" found in the schema. If a type called "User" is not 18 | * found in the schema, then undefined will be returned. 19 | */ 20 | export function typeFromAST( 21 | schema: GraphQLSchema, 22 | typeNode: NamedTypeNode, 23 | ): GraphQLNamedType | undefined; 24 | export function typeFromAST( 25 | schema: GraphQLSchema, 26 | typeNode: ListTypeNode, 27 | ): GraphQLList | undefined; 28 | export function typeFromAST( 29 | schema: GraphQLSchema, 30 | typeNode: NonNullTypeNode, 31 | ): GraphQLNonNull | undefined; 32 | export function typeFromAST( 33 | schema: GraphQLSchema, 34 | typeNode: TypeNode, 35 | ): GraphQLType | undefined; 36 | export function typeFromAST( 37 | schema: GraphQLSchema, 38 | typeNode: TypeNode, 39 | ): GraphQLType | undefined { 40 | switch (typeNode.kind) { 41 | case Kind.LIST_TYPE: { 42 | const innerType = typeFromAST(schema, typeNode.type); 43 | return innerType && new GraphQLList(innerType); 44 | } 45 | case Kind.NON_NULL_TYPE: { 46 | const innerType = typeFromAST(schema, typeNode.type); 47 | return innerType && new GraphQLNonNull(innerType); 48 | } 49 | case Kind.NAMED_TYPE: 50 | return schema.getType(typeNode.name.value); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/utilities/typedQueryDocumentNode.ts: -------------------------------------------------------------------------------- 1 | import type { DocumentNode, ExecutableDefinitionNode } from '../language/ast'; 2 | /** 3 | * Wrapper type that contains DocumentNode and types that can be deduced from it. 4 | */ 5 | export interface TypedQueryDocumentNode< 6 | TResponseData = { [key: string]: any }, 7 | TRequestVariables = { [key: string]: any }, 8 | > extends DocumentNode { 9 | readonly definitions: ReadonlyArray; 10 | // FIXME: remove once TS implements proper way to enforce nominal typing 11 | /** 12 | * This type is used to ensure that the variables you pass in to the query are assignable to Variables 13 | * and that the Result is assignable to whatever you pass your result to. The method is never actually 14 | * implemented, but the type is valid because we list it as optional 15 | */ 16 | __ensureTypesOfVariablesAndResultMatching?: ( 17 | variables: TRequestVariables, 18 | ) => TResponseData; 19 | } 20 | -------------------------------------------------------------------------------- /src/utilities/valueFromASTUntyped.ts: -------------------------------------------------------------------------------- 1 | import { keyValMap } from '../jsutils/keyValMap'; 2 | import type { Maybe } from '../jsutils/Maybe'; 3 | import type { ObjMap } from '../jsutils/ObjMap'; 4 | 5 | import type { ValueNode } from '../language/ast'; 6 | import { Kind } from '../language/kinds'; 7 | 8 | /** 9 | * Produces a JavaScript value given a GraphQL Value AST. 10 | * 11 | * Unlike `valueFromAST()`, no type is provided. The resulting JavaScript value 12 | * will reflect the provided GraphQL value AST. 13 | * 14 | * | GraphQL Value | JavaScript Value | 15 | * | -------------------- | ---------------- | 16 | * | Input Object | Object | 17 | * | List | Array | 18 | * | Boolean | Boolean | 19 | * | String / Enum | String | 20 | * | Int / Float | Number | 21 | * | Null | null | 22 | * 23 | */ 24 | export function valueFromASTUntyped( 25 | valueNode: ValueNode, 26 | variables?: Maybe>, 27 | ): unknown { 28 | switch (valueNode.kind) { 29 | case Kind.NULL: 30 | return null; 31 | case Kind.INT: 32 | return parseInt(valueNode.value, 10); 33 | case Kind.FLOAT: 34 | return parseFloat(valueNode.value); 35 | case Kind.STRING: 36 | case Kind.ENUM: 37 | case Kind.BOOLEAN: 38 | return valueNode.value; 39 | case Kind.LIST: 40 | return valueNode.values.map((node) => 41 | valueFromASTUntyped(node, variables), 42 | ); 43 | case Kind.OBJECT: 44 | return keyValMap( 45 | valueNode.fields, 46 | (field) => field.name.value, 47 | (field) => valueFromASTUntyped(field.value, variables), 48 | ); 49 | case Kind.VARIABLE: 50 | return variables?.[valueNode.name.value]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/validation/README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL Validation 2 | 3 | The `graphql/validation` module fulfills the Validation phase of fulfilling a 4 | GraphQL result. 5 | 6 | ```js 7 | import { validate } from 'graphql/validation'; // ES6 8 | var GraphQLValidator = require('graphql/validation'); // CommonJS 9 | ``` 10 | -------------------------------------------------------------------------------- /src/validation/__tests__/ExecutableDefinitionsRule-test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | 3 | import { ExecutableDefinitionsRule } from '../rules/ExecutableDefinitionsRule'; 4 | 5 | import { expectValidationErrors } from './harness'; 6 | 7 | function expectErrors(queryStr: string) { 8 | return expectValidationErrors(ExecutableDefinitionsRule, queryStr); 9 | } 10 | 11 | function expectValid(queryStr: string) { 12 | expectErrors(queryStr).toDeepEqual([]); 13 | } 14 | 15 | describe('Validate: Executable definitions', () => { 16 | it('with only operation', () => { 17 | expectValid(` 18 | query Foo { 19 | dog { 20 | name 21 | } 22 | } 23 | `); 24 | }); 25 | 26 | it('with operation and fragment', () => { 27 | expectValid(` 28 | query Foo { 29 | dog { 30 | name 31 | ...Frag 32 | } 33 | } 34 | 35 | fragment Frag on Dog { 36 | name 37 | } 38 | `); 39 | }); 40 | 41 | it('with type definition', () => { 42 | expectErrors(` 43 | query Foo { 44 | dog { 45 | name 46 | } 47 | } 48 | 49 | type Cow { 50 | name: String 51 | } 52 | 53 | extend type Dog { 54 | color: String 55 | } 56 | `).toDeepEqual([ 57 | { 58 | message: 'The "Cow" definition is not executable.', 59 | locations: [{ line: 8, column: 7 }], 60 | }, 61 | { 62 | message: 'The "Dog" definition is not executable.', 63 | locations: [{ line: 12, column: 7 }], 64 | }, 65 | ]); 66 | }); 67 | 68 | it('with schema definition', () => { 69 | expectErrors(` 70 | schema { 71 | query: Query 72 | } 73 | 74 | type Query { 75 | test: String 76 | } 77 | 78 | extend schema @directive 79 | `).toDeepEqual([ 80 | { 81 | message: 'The schema definition is not executable.', 82 | locations: [{ line: 2, column: 7 }], 83 | }, 84 | { 85 | message: 'The "Query" definition is not executable.', 86 | locations: [{ line: 6, column: 7 }], 87 | }, 88 | { 89 | message: 'The schema definition is not executable.', 90 | locations: [{ line: 10, column: 7 }], 91 | }, 92 | ]); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /src/validation/__tests__/KnownFragmentNamesRule-test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | 3 | import { KnownFragmentNamesRule } from '../rules/KnownFragmentNamesRule'; 4 | 5 | import { expectValidationErrors } from './harness'; 6 | 7 | function expectErrors(queryStr: string) { 8 | return expectValidationErrors(KnownFragmentNamesRule, queryStr); 9 | } 10 | 11 | function expectValid(queryStr: string) { 12 | expectErrors(queryStr).toDeepEqual([]); 13 | } 14 | 15 | describe('Validate: Known fragment names', () => { 16 | it('known fragment names are valid', () => { 17 | expectValid(` 18 | { 19 | human(id: 4) { 20 | ...HumanFields1 21 | ... on Human { 22 | ...HumanFields2 23 | } 24 | ... { 25 | name 26 | } 27 | } 28 | } 29 | fragment HumanFields1 on Human { 30 | name 31 | ...HumanFields3 32 | } 33 | fragment HumanFields2 on Human { 34 | name 35 | } 36 | fragment HumanFields3 on Human { 37 | name 38 | } 39 | `); 40 | }); 41 | 42 | it('unknown fragment names are invalid', () => { 43 | expectErrors(` 44 | { 45 | human(id: 4) { 46 | ...UnknownFragment1 47 | ... on Human { 48 | ...UnknownFragment2 49 | } 50 | } 51 | } 52 | fragment HumanFields on Human { 53 | name 54 | ...UnknownFragment3 55 | } 56 | `).toDeepEqual([ 57 | { 58 | message: 'Unknown fragment "UnknownFragment1".', 59 | locations: [{ line: 4, column: 14 }], 60 | }, 61 | { 62 | message: 'Unknown fragment "UnknownFragment2".', 63 | locations: [{ line: 6, column: 16 }], 64 | }, 65 | { 66 | message: 'Unknown fragment "UnknownFragment3".', 67 | locations: [{ line: 12, column: 12 }], 68 | }, 69 | ]); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /src/validation/__tests__/LoneAnonymousOperationRule-test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | 3 | import { LoneAnonymousOperationRule } from '../rules/LoneAnonymousOperationRule'; 4 | 5 | import { expectValidationErrors } from './harness'; 6 | 7 | function expectErrors(queryStr: string) { 8 | return expectValidationErrors(LoneAnonymousOperationRule, queryStr); 9 | } 10 | 11 | function expectValid(queryStr: string) { 12 | expectErrors(queryStr).toDeepEqual([]); 13 | } 14 | 15 | describe('Validate: Anonymous operation must be alone', () => { 16 | it('no operations', () => { 17 | expectValid(` 18 | fragment fragA on Type { 19 | field 20 | } 21 | `); 22 | }); 23 | 24 | it('one anon operation', () => { 25 | expectValid(` 26 | { 27 | field 28 | } 29 | `); 30 | }); 31 | 32 | it('multiple named operations', () => { 33 | expectValid(` 34 | query Foo { 35 | field 36 | } 37 | 38 | query Bar { 39 | field 40 | } 41 | `); 42 | }); 43 | 44 | it('anon operation with fragment', () => { 45 | expectValid(` 46 | { 47 | ...Foo 48 | } 49 | fragment Foo on Type { 50 | field 51 | } 52 | `); 53 | }); 54 | 55 | it('multiple anon operations', () => { 56 | expectErrors(` 57 | { 58 | fieldA 59 | } 60 | { 61 | fieldB 62 | } 63 | `).toDeepEqual([ 64 | { 65 | message: 'This anonymous operation must be the only defined operation.', 66 | locations: [{ line: 2, column: 7 }], 67 | }, 68 | { 69 | message: 'This anonymous operation must be the only defined operation.', 70 | locations: [{ line: 5, column: 7 }], 71 | }, 72 | ]); 73 | }); 74 | 75 | it('anon operation with a mutation', () => { 76 | expectErrors(` 77 | { 78 | fieldA 79 | } 80 | mutation Foo { 81 | fieldB 82 | } 83 | `).toDeepEqual([ 84 | { 85 | message: 'This anonymous operation must be the only defined operation.', 86 | locations: [{ line: 2, column: 7 }], 87 | }, 88 | ]); 89 | }); 90 | 91 | it('anon operation with a subscription', () => { 92 | expectErrors(` 93 | { 94 | fieldA 95 | } 96 | subscription Foo { 97 | fieldB 98 | } 99 | `).toDeepEqual([ 100 | { 101 | message: 'This anonymous operation must be the only defined operation.', 102 | locations: [{ line: 2, column: 7 }], 103 | }, 104 | ]); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /src/validation/__tests__/UniqueVariableNamesRule-test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | 3 | import { UniqueVariableNamesRule } from '../rules/UniqueVariableNamesRule'; 4 | 5 | import { expectValidationErrors } from './harness'; 6 | 7 | function expectErrors(queryStr: string) { 8 | return expectValidationErrors(UniqueVariableNamesRule, queryStr); 9 | } 10 | 11 | function expectValid(queryStr: string) { 12 | expectErrors(queryStr).toDeepEqual([]); 13 | } 14 | 15 | describe('Validate: Unique variable names', () => { 16 | it('unique variable names', () => { 17 | expectValid(` 18 | query A($x: Int, $y: String) { __typename } 19 | query B($x: String, $y: Int) { __typename } 20 | `); 21 | }); 22 | 23 | it('duplicate variable names', () => { 24 | expectErrors(` 25 | query A($x: Int, $x: Int, $x: String) { __typename } 26 | query B($x: String, $x: Int) { __typename } 27 | query C($x: Int, $x: Int) { __typename } 28 | `).toDeepEqual([ 29 | { 30 | message: 'There can be only one variable named "$x".', 31 | locations: [ 32 | { line: 2, column: 16 }, 33 | { line: 2, column: 25 }, 34 | { line: 2, column: 34 }, 35 | ], 36 | }, 37 | { 38 | message: 'There can be only one variable named "$x".', 39 | locations: [ 40 | { line: 3, column: 16 }, 41 | { line: 3, column: 28 }, 42 | ], 43 | }, 44 | { 45 | message: 'There can be only one variable named "$x".', 46 | locations: [ 47 | { line: 4, column: 16 }, 48 | { line: 4, column: 25 }, 49 | ], 50 | }, 51 | ]); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/validation/__tests__/ValidationContext-test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { describe, it } from 'mocha'; 3 | 4 | import { identityFunc } from '../../jsutils/identityFunc'; 5 | 6 | import { parse } from '../../language/parser'; 7 | 8 | import { GraphQLSchema } from '../../type/schema'; 9 | 10 | import { TypeInfo } from '../../utilities/TypeInfo'; 11 | 12 | import { 13 | ASTValidationContext, 14 | SDLValidationContext, 15 | ValidationContext, 16 | } from '../ValidationContext'; 17 | 18 | describe('ValidationContext', () => { 19 | it('can be Object.toStringified', () => { 20 | const schema = new GraphQLSchema({}); 21 | const typeInfo = new TypeInfo(schema); 22 | const ast = parse('{ foo }'); 23 | const onError = identityFunc; 24 | 25 | const astContext = new ASTValidationContext(ast, onError); 26 | expect(Object.prototype.toString.call(astContext)).to.equal( 27 | '[object ASTValidationContext]', 28 | ); 29 | 30 | const sdlContext = new SDLValidationContext(ast, schema, onError); 31 | expect(Object.prototype.toString.call(sdlContext)).to.equal( 32 | '[object SDLValidationContext]', 33 | ); 34 | 35 | const context = new ValidationContext(schema, ast, typeInfo, onError); 36 | expect(Object.prototype.toString.call(context)).to.equal( 37 | '[object ValidationContext]', 38 | ); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/validation/__tests__/VariablesAreInputTypesRule-test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'mocha'; 2 | 3 | import { VariablesAreInputTypesRule } from '../rules/VariablesAreInputTypesRule'; 4 | 5 | import { expectValidationErrors } from './harness'; 6 | 7 | function expectErrors(queryStr: string) { 8 | return expectValidationErrors(VariablesAreInputTypesRule, queryStr); 9 | } 10 | 11 | function expectValid(queryStr: string) { 12 | expectErrors(queryStr).toDeepEqual([]); 13 | } 14 | 15 | describe('Validate: Variables are input types', () => { 16 | it('unknown types are ignored', () => { 17 | expectValid(` 18 | query Foo($a: Unknown, $b: [[Unknown!]]!) { 19 | field(a: $a, b: $b) 20 | } 21 | `); 22 | }); 23 | 24 | it('input types are valid', () => { 25 | expectValid(` 26 | query Foo($a: String, $b: [Boolean!]!, $c: ComplexInput) { 27 | field(a: $a, b: $b, c: $c) 28 | } 29 | `); 30 | }); 31 | 32 | it('output types are invalid', () => { 33 | expectErrors(` 34 | query Foo($a: Dog, $b: [[CatOrDog!]]!, $c: Pet) { 35 | field(a: $a, b: $b, c: $c) 36 | } 37 | `).toDeepEqual([ 38 | { 39 | locations: [{ line: 2, column: 21 }], 40 | message: 'Variable "$a" cannot be non-input type "Dog".', 41 | }, 42 | { 43 | locations: [{ line: 2, column: 30 }], 44 | message: 'Variable "$b" cannot be non-input type "[[CatOrDog!]]!".', 45 | }, 46 | { 47 | locations: [{ line: 2, column: 50 }], 48 | message: 'Variable "$c" cannot be non-input type "Pet".', 49 | }, 50 | ]); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/validation/rules/ExecutableDefinitionsRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import { Kind } from '../../language/kinds'; 4 | import { isExecutableDefinitionNode } from '../../language/predicates'; 5 | import type { ASTVisitor } from '../../language/visitor'; 6 | 7 | import type { ASTValidationContext } from '../ValidationContext'; 8 | 9 | /** 10 | * Executable definitions 11 | * 12 | * A GraphQL document is only valid for execution if all definitions are either 13 | * operation or fragment definitions. 14 | * 15 | * See https://spec.graphql.org/draft/#sec-Executable-Definitions 16 | */ 17 | export function ExecutableDefinitionsRule( 18 | context: ASTValidationContext, 19 | ): ASTVisitor { 20 | return { 21 | Document(node) { 22 | for (const definition of node.definitions) { 23 | if (!isExecutableDefinitionNode(definition)) { 24 | const defName = 25 | definition.kind === Kind.SCHEMA_DEFINITION || 26 | definition.kind === Kind.SCHEMA_EXTENSION 27 | ? 'schema' 28 | : '"' + definition.name.value + '"'; 29 | context.reportError( 30 | new GraphQLError(`The ${defName} definition is not executable.`, { 31 | nodes: definition, 32 | }), 33 | ); 34 | } 35 | } 36 | return false; 37 | }, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/validation/rules/FragmentsOnCompositeTypesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import { print } from '../../language/printer'; 4 | import type { ASTVisitor } from '../../language/visitor'; 5 | 6 | import { isCompositeType } from '../../type/definition'; 7 | 8 | import { typeFromAST } from '../../utilities/typeFromAST'; 9 | 10 | import type { ValidationContext } from '../ValidationContext'; 11 | 12 | /** 13 | * Fragments on composite type 14 | * 15 | * Fragments use a type condition to determine if they apply, since fragments 16 | * can only be spread into a composite type (object, interface, or union), the 17 | * type condition must also be a composite type. 18 | * 19 | * See https://spec.graphql.org/draft/#sec-Fragments-On-Composite-Types 20 | */ 21 | export function FragmentsOnCompositeTypesRule( 22 | context: ValidationContext, 23 | ): ASTVisitor { 24 | return { 25 | InlineFragment(node) { 26 | const typeCondition = node.typeCondition; 27 | if (typeCondition) { 28 | const type = typeFromAST(context.getSchema(), typeCondition); 29 | if (type && !isCompositeType(type)) { 30 | const typeStr = print(typeCondition); 31 | context.reportError( 32 | new GraphQLError( 33 | `Fragment cannot condition on non composite type "${typeStr}".`, 34 | { nodes: typeCondition }, 35 | ), 36 | ); 37 | } 38 | } 39 | }, 40 | FragmentDefinition(node) { 41 | const type = typeFromAST(context.getSchema(), node.typeCondition); 42 | if (type && !isCompositeType(type)) { 43 | const typeStr = print(node.typeCondition); 44 | context.reportError( 45 | new GraphQLError( 46 | `Fragment "${node.name.value}" cannot condition on non composite type "${typeStr}".`, 47 | { nodes: node.typeCondition }, 48 | ), 49 | ); 50 | } 51 | }, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/validation/rules/KnownFragmentNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { ASTVisitor } from '../../language/visitor'; 4 | 5 | import type { ValidationContext } from '../ValidationContext'; 6 | 7 | /** 8 | * Known fragment names 9 | * 10 | * A GraphQL document is only valid if all `...Fragment` fragment spreads refer 11 | * to fragments defined in the same document. 12 | * 13 | * See https://spec.graphql.org/draft/#sec-Fragment-spread-target-defined 14 | */ 15 | export function KnownFragmentNamesRule(context: ValidationContext): ASTVisitor { 16 | return { 17 | FragmentSpread(node) { 18 | const fragmentName = node.name.value; 19 | const fragment = context.getFragment(fragmentName); 20 | if (!fragment) { 21 | context.reportError( 22 | new GraphQLError(`Unknown fragment "${fragmentName}".`, { 23 | nodes: node.name, 24 | }), 25 | ); 26 | } 27 | }, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /src/validation/rules/LoneAnonymousOperationRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import { Kind } from '../../language/kinds'; 4 | import type { ASTVisitor } from '../../language/visitor'; 5 | 6 | import type { ASTValidationContext } from '../ValidationContext'; 7 | 8 | /** 9 | * Lone anonymous operation 10 | * 11 | * A GraphQL document is only valid if when it contains an anonymous operation 12 | * (the query short-hand) that it contains only that one operation definition. 13 | * 14 | * See https://spec.graphql.org/draft/#sec-Lone-Anonymous-Operation 15 | */ 16 | export function LoneAnonymousOperationRule( 17 | context: ASTValidationContext, 18 | ): ASTVisitor { 19 | let operationCount = 0; 20 | return { 21 | Document(node) { 22 | operationCount = node.definitions.filter( 23 | (definition) => definition.kind === Kind.OPERATION_DEFINITION, 24 | ).length; 25 | }, 26 | OperationDefinition(node) { 27 | if (!node.name && operationCount > 1) { 28 | context.reportError( 29 | new GraphQLError( 30 | 'This anonymous operation must be the only defined operation.', 31 | { nodes: node }, 32 | ), 33 | ); 34 | } 35 | }, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/validation/rules/LoneSchemaDefinitionRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { ASTVisitor } from '../../language/visitor'; 4 | 5 | import type { SDLValidationContext } from '../ValidationContext'; 6 | 7 | /** 8 | * Lone Schema definition 9 | * 10 | * A GraphQL document is only valid if it contains only one schema definition. 11 | */ 12 | export function LoneSchemaDefinitionRule( 13 | context: SDLValidationContext, 14 | ): ASTVisitor { 15 | const oldSchema = context.getSchema(); 16 | const alreadyDefined = 17 | oldSchema?.astNode ?? 18 | oldSchema?.getQueryType() ?? 19 | oldSchema?.getMutationType() ?? 20 | oldSchema?.getSubscriptionType(); 21 | 22 | let schemaDefinitionsCount = 0; 23 | return { 24 | SchemaDefinition(node) { 25 | if (alreadyDefined) { 26 | context.reportError( 27 | new GraphQLError( 28 | 'Cannot define a new schema within a schema extension.', 29 | { nodes: node }, 30 | ), 31 | ); 32 | return; 33 | } 34 | 35 | if (schemaDefinitionsCount > 0) { 36 | context.reportError( 37 | new GraphQLError('Must provide only one schema definition.', { 38 | nodes: node, 39 | }), 40 | ); 41 | } 42 | ++schemaDefinitionsCount; 43 | }, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/validation/rules/NoUndefinedVariablesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { ASTVisitor } from '../../language/visitor'; 4 | 5 | import type { ValidationContext } from '../ValidationContext'; 6 | 7 | /** 8 | * No undefined variables 9 | * 10 | * A GraphQL operation is only valid if all variables encountered, both directly 11 | * and via fragment spreads, are defined by that operation. 12 | * 13 | * See https://spec.graphql.org/draft/#sec-All-Variable-Uses-Defined 14 | */ 15 | export function NoUndefinedVariablesRule( 16 | context: ValidationContext, 17 | ): ASTVisitor { 18 | let variableNameDefined = Object.create(null); 19 | 20 | return { 21 | OperationDefinition: { 22 | enter() { 23 | variableNameDefined = Object.create(null); 24 | }, 25 | leave(operation) { 26 | const usages = context.getRecursiveVariableUsages(operation); 27 | 28 | for (const { node } of usages) { 29 | const varName = node.name.value; 30 | if (variableNameDefined[varName] !== true) { 31 | context.reportError( 32 | new GraphQLError( 33 | operation.name 34 | ? `Variable "$${varName}" is not defined by operation "${operation.name.value}".` 35 | : `Variable "$${varName}" is not defined.`, 36 | { nodes: [node, operation] }, 37 | ), 38 | ); 39 | } 40 | } 41 | }, 42 | }, 43 | VariableDefinition(node) { 44 | variableNameDefined[node.variable.name.value] = true; 45 | }, 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/validation/rules/NoUnusedFragmentsRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { 4 | FragmentDefinitionNode, 5 | OperationDefinitionNode, 6 | } from '../../language/ast'; 7 | import type { ASTVisitor } from '../../language/visitor'; 8 | 9 | import type { ASTValidationContext } from '../ValidationContext'; 10 | 11 | /** 12 | * No unused fragments 13 | * 14 | * A GraphQL document is only valid if all fragment definitions are spread 15 | * within operations, or spread within other fragments spread within operations. 16 | * 17 | * See https://spec.graphql.org/draft/#sec-Fragments-Must-Be-Used 18 | */ 19 | export function NoUnusedFragmentsRule( 20 | context: ASTValidationContext, 21 | ): ASTVisitor { 22 | const operationDefs: Array = []; 23 | const fragmentDefs: Array = []; 24 | 25 | return { 26 | OperationDefinition(node) { 27 | operationDefs.push(node); 28 | return false; 29 | }, 30 | FragmentDefinition(node) { 31 | fragmentDefs.push(node); 32 | return false; 33 | }, 34 | Document: { 35 | leave() { 36 | const fragmentNameUsed = Object.create(null); 37 | for (const operation of operationDefs) { 38 | for (const fragment of context.getRecursivelyReferencedFragments( 39 | operation, 40 | )) { 41 | fragmentNameUsed[fragment.name.value] = true; 42 | } 43 | } 44 | 45 | for (const fragmentDef of fragmentDefs) { 46 | const fragName = fragmentDef.name.value; 47 | if (fragmentNameUsed[fragName] !== true) { 48 | context.reportError( 49 | new GraphQLError(`Fragment "${fragName}" is never used.`, { 50 | nodes: fragmentDef, 51 | }), 52 | ); 53 | } 54 | } 55 | }, 56 | }, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/validation/rules/NoUnusedVariablesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { VariableDefinitionNode } from '../../language/ast'; 4 | import type { ASTVisitor } from '../../language/visitor'; 5 | 6 | import type { ValidationContext } from '../ValidationContext'; 7 | 8 | /** 9 | * No unused variables 10 | * 11 | * A GraphQL operation is only valid if all variables defined by an operation 12 | * are used, either directly or within a spread fragment. 13 | * 14 | * See https://spec.graphql.org/draft/#sec-All-Variables-Used 15 | */ 16 | export function NoUnusedVariablesRule(context: ValidationContext): ASTVisitor { 17 | let variableDefs: Array = []; 18 | 19 | return { 20 | OperationDefinition: { 21 | enter() { 22 | variableDefs = []; 23 | }, 24 | leave(operation) { 25 | const variableNameUsed = Object.create(null); 26 | const usages = context.getRecursiveVariableUsages(operation); 27 | 28 | for (const { node } of usages) { 29 | variableNameUsed[node.name.value] = true; 30 | } 31 | 32 | for (const variableDef of variableDefs) { 33 | const variableName = variableDef.variable.name.value; 34 | if (variableNameUsed[variableName] !== true) { 35 | context.reportError( 36 | new GraphQLError( 37 | operation.name 38 | ? `Variable "$${variableName}" is never used in operation "${operation.name.value}".` 39 | : `Variable "$${variableName}" is never used.`, 40 | { nodes: variableDef }, 41 | ), 42 | ); 43 | } 44 | } 45 | }, 46 | }, 47 | VariableDefinition(def) { 48 | variableDefs.push(def); 49 | }, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/validation/rules/ScalarLeafsRule.ts: -------------------------------------------------------------------------------- 1 | import { inspect } from '../../jsutils/inspect'; 2 | 3 | import { GraphQLError } from '../../error/GraphQLError'; 4 | 5 | import type { FieldNode } from '../../language/ast'; 6 | import type { ASTVisitor } from '../../language/visitor'; 7 | 8 | import { getNamedType, isLeafType } from '../../type/definition'; 9 | 10 | import type { ValidationContext } from '../ValidationContext'; 11 | 12 | /** 13 | * Scalar leafs 14 | * 15 | * A GraphQL document is valid only if all leaf fields (fields without 16 | * sub selections) are of scalar or enum types. 17 | */ 18 | export function ScalarLeafsRule(context: ValidationContext): ASTVisitor { 19 | return { 20 | Field(node: FieldNode) { 21 | const type = context.getType(); 22 | const selectionSet = node.selectionSet; 23 | if (type) { 24 | if (isLeafType(getNamedType(type))) { 25 | if (selectionSet) { 26 | const fieldName = node.name.value; 27 | const typeStr = inspect(type); 28 | context.reportError( 29 | new GraphQLError( 30 | `Field "${fieldName}" must not have a selection since type "${typeStr}" has no subfields.`, 31 | { nodes: selectionSet }, 32 | ), 33 | ); 34 | } 35 | } else if (!selectionSet) { 36 | const fieldName = node.name.value; 37 | const typeStr = inspect(type); 38 | context.reportError( 39 | new GraphQLError( 40 | `Field "${fieldName}" of type "${typeStr}" must have a selection of subfields. Did you mean "${fieldName} { ... }"?`, 41 | { nodes: node }, 42 | ), 43 | ); 44 | } else if (selectionSet.selections.length === 0) { 45 | const fieldName = node.name.value; 46 | const typeStr = inspect(type); 47 | context.reportError( 48 | new GraphQLError( 49 | `Field "${fieldName}" of type "${typeStr}" must have at least one field selected.`, 50 | { nodes: node }, 51 | ), 52 | ); 53 | } 54 | } 55 | }, 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueArgumentNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { groupBy } from '../../jsutils/groupBy'; 2 | 3 | import { GraphQLError } from '../../error/GraphQLError'; 4 | 5 | import type { ArgumentNode } from '../../language/ast'; 6 | import type { ASTVisitor } from '../../language/visitor'; 7 | 8 | import type { ASTValidationContext } from '../ValidationContext'; 9 | 10 | /** 11 | * Unique argument names 12 | * 13 | * A GraphQL field or directive is only valid if all supplied arguments are 14 | * uniquely named. 15 | * 16 | * See https://spec.graphql.org/draft/#sec-Argument-Names 17 | */ 18 | export function UniqueArgumentNamesRule( 19 | context: ASTValidationContext, 20 | ): ASTVisitor { 21 | return { 22 | Field: checkArgUniqueness, 23 | Directive: checkArgUniqueness, 24 | }; 25 | 26 | function checkArgUniqueness(parentNode: { 27 | arguments?: ReadonlyArray; 28 | }) { 29 | // FIXME: https://github.com/graphql/graphql-js/issues/2203 30 | /* c8 ignore next */ 31 | const argumentNodes = parentNode.arguments ?? []; 32 | 33 | const seenArgs = groupBy(argumentNodes, (arg) => arg.name.value); 34 | 35 | for (const [argName, argNodes] of seenArgs) { 36 | if (argNodes.length > 1) { 37 | context.reportError( 38 | new GraphQLError( 39 | `There can be only one argument named "${argName}".`, 40 | { nodes: argNodes.map((node) => node.name) }, 41 | ), 42 | ); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueDirectiveNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { ASTVisitor } from '../../language/visitor'; 4 | 5 | import type { SDLValidationContext } from '../ValidationContext'; 6 | 7 | /** 8 | * Unique directive names 9 | * 10 | * A GraphQL document is only valid if all defined directives have unique names. 11 | */ 12 | export function UniqueDirectiveNamesRule( 13 | context: SDLValidationContext, 14 | ): ASTVisitor { 15 | const knownDirectiveNames = Object.create(null); 16 | const schema = context.getSchema(); 17 | 18 | return { 19 | DirectiveDefinition(node) { 20 | const directiveName = node.name.value; 21 | 22 | if (schema?.getDirective(directiveName)) { 23 | context.reportError( 24 | new GraphQLError( 25 | `Directive "@${directiveName}" already exists in the schema. It cannot be redefined.`, 26 | { nodes: node.name }, 27 | ), 28 | ); 29 | return; 30 | } 31 | 32 | if (knownDirectiveNames[directiveName]) { 33 | context.reportError( 34 | new GraphQLError( 35 | `There can be only one directive named "@${directiveName}".`, 36 | { nodes: [knownDirectiveNames[directiveName], node.name] }, 37 | ), 38 | ); 39 | } else { 40 | knownDirectiveNames[directiveName] = node.name; 41 | } 42 | 43 | return false; 44 | }, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueEnumValueNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { 4 | EnumTypeDefinitionNode, 5 | EnumTypeExtensionNode, 6 | } from '../../language/ast'; 7 | import type { ASTVisitor } from '../../language/visitor'; 8 | 9 | import { isEnumType } from '../../type/definition'; 10 | 11 | import type { SDLValidationContext } from '../ValidationContext'; 12 | 13 | /** 14 | * Unique enum value names 15 | * 16 | * A GraphQL enum type is only valid if all its values are uniquely named. 17 | */ 18 | export function UniqueEnumValueNamesRule( 19 | context: SDLValidationContext, 20 | ): ASTVisitor { 21 | const schema = context.getSchema(); 22 | const existingTypeMap = schema ? schema.getTypeMap() : Object.create(null); 23 | const knownValueNames = Object.create(null); 24 | 25 | return { 26 | EnumTypeDefinition: checkValueUniqueness, 27 | EnumTypeExtension: checkValueUniqueness, 28 | }; 29 | 30 | function checkValueUniqueness( 31 | node: EnumTypeDefinitionNode | EnumTypeExtensionNode, 32 | ) { 33 | const typeName = node.name.value; 34 | 35 | if (!knownValueNames[typeName]) { 36 | knownValueNames[typeName] = Object.create(null); 37 | } 38 | 39 | // FIXME: https://github.com/graphql/graphql-js/issues/2203 40 | /* c8 ignore next */ 41 | const valueNodes = node.values ?? []; 42 | const valueNames = knownValueNames[typeName]; 43 | 44 | for (const valueDef of valueNodes) { 45 | const valueName = valueDef.name.value; 46 | 47 | const existingType = existingTypeMap[typeName]; 48 | if (isEnumType(existingType) && existingType.getValue(valueName)) { 49 | context.reportError( 50 | new GraphQLError( 51 | `Enum value "${typeName}.${valueName}" already exists in the schema. It cannot also be defined in this type extension.`, 52 | { nodes: valueDef.name }, 53 | ), 54 | ); 55 | } else if (valueNames[valueName]) { 56 | context.reportError( 57 | new GraphQLError( 58 | `Enum value "${typeName}.${valueName}" can only be defined once.`, 59 | { nodes: [valueNames[valueName], valueDef.name] }, 60 | ), 61 | ); 62 | } else { 63 | valueNames[valueName] = valueDef.name; 64 | } 65 | } 66 | 67 | return false; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueFragmentNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { ASTVisitor } from '../../language/visitor'; 4 | 5 | import type { ASTValidationContext } from '../ValidationContext'; 6 | 7 | /** 8 | * Unique fragment names 9 | * 10 | * A GraphQL document is only valid if all defined fragments have unique names. 11 | * 12 | * See https://spec.graphql.org/draft/#sec-Fragment-Name-Uniqueness 13 | */ 14 | export function UniqueFragmentNamesRule( 15 | context: ASTValidationContext, 16 | ): ASTVisitor { 17 | const knownFragmentNames = Object.create(null); 18 | return { 19 | OperationDefinition: () => false, 20 | FragmentDefinition(node) { 21 | const fragmentName = node.name.value; 22 | if (knownFragmentNames[fragmentName]) { 23 | context.reportError( 24 | new GraphQLError( 25 | `There can be only one fragment named "${fragmentName}".`, 26 | { nodes: [knownFragmentNames[fragmentName], node.name] }, 27 | ), 28 | ); 29 | } else { 30 | knownFragmentNames[fragmentName] = node.name; 31 | } 32 | return false; 33 | }, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueInputFieldNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { invariant } from '../../jsutils/invariant'; 2 | import type { ObjMap } from '../../jsutils/ObjMap'; 3 | 4 | import { GraphQLError } from '../../error/GraphQLError'; 5 | 6 | import type { NameNode } from '../../language/ast'; 7 | import type { ASTVisitor } from '../../language/visitor'; 8 | 9 | import type { ASTValidationContext } from '../ValidationContext'; 10 | 11 | /** 12 | * Unique input field names 13 | * 14 | * A GraphQL input object value is only valid if all supplied fields are 15 | * uniquely named. 16 | * 17 | * See https://spec.graphql.org/draft/#sec-Input-Object-Field-Uniqueness 18 | */ 19 | export function UniqueInputFieldNamesRule( 20 | context: ASTValidationContext, 21 | ): ASTVisitor { 22 | const knownNameStack: Array> = []; 23 | let knownNames: ObjMap = Object.create(null); 24 | 25 | return { 26 | ObjectValue: { 27 | enter() { 28 | knownNameStack.push(knownNames); 29 | knownNames = Object.create(null); 30 | }, 31 | leave() { 32 | const prevKnownNames = knownNameStack.pop(); 33 | invariant(prevKnownNames); 34 | knownNames = prevKnownNames; 35 | }, 36 | }, 37 | ObjectField(node) { 38 | const fieldName = node.name.value; 39 | if (knownNames[fieldName]) { 40 | context.reportError( 41 | new GraphQLError( 42 | `There can be only one input field named "${fieldName}".`, 43 | { nodes: [knownNames[fieldName], node.name] }, 44 | ), 45 | ); 46 | } else { 47 | knownNames[fieldName] = node.name; 48 | } 49 | }, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueOperationNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { ASTVisitor } from '../../language/visitor'; 4 | 5 | import type { ASTValidationContext } from '../ValidationContext'; 6 | 7 | /** 8 | * Unique operation names 9 | * 10 | * A GraphQL document is only valid if all defined operations have unique names. 11 | * 12 | * See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness 13 | */ 14 | export function UniqueOperationNamesRule( 15 | context: ASTValidationContext, 16 | ): ASTVisitor { 17 | const knownOperationNames = Object.create(null); 18 | return { 19 | OperationDefinition(node) { 20 | const operationName = node.name; 21 | if (operationName) { 22 | if (knownOperationNames[operationName.value]) { 23 | context.reportError( 24 | new GraphQLError( 25 | `There can be only one operation named "${operationName.value}".`, 26 | { 27 | nodes: [ 28 | knownOperationNames[operationName.value], 29 | operationName, 30 | ], 31 | }, 32 | ), 33 | ); 34 | } else { 35 | knownOperationNames[operationName.value] = operationName; 36 | } 37 | } 38 | return false; 39 | }, 40 | FragmentDefinition: () => false, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueOperationTypesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { 4 | SchemaDefinitionNode, 5 | SchemaExtensionNode, 6 | } from '../../language/ast'; 7 | import type { ASTVisitor } from '../../language/visitor'; 8 | 9 | import type { SDLValidationContext } from '../ValidationContext'; 10 | 11 | /** 12 | * Unique operation types 13 | * 14 | * A GraphQL document is only valid if it has only one type per operation. 15 | */ 16 | export function UniqueOperationTypesRule( 17 | context: SDLValidationContext, 18 | ): ASTVisitor { 19 | const schema = context.getSchema(); 20 | const definedOperationTypes = Object.create(null); 21 | const existingOperationTypes = schema 22 | ? { 23 | query: schema.getQueryType(), 24 | mutation: schema.getMutationType(), 25 | subscription: schema.getSubscriptionType(), 26 | } 27 | : {}; 28 | 29 | return { 30 | SchemaDefinition: checkOperationTypes, 31 | SchemaExtension: checkOperationTypes, 32 | }; 33 | 34 | function checkOperationTypes( 35 | node: SchemaDefinitionNode | SchemaExtensionNode, 36 | ) { 37 | // See: https://github.com/graphql/graphql-js/issues/2203 38 | /* c8 ignore next */ 39 | const operationTypesNodes = node.operationTypes ?? []; 40 | 41 | for (const operationType of operationTypesNodes) { 42 | const operation = operationType.operation; 43 | const alreadyDefinedOperationType = definedOperationTypes[operation]; 44 | 45 | if (existingOperationTypes[operation]) { 46 | context.reportError( 47 | new GraphQLError( 48 | `Type for ${operation} already defined in the schema. It cannot be redefined.`, 49 | { nodes: operationType }, 50 | ), 51 | ); 52 | } else if (alreadyDefinedOperationType) { 53 | context.reportError( 54 | new GraphQLError( 55 | `There can be only one ${operation} type in schema.`, 56 | { nodes: [alreadyDefinedOperationType, operationType] }, 57 | ), 58 | ); 59 | } else { 60 | definedOperationTypes[operation] = operationType; 61 | } 62 | } 63 | 64 | return false; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueTypeNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { TypeDefinitionNode } from '../../language/ast'; 4 | import type { ASTVisitor } from '../../language/visitor'; 5 | 6 | import type { SDLValidationContext } from '../ValidationContext'; 7 | 8 | /** 9 | * Unique type names 10 | * 11 | * A GraphQL document is only valid if all defined types have unique names. 12 | */ 13 | export function UniqueTypeNamesRule(context: SDLValidationContext): ASTVisitor { 14 | const knownTypeNames = Object.create(null); 15 | const schema = context.getSchema(); 16 | 17 | return { 18 | ScalarTypeDefinition: checkTypeName, 19 | ObjectTypeDefinition: checkTypeName, 20 | InterfaceTypeDefinition: checkTypeName, 21 | UnionTypeDefinition: checkTypeName, 22 | EnumTypeDefinition: checkTypeName, 23 | InputObjectTypeDefinition: checkTypeName, 24 | }; 25 | 26 | function checkTypeName(node: TypeDefinitionNode) { 27 | const typeName = node.name.value; 28 | 29 | if (schema?.getType(typeName)) { 30 | context.reportError( 31 | new GraphQLError( 32 | `Type "${typeName}" already exists in the schema. It cannot also be defined in this type definition.`, 33 | { nodes: node.name }, 34 | ), 35 | ); 36 | return; 37 | } 38 | 39 | if (knownTypeNames[typeName]) { 40 | context.reportError( 41 | new GraphQLError(`There can be only one type named "${typeName}".`, { 42 | nodes: [knownTypeNames[typeName], node.name], 43 | }), 44 | ); 45 | } else { 46 | knownTypeNames[typeName] = node.name; 47 | } 48 | 49 | return false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/validation/rules/UniqueVariableNamesRule.ts: -------------------------------------------------------------------------------- 1 | import { groupBy } from '../../jsutils/groupBy'; 2 | 3 | import { GraphQLError } from '../../error/GraphQLError'; 4 | 5 | import type { ASTVisitor } from '../../language/visitor'; 6 | 7 | import type { ASTValidationContext } from '../ValidationContext'; 8 | 9 | /** 10 | * Unique variable names 11 | * 12 | * A GraphQL operation is only valid if all its variables are uniquely named. 13 | */ 14 | export function UniqueVariableNamesRule( 15 | context: ASTValidationContext, 16 | ): ASTVisitor { 17 | return { 18 | OperationDefinition(operationNode) { 19 | // See: https://github.com/graphql/graphql-js/issues/2203 20 | /* c8 ignore next */ 21 | const variableDefinitions = operationNode.variableDefinitions ?? []; 22 | 23 | const seenVariableDefinitions = groupBy( 24 | variableDefinitions, 25 | (node) => node.variable.name.value, 26 | ); 27 | 28 | for (const [variableName, variableNodes] of seenVariableDefinitions) { 29 | if (variableNodes.length > 1) { 30 | context.reportError( 31 | new GraphQLError( 32 | `There can be only one variable named "$${variableName}".`, 33 | { nodes: variableNodes.map((node) => node.variable.name) }, 34 | ), 35 | ); 36 | } 37 | } 38 | }, 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/validation/rules/VariablesAreInputTypesRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../error/GraphQLError'; 2 | 3 | import type { VariableDefinitionNode } from '../../language/ast'; 4 | import { print } from '../../language/printer'; 5 | import type { ASTVisitor } from '../../language/visitor'; 6 | 7 | import { isInputType } from '../../type/definition'; 8 | 9 | import { typeFromAST } from '../../utilities/typeFromAST'; 10 | 11 | import type { ValidationContext } from '../ValidationContext'; 12 | 13 | /** 14 | * Variables are input types 15 | * 16 | * A GraphQL operation is only valid if all the variables it defines are of 17 | * input types (scalar, enum, or input object). 18 | * 19 | * See https://spec.graphql.org/draft/#sec-Variables-Are-Input-Types 20 | */ 21 | export function VariablesAreInputTypesRule( 22 | context: ValidationContext, 23 | ): ASTVisitor { 24 | return { 25 | VariableDefinition(node: VariableDefinitionNode) { 26 | const type = typeFromAST(context.getSchema(), node.type); 27 | 28 | if (type !== undefined && !isInputType(type)) { 29 | const variableName = node.variable.name.value; 30 | const typeName = print(node.type); 31 | 32 | context.reportError( 33 | new GraphQLError( 34 | `Variable "$${variableName}" cannot be non-input type "${typeName}".`, 35 | { nodes: node.type }, 36 | ), 37 | ); 38 | } 39 | }, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/validation/rules/custom/NoSchemaIntrospectionCustomRule.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLError } from '../../../error/GraphQLError'; 2 | 3 | import type { FieldNode } from '../../../language/ast'; 4 | import type { ASTVisitor } from '../../../language/visitor'; 5 | 6 | import { getNamedType } from '../../../type/definition'; 7 | import { isIntrospectionType } from '../../../type/introspection'; 8 | 9 | import type { ValidationContext } from '../../ValidationContext'; 10 | 11 | /** 12 | * Prohibit introspection queries 13 | * 14 | * A GraphQL document is only valid if all fields selected are not fields that 15 | * return an introspection type. 16 | * 17 | * Note: This rule is optional and is not part of the Validation section of the 18 | * GraphQL Specification. This rule effectively disables introspection, which 19 | * does not reflect best practices and should only be done if absolutely necessary. 20 | */ 21 | export function NoSchemaIntrospectionCustomRule( 22 | context: ValidationContext, 23 | ): ASTVisitor { 24 | return { 25 | Field(node: FieldNode) { 26 | const type = getNamedType(context.getType()); 27 | if (type && isIntrospectionType(type)) { 28 | context.reportError( 29 | new GraphQLError( 30 | `GraphQL introspection has been disabled, but the requested query contained the field "${node.name.value}".`, 31 | { nodes: node }, 32 | ), 33 | ); 34 | } 35 | }, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | // Note: This file is autogenerated using "resources/gen-version.js" script and 2 | // automatically updated by "npm version" command. 3 | 4 | /** 5 | * A string containing the version of the GraphQL.js library 6 | */ 7 | export const version = '16.11.0' as string; 8 | 9 | /** 10 | * An object containing the components of the GraphQL.js version string 11 | */ 12 | export const versionInfo = Object.freeze({ 13 | major: 16 as number, 14 | minor: 11 as number, 15 | patch: 0 as number, 16 | preReleaseTag: null as string | null, 17 | }); 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*"], 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "lib": ["es2019", "es2020.promise", "es2020.bigint", "es2020.string"], 6 | "target": "es2019", 7 | "strict": true, 8 | "useUnknownInCatchVariables": false, 9 | "noEmit": true, 10 | "isolatedModules": true, 11 | "importsNotUsedAsValues": "error", 12 | "forceConsistentCasingInFileNames": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /website/.eslintignore: -------------------------------------------------------------------------------- 1 | /pages/_document.tsx 2 | /pages/_app.tsx 3 | -------------------------------------------------------------------------------- /website/icons/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /website/icons/github.svg: -------------------------------------------------------------------------------- 1 | 7 | 12 | -------------------------------------------------------------------------------- /website/icons/graphql.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /website/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DiscordIcon } from './discord.svg'; 2 | export { default as GitHubIcon } from './github.svg'; 3 | export { default as GraphQLLogo } from './graphql.svg'; 4 | export { default as GraphQLWordmarkLogo } from './graphql-wordmark.svg'; 5 | export { default as StackOverflowIcon } from './stackoverflow.svg'; 6 | export { default as TwitterIcon } from './twitter.svg'; 7 | -------------------------------------------------------------------------------- /website/icons/stackoverflow.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | -------------------------------------------------------------------------------- /website/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /website/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /website/next.config.mjs: -------------------------------------------------------------------------------- 1 | import nextra from 'nextra'; 2 | import path from 'node:path'; 3 | import fs from 'node:fs'; 4 | 5 | const fileContents = fs.readFileSync('./vercel.json', 'utf-8'); 6 | const vercel = JSON.parse(fileContents); 7 | 8 | const withNextra = nextra({ 9 | theme: 'nextra-theme-docs', 10 | themeConfig: './theme.config.tsx', 11 | }); 12 | 13 | const sep = path.sep === '/' ? '/' : '\\\\'; 14 | 15 | const ALLOWED_SVG_REGEX = new RegExp(`${sep}icons${sep}.+\\.svg$`); 16 | 17 | /** 18 | * @type {import('next').NextConfig} 19 | */ 20 | export default withNextra({ 21 | webpack(config) { 22 | const fileLoaderRule = config.module.rules.find((rule) => 23 | rule.test?.test?.('.svg'), 24 | ); 25 | 26 | fileLoaderRule.exclude = ALLOWED_SVG_REGEX; 27 | 28 | config.module.rules.push({ 29 | test: ALLOWED_SVG_REGEX, 30 | use: ['@svgr/webpack'], 31 | }); 32 | return config; 33 | }, 34 | redirects: async () => vercel.redirects, 35 | output: 'export', 36 | images: { 37 | loader: 'custom', 38 | imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], 39 | deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], 40 | }, 41 | transpilePackages: ['next-image-export-optimizer'], 42 | env: { 43 | nextImageExportOptimizer_imageFolderPath: 'public/images', 44 | nextImageExportOptimizer_exportFolderPath: 'out', 45 | nextImageExportOptimizer_quality: '75', 46 | nextImageExportOptimizer_storePicturesInWEBP: 'true', 47 | nextImageExportOptimizer_exportFolderName: 'nextImageExportOptimizer', 48 | // If you do not want to use blurry placeholder images, then you can set 49 | // nextImageExportOptimizer_generateAndUseBlurImages to false and pass 50 | // `placeholder="empty"` to all components. 51 | nextImageExportOptimizer_generateAndUseBlurImages: 'true', 52 | // If you want to cache the remote images, you can set the time to live of the cache in seconds. 53 | // The default value is 0 seconds. 54 | nextImageExportOptimizer_remoteImageCacheTTL: '0', 55 | }, 56 | trailingSlash: true, 57 | }); 58 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "description": "The GraphQL.JS documentation website", 5 | "private": true, 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "build": "next build", 11 | "dev": "next" 12 | }, 13 | "devDependencies": { 14 | "@svgr/webpack": "^8.1.0", 15 | "@tailwindcss/typography": "^0.5.10", 16 | "@types/node": "^22.7.5", 17 | "autoprefixer": "^10.4.20", 18 | "next": "^14.2.15", 19 | "nextra": "^3.0.13", 20 | "nextra-theme-docs": "^3.0.13", 21 | "postcss": "^8.4.47", 22 | "react": "^18.3.1", 23 | "react-dom": "^18.3.1", 24 | "tailwindcss": "^3.4.14", 25 | "typescript": "^5.6.3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /website/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from 'next/app'; 2 | import { Roboto_Flex, Roboto_Mono } from 'next/font/google'; 3 | 4 | import '../css/globals.css'; 5 | 6 | const robotoFlex = Roboto_Flex({ 7 | subsets: ['latin'], 8 | }); 9 | 10 | const robotoMono = Roboto_Mono({ 11 | subsets: ['latin'], 12 | }); 13 | 14 | // TODO: do we need google analytics? 15 | 16 | export default function App({ Component, pageProps }: AppProps) { 17 | return ( 18 | <> 19 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /website/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /website/pages/_meta.ts: -------------------------------------------------------------------------------- 1 | const meta = { 2 | docs: { 3 | type: 'page', 4 | title: 'Documentation', 5 | }, 6 | 'upgrade-guides': { 7 | type: 'menu', 8 | title: 'Upgrade Guides', 9 | items: { 10 | 'v16-v17': { 11 | title: 'v16 to v17', 12 | href: '/upgrade-guides/v16-v17', 13 | }, 14 | }, 15 | }, 16 | 'api-v16': { 17 | type: 'menu', 18 | title: 'API', 19 | items: { 20 | 2: { 21 | title: 'V16', 22 | href: '/api-v16/graphql', 23 | }, 24 | }, 25 | }, 26 | }; 27 | 28 | export default meta; 29 | -------------------------------------------------------------------------------- /website/pages/api-v16/_meta.ts: -------------------------------------------------------------------------------- 1 | const meta = { 2 | graphql: '', 3 | error: '', 4 | execution: '', 5 | language: '', 6 | type: '', 7 | utilities: '', 8 | validation: '', 9 | 'graphql-http': '', 10 | }; 11 | 12 | export default meta; 13 | -------------------------------------------------------------------------------- /website/pages/api-v16/graphql-http.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: graphql-http 3 | --- 4 | 5 | {/* title can be removed in Nextra 4, since sidebar title will take from first h1 */} 6 | 7 | # `graphql-http` 8 | 9 | The [official `graphql-http` package](https://github.com/graphql/graphql-http) provides a simple way to create a fully compliant GraphQL server. It has a handler for Node.js native [`http`](https://nodejs.org/api/http.html), together with handlers for well-known frameworks like [Express](https://expressjs.com/), [Fastify](https://www.fastify.io/) and [Koa](https://koajs.com/); as well as handlers for different runtimes like [Deno](https://deno.land/) and [Bun](https://bun.sh/). 10 | 11 | ## Express 12 | 13 | ```js 14 | import { createHandler } from 'graphql-http/lib/use/express'; 15 | ``` 16 | 17 | ### createHandler 18 | 19 | ```ts 20 | function createHandler({ 21 | schema, 22 | rootValue, 23 | context, 24 | formatError, 25 | validationRules, 26 | }: { 27 | rootValue?: any; 28 | context?: any; 29 | formatError?: Function; 30 | validationRules?: any[]; 31 | }): Handler; 32 | ``` 33 | 34 | Constructs an Express handler based on a GraphQL schema. 35 | 36 | See the [tutorial](/running-an-express-graphql-server/) for sample usage. 37 | 38 | See the [GitHub README](https://github.com/graphql/graphql-http) for more extensive documentation, including how to use `graphql-http` with other server frameworks and runtimes. 39 | -------------------------------------------------------------------------------- /website/pages/api-v16/validation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: graphql/validation 3 | --- 4 | 5 | {/* title can be removed in Nextra 4, since sidebar title will take from first h1 */} 6 | 7 | # `graphql/validation` 8 | 9 | The `graphql/validation` module fulfills the Validation phase of fulfilling a 10 | GraphQL result. You can import either from the `graphql/validation` module, or from the root `graphql` module. For example: 11 | 12 | ```js 13 | import { validate } from 'graphql/validation'; 14 | ``` 15 | 16 | ## Overview 17 | 18 | 31 | 32 | ## Validation 33 | 34 | ### `validate` 35 | 36 | ```ts 37 | function validate( 38 | schema: GraphQLSchema, 39 | ast: Document, 40 | rules?: any[], 41 | ): GraphQLError[]; 42 | ``` 43 | 44 | Implements the "Validation" section of the spec. 45 | 46 | Validation runs synchronously, returning an array of encountered errors, or 47 | an empty array if no errors were encountered and the document is valid. 48 | 49 | A list of specific validation rules may be provided. If not provided, the 50 | default list of rules defined by the GraphQL specification will be used. 51 | 52 | Each validation rules is a function which returns a visitor 53 | (see the language/visitor API). Visitor methods are expected to return 54 | GraphQLErrors, or Arrays of GraphQLErrors when invalid. 55 | 56 | Visitors can also supply `visitSpreadFragments: true` which will alter the 57 | behavior of the visitor to skip over top level defined fragments, and instead 58 | visit those fragments at every point a spread is encountered. 59 | 60 | ### `specifiedRules` 61 | 62 | ```ts 63 | let specifiedRules: Array<(context: ValidationContext) => any>; 64 | ``` 65 | 66 | This set includes all validation rules defined by the GraphQL spec 67 | -------------------------------------------------------------------------------- /website/pages/docs/_meta.ts: -------------------------------------------------------------------------------- 1 | const meta = { 2 | index: '', 3 | '-- 1': { 4 | type: 'separator', 5 | title: 'Getting Started', 6 | }, 7 | 'getting-started': '', 8 | 'running-an-express-graphql-server': '', 9 | 'graphql-clients': '', 10 | 'authentication-and-express-middleware': '', 11 | '-- 2': { 12 | type: 'separator', 13 | title: 'Core Concepts', 14 | }, 15 | 'basic-types': '', 16 | 'passing-arguments': '', 17 | 'object-types': '', 18 | 'mutations-and-input-types': '', 19 | nullability: '', 20 | 'abstract-types': '', 21 | 'custom-scalars': '', 22 | '-- 3': { 23 | type: 'separator', 24 | title: 'Advanced Guides', 25 | }, 26 | 'constructing-types': '', 27 | 'oneof-input-objects': '', 28 | 'defer-stream': '', 29 | subscriptions: '', 30 | 'type-generation': '', 31 | 'cursor-based-pagination': '', 32 | 'advanced-custom-scalars': '', 33 | 'operation-complexity-controls': '', 34 | 'n1-dataloader': '', 35 | 'caching-strategies': '', 36 | 'resolver-anatomy': '', 37 | 'graphql-errors': '', 38 | 'using-directives': '', 39 | 'authorization-strategies': '', 40 | '-- 4': { 41 | type: 'separator', 42 | title: 'Production & Scaling', 43 | }, 44 | 'going-to-production': '', 45 | 'scaling-graphql': '', 46 | }; 47 | 48 | export default meta; 49 | -------------------------------------------------------------------------------- /website/pages/docs/defer-stream.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Enabling Defer & Stream 3 | --- 4 | 5 | # Enabling Defer and Stream 6 | 7 | import { Callout } from 'nextra/components' 8 | 9 | 10 | These exports are only available in v17 and beyond. 11 | 12 | 13 | The `@defer` and `@stream` directives are not enabled by default. 14 | In order to use these directives, you must add them to your GraphQL Schema and 15 | use the `experimentalExecuteIncrementally` function instead of `execute`. 16 | 17 | ```js 18 | import { 19 | GraphQLSchema, 20 | GraphQLDeferDirective, 21 | GraphQLStreamDirective, 22 | specifiedDirectives, 23 | } from 'graphql'; 24 | 25 | const schema = new GraphQLSchema({ 26 | query, 27 | directives: [ 28 | ...specifiedDirectives, 29 | GraphQLDeferDirective, 30 | GraphQLStreamDirective, 31 | ], 32 | }); 33 | 34 | const result = experimentalExecuteIncrementally({ 35 | schema, 36 | document, 37 | }); 38 | ``` 39 | 40 | If the `directives` option is passed to `GraphQLSchema`, the default directives will not be included. `specifiedDirectives` must be passed to ensure all standard directives are added in addition to `defer` & `stream`. 41 | -------------------------------------------------------------------------------- /website/pages/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | sidebarTitle: Overview 4 | --- 5 | 6 | GraphQL.js is the official JavaScript implementation of the 7 | [GraphQL Specification](https://spec.graphql.org/draft/). It provides the core building blocks 8 | for constructing GraphQL servers, clients, tools, and utilities in JavaScript and TypeScript. 9 | 10 | This documentation site is for developers who want to: 11 | 12 | - Understand how GraphQL works 13 | - Build a GraphQL API using GraphQL.js 14 | - Extend, customize, or introspect GraphQL systems 15 | - Learn best practices for using GraphQL.js in production 16 | 17 | Whether you're writing your own server, building a GraphQL clients, or creating tools 18 | that work with GraphQL, this site guides you through core concepts, APIs, and 19 | advanced use cases of GraphQL.js. 20 | -------------------------------------------------------------------------------- /website/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'tailwindcss/nesting': {}, 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /website/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import typography from '@tailwindcss/typography'; 2 | 3 | module.exports = { 4 | content: [ 5 | './pages/**/*.{ts,tsx,mdx}', 6 | './icons/**/*.{ts,tsx,mdx}', 7 | './theme.config.tsx', 8 | ], 9 | theme: { 10 | container: { 11 | center: true, 12 | padding: '1rem', 13 | }, 14 | extend: { 15 | colors: { 16 | primary: '#e10098', 17 | 'conf-black': '#0e031c', 18 | black: '#1b1b1b', 19 | }, 20 | backgroundImage: { 21 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 22 | 'gradient-conic': 23 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 24 | }, 25 | animation: { 26 | scroll: 27 | 'scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite', 28 | }, 29 | keyframes: { 30 | scroll: { 31 | to: { 32 | transform: 'translate(calc(-50% - .5rem))', 33 | }, 34 | }, 35 | }, 36 | }, 37 | }, 38 | plugins: [typography], 39 | darkMode: ['class', 'html[class~="dark"]'], 40 | }; 41 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": false, 7 | "forceConsistentCasingInFileNames": true, 8 | "noEmit": true, 9 | "incremental": true, 10 | "esModuleInterop": true, 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "strictNullChecks": true, 21 | "module": "esnext" 22 | }, 23 | "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /website/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "redirects": [ 3 | { 4 | "source": "/api", 5 | "destination": "/api-v16/graphql", 6 | "permanent": true 7 | }, 8 | { 9 | "source": "/", 10 | "destination": "/docs", 11 | "permanent": true 12 | } 13 | ] 14 | } 15 | --------------------------------------------------------------------------------