Compare commits

..

69 Commits

Author SHA1 Message Date
Sam Burba 9e5c27c509
docs: Fix readme converter link (#1108) 2023-03-16 11:04:52 -04:00
Jan Piotrowski 601c2f03f4
fix(prisma): Replace outdated note (#1153)Co-authored-by: Andrew Carlson <5479270+andrewicarlson@users.noreply.github.com>
* fix(prisma): Remove outdated note

* Update 010-overview.mdx

---------

Co-authored-by: Andrew Carlson <5479270+andrewicarlson@users.noreply.github.com>
2023-03-16 11:04:26 -04:00
Andrew Carlson 4e085dc7b2
Feature/update docs (#1136)
* Replaced Prisma reference

* Updated copyright info
2023-02-02 10:48:58 -06:00
Alex Kunin 1e6b9212d3
chore: remove irrelevant paragraph in docs (#1128)
Resolver shorthands were removed in v0.18.0, docs need to be updated.
2022-10-31 09:25:32 -04:00
Tim Griesser 4d8e37177b
feat: better esm support, remove top-level node imports (#1112)
* test for esm/esbuild use of nexus

* refactor: add nodeImports, remove top-level references

* Don't minify the esbuild test script

* A bit more explicit of a check on the process being node
2022-07-02 16:08:16 -04:00
Tim Griesser eec4b91091
feat: add sourceType option to fieldDefinition (#1106)
* feat: add sourceType option to fieldDefinition

* add additional configurability for field-level sourceType
2022-06-28 13:21:58 -04:00
Tim Griesser 8e65081539
fix: remove hasSDLDirectives internal state (#1091)
* fix: remove hasSDLDirectives from schema construction
2022-05-16 00:12:23 -04:00
Tim Griesser 7ee48c4f2c
fix: add missing as const on the RequestDirectiveLocation (#1090) 2022-05-15 22:48:26 -04:00
Tim Griesser e9dc7e0c4a chore: Fix codecov 2022-05-15 16:59:29 -04:00
Tim Griesser 9875e90dae
feat: Add Schema Directives to SDL (#952)
Adds directive & addDirective utilities for defining directives emitted in the SDL
2022-05-15 16:59:05 -04:00
Tim Griesser 9a1050792b chore: attempt to fix the dripip workflow, again 2022-05-15 14:56:32 -04:00
Tim Griesser df9cb700a9 chore: update the dripip workflow, bump lint-staged 2022-05-15 14:50:03 -04:00
Tim Griesser 123dc61fac
fix: incorrect logic in backward pagination (#1084) 2022-05-03 15:35:21 -04:00
Tim Griesser 251af9461b fix: update snapshots for change in #1083 2022-05-03 11:38:57 -04:00
Daniel Schwartz b906288c58
chore: change facebook.github.io to relay.dev links (#1083) 2022-05-03 11:24:45 -04:00
Tim Griesser 11d028277e
feat: allow specifying custom directives in makeSchema (#1065) 2022-03-25 10:38:08 -04:00
Jonathan Zinger 509c246e88
Update comment for `shouldGenerateArtifacts` (#1057)
Update comment to reflect the current behavior as per [`src/typegenUtils.ts`](bc12ca0f8e/src/typegenUtils.ts (L11))
2022-03-25 10:28:24 -04:00
Vincent François aaa45204b6
feat: Run both formatTypegen and prettier formatter if given (#1042) 2022-03-25 08:20:32 -04:00
Tim Griesser 7349e3633c
feat: allow specifying custom directives in makeSchema (#1064)
* feat: allow specifying custom directives in makeSchema

* fix for earlier graphql snapshots
2022-03-24 18:48:41 -04:00
Vincent François 892af670a4
feat: Use ReadonlyArray in typings (#1041)
* Use ReadonlArray when generating output types

* Add useReadonlyArrayForInputs config and use it accordingly

* Update tests snapshots

* Move useReadonlyArrayForInputs in ConfiguredTypegen option

* Add tests for useReadonlyArrayForInputs

Co-authored-by: Jason Kuhrt <jasonkuhrt@me.com>
Co-authored-by: Tim Griesser <tgriesser10@gmail.com>
2022-03-24 17:02:17 -04:00
Jason Kuhrt bc12ca0f8e chore: use dripip reusable workflow 2022-03-10 14:02:20 -05:00
Tim Griesser 0f37c3e9d4
docs: Update npm badge 2022-03-05 15:29:59 -05:00
Tim Griesser 6c1530a3d5 v1.3.0 2022-03-05 15:26:42 -05:00
Tim Griesser 92f20dc9e9
chore: add test confirming v16 schema compat (#1054) 2022-03-05 15:25:11 -05:00
Tim Griesser bbc969a5b0
chore: update github workflows (#1053) 2022-03-05 14:12:48 -05:00
Tim Griesser 0d06f26b12
feat: add GraphQL 16 support (#977) 2022-02-17 12:47:40 -05:00
Aniruddh Mukherjee e901bebd51
chore(docs): fix typos (#1005)
Fixed the typos where `draftToPublish` (used in the previous example) were mistakenly written as `postToPublish` which might lead to confusion for some readers.
2021-12-15 09:10:02 -05:00
Riccardo Scalco 87a82a28b1
chore(docs): fix typo 06-chapter-5-persisting-data-via-prisma.mdx (#1007) 2021-12-15 09:09:34 -05:00
Riccardo Scalco 3de7f399c6
chore(docs): fix typo 04-why-nexus.mdx (#1008) 2021-12-15 09:09:02 -05:00
Tana M Berry 45b9d5a926
chore(docs): fix typo 030-neuxs-framework-prisma-users.mdx (#1016) 2021-12-15 09:08:30 -05:00
ChengQing b92545fe4f
chore(docs): fix typo 05-chapter-4-testing-your-api.mdx (#1023)
Fix typo.
2021-12-15 09:08:03 -05:00
ChengQing 4d035019a4
chore(docs): fix typo in 07-chapter-6-testing-with-prisma.mdx (#1024) 2021-12-15 09:07:24 -05:00
Tim Griesser 9ec3442539
fix: Minimum GraphQL v16 support (#1017) 2021-11-18 15:53:18 -05:00
Johan Kim c0e55b53b4
chore(docs): add Example Code (#948) 2021-10-26 21:50:03 -04:00
Johan Kim a7c26ba587
chore(docs): Update Ordering section (#950) 2021-10-26 21:49:26 -04:00
lockedNLevered 703d5595da
chore(docs): Nexus Getting started: missing type in queryType (#995)
queryType is missing type field. TypeScript would throw error and not compile.
2021-10-26 21:48:29 -04:00
Ella Nan 02015ede84
chore(docs): readme update -- fix code sample to include ".ts" in module path (#998) 2021-10-26 21:47:51 -04:00
Dani Guardiola 2934d6d38c
chore(docs): typo fix in docs (#999) 2021-10-26 21:47:19 -04:00
safaure 5e3b837ec0
chore(docs): change package get-port ver in tutorial (#1000)
See https://github.com/sindresorhus/get-port/releases

get-port does not support commonJS anymore.
2021-10-26 21:46:53 -04:00
Timo Heman b24d132d38
docs: change all "npm add" to "npm install" (#991) 2021-09-17 11:19:52 -04:00
Andrew McCallum e5658cadba
docs: Update 061-list-nonNull.mdx (#986)
Fixed syntax in docs where comma was missing
2021-09-07 09:07:05 -04:00
Tim Griesser 333dfb8757
feat: Add mergeSchema, better graphql-js interop (#983)
- Adds a new feature mergeSchema to the makeSchema config, to consume an external schema and merge with the locally defined Nexus types
- Standardizes the consumption of external GraphQLNamedType's so they are converted into Nexus type definitions, simplifying code paths
- Order of resolution goes from local Nexus types -> graphql-js types -> external schema types
- Adds asNexusMethod to all types, allowing for any commonly used types to be used as methods, not just scalars
- Convert methods / properties from protected -> private in Builder to better detect unused code
2021-09-06 09:32:43 -04:00
Tim Griesser 02a73b5d74
refactor: More internal cleanup (#981)
- Add Maybe on resolveType to match GraphQL signature
- Don't create placeholder Query type when renamed
- Internal rename rootTypings -> sourceTypings
2021-09-03 17:00:35 +00:00
Tim Griesser 7e744aa0d3
refactor: Internal type cleanup (#980)
* refactor: Internal type cleanup
* chore: Remove prettier-plugin-jsdoc as its output is non-determinstic
2021-09-03 14:04:28 +00:00
Tim Griesser e65b5a7952
refactor: remove duplicate core schema type checks (#978) 2021-09-02 23:44:37 +00:00
Tim Griesser d4c6260774
feat: ability to rename root types (#976)
Closes #867
2021-09-02 09:38:16 -04:00
Gary Lamp 6de1cb6917
docs: Remove `.ts` extension from import statement (#974) 2021-09-02 09:20:54 -04:00
Tim Griesser 555ae79ded
fix: revert declareInputs default to false (#975) 2021-09-02 13:18:47 +00:00
Tim Griesser a003ff074f
fix: printer imports, follow up from #967 (#970) 2021-08-19 14:56:35 +00:00
Johannes Choo 855a482c54
fix: plugin inputFieldDefTypes (#919) 2021-08-18 14:50:09 -04:00
Tim Griesser 266f1a82ce
fix: add globalHeaders to imports for global def file (#969) 2021-08-17 20:15:49 -04:00
Tim Griesser 380ed9d4ca
fix: correct imports for changes in #967 (#968)
* fix: correct imports for changes in #967
2021-08-17 19:35:56 -04:00
Tim Griesser 5984ee122e
feat: ConfiguredTypegen for splitting global types & configuring input type emission (#967)
* feat: ConfiguredTypegen for splitting globals from other types
* default the declareInputs to true
2021-08-17 15:49:47 -04:00
Tim Griesser a914cc33d1
Revert "fix: #921 wrong typegen for nullable values w/ default (#965)" (#966)
This reverts commit 03b5ffd24a.
2021-08-17 13:50:36 -04:00
Tim Griesser 03b5ffd24a
fix: #921 wrong typegen for nullable values w/ default (#965)
* fix: #921 wrong typegen for nullable values w/ default

* refactor: make the conditional easier to follow
2021-08-04 10:59:50 -04:00
Tim Griesser 4531519e6c fix failing CI types 2021-07-21 12:19:17 -04:00
Tobias Lins 8e0ce1b4a1
chore(docs): improve scalar documentation (#939) 2021-06-25 13:42:06 -04:00
Jason Kuhrt df7d1370f9
feat: field config with name (#938) 2021-06-23 16:27:02 -04:00
Anton Lukin ebcdb29a3e
chore(docs): Fix a typo in JSDoc example (#937) 2021-06-21 08:20:39 -04:00
Tim Griesser 81ec5c0f08
fix: Allow meta types to be wrapped with null/list (#935) 2021-06-19 13:36:07 -04:00
Tim Griesser 2b0708d675
refactor: TO_NEXUS -> NEXUS_TYPE / NEXUS_BUILD (#934) 2021-06-16 08:42:44 -04:00
Tim Griesser 2759c551c7
feat: TO_NEXUS symbol for adding types via metaprogramming (#932) 2021-06-14 17:37:30 -04:00
Nenad Filipovic 10ea65f6f1
chore(docs): add formatTypegen example (#930) 2021-06-11 17:47:21 -04:00
Tim Griesser 690abb7299
fix: correct edge node typing (#876) 2021-06-10 07:49:34 -04:00
Tim Griesser 2e1c32b794
refactor: move makeSchema into separate file from builder (#927) 2021-06-09 11:40:53 -04:00
Tom Sherman 3a92760058
feat: Accept `ReadonlyArray<T>` for enum members (#916) 2021-05-25 19:36:07 -04:00
Philipp Strathausen 031c77257a
chore(docs): Fix prisma link in examples README (#912) 2021-05-25 19:32:31 -04:00
Tom Sherman 31a6137f90
Fix graphql docs link for interfaces (#913) 2021-05-22 12:53:16 -04:00
Jan Piotrowski 571fa4d124
docs(README): Add link to GH Actions 2021-05-20 22:00:43 +02:00
167 changed files with 10688 additions and 4827 deletions

View File

@ -1,36 +1,36 @@
name: pr name: pr
on: on:
- pull_request - pull_request
jobs: jobs:
graphql-14: graphql-15:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: 12.x node-version: 14.x
- name: Install Dependencies - name: Install Dependencies
run: yarn --frozen-lockfile && yarn format:ci run: yarn --frozen-lockfile && yarn format:ci
- name: Install GraphQL@14.x - name: Install GraphQL@15.x
run: yarn add graphql@^14.5.8 run: yarn add graphql@^15
- name: Test - name: Test
run: yarn -s test:ci --testPathIgnorePatterns v15 run: yarn -s test:ci
test: test:
strategy: strategy:
matrix: matrix:
node-version: [10.x, 12.x] node-version: [14.x, 16.x]
os: [macos-latest, ubuntu-latest, windows-latest] os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install Dependencies - name: Install Dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile || yarn --frozen-lockfile
- name: Check Prettier - name: Check Prettier
if: matrix.os != 'windows-latest' if: matrix.os != 'windows-latest'
run: yarn format:ci run: yarn format:ci
@ -38,7 +38,7 @@ jobs:
run: yarn -s test:ci run: yarn -s test:ci
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '10.x' if: matrix.os == 'ubuntu-latest' && matrix.node-version == '14.x'
with: with:
directory: ./coverage directory: ./coverage
@ -56,16 +56,18 @@ jobs:
# rm examples/star-wars/star-wars-schema.graphql # rm examples/star-wars/star-wars-schema.graphql
# node examples/star-wars/dist/schema.js # node examples/star-wars/dist/schema.js
# git diff --exit-code # git diff --exit-code
node-version: [10.x] node-version: [14.x]
os: [macos-latest] os: [macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install Dependencies - name: Install Dependencies
run: yarn --frozen-lockfile && yarn format:ci run: yarn --frozen-lockfile || yarn --frozen-lockfile
- name: Check Formatting
run: yarn format:ci
- name: Link - name: Link
run: yarn link run: yarn link
- name: Example Install - name: Example Install
@ -81,8 +83,8 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
- name: Install Dependencies - name: Install Dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile || yarn --frozen-lockfile
- name: Prettier - name: Prettier
run: yarn format:ci run: yarn format:ci

View File

@ -5,40 +5,40 @@ on:
branches: [main] branches: [main]
jobs: jobs:
graphql-14: graphql-15:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
with: with:
node-version: 12.x node-version: 14.x
- name: Install Dependencies - name: Install Dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile && yarn format:ci
- name: Install GraphQL@14.x - name: Install GraphQL@15.x
run: yarn add graphql@^14.5.8 run: yarn add graphql@^15
- name: Test - name: Test
run: yarn -s test:ci --testPathIgnorePatterns v15 run: yarn -s test:ci
test: test:
strategy: strategy:
matrix: matrix:
node-version: [10.x, 12.x] node-version: [14.x, 16.x]
os: [macos-latest, ubuntu-latest, windows-latest] os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install Dependencies - name: Install Dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile || yarn --frozen-lockfile
- name: Check Prettier - name: Check Prettier
if: matrix.os != 'windows-latest' if: matrix.os != 'windows-latest'
run: yarn format:ci run: yarn format:ci
- name: Test - name: Test
run: yarn -s test:ci run: yarn -s test:ci
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.node-version == '10.x' if: matrix.os == 'ubuntu-latest' && matrix.node-version == '14.x'
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
with: with:
directory: ./coverage directory: ./coverage
@ -57,16 +57,18 @@ jobs:
# rm examples/star-wars/star-wars-schema.graphql # rm examples/star-wars/star-wars-schema.graphql
# node examples/star-wars/dist/schema.js # node examples/star-wars/dist/schema.js
# git diff --exit-code # git diff --exit-code
node-version: [10.x] node-version: [14.x]
os: [macos-latest] os: [macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install Dependencies - name: Install Dependencies
run: yarn --frozen-lockfile && yarn format:ci run: yarn --frozen-lockfile || yarn --frozen-lockfile
- name: Check Formatting
run: yarn format:ci
- name: Link - name: Link
run: yarn link run: yarn link
- name: Example Install - name: Example Install
@ -83,9 +85,9 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-node@v1 - uses: actions/setup-node@v2
- name: Install Dependencies - name: Install Dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile || yarn --frozen-lockfile
- name: Release Canary - name: Release Canary
env: env:
NPM_TOKEN: ${{secrets.NPM_TOKEN}} NPM_TOKEN: ${{secrets.NPM_TOKEN}}

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ examples/*/dist
website/static/playground-dist website/static/playground-dist
yarn-error.log yarn-error.log
coverage/* coverage/*
tests/esm/out/

View File

@ -1,4 +1,4 @@
(c) 2018-2019 Tim Griesser (c) 2018-2022 Tim Griesser
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation

View File

@ -1,7 +1,7 @@
# Nexus # Nexus
![trunk](https://github.com/graphql-nexus/nexus/workflows/trunk/badge.svg) [![trunk](https://github.com/graphql-nexus/nexus/workflows/trunk/badge.svg)](https://github.com/graphql-nexus/nexus/actions/workflows/trunk.yml)
[![npm version](https://badge.fury.io/js/%40nexus%2Fschema.svg)](https://badge.fury.io/js/%40nexus%2Fschema) [![npm version](https://badge.fury.io/js/nexus.svg)](https://badge.fury.io/js/nexus)
Declarative, code-first and strongly typed GraphQL schema construction for TypeScript & JavaScript. Declarative, code-first and strongly typed GraphQL schema construction for TypeScript & JavaScript.
@ -74,4 +74,4 @@ You can find the docs for Nexus [here](http://nexusjs.org/).
## Migrate from SDL ## Migrate from SDL
If you've been following an [SDL-first](https://www.prisma.io/blog/the-problems-of-schema-first-graphql-development-x1mn4cb0tyl3/) approach to build your GraphQL server and want to see what your code looks like when written with GraphQL Nexus, you can use the [**SDL converter**](https://nexus.js.org/converter). If you've been following an [SDL-first](https://www.prisma.io/blog/the-problems-of-schema-first-graphql-development-x1mn4cb0tyl3/) approach to build your GraphQL server and want to see what your code looks like when written with GraphQL Nexus, you can use the [**SDL converter**](https://nexusjs.org/converter).

View File

@ -18,7 +18,7 @@ Start by creating your project directory, initializing your `package.json`, and
```bash-symbol copy ```bash-symbol copy
mkdir nexus-tutorial && cd nexus-tutorial mkdir nexus-tutorial && cd nexus-tutorial
npm init -y npm init -y
npm add nexus graphql apollo-server npm install nexus graphql apollo-server
``` ```
> Note: `nexus` works with any GraphQL compliant server. We'll use `apollo-server` in this tutorial, but you're free to use whichever fits your use-case best. > Note: `nexus` works with any GraphQL compliant server. We'll use `apollo-server` in this tutorial, but you're free to use whichever fits your use-case best.
@ -26,7 +26,7 @@ npm add nexus graphql apollo-server
We'll also need `typescript` and `ts-node-dev` as dev dependencies. `ts-node-dev` will enable you to transpile your TS files on the fly and restart your API on changes. We'll also need `typescript` and `ts-node-dev` as dev dependencies. `ts-node-dev` will enable you to transpile your TS files on the fly and restart your API on changes.
```bash-symbol copy ```bash-symbol copy
npm add --save-dev typescript ts-node-dev npm install --save-dev typescript ts-node-dev
``` ```
To properly get full advantage of TypeScript, we'll need a `tsconfig.json` file. Create one at the root of your project and copy paste the following To properly get full advantage of TypeScript, we'll need a `tsconfig.json` file. Create one at the root of your project and copy paste the following
@ -118,7 +118,7 @@ export const server = new ApolloServer({ schema })
```ts copy ```ts copy
// api/index.ts // api/index.ts
import { server } from './server.ts' import { server } from './server'
server.listen().then(({ url }) => { server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`) console.log(`🚀 Server ready at ${url}`)

View File

@ -25,7 +25,7 @@ During this tutorial, you'll use the [Jest testing framework](https://jestjs.io/
First, install `jest` and accompanying tools. First, install `jest` and accompanying tools.
```bash-symbol copy ```bash-symbol copy
npm add --save-dev jest @types/jest ts-jest graphql-request get-port npm install --save-dev jest @types/jest ts-jest graphql-request get-port@5.1.1
``` ```
Then, configure jest and npm scripts in your `package.json` Then, configure jest and npm scripts in your `package.json`
@ -88,7 +88,7 @@ mkdir tests && touch tests/Post.test.ts
To ease testing, we'll create a small utility that we'll call `createTestContext`, which is designed for running integration tests. To ease testing, we'll create a small utility that we'll call `createTestContext`, which is designed for running integration tests.
When run, it will boot your app in the same process as the test suite and expose an interface for your tests to interact with it. Jest runs each test suite in its own process, so if you have have say eight test suites running in parallel that means you'll have eight app processes running too. When run, it will boot your app in the same process as the test suite and expose an interface for your tests to interact with it. Jest runs each test suite in its own process, so if you have say eight test suites running in parallel that means you'll have eight app processes running too.
Create a `tests/__helpers.ts` module with the following contents. Create a `tests/__helpers.ts` module with the following contents.

View File

@ -35,8 +35,8 @@ Now that you know a bit about Prisma, let's get going! Do the following:
like so: like so:
```bash-symbol copy ```bash-symbol copy
npm add @prisma/client npm install @prisma/client
npm add --save-dev prisma npm install --save-dev prisma
``` ```
Next, add Prisma to your project by creating your [Prisma schema](https://www.prisma.io/docs/concepts/components/prisma-schema/) file with the following command: Next, add Prisma to your project by creating your [Prisma schema](https://www.prisma.io/docs/concepts/components/prisma-schema/) file with the following command:
@ -135,7 +135,7 @@ Now let's finally ditch our in-memory data! Let's replace it with the Prisma Cli
// api/db.ts // api/db.ts
+import { PrismaClient } from '@prisma/client' +import { PrismaClient } from '@prisma/client'
export const db = new PrismaClient() +export const db = new PrismaClient()
-export interface Post { -export interface Post {
- id: number - id: number
@ -266,15 +266,15 @@ export const PostMutation = extendType({
draftId: nonNull(intArg()), draftId: nonNull(intArg()),
}, },
resolve(_root, args, ctx) { resolve(_root, args, ctx) {
- let postToPublish = ctx.db.posts.find((p) => p.id === args.draftId) - let draftToPublish = ctx.db.posts.find((p) => p.id === args.draftId)
- if (!postToPublish) { - if (!draftToPublish) {
- throw new Error('Could not find draft with id ' + args.draftId) - throw new Error('Could not find draft with id ' + args.draftId)
- } - }
- postToPublish.published = true - draftToPublish.published = true
- return postToPublish - return draftToPublish
+ return ctx.db.post.update({ + return ctx.db.post.update({
+ where: { id: args.draftId }, + where: { id: args.draftId },

View File

@ -26,7 +26,7 @@ To achieve some of the steps described above, we'll tweak our test context.
First, install the `sqlite3` and `nanoid` packages First, install the `sqlite3` and `nanoid` packages
```bash copy ```bash copy
npm add --save-dev sqlite3 @types/sqlite3 npm install --save-dev sqlite3 @types/sqlite3
``` ```
Then, head to your `tests/__helpers.ts` file to add the following imports and code Then, head to your `tests/__helpers.ts` file to add the following imports and code
@ -220,7 +220,7 @@ function prismaTestContext() {
The `prismaTestContext` is in charge of a couple of things: The `prismaTestContext` is in charge of a couple of things:
1. Connect to an in-memory instance of the SQLite database 1. Connect to an in-memory instance of the SQLite database
2. Pushes the Prisma Schema to the adatabase 2. Pushes the Prisma Schema to the database
3. Generates a new Prisma Client 3. Generates a new Prisma Client
4. Add an instance of a Prisma Client connected to the schema specifically for the test 4. Add an instance of a Prisma Client connected to the schema specifically for the test

View File

@ -63,6 +63,7 @@ const Post = objectType({
const Query = queryType({ const Query = queryType({
definition(t) { definition(t) {
t.list.field('posts', { t.list.field('posts', {
type: "Post",
resolve: () => [ resolve: () => [
{ {
id: '1', id: '1',
@ -93,7 +94,7 @@ There are numerous benefits to taking a code-first approach with Nexus:
When building a schema-first GraphQL API, it is common to start out by placing all type definitions and resolvers in a single file. When both the schema and the resolvers live next to one another, it's fairly straightforward to work in both at the same time. When building a schema-first GraphQL API, it is common to start out by placing all type definitions and resolvers in a single file. When both the schema and the resolvers live next to one another, it's fairly straightforward to work in both at the same time.
As the application grows, however, it is most often desired to move parts of the schema into their own separate modules and files. It's at this point that working on a GraphQL API becomes a bit more tedious. With this modularization comes the need to switch back and forth between the Schema Definition Language and JavaScript/TypeScript to write the resolvers. Not only does one need to constantly switch between files, they also need to do a context switch mentally to work between the two langauges. As the application grows, however, it is most often desired to move parts of the schema into their own separate modules and files. It's at this point that working on a GraphQL API becomes a bit more tedious. With this modularization comes the need to switch back and forth between the Schema Definition Language and JavaScript/TypeScript to write the resolvers. Not only does one need to constantly switch between files, they also need to do a context switch mentally to work between the two languages.
With Nexus, our schema and its resolvers are always defined together. Nexus also allows us to write everything in a common language. This allows us to side-step the co-location/context switching issue altogether and helps us to be more productive, even as our applications grow to be quite large. With Nexus, our schema and its resolvers are always defined together. Nexus also allows us to write everything in a common language. This allows us to side-step the co-location/context switching issue altogether and helps us to be more productive, even as our applications grow to be quite large.
@ -133,7 +134,7 @@ A downside of the schema-first approach is the need to repeat yourself in schema
### Defining enums ### Defining enums
When defining an enum using the schema-first approach, the enum must first be defined in the schema definition languange: When defining an enum using the schema-first approach, the enum must first be defined in the schema definition language:
```graphql ```graphql
enum UserRole { enum UserRole {

View File

@ -28,18 +28,3 @@ Each public API is documented below, feel free to open a PR with more examples/c
- [extendType / extendInputType](/api/extend-type) - [extendType / extendInputType](/api/extend-type)
- [mutationField](/api/mutation-field) - [mutationField](/api/mutation-field)
- [queryField](/api/query-field) - [queryField](/api/query-field)
## Resolving: Inline Function
One common idiom in GraphQL is exposing fields that mask or rename the property name on the backing object. GraphQL Nexus makes this simple by allowing a function as the second parameter to any built-in scalar resolver function.
```ts
const User = objectType({
name: 'User',
definition(t) {
t.id('id', o => o.user_id)
t.string('name', o => o.user_name)
t.string('description', o => o.user_description)
},
})
```

View File

@ -65,3 +65,14 @@ const SomeObject = objectType({
``` ```
Check the type-definitions or [the examples](https://github.com/graphql-nexus/nexus/tree/main/examples) for a full illustration of the various options for `scalarType`, or feel free to open a PR on the docs to help document! Check the type-definitions or [the examples](https://github.com/graphql-nexus/nexus/tree/main/examples) for a full illustration of the various options for `scalarType`, or feel free to open a PR on the docs to help document!
## Pass scalar to schema generation
It's important to list the scalar in the `types` attribute of `makeSchema`
```ts
const schema = makeSchema({
types: [GQLDate] // Add Scalar to Array
})
```

View File

@ -5,7 +5,7 @@ codeStyle: true
## interfaceType ## interfaceType
[GraphQL Docs for Interface Types](https://graphql.org/learn/schema/#input-types) [GraphQL Docs for Interface Types](https://graphql.org/learn/schema/#interfaces)
In Nexus, you do not need to redefine the interface fields on the In Nexus, you do not need to redefine the interface fields on the
implementing object types, instead you may use `.implements(interfaceName)` implementing object types, instead you may use `.implements(interfaceName)`

View File

@ -10,11 +10,31 @@ codeStyle: true
Defines a complex object which can be passed as an input value. Defines a complex object which can be passed as an input value.
```ts ```ts
export const InputType = inputObjectType({ import { extendType, inputObjectType } from 'nexus'
name: 'InputType',
export const CommentInputType = inputObjectType({
name: 'CommentInputType',
definition(t) { definition(t) {
t.nonNull.string('key') t.nonNull.int('userId')
t.int('answer') t.nonNull.string('body')
}
})
export const CommentMutation = extendType({
type: 'Mutation',
definition(t) {
t.field('createComment', {
type: 'Comment',
args: { data: CommentInputType },
resolve(_root, args, ctx) {
return ctx.prisma.comment.create({
data: {
user_id: args.userId,
body: args.body,
}
})
}
})
}, },
}) })
``` ```

View File

@ -15,7 +15,7 @@ import { queryType, stringArg, list } from 'nexus'
queryType({ queryType({
definition(t) { definition(t) {
t.field('tags', { t.field('tags', {
type: list('String') // -> [String] type: list('String'), // -> [String]
args: { args: {
ids: list(stringArg()) // or list('String') -> [String] ids: list(stringArg()) // or list('String') -> [String]
}, },

View File

@ -133,9 +133,26 @@ You should make a decision on this and supply the option yourself, it may be cha
Read more on this in the [getting-started](../../../getting-started) guide. Read more on this in the [getting-started](../../../getting-started) guide.
### typegenConfig, formatTypegen ### typegenConfig
Escape hatches for more advanced cases which need further control over. You typically won't need these. Escape hatch for more advanced cases which need further control over generated files. You typically won't need this.
### formatTypegen
Manually apply a formatter to the generated content before saving. Function exposes content and type of generated file.
```ts
makeSchema({
// ...
formatTypegen: (content, type) => {
if (type === 'types') {
return `/* eslint-disable */
\n ${content}`;
}
return content;
},
})
```
### customPrintSchemaFn ### customPrintSchemaFn

View File

@ -4,7 +4,7 @@ title: Relay Connection
## Connection Plugin ## Connection Plugin
The connection plugin provides a new method on the object definition builder, enabling paginated associations between types, following the [Relay Connection Specification](https://facebook.github.io/relay/graphql/connections.htm#sec-Node). It provides simple ways to customize fields available on the `Connection`, `Edges`, or `PageInfo` types. The connection plugin provides a new method on the object definition builder, enabling paginated associations between types, following the [Relay Connection Specification](https://relay.dev/graphql/connections.htm#sec-Node). It provides simple ways to customize fields available on the `Connection`, `Edges`, or `PageInfo` types.
To install, add the `connectionPlugin` to the `makeSchema.plugins` array, along with any other plugins To install, add the `connectionPlugin` to the `makeSchema.plugins` array, along with any other plugins
you'd like to include: you'd like to include:

View File

@ -6,13 +6,13 @@ title: Overview
This plugin integrates [Prisma](https://www.prisma.io/) into [Nexus](https://nexusjs.org/). It gives you an API to project fields from models defined in your Prisma schema into your GraphQL API. It also gives you an API to build GraphQL root fields that allow your API clients to query and mutate data. This plugin integrates [Prisma](https://www.prisma.io/) into [Nexus](https://nexusjs.org/). It gives you an API to project fields from models defined in your Prisma schema into your GraphQL API. It also gives you an API to build GraphQL root fields that allow your API clients to query and mutate data.
> **Note**: The Prisma team is currently [rewriting](https://github.com/graphql-nexus/nexus-plugin-prisma/issues/1039) the plugin to make it maintainable longterm. **Note**: You may also use [`nexus-prisma`](https://github.com/graphql-nexus/nexus-prisma), a newer API for integrating Nexus and Prisma.
## Installation ## Installation
```bash-symbol ```bash-symbol
npm add nexus-plugin-prisma @prisma/client npm install nexus-plugin-prisma @prisma/client
npm add -D prisma npm install -D prisma
``` ```
## Usage ## Usage

View File

@ -1586,7 +1586,7 @@ queryType({
type Query { type Query {
user(where: UserWhereUniqueInput!): User user(where: UserWhereUniqueInput!): User
users(orderBy: UserOrderByInput): [User!]! users(orderBy: [UserOrderByInput!]): [User!]!
} }
type Post { type Post {
@ -1598,7 +1598,7 @@ type Post {
type User { type User {
id: Int! id: Int!
name: String! name: String!
posts(orderBy: UserPostsOrderByInput): [Post!]! posts(orderBy: [UserPostsOrderByInput!]): [Post!]!
} }
input UserOrderByInput { input UserOrderByInput {
@ -1625,7 +1625,7 @@ enum OrderByArg {
```graphql ```graphql
query entrypointOrdering { query entrypointOrdering {
users(orderBy: { name: asc }) { users(orderBy: [{ name: asc }]) {
id id
name name
} }
@ -1633,7 +1633,7 @@ query entrypointOrdering {
query relationOrdering { query relationOrdering {
user(where: { id: 1643 }) { user(where: { id: 1643 }) {
posts(orderBy: { title: dsc }) { posts(orderBy: [{ title: desc }]) {
title title
body body
} }

View File

@ -27,7 +27,7 @@ You will need to manage the TypeScript toolchain yourself.
1. Install `typescript`, `ts-node`, and `ts-node-dev`. 1. Install `typescript`, `ts-node`, and `ts-node-dev`.
```bash-symbol ```bash-symbol
npm add -D typescript ts-node ts-node-dev npm install -D typescript ts-node ts-node-dev
``` ```
2. The following applies to VSCode but something like it might apply to other editors as well. You must also make sure your editor is set to use the local version of TypeScript rather than the one the editor ships with. Summon the command pallet `command+shift+p` and then enter and select `typescript: select TypeScript Version...`. Then select `Workspace Version`. 2. The following applies to VSCode but something like it might apply to other editors as well. You must also make sure your editor is set to use the local version of TypeScript rather than the one the editor ships with. Summon the command pallet `command+shift+p` and then enter and select `typescript: select TypeScript Version...`. Then select `Workspace Version`.
@ -205,13 +205,13 @@ Nexus framework comes bundled with Apollo Server and runs it for you. You will n
1. Install new dependencies 1. Install new dependencies
```tsx ```tsx
npm add express apollo-server-express npm install express apollo-server-express
``` ```
2. If using TypeScript then Install typings for Express 2. If using TypeScript then Install typings for Express
```tsx ```tsx
npm add -D @types/express npm install -D @types/express
``` ```
3. If using TypeScript then you [need to](https://github.com/apollographql/apollo-server/issues/1977#issuecomment-662946590) enable `esModuleInterop` to be able to use Apollo Server 3. If using TypeScript then you [need to](https://github.com/apollographql/apollo-server/issues/1977#issuecomment-662946590) enable `esModuleInterop` to be able to use Apollo Server
@ -337,7 +337,7 @@ Nexus Framework has some builtin logging functionality. You can approximate it a
1. Install your logger 1. Install your logger
```tsx ```tsx
npm add floggy npm install floggy
``` ```
2. Create your application logger. The logger you export here should be used across your codebase. 2. Create your application logger. The logger you export here should be used across your codebase.
@ -386,7 +386,7 @@ You need to explicitly setup all custom scalars yourself. To match what Nexus Fr
1. Install the `graphql-scalars` package 1. Install the `graphql-scalars` package
```json ```json
npm add graphql-scalars npm install graphql-scalars
``` ```
2. Setup the `Json` and `DateTime` scalars 2. Setup the `Json` and `DateTime` scalars
@ -448,7 +448,7 @@ import * as Path from 'path'
Nexus.makeSchema({ Nexus.makeSchema({
contextType: { contextType: {
module: Path.join(__dirname, './path/to/contextModule'), module: Path.join(__dirname, './path/to/contextModule.ts'),
alias: 'ContextModule', alias: 'ContextModule',
export: 'Context' export: 'Context'
}, },
@ -536,13 +536,13 @@ In Nexus Framework there was a testing module for system testing your app. You n
1. Install your test framework 1. Install your test framework
```tsx ```tsx
npm add jest npm install jest
``` ```
2. If you are a TypeScript user then install and configure `ts-jest` 2. If you are a TypeScript user then install and configure `ts-jest`
```bash ```bash
npm add ts-jest npm install ts-jest
``` ```
We will configure using a `jest.config.js` module in your project root. Do not use a JSON based configuration unless you know that you won't need the dynamic code used in some later steps here. We will configure using a `jest.config.js` module in your project root. Do not use a JSON based configuration unless you know that you won't need the dynamic code used in some later steps here.
@ -639,7 +639,7 @@ In Nexus Framework there was a testing module for system testing your app. You n
6. You will need a way to run your app in tests. Here is one way [adapted from the tutorial](https://todo.com). 6. You will need a way to run your app in tests. Here is one way [adapted from the tutorial](https://todo.com).
```tsx ```tsx
npm add -D graphql-request npm install -D graphql-request
``` ```
```tsx ```tsx
@ -688,7 +688,7 @@ Nexus Framework had a CLI for scaffolding new projects. You can approximate this
Nexus Framework has a gradual settings API with features such as automatic mapping of environment variables to settings. You can use the [`setset`](https://github.com/jasonkuhrt/setset) package manually to gain back this functionality. Nexus Framework has a gradual settings API with features such as automatic mapping of environment variables to settings. You can use the [`setset`](https://github.com/jasonkuhrt/setset) package manually to gain back this functionality.
```tsx ```tsx
npm add setset npm install setset
``` ```
```tsx ```tsx

View File

@ -13,8 +13,8 @@ If you're looking to migrate from the Nexus Framework over to Nexus and you're u
You will need to install the Prisma dependencies yourself now. You will need to install the Prisma dependencies yourself now.
``` ```
npm add -D @prisma/cli npm install -D @prisma/cli
npm add @prisma/client npm install @prisma/client
``` ```
You must also make sure that you are using the version `0.20` or later of `nexus-plugin-prisma`. You must also make sure that you are using the version `0.20` or later of `nexus-plugin-prisma`.
@ -82,7 +82,7 @@ export const schema = makeSchema({
### Configuring Context Type ### Configuring Context Type
The framework used to automatically inject and type your context so that an instance of the client would be there for you. We know need to configure that manually. To do so, we'll first create a `context.ts` file where we'll export a type containing the Prisma Client. The framework used to automatically inject and type your context so that an instance of the client would be there for you. We now need to configure that manually. To do so, we'll first create a `context.ts` file where we'll export a type containing the Prisma Client.
```tsx ```tsx
// context.ts // context.ts
@ -128,7 +128,7 @@ If your Prisma Schema is using either the `Json` or `DateTime` type, the framewo
```bash ```bash
npm install graphql-scalars npm install graphql-scalars
``` ```xf
2. Then, add the following configuration property to the Prisma plugin 2. Then, add the following configuration property to the Prisma plugin

View File

@ -22,8 +22,8 @@ Check out the [example projects](https://github.com/graphql-nexus/nexus/tree/mai
## Installation ## Installation
```sh ```sh
npm add nexus npm install nexus
npm add graphql # required as a peer dependency npm install graphql # required as a peer dependency
``` ```
If you are using TypeScript version `4.1` is suggested. Nexus doesn't have a hard requirement for it yet but may in a future non-breaking release. If you are using TypeScript version `4.1` is suggested. Nexus doesn't have a hard requirement for it yet but may in a future non-breaking release.

View File

@ -214,8 +214,7 @@ const Footer = ({ footerProps }: FooterViewProps) => {
} }
</div> </div>
<p className="social-text">Prisma © 2018-2020</p> <p className="social-text">Tim Griesser © 2018-2022</p>
<p>Made with in Berlin</p>
</div> </div>
</SocialWrapper> </SocialWrapper>
</div> </div>

View File

@ -34,6 +34,6 @@ npm run examples
- [apollo-fullstack](apollo-fullstack) - [apollo-fullstack](apollo-fullstack)
- [star-wars](star-wars) - [star-wars](star-wars)
- [kitchen-sink](kitchen-sink) - [kitchen-sink](kitchen-sink)
- [nexus-prisma](nexus-prisma) - [with-prisma](with-prisma)
> The `nexus-prisma` example is not included when all examples are ran at once. You can try it by following the instructions in its [README](nexus-prisma) instead. > The `with-prisma` example is not included when all examples are ran at once. You can try it by following the instructions in its [README](with-prisma) instead.

View File

@ -22,7 +22,7 @@
"apollo-server-testing": "^2.18.1", "apollo-server-testing": "^2.18.1",
"dedent": "^0.7.0", "dedent": "^0.7.0",
"fullstack-tutorial": "apollographql/fullstack-tutorial.git", "fullstack-tutorial": "apollographql/fullstack-tutorial.git",
"graphql": "^15.3.0", "graphql": "^16.3.0",
"graphql-tools": "^4.0.7", "graphql-tools": "^4.0.7",
"isemail": "^3.2.0", "isemail": "^3.2.0",
"nexus": "^1.0.0", "nexus": "^1.0.0",
@ -39,6 +39,6 @@
"node-fetch": "^2.2.1", "node-fetch": "^2.2.1",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"ts-node-dev": "^1.0.0-pre.30", "ts-node-dev": "^1.0.0-pre.30",
"typescript": "^3.9" "typescript": "^4.5.5"
} }
} }

View File

@ -1,4 +1,4 @@
import { idArg, list, nonNull, nullable, objectType, stringArg } from 'nexus' import { idArg, list, nonNull, objectType, stringArg } from 'nexus'
export const Mutation = objectType({ export const Mutation = objectType({
name: 'Mutation', name: 'Mutation',

View File

@ -3169,6 +3169,11 @@ graphql@^15.3.0:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278"
integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==
graphql@^16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
growly@^1.2.0, growly@^1.3.0: growly@^1.2.0, growly@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -6799,10 +6804,10 @@ type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.24" mime-types "~2.1.24"
typescript@^3.9: typescript@^4.5.5:
version "3.9.7" version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
uglify-js@^3.1.4: uglify-js@^3.1.4:
version "3.4.9" version "3.4.9"

View File

@ -9,7 +9,7 @@
"dataloader": "tgriesser/dataloader.git#ts-types", "dataloader": "tgriesser/dataloader.git#ts-types",
"express": "^4.16.4", "express": "^4.16.4",
"ghost": "^3.35.3", "ghost": "^3.35.3",
"graphql": "^15.3.0", "graphql": "^16.3.0",
"graphql-scalars": "^1.2.6", "graphql-scalars": "^1.2.6",
"graphql-tools": "^4.0.7", "graphql-tools": "^4.0.7",
"knex": "^0.19.5", "knex": "^0.19.5",
@ -27,6 +27,6 @@
"prettier": "^1.19.1", "prettier": "^1.19.1",
"ts-node": "^8.0.2", "ts-node": "^8.0.2",
"ts-node-dev": "^1.0.0-pre.30", "ts-node-dev": "^1.0.0-pre.30",
"typescript": "^3.9" "typescript": "^4.5.5"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@
"dependencies": { "dependencies": {
"apollo-server": "^2.18.1", "apollo-server": "^2.18.1",
"githunt-api": "peggyrayzis/GitHunt-API", "githunt-api": "peggyrayzis/GitHunt-API",
"graphql": "^14.5.8", "graphql": "^16.3.0",
"graphql-tools": "^4.0.7", "graphql-tools": "^4.0.7",
"nexus": "^1.0.0" "nexus": "^1.0.0"
}, },
@ -14,6 +14,6 @@
"nodemon": "^1.18.6", "nodemon": "^1.18.6",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"sqlite3": "^4.1.0", "sqlite3": "^4.1.0",
"typescript": "^3.9" "typescript": "^4.5.5"
} }
} }

View File

@ -2962,18 +2962,16 @@ graphql-upload@^8.0.2:
dependencies: dependencies:
iterall "^1.1.0" iterall "^1.1.0"
graphql@^14.5.8:
version "14.5.8"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.5.8.tgz#504f3d3114cb9a0a3f359bbbcf38d9e5bf6a6b3c"
integrity sha512-MMwmi0zlVLQKLdGiMfWkgQD7dY/TUKt4L+zgJ/aR0Howebod3aNgP5JkgvAULiR2HPVZaP2VEElqtdidHweLkg==
dependencies:
iterall "^1.2.2"
graphql@^15.3.0: graphql@^15.3.0:
version "15.3.0" version "15.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278"
integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==
graphql@^16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
har-schema@^2.0.0: har-schema@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@ -3516,7 +3514,7 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
iterall@^1.1.0, iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2: iterall@^1.1.0, iterall@^1.1.3, iterall@^1.2.1:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7"
integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==
@ -5588,10 +5586,10 @@ type-is@~1.6.14, type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.24" mime-types "~2.1.24"
typescript@^3.9: typescript@^4.5.5:
version "3.9.7" version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
uid-safe@~2.1.4: uid-safe@~2.1.4:
version "2.1.5" version "2.1.5"

View File

@ -19,24 +19,24 @@ interface Baz {
type BooleanConnection { type BooleanConnection {
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types https://relay.dev/graphql/connections.htm#sec-Edge-Types
""" """
edges: [BooleanEdge] edges: [BooleanEdge]
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo
""" """
pageInfo: PageInfo! pageInfo: PageInfo!
} }
type BooleanEdge { type BooleanEdge {
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Cursor https://relay.dev/graphql/connections.htm#sec-Cursor
""" """
cursor: String! cursor: String!
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Node https://relay.dev/graphql/connections.htm#sec-Node
""" """
node: Boolean node: Boolean
} }
@ -49,24 +49,24 @@ scalar Date
type DateConnection { type DateConnection {
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types https://relay.dev/graphql/connections.htm#sec-Edge-Types
""" """
edges: [DateEdge] edges: [DateEdge]
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo
""" """
pageInfo: PageInfo! pageInfo: PageInfo!
} }
type DateEdge { type DateEdge {
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Cursor https://relay.dev/graphql/connections.htm#sec-Cursor
""" """
cursor: String! cursor: String!
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Node https://relay.dev/graphql/connections.htm#sec-Node
""" """
node: Date node: Date
} }
@ -104,7 +104,7 @@ interface Node {
} }
""" """
PageInfo cursor, as defined in https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo PageInfo cursor, as defined in https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo
""" """
type PageInfo { type PageInfo {
""" """
@ -306,24 +306,24 @@ type User {
type UserConnection { type UserConnection {
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types https://relay.dev/graphql/connections.htm#sec-Edge-Types
""" """
edges: [UserEdge] edges: [UserEdge]
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo
""" """
pageInfo: PageInfo! pageInfo: PageInfo!
} }
type UserEdge { type UserEdge {
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Cursor https://relay.dev/graphql/connections.htm#sec-Cursor
""" """
cursor: String! cursor: String!
""" """
https://facebook.github.io/relay/graphql/connections.htm#sec-Node https://relay.dev/graphql/connections.htm#sec-Node
""" """
node: User node: User
} }

View File

@ -10,7 +10,7 @@
}, },
"dependencies": { "dependencies": {
"apollo-server": "^2.18.1", "apollo-server": "^2.18.1",
"graphql": "^15.3.0", "graphql": "^16.3.0",
"graphql-query-complexity": "^0.4.1", "graphql-query-complexity": "^0.4.1",
"graphql-relay": "^0.6.0", "graphql-relay": "^0.6.0",
"graphql-subscriptions": "^1.0.0", "graphql-subscriptions": "^1.0.0",
@ -19,7 +19,7 @@
"nexus": "^1.0.0", "nexus": "^1.0.0",
"subscriptions-transport-ws": "^0.9.15", "subscriptions-transport-ws": "^0.9.15",
"ts-node": "^8.5.4", "ts-node": "^8.5.4",
"typescript": "^3.9" "typescript": "^4.5.5"
}, },
"devDependencies": { "devDependencies": {
"@types/graphql-relay": "^0.4.11", "@types/graphql-relay": "^0.4.11",

View File

@ -21,7 +21,11 @@ const schema = makeSchema({
types, types,
outputs: { outputs: {
schema: path.join(__dirname, '../kitchen-sink-schema.graphql'), schema: path.join(__dirname, '../kitchen-sink-schema.graphql'),
typegen: path.join(__dirname, './kitchen-sink.gen.ts'), typegen: {
outputPath: path.join(__dirname, './kitchen-sink.gen.ts'),
globalsPath: path.join(__dirname, './kitchen-sink-globals.gen.ts'),
declareInputs: true,
},
}, },
plugins: [ plugins: [
NodePlugin, NodePlugin,

View File

@ -391,9 +391,17 @@ export const MoreQueryFields = extendType({
export const DateScalar = scalarType({ export const DateScalar = scalarType({
name: 'Date', name: 'Date',
serialize: (value) => value.getTime(), serialize: (value) => (value as Date).toISOString(),
parseValue: (value) => new Date(value), parseValue: (value) => new Date(value as string | number),
parseLiteral: (ast) => (ast.kind === 'IntValue' ? new Date(ast.value) : null), parseLiteral: (ast) => {
if (ast.kind === 'IntValue' || ast.kind === 'StringValue') {
const d = new Date(ast.value)
if (!isNaN(d.valueOf())) {
return d
}
}
throw new Error('Invalid date')
},
asNexusMethod: 'date', asNexusMethod: 'date',
sourceType: 'Date', sourceType: 'Date',
}) })

View File

@ -0,0 +1,65 @@
/** This file was generated by Nexus Schema Do not make changes to this file directly */
import type { NexusGenTypes } from './kitchen-sink.gen'
declare global {
interface NexusGenCustomInputMethods<TypeName extends string> {
date<FieldName extends string>(
fieldName: FieldName,
opts?: core.CommonInputFieldConfig<TypeName, FieldName>
): void // "Date";
}
}
declare global {
interface NexusGenCustomOutputMethods<TypeName extends string> {
date<FieldName extends string>(
fieldName: FieldName,
...opts: core.ScalarOutSpread<TypeName, FieldName>
): void // "Date";
/**
* Adds a Relay-style connection to the type, with numerous options for configuration
*
* @see https://nexusjs.org/docs/plugins/connection
*/
connectionField<FieldName extends string>(
fieldName: FieldName,
config: connectionPluginCore.ConnectionFieldConfig<TypeName, FieldName>
): void
}
}
declare global {
interface NexusGen extends NexusGenTypes {}
}
import type { core, connectionPluginCore } from 'nexus'
import type { QueryComplexity } from 'nexus/dist/plugins/queryComplexityPlugin'
import type { FieldAuthorizeResolver } from 'nexus/dist/plugins/fieldAuthorizePlugin'
declare global {
interface NexusGenPluginTypeConfig<TypeName extends string> {
node?: string | core.FieldResolver<TypeName, any>
}
interface NexusGenPluginInputTypeConfig<TypeName extends string> {}
interface NexusGenPluginFieldConfig<TypeName extends string, FieldName extends string> {
/**
* The complexity for an individual field. Return a number or a function that returns a number to specify
* the complexity for this field.
*/
complexity?: QueryComplexity<TypeName, FieldName>
/**
* Authorization for an individual field. Returning "true" or "Promise<true>" means the field can be
* accessed. Returning "false" or "Promise<false>" will respond with a "Not Authorized" error for the
* field. Returning or throwing an error will also prevent the resolver from executing.
*/
authorize?: FieldAuthorizeResolver<TypeName, FieldName>
/**
* The nullability guard can be helpful, but is also a potentially expensive operation for lists. We need
* to iterate the entire list to check for null items to guard against. Set this to true to skip the null
* guard on a specific field if you know there's no potential for unsafe types.
*/
skipNullGuard?: boolean
}
interface NexusGenPluginInputFieldConfig<TypeName extends string, FieldName extends string> {}
interface NexusGenPluginSchemaConfig {}
interface NexusGenPluginArgConfig {}
}

View File

@ -1,61 +1,34 @@
/** This file was generated by Nexus Schema Do not make changes to this file directly */ /** This file was generated by Nexus Schema Do not make changes to this file directly */
import { UnusedInterfaceTypeDef } from './kitchen-sink-definitions' import type { UnusedInterfaceTypeDef } from './kitchen-sink-definitions'
import { core, connectionPluginCore } from 'nexus' import type { core } from 'nexus'
import { QueryComplexity } from 'nexus/dist/plugins/queryComplexityPlugin'
import { FieldAuthorizeResolver } from 'nexus/dist/plugins/fieldAuthorizePlugin' export interface InputType {
declare global { answer?: number | null // Int
interface NexusGenCustomInputMethods<TypeName extends string> { key: string // String!
date<FieldName extends string>( nestedInput?: InputType2 | null // InputType2
fieldName: FieldName,
opts?: core.CommonInputFieldConfig<TypeName, FieldName>
): void // "Date";
}
}
declare global {
interface NexusGenCustomOutputMethods<TypeName extends string> {
date<FieldName extends string>(
fieldName: FieldName,
...opts: core.ScalarOutSpread<TypeName, FieldName>
): void // "Date";
/**
* Adds a Relay-style connection to the type, with numerous options for configuration
*
* @see https://nexusjs.org/docs/plugins/connection
*/
connectionField<FieldName extends string>(
fieldName: FieldName,
config: connectionPluginCore.ConnectionFieldConfig<TypeName, FieldName>
): void
}
} }
declare global { export interface InputType2 {
interface NexusGen extends NexusGenTypes {} answer?: number | null // Int
key: string // String!
someDate: NexusGenScalars['Date'] // Date!
}
export interface NestedType {
veryNested?: string | null // String
}
export interface SomeArg {
arg?: NestedType | null // NestedType
someField?: string | null // String
} }
export interface NexusGenInputs { export interface NexusGenInputs {
InputType: { InputType: InputType
// input type InputType2: InputType2
answer?: number | null // Int NestedType: NestedType
key: string // String! SomeArg: SomeArg
nestedInput?: NexusGenInputs['InputType2'] | null // InputType2
}
InputType2: {
// input type
answer?: number | null // Int
key: string // String!
someDate: NexusGenScalars['Date'] // Date!
}
NestedType: {
// input type
veryNested?: string | null // String
}
SomeArg: {
// input type
arg?: NexusGenInputs['NestedType'] | null // NestedType
someField?: string | null // String
}
} }
export interface NexusGenEnums {} export interface NexusGenEnums {}
@ -390,100 +363,117 @@ export interface NexusGenFieldTypeNames {
} }
} }
export interface FooArgsTestArgs {
a: InputType | null // InputType
}
export interface MutationSomeMutationFieldArgs {
id: string // ID!
}
export interface QueryAsArgExampleArgs {
testAsArg: InputType // InputType!
}
export interface QueryBooleanConnectionArgs {
after?: string | null // String
first: number // Int!
}
export interface QueryComplexQueryArgs {
count: number // Int!
}
export interface QueryDeprecatedConnectionArgs {
after?: string | null // String
before?: string | null // String
first?: number | null // Int
last?: number | null // Int
}
export interface QueryGetNumberOrNullArgs {
a: number // Int!
}
export interface QueryGuardedConnectionArgs {
after?: string | null // String
first: number // Int!
}
export interface QueryInlineArgsArgs {
someArg?: SomeArg | null // SomeArg
}
export interface QueryInputAsArgExampleArgs {
testInput?: InputType | null // InputType
testScalar?: string | null // String
}
export interface QueryUserConnectionAdditionalArgsArgs {
after?: string | null // String
first: number // Int!
isEven?: boolean | null // Boolean
}
export interface QueryUserConnectionBackwardOnlyArgs {
before?: string | null // String
last: number // Int!
}
export interface QueryUserConnectionForwardOnlyArgs {
after?: string | null // String
first: number // Int!
}
export interface QueryUsersConnectionNodesArgs {
after?: string | null // String
before?: string | null // String
first?: number | null // Int
last?: number | null // Int
}
export interface QueryUsersConnectionResolveArgs {
after?: string | null // String
before?: string | null // String
first?: number | null // Int
last?: number | null // Int
}
export interface TestObjArgsTestArgs {
a: InputType | null // InputType
}
export interface BarArgsTestArgs {
a: InputType | null // InputType
}
export interface NexusGenArgTypes { export interface NexusGenArgTypes {
Foo: { Foo: {
argsTest: { argsTest: FooArgsTestArgs
// args
a: NexusGenInputs['InputType'] | null // InputType
}
} }
Mutation: { Mutation: {
someMutationField: { someMutationField: MutationSomeMutationFieldArgs
// args
id: string // ID!
}
} }
Query: { Query: {
asArgExample: { asArgExample: QueryAsArgExampleArgs
// args booleanConnection: QueryBooleanConnectionArgs
testAsArg: NexusGenInputs['InputType'] // InputType! complexQuery: QueryComplexQueryArgs
} deprecatedConnection: QueryDeprecatedConnectionArgs
booleanConnection: { getNumberOrNull: QueryGetNumberOrNullArgs
// args guardedConnection: QueryGuardedConnectionArgs
after?: string | null // String inlineArgs: QueryInlineArgsArgs
first: number // Int! inputAsArgExample: QueryInputAsArgExampleArgs
} userConnectionAdditionalArgs: QueryUserConnectionAdditionalArgsArgs
complexQuery: { userConnectionBackwardOnly: QueryUserConnectionBackwardOnlyArgs
// args userConnectionForwardOnly: QueryUserConnectionForwardOnlyArgs
count: number // Int! usersConnectionNodes: QueryUsersConnectionNodesArgs
} usersConnectionResolve: QueryUsersConnectionResolveArgs
deprecatedConnection: {
// args
after?: string | null // String
before?: string | null // String
first?: number | null // Int
last?: number | null // Int
}
getNumberOrNull: {
// args
a: number // Int!
}
guardedConnection: {
// args
after?: string | null // String
first: number // Int!
}
inlineArgs: {
// args
someArg?: NexusGenInputs['SomeArg'] | null // SomeArg
}
inputAsArgExample: {
// args
testInput?: NexusGenInputs['InputType'] | null // InputType
testScalar?: string | null // String
}
userConnectionAdditionalArgs: {
// args
after?: string | null // String
first: number // Int!
isEven?: boolean | null // Boolean
}
userConnectionBackwardOnly: {
// args
before?: string | null // String
last: number // Int!
}
userConnectionForwardOnly: {
// args
after?: string | null // String
first: number // Int!
}
usersConnectionNodes: {
// args
after?: string | null // String
before?: string | null // String
first?: number | null // Int
last?: number | null // Int
}
usersConnectionResolve: {
// args
after?: string | null // String
before?: string | null // String
first?: number | null // Int
last?: number | null // Int
}
} }
TestObj: { TestObj: {
argsTest: { argsTest: TestObjArgsTestArgs
// args
a: NexusGenInputs['InputType'] | null // InputType
}
} }
Bar: { Bar: {
argsTest: { argsTest: BarArgsTestArgs
// args
a: NexusGenInputs['InputType'] | null // InputType
}
} }
} }
@ -554,99 +544,3 @@ export interface NexusGenTypes {
abstractsUsingStrategyResolveType: NexusGenAbstractsUsingStrategyResolveType abstractsUsingStrategyResolveType: NexusGenAbstractsUsingStrategyResolveType
features: NexusGenFeaturesConfig features: NexusGenFeaturesConfig
} }
declare global {
interface NexusGenPluginTypeConfig<TypeName extends string> {
node?: string | core.FieldResolver<TypeName, any>
}
interface NexusGenPluginFieldConfig<TypeName extends string, FieldName extends string> {
/**
* The complexity for an individual field. Return a number or a function that returns a number to specify
* the complexity for this field.
*/
complexity?: QueryComplexity<TypeName, FieldName>
/**
* Authorization for an individual field. Returning "true" or "Promise<true>" means the field can be
* accessed. Returning "false" or "Promise<false>" will respond with a "Not Authorized" error for the
* field. Returning or throwing an error will also prevent the resolver from executing.
*/
authorize?: FieldAuthorizeResolver<TypeName, FieldName>
/**
* The nullability guard can be helpful, but is also a potentially expensive operation for lists. We need
* to iterate the entire list to check for null items to guard against. Set this to true to skip the null
* guard on a specific field if you know there's no potential for unsafe types.
*/
skipNullGuard?: boolean
/**
* Whether the type can be null
*
* @default (depends on whether nullability is configured in type or schema)
* @see declarativeWrappingPlugin
*/
nullable?: boolean
/**
* Whether the type is list of values, or just a single value. If list is true, we assume the type is a
* list. If list is an array, we'll assume that it's a list with the depth. The boolean indicates whether
* the type is required (non-null), where true = nonNull, false = nullable.
*
* @see declarativeWrappingPlugin
*/
list?: true | boolean[]
/**
* Whether the type should be non null, `required: true` = `nullable: false`
*
* @default (depends on whether nullability is configured in type or schema)
* @see declarativeWrappingPlugin
*/
required?: boolean
}
interface NexusGenPluginInputFieldConfig<TypeName extends string, FieldName extends string> {
/**
* Whether the type can be null
*
* @default (depends on whether nullability is configured in type or schema)
* @see declarativeWrappingPlugin
*/
nullable?: boolean
/**
* Whether the type is list of values, or just a single value. If list is true, we assume the type is a
* list. If list is an array, we'll assume that it's a list with the depth. The boolean indicates whether
* the type is required (non-null), where true = nonNull, false = nullable.
*
* @see declarativeWrappingPlugin
*/
list?: true | boolean[]
/**
* Whether the type should be non null, `required: true` = `nullable: false`
*
* @default (depends on whether nullability is configured in type or schema)
* @see declarativeWrappingPlugin
*/
required?: boolean
}
interface NexusGenPluginSchemaConfig {}
interface NexusGenPluginArgConfig {
/**
* Whether the type can be null
*
* @default (depends on whether nullability is configured in type or schema)
* @see declarativeWrappingPlugin
*/
nullable?: boolean
/**
* Whether the type is list of values, or just a single value. If list is true, we assume the type is a
* list. If list is an array, we'll assume that it's a list with the depth. The boolean indicates whether
* the type is required (non-null), where true = nonNull, false = nullable.
*
* @see declarativeWrappingPlugin
*/
list?: true | boolean[]
/**
* Whether the type should be non null, `required: true` = `nullable: false`
*
* @default (depends on whether nullability is configured in type or schema)
* @see declarativeWrappingPlugin
*/
required?: boolean
}
}

View File

@ -2140,6 +2140,11 @@ graphql@^15.3.0:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278"
integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==
graphql@^16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
growly@^1.2.0, growly@^1.3.0: growly@^1.2.0, growly@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -5070,10 +5075,10 @@ type-is@~1.6.16:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.18" mime-types "~2.1.18"
typescript@^3.9: typescript@^4.5.5:
version "3.9.7" version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
uglify-js@^3.1.4: uglify-js@^3.1.4:
version "3.4.9" version "3.4.9"

View File

@ -6,7 +6,7 @@
}, },
"dependencies": { "dependencies": {
"apollo-server": "^2.18.1", "apollo-server": "^2.18.1",
"graphql": "^15.3.0", "graphql": "^16.3.0",
"graphql-tools": "^4.0.7", "graphql-tools": "^4.0.7",
"nexus": "^1.0.0", "nexus": "^1.0.0",
"ts-node-dev": "^1.0.0-pre.30" "ts-node-dev": "^1.0.0-pre.30"
@ -15,6 +15,6 @@
"jest": "^23.6.0", "jest": "^23.6.0",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"ts-jest": "^24.1.0", "ts-jest": "^24.1.0",
"typescript": "^3.9" "typescript": "^4.5.5"
} }
} }

View File

@ -2097,6 +2097,11 @@ graphql@^15.3.0:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278"
integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==
graphql@^16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
growly@^1.2.0, growly@^1.3.0: growly@^1.2.0, growly@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -5006,10 +5011,10 @@ type-is@~1.6.16:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.18" mime-types "~2.1.18"
typescript@^3.9: typescript@^4.5.5:
version "3.9.7" version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
uglify-js@^3.1.4: uglify-js@^3.1.4:
version "3.4.9" version "3.4.9"

View File

@ -8,11 +8,11 @@
"dependencies": { "dependencies": {
"apollo-server": "^2.18.1", "apollo-server": "^2.18.1",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"graphql": "^15.3.0", "graphql": "^16.3.0",
"graphql-tools": "^4.0.7", "graphql-tools": "^4.0.7",
"nexus": "^1.0.0", "nexus": "^1.0.0",
"ts-node-dev": "1.0.0-pre.31", "ts-node-dev": "1.0.0-pre.31",
"typescript": "^3.9" "typescript": "^4.5.5"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^5.0.4" "@types/fs-extra": "^5.0.4"

View File

@ -1,4 +1,5 @@
import { objectType, arg, list, nullable } from 'nexus' import { objectType, arg, list, nullable, core } from 'nexus'
import ts from 'typescript'
import { nodeType, functionLikeDeclaration, hasTypeParameters } from './mixins' import { nodeType, functionLikeDeclaration, hasTypeParameters } from './mixins'
import { filteredNodesList } from './utils' import { filteredNodesList } from './utils'
@ -21,7 +22,7 @@ export const SourceFile = objectType({
t.list.field('statements', { t.list.field('statements', {
type: 'Node', type: 'Node',
args: nodeSkipSyntax, args: nodeSkipSyntax,
resolve: (root, args) => filteredNodesList(args, Array.from(root.statements)), resolve: (root, args) => filteredNodesList<ts.Statement>(args, Array.from(root.statements)),
}) })
}, },
}) })
@ -156,7 +157,7 @@ export const ClassDeclaration = objectType({
t.list.field('members', { t.list.field('members', {
type: 'Node', type: 'Node',
args: nodeSkipSyntax, args: nodeSkipSyntax,
resolve: (root, args) => filteredNodesList(args, Array.from(root.members)), resolve: (root, args) => filteredNodesList<ts.ClassElement>(args, Array.from(root.members)),
}) })
}, },
}) })

View File

@ -1,5 +1,5 @@
import { arg, interfaceType, list, nullable } from 'nexus' import { arg, interfaceType, list, nullable } from 'nexus'
import { JSDoc, SyntaxKind } from 'typescript' import ts, { JSDoc, SyntaxKind } from 'typescript'
import { allKnownNodes, syntaxKindFilter } from './utils' import { allKnownNodes, syntaxKindFilter } from './utils'
const syntaxKindArgs = { const syntaxKindArgs = {
@ -50,7 +50,7 @@ export const Node = interfaceType({
if (!root.modifiers) { if (!root.modifiers) {
return null return null
} }
return syntaxKindFilter(args, Array.from(root.modifiers)) return syntaxKindFilter<ts.Modifier>(args, Array.from(root.modifiers))
}, },
}) })
t.field('parent', { type: 'Node' }) t.field('parent', { type: 'Node' })
@ -78,7 +78,7 @@ export const JSDocInterface = interfaceType({
resolve(root) { resolve(root) {
if ('jsDoc' in root) { if ('jsDoc' in root) {
// https://github.com/Microsoft/TypeScript/issues/19856 // https://github.com/Microsoft/TypeScript/issues/19856
return ((root as unknown) as { jsDoc: JSDoc[] }).jsDoc return (root as unknown as { jsDoc: JSDoc[] }).jsDoc
} }
return null return null
}, },

View File

@ -50,7 +50,7 @@ export const TupleTypeNode = objectType({
name: 'TupleTypeNode', name: 'TupleTypeNode',
definition(t) { definition(t) {
nodeType(t) nodeType(t)
t.list.field('elementTypes', { type: 'Node' }) t.list.field('elements', { type: 'Node' })
}, },
}) })

View File

@ -1,72 +1,69 @@
import { GraphQLSchema, isObjectType, GraphQLResolveInfo } from "graphql"; import { GraphQLSchema, isObjectType, GraphQLResolveInfo } from 'graphql'
import ts from "typescript"; import ts from 'typescript'
import { NexusGenArgTypes } from "../ts-ast-reader-typegen"; import { NexusGenArgTypes } from '../ts-ast-reader-typegen'
export function convertTsEnum(toConvert: any) { export function convertTsEnum(toConvert: any) {
const converted: { [key: string]: number } = {}; const converted: { [key: string]: number } = {}
Object.keys(toConvert).forEach((key) => { Object.keys(toConvert).forEach((key) => {
if (/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(key)) { if (/^[_a-zA-Z][_a-zA-Z0-9]*$/.test(key)) {
converted[key] = toConvert[key]; converted[key] = toConvert[key]
} }
}); })
return converted; return converted
} }
const knownNodesMap = new WeakMap<GraphQLSchema, Set<string>>(); const knownNodesMap = new WeakMap<GraphQLSchema, Set<string>>()
export function allKnownNodes(schema: GraphQLSchema) { export function allKnownNodes(schema: GraphQLSchema) {
let knownNodes = knownNodesMap.get(schema); let knownNodes = knownNodesMap.get(schema)
if (!knownNodes) { if (!knownNodes) {
const fields = schema.getTypeMap(); const fields = schema.getTypeMap()
knownNodes = new Set(); knownNodes = new Set()
Object.keys(fields).forEach((key) => { Object.keys(fields).forEach((key) => {
const field = fields[key]; const field = fields[key]
if ( if (isObjectType(field) && Boolean(field.getInterfaces().find((i) => i.name === 'Node'))) {
isObjectType(field) && knownNodes!.add(field.name)
Boolean(field.getInterfaces().find((i) => i.name === "Node"))
) {
knownNodes!.add(field.name);
} }
}); })
knownNodesMap.set(schema, knownNodes); knownNodesMap.set(schema, knownNodes)
} }
return knownNodes!; return knownNodes!
} }
export const filteredNodesList = <T extends ts.Node>( export const filteredNodesList = <T extends ts.Node>(
args: NexusGenArgTypes["SourceFile"]["statements"], args: NexusGenArgTypes['SourceFile']['statements'],
nodes: T[] nodes: T[]
) => { ): T[] => {
const { skip, only } = args; const { skip, only } = args
if ((skip && skip.length) || (only && only.length)) { if ((skip && skip.length) || (only && only.length)) {
return nodes.filter((node: ts.Node) => { return nodes.filter((node: ts.Node) => {
if (skip && skip.length > 0 && skip.indexOf(node.kind) !== -1) { if (skip && skip.length > 0 && skip.indexOf(node.kind) !== -1) {
return; return
} }
if (only && only.length > 0 && only.indexOf(node.kind) === -1) { if (only && only.length > 0 && only.indexOf(node.kind) === -1) {
return; return
} }
return node; return node
}); })
} }
return nodes; return nodes
}; }
export const syntaxKindFilter = <T extends { kind: ts.SyntaxKind }>( export const syntaxKindFilter = <T extends { kind: ts.SyntaxKind }>(
args: NexusGenArgTypes["Node"]["modifiers"], args: NexusGenArgTypes['Node']['modifiers'],
items: T[] items: T[]
) => { ) => {
const { skip, only } = args; const { skip, only } = args
if ((only && only.length) || (skip && skip.length)) { if ((only && only.length) || (skip && skip.length)) {
return items.filter((item) => { return items.filter((item) => {
if (only && only.length && only.indexOf(item.kind) === -1) { if (only && only.length && only.indexOf(item.kind) === -1) {
return null; return null
} }
if (skip && skip.length && skip.indexOf(item.kind) !== -1) { if (skip && skip.length && skip.indexOf(item.kind) !== -1) {
return null; return null
} }
return item; return item
}); })
} }
return items; return items
}; }

View File

@ -1113,6 +1113,11 @@ graphql@^15.3.0:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278"
integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w==
graphql@^16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
growly@^1.2.0: growly@^1.2.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -2164,10 +2169,10 @@ type-is@~1.6.16:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.18" mime-types "~2.1.18"
typescript@^3.9: typescript@^4.5.5:
version "3.9.7" version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
universalify@^0.1.0: universalify@^0.1.0:
version "0.1.2" version "0.1.2"

View File

@ -20,13 +20,13 @@
"@prisma/client": "2.14.0", "@prisma/client": "2.14.0",
"apollo-server-express": "^2.19.1", "apollo-server-express": "^2.19.1",
"express": "^4.17.1", "express": "^4.17.1",
"graphql": "^15.3.0", "graphql": "^16.3.0",
"nexus": "^1.0.0" "nexus": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@prisma/cli": "^2.14.0", "@prisma/cli": "^2.14.0",
"prettier": "2.2.1", "prettier": "2.2.1",
"ts-node-dev": "^1.0.0-pre.62", "ts-node-dev": "^1.0.0-pre.62",
"typescript": "^4.0.2" "typescript": "^4.5.5"
} }
} }

View File

@ -1015,6 +1015,11 @@ graphql@^15.3.0:
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.4.0.tgz#e459dea1150da5a106486ba7276518b5295a4347" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.4.0.tgz#e459dea1150da5a106486ba7276518b5295a4347"
integrity sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA== integrity sha512-EB3zgGchcabbsU9cFe1j+yxdzKQKAbGUWRb13DsrsMN1yyfmmIq+2+L5MqVWcDCE4V89R5AyUOi7sMOGxdsYtA==
graphql@^16.3.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
has-symbols@^1.0.1: has-symbols@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
@ -1814,10 +1819,10 @@ type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.24" mime-types "~2.1.24"
typescript@^4.0.2: typescript@^4.5.5:
version "4.1.3" version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
unpipe@1.0.0, unpipe@~1.0.0: unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0" version "1.0.0"

View File

@ -13,13 +13,13 @@
"trailingComma": "all" "trailingComma": "all"
}, },
"dependencies": { "dependencies": {
"graphql": "^15.3.0", "graphql": "^16.3.0",
"nexus": "^1.0.0" "nexus": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@now/node": "^1.0.1", "@now/node": "^1.0.1",
"@types/node": "^12.11.1", "@types/node": "^12.11.1",
"ts-node": "^8.4.1", "ts-node": "^8.4.1",
"typescript": "^3.9" "typescript": "^4.5.5"
} }
} }

View File

@ -29,10 +29,10 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
graphql@^15.3.0: graphql@^16.3.0:
version "15.3.0" version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==
iterall@^1.3.0: iterall@^1.3.0:
version "1.3.0" version "1.3.0"
@ -81,10 +81,10 @@ tslib@^2.0.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==
typescript@^3.9: typescript@^4.5.5:
version "3.9.7" version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
yn@^3.0.0: yn@^3.0.0:
version "3.1.1" version "3.1.1"

View File

@ -1,6 +1,6 @@
{ {
"name": "nexus", "name": "nexus",
"version": "0.0.0-dripip", "version": "1.3.0",
"description": "Scalable, strongly typed GraphQL schema development", "description": "Scalable, strongly typed GraphQL schema development",
"keywords": [ "keywords": [
"graphql", "graphql",
@ -46,7 +46,6 @@
"release:pr": "dripip pr", "release:pr": "dripip pr",
"release:preview": "dripip preview", "release:preview": "dripip preview",
"release:stable": "dripip stable", "release:stable": "dripip stable",
"pretest": "yarn patch-package",
"test": "yarn test:types && jest --testTimeout 10000", "test": "yarn test:types && jest --testTimeout 10000",
"test:ci": "yarn test:types && jest --maxWorkers 2 --coverage --testTimeout 10000", "test:ci": "yarn test:types && jest --maxWorkers 2 --coverage --testTimeout 10000",
"test:debug": "node --inspect-brk $(yarn bin)/jest -i --watch", "test:debug": "node --inspect-brk $(yarn bin)/jest -i --watch",
@ -62,12 +61,10 @@
}, },
"lint-staged": { "lint-staged": {
"*.{ts,js,graphql,json,css,md}": [ "*.{ts,js,graphql,json,css,md}": [
"prettier --write", "prettier --write"
"git add"
], ],
"*package.json": [ "*package.json": [
"sort-package-json", "sort-package-json"
"git add"
] ]
}, },
"dependencies": { "dependencies": {
@ -82,29 +79,28 @@
"@types/prettier": "^1.18.3", "@types/prettier": "^1.18.3",
"@typescript-eslint/eslint-plugin": "2.7.0", "@typescript-eslint/eslint-plugin": "2.7.0",
"dripip": "^0.10.0", "dripip": "^0.10.0",
"esbuild": "^0.14.48",
"eslint": "^6.6.0", "eslint": "^6.6.0",
"get-port": "^5.1.1", "get-port": "^5.1.1",
"graphql": "^15.3.0", "graphql": "^16.3.0",
"graphql-relay": "^0.6.0", "graphql-relay": "^0.10.0",
"graphql-scalars": "^1.2.6", "graphql-scalars": "^1.14.1",
"gulp": "4.0.2", "gulp": "4.0.2",
"husky": "^1.1.2", "husky": "^1.1.2",
"jest": "^26.6.3", "jest": "^26.6.3",
"jest-watch-typeahead": "^0.6.1", "jest-watch-typeahead": "^0.6.1",
"lint-staged": "^7.3.0", "lint-staged": "^12.4.1",
"patch-package": "6.2.2", "prettier": "^2.5.1",
"prettier": "^2.2.1",
"prettier-plugin-jsdoc": "^0.2.5",
"sort-package-json": "^1.22.1", "sort-package-json": "^1.22.1",
"ts-jest": "^26.4.4", "ts-jest": "^26.4.4",
"ts-morph": "^8.2.0", "ts-morph": "^13.0.3",
"ts-node": "^9.0.0", "ts-node": "^9.0.0",
"tsd": "^0.13.1", "tsd": "^0.13.1",
"tslint": "^5.11.0", "tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0", "tslint-config-prettier": "^1.15.0",
"typescript": "^4.1.2" "typescript": "^4.5.5"
}, },
"peerDependencies": { "peerDependencies": {
"graphql": "^14.5.8 || 15.x" "graphql": "15.x || 16.x"
} }
} }

View File

@ -1,13 +0,0 @@
diff --git a/node_modules/prettier-plugin-jsdoc/lib/parser.js b/node_modules/prettier-plugin-jsdoc/lib/parser.js
index 85b9e5d..3b98730 100644
--- a/node_modules/prettier-plugin-jsdoc/lib/parser.js
+++ b/node_modules/prettier-plugin-jsdoc/lib/parser.js
@@ -12,7 +12,7 @@ const stringify_1 = require("./stringify");
/** @link https://prettier.io/docs/en/api.html#custom-parser-api} */
exports.getParser = (parser) => function jsdocParser(text, parsers, options) {
const ast = parser(text, parsers, options);
- if (!options.jsdocParser) {
+ if (!options || !options.jsdocParser) {
return ast;
}
/**

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,9 @@
// The "core" is used as a namespace to re-export everything, // The "core" is used as a namespace to re-export everything,
// For anyone who wants to use the internals // For anyone who wants to use the internals
export * from './builder' export * from './builder'
export * from './makeSchema'
export * from './definitions/args' export * from './definitions/args'
export * from './definitions/directive'
export * from './definitions/decorateType' export * from './definitions/decorateType'
export * from './definitions/definitionBlocks' export * from './definitions/definitionBlocks'
export * from './definitions/enumType' export * from './definitions/enumType'
@ -20,12 +22,14 @@ export * from './definitions/queryType'
export * from './definitions/scalarType' export * from './definitions/scalarType'
export * from './definitions/subscriptionField' export * from './definitions/subscriptionField'
export * from './definitions/subscriptionType' export * from './definitions/subscriptionType'
export * from './definitions/nexusMeta'
export * from './definitions/unionType' export * from './definitions/unionType'
export * from './definitions/wrapping' export * from './definitions/wrapping'
export * from './definitions/_types' export * from './definitions/_types'
export * from './dynamicMethod' export * from './dynamicMethod'
export * from './plugin' export * from './plugin'
export * from './plugins' export * from './plugins'
export * from './rebuildType'
export * from './sdlConverter' export * from './sdlConverter'
export * from './typegenAutoConfig' export * from './typegenAutoConfig'
export * from './typegenFormatPrettier' export * from './typegenFormatPrettier'

View File

@ -13,6 +13,7 @@ import type {
GraphQLSchema, GraphQLSchema,
GraphQLUnionType, GraphQLUnionType,
} from 'graphql' } from 'graphql'
import type { NexusDirectiveConfig } from '../core'
import type { import type {
NexusFieldExtension, NexusFieldExtension,
NexusInputObjectTypeExtension, NexusInputObjectTypeExtension,
@ -25,7 +26,8 @@ import type { RequiredDeeply } from '../typeHelpersInternal'
export type { AbstractTypes } export type { AbstractTypes }
export type Maybe<T> = T | null /** Conveniently represents flow's "Maybe" type https://flow.org/en/docs/types/maybe/ */
export type Maybe<T> = null | undefined | T
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
@ -52,6 +54,8 @@ export enum NexusTypes {
PrintedGenTypingImport = 'PrintedGenTypingImport', PrintedGenTypingImport = 'PrintedGenTypingImport',
Scalar = 'Scalar', Scalar = 'Scalar',
Union = 'Union', Union = 'Union',
Directive = 'Directive',
DirectiveUse = 'DirectiveUse',
} }
export interface DeprecationInfo { export interface DeprecationInfo {
@ -154,8 +158,13 @@ export type NexusGraphQLInterfaceTypeConfig = WithExt<
NexusInterfaceTypeExtension NexusInterfaceTypeExtension
> & { interfaces: () => GraphQLInterfaceType[] } > & { interfaces: () => GraphQLInterfaceType[] }
export type NexusGraphQLSchema = Omit<GraphQLSchema, 'extensions'> & { export type NexusGraphQLDirectiveConfig = WithExt<NexusDirectiveConfig, NexusInputObjectTypeExtension>
extensions: { nexus: NexusSchemaExtension }
export interface NexusGraphQLSchema extends GraphQLSchema {
extensions: {
nexus: NexusSchemaExtension
[attributeName: string]: unknown
}
} }
export type NexusFeaturesInput = { export type NexusFeaturesInput = {
@ -178,12 +187,9 @@ export type NexusFeaturesInput = {
* using multiple strategies](https://nxs.li/guides/abstract-types/using-multiple-strategies) as there are * using multiple strategies](https://nxs.li/guides/abstract-types/using-multiple-strategies) as there are
* a few quirks to be aware of. * a few quirks to be aware of.
* *
* @default * @default {resolveType: true,
* {
* resolveType: true,
* __typename: false * __typename: false
* isTypeOf: false, * isTypeOf: false,}
* }
*/ */
abstractTypeStrategies?: { abstractTypeStrategies?: {
/** /**
@ -212,8 +218,8 @@ export type NexusFeaturesInput = {
* static typing. So as long as you are not ignoring static errors related to Nexus' abstract type type * static typing. So as long as you are not ignoring static errors related to Nexus' abstract type type
* checks then you then you should still have a safe implementation. * checks then you then you should still have a safe implementation.
* *
* Furthermore another effect is that statically the other strategies will not appear to be _required_, * Furthermore another effect is that statically the other strategies will not appear to be *required*,
* but instead _optional_, while only this one will appear required. However, upon implementing any of * but instead *optional*, while only this one will appear required. However, upon implementing any of
* the other strategies, this one will not longer be required. This quirk is explained in the guide * the other strategies, this one will not longer be required. This quirk is explained in the guide
* section about [using multiple strategies](https://nxs.li/guides/abstract-types/using-multiple-strategies). * section about [using multiple strategies](https://nxs.li/guides/abstract-types/using-multiple-strategies).
*/ */

View File

@ -1,7 +1,8 @@
import type { GraphQLScalarTypeConfig } from 'graphql' import type { GraphQLScalarTypeConfig } from 'graphql'
import type { AllInputTypes, GetGen2 } from '../typegenTypeHelpers' import type { AllInputTypes, GetGen2 } from '../typegenTypeHelpers'
import type { Directives } from './directive'
import type { AllNexusArgsDefs, AllNexusInputTypeDefs } from './wrapping' import type { AllNexusArgsDefs, AllNexusInputTypeDefs } from './wrapping'
import { NexusTypes, withNexusSymbol } from './_types' import { Maybe, NexusTypes, withNexusSymbol } from './_types'
export type ArgsRecord = Record<string, AllNexusArgsDefs> export type ArgsRecord = Record<string, AllNexusArgsDefs>
@ -39,7 +40,7 @@ export type CommonArgConfig = {
* // ): [Int] * // ): [Int]
* // } * // }
*/ */
description?: string description?: Maybe<string>
/** /**
* Data that will be added to the arg-level [extensions field on the graphql-js type def * Data that will be added to the arg-level [extensions field on the graphql-js type def
@ -47,13 +48,20 @@ export type CommonArgConfig = {
* graphql-js based tools which rely on looking for special data here. * graphql-js based tools which rely on looking for special data here.
*/ */
extensions?: GraphQLScalarTypeConfig<any, any>['extensions'] extensions?: GraphQLScalarTypeConfig<any, any>['extensions']
/**
* A list of directives / directive uses (with args) for the arg definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
} & NexusGenPluginArgConfig } & NexusGenPluginArgConfig
export interface ScalarArgConfig<T> extends CommonArgConfig { export interface ScalarArgConfig<T> extends CommonArgConfig {
/** /**
* The default value for this argument when ***none*** is given by the client. * The default value for this argument when ***none*** is given by the client.
* *
* Note that _null_ is still considered something meaning if the client gives an explicit null that will * Note that *null* is still considered something meaning if the client gives an explicit null that will
* prevent the default from activating. This is why the type of an arg with a default value in the resolver * prevent the default from activating. This is why the type of an arg with a default value in the resolver
* includes "undefined | null". * includes "undefined | null".
* *
@ -86,12 +94,10 @@ export interface NexusArgConfig<T extends string> extends NexusAsArgConfig<T> {
* *
* Types may be expressed in one of three ways: * Types may be expressed in one of three ways:
* *
* 1. As string literals matching the name of a builtin scalar. * 1. As string literals matching the name of a builtin scalar. 2. As string literals matching the name of
* * another type. Thanks to [Nexus' reflection
* 2. As string literals matching the name of another type. Thanks to [Nexus' reflection * system](https://nxs.li/guides/reflection) this is typesafe and autocompletable. 3. As references to other
* system](https://nxs.li/guides/reflection) this is typesafe and autocompletable. * enums or input object type definitions.
*
* 3. As references to other enums or input object type definitions.
* *
* Type modifier helpers like list() may also be used and in turn accept one of the three methods listed above. * Type modifier helpers like list() may also be used and in turn accept one of the three methods listed above.
* *
@ -193,8 +199,7 @@ export function arg<T extends string>(config: NexusArgConfig<T>) {
* }, * },
* }) * })
* *
* @param config Configuration for the argument like its description. See jsdoc on each config field * @param config Configuration for the argument like its description. See jsdoc on each config field for details.
* for details.
*/ */
export function stringArg(config?: ScalarArgConfig<string>) { export function stringArg(config?: ScalarArgConfig<string>) {
return arg({ type: 'String', ...config }) return arg({ type: 'String', ...config })
@ -224,8 +229,7 @@ export function stringArg(config?: ScalarArgConfig<string>) {
* }, * },
* }) * })
* *
* @param config Configuration for the argument like its description. See jsdoc on each config field * @param config Configuration for the argument like its description. See jsdoc on each config field for details.
* for details.
*/ */
export function intArg(config?: ScalarArgConfig<number>) { export function intArg(config?: ScalarArgConfig<number>) {
return arg({ type: 'Int', ...config }) return arg({ type: 'Int', ...config })
@ -255,8 +259,7 @@ export function intArg(config?: ScalarArgConfig<number>) {
* }, * },
* }) * })
* *
* @param config Configuration for the argument like its description. See jsdoc on each config field * @param config Configuration for the argument like its description. See jsdoc on each config field for details.
* for details.
*/ */
export function floatArg(config?: ScalarArgConfig<number>) { export function floatArg(config?: ScalarArgConfig<number>) {
return arg({ type: 'Float', ...config }) return arg({ type: 'Float', ...config })
@ -286,8 +289,7 @@ export function floatArg(config?: ScalarArgConfig<number>) {
* }, * },
* }) * })
* *
* @param config Configuration for the argument like its description. See jsdoc on each config field * @param config Configuration for the argument like its description. See jsdoc on each config field for details.
* for details.
*/ */
export function idArg(config?: ScalarArgConfig<string>) { export function idArg(config?: ScalarArgConfig<string>) {
return arg({ type: 'ID', ...config }) return arg({ type: 'ID', ...config })
@ -317,8 +319,7 @@ export function idArg(config?: ScalarArgConfig<string>) {
* }, * },
* }) * })
* *
* @param config Configuration for the argument like its description. See jsdoc on each config field * @param config Configuration for the argument like its description. See jsdoc on each config field for details.
* for details.
*/ */
export function booleanArg(config?: ScalarArgConfig<boolean>) { export function booleanArg(config?: ScalarArgConfig<boolean>) {
return arg({ type: 'Boolean', ...config }) return arg({ type: 'Boolean', ...config })

View File

@ -6,17 +6,13 @@ export interface TypeExtensionConfig {
sourceType?: SourceTypingDef sourceType?: SourceTypingDef
} }
export type NexusTypeExtensions = {
nexus: TypeExtensionConfig
}
export function decorateType<T extends GraphQLNamedType>(type: T, config: TypeExtensionConfig): T { export function decorateType<T extends GraphQLNamedType>(type: T, config: TypeExtensionConfig): T {
type.extensions = { type.extensions = {
...type.extensions, ...type.extensions,
nexus: { nexus: {
asNexusMethod: config.asNexusMethod, ...Object(type.extensions?.nexus),
sourceType: config.sourceType, ...config,
}, },
} }
return type as any return type
} }

View File

@ -9,19 +9,19 @@ import type {
NeedsResolver, NeedsResolver,
} from '../typegenTypeHelpers' } from '../typegenTypeHelpers'
import type { ArgsRecord } from './args' import type { ArgsRecord } from './args'
import type { Directives } from './directive'
import type { NexusMetaType } from './nexusMeta'
import type { AllNexusInputTypeDefs, AllNexusOutputTypeDefs, NexusWrapKind } from './wrapping' import type { AllNexusInputTypeDefs, AllNexusOutputTypeDefs, NexusWrapKind } from './wrapping'
import type { BaseScalars } from './_types' import type { BaseScalars, Maybe } from './_types'
export interface CommonFieldConfig { export interface CommonFieldConfig {
//todo
/** The description to annotate the GraphQL SDL */ /** The description to annotate the GraphQL SDL */
description?: string description?: Maybe<string>
//todo
/** /**
* Info about a field deprecation. Formatted as a string and provided with the deprecated directive on * Info about a field deprecation. Formatted as a string and provided with the deprecated directive on
* field/enum types and as a comment on input fields. * field/enum types and as a comment on input fields.
*/ */
deprecation?: string // | DeprecationInfo; deprecation?: Maybe<string> // | DeprecationInfo;
} }
export type CommonOutputFieldConfig<TypeName extends string, FieldName extends string> = CommonFieldConfig & { export type CommonOutputFieldConfig<TypeName extends string, FieldName extends string> = CommonFieldConfig & {
@ -74,7 +74,7 @@ export type CommonOutputFieldConfig<TypeName extends string, FieldName extends s
* }, * },
* }) * })
*/ */
args?: ArgsRecord args?: Maybe<ArgsRecord>
/** /**
* Data that will be added to the field-level [extensions field on the graphql-js type def * Data that will be added to the field-level [extensions field on the graphql-js type def
* instances](https://github.com/graphql/graphql-js/issues/1527) resulting from makeSchema. Useful for some * instances](https://github.com/graphql/graphql-js/issues/1527) resulting from makeSchema. Useful for some
@ -104,8 +104,43 @@ export type CommonOutputFieldConfig<TypeName extends string, FieldName extends s
* }) * })
*/ */
extensions?: GraphQLFieldConfig<any, any>['extensions'] extensions?: GraphQLFieldConfig<any, any>['extensions']
/**
* A list of directives / directive uses (with args) for the output field definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
/**
* Defines a typing for the field, overriding the default behavior to default to the scalar,
* and omit the field if a resolver exists. Most useful in situations where we have a resolver
* but we still want the field defined on the output type.
*
* @example
* sourceType: 'string | number'
*/
sourceType?: string | FieldSourceType | NamedFieldSourceType[]
} & NexusGenPluginFieldConfig<TypeName, FieldName> } & NexusGenPluginFieldConfig<TypeName, FieldName>
export interface FieldSourceType {
/**
* String representing the TypeScript type output as the value
*/
type: string
/**
* If true, marks the field as optional `?:`
* @default false
*/
optional?: boolean
}
export interface NamedFieldSourceType extends FieldSourceType {
/**
* Property name in the output TypeScript field
*/
name: string
}
export type CommonInputFieldConfig<TypeName extends string, FieldName extends string> = CommonFieldConfig & { export type CommonInputFieldConfig<TypeName extends string, FieldName extends string> = CommonFieldConfig & {
/** The default value for the field, if any */ /** The default value for the field, if any */
default?: GetGen3<'inputTypes', TypeName, FieldName> default?: GetGen3<'inputTypes', TypeName, FieldName>
@ -115,7 +150,16 @@ export type CommonInputFieldConfig<TypeName extends string, FieldName extends st
* graphql-js based tools which rely on looking for special data here. * graphql-js based tools which rely on looking for special data here.
*/ */
extensions?: GraphQLInputFieldConfig['extensions'] extensions?: GraphQLInputFieldConfig['extensions']
} & NexusGenPluginFieldConfig<TypeName, FieldName> /**
* A list of directives / directive uses (with args) for the input field definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
} & NexusGenPluginFieldConfig<TypeName, FieldName> &
NexusGenPluginInputFieldConfig<TypeName, FieldName>
export interface OutputScalarConfig<TypeName extends string, FieldName extends string> export interface OutputScalarConfig<TypeName extends string, FieldName extends string>
extends CommonOutputFieldConfig<TypeName, FieldName> { extends CommonOutputFieldConfig<TypeName, FieldName> {
/** /**
@ -126,11 +170,7 @@ export interface OutputScalarConfig<TypeName extends string, FieldName extends s
* Every field has a resolver and they are the basis for resolving queries at runtime. You do not need to * Every field has a resolver and they are the basis for resolving queries at runtime. You do not need to
* explicitly implement every resolver however. If the [source typing](https://nxs.li/guides/backing-types) includes: * explicitly implement every resolver however. If the [source typing](https://nxs.li/guides/backing-types) includes:
* *
* 1. A field whose name matches this one * 1. A field whose name matches this one 2. And whose type is compatible 3. And is a scalar
*
* 2. And whose type is compatible
*
* 3. And is a scalar
* *
* ...then the default resolver will be available, whose behaviour is to simply return that field from the * ...then the default resolver will be available, whose behaviour is to simply return that field from the
* received source type. * received source type.
@ -148,23 +188,23 @@ export interface OutputScalarConfig<TypeName extends string, FieldName extends s
* }) * })
* *
* @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL object that this * @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL object that this
* field belongs to, unless this is a root * field belongs to, unless this is a root field (any field on a [root operation
* field (any field on a [root * type](https://spec.graphql.org/June2018/#sec-Root-Operation-Types): Query, Mutation, Subscription),
* operation type](https://spec.graphql.org/June2018/#sec-Root-Operation-Types): Query, Mutation, * in which case there is no source data and this will be undefined.
* Subscription), in which case there is no source data and this will be undefined.
* @param args If you have defined arguments on this field then this parameter will contain any arguments * @param args If you have defined arguments on this field then this parameter will contain any arguments
* passed by the client. If you specified default values for any arguments and the client did not explicitly * passed by the client. If you specified default values for any arguments and the client did not
* pass _any_ value (including null) for those arguments then you will see the defaults here. * explicitly pass *any* value (including null) for those arguments then you will see the defaults here.
* *
* Note that thanks to [Nexus' reflection system](https://nxs.li/guides/reflection) this parameter's type * Note that thanks to [Nexus' reflection system](https://nxs.li/guides/reflection) this parameter's type
* will always be type safe. * will always be type safe.
* @param context The context data for this request. * @param context The context data for this request.
* *
* The context data is typically a singleton scoped to the lifecycle of the request. This means created at * The context data is typically a singleton scoped to the lifecycle of the request. This means created at
* the beginning of a request and then passed to all the resolvers that execute while resolving the request. * the beginning of a request and then passed to all the resolvers that execute while resolving the
* It is often used to store information like the current user making the request. Nexus is not responsible * request. It is often used to store information like the current user making the request. Nexus is
* for this however. That is typically something you'll do with e.g. [Mercurius](https://mercurius.dev) or * not responsible for this however. That is typically something you'll do with e.g.
* [Apollo Server](https://apollographql.com/docs/apollo-server/api/apollo-server). * [Mercurius](https://mercurius.dev) or [Apollo
* Server](https://apollographql.com/docs/apollo-server/api/apollo-server).
* *
* Note that the type here will be whatever you have specified for "contextType" in your makeSchema configuration. * Note that the type here will be whatever you have specified for "contextType" in your makeSchema configuration.
* @param info The GraphQL resolve info. * @param info The GraphQL resolve info.
@ -175,8 +215,8 @@ export interface OutputScalarConfig<TypeName extends string, FieldName extends s
resolve?: FieldResolver<TypeName, FieldName> resolve?: FieldResolver<TypeName, FieldName>
} }
export interface NexusOutputFieldConfig<TypeName extends string, FieldName extends string> // prettier-ignore
extends OutputScalarConfig<TypeName, FieldName> { export interface NexusOutputFieldConfig<TypeName extends string, FieldName extends string> extends OutputScalarConfig<TypeName, FieldName> {
/** /**
* [GraphQL 2018 Spec](https://spec.graphql.org/June2018/#sec-Types) * [GraphQL 2018 Spec](https://spec.graphql.org/June2018/#sec-Types)
* *
@ -270,7 +310,15 @@ export interface NexusOutputFieldConfig<TypeName extends string, FieldName exten
* }, * },
* }) * })
*/ */
type: GetGen<'allOutputTypes', string> | AllNexusOutputTypeDefs type: GetGen<'allOutputTypes', string> | AllNexusOutputTypeDefs | NexusMetaType
}
// prettier-ignore
export interface NexusOutputFieldConfigWithName<TypeName extends string, FieldName extends string> extends NexusOutputFieldConfig<TypeName, FieldName> {
/**
* The name of this field. Must conform to the regex pattern: [_A-Za-z][_0-9A-Za-z]*
*/
name: FieldName
} }
export type NexusOutputFieldDef = NexusOutputFieldConfig<string, any> & { export type NexusOutputFieldDef = NexusOutputFieldConfig<string, any> & {
@ -298,14 +346,21 @@ export type ScalarOutConfig<TypeName extends string, FieldName extends string> =
} }
: OutputScalarConfig<TypeName, FieldName> : OutputScalarConfig<TypeName, FieldName>
export type FieldOutConfig<TypeName extends string, FieldName extends string> = NeedsResolver< // prettier-ignore
TypeName, export type FieldOutConfig<TypeName extends string, FieldName extends string> =
FieldName NeedsResolver<TypeName, FieldName> extends true
> extends true ? NexusOutputFieldConfig<TypeName, FieldName> & {
? NexusOutputFieldConfig<TypeName, FieldName> & { resolve: FieldResolver<TypeName, FieldName>
resolve: FieldResolver<TypeName, FieldName> }
} : NexusOutputFieldConfig<TypeName, FieldName>
: NexusOutputFieldConfig<TypeName, FieldName>
// prettier-ignore
export type FieldOutConfigWithName<TypeName extends string, FieldName extends string> =
NeedsResolver<TypeName, FieldName> extends true
? NexusOutputFieldConfigWithName<TypeName, FieldName> & {
resolve: FieldResolver<TypeName, FieldName>
}
: NexusOutputFieldConfigWithName<TypeName, FieldName>
export interface OutputDefinitionBuilder { export interface OutputDefinitionBuilder {
typeName: string typeName: string
@ -413,7 +468,7 @@ export class OutputDefinitionBlock<TypeName extends string> {
* Guide](https://nexusjs.org/docs/guides/nullability) | [GraphQL 2018 * Guide](https://nexusjs.org/docs/guides/nullability) | [GraphQL 2018
* Spec](https://spec.graphql.org/June2018/#sec-Type-System.Non-Null) * Spec](https://spec.graphql.org/June2018/#sec-Type-System.Non-Null)
* *
* Chain this property to _unwrap_ the right-hand-side type (the field type or a list) of a Non-Null type. * Chain this property to *unwrap* the right-hand-side type (the field type or a list) of a Non-Null type.
* *
* In Nexus output types are nullable by default so this is only useful when you have changed your * In Nexus output types are nullable by default so this is only useful when you have changed your
* nonNullDefaults configuration either globally in your makeSchema config or at the type definition level * nonNullDefaults configuration either globally in your makeSchema config or at the type definition level
@ -461,7 +516,7 @@ export class OutputDefinitionBlock<TypeName extends string> {
* *
* This is a shorthand equivalent to: * This is a shorthand equivalent to:
* *
* ` t.field('...', { type: boolean() }) ` * `t.field('...', { type: boolean() })`
* *
* @example * @example
* objectType({ * objectType({
@ -478,14 +533,10 @@ export class OutputDefinitionBlock<TypeName extends string> {
* This parameter is optional if no resolver is required. No resolver is required if the [source * This parameter is optional if no resolver is required. No resolver is required if the [source
* typing](https://nxs.li/guides/backing-types): * typing](https://nxs.li/guides/backing-types):
* *
* 1. Has a field whose name matches this one * 1. Has a field whose name matches this one 2. And whose type is compatible 3. And is a scalar
* *
* 2. And whose type is compatible * ...in which case the default resolver will be available whose behaviour is to simply return that field
* * from the received source type.
* 3. And is a scalar
*
* ...in which case the default resolver will be available whose behaviour is to simply return that field from the
* received source type.
*/ */
boolean<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) { boolean<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) {
this.addScalarField(name, 'Boolean', config) this.addScalarField(name, 'Boolean', config)
@ -503,7 +554,7 @@ export class OutputDefinitionBlock<TypeName extends string> {
* *
* This is a shorthand, equivalent to: * This is a shorthand, equivalent to:
* *
* ` t.field('...', { type: string() }) ` * `t.field('...', { type: string() })`
* *
* @example * @example
* objectType({ * objectType({
@ -520,14 +571,10 @@ export class OutputDefinitionBlock<TypeName extends string> {
* This parameter is optional if no resolver is required. No resolver is required if the [source * This parameter is optional if no resolver is required. No resolver is required if the [source
* typing](https://nxs.li/guides/backing-types): * typing](https://nxs.li/guides/backing-types):
* *
* 1. Has a field whose name matches this one * 1. Has a field whose name matches this one 2. And whose type is compatible 3. And is a scalar
* *
* 2. And whose type is compatible * ...in which case the default resolver will be available whose behaviour is to simply return that field
* * from the received source type.
* 3. And is a scalar
*
* ...in which case the default resolver will be available whose behaviour is to simply return that field from the
* received source type.
*/ */
string<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) { string<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) {
this.addScalarField(name, 'String', config) this.addScalarField(name, 'String', config)
@ -546,7 +593,7 @@ export class OutputDefinitionBlock<TypeName extends string> {
* *
* This is a shorthand, equivalent to: * This is a shorthand, equivalent to:
* *
* ` t.field('...', { type: id() }) ` * `t.field('...', { type: id() })`
* *
* @example * @example
* objectType({ * objectType({
@ -563,14 +610,10 @@ export class OutputDefinitionBlock<TypeName extends string> {
* This parameter is optional if no resolver is required. No resolver is required if the [source * This parameter is optional if no resolver is required. No resolver is required if the [source
* typing](https://nxs.li/guides/backing-types): * typing](https://nxs.li/guides/backing-types):
* *
* 1. Has a field whose name matches this one * 1. Has a field whose name matches this one 2. And whose type is compatible 3. And is a scalar
* *
* 2. And whose type is compatible * ...in which case the default resolver will be available whose behaviour is to simply return that field
* * from the received source type.
* 3. And is a scalar
*
* ...in which case the default resolver will be available whose behaviour is to simply return that field from the
* received source type.
*/ */
id<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) { id<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) {
this.addScalarField(name, 'ID', config) this.addScalarField(name, 'ID', config)
@ -587,7 +630,7 @@ export class OutputDefinitionBlock<TypeName extends string> {
* *
* This is a shorthand equivalent to: * This is a shorthand equivalent to:
* *
* ` t.field('...', { type: int() }) ` * `t.field('...', { type: int() })`
* *
* @example * @example
* objectType({ * objectType({
@ -604,14 +647,10 @@ export class OutputDefinitionBlock<TypeName extends string> {
* This parameter is optional if no resolver is required. No resolver is required if the [source * This parameter is optional if no resolver is required. No resolver is required if the [source
* typing](https://nxs.li/guides/backing-types): * typing](https://nxs.li/guides/backing-types):
* *
* 1. Has a field whose name matches this one * 1. Has a field whose name matches this one 2. And whose type is compatible 3. And is a scalar
* *
* 2. And whose type is compatible * ...in which case the default resolver will be available whose behaviour is to simply return that field
* * from the received source type.
* 3. And is a scalar
*
* ...in which case the default resolver will be available whose behaviour is to simply return that field from the
* received source type.
*/ */
int<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) { int<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) {
this.addScalarField(name, 'Int', config) this.addScalarField(name, 'Int', config)
@ -629,7 +668,7 @@ export class OutputDefinitionBlock<TypeName extends string> {
* *
* This is a shorthand, equivalent to: * This is a shorthand, equivalent to:
* *
* ` t.field('...', { type: float() }) ` * `t.field('...', { type: float() })`
* *
* @example * @example
* objectType({ * objectType({
@ -646,14 +685,10 @@ export class OutputDefinitionBlock<TypeName extends string> {
* This parameter is optional if no resolver is required. No resolver is required if the [source * This parameter is optional if no resolver is required. No resolver is required if the [source
* typing](https://nxs.li/guides/backing-types): * typing](https://nxs.li/guides/backing-types):
* *
* 1. Has a field whose name matches this one * 1. Has a field whose name matches this one 2. And whose type is compatible 3. And is a scalar
* *
* 2. And whose type is compatible * ...in which case the default resolver will be available whose behaviour is to simply return that field
* * from the received source type.
* 3. And is a scalar
*
* ...in which case the default resolver will be available whose behaviour is to simply return that field from the
* received source type.
*/ */
float<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) { float<FieldName extends string>(name: FieldName, ...config: ScalarOutSpread<TypeName, FieldName>) {
this.addScalarField(name, 'Float', config) this.addScalarField(name, 'Float', config)
@ -681,12 +716,44 @@ export class OutputDefinitionBlock<TypeName extends string> {
* }) * })
* *
* @param name The name of this field. Must conform to the regex pattern: [_A-Za-z][_0-9A-Za-z]* * @param name The name of this field. Must conform to the regex pattern: [_A-Za-z][_0-9A-Za-z]*
* @param config The configuration for things like the field's type, its description, its arguments, * @param config The configuration for things like the field's type, its description, its arguments, its
* its resolver, and more. See jsdoc on each field within for details. * resolver, and more. See jsdoc on each field within for details.
*/ */
field<FieldName extends string>(name: FieldName, config: FieldOutConfig<TypeName, FieldName>) { field<FieldName extends string>(name: FieldName, config: FieldOutConfig<TypeName, FieldName>): void
/**
* [GraphQL 2018 Spec](https://spec.graphql.org/June2018/#sec-Language.Fields)
*
* Define a field on this object.
*
* A field describes one discrete piece of information available to request within a [selection
* set](https://spec.graphql.org/June2018/#sec-Selection-Sets). They are in fact most of what any selection
* set will contain. Fields can be typed as scalars (marking the terminal point of a branch of a selection
* set) or as other object types in your schema thus allowing you to model relationships between things.
*
* @example
* objectType({
* name: 'User',
* definition(t) {
* t.field({
* name: 'id',
* type: id(),
* description: 'The unique identification number for this user',
* })
* },
* })
*
* @param config The configuration for things like the field's type, its description, its arguments, its
* resolver, and more. See jsdoc on each field within for details.
*/
field<FieldName extends string>(config: FieldOutConfigWithName<TypeName, FieldName>): void
field<FieldName extends string>(
...args:
| [name: FieldName, config: FieldOutConfig<TypeName, FieldName>]
| [config: FieldOutConfigWithName<TypeName, FieldName>]
): void {
const config = args.length === 2 ? { name: args[0], ...args[1] } : args[0]
this.typeBuilder.addField({ this.typeBuilder.addField({
name,
...config, ...config,
configFor: 'outputField', configFor: 'outputField',
wrapping: this.wrapping, wrapping: this.wrapping,
@ -725,11 +792,21 @@ export class OutputDefinitionBlock<TypeName extends string> {
} }
} }
export interface NexusInputFieldConfig<TypeName extends string, FieldName extends string> /** TODO move the code below to definitionBlocks/input.ts Input */
extends CommonInputFieldConfig<TypeName, FieldName> {
// prettier-ignore
export interface NexusInputFieldConfig<TypeName extends string, FieldName extends string> extends CommonInputFieldConfig<TypeName, FieldName> {
type: AllInputTypes | AllNexusInputTypeDefs type: AllInputTypes | AllNexusInputTypeDefs
} }
// prettier-ignore
export interface NexusInputFieldConfigWithName<TypeName extends string, FieldName extends string> extends NexusInputFieldConfig<TypeName, FieldName> {
/**
* The name of this field. Must conform to the regex pattern: [_A-Za-z][_0-9A-Za-z]*
*/
name: FieldName
}
export type NexusInputFieldDef = NexusInputFieldConfig<string, string> & { export type NexusInputFieldDef = NexusInputFieldConfig<string, string> & {
configFor: 'inputField' configFor: 'inputField'
name: string name: string
@ -787,13 +864,17 @@ export class InputDefinitionBlock<TypeName extends string> {
this.field(fieldName, { ...config, type: 'Float' }) this.field(fieldName, { ...config, type: 'Float' })
} }
field<FieldName extends string>(config: NexusInputFieldConfigWithName<TypeName, FieldName>): void
field<FieldName extends string>(name: FieldName, config: NexusInputFieldConfig<TypeName, FieldName>): void
field<FieldName extends string>( field<FieldName extends string>(
fieldName: FieldName, ...args:
fieldConfig: NexusInputFieldConfig<TypeName, FieldName> | [FieldName, NexusInputFieldConfig<TypeName, FieldName>]
) { | [NexusInputFieldConfigWithName<TypeName, FieldName>]
): void {
const config = args.length === 2 ? { name: args[0], ...args[1] } : args[0]
this.typeBuilder.addField({ this.typeBuilder.addField({
name: fieldName, ...config,
...fieldConfig,
wrapping: this.wrapping, wrapping: this.wrapping,
parentType: this.typeName, parentType: this.typeName,
configFor: 'inputField', configFor: 'inputField',

View File

@ -0,0 +1,230 @@
import {
ArgumentNode,
assertValidName,
astFromValue,
ASTKindToNode,
DirectiveNode,
GraphQLDirective,
GraphQLDirectiveConfig,
Kind,
} from 'graphql'
import { GetGen, GetGen2, NexusWrappedSymbol } from '../core'
import type { MaybeReadonlyArray } from '../typeHelpersInternal'
import { mapObj } from '../utils'
import type { ArgsRecord } from './args'
import { isNexusDirective, isNexusDirectiveUse } from './wrapping'
import { Maybe, NexusTypes, withNexusSymbol } from './_types'
export type Directives = MaybeReadonlyArray<NexusDirectiveDef | GraphQLDirective | NexusDirectiveUse>
export const RequestDirectiveLocation = [
/** Request Definitions */
'QUERY',
'MUTATION',
'SUBSCRIPTION',
'FIELD',
'FRAGMENT_DEFINITION',
'FRAGMENT_SPREAD',
'INLINE_FRAGMENT',
'VARIABLE_DEFINITION',
] as const
export const SchemaDirectiveLocation = [
/** Type System Definitions */
'SCHEMA',
'SCALAR',
'OBJECT',
'FIELD_DEFINITION',
'ARGUMENT_DEFINITION',
'INTERFACE',
'UNION',
'ENUM',
'ENUM_VALUE',
'INPUT_OBJECT',
'INPUT_FIELD_DEFINITION',
] as const
export type SchemaDirectiveLocationEnum = typeof SchemaDirectiveLocation[number]
export type RequestDirectiveLocationEnum = typeof RequestDirectiveLocation[number]
export interface NexusDirectiveConfig<DirectiveName extends string = string> {
/** Name of the directive */
name: DirectiveName
/** The description to annotate the GraphQL SDL */
description?: string
/** Valid locations that this directive may be used */
locations: MaybeReadonlyArray<SchemaDirectiveLocationEnum | RequestDirectiveLocationEnum>
/** Whether the directive can be repeated */
isRepeatable?: Maybe<boolean> | undefined
/**
* [GraphQL.org Docs](https://graphql.github.io/learn/schema/#arguments) | [GraphQL 2018
* Spec](https://spec.graphql.org/June2018/#sec-Language.Arguments)
*
* Define arguments for this directive.
*
* All directives in GraphQL can have arguments defined for them. Nexus provides a number of helpers for
* defining arguments. All builtin GraphQL scalar types have helpers named "{scalarName}Arg" such as
* "stringArg" and "intArg". You can also use type modifier helpers "[list](https://nxs.li/docs/api/list)"
* "[nullable](https://nxs.li/docs/api/nullable)" and "[nonNull](https://nxs.li/docs/api/nonNull)". For
* details about nonNull/nullable refer to the [nullability guide](https://nxs.li/guides/nullability).
*
* @example
* export const TestValue = directive({
* name: 'TestValue',
* description: 'Denotes the value used when testing this type',
* args: {
* type: enumType({
* name: 'TestValueType',
* members: ['String', 'Int', 'Float', 'JSON'],
* }),
* value: stringArg(),
* listLength: intArg(),
* },
* locations: ['FIELD_DEFINITION'],
* })
*/
args?: ArgsRecord
/** Data that will be added to the directive extensions field on the graphql-js type def instances */
extensions?: GraphQLDirectiveConfig['extensions']
}
export interface NexusDirectiveDef<DirectiveName extends string = string> {
(...args: MaybeArgsFor<DirectiveName>): NexusDirectiveUse<DirectiveName>
value: NexusDirectiveConfig
[NexusWrappedSymbol]: 'Directive'
}
export class NexusDirectiveUse<DirectiveName extends string = string> {
constructor(
readonly name: DirectiveName,
readonly args: MaybeArgsFor<DirectiveName>[0] | undefined,
readonly config?: NexusDirectiveConfig<DirectiveName>
) {}
}
withNexusSymbol(NexusDirectiveUse, NexusTypes.DirectiveUse)
/** Defines a directive, which can be used positionally when generating the SDL */
export function directive<DirectiveName extends string>(
config: NexusDirectiveConfig<DirectiveName>
): NexusDirectiveDef<DirectiveName> {
assertValidName(config.name)
config = Object.freeze(config)
function addDirective(...args: MaybeArgsFor<DirectiveName>) {
return new NexusDirectiveUse(config.name, args[0], config)
}
addDirective[NexusWrappedSymbol] = NexusTypes.Directive as 'Directive'
addDirective.value = config
return addDirective
}
type MaybeArgsFor<DirectiveName extends string> = GetGen2<'directiveArgs', DirectiveName> extends object
? [GetGen2<'directiveArgs', DirectiveName>]
: []
export function addDirective<DirectiveName extends GetGen<'directives', string>>(
directiveName: DirectiveName,
...args: MaybeArgsFor<DirectiveName>
) {
return new NexusDirectiveUse<DirectiveName>(directiveName, args[0])
}
const DirectiveASTKindMapping = {
SCALAR: Kind.SCALAR_TYPE_DEFINITION, // 'ScalarTypeDefinition',
SCHEMA: Kind.SCHEMA_DEFINITION, // 'SchemaDefinition',
OBJECT: Kind.OBJECT_TYPE_DEFINITION, // 'ObjectTypeDefinition',
FIELD_DEFINITION: Kind.FIELD_DEFINITION, // 'FieldDefinition',
ARGUMENT_DEFINITION: Kind.INPUT_VALUE_DEFINITION, // 'InputValueDefinition',
INTERFACE: Kind.INTERFACE_TYPE_DEFINITION, // 'InterfaceTypeDefinition',
UNION: Kind.UNION_TYPE_DEFINITION, // 'UnionTypeDefinition',
ENUM: Kind.ENUM_TYPE_DEFINITION, // 'EnumTypeDefinition',
ENUM_VALUE: Kind.ENUM_VALUE_DEFINITION, // 'EnumValueDefinition',
INPUT_OBJECT: Kind.INPUT_OBJECT_TYPE_DEFINITION, // 'InputObjectTypeDefinition',
INPUT_FIELD_DEFINITION: Kind.INPUT_VALUE_DEFINITION, // 'InputValueDefinition',
} as const
type DirectiveASTKindMapping = typeof DirectiveASTKindMapping
export type DirectiveASTKinds = keyof DirectiveASTKindMapping
/**
* Creates the ASTNode with the directives
*
* @param
*/
export function maybeAddDirectiveUses<
Kind extends DirectiveASTKinds,
Result extends { astNode: ASTKindToNode[DirectiveASTKindMapping[Kind]] }
>(
kind: Kind,
directiveUses: Directives | undefined,
customDirectives: Record<string, GraphQLDirective>
): Result | undefined {
if (!directiveUses?.length) {
return undefined
}
const seenDirectives = new Set<string>()
return {
astNode: {
kind: DirectiveASTKindMapping[kind],
directives: Array.from(directiveUses).map((d): DirectiveNode => {
const directiveName = isNexusDirective(d) ? d.value.name : d.name
const directiveDef = customDirectives[directiveName]
if (seenDirectives.has(directiveName) && !directiveDef.isRepeatable) {
throw new Error(`Cannot use directive ${directiveName} more than once in a row`)
}
assertValidDirectiveFor(kind, directiveDef)
seenDirectives.add(directiveName)
const args = isNexusDirectiveUse(d) ? directiveArgs(d, directiveDef) : undefined
return {
kind: Kind.DIRECTIVE,
name: {
kind: Kind.NAME,
value: directiveName,
},
arguments: args,
}
}),
},
} as any
}
function assertValidDirectiveFor(
kind: DirectiveASTKinds,
directiveDef: GraphQLDirective & { locations: readonly any[] } // any is a hack to make this work w/ v15 & 16
) {
if (!directiveDef.locations.includes(kind)) {
throw new Error(`Directive ${directiveDef.name} cannot be applied to ${kind}`)
}
}
function directiveArgs(
directiveUse: NexusDirectiveUse,
directiveDef: GraphQLDirective
): ReadonlyArray<ArgumentNode> {
return mapObj(directiveUse.args ?? {}, (val, key) => {
const arg = directiveDef.args.find((a) => a.name === key)
if (!arg) {
throw new Error(`Unknown directive arg ${key}, expected one of ${directiveDef.args.map((a) => a.name)}`)
}
const value = astFromValue(val, arg.type)
if (!value) {
throw new Error(`Unable to get ast for ${key}`)
}
return {
kind: Kind.ARGUMENT,
name: {
kind: Kind.NAME,
value: key,
},
value,
}
})
}

View File

@ -1,6 +1,7 @@
import { assertValidName, GraphQLEnumTypeConfig, GraphQLEnumValueConfig } from 'graphql' import { assertValidName, GraphQLEnumTypeConfig, GraphQLEnumValueConfig } from 'graphql'
import { arg, NexusArgDef, NexusAsArgConfig } from './args' import { arg, NexusArgDef, NexusAsArgConfig } from './args'
import { NexusTypes, SourceTypingDef, withNexusSymbol } from './_types' import type { Directives } from './directive'
import { Maybe, NexusTypes, SourceTypingDef, withNexusSymbol } from './_types'
type TypeScriptEnumLike = { type TypeScriptEnumLike = {
[key: number]: string [key: number]: string
@ -12,29 +13,36 @@ export interface EnumMemberInfo {
/** The internal representation of the enum */ /** The internal representation of the enum */
value?: string | number | object | boolean value?: string | number | object | boolean
/** The description to annotate the GraphQL SDL */ /** The description to annotate the GraphQL SDL */
description?: string description?: Maybe<string>
/** /**
* Info about a field deprecation. Formatted as a string and provided with the deprecated directive on * Info about a field deprecation. Formatted as a string and provided with the deprecated directive on
* field/enum types and as a comment on input fields. * field/enum types and as a comment on input fields.
*/ */
deprecation?: string // | DeprecationInfo; deprecation?: Maybe<string> // | DeprecationInfo;
/** /**
* Custom extensions, as supported in graphql-js * Custom extensions, as supported in graphql-js
* *
* @see https://github.com/graphql/graphql-js/issues/1527 * @see https://github.com/graphql/graphql-js/issues/1527
*/ */
extensions?: GraphQLEnumValueConfig['extensions'] extensions?: GraphQLEnumValueConfig['extensions']
/**
* A list of directives / directive uses (with args) for the enum member definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
} }
export interface NexusEnumTypeConfig<TypeName extends string> { export interface NexusEnumTypeConfig<TypeName extends string> {
name: TypeName name: TypeName
/** The description to annotate the GraphQL SDL */ /** The description to annotate the GraphQL SDL */
description?: string | null description?: Maybe<string>
/** Source type information for this type */ /** Source type information for this type */
sourceType?: SourceTypingDef sourceType?: SourceTypingDef
/** All members of the enum, either as an array of strings/definition objects, as an object, or as a TypeScript enum */ /** All members of the enum, either as an array of strings/definition objects, as an object, or as a TypeScript enum */
members: members:
| Array<string | EnumMemberInfo> | ReadonlyArray<string | EnumMemberInfo>
| Record<string, string | number | object | boolean> | Record<string, string | number | object | boolean>
| TypeScriptEnumLike | TypeScriptEnumLike
/** /**
@ -43,6 +51,15 @@ export interface NexusEnumTypeConfig<TypeName extends string> {
* @see https://github.com/graphql/graphql-js/issues/1527 * @see https://github.com/graphql/graphql-js/issues/1527
*/ */
extensions?: GraphQLEnumTypeConfig['extensions'] extensions?: GraphQLEnumTypeConfig['extensions']
/**
* A list of directives / directive uses (with args) for the enum type definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
/** Adds this type as a method on the Object/Interface definition blocks */
asNexusMethod?: string
} }
export class NexusEnumTypeDef<TypeName extends string> { export class NexusEnumTypeDef<TypeName extends string> {

View File

@ -21,8 +21,8 @@ export interface NexusExtendTypeConfig<TypeName extends string> {
* "definition" method. * "definition" method.
* *
* @param t The type builder. Usually the same as that passed to objectType "definition" method except if * @param t The type builder. Usually the same as that passed to objectType "definition" method except if
* extending the Subscription type in which case you get a subscription type builder (which differs * extending the Subscription type in which case you get a subscription type builder (which differs
* slightly in that it requires implementation of a "subscribe" method on field configurations). * slightly in that it requires implementation of a "subscribe" method on field configurations).
*/ */
definition( definition(
t: IsSubscriptionType<TypeName> extends true ? SubscriptionBuilder : ObjectDefinitionBlock<TypeName> t: IsSubscriptionType<TypeName> extends true ? SubscriptionBuilder : ObjectDefinitionBlock<TypeName>
@ -57,10 +57,10 @@ withNexusSymbol(NexusExtendTypeDef, NexusTypes.ExtendObject)
* use-case is so common Nexus ships dedicated functions for it: queryField, mutationField, subscriptionField. * use-case is so common Nexus ships dedicated functions for it: queryField, mutationField, subscriptionField.
* *
* You can extend types before defining them strictly with objectType or the root field functions (queryType * You can extend types before defining them strictly with objectType or the root field functions (queryType
* etc.). The typing for "type" property will appear to suggest that you cannot, however once Nexus * etc.). The typing for "type" property will appear to suggest that you cannot, however once Nexus reflection
* reflection has run you'll see that the type you "extended" exists in the schema and that your static * has run you'll see that the type you "extended" exists in the schema and that your static typing error has
* typing error has been resolved. This behaviour is a convenience especially when extending root types which * been resolved. This behaviour is a convenience especially when extending root types which you might never
* you might never define in your schema directly. * define in your schema directly.
* *
* @example * @example
* // types/User.ts * // types/User.ts
@ -85,8 +85,8 @@ withNexusSymbol(NexusExtendTypeDef, NexusTypes.ExtendObject)
* }, * },
* }) * })
* *
* @param config The specification of which type to extend and how. This is basically a subset of the configuration * @param config The specification of which type to extend and how. This is basically a subset of the
* object passed to the objectType function. * configuration object passed to the objectType function.
*/ */
export function extendType<TypeName extends AllOutputTypesPossible>(config: NexusExtendTypeConfig<TypeName>) { export function extendType<TypeName extends AllOutputTypesPossible>(config: NexusExtendTypeConfig<TypeName>) {
return new NexusExtendTypeDef(config.type, { ...config, name: config.type }) as NexusExtendTypeDef<any> return new NexusExtendTypeDef(config.type, { ...config, name: config.type }) as NexusExtendTypeDef<any>

View File

@ -1,7 +1,8 @@
import { assertValidName, GraphQLInputObjectTypeConfig } from 'graphql' import { assertValidName, GraphQLInputObjectTypeConfig } from 'graphql'
import { arg, NexusArgDef, NexusAsArgConfig } from './args' import { arg, NexusArgDef, NexusAsArgConfig } from './args'
import type { InputDefinitionBlock } from './definitionBlocks' import type { InputDefinitionBlock } from './definitionBlocks'
import { NexusTypes, NonNullConfig, withNexusSymbol } from './_types' import type { Directives } from './directive'
import { Maybe, NexusTypes, NonNullConfig, withNexusSymbol } from './_types'
export type NexusInputObjectTypeConfig<TypeName extends string> = { export type NexusInputObjectTypeConfig<TypeName extends string> = {
/** Name of the input object type */ /** Name of the input object type */
@ -9,7 +10,7 @@ export type NexusInputObjectTypeConfig<TypeName extends string> = {
/** Definition block for the input type */ /** Definition block for the input type */
definition(t: InputDefinitionBlock<TypeName>): void definition(t: InputDefinitionBlock<TypeName>): void
/** The description to annotate the GraphQL SDL */ /** The description to annotate the GraphQL SDL */
description?: string description?: Maybe<string>
/** /**
* Configures the nullability for the type, check the documentation's "Getting Started" section to learn * Configures the nullability for the type, check the documentation's "Getting Started" section to learn
* more about GraphQL Nexus's assumptions and configuration on nullability. * more about GraphQL Nexus's assumptions and configuration on nullability.
@ -21,6 +22,15 @@ export type NexusInputObjectTypeConfig<TypeName extends string> = {
* @see https://github.com/graphql/graphql-js/issues/1527 * @see https://github.com/graphql/graphql-js/issues/1527
*/ */
extensions?: GraphQLInputObjectTypeConfig['extensions'] extensions?: GraphQLInputObjectTypeConfig['extensions']
/**
* A list of directives / directive uses (with args) for the input object type definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
/** Adds this type as a method on the Object/Interface definition blocks */
asNexusMethod?: string
} & NexusGenPluginInputTypeConfig<TypeName> } & NexusGenPluginInputTypeConfig<TypeName>
export class NexusInputObjectTypeDef<TypeName extends string> { export class NexusInputObjectTypeDef<TypeName extends string> {

View File

@ -2,18 +2,19 @@ import { assertValidName, GraphQLInterfaceTypeConfig } from 'graphql'
import type { FieldResolver, GetGen, InterfaceFieldsFor, ModificationType } from '../typegenTypeHelpers' import type { FieldResolver, GetGen, InterfaceFieldsFor, ModificationType } from '../typegenTypeHelpers'
import type { ArgsRecord } from './args' import type { ArgsRecord } from './args'
import { OutputDefinitionBlock, OutputDefinitionBuilder } from './definitionBlocks' import { OutputDefinitionBlock, OutputDefinitionBuilder } from './definitionBlocks'
import { AbstractTypes, NexusTypes, NonNullConfig, SourceTypingDef, withNexusSymbol } from './_types' import type { Directives } from './directive'
import { AbstractTypes, Maybe, NexusTypes, NonNullConfig, SourceTypingDef, withNexusSymbol } from './_types'
export type Implemented = GetGen<'interfaceNames'> | NexusInterfaceTypeDef<any> export type Implemented = GetGen<'interfaceNames'> | NexusInterfaceTypeDef<any>
export interface FieldModification<TypeName extends string, FieldName extends string> { export interface FieldModification<TypeName extends string, FieldName extends string> {
type?: ModificationType<TypeName, FieldName> type?: ModificationType<TypeName, FieldName>
/** The description to annotate the GraphQL SDL */ /** The description to annotate the GraphQL SDL */
description?: string description?: Maybe<string>
/** The resolve method we should be resolving the field with */ /** The resolve method we should be resolving the field with */
resolve?: FieldResolver<TypeName, FieldName> resolve?: FieldResolver<TypeName, FieldName>
/** You are allowed to add non-required args when modifying a field */ /** You are allowed to add non-required args when modifying a field */
args?: ArgsRecord args?: Maybe<ArgsRecord>
/** /**
* Custom extensions, as supported in graphql-js * Custom extensions, as supported in graphql-js
* *
@ -44,7 +45,7 @@ export type NexusInterfaceTypeConfig<TypeName extends string> = {
*/ */
nonNullDefaults?: NonNullConfig nonNullDefaults?: NonNullConfig
/** The description to annotate the GraphQL SDL */ /** The description to annotate the GraphQL SDL */
description?: string | null description?: Maybe<string>
/** Source type information for this type */ /** Source type information for this type */
sourceType?: SourceTypingDef sourceType?: SourceTypingDef
/** /**
@ -53,6 +54,15 @@ export type NexusInterfaceTypeConfig<TypeName extends string> = {
* @see https://github.com/graphql/graphql-js/issues/1527 * @see https://github.com/graphql/graphql-js/issues/1527
*/ */
extensions?: GraphQLInterfaceTypeConfig<any, any>['extensions'] extensions?: GraphQLInterfaceTypeConfig<any, any>['extensions']
/**
* A list of directives / directive uses (with args) for the output field definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
/** Adds this type as a method on the Object/Interface definition blocks */
asNexusMethod?: string
} & AbstractTypes.MaybeTypeDefConfigFieldResolveType<TypeName> } & AbstractTypes.MaybeTypeDefConfigFieldResolveType<TypeName>
export interface InterfaceDefinitionBuilder<TypeName extends string> extends OutputDefinitionBuilder { export interface InterfaceDefinitionBuilder<TypeName extends string> extends OutputDefinitionBuilder {

View File

@ -1,10 +1,7 @@
import { isType } from 'graphql' import { isType } from 'graphql'
import { AllNamedTypeDefs, isNexusStruct, NexusListableTypes } from './wrapping' import { isNexusMeta } from './nexusMeta'
import { isNexusStruct, NexusListableTypes } from './wrapping'
import { NexusTypes, withNexusSymbol } from './_types' import { NexusTypes, withNexusSymbol } from './_types'
/** List() */
export type NexusListDefConfig<TypeName extends AllNamedTypeDefs> = {
type: TypeName
}
export class NexusListDef<TypeName extends NexusListableTypes> { export class NexusListDef<TypeName extends NexusListableTypes> {
// @ts-ignore // @ts-ignore
@ -13,7 +10,12 @@ export class NexusListDef<TypeName extends NexusListableTypes> {
constructor(readonly ofNexusType: TypeName) { constructor(readonly ofNexusType: TypeName) {
/* istanbul ignore if */ /* istanbul ignore if */
if (typeof ofNexusType !== 'string' && !isNexusStruct(ofNexusType) && !isType(ofNexusType)) { if (
typeof ofNexusType !== 'string' &&
!isNexusStruct(ofNexusType) &&
!isNexusMeta(ofNexusType) &&
!isType(ofNexusType)
) {
throw new Error('Cannot wrap unknown types in list(). Saw ' + ofNexusType) throw new Error('Cannot wrap unknown types in list(). Saw ' + ofNexusType)
} }
} }

View File

@ -1,4 +1,4 @@
import type { FieldOutConfig, OutputDefinitionBlock } from '../core' import type { FieldOutConfig, OutputDefinitionBlock } from './definitionBlocks'
import { extendType, NexusExtendTypeDef } from './extendType' import { extendType, NexusExtendTypeDef } from './extendType'
export type MutationFieldConfig<FieldName extends string> = export type MutationFieldConfig<FieldName extends string> =
@ -114,9 +114,8 @@ export function mutationField(
* // ... * // ...
* }) * })
* *
* @param name The name of the field on the Mutation type. Names are casesensitive and must conform to pattern: * @param name The name of the field on the Mutation type. Names are casesensitive and must conform to
* * pattern: `[_A-Za-z][_0-9A-Za-z]*`
* [_A-Za-z][_0-9A-Za-z]*
* @param config The same type of configuration you would pass to t.field("...", config) * @param config The same type of configuration you would pass to t.field("...", config)
*/ */
export function mutationField<FieldName extends string>( export function mutationField<FieldName extends string>(

View File

@ -18,7 +18,7 @@ import { NexusObjectTypeConfig, objectType } from './objectType'
* *
* @example * @example
* mutationType({ * mutationType({
* definitin(t) { * definition(t) {
* t.field('signup', { * t.field('signup', {
* type: 'User', * type: 'User',
* args: { * args: {
@ -37,7 +37,7 @@ import { NexusObjectTypeConfig, objectType } from './objectType'
* }) * })
* *
* @param config Specify your Mutation type's fields, description, and more. See each config property's jsDoc * @param config Specify your Mutation type's fields, description, and more. See each config property's jsDoc
* for more detail. * for more detail.
*/ */
export function mutationType(config: Omit<NexusObjectTypeConfig<'Mutation'>, 'name'>) { export function mutationType(config: Omit<NexusObjectTypeConfig<'Mutation'>, 'name'>) {
return objectType({ ...config, name: 'Mutation' }) return objectType({ ...config, name: 'Mutation' })

View File

@ -0,0 +1,64 @@
import { ownProp } from '../utils'
import type { NexusInterfaceTypeDef } from './interfaceType'
import type { NexusObjectTypeDef } from './objectType'
import { isNexusStruct, isNexusInterfaceTypeDef, isNexusObjectTypeDef } from './wrapping'
/** Symbol marking an object as something that can provide Nexus schema definitions */
export const NEXUS_TYPE = Symbol.for('@nexus/meta/NEXUS_TYPE')
export const NEXUS_BUILD = Symbol.for('@nexus/meta/NEXUS_BUILD')
type OutType = NexusObjectTypeDef<any> | NexusInterfaceTypeDef<any>
/** Object representing a single output or interface type */
export type NexusMetaTypeProp = {
[NEXUS_TYPE]: OutType
}
export type NexusMetaTypeFn = {
[NEXUS_TYPE]: () => OutType
}
export type NexusMetaType = NexusMetaTypeProp | NexusMetaTypeFn
/** Object containing a symbol defining a function that should be fed into the Nexus type construction layer */
export type NexusMetaBuild = {
[NEXUS_BUILD]: () => any
}
export type NexusMeta = NexusMetaType | NexusMetaBuild
export function isNexusMetaBuild(obj: any): obj is NexusMetaBuild {
return Boolean(obj && typeof ownProp.get(obj, NEXUS_BUILD) === 'function')
}
export function isNexusMetaType(obj: any): obj is NexusMetaType {
return isNexusMetaTypeProp(obj) || isNexusMetaTypeFn(obj)
}
export function isNexusMetaTypeProp(obj: any): obj is NexusMetaTypeProp {
return Boolean(obj && ownProp.has(obj, NEXUS_TYPE) && isNexusStruct(ownProp.get(obj, NEXUS_TYPE)))
}
export function isNexusMetaTypeFn(obj: any): obj is NexusMetaTypeFn {
return Boolean(obj && ownProp.has(obj, NEXUS_TYPE) && typeof ownProp.get(obj, NEXUS_TYPE) === 'function')
}
export function isNexusMeta(obj: any): obj is NexusMetaBuild | NexusMetaTypeFn | NexusMetaType {
return isNexusMetaBuild(obj) || isNexusMetaType(obj) || isNexusMetaTypeFn(obj)
}
/**
* Evaluates the thunk, replacing it with the type
*
* @param obj
*/
export function resolveNexusMetaType(obj: NexusMetaType): OutType {
let value = ownProp.get(obj, NEXUS_TYPE)
if (typeof value === 'function') {
value = ownProp.set(obj, NEXUS_TYPE, value.call(obj))
}
if (!isNexusObjectTypeDef(value) && !isNexusInterfaceTypeDef(value)) {
throw new Error(`Expected property of NEXUS_TYPE to be an object or interface type`)
}
return value
}

View File

@ -1,4 +1,5 @@
import { isNonNullType, isType } from 'graphql' import { isNonNullType, isType } from 'graphql'
import { isNexusMeta } from './nexusMeta'
import { isNexusNonNullTypeDef, isNexusNullTypeDef, isNexusStruct, NexusNonNullableTypes } from './wrapping' import { isNexusNonNullTypeDef, isNexusNullTypeDef, isNexusStruct, NexusNonNullableTypes } from './wrapping'
import { NexusTypes, withNexusSymbol } from './_types' import { NexusTypes, withNexusSymbol } from './_types'
@ -8,7 +9,12 @@ export class NexusNonNullDef<TypeName extends NexusNonNullableTypes> {
private _isNexusNonNullDef: boolean = true private _isNexusNonNullDef: boolean = true
constructor(readonly ofNexusType: TypeName) { constructor(readonly ofNexusType: TypeName) {
if (typeof ofNexusType !== 'string' && !isNexusStruct(ofNexusType) && !isType(ofNexusType)) { if (
typeof ofNexusType !== 'string' &&
!isNexusStruct(ofNexusType) &&
!isNexusMeta(ofNexusType) &&
!isType(ofNexusType)
) {
throw new Error('Cannot wrap unknown types in a nonNull(). Saw ' + ofNexusType) throw new Error('Cannot wrap unknown types in a nonNull(). Saw ' + ofNexusType)
} }
} }
@ -54,16 +60,13 @@ withNexusSymbol(NexusNonNullDef, NexusTypes.NonNull)
* *
* @param type The type to wrap in Non-Null. This may be expressed in one of three ways: * @param type The type to wrap in Non-Null. This may be expressed in one of three ways:
* *
* 1. As string literals matching the name of a builtin scalar. E.g.: 'ID', 'String', ... * 1. As string literals matching the name of a builtin scalar. E.g.: 'ID', 'String', ...
* 2. As string literals matching the name of another type. E.g.: 'User', 'Location', ... Thanks to [Nexus'
* reflection system](https://nxs.li/guides/reflection) this is typesafe and autocompletable. This is
* the idiomatic approach in Nexus because it avoids excessive importing and circular references.
* 3. As references to other enums or object type definitions. E.g.: User, Location
* *
* 2. As string literals matching the name of another type. E.g.: 'User', 'Location', ... Thanks to * You may also use other type modifier helpers like list() which in turn accept one of the three
* [Nexus' reflection
* system](https://nxs.li/guides/reflection) this is typesafe and autocompletable. This is the idiomatic
* approach in Nexus because it avoids excessive importing and circular references.
*
* 3. As references to other enums or object type definitions. E.g.: User, Location
*
* You may also use other type modifier helpers like list() which in turn accept one of the three
*/ */
export function nonNull<TypeName extends NexusNonNullableTypes>(type: TypeName) { export function nonNull<TypeName extends NexusNonNullableTypes>(type: TypeName) {
if (isNexusNonNullTypeDef(type) || isNonNullType(type)) { if (isNexusNonNullTypeDef(type) || isNonNullType(type)) {

View File

@ -1,4 +1,5 @@
import { isType } from 'graphql' import { isType } from 'graphql'
import { isNexusMeta } from './nexusMeta'
import { isNexusNonNullTypeDef, isNexusNullTypeDef, isNexusStruct, NexusNullableTypes } from './wrapping' import { isNexusNonNullTypeDef, isNexusNullTypeDef, isNexusStruct, NexusNullableTypes } from './wrapping'
import { NexusTypes, withNexusSymbol } from './_types' import { NexusTypes, withNexusSymbol } from './_types'
@ -8,7 +9,12 @@ export class NexusNullDef<TypeName extends NexusNullableTypes> {
private _isNexusNullDef: boolean = true private _isNexusNullDef: boolean = true
constructor(readonly ofNexusType: TypeName) { constructor(readonly ofNexusType: TypeName) {
if (typeof ofNexusType !== 'string' && !isNexusStruct(ofNexusType) && !isType(ofNexusType)) { if (
typeof ofNexusType !== 'string' &&
!isNexusStruct(ofNexusType) &&
!isNexusMeta(ofNexusType) &&
!isType(ofNexusType)
) {
throw new Error('Cannot wrap unknown types in nullable(). Saw ' + ofNexusType) throw new Error('Cannot wrap unknown types in nullable(). Saw ' + ofNexusType)
} }
} }
@ -58,16 +64,13 @@ withNexusSymbol(NexusNullDef, NexusTypes.Null)
* *
* @param type The type to wrap in Non-Null. This may be expressed in one of three ways: * @param type The type to wrap in Non-Null. This may be expressed in one of three ways:
* *
* 1. As string literals matching the name of a builtin scalar. E.g.: 'ID', 'String', ... * 1. As string literals matching the name of a builtin scalar. E.g.: 'ID', 'String', ...
* 2. As string literals matching the name of another type. E.g.: 'User', 'Location', ... Thanks to [Nexus'
* reflection system](https://nxs.li/guides/reflection) this is typesafe and autocompletable. This is
* the idiomatic approach in Nexus because it avoids excessive importing and circular references.
* 3. As references to other enums or object type definitions. E.g.: User, Location
* *
* 2. As string literals matching the name of another type. E.g.: 'User', 'Location', ... Thanks to * You may also use other type modifier helpers like list() which in turn accept one of the three
* [Nexus' reflection
* system](https://nxs.li/guides/reflection) this is typesafe and autocompletable. This is the idiomatic
* approach in Nexus because it avoids excessive importing and circular references.
*
* 3. As references to other enums or object type definitions. E.g.: User, Location
*
* You may also use other type modifier helpers like list() which in turn accept one of the three
*/ */
export function nullable<TypeName extends NexusNullableTypes>(type: TypeName) { export function nullable<TypeName extends NexusNullableTypes>(type: TypeName) {
if (isNexusNonNullTypeDef(type)) { if (isNexusNonNullTypeDef(type)) {

View File

@ -1,8 +1,9 @@
import { assertValidName, GraphQLObjectType } from 'graphql' import { assertValidName, GraphQLObjectType } from 'graphql'
import type { InterfaceFieldsFor } from '../typegenTypeHelpers' import type { InterfaceFieldsFor } from '../typegenTypeHelpers'
import { OutputDefinitionBlock, OutputDefinitionBuilder } from './definitionBlocks' import { OutputDefinitionBlock, OutputDefinitionBuilder } from './definitionBlocks'
import type { Directives } from './directive'
import type { FieldModification, FieldModificationDef, Implemented } from './interfaceType' import type { FieldModification, FieldModificationDef, Implemented } from './interfaceType'
import { AbstractTypes, NexusTypes, NonNullConfig, SourceTypingDef, withNexusSymbol } from './_types' import { AbstractTypes, Maybe, NexusTypes, NonNullConfig, SourceTypingDef, withNexusSymbol } from './_types'
export interface ObjectDefinitionBuilder extends OutputDefinitionBuilder { export interface ObjectDefinitionBuilder extends OutputDefinitionBuilder {
addInterfaces(toAdd: Implemented[]): void addInterfaces(toAdd: Implemented[]): void
@ -102,7 +103,7 @@ export type NexusObjectTypeConfig<TypeName extends string> = {
* // # ... * // # ...
* // } * // }
*/ */
description?: string description?: Maybe<string>
/** /**
* [Source Types Guide](https://nxs.li/guides/backing-types) * [Source Types Guide](https://nxs.li/guides/backing-types)
* *
@ -118,8 +119,8 @@ export type NexusObjectTypeConfig<TypeName extends string> = {
* *
* @example * @example
* { * {
* module: 'some-package', * "module": "some-package",
* export: 'User', * "export": "User"
* } * }
* *
* @example * @example
@ -165,10 +166,9 @@ export type NexusObjectTypeConfig<TypeName extends string> = {
* within. You can leverage conditionals, loops, other functions (that take the builder api as an * within. You can leverage conditionals, loops, other functions (that take the builder api as an
* argument), pull in variables from higher scopes, and so on, to help define your fields. However avoid two things: * argument), pull in variables from higher scopes, and so on, to help define your fields. However avoid two things:
* *
* 1. Doing asynchronous work when defining fields. * 1. Doing asynchronous work when defining fields. 2. Triggering side-effects that you would NOT want run
* * at *build* timeas this code will run during build
* 2. Triggering side-effects that you would NOT want run at _build_ timeas this code will run during * to support [Nexus' reflection system](https://nxs.li/guides/reflection).
* build to support [Nexus' reflection system](https://nxs.li/guides/reflection).
* *
* @example * @example
* objectType({ * objectType({
@ -186,10 +186,19 @@ export type NexusObjectTypeConfig<TypeName extends string> = {
* }) * })
* *
* @param t The type builder API for object types. The primary method you'll find is "t.field" but there are * @param t The type builder API for object types. The primary method you'll find is "t.field" but there are
* many convenient shorthands available as well, plus anything plugins have added. Explore each one's jsDoc * many convenient shorthands available as well, plus anything plugins have added. Explore each one's
* for more detail. * jsDoc for more detail.
*/ */
definition(t: ObjectDefinitionBlock<TypeName>): void definition(t: ObjectDefinitionBlock<TypeName>): void
/**
* A list of directives / directive uses (with args) for the object type definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
/** Adds this type as a method on the Object/Interface definition blocks */
asNexusMethod?: string
} & AbstractTypes.MaybeTypeDefConfigFieldIsTypeOf<TypeName> & } & AbstractTypes.MaybeTypeDefConfigFieldIsTypeOf<TypeName> &
NexusGenPluginTypeConfig<TypeName> NexusGenPluginTypeConfig<TypeName>

View File

@ -1,4 +1,4 @@
import type { FieldOutConfig, OutputDefinitionBlock } from '../core' import type { FieldOutConfig, OutputDefinitionBlock } from './definitionBlocks'
import { extendType, NexusExtendTypeDef } from './extendType' import { extendType, NexusExtendTypeDef } from './extendType'
export type QueryFieldConfig<FieldName extends string> = export type QueryFieldConfig<FieldName extends string> =
@ -101,8 +101,7 @@ export function queryField(
* }) * })
* *
* @param name The name of the field on the Query type. Names are casesensitive and must conform to pattern: * @param name The name of the field on the Query type. Names are casesensitive and must conform to pattern:
* * `[_A-Za-z][_0-9A-Za-z]*`
* [_A-Za-z][_0-9A-Za-z]*
* @param config The same type of configuration you would pass to t.field("...", config) * @param config The same type of configuration you would pass to t.field("...", config)
*/ */
export function queryField<FieldName extends string>( export function queryField<FieldName extends string>(

View File

@ -18,7 +18,7 @@ import { NexusObjectTypeConfig, objectType } from './objectType'
* *
* @example * @example
* queryType({ * queryType({
* definitin(t) { * definition(t) {
* t.field('user', { * t.field('user', {
* type: 'User', * type: 'User',
* args: { * args: {
@ -36,8 +36,8 @@ import { NexusObjectTypeConfig, objectType } from './objectType'
* }, * },
* }) * })
* *
* @param config Specify your Query type's fields, description, and more. See each config property's jsDoc * @param config Specify your Query type's fields, description, and more. See each config property's jsDoc for
* for more detail. * more detail.
*/ */
export function queryType(config: Omit<NexusObjectTypeConfig<'Query'>, 'name'>) { export function queryType(config: Omit<NexusObjectTypeConfig<'Query'>, 'name'>) {
return objectType({ ...config, name: 'Query' }) return objectType({ ...config, name: 'Query' })

View File

@ -1,6 +1,8 @@
import { assertValidName, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql' import { assertValidName, GraphQLNamedType, GraphQLScalarTypeConfig } from 'graphql'
import type { AllNexusInputTypeDefs, AllNexusOutputTypeDefs } from '../core'
import { decorateType } from './decorateType' import { decorateType } from './decorateType'
import { NexusTypes, SourceTypingDef, withNexusSymbol } from './_types' import type { Directives } from './directive'
import { GraphQLNamedOutputType, Maybe, NexusTypes, SourceTypingDef, withNexusSymbol } from './_types'
export interface ScalarBase export interface ScalarBase
extends Pick< extends Pick<
@ -10,7 +12,7 @@ export interface ScalarBase
export interface ScalarConfig { export interface ScalarConfig {
/** Any deprecation info for this scalar type */ /** Any deprecation info for this scalar type */
deprecation?: string // | DeprecationInfo; deprecation?: Maybe<string> // | DeprecationInfo;
/** Adds this type as a method on the Object/Interface definition blocks */ /** Adds this type as a method on the Object/Interface definition blocks */
asNexusMethod?: string asNexusMethod?: string
/** Source type information for this type */ /** Source type information for this type */
@ -21,6 +23,13 @@ export interface ScalarConfig {
* @see https://github.com/graphql/graphql-js/issues/1527 * @see https://github.com/graphql/graphql-js/issues/1527
*/ */
extensions?: GraphQLScalarTypeConfig<any, any>['extensions'] extensions?: GraphQLScalarTypeConfig<any, any>['extensions']
/**
* A list of directives / directive uses (with args) for the scalar type definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
} }
export interface NexusScalarTypeConfig<T extends string> extends ScalarBase, ScalarConfig { export interface NexusScalarTypeConfig<T extends string> extends ScalarBase, ScalarConfig {
@ -43,13 +52,13 @@ export function scalarType<TypeName extends string>(options: NexusScalarTypeConf
return new NexusScalarTypeDef(options.name, options) return new NexusScalarTypeDef(options.name, options)
} }
export function asNexusMethod<T extends GraphQLScalarType>( export function asNexusMethod<T extends GraphQLNamedType>(
scalar: T, namedType: T,
methodName: string, methodName: string,
sourceType?: SourceTypingDef sourceType?: SourceTypingDef
): T { ): T extends GraphQLNamedOutputType ? AllNexusOutputTypeDefs : AllNexusInputTypeDefs {
return decorateType(scalar, { return decorateType(namedType, {
asNexusMethod: methodName, asNexusMethod: methodName,
sourceType, sourceType,
}) }) as any
} }

View File

@ -15,16 +15,16 @@ export type SubscriptionFieldConfig<FieldName extends string, Event> =
* `extendType({ type: 'Subscription' })` * `extendType({ type: 'Subscription' })`
* *
* The Subscription type is one of three [root * The Subscription type is one of three [root
* types](https://spec.graphql.org/June2018/#sec-Root-Operation-Types) in GraphQL and its fields represent * types](https://spec.graphql.org/June2018/#sec-Root-Operation-Types) in GraphQL and its fields represent API
* API operations your clients can run to be pushed data changes over time. * operations your clients can run to be pushed data changes over time.
* *
* Use this instead of subscriptionType if you are going to modularize your schema and thus be wanting to * Use this instead of subscriptionType if you are going to modularize your schema and thus be wanting to
* contribute fields to the Subscription type from multiple modules. You do not have to have previously * contribute fields to the Subscription type from multiple modules. You do not have to have previously
* defined a Query type before using this. If you haven't Nexus will create one automatically for you. * defined a Query type before using this. If you haven't Nexus will create one automatically for you.
* *
* Note that the main difference about Subscription type from other object types is that its field * Note that the main difference about Subscription type from other object types is that its field
* configurations require a special "subscribe" method where you can return an asynchronous iterator. * configurations require a special "subscribe" method where you can return an asynchronous iterator. Promises
* Promises yielded by that iterator become available to the resolver in its first param, the source data. * yielded by that iterator become available to the resolver in its first param, the source data.
* *
* If you need to leverage plugins or define multiple fields then use the typeBuilder overload variant of this * If you need to leverage plugins or define multiple fields then use the typeBuilder overload variant of this
* function. Otherwise you may prefer to the field name/config variant. * function. Otherwise you may prefer to the field name/config variant.
@ -126,16 +126,16 @@ export function subscriptionField(
* `extendType({ type: 'Subscription' })` * `extendType({ type: 'Subscription' })`
* *
* The Subscription type is one of three [root * The Subscription type is one of three [root
* types](https://spec.graphql.org/June2018/#sec-Root-Operation-Types) in GraphQL and its fields represent * types](https://spec.graphql.org/June2018/#sec-Root-Operation-Types) in GraphQL and its fields represent API
* API operations your clients can run to be pushed data changes over time. * operations your clients can run to be pushed data changes over time.
* *
* Use this instead of subscriptionType if you are going to modularize your schema and thus be wanting to * Use this instead of subscriptionType if you are going to modularize your schema and thus be wanting to
* contribute fields to the Subscription type from multiple modules. You do not have to have previously * contribute fields to the Subscription type from multiple modules. You do not have to have previously
* defined a Query type before using this. If you haven't Nexus will create one automatically for you. * defined a Query type before using this. If you haven't Nexus will create one automatically for you.
* *
* Note that the main difference about Subscription type from other object types is that its field * Note that the main difference about Subscription type from other object types is that its field
* configurations require a special "subscribe" method where you can return an asynchronous iterator. * configurations require a special "subscribe" method where you can return an asynchronous iterator. Promises
* Promises yielded by that iterator become available to the resolver in its first param, the source data. * yielded by that iterator become available to the resolver in its first param, the source data.
* *
* If you need to leverage plugins or define multiple fields then use the typeBuilder overload variant of this * If you need to leverage plugins or define multiple fields then use the typeBuilder overload variant of this
* function. Otherwise you may prefer to the field name/config variant. * function. Otherwise you may prefer to the field name/config variant.
@ -222,8 +222,7 @@ export function subscriptionField(
* }) * })
* *
* @param name The name of the field on the Query type. Names are casesensitive and must conform to pattern: * @param name The name of the field on the Query type. Names are casesensitive and must conform to pattern:
* * `[_A-Za-z][_0-9A-Za-z]*`
* [_A-Za-z][_0-9A-Za-z]*
* @param config The same type of configuration you would pass to t.field("...", config) * @param config The same type of configuration you would pass to t.field("...", config)
*/ */
export function subscriptionField<FieldName extends string, Event>( export function subscriptionField<FieldName extends string, Event>(

View File

@ -30,11 +30,17 @@ export type SubscriptionScalarConfig<FieldName extends string, Event> =
& SubscriptionTypeConfigBase<FieldName, Event> & SubscriptionTypeConfigBase<FieldName, Event>
// prettier-ignore // prettier-ignore
export interface SubscriptionTypeConfig<FieldName extends string, Event> export interface SubscriptionTypeConfig<FieldName extends string, Event> extends SubscriptionScalarConfig<FieldName, Event> {
extends SubscriptionScalarConfig<FieldName, Event> type: GetGen<'allOutputTypes'> | AllNexusOutputTypeDefs
{ }
type: GetGen<'allOutputTypes'> | AllNexusOutputTypeDefs
} // prettier-ignore
export interface SubscriptionTypeConfigWithName<FieldName extends string, Event> extends SubscriptionTypeConfig<FieldName, Event> {
/**
* The name of this field. Must conform to the regex pattern: [_A-Za-z][_0-9A-Za-z]*
*/
name: FieldName
}
// prettier-ignore // prettier-ignore
export interface SubscriptionBuilder { export interface SubscriptionBuilder {
@ -46,7 +52,8 @@ export interface SubscriptionBuilder {
boolean<FieldName extends string, Event>(fieldName: FieldName, opts: SubscriptionScalarConfig<FieldName, Event>): void boolean<FieldName extends string, Event>(fieldName: FieldName, opts: SubscriptionScalarConfig<FieldName, Event>): void
id<FieldName extends string, Event>(fieldName: FieldName, config: SubscriptionScalarConfig<FieldName, Event>): void id<FieldName extends string, Event>(fieldName: FieldName, config: SubscriptionScalarConfig<FieldName, Event>): void
float<FieldName extends string, Event>(fieldName: FieldName, config: SubscriptionScalarConfig<FieldName, Event>): void float<FieldName extends string, Event>(fieldName: FieldName, config: SubscriptionScalarConfig<FieldName, Event>): void
field<FieldName extends string, Event>(name: FieldName, fieldConfig: SubscriptionTypeConfig<FieldName, Event>): void field<FieldName extends string, Event>(config: SubscriptionTypeConfigWithName<FieldName, Event>): void
field<FieldName extends string, Event>(name: FieldName, config: SubscriptionTypeConfig<FieldName, Event>): void
} }
export type SubscriptionTypeParams = { export type SubscriptionTypeParams = {
@ -63,16 +70,16 @@ export type SubscriptionTypeParams = {
* `objectType({ name: 'Subscription' })` * `objectType({ name: 'Subscription' })`
* *
* The Subscription type is one of three [root * The Subscription type is one of three [root
* types](https://spec.graphql.org/June2018/#sec-Root-Operation-Types) in GraphQL and its fields represent * types](https://spec.graphql.org/June2018/#sec-Root-Operation-Types) in GraphQL and its fields represent API
* API operations your clients can run to be pushed data changes over time. * operations your clients can run to be pushed data changes over time.
* *
* You can only have one of these in your schema. If you are going to modularize your schema and thus be * You can only have one of these in your schema. If you are going to modularize your schema and thus be
* wanting to contribute fields to the Subscription type from multiple modules then use * wanting to contribute fields to the Subscription type from multiple modules then use
* [queryField](https://nxs.li/docs/api/subscription-field) intead. * [queryField](https://nxs.li/docs/api/subscription-field) intead.
* *
* Note that the main difference about Subscription type from other object types is that its field * Note that the main difference about Subscription type from other object types is that its field
* configurations require a special "subscribe" method where you can return an asynchronous iterator. * configurations require a special "subscribe" method where you can return an asynchronous iterator. Promises
* Promises yielded by that iterator become available to the resolver in its first param, the source data. * yielded by that iterator become available to the resolver in its first param, the source data.
* *
* @example * @example
* // Contrived but simple self-contained example * // Contrived but simple self-contained example
@ -165,8 +172,8 @@ export type SubscriptionTypeParams = {
* }, * },
* }) * })
* *
* @param config Specify your Subscription type's fields, description, and more. See each config property's jsDoc * @param config Specify your Subscription type's fields, description, and more. See each config property's
* for more detail. * jsDoc for more detail.
*/ */
export function subscriptionType(config: SubscriptionTypeParams) { export function subscriptionType(config: SubscriptionTypeParams) {
return objectType({ ...config, name: 'Subscription' } as any) return objectType({ ...config, name: 'Subscription' } as any)

View File

@ -1,7 +1,8 @@
import { assertValidName, GraphQLUnionTypeConfig } from 'graphql' import { assertValidName, GraphQLUnionTypeConfig } from 'graphql'
import type { Directives } from '../core'
import type { GetGen } from '../typegenTypeHelpers' import type { GetGen } from '../typegenTypeHelpers'
import type { NexusObjectTypeDef } from './objectType' import type { NexusObjectTypeDef } from './objectType'
import { AbstractTypes, NexusTypes, SourceTypingDef, withNexusSymbol } from './_types' import { AbstractTypes, Maybe, NexusTypes, SourceTypingDef, withNexusSymbol } from './_types'
export interface UnionDefinitionBuilder { export interface UnionDefinitionBuilder {
typeName: string typeName: string
@ -27,12 +28,12 @@ export type NexusUnionTypeConfig<TypeName extends string> = {
/** Builds the definition for the union */ /** Builds the definition for the union */
definition(t: UnionDefinitionBlock): void definition(t: UnionDefinitionBlock): void
/** The description to annotate the GraphQL SDL */ /** The description to annotate the GraphQL SDL */
description?: string description?: Maybe<string>
/** /**
* Info about a field deprecation. Formatted as a string and provided with the deprecated directive on * Info about a field deprecation. Formatted as a string and provided with the deprecated directive on
* field/enum types and as a comment on input fields. * field/enum types and as a comment on input fields.
*/ */
deprecation?: string // | DeprecationInfo; deprecation?: Maybe<string> // | DeprecationInfo;
/** Source type information for this type */ /** Source type information for this type */
sourceType?: SourceTypingDef sourceType?: SourceTypingDef
/** /**
@ -41,6 +42,15 @@ export type NexusUnionTypeConfig<TypeName extends string> = {
* @see https://github.com/graphql/graphql-js/issues/1527 * @see https://github.com/graphql/graphql-js/issues/1527
*/ */
extensions?: GraphQLUnionTypeConfig<any, any>['extensions'] extensions?: GraphQLUnionTypeConfig<any, any>['extensions']
/**
* A list of directives / directive uses (with args) for the union type definition
*
* @example
* directives: [addDirective('ExampleDirective', { arg: true })]
*/
directives?: Directives
/** Adds this type as a method on the Object/Interface definition blocks */
asNexusMethod?: string
} & AbstractTypes.MaybeTypeDefConfigFieldResolveType<TypeName> } & AbstractTypes.MaybeTypeDefConfigFieldResolveType<TypeName>
export class NexusUnionTypeDef<TypeName extends string> { export class NexusUnionTypeDef<TypeName extends string> {

View File

@ -24,23 +24,33 @@ import { NexusNonNullDef, nonNull } from './nonNull'
import { NexusNullDef, nullable } from './nullable' import { NexusNullDef, nullable } from './nullable'
import type { NexusObjectTypeDef } from './objectType' import type { NexusObjectTypeDef } from './objectType'
import type { NexusScalarTypeDef } from './scalarType' import type { NexusScalarTypeDef } from './scalarType'
import { isNexusMetaType, NexusMetaType, resolveNexusMetaType } from './nexusMeta'
import type { NexusUnionTypeDef } from './unionType' import type { NexusUnionTypeDef } from './unionType'
import { NexusTypes, NexusWrappedSymbol } from './_types' import { NexusTypes, NexusWrappedSymbol } from './_types'
import type { NexusDirectiveDef } from './directive'
import type { NexusDirectiveUse } from '../core'
/** Input(named): Nexus only */
export type AllNexusNamedInputTypeDefs<T extends string = any> = export type AllNexusNamedInputTypeDefs<T extends string = any> =
| NexusInputObjectTypeDef<T> | NexusInputObjectTypeDef<T>
| NexusEnumTypeDef<T> | NexusEnumTypeDef<T>
| NexusScalarTypeDef<T> | NexusScalarTypeDef<T>
/** Input(named): Nexus + GraphQLInput */
export type AllNamedInputTypeDefs<T extends string = any> =
| AllNexusNamedInputTypeDefs<T>
| Exclude<GraphQLInputType, GraphQLList<any> | GraphQLNonNull<any>> | Exclude<GraphQLInputType, GraphQLList<any> | GraphQLNonNull<any>>
/** Input(all): Nexus + GraphQL */
export type AllNexusInputTypeDefs<T extends string = any> = export type AllNexusInputTypeDefs<T extends string = any> =
| AllNexusNamedInputTypeDefs<T> | AllNamedInputTypeDefs<T>
| NexusListDef<any> | NexusListDef<any>
| NexusNonNullDef<any> | NexusNonNullDef<any>
| NexusNullDef<any> | NexusNullDef<any>
| GraphQLList<any> | GraphQLList<any>
| GraphQLNonNull<any> | GraphQLNonNull<any>
/** Output(named): Nexus only */
export type AllNexusNamedOutputTypeDefs = export type AllNexusNamedOutputTypeDefs =
| NexusObjectTypeDef<any> | NexusObjectTypeDef<any>
| NexusInterfaceTypeDef<any> | NexusInterfaceTypeDef<any>
@ -48,34 +58,53 @@ export type AllNexusNamedOutputTypeDefs =
| NexusEnumTypeDef<any> | NexusEnumTypeDef<any>
| NexusScalarTypeDef<any> | NexusScalarTypeDef<any>
/** Output(all): Nexus only */
export type AllNexusOutputTypeDefs = export type AllNexusOutputTypeDefs =
| AllNexusNamedOutputTypeDefs | AllNexusNamedOutputTypeDefs
| NexusListDef<any> | NexusListDef<any>
| NexusNonNullDef<any> | NexusNonNullDef<any>
| NexusNullDef<any> | NexusNullDef<any>
/** Input + output(named): Nexus only */
export type AllNexusNamedTypeDefs = AllNexusNamedInputTypeDefs | AllNexusNamedOutputTypeDefs export type AllNexusNamedTypeDefs = AllNexusNamedInputTypeDefs | AllNexusNamedOutputTypeDefs
/** Input + output(all): Nexus only */
export type AllNexusTypeDefs = AllNexusOutputTypeDefs | AllNexusInputTypeDefs export type AllNexusTypeDefs = AllNexusOutputTypeDefs | AllNexusInputTypeDefs
/** Input + output(all): Nexus only + Name */
export type AllNamedTypeDefs = AllNexusNamedTypeDefs | GraphQLNamedType
/** All inputs to list(...) */
export type NexusListableTypes = export type NexusListableTypes =
| GetGen<'allNamedTypes', string>
| AllNamedTypeDefs | AllNamedTypeDefs
| NexusArgDef<any> | NexusArgDef<any>
| NexusListDef<NexusListableTypes> | NexusListDef<NexusListableTypes>
| NexusNonNullDef<NexusNonNullableTypes> | NexusNonNullDef<NexusNonNullableTypes>
| NexusNullDef<NexusNullableTypes> | NexusNullDef<NexusNullableTypes>
| GraphQLType | GraphQLType
| NexusMetaType
export type NexusNonNullableTypes = AllNamedTypeDefs | NexusListDef<NexusListableTypes> | NexusArgDef<any> /** All inputs to nonNull(...) */
export type NexusNonNullableTypes =
| GetGen<'allNamedTypes', string>
| AllNamedTypeDefs
| NexusListDef<NexusListableTypes>
| NexusArgDef<any>
| NexusMetaType
export type NexusNullableTypes = AllNamedTypeDefs | NexusListDef<NexusListableTypes> | NexusArgDef<any> /** All inputs to nullable(...) */
export type NexusNullableTypes =
export type AllNamedTypeDefs = GetGen<'allNamedTypes', string> | AllNexusNamedTypeDefs | GetGen<'allNamedTypes', string>
| AllNamedTypeDefs
| NexusListDef<NexusListableTypes>
| NexusArgDef<any>
| NexusMetaType
export type AllNexusNamedArgsDefs<T extends AllInputTypes = AllInputTypes> = export type AllNexusNamedArgsDefs<T extends AllInputTypes = AllInputTypes> =
| T | T
| NexusArgDef<T> | NexusArgDef<T>
| AllNexusNamedInputTypeDefs<T> | AllNamedInputTypeDefs<T>
| GraphQLInputType | GraphQLInputType
export type AllNexusArgsDefs = export type AllNexusArgsDefs =
@ -152,6 +181,13 @@ export function isNexusArgDef(obj: any): obj is NexusArgDef<AllInputTypes> {
return isNexusStruct(obj) && obj[NexusWrappedSymbol] === NexusTypes.Arg return isNexusStruct(obj) && obj[NexusWrappedSymbol] === NexusTypes.Arg
} }
export function isNexusNamedOuputTypeDef(obj: any): obj is AllNexusNamedOutputTypeDefs {
return isNexusNamedTypeDef(obj) && !isNexusInputObjectTypeDef(obj)
}
export function isNexusNamedInputTypeDef(obj: any): obj is AllNexusNamedInputTypeDefs {
return isNexusNamedTypeDef(obj) && !isNexusObjectTypeDef(obj) && !isNexusInterfaceTypeDef(obj)
}
export function isNexusDynamicOutputProperty<T extends string>(obj: any): obj is DynamicOutputPropertyDef<T> { export function isNexusDynamicOutputProperty<T extends string>(obj: any): obj is DynamicOutputPropertyDef<T> {
return isNexusStruct(obj) && obj[NexusWrappedSymbol] === NexusTypes.DynamicOutputProperty return isNexusStruct(obj) && obj[NexusWrappedSymbol] === NexusTypes.DynamicOutputProperty
} }
@ -172,12 +208,20 @@ export function isNexusPlugin(obj: any): obj is NexusPlugin {
return isNexusStruct(obj) && obj[NexusWrappedSymbol] === NexusTypes.Plugin return isNexusStruct(obj) && obj[NexusWrappedSymbol] === NexusTypes.Plugin
} }
export function isNexusDirective(obj: any): obj is NexusDirectiveDef {
return isNexusStruct(obj) && obj[NexusWrappedSymbol] === NexusTypes.Directive
}
export function isNexusDirectiveUse(obj: any): obj is NexusDirectiveUse {
return isNexusStruct(obj) && obj[NexusWrappedSymbol] === NexusTypes.DirectiveUse
}
export type NexusWrapKind = 'NonNull' | 'Null' | 'List' export type NexusWrapKind = 'NonNull' | 'Null' | 'List'
export type NexusFinalWrapKind = 'NonNull' | 'List' export type NexusFinalWrapKind = 'NonNull' | 'List'
export function unwrapGraphQLDef( export function unwrapGraphQLDef(typeDef: GraphQLType): {
typeDef: GraphQLType namedType: GraphQLNamedType
): { namedType: GraphQLNamedType; wrapping: NexusFinalWrapKind[] } { wrapping: NexusFinalWrapKind[]
} {
const wrapping: NexusFinalWrapKind[] = [] const wrapping: NexusFinalWrapKind[] = []
let namedType = typeDef let namedType = typeDef
while (isWrappingType(namedType)) { while (isWrappingType(namedType)) {
@ -195,15 +239,17 @@ export function unwrapGraphQLDef(
/** Unwraps any wrapped Nexus or GraphQL types, turning into a list of wrapping */ /** Unwraps any wrapped Nexus or GraphQL types, turning into a list of wrapping */
export function unwrapNexusDef( export function unwrapNexusDef(
typeDef: AllNexusTypeDefs | AllNexusArgsDefs | GraphQLType | string typeDef: AllNexusTypeDefs | AllNexusArgsDefs | GraphQLType | NexusMetaType | string
): { ): {
namedType: AllNexusNamedTypeDefs | AllNexusArgsDefs | GraphQLNamedType | string namedType: AllNexusNamedTypeDefs | AllNexusArgsDefs | GraphQLNamedType | string
wrapping: NexusWrapKind[] wrapping: NexusWrapKind[]
} { } {
const wrapping: NexusWrapKind[] = [] const wrapping: NexusWrapKind[] = []
let namedType = typeDef let namedType = typeDef
while (isNexusWrappingType(namedType) || isWrappingType(namedType)) { while (isNexusWrappingType(namedType) || isWrappingType(namedType) || isNexusMetaType(namedType)) {
if (isWrappingType(namedType)) { if (isNexusMetaType(namedType)) {
namedType = resolveNexusMetaType(namedType)
} else if (isWrappingType(namedType)) {
if (isListType(namedType)) { if (isListType(namedType)) {
wrapping.unshift('List') wrapping.unshift('List')
} else if (isNonNullType(namedType)) { } else if (isNonNullType(namedType)) {
@ -233,10 +279,10 @@ export function rewrapAsGraphQLType(baseType: GraphQLNamedType, wrapping: NexusF
let finalType: GraphQLType = baseType let finalType: GraphQLType = baseType
wrapping.forEach((wrap) => { wrapping.forEach((wrap) => {
if (wrap === 'List') { if (wrap === 'List') {
finalType = GraphQLList(finalType) finalType = new GraphQLList(finalType)
} else if (wrap === 'NonNull') { } else if (wrap === 'NonNull') {
if (!isNonNullType(finalType)) { if (!isNonNullType(finalType)) {
finalType = GraphQLNonNull(finalType) finalType = new GraphQLNonNull(finalType)
} }
} else { } else {
throw new Unreachable(wrap) throw new Unreachable(wrap)

View File

@ -63,9 +63,9 @@ withNexusSymbol(DynamicOutputMethodDef, NexusTypes.DynamicOutputMethod)
* Defines a new property on the object definition block for an output type, taking arbitrary input to define * Defines a new property on the object definition block for an output type, taking arbitrary input to define
* additional types. See the connectionPlugin: * additional types. See the connectionPlugin:
* *
* T.connectionField('posts', { nullable: true, totalCount(root, args, ctx, info) { * T.connectionField('posts', { nullable: true, totalCount(root, args, ctx, info) { return
* return ctx.user.getTotalPostCount(root.id, args) }, nodes(root, args, ctx, info) { * ctx.user.getTotalPostCount(root.id, args) }, nodes(root, args, ctx, info) { return
* return ctx.user.getPosts(root.id, args) } }) * ctx.user.getPosts(root.id, args) } })
*/ */
export function dynamicOutputMethod<T extends string>(config: DynamicOutputMethodConfig<T>) { export function dynamicOutputMethod<T extends string>(config: DynamicOutputMethodConfig<T>) {
return new DynamicOutputMethodDef(config.name, config) return new DynamicOutputMethodDef(config.name, config)

View File

@ -1,10 +1,20 @@
import { defaultFieldResolver, GraphQLNamedType } from 'graphql' import { defaultFieldResolver, GraphQLNamedType } from 'graphql'
import type { DynamicFieldDefs, SchemaConfig } from './builder' import type { DynamicFieldDefs, SchemaConfig } from './builder'
import type { SourceTypingDef, SourceTypings } from './definitions/_types' import type { SourceTypings } from './definitions/_types'
import type { NexusOutputFieldConfig } from './definitions/definitionBlocks' import type { NexusOutputFieldConfig } from './definitions/definitionBlocks'
import type { NexusInputObjectTypeConfig } from './definitions/inputObjectType' import type { NexusInputObjectTypeConfig } from './definitions/inputObjectType'
import type { NexusInterfaceTypeConfig } from './definitions/interfaceType' import type { NexusInterfaceTypeConfig } from './definitions/interfaceType'
import type { NexusObjectTypeConfig } from './definitions/objectType' import type { NexusObjectTypeConfig } from './definitions/objectType'
import type { Directives, FieldSourceType, NamedFieldSourceType } from './core'
/** @internal */
export function hasNexusExtension(val: any): val is any {
return Boolean(val)
}
export function isNexusFieldExtension(val: any): val is NexusFieldExtension {
return Boolean(val?._type === 'NexusFieldExtension')
}
export type NexusGraphQLNamedType = GraphQLNamedType & { export type NexusGraphQLNamedType = GraphQLNamedType & {
extensions?: { extensions?: {
@ -18,13 +28,17 @@ export type NexusTypeExtensions = NexusObjectTypeExtension | NexusInterfaceTypeE
/** Container object living on `fieldDefinition.extensions.nexus` */ /** Container object living on `fieldDefinition.extensions.nexus` */
export class NexusFieldExtension<TypeName extends string = any, FieldName extends string = any> { export class NexusFieldExtension<TypeName extends string = any, FieldName extends string = any> {
readonly _type = 'NexusFieldExtension' as const
readonly config: Omit<NexusOutputFieldConfig<TypeName, FieldName>, 'resolve'> readonly config: Omit<NexusOutputFieldConfig<TypeName, FieldName>, 'resolve'>
/** Whether the user has provided a custom "resolve" function, or whether we're using GraphQL's defaultResolver */ /** Whether the user has provided a custom "resolve" function, or whether we're using GraphQL's defaultResolver */
readonly hasDefinedResolver: boolean readonly hasDefinedResolver: boolean
readonly sourceType: string | FieldSourceType | NamedFieldSourceType[] | undefined
constructor(config: NexusOutputFieldConfig<TypeName, FieldName>) { constructor(config: NexusOutputFieldConfig<TypeName, FieldName>) {
const { resolve, ...rest } = config const { resolve, ...rest } = config
this.config = rest this.config = rest
this.hasDefinedResolver = Boolean(resolve && resolve !== defaultFieldResolver) this.hasDefinedResolver = Boolean(resolve && resolve !== defaultFieldResolver)
this.sourceType = rest.sourceType
} }
/** Called when there are modifications on the interface fields */ /** Called when there are modifications on the interface fields */
modify(modifications: Partial<NexusOutputFieldConfig<any, any>>) { modify(modifications: Partial<NexusOutputFieldConfig<any, any>>) {
@ -34,6 +48,7 @@ export class NexusFieldExtension<TypeName extends string = any, FieldName extend
/** Container object living on `inputObjectType.extensions.nexus` */ /** Container object living on `inputObjectType.extensions.nexus` */
export class NexusInputObjectTypeExtension<TypeName extends string = any> { export class NexusInputObjectTypeExtension<TypeName extends string = any> {
readonly _type = 'NexusInputObjectTypeExtension' as const
readonly config: Omit<NexusInputObjectTypeConfig<TypeName>, 'definition'> readonly config: Omit<NexusInputObjectTypeConfig<TypeName>, 'definition'>
constructor(config: NexusInputObjectTypeConfig<TypeName>) { constructor(config: NexusInputObjectTypeConfig<TypeName>) {
const { definition, ...rest } = config const { definition, ...rest } = config
@ -43,6 +58,7 @@ export class NexusInputObjectTypeExtension<TypeName extends string = any> {
/** Container object living on `objectType.extensions.nexus` */ /** Container object living on `objectType.extensions.nexus` */
export class NexusObjectTypeExtension<TypeName extends string = any> { export class NexusObjectTypeExtension<TypeName extends string = any> {
readonly _type = 'NexusObjectTypeExtension' as const
readonly config: Omit<NexusObjectTypeConfig<TypeName>, 'definition' | 'isTypeOf'> readonly config: Omit<NexusObjectTypeConfig<TypeName>, 'definition' | 'isTypeOf'>
constructor(config: NexusObjectTypeConfig<TypeName>) { constructor(config: NexusObjectTypeConfig<TypeName>) {
const { definition, ...rest } = config const { definition, ...rest } = config
@ -52,6 +68,7 @@ export class NexusObjectTypeExtension<TypeName extends string = any> {
/** Container object living on `interfaceType.extensions.nexus` */ /** Container object living on `interfaceType.extensions.nexus` */
export class NexusInterfaceTypeExtension<TypeName extends string = any> { export class NexusInterfaceTypeExtension<TypeName extends string = any> {
readonly _type = 'NexusInterfaceTypeExtension' as const
readonly config: Omit<NexusInterfaceTypeConfig<TypeName>, 'definition' | 'resolveType'> readonly config: Omit<NexusInterfaceTypeConfig<TypeName>, 'definition' | 'resolveType'>
constructor(config: NexusInterfaceTypeConfig<TypeName>) { constructor(config: NexusInterfaceTypeConfig<TypeName>) {
const { definition, ...rest } = config const { definition, ...rest } = config
@ -61,7 +78,8 @@ export class NexusInterfaceTypeExtension<TypeName extends string = any> {
export interface NexusSchemaExtensionConfig extends Omit<SchemaConfig, 'types'> { export interface NexusSchemaExtensionConfig extends Omit<SchemaConfig, 'types'> {
dynamicFields: DynamicFieldDefs dynamicFields: DynamicFieldDefs
rootTypings: SourceTypings sourceTypings: SourceTypings
schemaDirectives?: Directives
} }
/** /**
@ -71,10 +89,3 @@ export interface NexusSchemaExtensionConfig extends Omit<SchemaConfig, 'types'>
export class NexusSchemaExtension { export class NexusSchemaExtension {
constructor(readonly config: NexusSchemaExtensionConfig) {} constructor(readonly config: NexusSchemaExtensionConfig) {}
} }
export type NexusScalarExtensions = {
nexus: {
asNexusMethod?: string
sourceType?: SourceTypingDef
}
}

219
src/graphqlInternal.ts Normal file
View File

@ -0,0 +1,219 @@
const MAX_ARRAY_LENGTH = 10
const MAX_RECURSIVE_DEPTH = 2
/** Used to print values in error messages. */
export function inspect(value: unknown): string {
return formatValue(value, [])
}
function formatValue(value: unknown, seenValues: ReadonlyArray<unknown>): string {
switch (typeof value) {
case 'string':
return JSON.stringify(value)
case 'function':
return value.name ? `[function ${value.name}]` : '[function]'
case 'object':
return formatObjectValue(value, seenValues)
default:
return String(value)
}
}
function formatObjectValue(value: object | null, previouslySeenValues: ReadonlyArray<unknown>): string {
if (value === null) {
return 'null'
}
if (previouslySeenValues.includes(value)) {
return '[Circular]'
}
const seenValues = [...previouslySeenValues, value]
if (isJSONable(value)) {
const jsonValue = value.toJSON()
// check for infinite recursion
if (jsonValue !== value) {
return typeof jsonValue === 'string' ? jsonValue : formatValue(jsonValue, seenValues)
}
} else if (Array.isArray(value)) {
return formatArray(value, seenValues)
}
return formatObject(value, seenValues)
}
function isJSONable(value: any): value is { toJSON: () => unknown } {
return typeof value.toJSON === 'function'
}
function formatObject(object: object, seenValues: ReadonlyArray<unknown>): string {
const entries = Object.entries(object)
if (entries.length === 0) {
return '{}'
}
if (seenValues.length > MAX_RECURSIVE_DEPTH) {
return '[' + getObjectTag(object) + ']'
}
const properties = entries.map(([key, value]) => key + ': ' + formatValue(value, seenValues))
return '{ ' + properties.join(', ') + ' }'
}
function formatArray(array: ReadonlyArray<unknown>, seenValues: ReadonlyArray<unknown>): string {
if (array.length === 0) {
return '[]'
}
if (seenValues.length > MAX_RECURSIVE_DEPTH) {
return '[Array]'
}
const len = Math.min(MAX_ARRAY_LENGTH, array.length)
const remaining = array.length - len
const items = []
for (let i = 0; i < len; ++i) {
items.push(formatValue(array[i], seenValues))
}
if (remaining === 1) {
items.push('... 1 more item')
} else if (remaining > 1) {
items.push(`... ${remaining} more items`)
}
return '[' + items.join(', ') + ']'
}
function getObjectTag(object: object): string {
const tag = Object.prototype.toString
.call(object)
.replace(/^\[object /, '')
.replace(/]$/, '')
if (tag === 'Object' && typeof object.constructor === 'function') {
const name = object.constructor.name
if (typeof name === 'string' && name !== '') {
return name
}
}
return tag
}
export function invariant(condition: unknown, message?: string): asserts condition {
const booleanCondition = Boolean(condition)
// istanbul ignore else (See transformation done in './resources/inlineInvariant.js')
if (!booleanCondition) {
throw new Error(message != null ? message : 'Unexpected invariant triggered.')
}
}
/**
* Produces the value of a block string from its parsed raw value, similar to CoffeeScript's block string,
* Python's docstring trim or Ruby's strip_heredoc.
*
* This implements the GraphQL spec's BlockStringValue() static algorithm.
*
* @internal
*/
export function dedentBlockStringValue(rawString: string): string {
// Expand a block string's raw value into independent lines.
const lines = rawString.split(/\r\n|[\n\r]/g)
// Remove common indentation from all lines but first.
const commonIndent = getBlockStringIndentation(rawString)
if (commonIndent !== 0) {
for (let i = 1; i < lines.length; i++) {
lines[i] = lines[i].slice(commonIndent)
}
}
// Remove leading and trailing blank lines.
let startLine = 0
while (startLine < lines.length && isBlank(lines[startLine])) {
++startLine
}
let endLine = lines.length
while (endLine > startLine && isBlank(lines[endLine - 1])) {
--endLine
}
// Return a string of the lines joined with U+000A.
return lines.slice(startLine, endLine).join('\n')
}
function isBlank(str: string): boolean {
for (let i = 0; i < str.length; ++i) {
if (str[i] !== ' ' && str[i] !== '\t') {
return false
}
}
return true
}
/** @internal */
export function getBlockStringIndentation(value: string): number {
let isFirstLine = true
let isEmptyLine = true
let indent = 0
let commonIndent = null
for (let i = 0; i < value.length; ++i) {
switch (value.charCodeAt(i)) {
case 13: // \r
if (value.charCodeAt(i + 1) === 10) {
++i // skip \r\n as one symbol
}
// falls through
case 10: // \n
isFirstLine = false
isEmptyLine = true
indent = 0
break
case 9: // \t
case 32: // <space>
++indent
break
default:
if (isEmptyLine && !isFirstLine && (commonIndent === null || indent < commonIndent)) {
commonIndent = indent
}
isEmptyLine = false
}
}
return commonIndent ?? 0
}
/**
* Print a block string in the indented block form by adding a leading and trailing blank line. However, if a
* block string starts with whitespace and is a single-line, adding a leading blank line would strip that whitespace.
*
* @internal
*/
export function printBlockString(value: string, preferMultipleLines: boolean = false): string {
const isSingleLine = !value.includes('\n')
const hasLeadingSpace = value[0] === ' ' || value[0] === '\t'
const hasTrailingQuote = value[value.length - 1] === '"'
const hasTrailingSlash = value[value.length - 1] === '\\'
const printAsMultipleLines = !isSingleLine || hasTrailingQuote || hasTrailingSlash || preferMultipleLines
let result = ''
// Format a multi-line block quote to account for leading space.
if (printAsMultipleLines && !(isSingleLine && hasLeadingSpace)) {
result += '\n'
}
result += value
if (printAsMultipleLines) {
result += '\n'
}
return '"""' + result.replace(/"""/g, '\\"""') + '"""'
}

View File

@ -2,8 +2,9 @@ import * as blocks from './blocks'
import * as core from './core' import * as core from './core'
// All of the Public API definitions // All of the Public API definitions
export { makeSchema } from './builder' export { makeSchema } from './makeSchema'
export { arg, booleanArg, floatArg, idArg, intArg, stringArg } from './definitions/args' export { arg, booleanArg, floatArg, idArg, intArg, stringArg } from './definitions/args'
export { directive, addDirective } from './definitions/directive'
export { decorateType } from './definitions/decorateType' export { decorateType } from './definitions/decorateType'
export { enumType } from './definitions/enumType' export { enumType } from './definitions/enumType'
export { extendInputType } from './definitions/extendInputType' export { extendInputType } from './definitions/extendInputType'

171
src/makeSchema.ts Normal file
View File

@ -0,0 +1,171 @@
import { GraphQLNamedType, GraphQLSchema, isObjectType, specifiedDirectives } from 'graphql'
import { isNexusObjectTypeDef } from './definitions/wrapping'
import {
AdditionalGraphQLSchemaConfigOptions,
ConfiguredTypegen,
MakeSchemaOptions,
SchemaBuilder,
SchemaConfig,
} from './builder'
import type { NexusGraphQLSchema } from './definitions/_types'
import { TypegenMetadata } from './typegenMetadata'
import { resolveTypegenConfig } from './typegenUtils'
import { assertNoMissingTypes, objValues, runAbstractTypeRuntimeChecks } from './utils'
/**
* Defines the GraphQL schema, by combining the GraphQL types defined by the GraphQL Nexus layer or any
* manually defined GraphQLType objects.
*
* Requires at least one type be named "Query", which will be used as the root query type.
*/
export function makeSchema(config: SchemaConfig): NexusGraphQLSchema {
const { schema, missingTypes, finalConfig } = makeSchemaInternal(config)
const typegenConfig = resolveTypegenConfig(finalConfig)
const sdl = typegenConfig.outputs.schema
const typegen = typegenConfig.outputs.typegen
if (sdl || typegen) {
// Generating in the next tick allows us to use the schema
// in the optional thunk for the typegen config
const typegenPromise = new TypegenMetadata(typegenConfig).generateArtifacts(schema)
if (config.shouldExitAfterGenerateArtifacts) {
let typegenPath = '(not enabled)'
if (typegenConfig.outputs.typegen) {
typegenPath = typegenConfig.outputs.typegen.outputPath
if (typegenConfig.outputs.typegen.globalsPath) {
typegenPath += ` / ${typegenConfig.outputs.typegen.globalsPath}`
}
}
typegenPromise
.then(() => {
console.log(`Generated Artifacts:
TypeScript Types ==> ${typegenPath}
GraphQL Schema ==> ${typegenConfig.outputs.schema || '(not enabled)'}`)
process.exit(0)
})
.catch((e) => {
console.error(e)
process.exit(1)
})
} else {
typegenPromise.catch((e) => {
console.error(e)
})
}
}
assertNoMissingTypes(schema, missingTypes)
runAbstractTypeRuntimeChecks(schema, finalConfig.features)
return schema
}
/** Like makeSchema except that typegen is always run and waited upon. */
export async function generateSchema(config: SchemaConfig): Promise<NexusGraphQLSchema> {
const { schema, missingTypes, finalConfig } = makeSchemaInternal(config)
const typegenConfig = resolveTypegenConfig(finalConfig)
await new TypegenMetadata(typegenConfig).generateArtifacts(schema)
assertNoMissingTypes(schema, missingTypes)
runAbstractTypeRuntimeChecks(schema, finalConfig.features)
return schema
}
/**
* Mainly useful for testing, generates the schema and returns the artifacts that would have been otherwise
* written to the filesystem.
*/
generateSchema.withArtifacts = async (
config: SchemaConfig,
typegen: string | null | ConfiguredTypegen = null
): Promise<{
schema: NexusGraphQLSchema
schemaTypes: string
tsTypes: string
globalTypes: string | null
}> => {
const { schema, missingTypes, finalConfig } = makeSchemaInternal(config)
const typegenConfig = resolveTypegenConfig(finalConfig)
const { schemaTypes, tsTypes, globalTypes } = await new TypegenMetadata(
typegenConfig
).generateArtifactContents(schema, typegen)
assertNoMissingTypes(schema, missingTypes)
runAbstractTypeRuntimeChecks(schema, finalConfig.features)
return { schema, schemaTypes, tsTypes, globalTypes }
}
/** Builds the schema, we may return more than just the schema from this one day. */
export function makeSchemaInternal(config: SchemaConfig) {
const builder = new SchemaBuilder(config)
builder.addTypes(config.types)
builder.addTypes(config.directives)
if (config.schemaRoots) {
builder.addTypes(config.schemaRoots)
}
function getRootType(rootType: 'query' | 'mutation' | 'subscription', defaultType: string) {
const rootTypeVal = config.schemaRoots?.[rootType] ?? defaultType
let returnVal: null | GraphQLNamedType = null
if (typeof rootTypeVal === 'string') {
returnVal = typeMap[rootTypeVal]
} else if (rootTypeVal) {
if (isNexusObjectTypeDef(rootTypeVal)) {
returnVal = typeMap[rootTypeVal.name]
} else if (isObjectType(rootTypeVal)) {
returnVal = typeMap[rootTypeVal.name]
}
}
if (returnVal && !isObjectType(returnVal)) {
throw new Error(`Expected ${rootType} to be a objectType, saw ${returnVal.constructor.name}`)
}
return returnVal
}
const {
finalConfig,
typeMap,
missingTypes,
schemaExtension,
onAfterBuildFns,
customDirectives,
schemaDirectives,
} = builder.getFinalTypeMap()
const schema = new GraphQLSchema({
...extractGraphQLSchemaOptions(config),
query: getRootType('query', 'Query'),
mutation: getRootType('mutation', 'Mutation'),
subscription: getRootType('subscription', 'Subscription'),
types: objValues(typeMap),
extensions: {
...config.extensions,
nexus: schemaExtension,
},
directives: [...specifiedDirectives, ...Object.values(customDirectives)],
...schemaDirectives,
}) as NexusGraphQLSchema
onAfterBuildFns.forEach((fn) => fn(schema))
return { schema, missingTypes, finalConfig }
}
type OmittedVals = Partial<{ [K in keyof MakeSchemaOptions]: never }>
function extractGraphQLSchemaOptions(
config: SchemaConfig
): Partial<AdditionalGraphQLSchemaConfigOptions & OmittedVals> {
const {
formatTypegen,
nonNullDefaults,
mergeSchema,
outputs,
shouldExitAfterGenerateArtifacts,
shouldGenerateArtifacts,
schemaRoots,
sourceTypes,
prettierConfig,
plugins,
customPrintSchemaFn,
features,
contextType,
...graphqlConfigOpts
} = config
return graphqlConfigOpts
}

8
src/node.ts Normal file
View File

@ -0,0 +1,8 @@
export function nodeImports() {
const fs = require('fs') as typeof import('fs')
const path = require('path') as typeof import('path')
return {
fs,
path,
}
}

View File

@ -34,7 +34,7 @@ export type CreateFieldResolverInfo<FieldExt = any, TypeExt = any> = {
parentTypeConfig: ( parentTypeConfig: (
| Omit<NexusGraphQLObjectTypeConfig, 'fields' | 'extensions'> | Omit<NexusGraphQLObjectTypeConfig, 'fields' | 'extensions'>
| (Omit<NexusGraphQLInterfaceTypeConfig, 'fields' | 'extensions'> & { | (Omit<NexusGraphQLInterfaceTypeConfig, 'fields' | 'extensions'> & {
interfaces: GraphQLInterfaceType[] interfaces: readonly GraphQLInterfaceType[]
}) })
) & { ) & {
extensions?: Maybe<{ nexus?: { config: TypeExt } }> extensions?: Maybe<{ nexus?: { config: TypeExt } }>
@ -51,7 +51,7 @@ export interface PluginConfig {
/** A name for the plugin, useful for errors, etc. */ /** A name for the plugin, useful for errors, etc. */
name: string name: string
/** A description for the plugin */ /** A description for the plugin */
description?: string description?: Maybe<string>
/** Any type definitions we want to add to output field definitions */ /** Any type definitions we want to add to output field definitions */
fieldDefTypes?: StringLike | StringLike[] fieldDefTypes?: StringLike | StringLike[]
/** Any type definitions we want to add to input field definitions */ /** Any type definitions we want to add to input field definitions */

View File

@ -1,6 +1,10 @@
import { defaultFieldResolver, GraphQLFieldResolver, GraphQLResolveInfo } from 'graphql' import { defaultFieldResolver, GraphQLFieldResolver, GraphQLResolveInfo } from 'graphql'
import { ArgsRecord, intArg, stringArg } from '../definitions/args' import { ArgsRecord, intArg, stringArg } from '../definitions/args'
import type { CommonFieldConfig, FieldOutConfig } from '../definitions/definitionBlocks' import type {
CommonFieldConfig,
FieldOutConfig,
FieldOutConfigWithName,
} from '../definitions/definitionBlocks'
import { NexusNonNullDef, nonNull } from '../definitions/nonNull' import { NexusNonNullDef, nonNull } from '../definitions/nonNull'
import { NexusNullDef, nullable } from '../definitions/nullable' import { NexusNullDef, nullable } from '../definitions/nullable'
import { ObjectDefinitionBlock, objectType } from '../definitions/objectType' import { ObjectDefinitionBlock, objectType } from '../definitions/objectType'
@ -113,8 +117,8 @@ export interface ConnectionPluginConfig {
* Any additional fields to make available to the connection type, beyond edges / pageInfo / nodes. * Any additional fields to make available to the connection type, beyond edges / pageInfo / nodes.
* *
* Any fields defined extended on the Connection type will automatically receive the args from the * Any fields defined extended on the Connection type will automatically receive the args from the
* connection. If the field also defines args, they will be merged with the args of the connection, with * connection. If the field also defines args, they will be merged with the args of the connection, with the
* the extension's field args taking precedence if there is a conflict. * extension's field args taking precedence if there is a conflict.
*/ */
extendConnection?: Record< extendConnection?: Record<
string, string,
@ -156,10 +160,9 @@ export interface ConnectionPluginConfig {
} }
// Extract the node value from the connection for a given field. // Extract the node value from the connection for a given field.
export type NodeValue<TypeName extends string = any, FieldName extends string = any> = ResultValue< export type NodeValue<TypeName extends string = any, FieldName extends string = any> = SourceValue<
EdgeTypeLookup<TypeName, FieldName>, EdgeTypeLookup<TypeName, FieldName>
'node' >['node']
>
export type ConnectionFieldConfig<TypeName extends string = any, FieldName extends string = any> = { export type ConnectionFieldConfig<TypeName extends string = any, FieldName extends string = any> = {
type: GetGen<'allOutputTypes', string> | AllNexusNamedOutputTypeDefs type: GetGen<'allOutputTypes', string> | AllNexusNamedOutputTypeDefs
@ -300,8 +303,8 @@ export type ConnectionFieldConfig<TypeName extends string = any, FieldName exten
| { | {
/** /**
* Implement the full resolve, including `edges` and `pageInfo`. Useful in more complex pagination * Implement the full resolve, including `edges` and `pageInfo`. Useful in more complex pagination
* cases, or if you want to use utilities from other libraries like GraphQL Relay JS, and only use * cases, or if you want to use utilities from other libraries like GraphQL Relay JS, and only use Nexus
* Nexus for the construction and type-safety: * for the construction and type-safety:
* *
* Https://github.com/graphql/graphql-relay-js * Https://github.com/graphql/graphql-relay-js
*/ */
@ -491,11 +494,11 @@ export const connectionPlugin = (connectionPluginConfig?: ConnectionPluginConfig
definition(t2) { definition(t2) {
t2.list.field('edges', { t2.list.field('edges', {
type: edgeName as any, type: edgeName as any,
description: `https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types`, description: `https://relay.dev/graphql/connections.htm#sec-Edge-Types`,
}) })
t2.nonNull.field('pageInfo', { t2.nonNull.field('pageInfo', {
type: 'PageInfo' as any, type: 'PageInfo' as any,
description: `https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo`, description: `https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo`,
}) })
if (includeNodesField) { if (includeNodesField) {
t2.list.field('nodes', { t2.list.field('nodes', {
@ -527,11 +530,11 @@ export const connectionPlugin = (connectionPluginConfig?: ConnectionPluginConfig
definition(t2) { definition(t2) {
t2.field('cursor', { t2.field('cursor', {
type: cursorType ?? nonNull('String'), type: cursorType ?? nonNull('String'),
description: 'https://facebook.github.io/relay/graphql/connections.htm#sec-Cursor', description: 'https://relay.dev/graphql/connections.htm#sec-Cursor',
}) })
t2.field('node', { t2.field('node', {
type: targetType, type: targetType,
description: 'https://facebook.github.io/relay/graphql/connections.htm#sec-Node', description: 'https://relay.dev/graphql/connections.htm#sec-Node',
}) })
if (pluginExtendEdge) { if (pluginExtendEdge) {
eachObj(pluginExtendEdge, (val, key) => { eachObj(pluginExtendEdge, (val, key) => {
@ -555,7 +558,7 @@ export const connectionPlugin = (connectionPluginConfig?: ConnectionPluginConfig
objectType({ objectType({
name: 'PageInfo', name: 'PageInfo',
description: description:
'PageInfo cursor, as defined in https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo', 'PageInfo cursor, as defined in https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo',
definition(t2) { definition(t2) {
t2.nonNull.field('hasNextPage', { t2.nonNull.field('hasNextPage', {
type: 'Boolean', type: 'Boolean',
@ -706,10 +709,8 @@ export function makeResolveFn(
return (root, args: PaginationArgs, ctx, info) => { return (root, args: PaginationArgs, ctx, info) => {
const { nodes: nodesResolve } = fieldConfig const { nodes: nodesResolve } = fieldConfig
const { decodeCursor = base64Decode, encodeCursor = base64Encode } = pluginConfig const { decodeCursor = base64Decode, encodeCursor = base64Encode } = pluginConfig
const { const { pageInfoFromNodes = defaultPageInfoFromNodes, cursorFromNode = defaultCursorFromNode } =
pageInfoFromNodes = defaultPageInfoFromNodes, mergedConfig
cursorFromNode = defaultCursorFromNode,
} = mergedConfig
if (!nodesResolve) { if (!nodesResolve) {
return null return null
} }
@ -723,10 +724,6 @@ export function makeResolveFn(
formattedArgs.after = decodeCursor(args.after).replace(CURSOR_PREFIX, '') formattedArgs.after = decodeCursor(args.after).replace(CURSOR_PREFIX, '')
} }
if (args.last && !args.before && cursorFromNode === defaultCursorFromNode) {
throw new Error(`Cannot paginate backward without a "before" cursor by default.`)
}
// Local variable to cache the execution of fetching the nodes, // Local variable to cache the execution of fetching the nodes,
// which is needed for all fields. // which is needed for all fields.
let cachedNodes: MaybePromiseLike<Array<any>> let cachedNodes: MaybePromiseLike<Array<any>>
@ -803,10 +800,9 @@ export function makeResolveFn(
}) })
if (hasPromise) { if (hasPromise) {
return Promise.all([ return Promise.all([Promise.all(resolvedEdgeList), Promise.all(resolvedNodeList)]).then(
Promise.all(resolvedEdgeList), ([edges, nodes]) => ({ edges, nodes })
Promise.all(resolvedNodeList), )
]).then(([edges, nodes]) => ({ edges, nodes }))
} }
return { return {
@ -927,9 +923,16 @@ function mergeArgs(obj: object, fieldArgs: ArgsValue<any, any>): ArgsValue<any,
*/ */
function provideArgs(block: ObjectDefinitionBlock<any>, fn: () => void) { function provideArgs(block: ObjectDefinitionBlock<any>, fn: () => void) {
const fieldDef = block.field const fieldDef = block.field
block.field = function (fieldName, config) { block.field = function (
...args:
| [name: string, config: FieldOutConfig<any, string>]
| [config: FieldOutConfigWithName<any, string>]
) {
let config = args.length === 2 ? { name: args[0], ...args[1] } : args[0]
const { resolve = defaultFieldResolver } = config const { resolve = defaultFieldResolver } = config
fieldDef.call(this, fieldName, {
fieldDef.call(this, {
...config, ...config,
resolve(root, args, ctx, info) { resolve(root, args, ctx, info) {
return resolve(root, mergeArgs(root, args), ctx, info) return resolve(root, mergeArgs(root, args), ctx, info)
@ -942,9 +945,16 @@ function provideArgs(block: ObjectDefinitionBlock<any>, fn: () => void) {
function provideSourceAndArgs(block: ObjectDefinitionBlock<any>, fn: () => void) { function provideSourceAndArgs(block: ObjectDefinitionBlock<any>, fn: () => void) {
const fieldDef = block.field const fieldDef = block.field
block.field = function (fieldName, config) { block.field = function (
...args:
| [name: string, config: FieldOutConfig<any, string>]
| [config: FieldOutConfigWithName<any, string>]
) {
let config = args.length === 2 ? { name: args[0], ...args[1] } : args[0]
const { resolve = defaultFieldResolver } = config const { resolve = defaultFieldResolver } = config
fieldDef.call(this, fieldName, {
fieldDef.call(this, {
...config, ...config,
resolve(root, args, ctx, info) { resolve(root, args, ctx, info) {
return resolve(root.__connectionSource, mergeArgs(root, args), ctx, info) return resolve(root.__connectionSource, mergeArgs(root, args), ctx, info)
@ -964,8 +974,10 @@ function iterateNodes(nodes: any[], args: PaginationArgs, cb: (node: any, i: num
} }
} else if (typeof args.last === 'number') { } else if (typeof args.last === 'number') {
const len = Math.min(args.last, nodes.length) const len = Math.min(args.last, nodes.length)
const startOffset = Math.max(nodes.length - args.last, 0)
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
cb(nodes[i], i) cb(nodes[i + startOffset], i)
} }
} else { } else {
// Only happens if we have a custom validateArgs that ignores first/last // Only happens if we have a custom validateArgs that ignores first/last
@ -1049,14 +1061,9 @@ function defaultCursorFromNode(
// If we're paginating backward, assume we're working backward from the assumed length // If we're paginating backward, assume we're working backward from the assumed length
// e.g. [0...20] (last: 5, before: "cursor:20") -> [cursor:15, cursor:16, cursor:17, cursor:18, cursor:19] // e.g. [0...20] (last: 5, before: "cursor:20") -> [cursor:15, cursor:16, cursor:17, cursor:18, cursor:19]
if (typeof args.last === 'number') { if (typeof args.last === 'number') {
if (args.before) { const offset = args.before ? parseInt(args.before, 10) : nodes.length
const offset = parseInt(args.before, 10) const len = Math.min(nodes.length, args.last)
const len = Math.min(nodes.length, args.last) cursorIndex = offset - len + index
cursorIndex = offset - len + index
} else {
/* istanbul ignore next */
throw new Error('Unreachable')
}
} }
return `${CURSOR_PREFIX}${cursorIndex}` return `${CURSOR_PREFIX}${cursorIndex}`
} }

View File

@ -50,16 +50,15 @@ export const defaultFormatError = ({ error }: FieldAuthorizePluginErrorConfig):
export const fieldAuthorizePlugin = (authConfig: FieldAuthorizePluginConfig = {}) => { export const fieldAuthorizePlugin = (authConfig: FieldAuthorizePluginConfig = {}) => {
const { formatError = defaultFormatError } = authConfig const { formatError = defaultFormatError } = authConfig
const ensureError = (root: any, args: any, ctx: GetGen<'context'>, info: GraphQLResolveInfo) => ( const ensureError =
error: Error (root: any, args: any, ctx: GetGen<'context'>, info: GraphQLResolveInfo) => (error: Error) => {
) => { const finalErr = formatError({ error, root, args, ctx, info })
const finalErr = formatError({ error, root, args, ctx, info }) if (finalErr instanceof Error) {
if (finalErr instanceof Error) { throw finalErr
throw finalErr }
console.error(`Non-Error value ${finalErr} returned from custom formatError in authorize plugin`)
throw new Error('Not authorized')
} }
console.error(`Non-Error value ${finalErr} returned from custom formatError in authorize plugin`)
throw new Error('Not authorized')
}
let hasWarned = false let hasWarned = false
return plugin({ return plugin({
name: 'NexusAuthorize', name: 'NexusAuthorize',

View File

@ -31,11 +31,9 @@ export interface NullabilityPluginOnGuardedConfig {
type: GraphQLNullableType type: GraphQLNullableType
} }
export type NullFallbackValues = Partial< export type NullFallbackValues = Partial<{
{ [K in AllOutputTypes]: (obj: NullabilityPluginFallbackFn) => GetGen2<'rootTypes', K>
[K in AllOutputTypes]: (obj: NullabilityPluginFallbackFn) => GetGen2<'rootTypes', K> }>
}
>
export type NullabilityGuardConfig = { export type NullabilityGuardConfig = {
/** Whether we should guard against non-null values. Defaults to "true" if NODE_ENV === "production", false otherwise. */ /** Whether we should guard against non-null values. Defaults to "true" if NODE_ENV === "production", false otherwise. */

View File

@ -0,0 +1,323 @@
/** Conveniently represents flow's "Maybe" type https://flow.org/en/docs/types/maybe/ */
type Maybe<T> = null | undefined | T
import { DirectiveNode, Kind, print } from 'graphql'
import { printBlockString, invariant, inspect } from './graphqlInternal'
import type { GraphQLSchema } from 'graphql'
import type { GraphQLDirective } from 'graphql'
import type {
GraphQLNamedType,
GraphQLArgument,
GraphQLInputField,
GraphQLScalarType,
GraphQLEnumType,
GraphQLObjectType,
GraphQLInterfaceType,
GraphQLUnionType,
GraphQLInputObjectType,
} from 'graphql'
import { isIntrospectionType } from 'graphql'
import { isSpecifiedScalarType } from 'graphql'
import { DEFAULT_DEPRECATION_REASON, isSpecifiedDirective } from 'graphql'
import {
isScalarType,
isObjectType,
isInterfaceType,
isUnionType,
isEnumType,
isInputObjectType,
} from 'graphql'
import { astFromValue } from 'graphql'
export function printSchemaWithDirectives(schema: GraphQLSchema): string {
return printFilteredSchemaWithDirectives(schema, (n) => !isSpecifiedDirective(n), isDefinedType)
}
function isDefinedType(type: GraphQLNamedType): boolean {
return !isSpecifiedScalarType(type) && !isIntrospectionType(type)
}
function printFilteredSchemaWithDirectives(
schema: GraphQLSchema,
directiveFilter: (type: GraphQLDirective) => boolean,
typeFilter: (type: GraphQLNamedType) => boolean
): string {
const directives = schema.getDirectives().filter(directiveFilter)
const types = Object.values(schema.getTypeMap()).filter(typeFilter)
return [
printSchemaDefinition(schema),
...directives.map((directive) => printDirective(directive)),
...types.map((type) => printType(type)),
]
.filter(Boolean)
.join('\n\n')
}
function printSchemaDefinition(
// & description for GraphQL 14 types
schema: GraphQLSchema & { description?: Maybe<string> }
): Maybe<string> {
if (schema.description == null && isSchemaOfCommonNames(schema)) {
return
}
const operationTypes = []
const queryType = schema.getQueryType()
if (queryType) {
operationTypes.push(` query: ${queryType.name}`)
}
const mutationType = schema.getMutationType()
if (mutationType) {
operationTypes.push(` mutation: ${mutationType.name}`)
}
const subscriptionType = schema.getSubscriptionType()
if (subscriptionType) {
operationTypes.push(` subscription: ${subscriptionType.name}`)
}
return printDescription(schema) + `schema {\n${operationTypes.join('\n')}\n}`
}
/**
* GraphQL schema define root types for each type of operation. These types are the same as any other type and
* can be named in any manner, however there is a common naming convention:
*
* ```graphql
* query: Query
* mutation: Mutation
* subscription: Subscription } ```
*
* When using this naming convention, the schema description can be omitted.
* ```
*/
function isSchemaOfCommonNames(schema: GraphQLSchema): boolean {
const queryType = schema.getQueryType()
if (queryType && queryType.name !== 'Query') {
return false
}
const mutationType = schema.getMutationType()
if (mutationType && mutationType.name !== 'Mutation') {
return false
}
const subscriptionType = schema.getSubscriptionType()
if (subscriptionType && subscriptionType.name !== 'Subscription') {
return false
}
return true
}
export function printType(type: GraphQLNamedType): string {
if (isScalarType(type)) {
return printScalar(type)
}
if (isObjectType(type)) {
return printObject(type)
}
if (isInterfaceType(type)) {
return printInterface(type)
}
if (isUnionType(type)) {
return printUnion(type)
}
if (isEnumType(type)) {
return printEnum(type)
}
// istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
if (isInputObjectType(type)) {
return printInputObject(type)
}
// istanbul ignore next (Not reachable. All possible types have been considered)
invariant(false, 'Unexpected type: ' + inspect(type))
}
function printScalar(type: GraphQLScalarType): string {
return printDescription(type) + `scalar ${type.name}` + printSpecifiedByURL(type) + printDirectives(type)
}
function printImplementedInterfaces(
// & getInterfaces added for GraphQL 14 types
type: (GraphQLObjectType | GraphQLInterfaceType) & { getInterfaces?: () => readonly GraphQLInterfaceType[] }
): string {
const interfaces = type.getInterfaces?.()
return interfaces?.length ? ' implements ' + interfaces.map((i) => i.name).join(' & ') : ''
}
function printObject(type: GraphQLObjectType): string {
return (
printDescription(type) +
`type ${type.name}` +
printImplementedInterfaces(type) +
printDirectives(type) +
printFields(type)
)
}
function printInterface(type: GraphQLInterfaceType): string {
return (
printDescription(type) +
`interface ${type.name}` +
printImplementedInterfaces(type) +
printDirectives(type) +
printFields(type)
)
}
function printUnion(type: GraphQLUnionType): string {
const types = type.getTypes()
const possibleTypes = types.length ? ' = ' + types.join(' | ') : ''
return printDescription(type) + 'union ' + type.name + printDirectives(type) + possibleTypes
}
function printEnum(type: GraphQLEnumType): string {
const values = type
.getValues()
.map(
(value, i) =>
printDescription(value, ' ', !i) +
' ' +
value.name +
printDeprecated(value.deprecationReason) +
printDirectives(value)
)
return printDescription(type) + `enum ${type.name}` + printDirectives(type) + printBlock(values)
}
function printInputObject(type: GraphQLInputObjectType): string {
const fields = Object.values(type.getFields()).map(
(f, i) => printDescription(f, ' ', !i) + ' ' + printInputValue(f)
)
return printDescription(type) + `input ${type.name}` + printDirectives(type) + printBlock(fields)
}
function printFields(type: GraphQLObjectType | GraphQLInterfaceType): string {
const fields = Object.values(type.getFields()).map(
(f, i) =>
printDescription(f, ' ', !i) +
' ' +
f.name +
printArgs(f.args, ' ') +
': ' +
String(f.type) +
printDeprecated(f.deprecationReason) +
printDirectives(f)
)
return printBlock(fields)
}
function printBlock(items: ReadonlyArray<string>): string {
return items.length !== 0 ? ' {\n' + items.join('\n') + '\n}' : ''
}
function printArgs(args: ReadonlyArray<GraphQLArgument>, indentation: string = ''): string {
if (args.length === 0) {
return ''
}
// If every arg does not have a description, print them on one line.
if (args.every((arg) => !arg.description)) {
return '(' + args.map(printInputValue).join(', ') + ')'
}
return (
'(\n' +
args
.map(
(arg, i) => printDescription(arg, ' ' + indentation, !i) + ' ' + indentation + printInputValue(arg)
)
.join('\n') +
'\n' +
indentation +
')'
)
}
function printInputValue(arg: GraphQLInputField & { deprecationReason?: Maybe<string> }): string {
const defaultAST = astFromValue(arg.defaultValue, arg.type)
let argDecl = arg.name + ': ' + String(arg.type)
if (defaultAST) {
argDecl += ` = ${print(defaultAST)}`
}
return argDecl + printDeprecated(arg.deprecationReason) + printDirectives(arg)
}
function printDirective(directive: GraphQLDirective): string {
return (
printDescription(directive) +
'directive @' +
directive.name +
printArgs(directive.args) +
(directive.isRepeatable ? ' repeatable' : '') +
' on ' +
directive.locations.join(' | ')
)
}
function printDeprecated(reason: Maybe<string>): string {
if (reason == null) {
return ''
}
if (reason !== DEFAULT_DEPRECATION_REASON) {
const astValue = print({ kind: Kind.STRING, value: reason })
return ` @deprecated(reason: ${astValue})`
}
return ' @deprecated'
}
function printSpecifiedByURL(
// https://github.com/graphql/graphql-js/issues/3156
scalar: GraphQLScalarType & { specifiedByURL?: Maybe<string>; specifiedByUrl?: Maybe<string> }
): string {
const specifiedByURL = scalar.specifiedByURL ?? scalar.specifiedByUrl
if (specifiedByURL == null) {
return ''
}
const astValue = print({ kind: Kind.STRING, value: specifiedByURL })
return ` @specifiedBy(url: ${astValue})`
}
function printDescription(
def: { readonly description?: Maybe<string> },
indentation: string = '',
firstInBlock: boolean = true
): string {
const { description } = def
if (description == null) {
return ''
}
const preferMultipleLines = description.length > 70
const blockString = printBlockString(description, preferMultipleLines)
const prefix = indentation && !firstInBlock ? '\n' + indentation : indentation
return prefix + blockString.replace(/\n/g, '\n' + indentation) + '\n'
}
function printDirectives(value: { astNode?: Maybe<{ readonly directives?: ReadonlyArray<DirectiveNode> }> }) {
const directives = value.astNode?.directives
if (!directives?.length) {
return ''
}
return ' ' + directives.map(printDirectiveUse).join(' ')
}
function printDirectiveUse(node: DirectiveNode) {
return `@${node.name.value}` + printDirectiveArgs(node)
}
function printDirectiveArgs(node: DirectiveNode) {
if (!node.arguments?.length) {
return ''
}
return `(${node.arguments.map((a) => `${a.name.value}: ${print(a.value)}`).join(', ')})`
}

232
src/rebuildType.ts Normal file
View File

@ -0,0 +1,232 @@
import {
GraphQLEnumType,
GraphQLFieldConfigArgumentMap,
GraphQLFieldConfigMap,
GraphQLInputFieldConfigMap,
GraphQLInputObjectType,
GraphQLInterfaceType,
GraphQLNamedType,
GraphQLObjectType,
GraphQLScalarType,
GraphQLUnionType,
isEnumType,
isInputObjectType,
isInterfaceType,
isObjectType,
isScalarType,
isUnionType,
defaultTypeResolver,
} from 'graphql'
import type { MergeSchemaConfig } from './builder'
import { arg, ArgsRecord } from './definitions/args'
import type { InputDefinitionBlock } from './definitions/definitionBlocks'
import { enumType } from './definitions/enumType'
import { inputObjectType } from './definitions/inputObjectType'
import { InterfaceDefinitionBlock, interfaceType } from './definitions/interfaceType'
import { ObjectDefinitionBlock, objectType } from './definitions/objectType'
import { scalarType } from './definitions/scalarType'
import { unionType } from './definitions/unionType'
import { AllNexusArgsDefs, applyNexusWrapping, unwrapGraphQLDef } from './definitions/wrapping'
import type { Maybe, SourceTypingDef } from './definitions/_types'
import type { GetGen } from './typegenTypeHelpers'
import { graphql15InterfaceConfig, Unreachable } from './utils'
export interface RebuildConfig extends Omit<MergeSchemaConfig, 'schema'> {
captureLeafType?: (type: GraphQLNamedType) => void
asNexusMethod?: string
sourceType?: SourceTypingDef
}
export function rebuildNamedType(type: GraphQLNamedType, config: RebuildConfig) {
if (isObjectType(type)) {
return rebuildObjectType(type, config)
} else if (isInputObjectType(type)) {
return rebuildInputObjectType(type, config)
} else if (isInterfaceType(type)) {
return rebuildInterfaceType(type, config)
} else if (isUnionType(type)) {
return rebuildUnionType(type, config)
} else if (isScalarType(type)) {
return rebuildScalarType(type, config)
} else if (isEnumType(type)) {
return rebuildEnumType(type, config)
}
throw new Unreachable(type)
}
export function rebuildInputObjectType(type: GraphQLInputObjectType, config: RebuildConfig) {
const { name, fields, description, extensions } = type.toConfig()
return inputObjectType({
name,
description,
definition: (t) => {
rebuildInputDefinition(name, t, fields, config)
},
extensions,
nonNullDefaults: {
output: false,
input: false,
},
})
}
export function rebuildUnionType(type: GraphQLUnionType, config: RebuildConfig) {
const { name, types, description, resolveType, extensions } = type.toConfig()
return unionType({
name,
description,
// @ts-ignore - todo, see why this is the case
resolveType: resolveType ?? defaultTypeResolver,
definition(t) {
t.members(
...types.map((o) => {
config.captureLeafType?.(o)
return o.name as GetGen<'objectNames'>
})
)
},
extensions,
})
}
export function rebuildScalarType(type: GraphQLScalarType, config: RebuildConfig) {
return scalarType({
...type.toConfig(),
sourceType: config.sourceType,
asNexusMethod: config.asNexusMethod,
})
}
export function rebuildEnumType(type: GraphQLEnumType, { sourceType, asNexusMethod }: RebuildConfig) {
const { name, values, ...config } = type.toConfig()
return enumType({
name,
...config,
members: Object.entries(values).map(([valueName, config]) => {
return {
name: valueName,
deprecation: config.deprecationReason,
...config,
}
}),
sourceType,
asNexusMethod,
})
}
export function rebuildInterfaceType(type: GraphQLInterfaceType, config: RebuildConfig) {
const { name, fields, description, interfaces, extensions, resolveType } = graphql15InterfaceConfig(
type.toConfig()
)
return interfaceType({
name,
description,
// @ts-ignore - todo, see why this is the case
resolveType: resolveType ?? defaultTypeResolver,
definition: (t) => {
rebuildOutputDefinition(name, t, fields, interfaces, config)
},
nonNullDefaults: {
output: false,
input: false,
},
extensions,
sourceType: config.sourceType,
asNexusMethod: config.asNexusMethod,
})
}
export function rebuildObjectType(type: GraphQLObjectType, config: RebuildConfig) {
const { name, fields, interfaces, description, extensions } = type.toConfig()
return objectType({
name,
description,
definition: (t) => {
rebuildOutputDefinition(name, t, fields, interfaces, config)
},
nonNullDefaults: {
output: false,
input: false,
},
extensions,
sourceType: config.sourceType,
asNexusMethod: config.asNexusMethod,
})
}
export function rebuildOutputDefinition(
typeName: string,
t: ObjectDefinitionBlock<string> | InterfaceDefinitionBlock<string>,
fields: GraphQLFieldConfigMap<any, any>,
interfaces: ReadonlyArray<GraphQLInterfaceType>,
config: RebuildConfig
) {
t.implements(
...interfaces.map((i) => {
config.captureLeafType?.(i)
return i.name as GetGen<'interfaceNames'>
})
)
for (const [fieldName, fieldConfig] of Object.entries(fields)) {
if (config.skipFields?.[typeName] && config.skipFields?.[typeName].includes(fieldName)) {
continue
}
const { namedType, wrapping } = unwrapGraphQLDef(fieldConfig.type)
config.captureLeafType?.(namedType)
t.field(fieldName, {
type: applyNexusWrapping(namedType.name, wrapping),
description: fieldConfig.description,
deprecation: fieldConfig.deprecationReason,
extensions: fieldConfig.extensions,
args: rebuildArgs(typeName, fieldName, fieldConfig.args ?? {}, config),
resolve: fieldConfig.resolve,
})
}
}
export function rebuildInputDefinition(
typeName: string,
t: InputDefinitionBlock<string>,
fields: GraphQLInputFieldConfigMap,
config: RebuildConfig
) {
for (const [fieldName, fieldConfig] of Object.entries(fields)) {
if (config.skipFields?.[typeName] && config.skipFields?.[typeName].includes(fieldName)) {
continue
}
const { namedType, wrapping } = unwrapGraphQLDef(fieldConfig.type)
config.captureLeafType?.(namedType)
t.field(fieldName, {
type: applyNexusWrapping(namedType.name, wrapping),
description: fieldConfig.description,
default: fieldConfig.defaultValue,
extensions: fieldConfig.extensions,
})
}
}
export function rebuildArgs(
typeName: string,
fieldName: string,
argMap: Maybe<GraphQLFieldConfigArgumentMap>,
config: RebuildConfig
): Maybe<ArgsRecord> {
if (!argMap) {
return null
}
const rebuiltArgs: Record<string, AllNexusArgsDefs> = {}
for (const [argName, argConfig] of Object.entries(argMap)) {
if (config.skipArgs?.[typeName]?.[fieldName]) {
continue
}
const { namedType, wrapping } = unwrapGraphQLDef(argConfig.type)
config.captureLeafType?.(namedType)
rebuiltArgs[argName] = arg({
type: applyNexusWrapping(namedType.name, wrapping),
default: argConfig.defaultValue,
description: argConfig.description,
extensions: argConfig.extensions,
})
}
return rebuiltArgs
}

View File

@ -38,7 +38,7 @@ export type RequiredDeeply<T> = DoRequireDeeply<Exclude<T, undefined>>
* Represents a POJO. Prevents from allowing arrays and functions. * Represents a POJO. Prevents from allowing arrays and functions.
* *
* @remarks * @remarks
* TypeScript interfaces will not be considered sub-types. * TypeScript interfaces will not be considered sub-types.
*/ */
export type PlainObject = { export type PlainObject = {
[x: string]: Primitive | object [x: string]: Primitive | object
@ -56,3 +56,5 @@ type DoRequireDeeply<T> = {
export type MaybePromiseLike<T> = T | PromiseLike<T> export type MaybePromiseLike<T> = T | PromiseLike<T>
export type UnwrapPromise<R> = R extends PromiseLike<infer U> ? U : R export type UnwrapPromise<R> = R extends PromiseLike<infer U> ? U : R
export type MaybeReadonlyArray<T> = Array<T> | ReadonlyArray<T>

View File

@ -1,4 +1,5 @@
import type { GraphQLResolveInfo } from 'graphql' import type { GraphQLResolveInfo } from 'graphql'
import type { Maybe } from './definitions/_types'
import type { import type {
AbstractTypeResolver, AbstractTypeResolver,
GetGen, GetGen,
@ -42,9 +43,8 @@ export type AbstractTypeNames<TypeName extends string> = ConditionalKeys<
> >
/** Returns whether all the abstract type names where TypeName is used have implemented `resolveType` */ /** Returns whether all the abstract type names where TypeName is used have implemented `resolveType` */
export type IsStrategyResolveTypeImplementedInAllAbstractTypes< export type IsStrategyResolveTypeImplementedInAllAbstractTypes<TypeName extends string> =
TypeName extends string AbstractTypeNames<TypeName> extends GetGen<'abstractsUsingStrategyResolveType'> ? true : false
> = AbstractTypeNames<TypeName> extends GetGen<'abstractsUsingStrategyResolveType'> ? true : false
/** Returns whether all the members of an abstract type have implemented `isTypeOf` */ /** Returns whether all the members of an abstract type have implemented `isTypeOf` */
export type IsStrategyIsTypeOfImplementedInAllMembers<AbstractTypeName extends string> = GetGen2< export type IsStrategyIsTypeOfImplementedInAllMembers<AbstractTypeName extends string> = GetGen2<
@ -122,16 +122,17 @@ export type MaybeTypeDefConfigFieldIsTypeOf<TypeName extends string> =
* }) * })
* *
* @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL objects that * @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL objects that
* are members of the abstract types that this type is a member of. For example for some type A in two * are members of the abstract types that this type is a member of. For example for some type A
* union types whose members are A,B.C and A,D,E respectively then isTypeOf method for A would receive * in two union types whose members are A,B.C and A,D,E respectively then isTypeOf method for A
* source data from A, B, C, D, & E at runtime. * would receive source data from A, B, C, D, & E at runtime.
* @param context The context data for this request. * @param context The context data for this request.
* *
* The context data is typically a singleton scoped to the lifecycle of the request. This means created at * The context data is typically a singleton scoped to the lifecycle of the request. This means
* the beginning of a request and then passed to all the resolvers that execute while resolving the request. * created at the beginning of a request and then passed to all the resolvers that execute while
* It is often used to store information like the current user making the request. Nexus is not responsible * resolving the request. It is often used to store information like the current user making the
* for this however. That is typically something you'll do with e.g. [Mercurius](https://mercurius.dev) or * request. Nexus is not responsible for this however. That is typically something you'll do with
* [Apollo Server](https://apollographql.com/docs/apollo-server/api/apollo-server). * e.g. [Mercurius](https://mercurius.dev) or [Apollo
* Server](https://apollographql.com/docs/apollo-server/api/apollo-server).
* *
* Note that the type here will be whatever you have specified for "contextType" in your makeSchema * Note that the type here will be whatever you have specified for "contextType" in your makeSchema
* configuration. * configuration.
@ -200,16 +201,17 @@ export type MaybeTypeDefConfigFieldIsTypeOf<TypeName extends string> =
* }) * })
* *
* @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL objects that * @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL objects that
* are members of the abstract types that this type is a member of. For example for some type A in two * are members of the abstract types that this type is a member of. For example for some type A
* union types whose members are A,B.C and A,D,E respectively then isTypeOf method for A would receive * in two union types whose members are A,B.C and A,D,E respectively then isTypeOf method for A
* source data from A, B, C, D, & E at runtime. * would receive source data from A, B, C, D, & E at runtime.
* @param context The context data for this request. * @param context The context data for this request.
* *
* The context data is typically a singleton scoped to the lifecycle of the request. This means created at * The context data is typically a singleton scoped to the lifecycle of the request. This means
* the beginning of a request and then passed to all the resolvers that execute while resolving the request. * created at the beginning of a request and then passed to all the resolvers that execute while
* It is often used to store information like the current user making the request. Nexus is not responsible * resolving the request. It is often used to store information like the current user making the
* for this however. That is typically something you'll do with e.g. [Mercurius](https://mercurius.dev) or * request. Nexus is not responsible for this however. That is typically something you'll do with
* [Apollo Server](https://apollographql.com/docs/apollo-server/api/apollo-server). * e.g. [Mercurius](https://mercurius.dev) or [Apollo
* Server](https://apollographql.com/docs/apollo-server/api/apollo-server).
* *
* Note that the type here will be whatever you have specified for "contextType" in your makeSchema * Note that the type here will be whatever you have specified for "contextType" in your makeSchema
* configuration. * configuration.
@ -277,16 +279,17 @@ export type MaybeTypeDefConfigFieldIsTypeOf<TypeName extends string> =
* }) * })
* *
* @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL objects that * @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL objects that
* are members of the abstract types that this type is a member of. For example for some type A in two * are members of the abstract types that this type is a member of. For example for some type A
* union types whose members are A,B.C and A,D,E respectively then isTypeOf method for A would receive * in two union types whose members are A,B.C and A,D,E respectively then isTypeOf method for A
* source data from A, B, C, D, & E at runtime. * would receive source data from A, B, C, D, & E at runtime.
* @param context The context data for this request. * @param context The context data for this request.
* *
* The context data is typically a singleton scoped to the lifecycle of the request. This means created at * The context data is typically a singleton scoped to the lifecycle of the request. This means
* the beginning of a request and then passed to all the resolvers that execute while resolving the request. * created at the beginning of a request and then passed to all the resolvers that execute while
* It is often used to store information like the current user making the request. Nexus is not responsible * resolving the request. It is often used to store information like the current user making the
* for this however. That is typically something you'll do with e.g. [Mercurius](https://mercurius.dev) or * request. Nexus is not responsible for this however. That is typically something you'll do with
* [Apollo Server](https://apollographql.com/docs/apollo-server/api/apollo-server). * e.g. [Mercurius](https://mercurius.dev) or [Apollo
* Server](https://apollographql.com/docs/apollo-server/api/apollo-server).
* *
* Note that the type here will be whatever you have specified for "contextType" in your makeSchema * Note that the type here will be whatever you have specified for "contextType" in your makeSchema
* configuration. * configuration.
@ -344,16 +347,17 @@ export type MaybeTypeDefConfigFieldIsTypeOf<TypeName extends string> =
* }) * })
* *
* @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL objects that * @param source The [source data](https://nxs.li/guides/source-types) for the GraphQL objects that
* are members of the abstract types that this type is a member of. For example for some type A in two * are members of the abstract types that this type is a member of. For example for some type A
* union types whose members are A,B.C and A,D,E respectively then isTypeOf method for A would receive * in two union types whose members are A,B.C and A,D,E respectively then isTypeOf method for A
* source data from A, B, C, D, & E at runtime. * would receive source data from A, B, C, D, & E at runtime.
* @param context The context data for this request. * @param context The context data for this request.
* *
* The context data is typically a singleton scoped to the lifecycle of the request. This means created at * The context data is typically a singleton scoped to the lifecycle of the request. This means
* the beginning of a request and then passed to all the resolvers that execute while resolving the request. * created at the beginning of a request and then passed to all the resolvers that execute while
* It is often used to store information like the current user making the request. Nexus is not responsible * resolving the request. It is often used to store information like the current user making the
* for this however. That is typically something you'll do with e.g. [Mercurius](https://mercurius.dev) or * request. Nexus is not responsible for this however. That is typically something you'll do with
* [Apollo Server](https://apollographql.com/docs/apollo-server/api/apollo-server). * e.g. [Mercurius](https://mercurius.dev) or [Apollo
* Server](https://apollographql.com/docs/apollo-server/api/apollo-server).
* *
* Note that the type here will be whatever you have specified for "contextType" in your makeSchema * Note that the type here will be whatever you have specified for "contextType" in your makeSchema
* configuration. * configuration.
@ -386,7 +390,7 @@ export type MaybeTypeDefConfigFieldResolveType<TypeName extends string> = IsFeat
* implementation will first look for __typename, then fallback to calling `isTypeOf` on each * implementation will first look for __typename, then fallback to calling `isTypeOf` on each
* implementing Object type. * implementing Object type.
*/ */
resolveType?: AbstractTypeResolver<TypeName> resolveType?: Maybe<AbstractTypeResolver<TypeName>>
} // Make resolveType optional when __typename strategy is enabled } // Make resolveType optional when __typename strategy is enabled
: IsFeatureEnabled2<'abstractTypeStrategies', '__typename'> extends true : IsFeatureEnabled2<'abstractTypeStrategies', '__typename'> extends true
? { ? {
@ -397,7 +401,7 @@ export type MaybeTypeDefConfigFieldResolveType<TypeName extends string> = IsFeat
* implementation will first look for __typename, then fallback to calling `isTypeOf` on each * implementation will first look for __typename, then fallback to calling `isTypeOf` on each
* implementing Object type. * implementing Object type.
*/ */
resolveType?: AbstractTypeResolver<TypeName> resolveType?: Maybe<AbstractTypeResolver<TypeName>>
} }
: { : {
/** /**

View File

@ -1,8 +1,8 @@
import { GraphQLNamedType, GraphQLSchema, isOutputType } from 'graphql' import { GraphQLNamedType, GraphQLSchema, isOutputType } from 'graphql'
import * as path from 'path'
import type { TypegenInfo } from './builder' import type { TypegenInfo } from './builder'
import type { TypingImport } from './definitions/_types' import type { TypingImport } from './definitions/_types'
import { TYPEGEN_HEADER } from './lang' import { TYPEGEN_HEADER } from './lang'
import { nodeImports } from './node'
import { getOwnPackage, log, objValues, relativePathTo, typeScriptFileExtension } from './utils' import { getOwnPackage, log, objValues, relativePathTo, typeScriptFileExtension } from './utils'
/** Any common types / constants that would otherwise be circular-imported */ /** Any common types / constants that would otherwise be circular-imported */
@ -31,8 +31,7 @@ export interface SourceTypeModule {
* *
* If not provided, the default implementation is: * If not provided, the default implementation is:
* *
* (type) => [ * (type) => [ new RegExp(`(?:interface|type|class|enum)\\s+(${type.name})\\W`, "g"), ]
* new RegExp(`(?:interface|type|class|enum)\\s+(${type.name})\\W`, "g"), ]
*/ */
typeMatch?: (type: GraphQLNamedType, defaultRegex: RegExp) => RegExp | RegExp[] typeMatch?: (type: GraphQLNamedType, defaultRegex: RegExp) => RegExp | RegExp[]
/** /**
@ -130,16 +129,12 @@ export function typegenAutoConfig(options: SourceTypesConfigOptions, contextType
} }
}) })
const path = nodeImports().path
const typeSources = await Promise.all( const typeSources = await Promise.all(
options.modules.map(async (source) => { options.modules.map(async (source) => {
// Keeping all of this in here so if we don't have any sources // Keeping all of this in here so if we don't have any sources
// e.g. in the Playground, it doesn't break things. // e.g. in the Playground, it doesn't break things.
// Yeah, this doesn't exist in Node 6, but since this is a new
// lib and Node 6 is close to EOL so if you really need it, open a PR :)
const fs = require('fs') as typeof import('fs')
const util = require('util') as typeof import('util')
const readFile = util.promisify(fs.readFile)
const { module: pathOrModule, glob = true, onlyTypes, alias, typeMatch } = source const { module: pathOrModule, glob = true, onlyTypes, alias, typeMatch } = source
if (path.isAbsolute(pathOrModule) && path.extname(pathOrModule) !== '.ts') { if (path.isAbsolute(pathOrModule) && path.extname(pathOrModule) !== '.ts') {
return console.warn( return console.warn(
@ -155,7 +150,7 @@ export function typegenAutoConfig(options: SourceTypesConfigOptions, contextType
if (path.extname(resolvedPath) !== '.ts') { if (path.extname(resolvedPath) !== '.ts') {
resolvedPath = findTypingForFile(resolvedPath, pathOrModule) resolvedPath = findTypingForFile(resolvedPath, pathOrModule)
} }
fileContents = await readFile(resolvedPath, 'utf-8') fileContents = String(await nodeImports().fs.promises.readFile(resolvedPath, 'utf-8'))
} catch (e) { } catch (e) {
if (e instanceof Error && e.message.indexOf('Cannot find module') !== -1) { if (e instanceof Error && e.message.indexOf('Cannot find module') !== -1) {
console.error(`GraphQL Nexus: Unable to find file or module ${pathOrModule}, skipping`) console.error(`GraphQL Nexus: Unable to find file or module ${pathOrModule}, skipping`)
@ -165,9 +160,8 @@ export function typegenAutoConfig(options: SourceTypesConfigOptions, contextType
return null return null
} }
const importPath = (path.isAbsolute(pathOrModule) const importPath = (
? relativePathTo(resolvedPath, outputPath) path.isAbsolute(pathOrModule) ? relativePathTo(resolvedPath, outputPath) : pathOrModule
: pathOrModule
).replace(typeScriptFileExtension, '') ).replace(typeScriptFileExtension, '')
if (allImportsMap[alias] && allImportsMap[alias] !== importPath) { if (allImportsMap[alias] && allImportsMap[alias] !== importPath) {
@ -196,7 +190,7 @@ export function typegenAutoConfig(options: SourceTypesConfigOptions, contextType
const builtinScalars = new Set(Object.keys(SCALAR_TYPES)) const builtinScalars = new Set(Object.keys(SCALAR_TYPES))
Object.keys(typeMap).forEach((typeName) => { Object.keys(typeMap).forEach((typeName) => {
if (typeName.indexOf('__') === 0) { if (typeName.startsWith('__')) {
return return
} }
if (typesToIgnore.has(typeName)) { if (typesToIgnore.has(typeName)) {
@ -279,7 +273,7 @@ export function typegenAutoConfig(options: SourceTypesConfigOptions, contextType
function findTypingForFile(absolutePath: string, pathOrModule: string) { function findTypingForFile(absolutePath: string, pathOrModule: string) {
// First try to find the "d.ts" adjacent to the file // First try to find the "d.ts" adjacent to the file
try { try {
const typeDefPath = absolutePath.replace(path.extname(absolutePath), '.d.ts') const typeDefPath = absolutePath.replace(nodeImports().path.extname(absolutePath), '.d.ts')
require.resolve(typeDefPath) require.resolve(typeDefPath)
return typeDefPath return typeDefPath
} catch (e) { } catch (e) {

View File

@ -1,5 +1,5 @@
import * as path from 'path'
import type * as Prettier from 'prettier' import type * as Prettier from 'prettier'
import { nodeImports } from './node'
export type TypegenFormatFn = (content: string, type: 'types' | 'schema') => string | Promise<string> export type TypegenFormatFn = (content: string, type: 'types' | 'schema') => string | Promise<string>
@ -22,6 +22,8 @@ export function typegenFormatPrettier(prettierConfig: string | object): TypegenF
let prettierConfigResolved: Prettier.Options let prettierConfigResolved: Prettier.Options
const path = nodeImports().path
if (typeof prettierConfig === 'string') { if (typeof prettierConfig === 'string') {
/* istanbul ignore if */ /* istanbul ignore if */
if (!path.isAbsolute(prettierConfig)) { if (!path.isAbsolute(prettierConfig)) {

View File

@ -1,10 +1,12 @@
import { GraphQLSchema, lexicographicSortSchema, printSchema } from 'graphql' import { GraphQLSchema, lexicographicSortSchema } from 'graphql'
import * as path from 'path'
import type { BuilderConfigInput, TypegenInfo } from './builder' import type { BuilderConfigInput, TypegenInfo } from './builder'
import type { ConfiguredTypegen } from './core'
import type { NexusGraphQLSchema } from './definitions/_types' import type { NexusGraphQLSchema } from './definitions/_types'
import { SDL_HEADER, TYPEGEN_HEADER } from './lang' import { SDL_HEADER, TYPEGEN_HEADER } from './lang'
import { nodeImports } from './node'
import { printSchemaWithDirectives } from './printSchemaWithDirectives'
import { typegenAutoConfig } from './typegenAutoConfig' import { typegenAutoConfig } from './typegenAutoConfig'
import { TypegenFormatFn, typegenFormatPrettier } from './typegenFormatPrettier' import { typegenFormatPrettier } from './typegenFormatPrettier'
import { TypegenPrinter } from './typegenPrinter' import { TypegenPrinter } from './typegenPrinter'
export interface TypegenMetadataConfig export interface TypegenMetadataConfig
@ -12,7 +14,7 @@ export interface TypegenMetadataConfig
nexusSchemaImportId?: string nexusSchemaImportId?: string
outputs: { outputs: {
schema: null | string schema: null | string
typegen: null | string typegen: null | ConfiguredTypegen
} }
} }
@ -26,26 +28,42 @@ export class TypegenMetadata {
/** Generates the artifacts of the build based on what we know about the schema and how it was defined. */ /** Generates the artifacts of the build based on what we know about the schema and how it was defined. */
async generateArtifacts(schema: NexusGraphQLSchema) { async generateArtifacts(schema: NexusGraphQLSchema) {
const sortedSchema = this.sortSchema(schema) const sortedSchema = this.sortSchema(schema)
if (this.config.outputs.schema || this.config.outputs.typegen) { const { typegen } = this.config.outputs
const { schemaTypes, tsTypes } = await this.generateArtifactContents( if (this.config.outputs.schema || typegen) {
sortedSchema, const { schemaTypes, tsTypes, globalTypes } = await this.generateArtifactContents(sortedSchema, typegen)
this.config.outputs.typegen
)
if (this.config.outputs.schema) { if (this.config.outputs.schema) {
await this.writeFile('schema', schemaTypes, this.config.outputs.schema) await this.writeFile('schema', schemaTypes, this.config.outputs.schema)
} }
if (this.config.outputs.typegen) { if (typegen) {
await this.writeFile('types', tsTypes, this.config.outputs.typegen) if (typeof typegen === 'string') {
await this.writeFile('types', tsTypes, typegen)
} else {
await this.writeFile('types', tsTypes, typegen.outputPath)
if (typeof typegen.globalsPath === 'string') {
await this.writeFile('types', globalTypes ?? '', typegen.globalsPath)
}
}
} }
} }
} }
async generateArtifactContents(schema: NexusGraphQLSchema, typeFilePath: string | null) { async generateArtifactContents(schema: NexusGraphQLSchema, typegen: string | null | ConfiguredTypegen) {
const [schemaTypes, tsTypes] = await Promise.all([ const result = {
this.generateSchemaFile(schema), schemaTypes: this.generateSchemaFile(schema),
typeFilePath ? this.generateTypesFile(schema, typeFilePath) : '', tsTypes: '',
]) globalTypes: null as null | string,
return { schemaTypes, tsTypes } }
if (!typegen) {
return result
}
if (typeof typegen === 'string') {
result.tsTypes = await this.generateTypesFile(schema, typegen)
} else {
const generateResult = await this.generateConfiguredTypes(schema, typegen)
result.tsTypes = generateResult.tsTypes
result.globalTypes = generateResult.globalTypes
}
return result
} }
sortSchema(schema: NexusGraphQLSchema) { sortSchema(schema: NexusGraphQLSchema) {
@ -57,31 +75,26 @@ export class TypegenMetadata {
} }
async writeFile(type: 'schema' | 'types', output: string, filePath: string) { async writeFile(type: 'schema' | 'types', output: string, filePath: string) {
if (typeof filePath !== 'string' || !path.isAbsolute(filePath)) { if (typeof filePath !== 'string' || !nodeImports().path.isAbsolute(filePath)) {
return Promise.reject( return Promise.reject(
new Error(`Expected an absolute path to output the Nexus ${type}, saw ${filePath}`) new Error(`Expected an absolute path to output the Nexus ${type}, saw ${filePath}`)
) )
} }
const fs = require('fs') as typeof import('fs') const fs = nodeImports().fs
const util = require('util') as typeof import('util') const formattedOutput =
const [readFile, writeFile, removeFile, mkdir] = [ typeof this.config.formatTypegen === 'function' ? await this.config.formatTypegen(output, type) : output
util.promisify(fs.readFile), const content = this.config.prettierConfig
util.promisify(fs.writeFile), ? await typegenFormatPrettier(this.config.prettierConfig)(formattedOutput, type)
util.promisify(fs.unlink), : formattedOutput
util.promisify(fs.mkdir),
] const [toSave, existing] = await Promise.all([
let formatTypegen: TypegenFormatFn | null = null content,
if (typeof this.config.formatTypegen === 'function') { fs.promises.readFile(filePath, 'utf8').catch(() => ''),
formatTypegen = this.config.formatTypegen ])
} else if (this.config.prettierConfig) {
formatTypegen = typegenFormatPrettier(this.config.prettierConfig)
}
const content = typeof formatTypegen === 'function' ? await formatTypegen(output, type) : output
const [toSave, existing] = await Promise.all([content, readFile(filePath, 'utf8').catch(() => '')])
if (toSave !== existing) { if (toSave !== existing) {
const dirPath = path.dirname(filePath) const dirPath = nodeImports().path.dirname(filePath)
try { try {
await mkdir(dirPath, { recursive: true }) await fs.promises.mkdir(dirPath, { recursive: true })
} catch (e) { } catch (e) {
if (e.code !== 'EEXIST') { if (e.code !== 'EEXIST') {
throw e throw e
@ -91,14 +104,14 @@ export class TypegenMetadata {
// apparently. See issue motivating this logic here: // apparently. See issue motivating this logic here:
// https://github.com/graphql-nexus/schema/issues/247. // https://github.com/graphql-nexus/schema/issues/247.
try { try {
await removeFile(filePath) await fs.promises.unlink(filePath)
} catch (e) { } catch (e) {
/* istanbul ignore next */ /* istanbul ignore next */
if (e.code !== 'ENOENT' && e.code !== 'ENOTDIR') { if (e.code !== 'ENOENT' && e.code !== 'ENOTDIR') {
throw e throw e
} }
} }
return writeFile(filePath, toSave) return fs.promises.writeFile(filePath, toSave)
} }
} }
@ -106,7 +119,7 @@ export class TypegenMetadata {
generateSchemaFile(schema: GraphQLSchema): string { generateSchemaFile(schema: GraphQLSchema): string {
let printedSchema = this.config.customPrintSchemaFn let printedSchema = this.config.customPrintSchemaFn
? this.config.customPrintSchemaFn(schema) ? this.config.customPrintSchemaFn(schema)
: printSchema(schema) : printSchemaWithDirectives(schema)
return [SDL_HEADER, printedSchema].join('\n\n') return [SDL_HEADER, printedSchema].join('\n\n')
} }
@ -115,11 +128,34 @@ export class TypegenMetadata {
const typegenInfo = await this.getTypegenInfo(schema, typegenPath) const typegenInfo = await this.getTypegenInfo(schema, typegenPath)
return new TypegenPrinter(schema, { return new TypegenPrinter(schema, {
declareInputs: false,
useReadonlyArrayForInputs: false,
...typegenInfo, ...typegenInfo,
typegenPath, typegenPath,
}).print() }).print()
} }
/** Generates the type definitions */
async generateConfiguredTypes(schema: NexusGraphQLSchema, typegen: ConfiguredTypegen) {
const {
outputPath: typegenPath,
globalsPath,
globalsHeaders,
declareInputs = false,
useReadonlyArrayForInputs = false,
} = typegen
const typegenInfo = await this.getTypegenInfo(schema, typegenPath)
return new TypegenPrinter(schema, {
...typegenInfo,
typegenPath,
globalsPath,
globalsHeaders,
declareInputs,
useReadonlyArrayForInputs,
}).printConfigured()
}
async getTypegenInfo(schema: GraphQLSchema, typegenPath?: string): Promise<TypegenInfo> { async getTypegenInfo(schema: GraphQLSchema, typegenPath?: string): Promise<TypegenInfo> {
if ('typegenConfig' in this.config) { if ('typegenConfig' in this.config) {
throw new Error( throw new Error(
@ -130,7 +166,7 @@ export class TypegenMetadata {
if (this.config.sourceTypes) { if (this.config.sourceTypes) {
return typegenAutoConfig(this.config.sourceTypes, this.config.contextType)( return typegenAutoConfig(this.config.sourceTypes, this.config.contextType)(
schema, schema,
typegenPath || this.config.outputs.typegen || '' typegenPath || this.config.outputs.typegen?.outputPath || ''
) )
} }

View File

@ -18,13 +18,17 @@ import {
isNonNullType, isNonNullType,
isObjectType, isObjectType,
isScalarType, isScalarType,
isSpecifiedDirective,
isSpecifiedScalarType, isSpecifiedScalarType,
isUnionType, isUnionType,
} from 'graphql' } from 'graphql'
import type { TypegenInfo } from './builder' import type { TypegenInfo } from './builder'
import { SchemaDirectiveLocation } from './definitions/directive'
import { isNexusPrintedGenTyping, isNexusPrintedGenTypingImport } from './definitions/wrapping' import { isNexusPrintedGenTyping, isNexusPrintedGenTypingImport } from './definitions/wrapping'
import type { NexusGraphQLSchema } from './definitions/_types' import type { NexusGraphQLSchema } from './definitions/_types'
import { TYPEGEN_HEADER } from './lang'
import type { StringLike } from './plugin' import type { StringLike } from './plugin'
import { hasNexusExtension, isNexusFieldExtension } from './extensions'
import { import {
eachObj, eachObj,
getOwnPackage, getOwnPackage,
@ -34,6 +38,7 @@ import {
mapObj, mapObj,
mapValues, mapValues,
PrintedGenTypingImport, PrintedGenTypingImport,
relativePathTo,
resolveImportPath, resolveImportPath,
} from './utils' } from './utils'
@ -52,27 +57,26 @@ type RootTypeMapping = Record<string, string | Record<string, [string, string]>>
interface TypegenInfoWithFile extends TypegenInfo { interface TypegenInfoWithFile extends TypegenInfo {
typegenPath: string typegenPath: string
globalsPath?: string
globalsHeaders?: string[]
declareInputs?: boolean
useReadonlyArrayForInputs?: boolean
} }
/** /**
* We track and output a few main things: * We track and output a few main things:
* *
* 1. "root" types, or the values that fill the first * 1. "root" types, or the values that fill the first argument for a given object type 2. "arg" types, the
* argument for a given object type * values that are arguments to output fields. 3. "return" types, the values returned from the resolvers...
* * usually just list/nullable variations on the
* 2. "arg" types, the values that are arguments to output fields. * "root" types for other types 4. The names of all types, grouped by type.
*
* 3. "return" types, the values returned from the resolvers... usually
* just list/nullable variations on the "root" types for other types
*
* 4. The names of all types, grouped by type.
* *
* - Non-scalar types will get a dedicated "Root" type associated with it * - Non-scalar types will get a dedicated "Root" type associated with it
*/ */
export class TypegenPrinter { export class TypegenPrinter {
groupedTypes: GroupedTypes private groupedTypes: GroupedTypes
printImports: Record<string, Record<string, boolean | string>> private printImports: Record<string, Record<string, boolean | string>>
hasDiscriminatedTypes: boolean private hasDiscriminatedTypes: boolean
constructor(protected schema: NexusGraphQLSchema, protected typegenInfo: TypegenInfoWithFile) { constructor(protected schema: NexusGraphQLSchema, protected typegenInfo: TypegenInfoWithFile) {
this.groupedTypes = groupTypes(schema) this.groupedTypes = groupTypes(schema)
@ -81,7 +85,33 @@ export class TypegenPrinter {
} }
print() { print() {
const body = [ const body = [this.printCommon(), this.printPlugins()].join('\n\n')
return [this.printHeaders(), body].join('\n\n')
}
printConfigured() {
if (this.typegenInfo.globalsPath) {
const plugins = this.printPlugins()
const globalTypes = [this.printHeadersGlobal(), this.printDynamicImport(true), plugins].join('\n\n')
// Reset printImports for the imports needed in the types
this.printImports = {}
const common = this.printCommon()
const tsTypes = [this.printHeadersCommon(), common].join('\n\n')
return {
tsTypes,
globalTypes,
}
}
return {
tsTypes: this.print(),
globalTypes: null,
}
}
private printCommon() {
return [
this.printInputTypeMap(), this.printInputTypeMap(),
this.printEnumTypeMap(), this.printEnumTypeMap(),
this.printScalarTypeMap(), this.printScalarTypeMap(),
@ -101,35 +131,98 @@ export class TypegenPrinter {
this.printTypeNames('interface', 'NexusGenInterfaceNames', 'NexusGenInterfaces'), this.printTypeNames('interface', 'NexusGenInterfaceNames', 'NexusGenInterfaces'),
this.printTypeNames('scalar', 'NexusGenScalarNames', 'NexusGenScalars'), this.printTypeNames('scalar', 'NexusGenScalarNames', 'NexusGenScalars'),
this.printTypeNames('union', 'NexusGenUnionNames', 'NexusGenUnions'), this.printTypeNames('union', 'NexusGenUnionNames', 'NexusGenUnions'),
this.printDirectives(),
this.printIsTypeOfObjectTypeNames('NexusGenObjectsUsingAbstractStrategyIsTypeOf'), this.printIsTypeOfObjectTypeNames('NexusGenObjectsUsingAbstractStrategyIsTypeOf'),
this.printResolveTypeAbstractTypes('NexusGenAbstractsUsingStrategyResolveType'), this.printResolveTypeAbstractTypes('NexusGenAbstractsUsingStrategyResolveType'),
this.printFeaturesConfig('NexusGenFeaturesConfig'), this.printFeaturesConfig('NexusGenFeaturesConfig'),
this.printGenTypeMap(), this.printGenTypeMap(),
this.printPlugins(),
].join('\n\n') ].join('\n\n')
return [this.printHeaders(), body].join('\n\n')
} }
printHeaders() { private printHeaders() {
const fieldDefs = [ return [this.printHeadersCommon(), this.printHeadersGlobal()].join('\n')
this.printDynamicInputFieldDefinitions(), }
this.printDynamicOutputFieldDefinitions(),
this.printDynamicOutputPropertyDefinitions(), private printHeadersCommon() {
]
return [ return [
this.typegenInfo.headers.join('\n'), this.typegenInfo.headers.join('\n'),
this.typegenInfo.imports.join('\n'), this.typegenInfo.imports.join('\n'),
this.printDynamicImport(), this.printDynamicImport(),
...fieldDefs,
GLOBAL_DECLARATION,
].join('\n') ].join('\n')
} }
printGenTypeMap() { private printDirectives() {
const customDirectives = this.schema.getDirectives().filter((d) => !isSpecifiedDirective(d))
const schemaDirectiveArgs: Record<string, readonly GraphQLArgument[] | undefined> = {}
// Gather the mappings between directives, locations, etc.
customDirectives.forEach((d) => {
d.locations.forEach((l) => {
if (SchemaDirectiveLocation.includes(l as any)) {
schemaDirectiveArgs[d.name] = d.args ?? undefined
}
})
})
const directiveNames = Object.keys(schemaDirectiveArgs)
.map((i) => JSON.stringify(i))
.join(' | ')
const toPrint: string[] = [`export type NexusGenDirectives = ${directiveNames || 'never'}`]
// Print the mappings of the directive names -> args
// NexusGenDirectiveArgs
let directiveArgs = [`export interface NexusGenDirectiveArgs {`]
eachObj(schemaDirectiveArgs, (val, key) => {
if (val) {
directiveArgs.push(` ${key}: {`)
val.forEach((arg) => {
const [sep, rep] = this.normalizeArg(arg)
directiveArgs.push(` ${arg.name}${sep}${rep}`)
})
directiveArgs.push(` }`)
} else {
directiveArgs.push(` ${key}: never`)
}
})
directiveArgs.push('}')
toPrint.push(directiveArgs.join('\n'))
return toPrint.join('\n\n')
}
private printHeadersGlobal() {
const headers = [
this.printDynamicInputFieldDefinitions(),
this.printDynamicOutputFieldDefinitions(),
this.printDynamicOutputPropertyDefinitions(),
GLOBAL_DECLARATION,
]
if (this.typegenInfo.globalsPath) {
headers.unshift(
`import type { NexusGenTypes } from '${relativePathTo(
this.typegenInfo.typegenPath,
this.typegenInfo.globalsPath ?? ''
)}'`
)
headers.unshift(...(this.typegenInfo.globalsHeaders ?? []))
headers.unshift(TYPEGEN_HEADER)
}
return headers.join('\n')
}
private printGenTypeMap() {
return [`export interface NexusGenTypes {`] return [`export interface NexusGenTypes {`]
.concat([ .concat([
` context: ${this.printContext()};`, ` context: ${this.printContext()};`,
` inputTypes: NexusGenInputs;`, ` inputTypes: NexusGenInputs;`,
` directives: NexusGenDirectives;`,
` directiveArgs: NexusGenDirectiveArgs;`,
` rootTypes: NexusGenRootTypes;`, ` rootTypes: NexusGenRootTypes;`,
` inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars;`, ` inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars;`,
` argTypes: NexusGenArgTypes;`, ` argTypes: NexusGenArgTypes;`,
@ -156,11 +249,8 @@ export class TypegenPrinter {
.join('\n') .join('\n')
} }
printDynamicImport() { private printDynamicImport(forGlobal = false) {
const { const { sourceTypings } = this.schema.extensions.nexus.config
rootTypings,
dynamicFields: { dynamicInputFields, dynamicOutputFields },
} = this.schema.extensions.nexus.config
const { contextTypeImport } = this.typegenInfo const { contextTypeImport } = this.typegenInfo
const imports: string[] = [] const imports: string[] = []
const importMap: Record<string, Set<string>> = {} const importMap: Record<string, Set<string>> = {}
@ -168,38 +258,33 @@ export class TypegenPrinter {
const nexusSchemaImportId = this.typegenInfo.nexusSchemaImportId ?? getOwnPackage().name const nexusSchemaImportId = this.typegenInfo.nexusSchemaImportId ?? getOwnPackage().name
if (!this.printImports[nexusSchemaImportId]) { if (!this.printImports[nexusSchemaImportId]) {
if ( this.maybeAddCoreImport(forGlobal)
[dynamicInputFields, dynamicOutputFields].some((o) => Object.keys(o).length > 0) ||
this.hasDiscriminatedTypes === true
) {
this.printImports[nexusSchemaImportId] = {
core: true,
}
}
} }
if (contextTypeImport) { if (!forGlobal) {
const importPath = resolveImportPath(contextTypeImport, 'context', outputPath) if (contextTypeImport) {
importMap[importPath] = importMap[importPath] || new Set() const importPath = resolveImportPath(contextTypeImport, 'context', outputPath)
importMap[importPath].add(
contextTypeImport.alias
? `${contextTypeImport.export} as ${contextTypeImport.alias}`
: contextTypeImport.export
)
}
eachObj(rootTypings, (rootType, typeName) => {
if (typeof rootType !== 'string') {
const importPath = resolveImportPath(rootType, typeName, outputPath)
importMap[importPath] = importMap[importPath] || new Set() importMap[importPath] = importMap[importPath] || new Set()
importMap[importPath].add( importMap[importPath].add(
rootType.alias ? `${rootType.export} as ${rootType.alias}` : rootType.export contextTypeImport.alias
? `${contextTypeImport.export} as ${contextTypeImport.alias}`
: contextTypeImport.export
) )
} }
}) eachObj(sourceTypings, (rootType, typeName) => {
eachObj(importMap, (val, key) => { if (typeof rootType !== 'string') {
imports.push(`import type { ${Array.from(val).join(', ')} } from ${JSON.stringify(key)}`) const importPath = resolveImportPath(rootType, typeName, outputPath)
}) importMap[importPath] = importMap[importPath] || new Set()
importMap[importPath].add(
rootType.alias ? `${rootType.export} as ${rootType.alias}` : rootType.export
)
}
})
eachObj(importMap, (val, key) => {
imports.push(`import type { ${Array.from(val).join(', ')} } from ${JSON.stringify(key)}`)
})
}
eachObj(this.printImports, (val, key) => { eachObj(this.printImports, (val, key) => {
const { default: def, ...rest } = val const { default: def, ...rest } = val
const idents = [] const idents = []
@ -218,7 +303,29 @@ export class TypegenPrinter {
return imports.join('\n') return imports.join('\n')
} }
printDynamicInputFieldDefinitions() { private maybeAddCoreImport(forGlobal = false) {
const nexusSchemaImportId = this.typegenInfo.nexusSchemaImportId ?? getOwnPackage().name
const {
dynamicFields: { dynamicInputFields, dynamicOutputFields },
} = this.schema.extensions.nexus.config
let shouldAdd = false
const hasDynamicFields = [dynamicInputFields, dynamicOutputFields].some((o) => Object.keys(o).length > 0)
if (!this.typegenInfo.globalsPath) {
shouldAdd = hasDynamicFields || this.hasDiscriminatedTypes
} else {
shouldAdd = forGlobal ? hasDynamicFields : this.hasDiscriminatedTypes
}
if (shouldAdd) {
this.printImports[nexusSchemaImportId] = {
core: true,
}
}
}
private printDynamicInputFieldDefinitions() {
const { dynamicInputFields } = this.schema.extensions.nexus.config.dynamicFields const { dynamicInputFields } = this.schema.extensions.nexus.config.dynamicFields
// If there is nothing custom... exit // If there is nothing custom... exit
if (!Object.keys(dynamicInputFields).length) { if (!Object.keys(dynamicInputFields).length) {
@ -246,7 +353,7 @@ export class TypegenPrinter {
.join('\n') .join('\n')
} }
printDynamicOutputFieldDefinitions() { private printDynamicOutputFieldDefinitions() {
const { dynamicOutputFields } = this.schema.extensions.nexus.config.dynamicFields const { dynamicOutputFields } = this.schema.extensions.nexus.config.dynamicFields
// If there is nothing custom... exit // If there is nothing custom... exit
if (!Object.keys(dynamicOutputFields).length) { if (!Object.keys(dynamicOutputFields).length) {
@ -274,7 +381,7 @@ export class TypegenPrinter {
.join('\n') .join('\n')
} }
prependDoc(typeDef: string, typeDescription?: string | null) { private prependDoc(typeDef: string, typeDescription?: string | null) {
let outStr = '' let outStr = ''
if (typeDescription) { if (typeDescription) {
let parts = typeDescription.split('\n').map((f) => f.trimLeft()) let parts = typeDescription.split('\n').map((f) => f.trimLeft())
@ -289,7 +396,7 @@ export class TypegenPrinter {
return `${outStr}${typeDef}` return `${outStr}${typeDef}`
} }
printDynamicOutputPropertyDefinitions() { private printDynamicOutputPropertyDefinitions() {
const { dynamicOutputProperties } = this.schema.extensions.nexus.config.dynamicFields const { dynamicOutputProperties } = this.schema.extensions.nexus.config.dynamicFields
// If there is nothing custom... exit // If there is nothing custom... exit
if (!Object.keys(dynamicOutputProperties).length) { if (!Object.keys(dynamicOutputProperties).length) {
@ -308,9 +415,9 @@ export class TypegenPrinter {
.join('\n') .join('\n')
} }
printInheritedFieldMap() { private printInheritedFieldMap() {
const hasInterfaces: ( const hasInterfaces: (
| (GraphQLInterfaceType & { getInterfaces(): GraphQLInterfaceType[] }) | (GraphQLInterfaceType & { getInterfaces(): ReadonlyArray<GraphQLInterfaceType> })
| GraphQLObjectType | GraphQLObjectType
)[] = [] )[] = []
const withInterfaces = hasInterfaces const withInterfaces = hasInterfaces
@ -333,37 +440,15 @@ export class TypegenPrinter {
.join('\n') .join('\n')
} }
printContext() { private printContext() {
return this.typegenInfo.contextTypeImport?.alias || this.typegenInfo.contextTypeImport?.export || 'any' return this.typegenInfo.contextTypeImport?.alias || this.typegenInfo.contextTypeImport?.export || 'any'
} }
buildResolveSourceTypeMap() { private printAbstractTypeMembers() {
const sourceMap: TypeMapping = {}
const abstractTypes: (GraphQLInterfaceType | GraphQLUnionType)[] = []
abstractTypes
.concat(this.groupedTypes.union)
.concat(this.groupedTypes.interface)
.forEach((type) => {
if (isInterfaceType(type)) {
const possibleNames = this.schema.getPossibleTypes(type).map((t) => t.name)
if (possibleNames.length > 0) {
sourceMap[type.name] = possibleNames.map((val) => `NexusGenRootTypes['${val}']`).join(' | ')
}
} else {
sourceMap[type.name] = type
.getTypes()
.map((t) => `NexusGenRootTypes['${t.name}']`)
.join(' | ')
}
})
return sourceMap
}
printAbstractTypeMembers() {
return this.printTypeInterface('NexusGenAbstractTypeMembers', this.buildAbstractTypeMembers()) return this.printTypeInterface('NexusGenAbstractTypeMembers', this.buildAbstractTypeMembers())
} }
buildAbstractTypeMembers() { private buildAbstractTypeMembers() {
const sourceMap: TypeMapping = {} const sourceMap: TypeMapping = {}
const abstractTypes: (GraphQLInterfaceType | GraphQLUnionType)[] = [] const abstractTypes: (GraphQLInterfaceType | GraphQLUnionType)[] = []
abstractTypes abstractTypes
@ -385,13 +470,13 @@ export class TypegenPrinter {
return sourceMap return sourceMap
} }
printTypeNames(name: keyof GroupedTypes, exportName: string, source: string) { private printTypeNames(name: keyof GroupedTypes, exportName: string, source: string) {
const obj = this.groupedTypes[name] as GraphQLNamedType[] const obj = this.groupedTypes[name] as GraphQLNamedType[]
const typeDef = obj.length === 0 ? 'never' : `keyof ${source}` const typeDef = obj.length === 0 ? 'never' : `keyof ${source}`
return `export type ${exportName} = ${typeDef};` return `export type ${exportName} = ${typeDef};`
} }
printIsTypeOfObjectTypeNames(exportName: string) { private printIsTypeOfObjectTypeNames(exportName: string) {
const objectTypes = this.groupedTypes.object.filter((o) => o.isTypeOf !== undefined) const objectTypes = this.groupedTypes.object.filter((o) => o.isTypeOf !== undefined)
const typeDef = const typeDef =
objectTypes.length === 0 objectTypes.length === 0
@ -403,7 +488,7 @@ export class TypegenPrinter {
return `export type ${exportName} = ${typeDef};` return `export type ${exportName} = ${typeDef};`
} }
printResolveTypeAbstractTypes(exportName: string) { private printResolveTypeAbstractTypes(exportName: string) {
const abstractTypes = [...this.groupedTypes.interface, ...this.groupedTypes.union].filter( const abstractTypes = [...this.groupedTypes.interface, ...this.groupedTypes.union].filter(
(o) => o.resolveType !== undefined (o) => o.resolveType !== undefined
) )
@ -418,7 +503,7 @@ export class TypegenPrinter {
return `export type ${exportName} = ${typeDef};` return `export type ${exportName} = ${typeDef};`
} }
printFeaturesConfig(exportName: string) { private printFeaturesConfig(exportName: string) {
const abstractTypes = this.schema.extensions.nexus.config.features?.abstractTypeStrategies ?? {} const abstractTypes = this.schema.extensions.nexus.config.features?.abstractTypeStrategies ?? {}
const unionProps = renderObject(mapValues(abstractTypes, (val) => val ?? false)) const unionProps = renderObject(mapValues(abstractTypes, (val) => val ?? false))
@ -428,7 +513,7 @@ export class TypegenPrinter {
.join('\n') .join('\n')
} }
buildEnumTypeMap() { private buildEnumTypeMap() {
const enumMap: TypeMapping = {} const enumMap: TypeMapping = {}
this.groupedTypes.enum.forEach((e) => { this.groupedTypes.enum.forEach((e) => {
const sourceType = this.resolveSourceType(e.name) const sourceType = this.resolveSourceType(e.name)
@ -442,7 +527,7 @@ export class TypegenPrinter {
return enumMap return enumMap
} }
buildInputTypeMap() { private buildInputTypeMap() {
const inputObjMap: TypeFieldMapping = {} const inputObjMap: TypeFieldMapping = {}
this.groupedTypes.input.forEach((input) => { this.groupedTypes.input.forEach((input) => {
eachObj(input.getFields(), (field) => { eachObj(input.getFields(), (field) => {
@ -453,7 +538,7 @@ export class TypegenPrinter {
return inputObjMap return inputObjMap
} }
buildScalarTypeMap() { private buildScalarTypeMap() {
const scalarMap: TypeMapping = {} const scalarMap: TypeMapping = {}
this.groupedTypes.scalar.forEach((e) => { this.groupedTypes.scalar.forEach((e) => {
if (isSpecifiedScalarType(e)) { if (isSpecifiedScalarType(e)) {
@ -470,19 +555,37 @@ export class TypegenPrinter {
return scalarMap return scalarMap
} }
printInputTypeMap() { private printInputTypeMap() {
return this.printTypeFieldInterface('NexusGenInputs', this.buildInputTypeMap(), 'input type') const inputTypeMap = this.buildInputTypeMap()
if (this.typegenInfo.declareInputs) {
const declaredInputs: string[] = mapObj(inputTypeMap, (fields, inputName) =>
this.printNamedObj(inputName, fields)
)
return [...declaredInputs, this.printNamedMap('NexusGenInputs', inputTypeMap)].join('\n\n')
}
return this.printTypeFieldInterface('NexusGenInputs', inputTypeMap, 'input type')
} }
printEnumTypeMap() { private printEnumTypeMap() {
return this.printTypeInterface('NexusGenEnums', this.buildEnumTypeMap()) const enumTypeMap = this.buildEnumTypeMap()
if (this.typegenInfo.declareInputs) {
return [
...mapObj(enumTypeMap, (val, name) => `export type ${name} = ${val}`),
this.printNamedMap('NexusGenEnums', enumTypeMap),
].join('\n\n')
}
return this.printTypeInterface('NexusGenEnums', enumTypeMap)
} }
printScalarTypeMap() { private printScalarTypeMap() {
return this.printTypeInterface('NexusGenScalars', this.buildScalarTypeMap()) return this.printTypeInterface('NexusGenScalars', this.buildScalarTypeMap())
} }
shouldDiscriminateType( private shouldDiscriminateType(
abstractType: GraphQLAbstractType, abstractType: GraphQLAbstractType,
objectType: GraphQLObjectType objectType: GraphQLObjectType
): 'required' | 'optional' | false { ): 'required' | 'optional' | false {
@ -501,7 +604,7 @@ export class TypegenPrinter {
return 'required' return 'required'
} }
maybeDiscriminate(abstractType: GraphQLAbstractType, objectType: GraphQLObjectType) { private maybeDiscriminate(abstractType: GraphQLAbstractType, objectType: GraphQLObjectType) {
const requiredOrOptional = this.shouldDiscriminateType(abstractType, objectType) const requiredOrOptional = this.shouldDiscriminateType(abstractType, objectType)
if (requiredOrOptional === false) { if (requiredOrOptional === false) {
@ -513,7 +616,7 @@ export class TypegenPrinter {
return `core.Discriminate<'${objectType.name}', '${requiredOrOptional}'>` return `core.Discriminate<'${objectType.name}', '${requiredOrOptional}'>`
} }
buildRootTypeMap(hasFields: Array<GraphQLInterfaceType | GraphQLObjectType | GraphQLUnionType>) { private buildRootTypeMap(hasFields: Array<GraphQLInterfaceType | GraphQLObjectType | GraphQLUnionType>) {
const rootTypeMap: RootTypeMapping = {} const rootTypeMap: RootTypeMapping = {}
hasFields.forEach((type) => { hasFields.forEach((type) => {
const rootTyping = this.resolveSourceType(type.name) const rootTyping = this.resolveSourceType(type.name)
@ -538,8 +641,17 @@ export class TypegenPrinter {
} else { } else {
eachObj(type.getFields(), (field) => { eachObj(type.getFields(), (field) => {
const obj = (rootTypeMap[type.name] = rootTypeMap[type.name] || {}) const obj = (rootTypeMap[type.name] = rootTypeMap[type.name] || {})
if (!this.hasResolver(field, type)) { const fieldSourceType = this.fieldSourceType(field, type)
if (typeof obj !== 'string') { if (typeof obj !== 'string') {
if (Array.isArray(fieldSourceType)) {
for (const field of fieldSourceType) {
obj[field.name] = [field.optional ? '?:' : ':', field.type]
}
} else if (typeof fieldSourceType === 'object') {
obj[field.name] = [fieldSourceType.optional ? '?:' : ':', fieldSourceType.type]
} else if (fieldSourceType) {
obj[field.name] = [':', fieldSourceType]
} else if (!this.hasResolver(field, type)) {
obj[field.name] = [ obj[field.name] = [
this.argSeparator(field.type as GraphQLInputType, false), this.argSeparator(field.type as GraphQLInputType, false),
this.printOutputType(field.type), this.printOutputType(field.type),
@ -552,44 +664,55 @@ export class TypegenPrinter {
return rootTypeMap return rootTypeMap
} }
resolveSourceType(typeName: string): string | undefined { private resolveSourceType(typeName: string): string | undefined {
const rootTyping = this.schema.extensions.nexus.config.rootTypings[typeName] const rootTyping = this.schema.extensions.nexus.config.sourceTypings[typeName]
if (rootTyping) { if (rootTyping) {
return typeof rootTyping === 'string' ? rootTyping : rootTyping.export return typeof rootTyping === 'string' ? rootTyping : rootTyping.export
} }
return (this.typegenInfo.sourceTypeMap as any)[typeName] return (this.typegenInfo.sourceTypeMap as any)[typeName]
} }
hasResolver( private fieldSourceType(
field: GraphQLField<any, any>, field: GraphQLField<any, any>,
// Used in test mocking // Used in test mocking
_type: GraphQLObjectType _type: GraphQLObjectType
) { ) {
if (field.extensions && field.extensions.nexus) { if (field.extensions && isNexusFieldExtension(field.extensions.nexus)) {
return field.extensions.nexus.sourceType
}
return undefined
}
private hasResolver(
field: GraphQLField<any, any>,
// Used in test mocking
_type: GraphQLObjectType
) {
if (field.extensions && hasNexusExtension(field.extensions.nexus)) {
return field.extensions.nexus.hasDefinedResolver return field.extensions.nexus.hasDefinedResolver
} }
return Boolean(field.resolve) return Boolean(field.resolve)
} }
printObjectTypeMap() { private printObjectTypeMap() {
return this.printRootTypeFieldInterface( return this.printRootTypeFieldInterface(
'NexusGenObjects', 'NexusGenObjects',
this.buildRootTypeMap(this.groupedTypes.object) this.buildRootTypeMap(this.groupedTypes.object)
) )
} }
printInterfaceTypeMap() { private printInterfaceTypeMap() {
return this.printRootTypeFieldInterface( return this.printRootTypeFieldInterface(
'NexusGenInterfaces', 'NexusGenInterfaces',
this.buildRootTypeMap(this.groupedTypes.interface) this.buildRootTypeMap(this.groupedTypes.interface)
) )
} }
printUnionTypeMap() { private printUnionTypeMap() {
return this.printRootTypeFieldInterface('NexusGenUnions', this.buildRootTypeMap(this.groupedTypes.union)) return this.printRootTypeFieldInterface('NexusGenUnions', this.buildRootTypeMap(this.groupedTypes.union))
} }
printRootTypeDef() { private printRootTypeDef() {
const toJoin: string[] = [] const toJoin: string[] = []
if (this.groupedTypes.interface.length) { if (this.groupedTypes.interface.length) {
toJoin.push('NexusGenInterfaces') toJoin.push('NexusGenInterfaces')
@ -603,7 +726,7 @@ export class TypegenPrinter {
return `export type NexusGenRootTypes = ${toJoin.join(' & ')}` return `export type NexusGenRootTypes = ${toJoin.join(' & ')}`
} }
printAllTypesMap() { private printAllTypesMap() {
const toJoin: string[] = ['NexusGenRootTypes'] const toJoin: string[] = ['NexusGenRootTypes']
if (this.groupedTypes.scalar.length) { if (this.groupedTypes.scalar.length) {
toJoin.push('NexusGenScalars') toJoin.push('NexusGenScalars')
@ -614,7 +737,7 @@ export class TypegenPrinter {
return `export type NexusGenAllTypes = ${toJoin.join(' & ')}` return `export type NexusGenAllTypes = ${toJoin.join(' & ')}`
} }
buildArgTypeMap() { private buildArgTypeMap() {
const argTypeMap: Record<string, TypeFieldMapping> = {} const argTypeMap: Record<string, TypeFieldMapping> = {}
const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = [] const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = []
hasFields hasFields
@ -634,11 +757,38 @@ export class TypegenPrinter {
return argTypeMap return argTypeMap
} }
printArgTypeMap() { private printArgTypeMap() {
return this.printArgTypeFieldInterface(this.buildArgTypeMap()) const argTypeMap = this.buildArgTypeMap()
if (this.typegenInfo.declareInputs) {
const declaredArgs: string[] = []
eachObj(argTypeMap, (fields, typeName) => {
eachObj(fields, (args, fieldName) => {
declaredArgs.push(this.printNamedObj(this.getArgsName(typeName, fieldName), args))
})
})
return [...declaredArgs, this.printArgTypeFieldInterface(argTypeMap)].join('\n\n')
}
return this.printArgTypeFieldInterface(argTypeMap)
} }
buildReturnTypeMap() { private getArgsName(typeName: string, fieldName: string) {
return `${typeName}${fieldName.slice(0, 1).toUpperCase().concat(fieldName.slice(1))}Args`
}
private printNamedObj(name: string, obj: Record<string, [string, string]>) {
return [
`export interface ${name} {`,
...mapObj(obj, (val, key) => ` ${key}${val[0]} ${val[1]}`),
`}`,
].join('\n')
}
private printNamedMap(name: string, obj: Record<string, any>) {
return [`export interface ${name} {`, ...mapObj(obj, (val, key) => ` ${key}: ${key}`), `}`].join('\n')
}
private buildReturnTypeMap() {
const returnTypeMap: TypeFieldMapping = {} const returnTypeMap: TypeFieldMapping = {}
const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = [] const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = []
hasFields hasFields
@ -653,7 +803,7 @@ export class TypegenPrinter {
return returnTypeMap return returnTypeMap
} }
buildReturnTypeNamesMap() { private buildReturnTypeNamesMap() {
const returnTypeMap: TypeFieldMapping = {} const returnTypeMap: TypeFieldMapping = {}
const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = [] const hasFields: (GraphQLInterfaceType | GraphQLObjectType)[] = []
hasFields hasFields
@ -668,26 +818,26 @@ export class TypegenPrinter {
return returnTypeMap return returnTypeMap
} }
printOutputType(type: GraphQLOutputType) { private printOutputType(type: GraphQLOutputType) {
const returnType = this.typeToArr(type) const returnType = this.typeToArr(type)
function combine(item: any[]): string { function combine(item: any[]): string {
if (item.length === 1) { if (item.length === 1) {
if (Array.isArray(item[0])) { if (Array.isArray(item[0])) {
const toPrint = combine(item[0]) const toPrint = combine(item[0])
return toPrint.indexOf('null') === -1 ? `${toPrint}[]` : `Array<${toPrint}>` return `ReadonlyArray<${toPrint}>`
} }
return item[0] return item[0]
} }
if (Array.isArray(item[1])) { if (Array.isArray(item[1])) {
const toPrint = combine(item[1]) const toPrint = combine(item[1])
return toPrint.indexOf('null') === -1 ? `${toPrint}[] | null` : `Array<${toPrint}> | null` return `ReadonlyArray<${toPrint}> | null`
} }
return `${item[1]} | null` return `${item[1]} | null`
} }
return `${combine(returnType)}; // ${type}` return `${combine(returnType)}; // ${type}`
} }
typeToArr(type: GraphQLOutputType): any[] { private typeToArr(type: GraphQLOutputType): any[] {
const typing = [] const typing = []
if (isNonNullType(type)) { if (isNonNullType(type)) {
type = type.ofType type = type.ofType
@ -699,18 +849,22 @@ export class TypegenPrinter {
} else if (isScalarType(type)) { } else if (isScalarType(type)) {
typing.push(this.printScalar(type)) typing.push(this.printScalar(type))
} else if (isEnumType(type)) { } else if (isEnumType(type)) {
typing.push(`NexusGenEnums['${type.name}']`) if (this.typegenInfo.declareInputs) {
typing.push(type.name)
} else {
typing.push(`NexusGenEnums['${type.name}']`)
}
} else if (isObjectType(type) || isInterfaceType(type) || isUnionType(type)) { } else if (isObjectType(type) || isInterfaceType(type) || isUnionType(type)) {
typing.push(`NexusGenRootTypes['${type.name}']`) typing.push(`NexusGenRootTypes['${type.name}']`)
} }
return typing return typing
} }
printFieldTypesMap() { private printFieldTypesMap() {
return this.printTypeFieldInterface('NexusGenFieldTypes', this.buildReturnTypeMap(), 'field return type') return this.printTypeFieldInterface('NexusGenFieldTypes', this.buildReturnTypeMap(), 'field return type')
} }
printFieldTypeNamesMap() { private printFieldTypeNamesMap() {
return this.printTypeFieldInterface( return this.printTypeFieldInterface(
'NexusGenFieldTypeNames', 'NexusGenFieldTypeNames',
this.buildReturnTypeNamesMap(), this.buildReturnTypeNamesMap(),
@ -718,11 +872,11 @@ export class TypegenPrinter {
) )
} }
normalizeArg(arg: GraphQLInputField | GraphQLArgument): [string, string] { private normalizeArg(arg: GraphQLInputField | GraphQLArgument): [string, string] {
return [this.argSeparator(arg.type, Boolean(arg.defaultValue)), this.argTypeRepresentation(arg.type)] return [this.argSeparator(arg.type, Boolean(arg.defaultValue)), this.argTypeRepresentation(arg.type)]
} }
argSeparator(type: GraphQLInputType, hasDefaultValue: boolean) { private argSeparator(type: GraphQLInputType, hasDefaultValue: boolean) {
if (hasDefaultValue || isNonNullType(type)) { if (hasDefaultValue || isNonNullType(type)) {
return ':' return ':'
} }
@ -730,18 +884,25 @@ export class TypegenPrinter {
return '?:' return '?:'
} }
argTypeRepresentation(arg: GraphQLInputType): string { private argTypeRepresentation(arg: GraphQLInputType): string {
const argType = this.argTypeArr(arg) const argType = this.argTypeArr(arg)
const useReadonlyArrayForInputs = !!this.typegenInfo.useReadonlyArrayForInputs
function combine(item: any[]): string { function combine(item: any[]): string {
if (item.length === 1) { if (item.length === 1) {
if (Array.isArray(item[0])) { if (Array.isArray(item[0])) {
const toPrint = combine(item[0]) const toPrint = combine(item[0])
if (useReadonlyArrayForInputs) {
return `ReadonlyArray<${toPrint}>`
}
return toPrint.indexOf('null') === -1 ? `${toPrint}[]` : `Array<${toPrint}>` return toPrint.indexOf('null') === -1 ? `${toPrint}[]` : `Array<${toPrint}>`
} }
return item[0] return item[0]
} }
if (Array.isArray(item[1])) { if (Array.isArray(item[1])) {
const toPrint = combine(item[1]) const toPrint = combine(item[1])
if (useReadonlyArrayForInputs) {
return `ReadonlyArray<${toPrint}> | null`
}
return toPrint.indexOf('null') === -1 ? `${toPrint}[] | null` : `Array<${toPrint}> | null` return toPrint.indexOf('null') === -1 ? `${toPrint}[] | null` : `Array<${toPrint}> | null`
} }
return `${item[1]} | null` return `${item[1]} | null`
@ -749,7 +910,7 @@ export class TypegenPrinter {
return `${combine(argType)}; // ${arg}` return `${combine(argType)}; // ${arg}`
} }
argTypeArr(arg: GraphQLInputType): any[] { private argTypeArr(arg: GraphQLInputType): any[] {
const typing = [] const typing = []
if (isNonNullType(arg)) { if (isNonNullType(arg)) {
arg = arg.ofType arg = arg.ofType
@ -761,21 +922,29 @@ export class TypegenPrinter {
} else if (isScalarType(arg)) { } else if (isScalarType(arg)) {
typing.push(this.printScalar(arg)) typing.push(this.printScalar(arg))
} else if (isEnumType(arg)) { } else if (isEnumType(arg)) {
typing.push(`NexusGenEnums['${arg.name}']`) if (this.typegenInfo.declareInputs) {
typing.push(arg.name)
} else {
typing.push(`NexusGenEnums['${arg.name}']`)
}
} else if (isInputObjectType(arg)) { } else if (isInputObjectType(arg)) {
typing.push(`NexusGenInputs['${arg.name}']`) if (this.typegenInfo.declareInputs) {
typing.push(arg.name)
} else {
typing.push(`NexusGenInputs['${arg.name}']`)
}
} }
return typing return typing
} }
printTypeInterface(interfaceName: string, typeMapping: TypeMapping) { private printTypeInterface(interfaceName: string, typeMapping: TypeMapping) {
return [`export interface ${interfaceName} {`] return [`export interface ${interfaceName} {`]
.concat(mapObj(typeMapping, (val, key) => ` ${key}: ${val}`)) .concat(mapObj(typeMapping, (val, key) => ` ${key}: ${val}`))
.concat('}') .concat('}')
.join('\n') .join('\n')
} }
printRootTypeFieldInterface(interfaceName: string, typeMapping: RootTypeMapping) { private printRootTypeFieldInterface(interfaceName: string, typeMapping: RootTypeMapping) {
return [`export interface ${interfaceName} {`] return [`export interface ${interfaceName} {`]
.concat( .concat(
mapObj(typeMapping, (val, key) => { mapObj(typeMapping, (val, key) => {
@ -792,18 +961,26 @@ export class TypegenPrinter {
.join('\n') .join('\n')
} }
printTypeFieldInterface(interfaceName: string, typeMapping: TypeFieldMapping, source: string) { private printTypeFieldInterface(interfaceName: string, typeMapping: TypeFieldMapping, source: string) {
return [`export interface ${interfaceName} {`] return [`export interface ${interfaceName} {`]
.concat(mapObj(typeMapping, this.printObj(' ', source))) .concat(mapObj(typeMapping, this.printObj(' ', source)))
.concat('}') .concat('}')
.join('\n') .join('\n')
} }
printArgTypeFieldInterface(typeMapping: Record<string, TypeFieldMapping>) { private printArgTypeFieldInterface(typeMapping: Record<string, TypeFieldMapping>) {
return [`export interface NexusGenArgTypes {`] return [`export interface NexusGenArgTypes {`]
.concat( .concat(
mapObj(typeMapping, (val, key) => { mapObj(typeMapping, (val, typeName) => {
return [` ${key}: {`] if (this.typegenInfo.declareInputs) {
return [` ${typeName}: {`]
.concat(
mapObj(val, (_, fieldName) => ` ${fieldName}: ${this.getArgsName(typeName, fieldName)}`)
)
.concat(' }')
.join('\n')
}
return [` ${typeName}: {`]
.concat(mapObj(val, this.printObj(' ', 'args'))) .concat(mapObj(val, this.printObj(' ', 'args')))
.concat(' }') .concat(' }')
.join('\n') .join('\n')
@ -813,25 +990,26 @@ export class TypegenPrinter {
.join('\n') .join('\n')
} }
printObj = (space: string, source: string) => (val: Record<string, [string, string]>, key: string) => { private printObj =
return [`${space}${key}: { // ${source}`] (space: string, source: string) => (val: Record<string, [string, string]>, key: string) => {
.concat( return [`${space}${key}: { // ${source}`]
mapObj(val, (v2, k2) => { .concat(
return `${space} ${k2}${v2[0]} ${v2[1]}` mapObj(val, (v2, k2) => {
}) return `${space} ${k2}${v2[0]} ${v2[1]}`
) })
.concat(`${space}}`) )
.join('\n') .concat(`${space}}`)
} .join('\n')
}
printScalar(type: GraphQLScalarType) { private printScalar(type: GraphQLScalarType) {
if (isSpecifiedScalarType(type)) { if (isSpecifiedScalarType(type)) {
return this.resolveSourceType(type.name) ?? SpecifiedScalars[type.name as SpecifiedScalarNames] return this.resolveSourceType(type.name) ?? SpecifiedScalars[type.name as SpecifiedScalarNames]
} }
return `NexusGenScalars['${type.name}']` return `NexusGenScalars['${type.name}']`
} }
printPlugins() { private printPlugins() {
const pluginFieldExt: string[] = [ const pluginFieldExt: string[] = [
` interface NexusGenPluginFieldConfig<TypeName extends string, FieldName extends string> {`, ` interface NexusGenPluginFieldConfig<TypeName extends string, FieldName extends string> {`,
] ]
@ -880,7 +1058,7 @@ export class TypegenPrinter {
].join('\n') ].join('\n')
} }
printType(strLike: StringLike | StringLike[]): string { private printType(strLike: StringLike | StringLike[]): string {
if (Array.isArray(strLike)) { if (Array.isArray(strLike)) {
return strLike.map((s) => this.printType(s)).join('\n') return strLike.map((s) => this.printType(s)).join('\n')
} }
@ -897,7 +1075,7 @@ export class TypegenPrinter {
return strLike return strLike
} }
addImport(i: PrintedGenTypingImport) { private addImport(i: PrintedGenTypingImport) {
/* istanbul ignore if */ /* istanbul ignore if */
if (!isNexusPrintedGenTypingImport(i)) { if (!isNexusPrintedGenTypingImport(i)) {
console.warn(`Expected printedGenTypingImport, saw ${i}`) console.warn(`Expected printedGenTypingImport, saw ${i}`)

View File

@ -100,20 +100,20 @@ export type FieldResolver<TypeName extends string, FieldName extends string> = (
source: SourceValue<TypeName>, source: SourceValue<TypeName>,
/** /**
* If you have defined arguments on this field then this parameter will contain any arguments passed by the * If you have defined arguments on this field then this parameter will contain any arguments passed by the
* client. If you specified default values for any arguments and the client did not explicitly pass _any_ * client. If you specified default values for any arguments and the client did not explicitly pass *any*
* value (including null) for those arguments then you will see the defaults here. * value (including null) for those arguments then you will see the defaults here.
* *
* Note that thanks to [Nexus' reflection system](https://nxs.li/guides/reflection) this parameter's type * Note that thanks to [Nexus' reflection system](https://nxs.li/guides/reflection) this parameter's type
* will always be type safe. * will always be type safe.
*/ */
args: ArgsValue<TypeName, FieldName>, args: ArgsValue<TypeName, FieldName>,
/** /**
* The context data for this request. * The context data for this request.
* *
* The context data is typically a singleton scoped to the lifecycle of the request. This means created at * The context data is typically a singleton scoped to the lifecycle of the request. This means created at
* the beginning of a request and then passed to all the resolvers that execute while resolving the request. * the beginning of a request and then passed to all the resolvers that execute while resolving the request.
* It is often used to store information like the current user making the request. Nexus is not responsible * It is often used to store information like the current user making the request. Nexus is not responsible
* for this however. That is typically something you'll do with e.g. [Mercurius](https://mercurius.dev) or * for this however. That is typically something you'll do with e.g. [Mercurius](https://mercurius.dev) or
* [Apollo Server](https://apollographql.com/docs/apollo-server/api/apollo-server). * [Apollo Server](https://apollographql.com/docs/apollo-server/api/apollo-server).
* *
* Note that the type here will be whatever you have specified for "contextType" in your makeSchema configuration. * Note that the type here will be whatever you have specified for "contextType" in your makeSchema configuration.
@ -155,6 +155,8 @@ export type GenTypesShapeKeys =
| 'inputTypes' | 'inputTypes'
| 'rootTypes' | 'rootTypes'
| 'inputTypeShapes' | 'inputTypeShapes'
| 'directives'
| 'directiveArgs'
| 'argTypes' | 'argTypes'
| 'fieldTypes' | 'fieldTypes'
| 'fieldTypeNames' | 'fieldTypeNames'
@ -179,7 +181,7 @@ export type GenTypesShapeKeys =
export type GenTypesShape = Record<GenTypesShapeKeys, any> export type GenTypesShape = Record<GenTypesShapeKeys, any>
export type GetGen<K extends GenTypesShapeKeys, Fallback = any> = NexusGen extends infer GenTypes export type GetGen<K extends GenTypesShapeKeys, Fallback = any> = NexusGen extends infer GenTypes
? GenTypes extends GenTypesShape ? K extends keyof GenTypes
? GenTypes[K] ? GenTypes[K]
: Fallback : Fallback
: Fallback : Fallback
@ -202,10 +204,8 @@ export type GetGen3<
: Fallback : Fallback
export type HasGen<K extends GenTypesShapeKeys> = NexusGen extends infer GenTypes export type HasGen<K extends GenTypesShapeKeys> = NexusGen extends infer GenTypes
? GenTypes extends GenTypesShape ? K extends keyof GenTypes
? K extends keyof GenTypes ? true
? true
: false
: false : false
: false : false
@ -213,11 +213,9 @@ export type HasGen2<
K extends GenTypesShapeKeys, K extends GenTypesShapeKeys,
K2 extends Extract<keyof GenTypesShape[K], string> K2 extends Extract<keyof GenTypesShape[K], string>
> = NexusGen extends infer GenTypes > = NexusGen extends infer GenTypes
? GenTypes extends GenTypesShape ? K extends keyof GenTypes
? K extends keyof GenTypes ? K2 extends keyof GenTypes[K]
? K2 extends keyof GenTypes[K] ? true
? true
: false
: false : false
: false : false
: false : false
@ -227,12 +225,10 @@ export type HasGen3<
K2 extends Extract<keyof GenTypesShape[K], string>, K2 extends Extract<keyof GenTypesShape[K], string>,
K3 extends Extract<keyof GenTypesShape[K][K2], string> K3 extends Extract<keyof GenTypesShape[K][K2], string>
> = NexusGen extends infer GenTypes > = NexusGen extends infer GenTypes
? GenTypes extends GenTypesShape ? K extends keyof GenTypes
? K extends keyof GenTypes ? K2 extends keyof GenTypes[K]
? K2 extends keyof GenTypes[K] ? K3 extends keyof GenTypes[K][K2]
? K3 extends keyof GenTypes[K][K2] ? true
? true
: false
: false : false
: false : false
: false : false

View File

@ -1,51 +1,75 @@
import * as path from 'path'
import type { BuilderConfigInput } from './builder' import type { BuilderConfigInput } from './builder'
import type { ConfiguredTypegen } from './core'
import { nodeImports } from './node'
import type { TypegenMetadataConfig } from './typegenMetadata' import type { TypegenMetadataConfig } from './typegenMetadata'
import { assertAbsolutePath, getOwnPackage, isProductionStage } from './utils' import { assertAbsolutePath, getOwnPackage, isProductionStage } from './utils'
/** Normalizes the builder config into the config we need for typegen */ /** Normalizes the builder config into the config we need for typegen */
export function resolveTypegenConfig(config: BuilderConfigInput): TypegenMetadataConfig { export function resolveTypegenConfig(config: BuilderConfigInput): TypegenMetadataConfig {
const { const { outputs, shouldGenerateArtifacts = defaultShouldGenerateArtifacts(), ...rest } = config
outputs,
shouldGenerateArtifacts = Boolean(!process.env.NODE_ENV || process.env.NODE_ENV !== 'production'),
...rest
} = config
const defaultSDLFilePath = path.join(process.cwd(), 'schema.graphql') function getOutputPaths() {
const defaultSDLFilePath = nodeImports().path.join(process.cwd(), 'schema.graphql')
let typegenFilePath: string | null = null let typegenFilePath: ConfiguredTypegen | null = null
let sdlFilePath: string | null = null let sdlFilePath: string | null = null
if (outputs === undefined) { if (outputs === undefined) {
if (isProductionStage()) { if (isProductionStage()) {
sdlFilePath = defaultSDLFilePath
}
} else if (outputs === true) {
sdlFilePath = defaultSDLFilePath sdlFilePath = defaultSDLFilePath
} else if (typeof outputs === 'object') {
if (outputs.schema === true) {
sdlFilePath = defaultSDLFilePath
} else if (typeof outputs.schema === 'string') {
sdlFilePath = assertAbsolutePath(outputs.schema, 'outputs.schema')
} else if (outputs.schema === undefined && isProductionStage()) {
}
// handle typegen configuration
if (typeof outputs.typegen === 'string') {
typegenFilePath = {
outputPath: assertAbsolutePath(outputs.typegen, 'outputs.typegen'),
}
} else if (typeof outputs.typegen === 'object') {
typegenFilePath = {
...outputs.typegen,
outputPath: assertAbsolutePath(outputs.typegen.outputPath, 'outputs.typegen.outputPath'),
} as ConfiguredTypegen
if (outputs.typegen.globalsPath) {
typegenFilePath.globalsPath = assertAbsolutePath(
outputs.typegen.globalsPath,
'outputs.typegen.globalsPath'
)
}
}
} else if (outputs !== false) {
console.warn(
`You should specify a configuration value for outputs in Nexus' makeSchema. ` +
`Provide one to remove this warning.`
)
} }
} else if (outputs === true) { return {
sdlFilePath = defaultSDLFilePath typegenFilePath,
} else if (typeof outputs === 'object') { sdlFilePath,
if (outputs.schema === true) {
sdlFilePath = defaultSDLFilePath
} else if (typeof outputs.schema === 'string') {
sdlFilePath = assertAbsolutePath(outputs.schema, 'outputs.schema')
} else if (outputs.schema === undefined && isProductionStage()) {
} }
// handle typegen configuration
if (typeof outputs.typegen === 'string') {
typegenFilePath = assertAbsolutePath(outputs.typegen, 'outputs.typegen')
}
} else if (outputs !== false) {
console.warn(
`You should specify a configuration value for outputs in Nexus' makeSchema. ` +
`Provide one to remove this warning.`
)
} }
return { return {
...rest, ...rest,
nexusSchemaImportId: getOwnPackage().name, nexusSchemaImportId: getOwnPackage().name,
outputs: { outputs: {
typegen: shouldGenerateArtifacts ? typegenFilePath : null, typegen: shouldGenerateArtifacts ? getOutputPaths().typegenFilePath : null,
schema: shouldGenerateArtifacts ? sdlFilePath : null, schema: shouldGenerateArtifacts ? getOutputPaths().sdlFilePath : null,
}, },
} }
} }
function defaultShouldGenerateArtifacts() {
return Boolean(
typeof process === 'object' &&
typeof process.cwd === 'function' &&
(!process.env.NODE_ENV || process.env.NODE_ENV !== 'production')
)
}

View File

@ -1,4 +1,3 @@
import * as fs from 'fs'
import { import {
GraphQLEnumType, GraphQLEnumType,
GraphQLInputObjectType, GraphQLInputObjectType,
@ -22,17 +21,18 @@ import {
isWrappingType, isWrappingType,
specifiedScalarTypes, specifiedScalarTypes,
} from 'graphql' } from 'graphql'
import * as Path from 'path'
import { decorateType } from './definitions/decorateType' import { decorateType } from './definitions/decorateType'
import { isNexusMetaType, NexusMetaType, resolveNexusMetaType } from './definitions/nexusMeta'
import { import {
AllNexusArgsDefs, AllNexusArgsDefs,
AllNexusNamedTypeDefs, AllNexusNamedTypeDefs,
AllNexusTypeDefs, AllNexusTypeDefs,
isNexusWrappingType, isNexusWrappingType,
isNexusArgDef, isNexusArgDef,
AllNexusNamedInputTypeDefs, AllNamedInputTypeDefs,
} from './definitions/wrapping' } from './definitions/wrapping'
import { import {
Maybe,
MissingType, MissingType,
NexusFeatures, NexusFeatures,
NexusGraphQLSchema, NexusGraphQLSchema,
@ -40,6 +40,7 @@ import {
TypingImport, TypingImport,
withNexusSymbol, withNexusSymbol,
} from './definitions/_types' } from './definitions/_types'
import { nodeImports } from './node'
export const isInterfaceField = (type: GraphQLObjectType, fieldName: string) => { export const isInterfaceField = (type: GraphQLObjectType, fieldName: string) => {
return type.getInterfaces().some((i) => Boolean(i.getFields()[fieldName])) return type.getInterfaces().some((i) => Boolean(i.getFields()[fieldName]))
@ -148,7 +149,7 @@ export function eachObj<T>(obj: Record<string, T>, iter: (val: T, key: string, i
export const isObject = (obj: any): boolean => obj !== null && typeof obj === 'object' export const isObject = (obj: any): boolean => obj !== null && typeof obj === 'object'
export const assertAbsolutePath = (pathName: string, property: string) => { export const assertAbsolutePath = (pathName: string, property: string) => {
if (!Path.isAbsolute(pathName)) { if (!nodeImports().path.isAbsolute(pathName)) {
throw new Error(`Expected path for "${property}" to be an absolute path, saw "${pathName}"`) throw new Error(`Expected path for "${property}" to be an absolute path, saw "${pathName}"`)
} }
return pathName return pathName
@ -176,7 +177,7 @@ export function groupTypes(schema: GraphQLSchema) {
Object.keys(schemaTypeMap) Object.keys(schemaTypeMap)
.sort() .sort()
.forEach((typeName) => { .forEach((typeName) => {
if (typeName.indexOf('__') === 0) { if (typeName.startsWith('__')) {
return return
} }
const type = schema.getType(typeName) const type = schema.getType(typeName)
@ -219,7 +220,7 @@ export function isPromiseLike(value: any): value is PromiseLike<any> {
export const typeScriptFileExtension = /(\.d)?\.ts$/ export const typeScriptFileExtension = /(\.d)?\.ts$/
function makeRelativePathExplicitlyRelative(path: string) { function makeRelativePathExplicitlyRelative(path: string) {
if (Path.isAbsolute(path)) return path if (nodeImports().path.isAbsolute(path)) return path
if (path.startsWith('./')) return path if (path.startsWith('./')) return path
return `./${path}` return `./${path}`
} }
@ -241,6 +242,7 @@ export function formatPathForModuleImport(path: string) {
} }
export function relativePathTo(absolutePath: string, fromPath: string): string { export function relativePathTo(absolutePath: string, fromPath: string): string {
const Path = nodeImports().path
const filename = Path.basename(absolutePath) const filename = Path.basename(absolutePath)
const relative = Path.relative(Path.dirname(fromPath), Path.dirname(absolutePath)) const relative = Path.relative(Path.dirname(fromPath), Path.dirname(absolutePath))
return formatPathForModuleImport(Path.join(relative, filename)) return formatPathForModuleImport(Path.join(relative, filename))
@ -265,7 +267,7 @@ export interface PrintedGenTypingConfig {
name: string name: string
optional: boolean optional: boolean
type: string type: string
description?: string description?: Maybe<string>
imports?: PrintedGenTypingImport[] imports?: PrintedGenTypingImport[]
} }
@ -475,20 +477,18 @@ export function casesHandled(x: never): never {
throw new Error(`A case was not handled for value: "${x}"`) throw new Error(`A case was not handled for value: "${x}"`)
} }
/** Quickly log objects */
export function dump(x: any) {
console.log(require('util').inspect(x, { depth: null }))
}
function isNodeModule(path: string) { function isNodeModule(path: string) {
// Avoid treating absolute windows paths as Node packages e.g. D:/a/b/c // Avoid treating absolute windows paths as Node packages e.g. D:/a/b/c
return !Path.isAbsolute(path) && /^([A-z0-9@])/.test(path) return !nodeImports().path.isAbsolute(path) && /^([A-z0-9@])/.test(path)
} }
export function resolveImportPath(rootType: TypingImport, typeName: string, outputPath: string) { export function resolveImportPath(rootType: TypingImport, typeName: string, outputPath: string) {
const rootTypePath = rootType.module const rootTypePath = rootType.module
if (typeof rootTypePath !== 'string' || (!Path.isAbsolute(rootTypePath) && !isNodeModule(rootTypePath))) { if (
typeof rootTypePath !== 'string' ||
(!nodeImports().path.isAbsolute(rootTypePath) && !isNodeModule(rootTypePath))
) {
throw new Error( throw new Error(
`Expected an absolute path or Node package for the root typing path of the type "${typeName}", saw "${rootTypePath}"` `Expected an absolute path or Node package for the root typing path of the type "${typeName}", saw "${rootTypePath}"`
) )
@ -500,7 +500,7 @@ export function resolveImportPath(rootType: TypingImport, typeName: string, outp
} catch (e) { } catch (e) {
throw new Error(`Module "${rootTypePath}" for the type "${typeName}" does not exist`) throw new Error(`Module "${rootTypePath}" for the type "${typeName}" does not exist`)
} }
} else if (!fs.existsSync(rootTypePath)) { } else if (!nodeImports().fs.existsSync(rootTypePath)) {
throw new Error(`Root typing path "${rootTypePath}" for the type "${typeName}" does not exist`) throw new Error(`Root typing path "${rootTypePath}" for the type "${typeName}" does not exist`)
} }
@ -508,7 +508,7 @@ export function resolveImportPath(rootType: TypingImport, typeName: string, outp
return rootTypePath return rootTypePath
} }
if (Path.isAbsolute(rootTypePath)) { if (nodeImports().path.isAbsolute(rootTypePath)) {
return relativePathTo(rootTypePath, outputPath) return relativePathTo(rootTypePath, outputPath)
} }
@ -516,7 +516,7 @@ export function resolveImportPath(rootType: TypingImport, typeName: string, outp
} }
/** Given the right hand side of an arg definition, returns the underlying "named type" for us to add to the builder */ /** Given the right hand side of an arg definition, returns the underlying "named type" for us to add to the builder */
export function getArgNamedType(argDef: AllNexusArgsDefs | string): AllNexusNamedInputTypeDefs | string { export function getArgNamedType(argDef: AllNexusArgsDefs | string): AllNamedInputTypeDefs | string {
let finalValue = argDef let finalValue = argDef
if (typeof finalValue === 'string') { if (typeof finalValue === 'string') {
return finalValue return finalValue
@ -534,21 +534,24 @@ export function getArgNamedType(argDef: AllNexusArgsDefs | string): AllNexusName
} }
export function getNexusNamedType( export function getNexusNamedType(
type: AllNexusTypeDefs | GraphQLType | string type: AllNexusTypeDefs | NexusMetaType | GraphQLType | string
): AllNexusNamedTypeDefs | GraphQLNamedType | string { ): AllNexusNamedTypeDefs | GraphQLNamedType | string {
if (typeof type === 'string') { if (typeof type === 'string') {
return type return type
} }
let namedType = type let namedType = type
while (isNexusWrappingType(namedType) || isWrappingType(namedType)) { while (isNexusWrappingType(namedType) || isWrappingType(namedType) || isNexusMetaType(namedType)) {
if (isNexusWrappingType(namedType)) { if (isNexusWrappingType(namedType)) {
namedType = namedType.ofNexusType namedType = namedType.ofNexusType
} }
if (isWrappingType(namedType)) { if (isWrappingType(namedType)) {
namedType = namedType.ofType namedType = namedType.ofType
} }
if (isNexusMetaType(namedType)) {
namedType = resolveNexusMetaType(namedType)
}
} }
return namedType return namedType as AllNexusNamedTypeDefs | GraphQLNamedType | string
} }
/** Assertion utility with nexus-aware feedback for users. */ /** Assertion utility with nexus-aware feedback for users. */
@ -593,10 +596,48 @@ export function graphql15InterfaceConfig<T extends GraphQLInterfaceTypeConfig<an
} }
export function graphql15InterfaceType<T extends GraphQLInterfaceType>( export function graphql15InterfaceType<T extends GraphQLInterfaceType>(
type: T & { getInterfaces?: () => GraphQLInterfaceType[] } type: T & { getInterfaces?: () => ReadonlyArray<GraphQLInterfaceType> }
): T & { getInterfaces(): GraphQLInterfaceType[] } { ): T & { getInterfaces(): ReadonlyArray<GraphQLInterfaceType> } {
if (typeof type.getInterfaces !== 'function') { if (typeof type.getInterfaces !== 'function') {
type.getInterfaces = () => [] type.getInterfaces = () => []
} }
return type as T & { getInterfaces(): GraphQLInterfaceType[] } return type as T & { getInterfaces(): ReadonlyArray<GraphQLInterfaceType> }
}
/** @internal */
export function unpack<T extends object>(val: T | (() => T)): T {
if (val instanceof Function) {
return val()
}
return val
}
/**
* A specially typed version of `Array.isArray` to work around [this
* issue](https://github.com/microsoft/TypeScript/issues/17002).
*/
export function isArray<T>(
arg: T | {}
): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[] {
return Array.isArray(arg)
}
export const ownProp = {
has<O extends object, K extends keyof O>(obj: O, key: K): boolean {
return Boolean(Object.getOwnPropertyDescriptor(obj, key))
},
set<O extends object, K extends keyof O>(obj: O, key: K, value: O[K]): O[K] {
Object.defineProperty(obj, key, { value })
return value
},
get<O extends object, K extends keyof O>(obj: O, key: K): O[K] | undefined {
return Object.getOwnPropertyDescriptor(obj, key)?.value
},
}
export function result<T>(val: T | (() => T)): T {
if (val instanceof Function) {
return val()
}
return val as T
} }

View File

@ -1,9 +1,9 @@
/// <reference path="../_setup.ts" /> /// <reference path="../_setup.ts" />
import { join, relative } from 'path' import { join, relative } from 'path'
import { core } from '../../src' import { core } from '../../src'
import { BuilderConfigInput } from '../../src/core' import type { BuilderConfigInput } from '../../src/core'
const { generateSchema, typegenFormatPrettier } = core const { generateSchema } = core
type HookSettings = { type HookSettings = {
rootDir: string rootDir: string
@ -29,12 +29,8 @@ export async function generateTypegen(settings: HookSettings) {
}, },
shouldGenerateArtifacts: true, shouldGenerateArtifacts: true,
plugins: plugins || [], plugins: plugins || [],
async formatTypegen(source, type) { prettierConfig: require.resolve('../../.prettierrc'),
const prettierConfigPath = require.resolve('../../.prettierrc') formatTypegen: (content) => content.replace('from "nexus"', `from '${importPath}'`),
const content = await typegenFormatPrettier(prettierConfigPath)(source, type)
return content.replace("'nexus'", `'${importPath}'`)
},
features: { features: {
abstractTypeStrategies: { abstractTypeStrategies: {
resolveType: true, resolveType: true,
@ -59,11 +55,11 @@ type Settings = {
/** /**
* Test that the given app can be built by TypeScript without any type errors. * Test that the given app can be built by TypeScript without any type errors.
* *
* - Nexus generateSchema will be run before TypeScript to ensure typegen is present. - By default looks for * - Nexus generateSchema will be run before TypeScript to ensure typegen is present. - By default looks for an
* an `__app.ts` entrypoint - All entrypoint exports are expected to be Nexus type definitions - Except the * `__app.ts` entrypoint - All entrypoint exports are expected to be Nexus type definitions - Except the
* optional export name "plugins" which is treated as an array of plugins for makeSchema - Outputs a * optional export name "plugins" which is treated as an array of plugins for makeSchema - Outputs a
* `__typegen.ts` typegen module - You must import the typegen module into your entrypoint module - If you * `__typegen.ts` typegen module - You must import the typegen module into your entrypoint module - If you
* provide a `tsconfig.json` file in the root dir it will be used. * provide a `tsconfig.json` file in the root dir it will be used.
*/ */
export function testApp(settings: Settings & HookSettings) { export function testApp(settings: Settings & HookSettings) {
const name = settings?.name ?? 'app' const name = settings?.name ?? 'app'

View File

@ -90,6 +90,11 @@ export type NexusGenScalarNames = keyof NexusGenScalars;
export type NexusGenUnionNames = never; export type NexusGenUnionNames = never;
export type NexusGenDirectives = never
export interface NexusGenDirectiveArgs {
}
export type NexusGenObjectsUsingAbstractStrategyIsTypeOf = never; export type NexusGenObjectsUsingAbstractStrategyIsTypeOf = never;
export type NexusGenAbstractsUsingStrategyResolveType = never; export type NexusGenAbstractsUsingStrategyResolveType = never;
@ -105,6 +110,8 @@ export type NexusGenFeaturesConfig = {
export interface NexusGenTypes { export interface NexusGenTypes {
context: any; context: any;
inputTypes: NexusGenInputs; inputTypes: NexusGenInputs;
directives: NexusGenDirectives;
directiveArgs: NexusGenDirectiveArgs;
rootTypes: NexusGenRootTypes; rootTypes: NexusGenRootTypes;
inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars; inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars;
argTypes: NexusGenArgTypes; argTypes: NexusGenArgTypes;

Some files were not shown because too many files have changed in this diff Show More