├── .eslintignore ├── plugins ├── #-sample │ ├── src │ │ └── index.ts │ ├── README.md │ ├── tests │ │ └── index.spec.ts │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── jest.config.js │ ├── package.json │ └── CHANGELOG.md ├── datetime │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── jest.config.js │ ├── LICENSE │ ├── package.json │ ├── tests │ │ ├── index.spec.ts │ │ └── __snapshots__ │ │ │ └── index.spec.ts.snap │ ├── README.md │ ├── CHANGELOG.md │ └── src │ │ └── index.ts ├── relay-global-id │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── jest.config.js │ ├── tests │ │ ├── __snapshots__ │ │ │ └── index.spec.ts.snap │ │ └── index.spec.ts │ ├── README.md │ ├── LICENSE │ ├── package.json │ ├── CHANGELOG.md │ └── src │ │ └── index.ts ├── relay-mutation │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── jest.config.js │ ├── tests │ │ ├── __snapshots__ │ │ │ └── index.spec.ts.snap │ │ └── index.spec.ts │ ├── package.json │ ├── README.md │ ├── CHANGELOG.md │ └── src │ │ └── index.ts ├── yup-validation │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── jest.config.js │ ├── CHANGELOG.md │ ├── tests │ │ ├── __snapshots__ │ │ │ └── index.spec.ts.snap │ │ └── index.spec.ts │ ├── package.json │ ├── README.md │ └── src │ │ └── index.ts ├── field-authentication │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── jest.config.js │ ├── LICENSE │ ├── package.json │ ├── README.md │ ├── CHANGELOG.md │ ├── tests │ │ ├── __snapshots__ │ │ │ └── index.spec.ts.snap │ │ └── index.spec.ts │ └── src │ │ └── index.ts ├── relay-node-interface │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── jest.config.js │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── CHANGELOG.md │ ├── tests │ │ ├── __snapshots__ │ │ │ └── index.spec.ts.snap │ │ └── index.spec.ts │ └── src │ │ └── index.ts └── elastic-apm-instrumentation │ ├── tsconfig.json │ ├── tsconfig.cjs.json │ ├── tsconfig.esm.json │ ├── tests │ └── index.spec.ts │ ├── jest.config.js │ ├── README.md │ ├── LICENSE │ ├── package.json │ ├── CHANGELOG.md │ └── src │ └── index.ts ├── jest.config.js ├── .gitignore ├── .prettierrc ├── lerna.json ├── .eslintrc.js ├── tsconfig.json ├── codecov.yml ├── LICENSE ├── package.json ├── .github └── workflows │ ├── test-plugin-sample.yaml │ ├── test-plugin-datetime.yaml │ ├── test-plugin-relay-mutation.yaml │ ├── test-plugin-relay-global-id.yaml │ ├── test-plugin-field-authentication.yaml │ └── test-plugin-relay-node-interface.yaml └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | dist-esm/ 3 | -------------------------------------------------------------------------------- /plugins/#-sample/src/index.ts: -------------------------------------------------------------------------------- 1 | export function hello() { 2 | return 'Hello World' 3 | } 4 | -------------------------------------------------------------------------------- /plugins/#-sample/README.md: -------------------------------------------------------------------------------- 1 | # Sample 2 | 3 | This is just an empty project to be used as base. 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: ['/plugins/*/jest.config.js'], 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/ 2 | **/dist/ 3 | **/dist-esm/ 4 | **/coverage/ 5 | **/generated/ 6 | **/yarn-error.log 7 | 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["plugins/*"], 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "version": "independent" 6 | } 7 | -------------------------------------------------------------------------------- /plugins/datetime/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": ["./src"] 6 | } 7 | -------------------------------------------------------------------------------- /plugins/#-sample/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { hello } from '../src/index' 2 | 3 | describe('sample', () => { 4 | it('hello', () => { 5 | expect(hello()).toEqual('Hello World') 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /plugins/relay-global-id/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": ["./src"] 6 | } 7 | -------------------------------------------------------------------------------- /plugins/relay-mutation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": ["./src"] 6 | } 7 | -------------------------------------------------------------------------------- /plugins/yup-validation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": ["./src"] 6 | } 7 | -------------------------------------------------------------------------------- /plugins/field-authentication/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": ["./src"] 6 | } 7 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": ["./src"] 6 | } 7 | -------------------------------------------------------------------------------- /plugins/#-sample/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": ["./src", "tests/index.spec.ts"] 6 | } 7 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": {}, 5 | "include": ["./src"] 6 | } 7 | -------------------------------------------------------------------------------- /plugins/#-sample/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plugins/datetime/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plugins/relay-global-id/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plugins/relay-mutation/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plugins/yup-validation/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plugins/field-authentication/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "module": "CommonJS", 6 | "outDir": "dist" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /plugins/#-sample/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "module": "ES2020", 7 | "outDir": "dist-esm" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugins/datetime/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "module": "ES2020", 7 | "outDir": "dist-esm" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugins/relay-mutation/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "module": "ES2020", 7 | "outDir": "dist-esm" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugins/yup-validation/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "module": "ES2020", 7 | "outDir": "dist-esm" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugins/relay-global-id/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "module": "ES2020", 7 | "outDir": "dist-esm" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@jcm/eslint-config'], 3 | parser: '@typescript-eslint/parser', 4 | parserOptions: { 5 | ecmaVersion: 2019, 6 | sourceType: 'module', 7 | }, 8 | env: { 9 | browser: true, 10 | node: true, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /plugins/#-sample/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist-esm/', '/dist/'], 5 | globals: { 6 | skipBabel: true, 7 | }, 8 | coverageDirectory: './coverage', 9 | } 10 | -------------------------------------------------------------------------------- /plugins/datetime/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist-esm/', '/dist/'], 5 | globals: { 6 | skipBabel: true, 7 | }, 8 | coverageDirectory: './coverage', 9 | } 10 | -------------------------------------------------------------------------------- /plugins/field-authentication/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "module": "ES2020", 7 | "outDir": "dist-esm" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "module": "ES2020", 7 | "outDir": "dist-esm" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "moduleResolution": "node", 6 | "module": "ES2020", 7 | "outDir": "dist-esm" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugins/relay-global-id/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist-esm/', '/dist/'], 5 | globals: { 6 | skipBabel: true, 7 | }, 8 | coverageDirectory: './coverage', 9 | } 10 | -------------------------------------------------------------------------------- /plugins/relay-mutation/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist-esm/', '/dist/'], 5 | globals: { 6 | skipBabel: true, 7 | }, 8 | coverageDirectory: './coverage', 9 | } 10 | -------------------------------------------------------------------------------- /plugins/yup-validation/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist-esm/', '/dist/'], 5 | globals: { 6 | skipBabel: true, 7 | }, 8 | coverageDirectory: './coverage', 9 | } 10 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | describe('elasticApmInstrumentation', () => { 2 | describe('basic behavior', () => { 3 | // TODO(jonathan): add mocked tests later on 4 | it('should have tests', async () => { 5 | expect(1).toBe(1) 6 | }) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /plugins/field-authentication/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist-esm/', '/dist/'], 5 | globals: { 6 | skipBabel: true, 7 | }, 8 | coverageDirectory: './coverage', 9 | } 10 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist-esm/', '/dist/'], 5 | globals: { 6 | skipBabel: true, 7 | }, 8 | coverageDirectory: './coverage', 9 | } 10 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist-esm/', '/dist/'], 5 | globals: { 6 | skipBabel: true, 7 | }, 8 | coverageDirectory: './coverage', 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | // node v12 supports 100% of ES2019 6 | "target": "ES2019", 7 | "sourceMap": true, 8 | "allowJs": false, 9 | "moduleResolution": "node", 10 | "strict": true, 11 | "declaration": true, 12 | "skipLibCheck": true, 13 | "importHelpers": true, 14 | // "typeRoots": ["./node_modules/@types"], 15 | "noUnusedLocals": true 16 | }, 17 | "exclude": ["**/*.test.ts", "**/test.config.ts", "node_modules", "**/build/*", "**/*.d.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/README.md: -------------------------------------------------------------------------------- 1 | # @jcm/nexus-plugin-elastic-apm-instrumentation 2 | 3 |

4 | 5 | Patreon Logo 6 | 7 |
8 | 9 | Discord Logo 10 | 11 |

12 | 13 | This plugin will use the passed [`apmAgent`](https://github.com/elastic/apm-agent-nodejs) to capture spans for each GraphQL field resolver. 14 | -------------------------------------------------------------------------------- /plugins/yup-validation/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.1.1](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-yup-validation@0.1.0...@jcm/nexus-plugin-yup-validation@0.1.1) (2021-01-25) 7 | 8 | ### Bug Fixes 9 | 10 | - **yup-validation:** dist file was wrongly including the tests files ([92bc8db](https://github.com/JCMais/nexus-plugins/commit/92bc8dbb5d5a5c01ea30017efbc76d8a60c3d5ff)) 11 | 12 | # 0.1.0 (2021-01-24) 13 | 14 | ### Features 15 | 16 | - add yup-validation plugin ([c3f0c70](https://github.com/JCMais/nexus-plugins/commit/c3f0c703a71414e9a7b59ae5d0e7bf5edacf57fe)) 17 | -------------------------------------------------------------------------------- /plugins/relay-global-id/tests/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`relayGlobalIdPlugin basic behavior should resolve all fields correctly 1`] = ` 4 | Object { 5 | "data": Object { 6 | "me": Object { 7 | "id": "VXNlcjppZC12YWx1ZQ==", 8 | "id2": "VXNlcjppZFR3by12YWx1ZQ==", 9 | "id3": "VXNlcjppZFRocmVlLXZhbHVl", 10 | "id4": "VXNlcjppZDQtdmFsdWU=", 11 | "rawId": "id-value", 12 | "rawId3": "idThree-value", 13 | "rawIdFour": "id4-value", 14 | }, 15 | }, 16 | } 17 | `; 18 | 19 | exports[`relayGlobalIdPlugin basic behavior should work correctly 1`] = ` 20 | "type User { 21 | id: ID! 22 | rawId: ID! 23 | id2: ID! 24 | id3: ID! 25 | rawId3: ID! 26 | id4: ID! 27 | rawIdFour: ID! 28 | }" 29 | `; 30 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # coverage: 2 | # status: 3 | # project: 4 | # target: 90% 5 | # threshold: null 6 | # projectA: 7 | # flags: projectA 8 | # target: 90% 9 | # projectB: 10 | # target: 50% 11 | # flags: projectB 12 | flags: 13 | sample: 14 | paths: 15 | - plugins/#-sample 16 | carryforward: true 17 | datetime: 18 | paths: 19 | - plugins/datetime 20 | carryforward: true 21 | fieldauthentication: 22 | paths: 23 | - plugins/field-authentication 24 | carryforward: true 25 | relayglobalid: 26 | paths: 27 | - plugins/relay-global-id 28 | carryforward: true 29 | relaymutation: 30 | paths: 31 | - plugins/relay-mutation 32 | carryforward: true 33 | relaynodeinterface: 34 | paths: 35 | - plugins/relay-node-interface 36 | carryforward: true 37 | -------------------------------------------------------------------------------- /plugins/yup-validation/tests/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`yupValidation basic behavior should work correctly with default values 1`] = ` 4 | Object { 5 | "data": Object { 6 | "addUser": Object { 7 | "error": Object { 8 | "details": Array [ 9 | Object { 10 | "messages": Array [ 11 | "email must be at least 10 characters", 12 | "email is a required field", 13 | ], 14 | "path": Array [ 15 | "email", 16 | ], 17 | }, 18 | ], 19 | "message": "2 errors occurred", 20 | }, 21 | }, 22 | }, 23 | } 24 | `; 25 | 26 | exports[`yupValidation basic behavior should work with abortEarly = true 1`] = ` 27 | Object { 28 | "data": Object { 29 | "addUser": Object { 30 | "error": Object { 31 | "details": Array [], 32 | "message": "email must be at least 10 characters", 33 | }, 34 | }, 35 | }, 36 | } 37 | `; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020-present Jonathan Cardoso 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /plugins/relay-global-id/README.md: -------------------------------------------------------------------------------- 1 | # @jcm/nexus-plugin-relay-global-id 2 | 3 |

4 | 5 | Patreon Logo 6 | 7 |
8 | 9 | Discord Logo 10 | 11 |

12 | 13 | This plugin adds the field method `relayGlobalId(fieldName, fieldConfig)` to the Nexus Schema Builder, which can be used to create [Relay-compliant global IDs](https://relay.dev/docs/en/graphql-server-specification.html#object-identification). 14 | 15 | Sample usage: 16 | 17 | ```typescript 18 | const User = objectType({ 19 | name: 'User', 20 | definition(t) { 21 | // ... 22 | t.relayGlobalId('id') 23 | // ... 24 | }, 25 | }) 26 | ``` 27 | 28 | With the above code, the following schema will be generated: 29 | 30 | ```graphql 31 | type User { 32 | id: ID! 33 | rawId: ID! 34 | } 35 | # ... 36 | ``` 37 | -------------------------------------------------------------------------------- /plugins/datetime/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020-present Jonathan Cardoso 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /plugins/relay-global-id/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020-present Jonathan Cardoso 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /plugins/field-authentication/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020-present Jonathan Cardoso 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020-present Jonathan Cardoso 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2020-present Jonathan Cardoso 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /plugins/relay-mutation/tests/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`relayMutationPlugin basic behavior should resolve all fields correctly 1`] = ` 4 | Object { 5 | "data": Object { 6 | "add": Object { 7 | "result": 3, 8 | }, 9 | }, 10 | } 11 | `; 12 | 13 | exports[`relayMutationPlugin basic behavior should work correctly 1`] = ` 14 | "input AddInput { 15 | number1: Int! 16 | number2: Int! 17 | clientMutationId: String 18 | } 19 | 20 | type AddPayload { 21 | result: Int! 22 | clientMutationId: String 23 | } 24 | 25 | type Query { 26 | ok: Boolean! 27 | } 28 | 29 | type Mutation { 30 | add(input: AddInput!): AddPayload! 31 | } 32 | " 33 | `; 34 | 35 | exports[`relayMutationPlugin error handling should throw error caught on async mutateAndGetPayload call 1`] = ` 36 | Object { 37 | "data": null, 38 | "errors": Array [ 39 | [GraphQLError: That happened], 40 | ], 41 | } 42 | `; 43 | 44 | exports[`relayMutationPlugin error handling should throw error caught on sync mutateAndGetPayload call 1`] = ` 45 | Object { 46 | "data": null, 47 | "errors": Array [ 48 | [GraphQLError: That happened], 49 | ], 50 | } 51 | `; 52 | -------------------------------------------------------------------------------- /plugins/#-sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jcm/nexus-plugin-sample", 3 | "version": "0.1.0", 4 | "private": true, 5 | "description": "Sample package", 6 | "keywords": [], 7 | "homepage": "https://github.com/JCMais/nexus-plugins/blob/plugins/sample", 8 | "license": "MIT", 9 | "author": "Jonathan Cardoso ", 10 | "main": "dist", 11 | "module": "dist-esm", 12 | "types": "dist/index.d.ts", 13 | "files": [ 14 | "dist", 15 | "dist-esm", 16 | "src", 17 | "LICENSE.md", 18 | "README.md", 19 | "yarn.lock" 20 | ], 21 | "scripts": { 22 | "build:dist": "yarn -s clean:dist && tsc -p tsconfig.cjs.json --pretty && tsc -p tsconfig.esm.json --pretty", 23 | "clean:dist": "rimraf ./dist ./dist-esm", 24 | "lint": "eslint \"{src,tests}/**/*.ts\" -f eslint-formatter-friendly", 25 | "pre-commit": "tsc -p tsconfig.esm.json --pretty", 26 | "prepare": "echo \"do nothing\"", 27 | "prepublishOnly": "yarn build:dist", 28 | "test": "jest", 29 | "test:coverage": "jest --coverage", 30 | "test:coverage:watch": "jest --coverage --watch", 31 | "test:watch": "jest --watch" 32 | }, 33 | "peerDependencies": { 34 | "graphql": "^15", 35 | "nexus": "^1.0.0" 36 | }, 37 | "engines": { 38 | "node": ">= 12" 39 | }, 40 | "publishConfig": { 41 | "access": "public" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/README.md: -------------------------------------------------------------------------------- 1 | # @jcm/nexus-plugin-relay-node-interface 2 | 3 |

4 | 5 | Patreon Logo 6 | 7 |
8 | 9 | Discord Logo 10 | 11 |

12 | 13 | This plugin adds the following to the `GraphQL` schema generated by nexus: 14 | 15 | - `Node` interface 16 | - `node($id: ID!): Node` field on `Query` 17 | - `nodes($ids: [ID!]!): Node!` field on `Query` 18 | 19 | The plugin itself accepts a few configuration options: 20 | 21 | ### idFetcher [required] 22 | 23 | A function with signature `(idValue, ctx, info) => Object`, which should return the node for given `idValue` 24 | 25 | ### resolveType [required] 26 | 27 | A function with signature `(object) => string | GraphQLType`, which should return the GraphQL type for the given object. This is basically the resolveType of the `Node` interface created by the plugin. 28 | 29 | ### idParser 30 | 31 | A function with signature `(id) => any`. It will be called with the ID passed to the `node` / `nodes` fields, and their result will be used as the first argument passed to `idFetcher` above. By default this is set to `fromGlobalId` from `graphql-relay`. 32 | -------------------------------------------------------------------------------- /plugins/relay-mutation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jcm/nexus-plugin-relay-mutation", 3 | "version": "0.2.0", 4 | "description": "Adds relayMutation(field, config) method to the nexus/schema builder to create Relay compatible mutations", 5 | "keywords": [ 6 | "nexus", 7 | "plugin", 8 | "nexus-plugin", 9 | "graphql", 10 | "relay", 11 | "graphql-relay", 12 | "mutation" 13 | ], 14 | "homepage": "https://github.com/JCMais/nexus-plugins/blob/plugins/relay-mutation", 15 | "license": "MIT", 16 | "author": "Jonathan Cardoso ", 17 | "main": "dist", 18 | "module": "dist-esm", 19 | "types": "dist/index.d.ts", 20 | "files": [ 21 | "dist", 22 | "dist-esm", 23 | "src", 24 | "LICENSE.md", 25 | "README.md", 26 | "yarn.lock" 27 | ], 28 | "scripts": { 29 | "build:dist": "yarn -s clean:dist && tsc -p tsconfig.cjs.json --pretty && tsc -p tsconfig.esm.json --pretty", 30 | "clean:dist": "rimraf ./dist ./dist-esm", 31 | "lint": "eslint \"{src,tests}/**/*.ts\" -f eslint-formatter-friendly", 32 | "pre-commit": "tsc -p tsconfig.esm.json --pretty", 33 | "prepare": "echo \"do nothing\"", 34 | "prepublishOnly": "yarn build:dist", 35 | "test": "jest", 36 | "test:coverage": "jest --coverage", 37 | "test:coverage:watch": "jest --coverage --watch", 38 | "test:watch": "jest --watch" 39 | }, 40 | "peerDependencies": { 41 | "graphql": "^15", 42 | "nexus": "^1.0.0" 43 | }, 44 | "engines": { 45 | "node": ">= 12" 46 | }, 47 | "publishConfig": { 48 | "access": "public" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /plugins/yup-validation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jcm/nexus-plugin-yup-validation", 3 | "version": "0.1.1", 4 | "description": "Allows to use Yup schemas to validate GraphQL arguments", 5 | "keywords": [ 6 | "nexus", 7 | "plugin", 8 | "nexus-plugin", 9 | "graphql", 10 | "yup", 11 | "validation" 12 | ], 13 | "homepage": "https://github.com/JCMais/nexus-plugins/blob/plugins/yup-validation", 14 | "license": "MIT", 15 | "author": "Jonathan Cardoso ", 16 | "main": "dist", 17 | "module": "dist-esm", 18 | "types": "dist/index.d.ts", 19 | "files": [ 20 | "dist", 21 | "dist-esm", 22 | "src", 23 | "LICENSE.md", 24 | "README.md", 25 | "yarn.lock" 26 | ], 27 | "scripts": { 28 | "build:dist": "yarn -s clean:dist && tsc -p tsconfig.cjs.json --pretty && tsc -p tsconfig.esm.json --pretty", 29 | "clean:dist": "rimraf ./dist ./dist-esm", 30 | "lint": "eslint \"{src,tests}/**/*.ts\" -f eslint-formatter-friendly", 31 | "pre-commit": "tsc -p tsconfig.esm.json --pretty", 32 | "prepare": "echo \"do nothing\"", 33 | "prepublishOnly": "yarn build:dist", 34 | "test": "jest", 35 | "test:coverage": "jest --coverage", 36 | "test:coverage:watch": "jest --coverage --watch", 37 | "test:watch": "jest --watch" 38 | }, 39 | "devDependencies": { 40 | "yup": "^0.32.8" 41 | }, 42 | "peerDependencies": { 43 | "graphql": "^15", 44 | "nexus": "^1.0.0", 45 | "yup": "^0.32.8" 46 | }, 47 | "engines": { 48 | "node": ">= 12" 49 | }, 50 | "publishConfig": { 51 | "access": "public" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plugins/datetime/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jcm/nexus-plugin-datetime", 3 | "version": "0.2.0", 4 | "description": "Creates t.dateTime field", 5 | "keywords": [ 6 | "nexus", 7 | "plugin", 8 | "nexus-plugin", 9 | "graphql", 10 | "moment", 11 | "datetime" 12 | ], 13 | "homepage": "https://github.com/JCMais/nexus-plugins/blob/plugins/relay-node-interface", 14 | "license": "MIT", 15 | "author": "Jonathan Cardoso ", 16 | "main": "dist", 17 | "module": "dist-esm", 18 | "types": "dist/index.d.ts", 19 | "files": [ 20 | "dist", 21 | "dist-esm", 22 | "src", 23 | "LICENSE.md", 24 | "README.md", 25 | "yarn.lock" 26 | ], 27 | "scripts": { 28 | "build:dist": "yarn -s clean:dist && tsc -p tsconfig.cjs.json --pretty && tsc -p tsconfig.esm.json --pretty", 29 | "clean:dist": "rimraf ./dist ./dist-esm", 30 | "lint": "eslint \"{src,tests}/**/*.ts\" -f eslint-formatter-friendly", 31 | "pre-commit": "tsc -p tsconfig.esm.json --pretty", 32 | "prepare": "echo \"do nothing\"", 33 | "prepublishOnly": "yarn build:dist", 34 | "test": "jest", 35 | "test:coverage": "jest --coverage", 36 | "test:coverage:watch": "jest --coverage --watch", 37 | "test:watch": "jest --watch" 38 | }, 39 | "dependencies": { 40 | "moment-timezone": "^0.5.31" 41 | }, 42 | "peerDependencies": { 43 | "graphql": "^15", 44 | "nexus": "^1.0.0" 45 | }, 46 | "engines": { 47 | "node": ">= 12" 48 | }, 49 | "publishConfig": { 50 | "access": "public" 51 | }, 52 | "gitHead": "0082bf6ac10a514558fe7985bcb905e117485b86" 53 | } 54 | -------------------------------------------------------------------------------- /plugins/field-authentication/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jcm/nexus-plugin-field-authentication", 3 | "version": "0.2.0", 4 | "description": "Adds a new field config \"authentication\" that should be used to determine if the field should be resolved or not", 5 | "keywords": [ 6 | "nexus", 7 | "plugin", 8 | "nexus-plugin", 9 | "graphql", 10 | "authorization" 11 | ], 12 | "homepage": "https://github.com/JCMais/nexus-plugins/blob/plugins/field-authentication", 13 | "license": "MIT", 14 | "author": "Jonathan Cardoso ", 15 | "main": "dist", 16 | "module": "dist-esm", 17 | "types": "dist/index.d.ts", 18 | "files": [ 19 | "dist", 20 | "dist-esm", 21 | "src", 22 | "LICENSE.md", 23 | "README.md", 24 | "yarn.lock" 25 | ], 26 | "scripts": { 27 | "build:dist": "yarn -s clean:dist && tsc -p tsconfig.cjs.json --pretty && tsc -p tsconfig.esm.json --pretty", 28 | "clean:dist": "rimraf ./dist ./dist-esm", 29 | "lint": "eslint \"{src,tests}/**/*.ts\" -f eslint-formatter-friendly", 30 | "pre-commit": "tsc -p tsconfig.esm.json --pretty", 31 | "prepare": "echo \"do nothing\"", 32 | "prepublishOnly": "yarn build:dist", 33 | "test": "jest", 34 | "test:coverage": "jest --coverage", 35 | "test:coverage:watch": "jest --coverage --watch", 36 | "test:watch": "jest --watch" 37 | }, 38 | "peerDependencies": { 39 | "graphql": "^15", 40 | "nexus": "^1.0.0" 41 | }, 42 | "engines": { 43 | "node": ">= 12" 44 | }, 45 | "publishConfig": { 46 | "access": "public" 47 | }, 48 | "gitHead": "0082bf6ac10a514558fe7985bcb905e117485b86" 49 | } 50 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jcm/nexus-plugin-elastic-apm-instrumentation", 3 | "version": "0.2.0", 4 | "description": "Captures transactions spans for each field using an Elastic APM Agent", 5 | "keywords": [ 6 | "nexus", 7 | "plugin", 8 | "nexus-plugin", 9 | "graphql", 10 | "instrumentation", 11 | "apm", 12 | "elastic" 13 | ], 14 | "homepage": "https://github.com/JCMais/nexus-plugins/blob/plugins/elastic-apm-instrumentation", 15 | "license": "MIT", 16 | "author": "Jonathan Cardoso ", 17 | "main": "dist", 18 | "module": "dist-esm", 19 | "types": "dist/index.d.ts", 20 | "files": [ 21 | "dist", 22 | "dist-esm", 23 | "src", 24 | "LICENSE.md", 25 | "README.md", 26 | "yarn.lock" 27 | ], 28 | "scripts": { 29 | "build:dist": "yarn -s clean:dist && tsc -p tsconfig.cjs.json --pretty && tsc -p tsconfig.esm.json --pretty", 30 | "clean:dist": "rimraf ./dist ./dist-esm", 31 | "lint": "eslint \"{src,tests}/**/*.ts\" -f eslint-formatter-friendly", 32 | "pre-commit": "tsc -p tsconfig.esm.json --pretty", 33 | "prepare": "echo \"do nothing\"", 34 | "prepublishOnly": "yarn build:dist", 35 | "test": "jest", 36 | "test:coverage": "jest --coverage", 37 | "test:coverage:watch": "jest --coverage --watch", 38 | "test:watch": "jest --watch" 39 | }, 40 | "devDependencies": { 41 | "elastic-apm-node": "^3.6.1" 42 | }, 43 | "peerDependencies": { 44 | "elastic-apm-node": "^3.6.1", 45 | "graphql": "^15", 46 | "nexus": "^1.0.0" 47 | }, 48 | "engines": { 49 | "node": ">= 12" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /plugins/yup-validation/README.md: -------------------------------------------------------------------------------- 1 | # @jcm/nexus-plugin-yup-validation 2 | 3 |

4 | 5 | Patreon Logo 6 | 7 |
8 | 9 | Discord Logo 10 | 11 |

12 | 13 | This plugin allows to set a `yup` property on some field (Mutations are the only ones supported at the moment), and it will validate the arguments passed to the field using the given [`yup`](https://github.com/jquense/yup) schema. It's inspired by something I wrote a long time ago: [GraphQL Mutation Arguments Validation with Yup using graphql-middleware](https://jonathancardoso.com/en/blog/graphql-mutation-arguments-validation-with-yup-using-graphql-middleware/) 14 | 15 | Sample usage: 16 | 17 | ```typescript 18 | const AddUserMutation = mutationField((t) => { 19 | t.field('addUser', { 20 | type: AddUserPayload, 21 | args: { 22 | email: stringArg(), 23 | }, 24 | yup: { 25 | schema: yup.object({ 26 | email: yup.string().email().required(), 27 | }), 28 | }, 29 | // ... 30 | }) 31 | }) 32 | ``` 33 | 34 | The default `errorPayloadBuilder` assumes that the mutation output type has a field named `error` which is an object with the following structure: 35 | 36 | ```ts 37 | { 38 | message: string, 39 | details: ErrorDetail[] 40 | } 41 | 42 | // ErrorDetails: 43 | { 44 | path: string[], 45 | messages: string[] 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /plugins/relay-global-id/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jcm/nexus-plugin-relay-global-id", 3 | "version": "0.2.0", 4 | "description": "Adds t.relayGlobalId to the schema builder", 5 | "keywords": [ 6 | "nexus", 7 | "plugin", 8 | "nexus-plugin", 9 | "graphql", 10 | "relay", 11 | "graphql-relay", 12 | "global-id" 13 | ], 14 | "homepage": "https://github.com/JCMais/nexus-plugins/blob/plugins/relay-global-id", 15 | "license": "MIT", 16 | "author": "Jonathan Cardoso ", 17 | "main": "dist", 18 | "module": "dist-esm", 19 | "types": "dist/index.d.ts", 20 | "files": [ 21 | "dist", 22 | "dist-esm", 23 | "src", 24 | "LICENSE.md", 25 | "README.md", 26 | "yarn.lock" 27 | ], 28 | "scripts": { 29 | "build:dist": "yarn -s clean:dist && tsc -p tsconfig.cjs.json --pretty && tsc -p tsconfig.esm.json --pretty", 30 | "clean:dist": "rimraf ./dist ./dist-esm", 31 | "lint": "eslint \"{src,tests}/**/*.ts\" -f eslint-formatter-friendly", 32 | "pre-commit": "tsc -p tsconfig.esm.json --pretty", 33 | "prepare": "echo \"do nothing\"", 34 | "prepublishOnly": "yarn build:dist", 35 | "test": "jest", 36 | "test:coverage": "jest --coverage", 37 | "test:coverage:watch": "jest --coverage --watch", 38 | "test:watch": "jest --watch" 39 | }, 40 | "devDependencies": { 41 | "@types/graphql-relay": "^0.6.0", 42 | "graphql-relay": "^0.6.0" 43 | }, 44 | "peerDependencies": { 45 | "graphql": "^15", 46 | "graphql-relay": "^0.6.0", 47 | "nexus": "^1.0.0" 48 | }, 49 | "engines": { 50 | "node": ">= 12" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "gitHead": "0082bf6ac10a514558fe7985bcb905e117485b86" 56 | } 57 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jcm/nexus-plugin-relay-node-interface", 3 | "version": "0.2.0", 4 | "description": "Creates the Relay Node interface and add node/nodes fields to the Query type", 5 | "keywords": [ 6 | "nexus", 7 | "plugin", 8 | "nexus-plugin", 9 | "graphql", 10 | "relay", 11 | "graphql-relay", 12 | "node-interface" 13 | ], 14 | "homepage": "https://github.com/JCMais/nexus-plugins/blob/plugins/relay-node-interface", 15 | "license": "MIT", 16 | "author": "Jonathan Cardoso ", 17 | "main": "dist", 18 | "module": "dist-esm", 19 | "types": "dist/index.d.ts", 20 | "files": [ 21 | "dist", 22 | "dist-esm", 23 | "src", 24 | "LICENSE.md", 25 | "README.md", 26 | "yarn.lock" 27 | ], 28 | "scripts": { 29 | "build:dist": "yarn -s clean:dist && tsc -p tsconfig.cjs.json --pretty && tsc -p tsconfig.esm.json --pretty", 30 | "clean:dist": "rimraf ./dist ./dist-esm", 31 | "lint": "eslint \"{src,tests}/**/*.ts\" -f eslint-formatter-friendly", 32 | "pre-commit": "tsc -p tsconfig.esm.json --pretty", 33 | "prepare": "echo \"do nothing\"", 34 | "prepublishOnly": "yarn build:dist", 35 | "test": "jest", 36 | "test:coverage": "jest --coverage", 37 | "test:coverage:watch": "jest --coverage --watch", 38 | "test:watch": "jest --watch" 39 | }, 40 | "devDependencies": { 41 | "@types/graphql-relay": "^0.6.0", 42 | "graphql-relay": "^0.6.0" 43 | }, 44 | "peerDependencies": { 45 | "graphql": "^15", 46 | "graphql-relay": "^0.6.0", 47 | "nexus": "^1.0.0" 48 | }, 49 | "engines": { 50 | "node": ">= 12" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "gitHead": "0082bf6ac10a514558fe7985bcb905e117485b86" 56 | } 57 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.2.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-elastic-apm-instrumentation@0.1.1...@jcm/nexus-plugin-elastic-apm-instrumentation@0.2.0) (2021-01-24) 7 | 8 | ### Features 9 | 10 | - add yup-validation plugin ([c3f0c70](https://github.com/JCMais/nexus-plugins/commit/c3f0c703a71414e9a7b59ae5d0e7bf5edacf57fe)) 11 | 12 | ## [0.1.1](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-elastic-apm-instrumentation@0.1.0...@jcm/nexus-plugin-elastic-apm-instrumentation@0.1.1) (2020-12-24) 13 | 14 | **Note:** Version bump only for package @jcm/nexus-plugin-elastic-apm-instrumentation 15 | 16 | # [0.1.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-elastic-apm-instrumentation@0.0.2...@jcm/nexus-plugin-elastic-apm-instrumentation@0.1.0) (2020-12-24) 17 | 18 | ### Features 19 | 20 | - upgrade dependencies and use nexus v1 instead of @nexus/schema ([2f27c77](https://github.com/JCMais/nexus-plugins/commit/2f27c77435060a89e89420ee7a35d9d6b67c2d15)) 21 | 22 | ### BREAKING CHANGES 23 | 24 | - You must be using GraphQL v15 and nexus v1 now. 25 | 26 | ## [0.0.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-elastic-apm-instrumentation@0.0.1...@jcm/nexus-plugin-elastic-apm-instrumentation@0.0.2) (2020-08-06) 27 | 28 | **Note:** Version bump only for package @jcm/nexus-plugin-elastic-apm-instrumentation 29 | 30 | ## 0.0.1 (2020-08-06) 31 | 32 | ### Features 33 | 34 | - add initial code for elastic-apm-instrumentation plugin ([9dc4323](https://github.com/JCMais/nexus-plugins/commit/9dc4323fcf509bcfe5003131747558f3f26cb5d1)) 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "workspaces": [ 5 | "plugins/*" 6 | ], 7 | "scripts": { 8 | "build:dist": "lerna run --stream --sort build:dist", 9 | "clean:dist": "lerna run --stream --sort clean:dist", 10 | "format": "prettier --write \"**/*.ts\" \"!**/dist/**\" \"!**/dist-esm/**\"", 11 | "lint": "eslint \"plugins/**/*.ts\" -f eslint-formatter-friendly", 12 | "p": "lerna publish --conventional-commits", 13 | "p:force:all": "lerna publish --force-publish=*", 14 | "pre-commit": "lerna run --stream --sort pre-commit", 15 | "prepare": "lerna run --stream --sort prepare", 16 | "test": "jest" 17 | }, 18 | "husky": { 19 | "hooks": { 20 | "pre-commit": "lint-staged && yarn pre-commit" 21 | } 22 | }, 23 | "lint-staged": { 24 | "**/*.ts": [ 25 | "prettier --write", 26 | "eslint --fix" 27 | ], 28 | "**/*.{json,md}": [ 29 | "prettier --write" 30 | ], 31 | "**/package.json": [ 32 | "sort-package-json" 33 | ] 34 | }, 35 | "dependencies": { 36 | "tslib": "^2.1.0" 37 | }, 38 | "devDependencies": { 39 | "@jcm/eslint-config": "^0.0.2", 40 | "@types/jest": "^26.0.20", 41 | "@types/node": "^14.14.22", 42 | "@typescript-eslint/eslint-plugin": "^4.14.0", 43 | "@typescript-eslint/parser": "^4.14.0", 44 | "eslint": "^7.18.0", 45 | "eslint-config-prettier": "^7.2.0", 46 | "eslint-formatter-friendly": "^7.0.0", 47 | "eslint-plugin-prettier": "^3.3.1", 48 | "graphql": "^15.5.0", 49 | "husky": "^4.3.8", 50 | "jest": "^26.6.3", 51 | "lerna": "^3.22.1", 52 | "lint-staged": "^10.5.3", 53 | "nexus": "^1.0.0", 54 | "np": "^7.2.0", 55 | "prettier": "^2.2.1", 56 | "rimraf": "^3.0.2", 57 | "sort-package-json": "^1.48.1", 58 | "ts-jest": "^26.4.4", 59 | "typescript": "^4.1.3" 60 | }, 61 | "peerDependencies": { 62 | "nexus": ">= 1.0.0" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /plugins/relay-mutation/README.md: -------------------------------------------------------------------------------- 1 | # @jcm/nexus-plugin-relay-mutation 2 | 3 |

4 | 5 | Patreon Logo 6 | 7 |
8 | 9 | Discord Logo 10 | 11 |

12 | 13 | This plugin adds the field method `relayMutation(fieldName, fieldConfig)` to the Nexus Schema Builder, which can be used to create [Relay-compliant mutations](https://relay.dev/docs/en/graphql-server-specification.html#mutations). 14 | 15 | It's based on the [`mutation` helper](https://github.com/graphql/graphql-relay-js/blob/f00bad1395eed1738264f41c35e3901dddff1559/src/mutation/mutation.js) from `graphql-relay`. 16 | 17 | Sample usage: 18 | 19 | ```typescript 20 | const mutation = mutationField((t) => { 21 | t.relayMutation('addNumbers', { 22 | inputFields(t2) { 23 | t2.int('number1', { 24 | required: true, 25 | }) 26 | t2.int('number2', { 27 | required: true, 28 | }) 29 | }, 30 | outputFields(t2) { 31 | t2.int('result') 32 | }, 33 | mutateAndGetPayload(_root, input, _ctx, _info) { 34 | return { 35 | result: input.number1 + input.number2, 36 | } 37 | }, 38 | }) 39 | }) 40 | ``` 41 | 42 | With the above code, the following schema will be generated: 43 | 44 | ```graphql 45 | input addNumbersInput { 46 | number1: Int! 47 | number2: Int! 48 | clientMutationId: String 49 | } 50 | 51 | type addNumbersPayload { 52 | result: Int! 53 | clientMutationId: String 54 | } 55 | 56 | type Mutation { 57 | addNumbers(input: addNumbersInput!): addNumbersPayload! 58 | # ... 59 | } 60 | 61 | # ... 62 | ``` 63 | -------------------------------------------------------------------------------- /plugins/#-sample/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.1.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-sample@0.0.6...@jcm/nexus-plugin-sample@0.1.0) (2021-01-24) 7 | 8 | ### Features 9 | 10 | - add yup-validation plugin ([c3f0c70](https://github.com/JCMais/nexus-plugins/commit/c3f0c703a71414e9a7b59ae5d0e7bf5edacf57fe)) 11 | 12 | ## [0.0.6](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-sample@0.0.5...@jcm/nexus-plugin-sample@0.0.6) (2020-12-24) 13 | 14 | **Note:** Version bump only for package @jcm/nexus-plugin-sample 15 | 16 | ## [0.0.5](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-sample@0.0.4...@jcm/nexus-plugin-sample@0.0.5) (2020-08-06) 17 | 18 | **Note:** Version bump only for package @jcm/nexus-plugin-sample 19 | 20 | ## [0.0.4](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-sample@0.0.3...@jcm/nexus-plugin-sample@0.0.4) (2020-07-31) 21 | 22 | ### Bug Fixes 23 | 24 | - fix datetime and relay-global-id to pass extra field configs back to nexus schema ([86d7bfb](https://github.com/JCMais/nexus-plugins/commit/86d7bfb5b0d3e9fecfd0ad5b59c16c9821a07817)) 25 | 26 | ## [0.0.3](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-sample@0.0.2...@jcm/nexus-plugin-sample@0.0.3) (2020-07-25) 27 | 28 | **Note:** Version bump only for package @jcm/nexus-plugin-sample 29 | 30 | ## [0.0.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-sample@0.0.1...@jcm/nexus-plugin-sample@0.0.2) (2020-07-25) 31 | 32 | ### Bug Fixes 33 | 34 | - add publishConfig to package.json ([8444527](https://github.com/JCMais/nexus-plugins/commit/8444527c32502e5b91369035cf68e8fa44366d6b)) 35 | 36 | ## 0.0.1 (2020-07-25) 37 | 38 | ### Bug Fixes 39 | 40 | - versions on package.json to 0.0.0 ([49283b5](https://github.com/JCMais/nexus-plugins/commit/49283b521f7dc14ea877f96b4e60665d890b736b)) 41 | -------------------------------------------------------------------------------- /.github/workflows/test-plugin-sample.yaml: -------------------------------------------------------------------------------- 1 | name: test-sample 2 | 3 | env: 4 | PROJECT_NAME: sample 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '.github/workflows/test-plugin-sample.yaml' 10 | - 'plugins/#-sample/**' 11 | - 'codecov.yml' 12 | push: 13 | paths: 14 | - '.github/workflows/test-plugin-sample.yaml' 15 | - 'plugins/#-sample/**' 16 | - 'codecov.yml' 17 | 18 | jobs: 19 | build-and-test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@main 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: '14.x' 29 | - name: Output yarn cache dir 30 | id: yarn-cache-dir 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | - name: Restore Yarn Cache 33 | uses: actions/cache@v1 34 | id: yarn-cache 35 | with: 36 | path: ${{ steps.yarn-cache-dir.outputs.dir }} 37 | key: v1-${{ runner.os }}-yarn-cache-${{ github.ref }}-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | v1-${{ runner.os }}-yarn-cache-${{ github.ref }}- 40 | v1-${{ runner.os }}-yarn-cache- 41 | - name: 'Yarn install' 42 | run: yarn install 43 | # We could have used lerna to run only on changed here... 44 | - name: 'Run lint' 45 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} lint 46 | - name: 'Run test' 47 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} test:coverage 48 | - name: 'Run build:dist' 49 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} build:dist 50 | - name: 'Echo project name without dashes' 51 | run: echo ::set-output name=name::$(echo $PROJECT_NAME | sed "s/-//g") 52 | id: project-name 53 | - name: Upload coverage to Codecov 54 | uses: codecov/codecov-action@v1 55 | with: 56 | token: ${{ secrets.CODECOV_TOKEN }} 57 | file: ./plugins/${{ env.PROJECT_NAME }}/converage/** 58 | flags: ${{ steps.project-name.outputs.name }} 59 | name: ${{ env.PROJECT_NAME }} 60 | fail_ci_if_error: false 61 | -------------------------------------------------------------------------------- /.github/workflows/test-plugin-datetime.yaml: -------------------------------------------------------------------------------- 1 | name: test-datetime 2 | 3 | env: 4 | PROJECT_NAME: datetime 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '.github/workflows/test-plugin-datetime.yaml' 10 | - 'plugins/datetime/**' 11 | - 'codecov.yml' 12 | push: 13 | paths: 14 | - '.github/workflows/test-plugin-datetime.yaml' 15 | - 'plugins/datetime/**' 16 | - 'codecov.yml' 17 | 18 | jobs: 19 | build-and-test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@main 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: '14.x' 29 | - name: Output yarn cache dir 30 | id: yarn-cache-dir 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | - name: Restore Yarn Cache 33 | uses: actions/cache@v1 34 | id: yarn-cache 35 | with: 36 | path: ${{ steps.yarn-cache-dir.outputs.dir }} 37 | key: v1-${{ runner.os }}-yarn-cache-${{ github.ref }}-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | v1-${{ runner.os }}-yarn-cache-${{ github.ref }}- 40 | v1-${{ runner.os }}-yarn-cache- 41 | - name: 'Yarn install' 42 | run: yarn install 43 | # We could have used lerna to run only on changed here... 44 | - name: 'Run lint' 45 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} lint 46 | - name: 'Run test' 47 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} test:coverage 48 | - name: 'Run build:dist' 49 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} build:dist 50 | - name: 'Echo project name without dashes' 51 | run: echo ::set-output name=name::$(echo $PROJECT_NAME | sed "s/-//g") 52 | id: project-name 53 | - name: Upload coverage to Codecov 54 | uses: codecov/codecov-action@v1 55 | with: 56 | token: ${{ secrets.CODECOV_TOKEN }} 57 | file: ./plugins/${{ env.PROJECT_NAME }}/converage/** 58 | flags: ${{ steps.project-name.outputs.name }} 59 | name: ${{ env.PROJECT_NAME }} 60 | fail_ci_if_error: false 61 | -------------------------------------------------------------------------------- /.github/workflows/test-plugin-relay-mutation.yaml: -------------------------------------------------------------------------------- 1 | name: test-relay-mutation 2 | 3 | env: 4 | PROJECT_NAME: relay-mutation 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '.github/workflows/test-plugin-relay-mutation.yaml' 10 | - 'plugins/relay-mutation/**' 11 | - 'codecov.yml' 12 | push: 13 | paths: 14 | - '.github/workflows/test-plugin-relay-mutation.yaml' 15 | - 'plugins/relay-mutation/**' 16 | - 'codecov.yml' 17 | 18 | jobs: 19 | build-and-test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@main 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: '14.x' 29 | - name: Output yarn cache dir 30 | id: yarn-cache-dir 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | - name: Restore Yarn Cache 33 | uses: actions/cache@v1 34 | id: yarn-cache 35 | with: 36 | path: ${{ steps.yarn-cache-dir.outputs.dir }} 37 | key: v1-${{ runner.os }}-yarn-cache-${{ github.ref }}-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | v1-${{ runner.os }}-yarn-cache-${{ github.ref }}- 40 | v1-${{ runner.os }}-yarn-cache- 41 | - name: 'Yarn install' 42 | run: yarn install 43 | # We could have used lerna to run only on changed here... 44 | - name: 'Run lint' 45 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} lint 46 | - name: 'Run test' 47 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} test:coverage 48 | - name: 'Run build:dist' 49 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} build:dist 50 | - name: 'Echo project name without dashes' 51 | run: echo ::set-output name=name::$(echo $PROJECT_NAME | sed "s/-//g") 52 | id: project-name 53 | - name: Upload coverage to Codecov 54 | uses: codecov/codecov-action@v1 55 | with: 56 | token: ${{ secrets.CODECOV_TOKEN }} 57 | file: ./plugins/${{ env.PROJECT_NAME }}/converage/** 58 | flags: ${{ steps.project-name.outputs.name }} 59 | name: ${{ env.PROJECT_NAME }} 60 | fail_ci_if_error: false 61 | -------------------------------------------------------------------------------- /.github/workflows/test-plugin-relay-global-id.yaml: -------------------------------------------------------------------------------- 1 | name: test-relay-global-id 2 | 3 | env: 4 | PROJECT_NAME: relay-global-id 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '.github/workflows/test-plugin-relay-global-id.yaml' 10 | - 'plugins/relay-global-id/**' 11 | - 'codecov.yml' 12 | push: 13 | paths: 14 | - '.github/workflows/test-plugin-relay-global-id.yaml' 15 | - 'plugins/relay-global-id/**' 16 | - 'codecov.yml' 17 | 18 | jobs: 19 | build-and-test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@main 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: '14.x' 29 | - name: Output yarn cache dir 30 | id: yarn-cache-dir 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | - name: Restore Yarn Cache 33 | uses: actions/cache@v1 34 | id: yarn-cache 35 | with: 36 | path: ${{ steps.yarn-cache-dir.outputs.dir }} 37 | key: v1-${{ runner.os }}-yarn-cache-${{ github.ref }}-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | v1-${{ runner.os }}-yarn-cache-${{ github.ref }}- 40 | v1-${{ runner.os }}-yarn-cache- 41 | - name: 'Yarn install' 42 | run: yarn install 43 | # We could have used lerna to run only on changed here... 44 | - name: 'Run lint' 45 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} lint 46 | - name: 'Run test' 47 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} test:coverage 48 | - name: 'Run build:dist' 49 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} build:dist 50 | - name: 'Echo project name without dashes' 51 | run: echo ::set-output name=name::$(echo $PROJECT_NAME | sed "s/-//g") 52 | id: project-name 53 | - name: Upload coverage to Codecov 54 | uses: codecov/codecov-action@v1 55 | with: 56 | token: ${{ secrets.CODECOV_TOKEN }} 57 | file: ./plugins/${{ env.PROJECT_NAME }}/converage/** 58 | flags: ${{ steps.project-name.outputs.name }} 59 | name: ${{ env.PROJECT_NAME }} 60 | fail_ci_if_error: false 61 | -------------------------------------------------------------------------------- /.github/workflows/test-plugin-field-authentication.yaml: -------------------------------------------------------------------------------- 1 | name: test-field-authentication 2 | 3 | env: 4 | PROJECT_NAME: field-authentication 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '.github/workflows/test-plugin-field-authentication.yaml' 10 | - 'plugins/field-authentication/**' 11 | - 'codecov.yml' 12 | push: 13 | paths: 14 | - '.github/workflows/test-plugin-field-authentication.yaml' 15 | - 'plugins/field-authentication/**' 16 | - 'codecov.yml' 17 | 18 | jobs: 19 | build-and-test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@main 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: '14.x' 29 | - name: Output yarn cache dir 30 | id: yarn-cache-dir 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | - name: Restore Yarn Cache 33 | uses: actions/cache@v1 34 | id: yarn-cache 35 | with: 36 | path: ${{ steps.yarn-cache-dir.outputs.dir }} 37 | key: v1-${{ runner.os }}-yarn-cache-${{ github.ref }}-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | v1-${{ runner.os }}-yarn-cache-${{ github.ref }}- 40 | v1-${{ runner.os }}-yarn-cache- 41 | - name: 'Yarn install' 42 | run: yarn install 43 | # We could have used lerna to run only on changed here... 44 | - name: 'Run lint' 45 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} lint 46 | - name: 'Run test' 47 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} test:coverage 48 | - name: 'Run build:dist' 49 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} build:dist 50 | - name: 'Echo project name without dashes' 51 | run: echo ::set-output name=name::$(echo $PROJECT_NAME | sed "s/-//g") 52 | id: project-name 53 | - name: Upload coverage to Codecov 54 | uses: codecov/codecov-action@v1 55 | with: 56 | token: ${{ secrets.CODECOV_TOKEN }} 57 | file: ./plugins/${{ env.PROJECT_NAME }}/converage/** 58 | flags: ${{ steps.project-name.outputs.name }} 59 | name: ${{ env.PROJECT_NAME }} 60 | fail_ci_if_error: false 61 | -------------------------------------------------------------------------------- /.github/workflows/test-plugin-relay-node-interface.yaml: -------------------------------------------------------------------------------- 1 | name: test-relay-node-interface 2 | 3 | env: 4 | PROJECT_NAME: relay-node-interface 5 | 6 | on: 7 | pull_request: 8 | paths: 9 | - '.github/workflows/test-plugin-relay-node-interface.yaml' 10 | - 'plugins/relay-node-interface/**' 11 | - 'codecov.yml' 12 | push: 13 | paths: 14 | - '.github/workflows/test-plugin-relay-node-interface.yaml' 15 | - 'plugins/relay-node-interface/**' 16 | - 'codecov.yml' 17 | 18 | jobs: 19 | build-and-test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@main 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: '14.x' 29 | - name: Output yarn cache dir 30 | id: yarn-cache-dir 31 | run: echo "::set-output name=dir::$(yarn cache dir)" 32 | - name: Restore Yarn Cache 33 | uses: actions/cache@v1 34 | id: yarn-cache 35 | with: 36 | path: ${{ steps.yarn-cache-dir.outputs.dir }} 37 | key: v1-${{ runner.os }}-yarn-cache-${{ github.ref }}-${{ hashFiles('**/yarn.lock') }} 38 | restore-keys: | 39 | v1-${{ runner.os }}-yarn-cache-${{ github.ref }}- 40 | v1-${{ runner.os }}-yarn-cache- 41 | - name: 'Yarn install' 42 | run: yarn install 43 | # We could have used lerna to run only on changed here... 44 | - name: 'Run lint' 45 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} lint 46 | - name: 'Run test' 47 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} test:coverage 48 | - name: 'Run build:dist' 49 | run: yarn workspace @jcm/nexus-plugin-${PROJECT_NAME} build:dist 50 | - name: 'Echo project name without dashes' 51 | run: echo ::set-output name=name::$(echo $PROJECT_NAME | sed "s/-//g") 52 | id: project-name 53 | - name: Upload coverage to Codecov 54 | uses: codecov/codecov-action@v1 55 | with: 56 | token: ${{ secrets.CODECOV_TOKEN }} 57 | file: ./plugins/${{ env.PROJECT_NAME }}/converage/** 58 | flags: ${{ steps.project-name.outputs.name }} 59 | name: ${{ env.PROJECT_NAME }} 60 | fail_ci_if_error: false 61 | -------------------------------------------------------------------------------- /plugins/yup-validation/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { execute, parse } from 'graphql' 2 | import { makeSchema, mutationField, objectType, stringArg } from 'nexus' 3 | import * as yup from 'yup' 4 | 5 | import { 6 | yupValidationPlugin, 7 | MutationUserErrorNexusObjectType, 8 | YupValidationPluginConfig, 9 | } from '../src' 10 | 11 | const AddUserPayload = objectType({ 12 | name: 'AddUserPayload', 13 | definition: (t) => { 14 | t.field('error', { 15 | type: MutationUserErrorNexusObjectType, 16 | }) 17 | }, 18 | }) 19 | 20 | const AddUserMutation = mutationField((t) => { 21 | t.field('addUser', { 22 | type: AddUserPayload, 23 | args: { 24 | email: stringArg(), 25 | }, 26 | // @ts-expect-error 27 | yup: { 28 | schema: yup.object({ 29 | email: yup.string().email().min(10).required(), 30 | }), 31 | }, 32 | // ... 33 | }) 34 | }) 35 | 36 | const CallAddUserMutation = parse( 37 | ` 38 | mutation AddUser($email: String) { 39 | addUser(email: $email) { 40 | error { 41 | message 42 | details { 43 | path 44 | messages 45 | } 46 | } 47 | } 48 | }`, 49 | ) 50 | 51 | const testSchema = (pluginConfig: YupValidationPluginConfig, outputs = false) => 52 | makeSchema({ 53 | outputs, 54 | types: [AddUserMutation], 55 | plugins: [yupValidationPlugin(pluginConfig)], 56 | nonNullDefaults: { 57 | input: false, 58 | output: false, 59 | }, 60 | }) 61 | 62 | // TODO(jonathan): increase tests code coverage 63 | 64 | describe('yupValidation', () => { 65 | describe('basic behavior', () => { 66 | it('should work correctly with default values', async () => { 67 | const schema = testSchema({}) 68 | const data = await execute({ 69 | schema, 70 | document: CallAddUserMutation, 71 | variableValues: { 72 | email: '', 73 | }, 74 | }) 75 | 76 | expect(data).toMatchSnapshot() 77 | }) 78 | 79 | it('should work with abortEarly = true', async () => { 80 | const schema = testSchema({ 81 | yup: { 82 | abortEarly: true, 83 | }, 84 | }) 85 | const data = await execute({ 86 | schema, 87 | document: CallAddUserMutation, 88 | variableValues: { 89 | email: '', 90 | }, 91 | }) 92 | 93 | expect(data).toMatchSnapshot() 94 | }) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /plugins/field-authentication/README.md: -------------------------------------------------------------------------------- 1 | # @jcm/nexus-plugin-relay-node-interface 2 | 3 |

4 | 5 | Patreon Logo 6 | 7 |
8 | 9 | Discord Logo 10 | 11 |

12 | 13 | This plugin allows to set a `authentication` property on any field, and it will validate that the user is / is not authenticated before resolving it. 14 | 15 | Sample usage: 16 | 17 | ```typescript 18 | const User = objectType({ 19 | name: 'User', 20 | definition(t) { 21 | // .. 22 | 23 | t.string('email', { 24 | // this field will only be resolved if the user is authenticated 25 | authentication: true, 26 | }) 27 | 28 | t.string('email', { 29 | // this field will only be resolved if the user is NOT authenticated 30 | authentication: false, 31 | }) 32 | 33 | t.string('email', { 34 | // this field will only be resolved if the user is authenticated 35 | // when they are not, 'random-email@domain.tld' will be returned instead 36 | authentication: [true, 'random-email@domain.tld'], 37 | }) 38 | 39 | t.string('email', { 40 | // this field will only be resolved if the user is authenticated 41 | // when they are not, an error will be thrown 42 | authentication: [true, new Error('Something happened!')], 43 | }) 44 | 45 | // you can also pass a resolve like function, their result must be like the value above 46 | // or it can also throw an error 47 | t.string('email', { 48 | // this field will only be resolved if the user is authenticated 49 | // when they are not, an error will be thrown 50 | authentication: async (root, args, ctx, info) => [false], 51 | }) 52 | }, 53 | }) 54 | ``` 55 | 56 | The plugin accepts a few options, but the main one is `isLogged`, which is a resolve like function that you can provide to assert that the client is logged or not. By default it's set to: 57 | 58 | ``` 59 | (_root, _args, ctx, _info) => !!ctx?.state?.user 60 | ``` 61 | 62 | Which will work if you are storing the logged user on `ctx.state.user`. Otherwise you can change it to your own logic. 63 | 64 | > It's so confusing allowing false in the `authentication` field! 65 | 66 | I agree, in reality I have never had the need to use something other than `true` | `Error` 67 | -------------------------------------------------------------------------------- /plugins/relay-global-id/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { execute, parse, printType } from 'graphql' 2 | import { fromGlobalId } from 'graphql-relay' 3 | import { makeSchema, objectType } from 'nexus' 4 | 5 | import { 6 | relayGlobalIdPlugin, 7 | RelayGlobalIdPluginConfig, 8 | RelayGlobalIdNexusFieldConfig, 9 | } from '../src' 10 | 11 | const user = { 12 | id: 'id-value', 13 | idTwo: 'idTwo-value', 14 | idThree: 'idThree-value', 15 | id4: 'id4-value', 16 | } as const 17 | 18 | const User = objectType({ 19 | name: 'User', 20 | definition(t) { 21 | // @ts-expect-error 22 | t.nonNull.relayGlobalId('id') 23 | // @ts-expect-error 24 | t.nonNull.relayGlobalId('id2', { 25 | field: 'idTwo', 26 | shouldAddRawId: false, 27 | }) 28 | 29 | // @ts-expect-error 30 | t.nonNull.relayGlobalId('id3', { 31 | resolve: (root: typeof user) => root.idThree, 32 | }) 33 | 34 | // @ts-expect-error 35 | t.nonNull.relayGlobalId('id4', { 36 | shouldAddRawId: 'rawIdFour', 37 | }) 38 | }, 39 | }) 40 | 41 | const QueryMeId = parse( 42 | `query QueryMeId { 43 | me { 44 | id 45 | rawId 46 | id2 47 | id3 48 | rawId3 49 | id4 50 | rawIdFour 51 | } 52 | }`, 53 | ) 54 | 55 | const testSchema = ( 56 | pluginConfig: RelayGlobalIdPluginConfig, 57 | _connectionFieldProps: RelayGlobalIdNexusFieldConfig = {}, 58 | outputs = false, 59 | ) => 60 | makeSchema({ 61 | outputs, 62 | types: [ 63 | User, 64 | objectType({ 65 | name: 'Query', 66 | definition(t) { 67 | t.field('me', { 68 | type: User, 69 | resolve: () => user, 70 | }) 71 | }, 72 | }), 73 | ], 74 | plugins: [relayGlobalIdPlugin(pluginConfig)], 75 | }) 76 | 77 | describe('relayGlobalIdPlugin', () => { 78 | describe('basic behavior', () => { 79 | it('should work correctly', () => { 80 | const schema = testSchema({}) 81 | expect(printType(schema.getType('User')!)).toMatchSnapshot() 82 | }) 83 | it('should resolve all fields correctly', async () => { 84 | const schema = testSchema({}) 85 | const nodes = await execute({ 86 | schema, 87 | document: QueryMeId, 88 | variableValues: { first: 1 }, 89 | }) 90 | 91 | expect(nodes).toMatchSnapshot() 92 | 93 | const me = nodes!.data!.me 94 | 95 | expect(fromGlobalId(me.id)).toEqual({ type: 'User', id: me.rawId }) 96 | expect(me.rawId).toEqual(user.id) 97 | 98 | expect(fromGlobalId(me.id2)).toEqual({ type: 'User', id: user.idTwo }) 99 | 100 | expect(fromGlobalId(me.id3)).toEqual({ type: 'User', id: me.rawId3 }) 101 | expect(me.rawId3).toEqual(user.idThree) 102 | 103 | expect(fromGlobalId(me.id4)).toEqual({ type: 'User', id: me.rawIdFour }) 104 | expect(me.rawIdFour).toEqual(user.id4) 105 | }) 106 | }) 107 | }) 108 | -------------------------------------------------------------------------------- /plugins/relay-mutation/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.2.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.1.3...@jcm/nexus-plugin-relay-mutation@0.2.0) (2021-01-24) 7 | 8 | ### Features 9 | 10 | - add yup-validation plugin ([c3f0c70](https://github.com/JCMais/nexus-plugins/commit/c3f0c703a71414e9a7b59ae5d0e7bf5edacf57fe)) 11 | 12 | ## [0.1.3](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.1.2...@jcm/nexus-plugin-relay-mutation@0.1.3) (2021-01-18) 13 | 14 | ### Bug Fixes 15 | 16 | - **relay-mutation:** fix types for mutateAndGetPayload ([09d9db9](https://github.com/JCMais/nexus-plugins/commit/09d9db93b86e0c13bc7799bb1d5fbac8bbc391e4)) 17 | 18 | ## [0.1.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.1.1...@jcm/nexus-plugin-relay-mutation@0.1.2) (2020-12-24) 19 | 20 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-mutation 21 | 22 | ## [0.1.1](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.1.0...@jcm/nexus-plugin-relay-mutation@0.1.1) (2020-12-24) 23 | 24 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-mutation 25 | 26 | # [0.1.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.0.5...@jcm/nexus-plugin-relay-mutation@0.1.0) (2020-12-24) 27 | 28 | ### Features 29 | 30 | - upgrade dependencies and use nexus v1 instead of @nexus/schema ([2f27c77](https://github.com/JCMais/nexus-plugins/commit/2f27c77435060a89e89420ee7a35d9d6b67c2d15)) 31 | 32 | ### BREAKING CHANGES 33 | 34 | - You must be using GraphQL v15 and nexus v1 now. 35 | 36 | ## [0.0.5](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.0.4...@jcm/nexus-plugin-relay-mutation@0.0.5) (2020-08-07) 37 | 38 | ### Bug Fixes 39 | 40 | - **relay-mutation:** make input/payload types follow CamelCase naming ([62c6336](https://github.com/JCMais/nexus-plugins/commit/62c63361141b145f2e91799621e4bb62ed88a01b)) 41 | 42 | ## [0.0.4](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.0.3...@jcm/nexus-plugin-relay-mutation@0.0.4) (2020-08-06) 43 | 44 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-mutation 45 | 46 | ## [0.0.3](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.0.2...@jcm/nexus-plugin-relay-mutation@0.0.3) (2020-08-06) 47 | 48 | ### Bug Fixes 49 | 50 | - **mutation:** fix type of input argument for the mutateAndGetPayload function ([f1cdff9](https://github.com/JCMais/nexus-plugins/commit/f1cdff9fa341cec5a9027acc103f6f4d2cae4fd9)) 51 | 52 | ## [0.0.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-mutation@0.0.1...@jcm/nexus-plugin-relay-mutation@0.0.2) (2020-08-05) 53 | 54 | ### Bug Fixes 55 | 56 | - **relay-mutation:** remove tests file from tsconfig.json ([3c8ae65](https://github.com/JCMais/nexus-plugins/commit/3c8ae653de95df9ca454cbaabfd2e5d999f0add8)) 57 | 58 | ## 0.0.1 (2020-08-05) 59 | 60 | ### Features 61 | 62 | - add relay-mutation plugin ([7f17137](https://github.com/JCMais/nexus-plugins/commit/7f17137e9bf974b157a14731a45034de9c261cc3)) 63 | -------------------------------------------------------------------------------- /plugins/relay-global-id/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.2.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.1.2...@jcm/nexus-plugin-relay-global-id@0.2.0) (2021-01-24) 7 | 8 | ### Features 9 | 10 | - add yup-validation plugin ([c3f0c70](https://github.com/JCMais/nexus-plugins/commit/c3f0c703a71414e9a7b59ae5d0e7bf5edacf57fe)) 11 | 12 | ## [0.1.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.1.1...@jcm/nexus-plugin-relay-global-id@0.1.2) (2020-12-24) 13 | 14 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-global-id 15 | 16 | ## [0.1.1](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.1.0...@jcm/nexus-plugin-relay-global-id@0.1.1) (2020-12-24) 17 | 18 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-global-id 19 | 20 | # [0.1.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.0.7...@jcm/nexus-plugin-relay-global-id@0.1.0) (2020-12-24) 21 | 22 | ### Features 23 | 24 | - upgrade dependencies and use nexus v1 instead of @nexus/schema ([2f27c77](https://github.com/JCMais/nexus-plugins/commit/2f27c77435060a89e89420ee7a35d9d6b67c2d15)) 25 | 26 | ### BREAKING CHANGES 27 | 28 | - You must be using GraphQL v15 and nexus v1 now. 29 | 30 | ## [0.0.7](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.0.6...@jcm/nexus-plugin-relay-global-id@0.0.7) (2020-08-06) 31 | 32 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-global-id 33 | 34 | ## [0.0.6](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.0.5...@jcm/nexus-plugin-relay-global-id@0.0.6) (2020-08-05) 35 | 36 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-global-id 37 | 38 | ## [0.0.5](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.0.4...@jcm/nexus-plugin-relay-global-id@0.0.5) (2020-07-31) 39 | 40 | ### Bug Fixes 41 | 42 | - fix datetime and relay-global-id to pass extra field configs back to nexus schema ([86d7bfb](https://github.com/JCMais/nexus-plugins/commit/86d7bfb5b0d3e9fecfd0ad5b59c16c9821a07817)) 43 | 44 | ## [0.0.4](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.0.3...@jcm/nexus-plugin-relay-global-id@0.0.4) (2020-07-25) 45 | 46 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-global-id 47 | 48 | ## [0.0.3](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.0.2...@jcm/nexus-plugin-relay-global-id@0.0.3) (2020-07-25) 49 | 50 | ### Bug Fixes 51 | 52 | - add publishConfig to package.json ([8444527](https://github.com/JCMais/nexus-plugins/commit/8444527c32502e5b91369035cf68e8fa44366d6b)) 53 | 54 | ## [0.0.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-global-id@0.0.1...@jcm/nexus-plugin-relay-global-id@0.0.2) (2020-07-25) 55 | 56 | ### Bug Fixes 57 | 58 | - stuff ([4fcae7d](https://github.com/JCMais/nexus-plugins/commit/4fcae7d93f09eaa7b4fcdd0b4a3c43f2666e0d1d)) 59 | 60 | ## 0.0.1 (2020-07-25) 61 | 62 | ### Bug Fixes 63 | 64 | - versions on package.json to 0.0.0 ([49283b5](https://github.com/JCMais/nexus-plugins/commit/49283b521f7dc14ea877f96b4e60665d890b736b)) 65 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.2.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.1.2...@jcm/nexus-plugin-relay-node-interface@0.2.0) (2021-01-24) 7 | 8 | ### Features 9 | 10 | - add yup-validation plugin ([c3f0c70](https://github.com/JCMais/nexus-plugins/commit/c3f0c703a71414e9a7b59ae5d0e7bf5edacf57fe)) 11 | 12 | ## [0.1.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.1.1...@jcm/nexus-plugin-relay-node-interface@0.1.2) (2020-12-24) 13 | 14 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-node-interface 15 | 16 | ## [0.1.1](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.1.0...@jcm/nexus-plugin-relay-node-interface@0.1.1) (2020-12-24) 17 | 18 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-node-interface 19 | 20 | # [0.1.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.0.7...@jcm/nexus-plugin-relay-node-interface@0.1.0) (2020-12-24) 21 | 22 | ### Features 23 | 24 | - upgrade dependencies and use nexus v1 instead of @nexus/schema ([2f27c77](https://github.com/JCMais/nexus-plugins/commit/2f27c77435060a89e89420ee7a35d9d6b67c2d15)) 25 | 26 | ### BREAKING CHANGES 27 | 28 | - You must be using GraphQL v15 and nexus v1 now. 29 | 30 | ## [0.0.7](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.0.6...@jcm/nexus-plugin-relay-node-interface@0.0.7) (2020-08-06) 31 | 32 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-node-interface 33 | 34 | ## [0.0.6](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.0.5...@jcm/nexus-plugin-relay-node-interface@0.0.6) (2020-08-05) 35 | 36 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-node-interface 37 | 38 | ## [0.0.5](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.0.4...@jcm/nexus-plugin-relay-node-interface@0.0.5) (2020-07-31) 39 | 40 | ### Bug Fixes 41 | 42 | - fix datetime and relay-global-id to pass extra field configs back to nexus schema ([86d7bfb](https://github.com/JCMais/nexus-plugins/commit/86d7bfb5b0d3e9fecfd0ad5b59c16c9821a07817)) 43 | 44 | ## [0.0.4](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.0.3...@jcm/nexus-plugin-relay-node-interface@0.0.4) (2020-07-25) 45 | 46 | **Note:** Version bump only for package @jcm/nexus-plugin-relay-node-interface 47 | 48 | ## [0.0.3](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.0.2...@jcm/nexus-plugin-relay-node-interface@0.0.3) (2020-07-25) 49 | 50 | ### Bug Fixes 51 | 52 | - add publishConfig to package.json ([8444527](https://github.com/JCMais/nexus-plugins/commit/8444527c32502e5b91369035cf68e8fa44366d6b)) 53 | 54 | ## [0.0.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-relay-node-interface@0.0.1...@jcm/nexus-plugin-relay-node-interface@0.0.2) (2020-07-25) 55 | 56 | ### Bug Fixes 57 | 58 | - stuff ([4fcae7d](https://github.com/JCMais/nexus-plugins/commit/4fcae7d93f09eaa7b4fcdd0b4a3c43f2666e0d1d)) 59 | 60 | ## 0.0.1 (2020-07-25) 61 | 62 | ### Bug Fixes 63 | 64 | - versions on package.json to 0.0.0 ([49283b5](https://github.com/JCMais/nexus-plugins/commit/49283b521f7dc14ea877f96b4e60665d890b736b)) 65 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/tests/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`relayNodeInterfacePlugin basic behavior should resolve all fields correctly 1`] = ` 4 | Object { 5 | "data": Object { 6 | "nodes": Array [ 7 | Object { 8 | "__typename": "User", 9 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 10 | "name": "user-a", 11 | }, 12 | Object { 13 | "__typename": "Post", 14 | "id": "710c173a-c2af-11ea-86f1-83226be355fb", 15 | }, 16 | Object { 17 | "__typename": "User", 18 | "id": "710c15fa-c2af-11ea-86f1-7f51ff98b69c", 19 | "name": "user-b", 20 | }, 21 | Object { 22 | "__typename": "Post", 23 | "id": "a7a5c06e-c2d9-11ea-86f1-bfb838f3b7c6", 24 | }, 25 | ], 26 | "post1": Object { 27 | "__typename": "Post", 28 | "id": "710c173a-c2af-11ea-86f1-83226be355fb", 29 | }, 30 | "post2": Object { 31 | "__typename": "Post", 32 | "id": "a7a5c06e-c2d9-11ea-86f1-bfb838f3b7c6", 33 | }, 34 | "user1": Object { 35 | "__typename": "User", 36 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 37 | "name": "user-a", 38 | }, 39 | "user2": Object { 40 | "__typename": "User", 41 | "id": "710c15fa-c2af-11ea-86f1-7f51ff98b69c", 42 | "name": "user-b", 43 | }, 44 | }, 45 | } 46 | `; 47 | 48 | exports[`relayNodeInterfacePlugin basic behavior should resolve not found fields as null correctly 1`] = ` 49 | Object { 50 | "data": Object { 51 | "nodes": Array [ 52 | Object { 53 | "__typename": "User", 54 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 55 | "name": "user-a", 56 | }, 57 | null, 58 | Object { 59 | "__typename": "User", 60 | "id": "710c15fa-c2af-11ea-86f1-7f51ff98b69c", 61 | "name": "user-b", 62 | }, 63 | Object { 64 | "__typename": "Post", 65 | "id": "a7a5c06e-c2d9-11ea-86f1-bfb838f3b7c6", 66 | }, 67 | ], 68 | "post1": null, 69 | "post2": Object { 70 | "__typename": "Post", 71 | "id": "a7a5c06e-c2d9-11ea-86f1-bfb838f3b7c6", 72 | }, 73 | "user1": Object { 74 | "__typename": "User", 75 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 76 | "name": "user-a", 77 | }, 78 | "user2": Object { 79 | "__typename": "User", 80 | "id": "710c15fa-c2af-11ea-86f1-7f51ff98b69c", 81 | "name": "user-b", 82 | }, 83 | }, 84 | } 85 | `; 86 | 87 | exports[`relayNodeInterfacePlugin basic behavior should throw error for invalid type 1`] = ` 88 | Object { 89 | "data": Object { 90 | "node": null, 91 | }, 92 | "errors": Array [ 93 | [GraphQLError: Abstract type "Node" must resolve to an Object type at runtime for field "Query.node". Either the "Node" type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function.], 94 | ], 95 | } 96 | `; 97 | 98 | exports[`relayNodeInterfacePlugin basic behavior should work correctly 1`] = ` 99 | "type Query { 100 | \\"\\"\\"Fetches an object given its global ID\\"\\"\\" 101 | node( 102 | \\"\\"\\"The global ID of an object\\"\\"\\" 103 | id: ID! 104 | ): Node 105 | 106 | \\"\\"\\"Fetches objects given their global IDs\\"\\"\\" 107 | nodes( 108 | \\"\\"\\"The global IDs of objects\\"\\"\\" 109 | ids: [ID!]! 110 | ): [Node]! 111 | }" 112 | `; 113 | -------------------------------------------------------------------------------- /plugins/datetime/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { execute, parse, printSchema } from 'graphql' 2 | import { makeSchema, objectType } from 'nexus' 3 | 4 | import { DateTimePluginConfig, dateTimePlugin } from '../src' 5 | 6 | const users = [ 7 | { 8 | id: '8a291df8-bbcf-11ea-9db0-ff6b23713451', 9 | name: 'user-a', 10 | createdAt: '2020-07-02T01:28:59.391Z', 11 | createdAt_other: '2120-07-02T01:28:59.391Z', 12 | }, 13 | { 14 | id: '710c15fa-c2af-11ea-86f1-7f51ff98b69c', 15 | name: 'user-b', 16 | createdAt: '2020-07-12T01:28:59.391Z', 17 | createdAt_other: '2120-07-02T01:28:59.391Z', 18 | }, 19 | { 20 | id: '710c16cc-c2af-11ea-86f1-479adc4ed46f', 21 | name: 'user-c', 22 | createdAt: '2020-07-22T01:28:59.391Z', 23 | createdAt_other: '2120-07-02T01:28:59.391Z', 24 | }, 25 | ] 26 | 27 | const User = objectType({ 28 | name: 'User', 29 | definition(t) { 30 | t.nonNull.id('id') 31 | // @ts-expect-error 32 | t.nonNull.dateTime('createdAt') 33 | // @ts-expect-error 34 | t.nonNull.dateTime('createdAt2', { 35 | dateTimeISOField: 'createdAt_other', 36 | }) 37 | // @ts-expect-error 38 | t.nullable.dateTime('createdAt3', { 39 | field: 'createdAt', 40 | }) 41 | }, 42 | }) 43 | 44 | const QueryAll = parse( 45 | ` 46 | fragment userInfo on User { 47 | name 48 | default_past: createdAt { 49 | iso 50 | formatted(format: "YYYY-MM-DD") 51 | isAfter 52 | isBefore 53 | isSameOrAfter 54 | isSameOrBefore 55 | isSame 56 | isBetween 57 | } 58 | default_future: createdAt2 { 59 | iso 60 | formatted(format: "YYYY-MM-DD") 61 | isAfter 62 | isBefore 63 | isSameOrAfter 64 | isSameOrBefore 65 | isSame 66 | isBetween 67 | } 68 | nullable: createdAt3 { 69 | formatted 70 | } 71 | param: createdAt { 72 | iso 73 | formatted(format: "YYYY-MM-DD") 74 | isAfter(iso: "2020-07-12T01:28:59.391Z") 75 | isBefore(iso: "2020-07-12T01:28:59.391Z") 76 | isSameOrAfter(iso: "2020-07-12T01:28:59.391Z") 77 | isSameOrBefore(iso: "2020-07-12T01:28:59.391Z") 78 | isSame(iso: "2020-07-12T01:28:59.391Z") 79 | isBetween(isoStart: "2020-07-12T00:28:59.391Z", isoEnd: "2020-07-12T02:28:59.391Z") 80 | } 81 | } 82 | 83 | query QueryAll { 84 | users { 85 | ...userInfo 86 | } 87 | }`, 88 | ) 89 | 90 | const testSchema = (pluginConfig: DateTimePluginConfig, outputs = false) => 91 | makeSchema({ 92 | outputs, 93 | types: [ 94 | objectType({ 95 | name: 'Query', 96 | // eslint-disable-next-line @typescript-eslint/no-empty-function 97 | definition(t) { 98 | t.nonNull.list.field('users', { 99 | type: User, 100 | resolve: () => users, 101 | }) 102 | }, 103 | }), 104 | ], 105 | plugins: [dateTimePlugin(pluginConfig)], 106 | }) 107 | 108 | describe('dateTimePlugin', () => { 109 | describe('basic behavior', () => { 110 | it('should work correctly', () => { 111 | const schema = testSchema({}) 112 | expect(printSchema(schema)).toMatchSnapshot() 113 | }) 114 | 115 | it('should resolve all fields correctly', async () => { 116 | const schema = testSchema({}) 117 | const nodes = await execute({ 118 | schema, 119 | document: QueryAll, 120 | }) 121 | 122 | expect(nodes).toMatchSnapshot() 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /plugins/datetime/README.md: -------------------------------------------------------------------------------- 1 | # @jcm/nexus-plugin-datetime 2 | 3 |

4 | 5 | Patreon Logo 6 | 7 |
8 | 9 | Discord Logo 10 | 11 |

12 | 13 | This plugin adds the field method `dateTime(fieldName, fieldConfig)` to the Nexus Schema Builder, which can be used to create date-time fields with a few helpful methods. 14 | 15 | Sample usage: 16 | 17 | ```typescript 18 | const User = objectType({ 19 | name: 'User', 20 | definition(t) { 21 | // ... 22 | t.dateTime('createdAt') 23 | // ... 24 | }, 25 | }) 26 | ``` 27 | 28 | With the above code, the following schema will be generated: 29 | 30 | ```graphql 31 | """ 32 | Represents an ISO datetime that can be formatted in other formats 33 | """ 34 | type DateTimeField implements DateTimeFieldInterface { 35 | formatted( 36 | format: String! 37 | 38 | """ 39 | Timezone to format the ISO date, defaults to UTC 40 | """ 41 | timezone: String 42 | ): String! 43 | isAfter( 44 | """ 45 | Defaults to the current time if not provided 46 | """ 47 | iso: String 48 | ): Boolean! 49 | isBefore( 50 | """ 51 | Defaults to the current time if not provided 52 | """ 53 | iso: String 54 | ): Boolean! 55 | isBetween( 56 | """ 57 | Defaults to the end of the current day if not provided 58 | """ 59 | isoEnd: String 60 | 61 | """ 62 | Defaults to the start of the current day if not provided 63 | """ 64 | isoStart: String 65 | ): Boolean! 66 | iso: String! 67 | isSame( 68 | """ 69 | Defaults to the current time if not provided 70 | """ 71 | iso: String 72 | ): Boolean! 73 | isSameOrAfter( 74 | """ 75 | Defaults to the current time if not provided 76 | """ 77 | iso: String 78 | ): Boolean! 79 | isSameOrBefore( 80 | """ 81 | Defaults to the current time if not provided 82 | """ 83 | iso: String 84 | ): Boolean! 85 | } 86 | 87 | """ 88 | A object that represents an ISO datetime 89 | """ 90 | interface DateTimeFieldInterface { 91 | formatted( 92 | format: String! 93 | 94 | """ 95 | Timezone to format the ISO date, defaults to UTC 96 | """ 97 | timezone: String 98 | ): String! 99 | isAfter( 100 | """ 101 | Defaults to the current time if not provided 102 | """ 103 | iso: String 104 | ): Boolean! 105 | isBefore( 106 | """ 107 | Defaults to the current time if not provided 108 | """ 109 | iso: String 110 | ): Boolean! 111 | isBetween( 112 | """ 113 | Defaults to the end of the current day if not provided 114 | """ 115 | isoEnd: String 116 | 117 | """ 118 | Defaults to the start of the current day if not provided 119 | """ 120 | isoStart: String 121 | ): Boolean! 122 | iso: String! 123 | isSame( 124 | """ 125 | Defaults to the current time if not provided 126 | """ 127 | iso: String 128 | ): Boolean! 129 | isSameOrAfter( 130 | """ 131 | Defaults to the current time if not provided 132 | """ 133 | iso: String 134 | ): Boolean! 135 | isSameOrBefore( 136 | """ 137 | Defaults to the current time if not provided 138 | """ 139 | iso: String 140 | ): Boolean! 141 | } 142 | 143 | type User { 144 | # ... 145 | createdAt: DateTimeField! 146 | } 147 | 148 | # ... 149 | ``` 150 | -------------------------------------------------------------------------------- /plugins/field-authentication/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.2.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.1.2...@jcm/nexus-plugin-field-authentication@0.2.0) (2021-01-24) 7 | 8 | ### Features 9 | 10 | - add yup-validation plugin ([c3f0c70](https://github.com/JCMais/nexus-plugins/commit/c3f0c703a71414e9a7b59ae5d0e7bf5edacf57fe)) 11 | 12 | ## [0.1.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.1.1...@jcm/nexus-plugin-field-authentication@0.1.2) (2020-12-24) 13 | 14 | ### Bug Fixes 15 | 16 | - **field-authentication:** not having the correct type for the authentication field ([a6fffeb](https://github.com/JCMais/nexus-plugins/commit/a6fffeb238dc7c4451594d106bd515e00f0e707a)) 17 | 18 | ## [0.1.1](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.1.0...@jcm/nexus-plugin-field-authentication@0.1.1) (2020-12-24) 19 | 20 | **Note:** Version bump only for package @jcm/nexus-plugin-field-authentication 21 | 22 | # [0.1.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.0.8...@jcm/nexus-plugin-field-authentication@0.1.0) (2020-12-24) 23 | 24 | ### Features 25 | 26 | - upgrade dependencies and use nexus v1 instead of @nexus/schema ([2f27c77](https://github.com/JCMais/nexus-plugins/commit/2f27c77435060a89e89420ee7a35d9d6b67c2d15)) 27 | 28 | ### BREAKING CHANGES 29 | 30 | - You must be using GraphQL v15 and nexus v1 now. 31 | 32 | ## [0.0.8](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.0.7...@jcm/nexus-plugin-field-authentication@0.0.8) (2020-08-06) 33 | 34 | **Note:** Version bump only for package @jcm/nexus-plugin-field-authentication 35 | 36 | ## [0.0.7](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.0.6...@jcm/nexus-plugin-field-authentication@0.0.7) (2020-08-06) 37 | 38 | ### Features 39 | 40 | - add initial code for elastic-apm-instrumentation plugin ([9dc4323](https://github.com/JCMais/nexus-plugins/commit/9dc4323fcf509bcfe5003131747558f3f26cb5d1)) 41 | 42 | ## [0.0.6](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.0.5...@jcm/nexus-plugin-field-authentication@0.0.6) (2020-08-05) 43 | 44 | **Note:** Version bump only for package @jcm/nexus-plugin-field-authentication 45 | 46 | ## [0.0.5](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.0.4...@jcm/nexus-plugin-field-authentication@0.0.5) (2020-07-31) 47 | 48 | ### Bug Fixes 49 | 50 | - fix datetime and relay-global-id to pass extra field configs back to nexus schema ([86d7bfb](https://github.com/JCMais/nexus-plugins/commit/86d7bfb5b0d3e9fecfd0ad5b59c16c9821a07817)) 51 | 52 | ## [0.0.4](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.0.3...@jcm/nexus-plugin-field-authentication@0.0.4) (2020-07-25) 53 | 54 | **Note:** Version bump only for package @jcm/nexus-plugin-field-authentication 55 | 56 | ## [0.0.3](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.0.2...@jcm/nexus-plugin-field-authentication@0.0.3) (2020-07-25) 57 | 58 | ### Bug Fixes 59 | 60 | - add publishConfig to package.json ([8444527](https://github.com/JCMais/nexus-plugins/commit/8444527c32502e5b91369035cf68e8fa44366d6b)) 61 | 62 | ## [0.0.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-field-authentication@0.0.1...@jcm/nexus-plugin-field-authentication@0.0.2) (2020-07-25) 63 | 64 | ### Bug Fixes 65 | 66 | - stuff ([4fcae7d](https://github.com/JCMais/nexus-plugins/commit/4fcae7d93f09eaa7b4fcdd0b4a3c43f2666e0d1d)) 67 | 68 | ## 0.0.1 (2020-07-25) 69 | 70 | ### Bug Fixes 71 | 72 | - versions on package.json to 0.0.0 ([49283b5](https://github.com/JCMais/nexus-plugins/commit/49283b521f7dc14ea877f96b4e60665d890b736b)) 73 | -------------------------------------------------------------------------------- /plugins/datetime/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.2.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.1.2...@jcm/nexus-plugin-datetime@0.2.0) (2021-01-24) 7 | 8 | ### Features 9 | 10 | - add yup-validation plugin ([c3f0c70](https://github.com/JCMais/nexus-plugins/commit/c3f0c703a71414e9a7b59ae5d0e7bf5edacf57fe)) 11 | 12 | ## [0.1.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.1.1...@jcm/nexus-plugin-datetime@0.1.2) (2020-12-24) 13 | 14 | **Note:** Version bump only for package @jcm/nexus-plugin-datetime 15 | 16 | ## [0.1.1](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.1.0...@jcm/nexus-plugin-datetime@0.1.1) (2020-12-24) 17 | 18 | **Note:** Version bump only for package @jcm/nexus-plugin-datetime 19 | 20 | # [0.1.0](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.10...@jcm/nexus-plugin-datetime@0.1.0) (2020-12-24) 21 | 22 | ### Features 23 | 24 | - upgrade dependencies and use nexus v1 instead of @nexus/schema ([2f27c77](https://github.com/JCMais/nexus-plugins/commit/2f27c77435060a89e89420ee7a35d9d6b67c2d15)) 25 | 26 | ### BREAKING CHANGES 27 | 28 | - You must be using GraphQL v15 and nexus v1 now. 29 | 30 | ## [0.0.10](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.9...@jcm/nexus-plugin-datetime@0.0.10) (2020-08-06) 31 | 32 | **Note:** Version bump only for package @jcm/nexus-plugin-datetime 33 | 34 | ## [0.0.9](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.8...@jcm/nexus-plugin-datetime@0.0.9) (2020-08-05) 35 | 36 | ### Bug Fixes 37 | 38 | - **datetime:** wrong default value for dateTimePluginImportId ([943a629](https://github.com/JCMais/nexus-plugins/commit/943a629881e4fb8998a87f418e6c9b3fb6fe2ae3)) 39 | 40 | ## [0.0.8](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.7...@jcm/nexus-plugin-datetime@0.0.8) (2020-08-05) 41 | 42 | ### Bug Fixes 43 | 44 | - **datetime:** nexusSchemaImportId was wrong ([7b747a8](https://github.com/JCMais/nexus-plugins/commit/7b747a8d2d800e2505e039e96eb5fc66e29fca10)) 45 | 46 | ## [0.0.7](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.6...@jcm/nexus-plugin-datetime@0.0.7) (2020-08-05) 47 | 48 | **Note:** Version bump only for package @jcm/nexus-plugin-datetime 49 | 50 | ## [0.0.6](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.5...@jcm/nexus-plugin-datetime@0.0.6) (2020-07-31) 51 | 52 | ### Bug Fixes 53 | 54 | - datetime plugin not checking if passed dateTimeISOField is null or not ([5f7f11c](https://github.com/JCMais/nexus-plugins/commit/5f7f11cf0183190a730def78bc493e3497ca6f97)) 55 | 56 | ## [0.0.5](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.4...@jcm/nexus-plugin-datetime@0.0.5) (2020-07-31) 57 | 58 | ### Bug Fixes 59 | 60 | - fix datetime and relay-global-id to pass extra field configs back to nexus schema ([86d7bfb](https://github.com/JCMais/nexus-plugins/commit/86d7bfb5b0d3e9fecfd0ad5b59c16c9821a07817)) 61 | 62 | ## [0.0.4](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.3...@jcm/nexus-plugin-datetime@0.0.4) (2020-07-25) 63 | 64 | **Note:** Version bump only for package @jcm/nexus-plugin-datetime 65 | 66 | ## [0.0.3](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.2...@jcm/nexus-plugin-datetime@0.0.3) (2020-07-25) 67 | 68 | ### Bug Fixes 69 | 70 | - add publishConfig to package.json ([8444527](https://github.com/JCMais/nexus-plugins/commit/8444527c32502e5b91369035cf68e8fa44366d6b)) 71 | 72 | ## [0.0.2](https://github.com/JCMais/nexus-plugins/compare/@jcm/nexus-plugin-datetime@0.0.1...@jcm/nexus-plugin-datetime@0.0.2) (2020-07-25) 73 | 74 | ### Bug Fixes 75 | 76 | - stuff ([4fcae7d](https://github.com/JCMais/nexus-plugins/commit/4fcae7d93f09eaa7b4fcdd0b4a3c43f2666e0d1d)) 77 | 78 | ## 0.0.1 (2020-07-25) 79 | 80 | ### Bug Fixes 81 | 82 | - versions on package.json to 0.0.0 ([49283b5](https://github.com/JCMais/nexus-plugins/commit/49283b521f7dc14ea877f96b4e60665d890b736b)) 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JCM / Nexus Plugins 2 | 3 |

4 | 5 | Patreon Logo 6 | 7 |
8 | 9 | Discord Logo 10 | 11 |

12 | 13 | [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/) 14 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 15 | 16 | This is a collection of [`nexus`](https://github.com/graphql-nexus/schema) plugins. 17 | 18 | ## Plugins 19 | 20 | | Plugin | Package | Version | Links | 21 | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | 22 | | **datetime** | [`@jcm/nexus-plugin-datetime`](https://npmjs.com/package/@jcm/nexus-plugin-datetime) | [![latest](https://img.shields.io/npm/v/@jcm/nexus-plugin-datetime/latest.svg)](https://npmjs.com/package/@jcm/nexus-plugin-datetime) | [![README](https://img.shields.io/badge/README--green.svg)](/plugins/datetime/README.md) | 23 | | **elastic-apm-instrumentation** | [`@jcm/nexus-plugin-elastic-apm-instrumentation`](https://npmjs.com/package/@jcm/nexus-plugin-elastic-apm-instrumentation) | [![latest](https://img.shields.io/npm/v/@jcm/nexus-plugin-elastic-apm-instrumentation/latest.svg)](https://npmjs.com/package/@jcm/nexus-plugin-elastic-apm-instrumentation) | [![README](https://img.shields.io/badge/README--green.svg)](/plugins/elastic-apm-instrumentation/README.md) | 24 | | **field-authentication** | [`@jcm/nexus-plugin-field-authentication`](https://npmjs.com/package/@jcm/nexus-plugin-field-authentication) | [![latest](https://img.shields.io/npm/v/@jcm/nexus-plugin-field-authentication/latest.svg)](https://npmjs.com/package/@jcm/nexus-plugin-field-authentication) | [![README](https://img.shields.io/badge/README--green.svg)](/plugins/field-authentication/README.md) | 25 | | **relay-global-id** | [`@jcm/nexus-plugin-relay-global-id`](https://npmjs.com/package/@jcm/nexus-plugin-relay-global-id) | [![latest](https://img.shields.io/npm/v/@jcm/nexus-plugin-relay-global-id/latest.svg)](https://npmjs.com/package/@jcm/nexus-plugin-relay-global-id) | [![README](https://img.shields.io/badge/README--green.svg)](/plugins/relay-global-id/README.md) | 26 | | **relay-mutation** | [`@jcm/nexus-plugin-relay-mutation`](https://npmjs.com/package/@jcm/nexus-plugin-relay-mutation) | [![latest](https://img.shields.io/npm/v/@jcm/nexus-plugin-relay-node-interface/latest.svg)](https://npmjs.com/package/@jcm/nexus-plugin-relay-node-interface) | [![README](https://img.shields.io/badge/README--green.svg)](/plugins/relay-mutation/README.md) | 27 | | **relay-node-interface** | [`@jcm/nexus-plugin-relay-node-interface`](https://npmjs.com/package/@jcm/nexus-plugin-relay-node-interface) | [![latest](https://img.shields.io/npm/v/@jcm/nexus-plugin-relay-node-interface/latest.svg)](https://npmjs.com/package/@jcm/nexus-plugin-relay-node-interface) | [![README](https://img.shields.io/badge/README--green.svg)](/plugins/relay-node-interface/README.md) | 28 | 29 | > This project was initially bootstraped using the [`lerna-typescript` yeoman generator](https://github.com/GaryB432/gb-generators/tree/master/packages/generator-lerna-typescript) 30 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/src/index.ts: -------------------------------------------------------------------------------- 1 | import { plugin, core, interfaceType, queryField, idArg, list, nonNull } from 'nexus' 2 | import { fromGlobalId } from 'graphql-relay' 3 | import { GraphQLResolveInfo } from 'graphql' 4 | 5 | export type RelayNodeInterfacePluginConfig = { 6 | idFetcher: ( 7 | val: { id: string; type: core.AllOutputTypes }, 8 | ctx: core.GetGen<'context'>, 9 | info: GraphQLResolveInfo, 10 | ) => core.ResultValue 11 | resolveType: (object: core.ResultValue) => core.AllOutputTypes 12 | /** 13 | * Used to parse the ID before calling idFetcher - By default this calls fromGlobalId from graphql-relay 14 | */ 15 | idParser?: (id: string) => any 16 | nonNullDefaults?: core.NonNullConfig 17 | } 18 | 19 | // Pretty much based on nodeDefinitions function from 20 | // relay: https://github.com/graphql/graphql-relay-js/blob/8f4ed1ad35805ef233ad9fd1af33abb9c0cad35a/src/node/node.js 21 | export function relayNodeInterfacePlugin(pluginConfig: RelayNodeInterfacePluginConfig) { 22 | const { idFetcher, resolveType, idParser = fromGlobalId, nonNullDefaults } = pluginConfig 23 | 24 | if (!idFetcher) { 25 | throw new Error('idFetcher option is required for relayNodeInterfacePlugin') 26 | } 27 | 28 | if (typeof idFetcher !== 'function') { 29 | throw new Error( 30 | 'idFetcher option must be a function with signature: async ({ id, type }, ctx, info) => T where T is the resolved value for the ID', 31 | ) 32 | } 33 | 34 | if (!resolveType) { 35 | throw new Error('resolveType option is required for relayNodeInterfacePlugin') 36 | } 37 | 38 | if (typeof resolveType !== 'function') { 39 | throw new Error( 40 | 'resolveType option must be an function with signature async (value) => T where T is the type name or object', 41 | ) 42 | } 43 | 44 | return plugin({ 45 | name: 'RelayNodeInterface', 46 | description: 'Creates the Relay Node interface and add node/nodes fields to the Query type', 47 | onInstall(builder) { 48 | if (!builder.hasType('Node')) { 49 | // node interface 50 | builder.addType( 51 | interfaceType({ 52 | name: 'Node', 53 | description: 'An object with a global ID', 54 | definition: (t) => { 55 | ;(nonNullDefaults ? (nonNullDefaults.output ? t.nonNull : t.nullable) : t).id('id', { 56 | description: 'The global ID of the object.', 57 | }) 58 | 59 | // overwrite the resolve type with the client function 60 | // resolveType is just one way to map objs -> to the graphql type they represent 61 | // we could also have simply relied on isTypeOf on the GraphQLObjectType themselves 62 | // but as relay uses this by default, let's keep using it 63 | }, 64 | resolveType, 65 | }), 66 | ) 67 | 68 | // node field 69 | builder.addType( 70 | queryField((t) => { 71 | t.nullable.field('node', { 72 | type: 'Node', 73 | args: { 74 | // defaults to nonNull to keep it backward compatible 75 | id: (nonNullDefaults ? (nonNullDefaults.input ? nonNull : (v: any) => v) : nonNull)( 76 | idArg({ 77 | description: 'The global ID of an object', 78 | }), 79 | ), 80 | }, 81 | description: 'Fetches an object given its global ID', 82 | resolve: (_obj, { id }, ctx, info) => idFetcher(fromGlobalId(id), ctx, info), 83 | }) 84 | }) as core.NexusExtendTypeDef, 85 | ) 86 | 87 | // nodes field 88 | builder.addType( 89 | queryField((t) => { 90 | t.nonNull.list.field('nodes', { 91 | type: 'Node', 92 | args: { 93 | ids: nonNull( 94 | list( 95 | nonNull( 96 | idArg({ 97 | description: 'The global IDs of objects', 98 | }), 99 | ), 100 | ), 101 | ), 102 | }, 103 | description: 'Fetches objects given their global IDs', 104 | resolve: (_obj, { ids }, ctx, info) => 105 | Promise.all( 106 | // @ts-expect-error 107 | ids.map((id) => Promise.resolve(idFetcher(idParser(id), ctx, info))), 108 | ), 109 | }) 110 | }) as core.NexusExtendTypeDef, 111 | ) 112 | } 113 | }, 114 | }) 115 | } 116 | -------------------------------------------------------------------------------- /plugins/field-authentication/tests/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`fieldAuthentication basic behavior format error returning non error value 1`] = ` 4 | Object { 5 | "data": Object { 6 | "me1": Object { 7 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 8 | "name": "user-a", 9 | }, 10 | "me10": null, 11 | "me2": Object { 12 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 13 | "name": "user-a", 14 | }, 15 | "me3": null, 16 | "me4": Object { 17 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 18 | "name": "user-a", 19 | }, 20 | "me5": Object { 21 | "id": "totally-different-than-the-logged", 22 | "name": "another-user", 23 | }, 24 | "me6": Object { 25 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 26 | "name": "user-a", 27 | }, 28 | "me7": null, 29 | "me8": null, 30 | "me9": null, 31 | }, 32 | "errors": Array [ 33 | [GraphQLError: You need to be authenticated], 34 | [GraphQLError: You need to be authenticated], 35 | [GraphQLError: You need to be authenticated], 36 | [GraphQLError: You need to be authenticated], 37 | ], 38 | } 39 | `; 40 | 41 | exports[`fieldAuthentication basic behavior should resolve all fields correctly with default plugin values - authenticated 1`] = ` 42 | Object { 43 | "data": Object { 44 | "me1": Object { 45 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 46 | "name": "user-a", 47 | }, 48 | "me10": null, 49 | "me2": Object { 50 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 51 | "name": "user-a", 52 | }, 53 | "me3": null, 54 | "me4": Object { 55 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 56 | "name": "user-a", 57 | }, 58 | "me5": Object { 59 | "id": "totally-different-than-the-logged", 60 | "name": "another-user", 61 | }, 62 | "me6": Object { 63 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 64 | "name": "user-a", 65 | }, 66 | "me7": null, 67 | "me8": null, 68 | "me9": null, 69 | }, 70 | "errors": Array [ 71 | [GraphQLError: Invalid - Authenticated], 72 | [GraphQLError: Field authentication for Query.me10 expected a boolean or [boolean, resolvedValue] tuple, saw {"something":true} instead], 73 | [GraphQLError: You shall not pass], 74 | ], 75 | } 76 | `; 77 | 78 | exports[`fieldAuthentication basic behavior should resolve all fields correctly with default plugin values - unauthenticated 1`] = ` 79 | Object { 80 | "data": Object { 81 | "me1": Object { 82 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 83 | "name": "user-a", 84 | }, 85 | "me10": null, 86 | "me2": null, 87 | "me3": Object { 88 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 89 | "name": "user-a", 90 | }, 91 | "me4": Object { 92 | "id": "unauthenticated", 93 | "name": "guest", 94 | }, 95 | "me5": Object { 96 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 97 | "name": "user-a", 98 | }, 99 | "me6": null, 100 | "me7": Object { 101 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 102 | "name": "user-a", 103 | }, 104 | "me8": null, 105 | "me9": null, 106 | }, 107 | "errors": Array [ 108 | [GraphQLError: Invalid - Unauthenticated], 109 | [GraphQLError: Field authentication for Query.me10 expected a boolean or [boolean, resolvedValue] tuple, saw {"something":true} instead], 110 | [GraphQLError: You shall not pass], 111 | ], 112 | } 113 | `; 114 | 115 | exports[`fieldAuthentication basic behavior throwErrorOnFailedAuthenticationByDefault = true / defaultErrorMessage = something 1`] = ` 116 | Object { 117 | "data": Object { 118 | "me1": Object { 119 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 120 | "name": "user-a", 121 | }, 122 | "me10": null, 123 | "me2": Object { 124 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 125 | "name": "user-a", 126 | }, 127 | "me3": null, 128 | "me4": Object { 129 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 130 | "name": "user-a", 131 | }, 132 | "me5": Object { 133 | "id": "totally-different-than-the-logged", 134 | "name": "another-user", 135 | }, 136 | "me6": Object { 137 | "id": "8a291df8-bbcf-11ea-9db0-ff6b23713451", 138 | "name": "user-a", 139 | }, 140 | "me7": null, 141 | "me8": null, 142 | "me9": null, 143 | }, 144 | "errors": Array [ 145 | [GraphQLError: You need to be authenticated], 146 | [GraphQLError: Invalid - Authenticated], 147 | [GraphQLError: Field authentication for Query.me10 expected a boolean or [boolean, resolvedValue] tuple, saw {"something":true} instead], 148 | [GraphQLError: You shall not pass], 149 | ], 150 | } 151 | `; 152 | -------------------------------------------------------------------------------- /plugins/relay-global-id/src/index.ts: -------------------------------------------------------------------------------- 1 | import { plugin, dynamicOutputMethod, core } from 'nexus' 2 | 3 | import { toGlobalId } from 'graphql-relay' 4 | 5 | export type RelayGlobalIdPluginConfig< 6 | TypeName extends string = any, 7 | FieldName extends string = any 8 | > = { 9 | nexusFieldName?: string 10 | nexusSchemaImportId?: string 11 | relayGlobalIdPluginImportId?: string 12 | 13 | /** 14 | * Defaults to true - Adds a new ID! field called `raw${uppercase(fieldName)}`, where `fieldName` is `t.relayGlobalId(fieldName)`, 15 | * with the original value of the field. 16 | * 17 | * It's also possible to pass a string with a different name for the field 18 | * 19 | * You can also set this in a per field basis 20 | */ 21 | shouldAddRawId?: boolean | string 22 | 23 | /** 24 | * If no resolve is supplied, the default value to be used as global ID will be root[field] 25 | * 26 | * This defaults to the fieldName used when calling relayGlobalId(fieldName) 27 | * 28 | * If a resolve is passed, this is ignored 29 | * 30 | * You can also set this in a per field basis 31 | */ 32 | field?: string 33 | 34 | /** 35 | * You can use this to specificy your own resolve function for the ID 36 | * 37 | * You can also set this in a per field basis 38 | */ 39 | resolve?: core.FieldResolver 40 | 41 | nonNullDefaults?: Omit 42 | } 43 | 44 | export type RelayGlobalIdNexusFieldConfig< 45 | TypeName extends string = any, 46 | FieldName extends string = any 47 | > = { 48 | /** 49 | * If no resolve is supplied, the default value to be used as global ID will be root[field] 50 | * 51 | * This defaults to the fieldName used when calling relayGlobalId(fieldName) 52 | * 53 | * If a resolve is passed, this is ignored 54 | */ 55 | field?: string 56 | 57 | /** 58 | * Defaults to true - Adds a new ID! field called `raw${uppercase(fieldName)}`, where `fieldName` is `t.relayGlobalId(fieldName)`, 59 | * with the original value of the field. 60 | * 61 | * It's also possible to pass a string with a different name for the field 62 | */ 63 | shouldAddRawId?: boolean | string 64 | 65 | /** 66 | * Defaults to the parent type of the current field. 67 | */ 68 | typeName?: string 69 | 70 | /** 71 | * You can use this to specificy your own resolve function for the ID 72 | */ 73 | resolve?: core.FieldResolver 74 | } & NexusGenPluginFieldConfig 75 | 76 | export function relayGlobalIdPlugin(pluginConfig: RelayGlobalIdPluginConfig = {}) { 77 | const { 78 | nexusFieldName = 'relayGlobalId', 79 | relayGlobalIdPluginImportId = '@jcm/nexus-plugin-relay-global-id', 80 | shouldAddRawId: shouldAddRawIdPluginConfig = true, 81 | field: fieldPluginConfig, 82 | resolve: resolvePluginConfig, 83 | nonNullDefaults, 84 | } = pluginConfig 85 | 86 | return plugin({ 87 | name: 'RelayGlobalId', 88 | description: 'add t.relayGlobalId(field) to the schema builder', 89 | fieldDefTypes: [ 90 | core.printedGenTypingImport({ 91 | module: relayGlobalIdPluginImportId, 92 | bindings: ['RelayGlobalIdNexusFieldConfig'], 93 | }), 94 | ], 95 | // we want to add a extension 96 | onInstall(builder) { 97 | builder.addType( 98 | dynamicOutputMethod({ 99 | name: nexusFieldName, 100 | typeDefinition: `( 101 | fieldName: FieldName, 102 | config?: RelayGlobalIdNexusFieldConfig 103 | ): void`, 104 | factory({ typeName: parentTypeName, typeDef: t, args: factoryArgs }) { 105 | const [fieldName, fieldConfig = {}] = factoryArgs as [ 106 | string, 107 | RelayGlobalIdNexusFieldConfig, 108 | ] 109 | 110 | const { 111 | field = fieldPluginConfig || fieldName, 112 | shouldAddRawId = shouldAddRawIdPluginConfig, 113 | typeName = parentTypeName, 114 | resolve: resolveFn = resolvePluginConfig, 115 | ...remainingFieldConfig 116 | } = fieldConfig 117 | 118 | ;(nonNullDefaults ? (nonNullDefaults.output ? t.nonNull : t.nullable) : t).id( 119 | fieldName, 120 | { 121 | ...remainingFieldConfig, 122 | async resolve(root, args, ctx, info) { 123 | const resolved = resolveFn ? await resolveFn(root, args, ctx, info) : root[field] 124 | return resolved && toGlobalId(typeName, resolved) 125 | }, 126 | }, 127 | ) 128 | 129 | if (shouldAddRawId) { 130 | ;(nonNullDefaults ? (nonNullDefaults.output ? t.nonNull : t.nullable) : t).id( 131 | typeof shouldAddRawId === 'string' 132 | ? shouldAddRawId 133 | : `raw${fieldName.charAt(0).toUpperCase()}${fieldName.slice(1)}`, 134 | { 135 | resolve: resolveFn ? resolveFn : (root) => root[field], 136 | }, 137 | ) 138 | } 139 | }, 140 | }), 141 | ) 142 | }, 143 | }) 144 | } 145 | -------------------------------------------------------------------------------- /plugins/relay-node-interface/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { execute, parse, printType } from 'graphql' 2 | import { toGlobalId } from 'graphql-relay' 3 | import { makeSchema, objectType } from 'nexus' 4 | 5 | import { RelayNodeInterfacePluginConfig, relayNodeInterfacePlugin } from '../src' 6 | 7 | const users = [ 8 | { 9 | id: '8a291df8-bbcf-11ea-9db0-ff6b23713451', 10 | name: 'user-a', 11 | }, 12 | { 13 | id: '710c15fa-c2af-11ea-86f1-7f51ff98b69c', 14 | name: 'user-b', 15 | }, 16 | { 17 | id: '710c16cc-c2af-11ea-86f1-479adc4ed46f', 18 | name: 'user-c', 19 | }, 20 | ] 21 | 22 | const posts = [ 23 | { id: '710c173a-c2af-11ea-86f1-83226be355fb', title: 'post-a' }, 24 | { id: 'a7a5c06e-c2d9-11ea-86f1-bfb838f3b7c6', title: 'post-b' }, 25 | ] 26 | 27 | const comments = [ 28 | { 29 | id: 'abc', 30 | text: 'comment-a', 31 | }, 32 | ] 33 | 34 | const User = objectType({ 35 | name: 'User', 36 | definition(t) { 37 | t.implements('Node') 38 | t.id('id') 39 | t.string('name') 40 | }, 41 | }) 42 | const Post = objectType({ 43 | name: 'Post', 44 | definition(t) { 45 | t.implements('Node') 46 | t.id('id') 47 | t.string('title') 48 | }, 49 | }) 50 | const Comment = objectType({ 51 | name: 'Comment', 52 | definition(t) { 53 | t.id('id') 54 | t.string('text') 55 | }, 56 | }) 57 | 58 | const QueryAll = parse( 59 | ` 60 | fragment nodeInfo on Node { 61 | __typename 62 | id 63 | } 64 | 65 | fragment userInfo on User { 66 | name 67 | } 68 | 69 | query QueryAll ($user1Id: ID! $user2Id: ID! $post1Id: ID! $post2Id: ID!) { 70 | user1: node(id: $user1Id) { 71 | ...nodeInfo 72 | ...userInfo 73 | } 74 | user2: node(id: $user2Id) { 75 | ...nodeInfo 76 | ...userInfo 77 | } 78 | post1: node(id: $post1Id) { 79 | ...nodeInfo 80 | ...userInfo 81 | } 82 | post2: node(id: $post2Id) { 83 | ...nodeInfo 84 | ...postInfo 85 | } 86 | 87 | nodes(ids: [$user1Id, $post1Id, $user2Id, $post2Id]) { 88 | ...nodeInfo 89 | ...userInfo 90 | ...postInfo 91 | } 92 | }`, 93 | ) 94 | const QueryNode = parse( 95 | ` 96 | fragment nodeInfo on Node { 97 | __typename 98 | id 99 | } 100 | 101 | query QueryNode ($nodeId: ID!) { 102 | node(id: $nodeId) { 103 | ...nodeInfo 104 | } 105 | }`, 106 | ) 107 | 108 | const testSchema = (pluginConfig: RelayNodeInterfacePluginConfig, outputs = false) => 109 | makeSchema({ 110 | outputs, 111 | types: [ 112 | User, 113 | Post, 114 | Comment, 115 | objectType({ 116 | name: 'Query', 117 | // eslint-disable-next-line @typescript-eslint/no-empty-function 118 | definition(_t) {}, 119 | }), 120 | ], 121 | plugins: [relayNodeInterfacePlugin(pluginConfig)], 122 | }) 123 | 124 | const testIdFetcher: RelayNodeInterfacePluginConfig['idFetcher'] = ({ id, type }, _ctx, _info) => { 125 | if (type === 'User') return users.find((u) => u.id === id) 126 | if (type === 'Post') return posts.find((p) => p.id === id) 127 | if (type === 'Comment') return comments.find((p) => p.id === id) 128 | return null 129 | } 130 | 131 | // @ts-ignore 132 | const testResolveType: RelayNodeInterfacePluginConfig['resolveType'] = (o) => { 133 | if (users.includes(o)) return 'User' 134 | if (posts.includes(o)) return 'Post' 135 | 136 | return null 137 | } 138 | 139 | const testPluginConfig = { 140 | idFetcher: testIdFetcher, 141 | resolveType: testResolveType, 142 | } 143 | 144 | describe('relayNodeInterfacePlugin', () => { 145 | describe('basic behavior', () => { 146 | it('should work correctly', () => { 147 | const schema = testSchema(testPluginConfig) 148 | expect(printType(schema.getType('Query')!)).toMatchSnapshot() 149 | }) 150 | 151 | it('should resolve all fields correctly', async () => { 152 | const schema = testSchema(testPluginConfig) 153 | const nodes = await execute({ 154 | schema, 155 | document: QueryAll, 156 | variableValues: { 157 | user1Id: toGlobalId('User', users[0].id), 158 | user2Id: toGlobalId('User', users[1].id), 159 | post1Id: toGlobalId('Post', posts[0].id), 160 | post2Id: toGlobalId('Post', posts[1].id), 161 | }, 162 | }) 163 | 164 | expect(nodes).toMatchSnapshot() 165 | }) 166 | 167 | it('should resolve not found fields as null correctly', async () => { 168 | const schema = testSchema(testPluginConfig) 169 | const nodes = await execute({ 170 | schema, 171 | document: QueryAll, 172 | variableValues: { 173 | user1Id: toGlobalId('User', users[0].id), 174 | user2Id: toGlobalId('User', users[1].id), 175 | post1Id: toGlobalId('Post', 'abcdef'), 176 | post2Id: toGlobalId('Post', posts[1].id), 177 | }, 178 | }) 179 | 180 | expect(nodes).toMatchSnapshot() 181 | }) 182 | 183 | it('should throw error for invalid type', async () => { 184 | const schema = testSchema(testPluginConfig) 185 | const nodes = await execute({ 186 | schema, 187 | document: QueryNode, 188 | variableValues: { 189 | nodeId: toGlobalId('Comment', comments[0].id), 190 | }, 191 | }) 192 | 193 | expect(nodes).toMatchSnapshot() 194 | }) 195 | }) 196 | }) 197 | -------------------------------------------------------------------------------- /plugins/elastic-apm-instrumentation/src/index.ts: -------------------------------------------------------------------------------- 1 | import { plugin, core } from 'nexus' 2 | import { 3 | isLeafType, 4 | isWrappingType, 5 | ResponsePath, 6 | GraphQLOutputType, 7 | GraphQLWrappingType, 8 | } from 'graphql' 9 | import type * as Agent from 'elastic-apm-node' 10 | 11 | // Most of this code is inspired on the authorize plugin from nexus 12 | 13 | const fieldDefTypes = core.printedGenTyping({ 14 | optional: true, 15 | name: 'shouldCreateApmSpan', 16 | description: ` 17 | This can be used to override the elastic apm instrumentation settings and force 18 | a span to [not] be created for this field. 19 | `, 20 | type: 'boolean', 21 | }) 22 | 23 | export interface ElasticApmInstrumentationPluginConfig { 24 | apmAgent: typeof Agent 25 | 26 | /** 27 | * Should create spans by default for all fields that pass the ignore checks? 28 | * 29 | * Defaults to true 30 | */ 31 | shouldCreateApmSpanByDefault?: boolean 32 | 33 | /** 34 | * Should leaf fields be ignored? 35 | * 36 | * Defaults to true 37 | */ 38 | shouldIgnoreLeafFieldsByDefault?: boolean 39 | 40 | /** 41 | * Pass an array of fields to be ignored from span creation, each item can be a string or a RegExp 42 | * If a string, it's also possible to use wildcards, like: `PageInfo.*` 43 | */ 44 | ignoredFields?: Array 45 | 46 | /** 47 | * Pass an array of types to be ignored from span creation, each item can be a string or a RegExp 48 | */ 49 | ignoredTypes?: Array 50 | } 51 | 52 | const isValidMatch = (strA: string, strB: string) => strA === strB || strA === '*' 53 | 54 | const isPatternMatchingField = (pattern: string, field: string) => { 55 | if (pattern === '*') return true 56 | 57 | const piecesPatternPieces = pattern.split('.') 58 | const fieldPatternPieces = field.split('.') 59 | 60 | if (piecesPatternPieces.length !== 2 || fieldPatternPieces.length !== 2) { 61 | throw new Error('Pattern/Field path must have exactly 2 parts, ParentType.childType') 62 | } 63 | 64 | return ( 65 | isValidMatch(piecesPatternPieces[0], fieldPatternPieces[0]) && 66 | isValidMatch(piecesPatternPieces[1], fieldPatternPieces[1]) 67 | ) 68 | } 69 | 70 | const getFirstNonWrappingType = ( 71 | parent: GraphQLOutputType, 72 | ): Exclude => { 73 | if (isWrappingType(parent)) return getFirstNonWrappingType(parent.ofType) 74 | 75 | return parent 76 | } 77 | 78 | const getGraphQLFieldPath = (path?: ResponsePath): string => 79 | (path 80 | ? [ 81 | getGraphQLFieldPath(path.prev), 82 | path.prev ? path.key : `${path.typename}.${path.key}`, 83 | ].filter((v) => !!v) 84 | : [] 85 | ).join('.') 86 | 87 | export const elasticApmInstrumentationPlugin = ( 88 | pluginConfig: ElasticApmInstrumentationPluginConfig, 89 | ) => { 90 | const { 91 | apmAgent, 92 | shouldCreateApmSpanByDefault = true, 93 | shouldIgnoreLeafFieldsByDefault = true, 94 | ignoredFields = [], 95 | ignoredTypes = [], 96 | } = pluginConfig 97 | 98 | return plugin({ 99 | name: 'ElasticApmReporter', 100 | description: 'Creates separated spans for each resolved field', 101 | fieldDefTypes: fieldDefTypes, 102 | onCreateFieldResolver(config) { 103 | const firstNonWrappingType = getFirstNonWrappingType(config.fieldConfig.type) 104 | const isLeaf = isLeafType(firstNonWrappingType) 105 | 106 | const parentTypeName = config.parentTypeConfig.name 107 | const fieldName = config.fieldConfig.name 108 | const qualifiedName = `${parentTypeName}.${fieldName}` 109 | 110 | const isIgnoredField = ignoredFields.some((ignoredField) => 111 | ignoredField instanceof RegExp 112 | ? ignoredField.test(qualifiedName) 113 | : isPatternMatchingField(ignoredField, qualifiedName), 114 | ) 115 | 116 | const isIgnoredType = ignoredTypes.some((type) => 117 | type instanceof RegExp 118 | ? type.test(firstNonWrappingType.name) 119 | : type === firstNonWrappingType.name, 120 | ) 121 | 122 | const shouldCreateApmSpan = 123 | config.fieldConfig.extensions?.nexus?.config?.shouldCreateApmSpan ?? 124 | (shouldCreateApmSpanByDefault && 125 | (!shouldIgnoreLeafFieldsByDefault || !isLeaf) && 126 | !isIgnoredField && 127 | !isIgnoredType) 128 | 129 | // console.log({ 130 | // qualifiedName, 131 | // shouldCreateApmSpan, 132 | // firstNonWrappingType, 133 | // isLeaf, 134 | // isIgnoredField, 135 | // isIgnoredType, 136 | // type: config.fieldConfig.type, 137 | // }) 138 | 139 | if (!shouldCreateApmSpan) { 140 | return 141 | } 142 | 143 | return function (root, args, ctx, info, next) { 144 | const isApmAgentStartedAndHasTransaction = 145 | apmAgent.isStarted() && apmAgent.currentTransaction 146 | 147 | if (!isApmAgentStartedAndHasTransaction) { 148 | return next(root, args, ctx, info) 149 | } 150 | 151 | const fieldPath = getGraphQLFieldPath(info.path) 152 | const span = apmAgent.startSpan( 153 | `GraphQL resolver: ${fieldPath}`, 154 | 'db', 155 | 'graphql', 156 | 'resolve', 157 | ) 158 | 159 | let toComplete 160 | try { 161 | toComplete = next(root, args, ctx, info) 162 | } catch (e) { 163 | toComplete = Promise.reject(e) 164 | } 165 | return plugin.completeValue( 166 | toComplete, 167 | (val) => { 168 | span?.end() 169 | return val 170 | }, 171 | (error) => { 172 | span?.end() 173 | throw error 174 | }, 175 | ) 176 | } 177 | }, 178 | }) 179 | } 180 | -------------------------------------------------------------------------------- /plugins/relay-mutation/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { execute, parse, printSchema } from 'graphql' 2 | import { makeSchema, mutationField, queryField } from 'nexus' 3 | 4 | import { 5 | RelayMutationPluginConfig, 6 | RelayMutationNexusFieldConfig, 7 | relayMutationPlugin, 8 | } from '../src' 9 | 10 | // @ts-ignore 11 | const inputFields = (t2) => { 12 | t2.nonNull.int('number1') 13 | t2.nonNull.int('number2') 14 | } 15 | // @ts-ignore 16 | const outputFields = (t2) => { 17 | t2.nonNull.int('result') 18 | } 19 | 20 | const mutationCorrect = mutationField((t) => { 21 | // @ts-expect-error 22 | t.relayMutation('add', { 23 | inputFields, 24 | outputFields, 25 | mutateAndGetPayload(_root, input, _ctx, _info) { 26 | return { 27 | result: input.number1 + input.number2, 28 | } 29 | }, 30 | } as RelayMutationNexusFieldConfig) 31 | }) 32 | 33 | const mutationOutsideMutationType = queryField((t) => { 34 | // @ts-expect-error 35 | t.relayMutation('add', {}) 36 | }) 37 | 38 | const mutationWithInvalidInputFields = mutationField((t) => { 39 | // @ts-expect-error 40 | t.relayMutation('add', { 41 | inputFields: true, 42 | mutateAndGetPayload() { 43 | return {} 44 | }, 45 | }) 46 | }) 47 | 48 | const mutationWithInvalidOutputFields = mutationField((t) => { 49 | // @ts-expect-error 50 | t.relayMutation('add', { 51 | outputFields: true, 52 | mutateAndGetPayload() { 53 | return {} 54 | }, 55 | }) 56 | }) 57 | 58 | const mutationMissingMutateAndGet = mutationField((t) => { 59 | // @ts-expect-error 60 | t.relayMutation('add', {}) 61 | }) 62 | 63 | const mutationWithSyncMutateAndGetError = mutationField((t) => { 64 | // @ts-expect-error 65 | t.relayMutation('add', { 66 | inputFields, 67 | outputFields, 68 | mutateAndGetPayload() { 69 | throw new Error('That happened') 70 | }, 71 | }) 72 | }) 73 | 74 | const mutationWithAsyncMutateAndGetError = mutationField((t) => { 75 | // @ts-expect-error 76 | t.relayMutation('add', { 77 | inputFields, 78 | outputFields, 79 | async mutateAndGetPayload() { 80 | throw new Error('That happened') 81 | }, 82 | }) 83 | }) 84 | 85 | const AddNumbers = parse( 86 | `mutation AddNumbers($input: AddInput) { 87 | add(input: $input) { 88 | result 89 | } 90 | }`, 91 | ) 92 | 93 | const testSchema = ({ 94 | pluginConfig = {}, 95 | outputs = false, 96 | shouldGenerateArtifacts = false, 97 | types = [mutationCorrect], 98 | }: { 99 | pluginConfig?: RelayMutationPluginConfig 100 | outputs?: any 101 | shouldGenerateArtifacts?: boolean 102 | types?: any 103 | } = {}) => 104 | makeSchema({ 105 | outputs, 106 | shouldGenerateArtifacts, 107 | types, 108 | plugins: [relayMutationPlugin(pluginConfig)], 109 | }) 110 | 111 | const variableValues = { input: { number1: 1, number2: 2 } } 112 | 113 | describe('relayMutationPlugin', () => { 114 | describe('basic behavior', () => { 115 | it('should work correctly', () => { 116 | const schema = testSchema() 117 | expect(printSchema(schema)).toMatchSnapshot() 118 | }) 119 | 120 | // uncoment to test the types 121 | // it('should generate the types', () => { 122 | // testSchema({ 123 | // outputs: { 124 | // typegen: path.join(__dirname, 'generated/types.out'), 125 | // schema: false, 126 | // }, 127 | // shouldGenerateArtifacts: true, 128 | // }) 129 | // }) 130 | 131 | it('should resolve all fields correctly', async () => { 132 | const schema = testSchema() 133 | const nodes = await execute({ 134 | schema, 135 | document: AddNumbers, 136 | variableValues, 137 | }) 138 | 139 | expect(nodes).toMatchSnapshot() 140 | }) 141 | }) 142 | 143 | describe('plugin DX', () => { 144 | it('should throw an error when trying to add mutation outside Mutation type', () => { 145 | expect(() => 146 | testSchema({ types: [mutationOutsideMutationType] }), 147 | ).toThrowErrorMatchingInlineSnapshot( 148 | `"Cannot call t.relayMutation inside a type other than Mutation"`, 149 | ) 150 | }) 151 | 152 | it('should throw an error when inputFields/outputFields are not functions', () => { 153 | expect(() => 154 | testSchema({ types: [mutationWithInvalidInputFields] }), 155 | ).toThrowErrorMatchingInlineSnapshot( 156 | `"Field config \\"inputFields\\" passed to t.relayMutation must be a function"`, 157 | ) 158 | expect(() => 159 | testSchema({ types: [mutationWithInvalidOutputFields] }), 160 | ).toThrowErrorMatchingInlineSnapshot( 161 | `"Field config \\"outputFields\\" passed to t.relayMutation must be a function"`, 162 | ) 163 | }) 164 | 165 | it('should throw an error when missing mutateAndGetPayload', () => { 166 | expect(() => 167 | testSchema({ types: [mutationMissingMutateAndGet] }), 168 | ).toThrowErrorMatchingInlineSnapshot( 169 | `"Field config \\"mutateAndGetPayload\\" passed to t.relayMutation must be a function"`, 170 | ) 171 | }) 172 | }) 173 | 174 | describe('error handling', () => { 175 | it('should throw error caught on sync mutateAndGetPayload call', async () => { 176 | const schema = testSchema({ types: [mutationWithSyncMutateAndGetError] }) 177 | const nodes = await execute({ 178 | schema, 179 | document: AddNumbers, 180 | variableValues, 181 | }) 182 | 183 | expect(nodes).toMatchSnapshot() 184 | }) 185 | 186 | it('should throw error caught on async mutateAndGetPayload call', async () => { 187 | const schema = testSchema({ types: [mutationWithAsyncMutateAndGetError] }) 188 | const nodes = await execute({ 189 | schema, 190 | document: AddNumbers, 191 | variableValues, 192 | }) 193 | 194 | expect(nodes).toMatchSnapshot() 195 | }) 196 | }) 197 | }) 198 | -------------------------------------------------------------------------------- /plugins/datetime/tests/__snapshots__/index.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`dateTimePlugin basic behavior should resolve all fields correctly 1`] = ` 4 | Object { 5 | "data": Object { 6 | "users": Array [ 7 | Object { 8 | "default_future": Object { 9 | "formatted": "2120-07-02", 10 | "isAfter": true, 11 | "isBefore": false, 12 | "isBetween": false, 13 | "isSame": false, 14 | "isSameOrAfter": true, 15 | "isSameOrBefore": false, 16 | "iso": "2120-07-02T01:28:59.391Z", 17 | }, 18 | "default_past": Object { 19 | "formatted": "2020-07-02", 20 | "isAfter": false, 21 | "isBefore": true, 22 | "isBetween": false, 23 | "isSame": false, 24 | "isSameOrAfter": false, 25 | "isSameOrBefore": true, 26 | "iso": "2020-07-02T01:28:59.391Z", 27 | }, 28 | "nullable": null, 29 | "param": Object { 30 | "formatted": "2020-07-02", 31 | "isAfter": false, 32 | "isBefore": true, 33 | "isBetween": false, 34 | "isSame": false, 35 | "isSameOrAfter": false, 36 | "isSameOrBefore": true, 37 | "iso": "2020-07-02T01:28:59.391Z", 38 | }, 39 | }, 40 | Object { 41 | "default_future": Object { 42 | "formatted": "2120-07-02", 43 | "isAfter": true, 44 | "isBefore": false, 45 | "isBetween": false, 46 | "isSame": false, 47 | "isSameOrAfter": true, 48 | "isSameOrBefore": false, 49 | "iso": "2120-07-02T01:28:59.391Z", 50 | }, 51 | "default_past": Object { 52 | "formatted": "2020-07-12", 53 | "isAfter": false, 54 | "isBefore": true, 55 | "isBetween": false, 56 | "isSame": false, 57 | "isSameOrAfter": false, 58 | "isSameOrBefore": true, 59 | "iso": "2020-07-12T01:28:59.391Z", 60 | }, 61 | "nullable": null, 62 | "param": Object { 63 | "formatted": "2020-07-12", 64 | "isAfter": false, 65 | "isBefore": false, 66 | "isBetween": true, 67 | "isSame": true, 68 | "isSameOrAfter": true, 69 | "isSameOrBefore": true, 70 | "iso": "2020-07-12T01:28:59.391Z", 71 | }, 72 | }, 73 | Object { 74 | "default_future": Object { 75 | "formatted": "2120-07-02", 76 | "isAfter": true, 77 | "isBefore": false, 78 | "isBetween": false, 79 | "isSame": false, 80 | "isSameOrAfter": true, 81 | "isSameOrBefore": false, 82 | "iso": "2120-07-02T01:28:59.391Z", 83 | }, 84 | "default_past": Object { 85 | "formatted": "2020-07-22", 86 | "isAfter": false, 87 | "isBefore": true, 88 | "isBetween": false, 89 | "isSame": false, 90 | "isSameOrAfter": false, 91 | "isSameOrBefore": true, 92 | "iso": "2020-07-22T01:28:59.391Z", 93 | }, 94 | "nullable": null, 95 | "param": Object { 96 | "formatted": "2020-07-22", 97 | "isAfter": true, 98 | "isBefore": false, 99 | "isBetween": false, 100 | "isSame": false, 101 | "isSameOrAfter": true, 102 | "isSameOrBefore": false, 103 | "iso": "2020-07-22T01:28:59.391Z", 104 | }, 105 | }, 106 | ], 107 | }, 108 | } 109 | `; 110 | 111 | exports[`dateTimePlugin basic behavior should work correctly 1`] = ` 112 | "type Query { 113 | users: [User]! 114 | } 115 | 116 | type User { 117 | id: ID! 118 | createdAt: DateTimeField! 119 | createdAt2: DateTimeField! 120 | createdAt3: DateTimeField 121 | } 122 | 123 | \\"\\"\\"A object that represents an ISO datetime\\"\\"\\" 124 | interface DateTimeFieldInterface { 125 | iso: String! 126 | formatted( 127 | format: String! 128 | 129 | \\"\\"\\"Timezone to format the ISO date, defaults to UTC\\"\\"\\" 130 | timezone: String 131 | ): String! 132 | isAfter( 133 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 134 | iso: String 135 | ): Boolean! 136 | isBefore( 137 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 138 | iso: String 139 | ): Boolean! 140 | isSameOrAfter( 141 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 142 | iso: String 143 | ): Boolean! 144 | isSameOrBefore( 145 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 146 | iso: String 147 | ): Boolean! 148 | isSame( 149 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 150 | iso: String 151 | ): Boolean! 152 | isBetween( 153 | \\"\\"\\"Defaults to the start of the current day if not provided\\"\\"\\" 154 | isoStart: String 155 | 156 | \\"\\"\\"Defaults to the end of the current day if not provided\\"\\"\\" 157 | isoEnd: String 158 | ): Boolean! 159 | } 160 | 161 | \\"\\"\\"Represents an ISO datetime that can be formatted in other formats\\"\\"\\" 162 | type DateTimeField implements DateTimeFieldInterface { 163 | iso: String! 164 | formatted( 165 | format: String! 166 | 167 | \\"\\"\\"Timezone to format the ISO date, defaults to UTC\\"\\"\\" 168 | timezone: String 169 | ): String! 170 | isAfter( 171 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 172 | iso: String 173 | ): Boolean! 174 | isBefore( 175 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 176 | iso: String 177 | ): Boolean! 178 | isSameOrAfter( 179 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 180 | iso: String 181 | ): Boolean! 182 | isSameOrBefore( 183 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 184 | iso: String 185 | ): Boolean! 186 | isSame( 187 | \\"\\"\\"Defaults to the current time if not provided\\"\\"\\" 188 | iso: String 189 | ): Boolean! 190 | isBetween( 191 | \\"\\"\\"Defaults to the start of the current day if not provided\\"\\"\\" 192 | isoStart: String 193 | 194 | \\"\\"\\"Defaults to the end of the current day if not provided\\"\\"\\" 195 | isoEnd: String 196 | ): Boolean! 197 | } 198 | " 199 | `; 200 | -------------------------------------------------------------------------------- /plugins/field-authentication/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { execute, parse } from 'graphql' 2 | import { makeSchema, objectType } from 'nexus' 3 | 4 | import { fieldAuthenticationPlugin, FieldAuthenticationPluginConfig } from '../src' 5 | 6 | const user = { 7 | id: '8a291df8-bbcf-11ea-9db0-ff6b23713451', 8 | name: 'user-a', 9 | } 10 | 11 | const User = objectType({ 12 | name: 'User', 13 | definition(t) { 14 | t.id('id') 15 | t.string('name') 16 | }, 17 | }) 18 | 19 | const QueryAll = parse( 20 | ` 21 | 22 | fragment userInfo on User { 23 | id 24 | name 25 | } 26 | 27 | query QueryAll { 28 | me1 { 29 | ...userInfo 30 | } 31 | me2 { 32 | ...userInfo 33 | } 34 | me3 { 35 | ...userInfo 36 | } 37 | me4 { 38 | ...userInfo 39 | } 40 | me5 { 41 | ...userInfo 42 | } 43 | me6 { 44 | ...userInfo 45 | } 46 | me7 { 47 | ...userInfo 48 | } 49 | me8 { 50 | ...userInfo 51 | } 52 | me9 { 53 | ...userInfo 54 | } 55 | me10 { 56 | ...userInfo 57 | } 58 | }`, 59 | ) 60 | 61 | const loggedOutUser = { 62 | id: 'unauthenticated', 63 | name: 'guest', 64 | } 65 | 66 | const fakeUser = { 67 | id: 'totally-different-than-the-logged', 68 | name: 'another-user', 69 | } 70 | 71 | const testSchema = (pluginConfig: FieldAuthenticationPluginConfig, outputs = false) => 72 | makeSchema({ 73 | outputs, 74 | types: [ 75 | User, 76 | objectType({ 77 | name: 'Query', 78 | definition(t) { 79 | // no authentication check 80 | t.field('me1', { 81 | type: User, 82 | resolve: () => user, 83 | }) 84 | // true authentication check 85 | t.field('me2', { 86 | type: User, 87 | // @ts-expect-error 88 | authentication: true, 89 | resolve: () => user, 90 | }) 91 | // false authentication check 92 | t.field('me3', { 93 | type: User, 94 | // @ts-expect-error 95 | authentication: false, 96 | resolve: () => user, 97 | }) 98 | // true authentication check - different return value 99 | t.field('me4', { 100 | type: User, 101 | // @ts-expect-error 102 | authentication: [true, loggedOutUser], 103 | resolve: () => user, 104 | }) 105 | // false authentication check - different return value 106 | t.field('me5', { 107 | type: User, 108 | // @ts-expect-error 109 | authentication: [false, fakeUser], 110 | resolve: () => user, 111 | }) 112 | // true authentication check - error 113 | t.field('me6', { 114 | type: User, 115 | // @ts-expect-error 116 | authentication: [true, new Error('Invalid - Unauthenticated')], 117 | resolve: () => user, 118 | }) 119 | // false authentication check - error 120 | t.field('me7', { 121 | type: User, 122 | // @ts-expect-error 123 | authentication: [false, new Error('Invalid - Authenticated')], 124 | resolve: () => user, 125 | }) 126 | // authentication function 127 | t.field('me8', { 128 | type: User, 129 | // @ts-expect-error 130 | authentication: () => true, 131 | }) 132 | // authentication function - thrown error 133 | t.field('me9', { 134 | type: User, 135 | // @ts-expect-error 136 | authentication: () => { 137 | throw new Error('You shall not pass') 138 | }, 139 | }) 140 | // invalid value 141 | t.field('me10', { 142 | type: User, 143 | // @ts-expect-error 144 | authentication: { something: true }, 145 | }) 146 | }, 147 | }), 148 | ], 149 | plugins: [fieldAuthenticationPlugin(pluginConfig)], 150 | nonNullDefaults: { 151 | input: false, 152 | output: false, 153 | }, 154 | }) 155 | 156 | describe('fieldAuthentication', () => { 157 | describe('basic behavior', () => { 158 | it('should resolve all fields correctly with default plugin values - unauthenticated', async () => { 159 | const schema = testSchema({}) 160 | const data = await execute({ 161 | schema, 162 | document: QueryAll, 163 | }) 164 | 165 | expect(data).toMatchSnapshot() 166 | }) 167 | 168 | it('should resolve all fields correctly with default plugin values - authenticated', async () => { 169 | const schema = testSchema({}) 170 | const data = await execute({ 171 | schema, 172 | contextValue: { 173 | state: { 174 | user: 'something', 175 | }, 176 | }, 177 | document: QueryAll, 178 | }) 179 | 180 | expect(data).toMatchSnapshot() 181 | }) 182 | 183 | it('throwErrorOnFailedAuthenticationByDefault = true / defaultErrorMessage = something', async () => { 184 | const schema = testSchema({ 185 | throwErrorOnFailedAuthenticationByDefault: true, 186 | defaultErrorMessage: 'You need to be authenticated', 187 | }) 188 | const data = await execute({ 189 | schema, 190 | contextValue: { 191 | state: { 192 | user: 'something', 193 | }, 194 | }, 195 | document: QueryAll, 196 | }) 197 | 198 | expect(data).toMatchSnapshot() 199 | }) 200 | 201 | it('format error returning non error value', async () => { 202 | const schema = testSchema({ 203 | throwErrorOnFailedAuthenticationByDefault: true, 204 | defaultErrorMessage: 'You need to be authenticated', 205 | // @ts-expect-error 206 | formatError: () => "I'm not an error", 207 | }) 208 | const errorLogger = jest.fn() 209 | const data = await execute({ 210 | schema, 211 | contextValue: { 212 | state: { 213 | user: 'something', 214 | }, 215 | logger: { 216 | error: errorLogger, 217 | }, 218 | }, 219 | document: QueryAll, 220 | }) 221 | 222 | expect(data).toMatchSnapshot() 223 | expect(errorLogger).toHaveBeenCalled() 224 | }) 225 | }) 226 | }) 227 | -------------------------------------------------------------------------------- /plugins/relay-mutation/src/index.ts: -------------------------------------------------------------------------------- 1 | import { core, plugin, dynamicOutputMethod, objectType, inputObjectType, arg, nonNull } from 'nexus' 2 | 3 | export type RelayMutationPluginConfig = { 4 | nexusFieldName?: string 5 | nexusSchemaImportId?: string 6 | relayMutationPluginImportId?: string 7 | /** 8 | * Default function used to generate the mutation input type name 9 | * defaults to a function that for a mutation named addUser, generates AddUserInput 10 | */ 11 | defaultMutationInputTypeNameCreator?: (mutationFieldName: string) => string 12 | /** 13 | * Default function used to generate the mutation payload type name 14 | * defaults to a function that for a mutation named addUser, generates AddUserPayload 15 | */ 16 | defaultMutationPayloadTypeNameCreator?: (mutationFieldName: string) => string 17 | } 18 | 19 | type FieldResolverWithArgParamChangedToInputType = T extends ( 20 | source: infer S, 21 | args: infer A, 22 | ctx: infer C, 23 | info: infer I, 24 | ) => infer R 25 | ? 'input' extends keyof A 26 | ? (source: S, args: A['input'], ctx: C, info: I) => R 27 | : (source: S, args: undefined, ctx: C, info: I) => R 28 | : T 29 | 30 | export type RelayMutationNexusFieldConfig< 31 | TypeName extends string = any, 32 | FieldName extends string = any 33 | > = { 34 | inputFields?: ( 35 | t: core.InputDefinitionBlock>, 36 | ) => void 37 | outputFields?: ( 38 | t: core.OutputDefinitionBlock>, 39 | ) => void 40 | mutateAndGetPayload: FieldResolverWithArgParamChangedToInputType< 41 | core.FieldResolver 42 | > 43 | } & Omit, 'resolve' | 'type' | 'args'> 44 | 45 | const ucfirst = (text: string) => 46 | text.length === 1 ? text.toUpperCase() : `${text.charAt(0).toUpperCase()}${text.slice(1)}` 47 | 48 | export const relayMutationPlugin = (pluginConfig: RelayMutationPluginConfig = {}) => { 49 | const { 50 | nexusFieldName = 'relayMutation', 51 | relayMutationPluginImportId = '@jcm/nexus-plugin-relay-mutation', 52 | defaultMutationInputTypeNameCreator = (text) => ucfirst(`${text}Input`), 53 | defaultMutationPayloadTypeNameCreator = (text) => ucfirst(`${text}Payload`), 54 | } = pluginConfig 55 | 56 | return plugin({ 57 | name: 'RelayMutation', 58 | description: 'add t.relayMutation(field, fieldConfig) to the schema builder', 59 | fieldDefTypes: [ 60 | core.printedGenTypingImport({ 61 | module: relayMutationPluginImportId, 62 | bindings: ['RelayMutationNexusFieldConfig'], 63 | }), 64 | ], 65 | // we want to add a extension 66 | onInstall(builder) { 67 | builder.addType( 68 | dynamicOutputMethod({ 69 | name: nexusFieldName, 70 | typeDefinition: `( 71 | fieldName: FieldName, 72 | config: RelayMutationNexusFieldConfig 73 | ): void`, 74 | factory({ typeName: parentTypeName, typeDef: t, args: factoryArgs }) { 75 | const [fieldName, fieldConfig] = factoryArgs as [string, RelayMutationNexusFieldConfig] 76 | 77 | if (parentTypeName !== 'Mutation') { 78 | throw new Error(`Cannot call t.${nexusFieldName} inside a type other than Mutation`) 79 | } 80 | 81 | const { 82 | inputFields, 83 | outputFields, 84 | mutateAndGetPayload, 85 | ...remainingFieldConfig 86 | } = fieldConfig 87 | 88 | const inputTypeName = defaultMutationInputTypeNameCreator(fieldName) 89 | const payloadTypeName = defaultMutationPayloadTypeNameCreator(fieldName) 90 | 91 | if (inputFields && typeof inputFields !== 'function') { 92 | throw new Error( 93 | `Field config "inputFields" passed to t.${nexusFieldName} must be a function`, 94 | ) 95 | } 96 | 97 | if (outputFields && typeof outputFields !== 'function') { 98 | throw new Error( 99 | `Field config "outputFields" passed to t.${nexusFieldName} must be a function`, 100 | ) 101 | } 102 | 103 | if (!mutateAndGetPayload || typeof outputFields !== 'function') { 104 | throw new Error( 105 | `Field config "mutateAndGetPayload" passed to t.${nexusFieldName} must be a function`, 106 | ) 107 | } 108 | 109 | if (!builder.hasType(inputTypeName)) { 110 | builder.addType( 111 | inputObjectType({ 112 | name: inputTypeName, 113 | definition(t2) { 114 | inputFields && inputFields(t2) 115 | 116 | t2.nullable.string('clientMutationId') 117 | }, 118 | }), 119 | ) 120 | } 121 | 122 | if (!builder.hasType(payloadTypeName)) { 123 | builder.addType( 124 | objectType({ 125 | name: payloadTypeName, 126 | definition(t2) { 127 | outputFields && outputFields(t2) 128 | 129 | t2.nullable.string('clientMutationId') 130 | }, 131 | }), 132 | ) 133 | } 134 | 135 | t.field(fieldName, { 136 | ...remainingFieldConfig, 137 | type: nonNull(payloadTypeName), 138 | args: { 139 | input: nonNull( 140 | arg({ 141 | type: inputTypeName, 142 | }), 143 | ), 144 | }, 145 | async resolve(root, { input }, ctx, info) { 146 | const { clientMutationId } = input 147 | 148 | let toComplete 149 | try { 150 | toComplete = mutateAndGetPayload(root, input, ctx, info) 151 | } catch (e) { 152 | toComplete = Promise.reject(e) 153 | } 154 | return plugin.completeValue( 155 | toComplete, 156 | (data) => ({ ...data, clientMutationId }), 157 | (error) => { 158 | throw error 159 | }, 160 | ) 161 | }, 162 | }) 163 | }, 164 | }), 165 | ) 166 | }, 167 | }) 168 | } 169 | -------------------------------------------------------------------------------- /plugins/datetime/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | core, 3 | plugin, 4 | dynamicOutputMethod, 5 | objectType, 6 | stringArg, 7 | interfaceType, 8 | nonNull, 9 | } from 'nexus' 10 | 11 | import * as moment from 'moment-timezone' 12 | 13 | export type DateTimePluginConfig = { 14 | /** 15 | * The name of the dateTime field, defaults to "dateTime" 16 | */ 17 | dateTimeFieldName?: string 18 | 19 | nexusSchemaImportId?: string 20 | dateTimePluginImportId?: string 21 | } 22 | 23 | export type DateTimePluginFieldConfig< 24 | TypeName extends string = any, 25 | FieldName extends string = any 26 | > = { 27 | /** 28 | * This defaults to the fieldName used when calling dateTime(fieldName) 29 | */ 30 | dateTimeISOField?: string 31 | } & Exclude, 'resolve' | 'type'> 32 | 33 | export function dateTimePlugin(pluginConfig: DateTimePluginConfig = {}) { 34 | const { 35 | dateTimeFieldName = 'dateTime', 36 | dateTimePluginImportId = '@jcm/nexus-plugin-datetime', 37 | } = pluginConfig 38 | 39 | return plugin({ 40 | name: 'DateTime', 41 | description: 'add t.dateTime(field) to the schema builder', 42 | // we want to add a extension 43 | fieldDefTypes: [ 44 | core.printedGenTypingImport({ 45 | module: dateTimePluginImportId, 46 | bindings: ['DateTimePluginFieldConfig'], 47 | }), 48 | ], 49 | onInstall(builder) { 50 | builder.addType( 51 | dynamicOutputMethod({ 52 | name: dateTimeFieldName, 53 | typeDefinition: `( 54 | fieldName: FieldName, 55 | config: DateTimePluginFieldConfig 56 | ): void`, 57 | factory({ typeName: _parentTypeName, typeDef: t, args: factoryArgs }) { 58 | const [fieldName, fieldConfig = {}] = factoryArgs 59 | const { dateTimeISOField = fieldName, ...remainingFieldConfig } = fieldConfig 60 | 61 | if (!builder.hasType('DateTimeFieldInterface')) { 62 | builder.addType( 63 | interfaceType({ 64 | name: 'DateTimeFieldInterface', 65 | description: 'A object that represents an ISO datetime', 66 | resolveType(obj) { 67 | if (obj.iso) return 'DateTimeField' 68 | return null 69 | }, 70 | definition(t2) { 71 | t2.nonNull.string('iso') 72 | t2.nonNull.string('formatted', { 73 | args: { 74 | format: nonNull(stringArg()), 75 | timezone: stringArg({ 76 | description: 'Timezone to format the ISO date, defaults to UTC', 77 | }), 78 | }, 79 | resolve: (root, args) => { 80 | return args.timezone 81 | ? moment.tz(root.iso, args.timzone).format(args.format) 82 | : moment.utc(root.iso).format(args.format) 83 | }, 84 | }) 85 | t2.nonNull.boolean('isAfter', { 86 | args: { 87 | iso: stringArg({ 88 | description: 'Defaults to the current time if not provided', 89 | }), 90 | }, 91 | resolve: (root, { iso }) => moment(root.iso).isAfter(iso || new Date()), 92 | }) 93 | t2.nonNull.boolean('isBefore', { 94 | args: { 95 | iso: stringArg({ 96 | description: 'Defaults to the current time if not provided', 97 | }), 98 | }, 99 | resolve: (root, { iso }) => moment(root.iso).isBefore(iso || new Date()), 100 | }) 101 | t2.nonNull.boolean('isSameOrAfter', { 102 | args: { 103 | iso: stringArg({ 104 | description: 'Defaults to the current time if not provided', 105 | }), 106 | }, 107 | resolve: (root, { iso }) => moment(root.iso).isSameOrAfter(iso || new Date()), 108 | }) 109 | t2.nonNull.boolean('isSameOrBefore', { 110 | args: { 111 | iso: stringArg({ 112 | description: 'Defaults to the current time if not provided', 113 | }), 114 | }, 115 | resolve: (root, { iso }) => 116 | moment(root.iso).isSameOrBefore(iso || new Date()), 117 | }) 118 | t2.nonNull.boolean('isSame', { 119 | args: { 120 | iso: stringArg({ 121 | description: 'Defaults to the current time if not provided', 122 | }), 123 | }, 124 | resolve: (root, { iso }) => moment(root.iso).isSame(iso || new Date()), 125 | }) 126 | t2.nonNull.boolean('isBetween', { 127 | args: { 128 | isoStart: stringArg({ 129 | description: 'Defaults to the start of the current day if not provided', 130 | }), 131 | isoEnd: stringArg({ 132 | description: 'Defaults to the end of the current day if not provided', 133 | }), 134 | }, 135 | resolve: (root, { isoStart, isoEnd }) => 136 | moment(root.iso).isBetween( 137 | isoStart || moment().startOf('day'), 138 | isoEnd || moment().endOf('day'), 139 | ), 140 | }) 141 | }, 142 | }), 143 | ) 144 | } 145 | 146 | if (!builder.hasType('DateTimeField')) { 147 | builder.addType( 148 | objectType({ 149 | name: 'DateTimeField', 150 | description: 'Represents an ISO datetime that can be formatted in other formats', 151 | definition: (t2) => { 152 | t2.implements('DateTimeFieldInterface') 153 | }, 154 | }), 155 | ) 156 | } 157 | 158 | t.field(fieldName, { 159 | ...remainingFieldConfig, 160 | type: 'DateTimeField', 161 | resolve: (root) => 162 | root[dateTimeISOField] && { 163 | iso: moment(root[dateTimeISOField]).toISOString(), 164 | }, 165 | }) 166 | }, 167 | }), 168 | ) 169 | }, 170 | }) 171 | } 172 | -------------------------------------------------------------------------------- /plugins/field-authentication/src/index.ts: -------------------------------------------------------------------------------- 1 | import { plugin, core } from 'nexus' 2 | import { GraphQLResolveInfo } from 'graphql' 3 | 4 | // Most of this code is inspired on the authorize plugin from nexus 5 | 6 | const fieldAuthenticationPluginResolverImport = core.printedGenTypingImport({ 7 | module: '@jcm/nexus-plugin-field-authentication', 8 | bindings: ['FieldAuthenticationResolver', 'FieldAuthenticationResolverReturnValue'], 9 | }) 10 | 11 | const fieldDefTypes = core.printedGenTyping({ 12 | optional: true, 13 | name: 'authentication', 14 | description: ` 15 | Authentication for an individual field. Returning "true" 16 | or "Promise" means the field can be accessed only if 17 | the request is authenticated. Returning "false" or "Promise" 18 | means the field can be acessed only if the request is *NOT* authenticated. 19 | It's also possible to return a tuple "[boolean, Error | ResolveValue]" 20 | where the first element specifies if the request should be authenticated 21 | or not and the second specifies the behavior: 22 | If the second element is an error, this error will be returned for the field, even if 23 | throwErrorOnFailedAuthentication is false. 24 | If the second element is anything else, this value will be resolved if the request 25 | fails the authentication check. 26 | `, 27 | type: 'FieldAuthenticationResolver | FieldAuthenticationResolverReturnValue', 28 | imports: [fieldAuthenticationPluginResolverImport], 29 | }) 30 | 31 | export type FieldAuthenticationResolverReturnValue = 32 | | boolean 33 | | Error 34 | | [boolean, Error] 35 | | [boolean, core.ResultValue] 36 | 37 | export type FieldAuthenticationResolver = ( 38 | root: core.RootValueField, 39 | args: core.ArgsValue, 40 | ctx: core.GetGen<'context'>, 41 | info: GraphQLResolveInfo, 42 | ) => core.MaybePromise 43 | 44 | export interface FieldAuthenticationPluginErrorConfig { 45 | error: Error 46 | root: any 47 | args: any 48 | ctx: core.GetGen<'context'> 49 | info: GraphQLResolveInfo 50 | } 51 | 52 | export const defaultFormatError = ({ error }: FieldAuthenticationPluginErrorConfig): Error => error 53 | 54 | export type FieldAuthenticationPluginConfigIsLogged< 55 | TypeName extends string, 56 | FieldName extends string 57 | > = ( 58 | root: core.RootValueField, 59 | args: core.ArgsValue, 60 | ctx: core.GetGen<'context'>, 61 | info: GraphQLResolveInfo, 62 | ) => boolean | core.MaybePromise 63 | 64 | export interface FieldAuthenticationPluginConfig { 65 | /** 66 | * This will be called with the error that will be returned when resolving a field 67 | * that did not pass the authentication check when throwErrorOnFailedAuthenticationByDefault 68 | * was set to true or when the authentication had a tuple [boolean, Error] 69 | * By default this is the function ({error: Error}) => error 70 | */ 71 | formatError?: (authConfig: FieldAuthenticationPluginErrorConfig) => Error 72 | /** 73 | * If this is true and the authentication field does not include 74 | * a default resolve value, an error will be thrown on failed authentication 75 | */ 76 | throwErrorOnFailedAuthenticationByDefault?: boolean 77 | /** 78 | * If throwErrorOnFailedAuthenticationByDefault is true, 79 | */ 80 | defaultErrorMessage?: string 81 | /** 82 | * If throwErrorOnFailedAuthenticationByDefault is false, a failed authentication check will return 83 | * this value as the resolved value of the field 84 | */ 85 | defaultResolveValue?: core.ResultValue 86 | /** 87 | * By default this checks if ctx.state.user exists 88 | */ 89 | isLogged?: FieldAuthenticationPluginConfigIsLogged 90 | } 91 | 92 | // This is much more complex than it could be 93 | // but it was done that way so that it handle all scenarios possible that we might ever encounter. 94 | 95 | export function fieldAuthenticationPlugin(pluginConfig: FieldAuthenticationPluginConfig = {}) { 96 | const { 97 | formatError = defaultFormatError, 98 | throwErrorOnFailedAuthenticationByDefault = false, 99 | defaultErrorMessage = 'Not Authenticated', 100 | defaultResolveValue = null, 101 | isLogged = (_root, _args, ctx, _info) => !!ctx?.state?.user, 102 | } = pluginConfig 103 | 104 | const ensureError = ( 105 | root: any, 106 | args: any, 107 | ctx: core.GetGen<'context'>, 108 | info: GraphQLResolveInfo, 109 | ) => (error: Error) => { 110 | const finalErr = formatError({ error, root, args, ctx, info }) 111 | if (finalErr instanceof Error) { 112 | throw finalErr 113 | } 114 | ;(ctx.logger || console).error( 115 | `Non-Error value ${JSON.stringify( 116 | finalErr, 117 | )} returned from custom formatError in field authentication plugin`, 118 | ) 119 | throw new Error(defaultErrorMessage) 120 | } 121 | 122 | return plugin({ 123 | name: 'FieldAuthentication', 124 | // we want to add a extension 125 | description: 'Makes sure request is authenticated before calling next resolvers in the chain', 126 | fieldDefTypes: fieldDefTypes, 127 | onCreateFieldResolver(config) { 128 | const authentication = config.fieldConfig.extensions?.nexus?.config?.authentication 129 | 130 | if (typeof authentication !== 'undefined') { 131 | // The authentication wrapping resolver. 132 | return function (root, args, ctx, info, next) { 133 | const { 134 | fieldName, 135 | parentType: { name: parentTypeName }, 136 | } = info 137 | 138 | const processAuthenticationResult = ( 139 | isUserLogged: boolean, 140 | result: FieldAuthenticationResolverReturnValue, 141 | ) => { 142 | const finalFormatError = ensureError(root, args, ctx, info) 143 | 144 | if ( 145 | typeof result === 'boolean' || 146 | (Array.isArray(result) && result.length === 2 && typeof result[0] === 'boolean') 147 | ) { 148 | const canProceed = 149 | isUserLogged === result || (Array.isArray(result) && isUserLogged === result[0]) 150 | 151 | // all branches here must return finalFormatError 152 | if (!canProceed) { 153 | if (Array.isArray(result)) { 154 | if (result[1] instanceof Error) { 155 | return finalFormatError(result[1]) 156 | } 157 | return result[1] 158 | } else if (throwErrorOnFailedAuthenticationByDefault) { 159 | return finalFormatError(new Error(defaultErrorMessage)) 160 | } 161 | return defaultResolveValue 162 | } 163 | 164 | return next(root, args, ctx, info) 165 | } else { 166 | return finalFormatError( 167 | new Error( 168 | `Field authentication for ${parentTypeName}.${fieldName} expected a boolean or [boolean, resolvedValue] tuple, saw ${JSON.stringify( 169 | result, 170 | )} instead`, 171 | ), 172 | ) 173 | } 174 | } 175 | 176 | let toCompleteIsLogged 177 | try { 178 | toCompleteIsLogged = isLogged(root, args, ctx, info) 179 | } catch (e) { 180 | toCompleteIsLogged = Promise.reject(e) 181 | } 182 | return plugin.completeValue( 183 | toCompleteIsLogged, 184 | (isUserLogged) => { 185 | if (typeof authentication !== 'function') { 186 | return processAuthenticationResult(isUserLogged, authentication) 187 | } 188 | 189 | let toComplete 190 | try { 191 | toComplete = authentication(root, args, ctx, info) 192 | } catch (e) { 193 | toComplete = Promise.reject(e) 194 | } 195 | return plugin.completeValue( 196 | toComplete, 197 | processAuthenticationResult.bind(undefined, isUserLogged), 198 | (err) => { 199 | ensureError(root, args, ctx, info)(err as Error) 200 | }, 201 | ) 202 | }, 203 | (err) => { 204 | ensureError(root, args, ctx, info)(err) 205 | }, 206 | ) 207 | } 208 | } 209 | }, 210 | }) 211 | } 212 | -------------------------------------------------------------------------------- /plugins/yup-validation/src/index.ts: -------------------------------------------------------------------------------- 1 | import { isObjectType, isNonNullType, GraphQLResolveInfo } from 'graphql/type' 2 | import { core, plugin, objectType } from 'nexus' 3 | import { SchemaOf, BaseSchema, ValidationError } from 'yup' 4 | 5 | type NestedOmit< 6 | T extends Record, 7 | K extends string 8 | > = K extends `${infer K1}.${infer K2}` 9 | ? K1 extends keyof T 10 | ? { 11 | [newK in Exclude]: T[newK] 12 | } & 13 | { 14 | [newK in K1]: NestedOmit 15 | } 16 | : never 17 | : Omit 18 | 19 | const groupBy = (arrObjs: Array, field: K) => { 20 | const map = new Map() 21 | 22 | arrObjs.forEach((obj) => { 23 | const val = obj[field] 24 | 25 | if (!map.has(val)) { 26 | map.set(val, []) 27 | } 28 | 29 | map.set(val, [...(map.get(val) || []), obj]) 30 | }) 31 | 32 | return map 33 | } 34 | 35 | type ErrorMessage = { path: string[]; messages: string[] } 36 | 37 | export type ErrorPayloadBuilderFunction = ( 38 | error: ValidationError, 39 | gqlContext: { 40 | root: core.RootValueField 41 | args: core.ArgsValue 42 | ctx: core.GetGen<'context'> 43 | info: GraphQLResolveInfo 44 | }, 45 | ) => any 46 | 47 | const defaultErrorPayloadBuilder: ErrorPayloadBuilderFunction = (error) => { 48 | // TODO(jonathan): Add support for fields other than Mutation - This will probably require throwing an error here 49 | 50 | let details: ErrorMessage[] = [] 51 | 52 | if (error.inner.length) { 53 | const errorsGrouped = groupBy(error.inner, 'path') 54 | 55 | details = Array.from(errorsGrouped).reduce((acc, [key, val]) => { 56 | return [ 57 | ...acc, 58 | { 59 | path: key!.split('.'), 60 | messages: val.map((fieldError) => fieldError.message), 61 | }, 62 | ] 63 | }, details as ErrorMessage[]) 64 | } 65 | 66 | const rootError = { 67 | message: error.message, 68 | details, 69 | } 70 | 71 | return { 72 | error: rootError, 73 | } 74 | } 75 | 76 | export type FieldYupValidation< 77 | TypeName extends string, 78 | FieldName extends string, 79 | TypeSafetyFieldsToIgnore extends string = '' 80 | > = { 81 | schema: 82 | | SchemaOf, TypeSafetyFieldsToIgnore>> 83 | | (( 84 | root: core.RootValueField, 85 | args: core.ArgsValue, 86 | context: core.GetGen<'context'>, 87 | info: GraphQLResolveInfo, 88 | ) => core.MaybePromise< 89 | SchemaOf, TypeSafetyFieldsToIgnore>> 90 | >) 91 | config?: YupValidationPluginConfigNonGeneral 92 | } 93 | 94 | export type YupValidationPluginConfigNonGeneral< 95 | TypeName extends string, 96 | FieldName extends string 97 | > = { 98 | errorPayloadBuilder?: ErrorPayloadBuilderFunction 99 | shouldTransformArgs?: boolean 100 | yup?: Parameters[1] 101 | } 102 | 103 | export type YupValidationPluginConfig = { 104 | yupValidationPluginImportId?: string 105 | yupValidationPluginTypeSafetyFieldsToIgnore?: string[] 106 | } & YupValidationPluginConfigNonGeneral 107 | 108 | export const MutationUserErrorDetailsNexusObjectType = objectType({ 109 | name: 'MutationUserErrorDetails', 110 | description: 'Individual related user error that made the mutation not complete', 111 | definition(t) { 112 | t.nonNull.list.nonNull.string('path', { 113 | description: 114 | 'Path where this error was found on the passed arguments - Can be null if this is a general error', 115 | }) 116 | t.nonNull.list.nonNull.string('messages', { 117 | description: 'The error(s) message(s) found for given path', 118 | }) 119 | }, 120 | }) 121 | 122 | export const MutationUserErrorNexusObjectType = objectType({ 123 | name: 'MutationUserError', 124 | description: 'User errors that made the mutation not complete', 125 | definition(t) { 126 | t.nonNull.string('message') 127 | t.nonNull.list.nonNull.field('details', { 128 | type: MutationUserErrorDetailsNexusObjectType, 129 | }) 130 | }, 131 | }) 132 | 133 | export const yupValidationPlugin = (pluginConfig: YupValidationPluginConfig = {}) => { 134 | const { 135 | yupValidationPluginImportId = '@jcm/nexus-plugin-yup-validation', 136 | yupValidationPluginTypeSafetyFieldsToIgnore = [], 137 | ...otherPluginOptions 138 | } = pluginConfig 139 | 140 | return plugin({ 141 | name: 'YupValidation', 142 | description: 'This plugin will validate fields arguments using a yup schema', 143 | fieldDefTypes: [ 144 | core.printedGenTyping({ 145 | optional: true, 146 | name: 'yup', 147 | description: ` 148 | Yup validation for this field arguments. 149 | `, 150 | type: `FieldYupValidation JSON.stringify(v)).join('|') 153 | : '' 154 | }>`, 155 | imports: [ 156 | core.printedGenTypingImport({ 157 | module: yupValidationPluginImportId, 158 | bindings: ['FieldYupValidation'], 159 | }), 160 | ], 161 | }), 162 | ], 163 | onCreateFieldResolver(config) { 164 | const parentTypeName = config.parentTypeConfig.name 165 | 166 | const yupValidation = config.fieldConfig.extensions?.nexus?.config?.yup 167 | 168 | // TODO(jonathan): add support for fields other than Mutations 169 | if (parentTypeName !== 'Mutation' || !yupValidation) { 170 | return 171 | } 172 | 173 | const { 174 | schema: mutationValidationSchema, 175 | config: mutationConfig, 176 | } = yupValidation as FieldYupValidation 177 | 178 | const finalConfig = { 179 | ...otherPluginOptions, 180 | ...mutationConfig, 181 | } 182 | 183 | const { 184 | errorPayloadBuilder = defaultErrorPayloadBuilder, 185 | shouldTransformArgs = true, 186 | yup: yupConfig = { 187 | abortEarly: false, 188 | }, 189 | } = finalConfig 190 | 191 | const hasSuppliedErrorPayloadBuilder = errorPayloadBuilder !== defaultErrorPayloadBuilder 192 | 193 | if (!hasSuppliedErrorPayloadBuilder) { 194 | const returnType = isNonNullType(config.fieldConfig.type) 195 | ? config.fieldConfig.type.ofType 196 | : config.fieldConfig.type 197 | 198 | const isObjReturnType = isObjectType(returnType) 199 | 200 | if (!isObjReturnType) { 201 | throw new Error( 202 | 'Only mutations with object return type are supported with the default errorPayloadBuilder', 203 | ) 204 | } 205 | 206 | // returnType cannot be anything else at this point 207 | const fields = returnType.getFields() 208 | 209 | if (!fields.error) { 210 | throw new Error( 211 | 'You must have an "error" field on the payload of your mutation when using the default errorPayloadBuilder', 212 | ) 213 | } 214 | 215 | const errorField = fields.error.type 216 | 217 | if (!isObjectType(errorField)) { 218 | throw new Error( 219 | 'Mutation payload "errors" field must be a list of GraphQLObjectType with structure { message: string, details: { path: string[], messages: string[] }[] }', 220 | ) 221 | } 222 | 223 | // TODO(jonathan): Improve this validation 224 | } 225 | 226 | return async function (root, args, ctx, info, next) { 227 | let toComplete 228 | try { 229 | toComplete = 230 | typeof mutationValidationSchema === 'function' 231 | ? mutationValidationSchema(root, args, ctx, info) 232 | : mutationValidationSchema 233 | } catch (e) { 234 | toComplete = Promise.reject(e) 235 | } 236 | return plugin.completeValue(toComplete, async (schema) => { 237 | try { 238 | const values = await schema.validate(args, yupConfig) 239 | 240 | return next(root, shouldTransformArgs ? values : args, ctx, info) 241 | } catch (error) { 242 | if (error instanceof ValidationError) { 243 | const errorResult = errorPayloadBuilder(error, { 244 | root, 245 | args, 246 | ctx, 247 | info, 248 | }) 249 | 250 | return errorResult 251 | } else { 252 | throw error 253 | } 254 | } 255 | }) 256 | } 257 | }, 258 | }) 259 | } 260 | --------------------------------------------------------------------------------