├── .nvmrc
├── src
├── index.js
├── etc.js
└── microfiber.js
├── static
├── anvil.png
└── microfiber.png
├── test
├── mocha-environment.js
├── mocha-setup.js
├── mocha-config.js
├── test-helpers
│ └── index.js
├── .eslintrc.js
└── index.test.js
├── index.js
├── nodemon.json
├── .gitignore
├── .github
├── dependabot.yml
└── workflows
│ └── test.yml
├── .eslintrc.js
├── CHANGELOG.md
├── babel.config.js
├── LICENSE.md
├── package.json
└── README.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export * from './microfiber'
2 | export * from './etc'
3 |
--------------------------------------------------------------------------------
/static/anvil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvilco/graphql-introspection-tools/HEAD/static/anvil.png
--------------------------------------------------------------------------------
/static/microfiber.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anvilco/graphql-introspection-tools/HEAD/static/microfiber.png
--------------------------------------------------------------------------------
/test/mocha-environment.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai')
2 |
3 | global.chai = chai
4 | global.expect = chai.expect
5 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // This file is mainly just for cleaning up the exports to be intuitive/commonjs
2 | module.exports = require('./dist')
3 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "delay": 1000,
3 | "verbose": true,
4 | "ext": "js,json,gql,graphql,graphqls",
5 | "ignore": [
6 | ".git"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Babel's output
2 | dist/
3 |
4 | yarn-error.log
5 |
6 | *.DS_Store
7 |
8 | # Dependency directories
9 | node_modules/
10 |
11 | # Output of 'npm pack'
12 | *.tgz
13 |
--------------------------------------------------------------------------------
/test/mocha-setup.js:
--------------------------------------------------------------------------------
1 | import { get } from 'bdd-lazy-var/getter'
2 |
3 | // https://github.com/stalniy/bdd-lazy-var/issues/56#issuecomment-639248242
4 | // eslint-disable-next-line id-length
5 | global.$ = get
6 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: weekly
7 | open-pull-requests-limit: 10
8 | reviewers:
9 | - newhouse
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | es2021: true,
4 | node: true,
5 | },
6 | extends: [
7 | 'eslint:recommended',
8 | ],
9 | parserOptions: {
10 | ecmaVersion: 12,
11 | sourceType: 'module'
12 | },
13 | plugins: [
14 | ],
15 | rules: {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | #### 2.1.1
2 | - Update dependencies
3 |
4 | #### 2.1.0
5 | - Fix/Add support for `EnumValue` via `getEnumValue()` and `getField()`
6 | - Update dependencies
7 |
8 | #### 2.0.2
9 | - Update dependencies
10 |
11 | #### 2.0.1
12 | - Update dependencies
13 |
14 | #### 2.0.0
15 | - Drop support for Node `< 14`
16 | - Add support for Node `18`
17 | - Update dependencies
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | // Using polyfills: No polyfills were added, since the `useBuiltIns` option was not set.
5 | '@babel/preset-env',
6 | {
7 | targets: {
8 | // Keep this roughly in-line with our "engines.node" value in package.json
9 | node: '14'
10 | }
11 | }
12 | ],
13 | ],
14 | plugins: [
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/test/mocha-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | diff: true,
3 | delay: false,
4 | extension: ['js'],
5 | package: './package.json',
6 | reporter: 'spec',
7 | slow: 75,
8 | timeout: 2000,
9 | spec: [
10 | './test/**/*.test.js',
11 | ],
12 | require: [
13 | // https://mochajs.org/#-require-module-r-module
14 | '@babel/register',
15 | './test/mocha-environment.js',
16 | ],
17 | ui: 'bdd-lazy-var/getter',
18 | file: './test/mocha-setup.js',
19 | exit: true,
20 | }
21 |
--------------------------------------------------------------------------------
/test/test-helpers/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | buildSchema,
3 | getIntrospectionQuery,
4 | graphqlSync,
5 | } from 'graphql'
6 |
7 |
8 | export const introspectionResponseFromSchemaSDL = ({ schemaSDL }) => {
9 | return introspectionResponseFromSchema({
10 | schema: buildSchema(schemaSDL),
11 | })
12 | }
13 |
14 | function introspectionResponseFromSchema ({ schema }) {
15 | return standardizeIntrospectionQueryResult(
16 | graphqlSync({ schema, source: getIntrospectionQuery() })
17 | )
18 | }
19 |
20 | // Get rid of the `data` envelope
21 | function standardizeIntrospectionQueryResult (result) {
22 | return result.data ? result.data : result
23 | }
24 |
--------------------------------------------------------------------------------
/test/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: '../.eslintrc.js',
3 | env: {
4 | mocha: true
5 | },
6 | plugins: [
7 | 'mocha',
8 | ],
9 | rules: {
10 | 'mocha/no-skipped-tests': 'error',
11 | 'mocha/no-exclusive-tests': 'error',
12 | },
13 | globals: {
14 | expect: 'readonly',
15 | pathToProject: 'readonly',
16 | // *************************************************
17 | // bdd-lazy-var
18 | //
19 | // In order to get around eslint complaining for now:
20 | // https://github.com/stalniy/bdd-lazy-var/issues/56#issuecomment-639248242
21 | // eslint-disable-next-line id-length
22 | $: 'readonly',
23 | def: 'readonly',
24 | //
25 | // *************************************************
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/etc.js:
--------------------------------------------------------------------------------
1 | // GraphQL constants
2 | const KIND_SCALAR = 'SCALAR'
3 | const KIND_OBJECT = 'OBJECT'
4 | const KIND_INTERFACE = 'INTERFACE'
5 | const KIND_UNION = 'UNION'
6 | const KIND_ENUM = 'ENUM'
7 | const KIND_INPUT_OBJECT = 'INPUT_OBJECT'
8 | const KIND_LIST = 'LIST'
9 | const KIND_NON_NULL = 'NON_NULL'
10 |
11 | // An Object containing all the GraphQL Kind values you may encounter.
12 | export const KINDS = Object.freeze({
13 | SCALAR: KIND_SCALAR,
14 | OBJECT: KIND_OBJECT,
15 | INTERFACE: KIND_INTERFACE,
16 | UNION: KIND_UNION,
17 | ENUM: KIND_ENUM,
18 | INPUT_OBJECT: KIND_INPUT_OBJECT,
19 | LIST: KIND_LIST,
20 | NON_NULL: KIND_NON_NULL,
21 | })
22 |
23 | // A function that compares 2 types and determines if they have the same Kind and Name.
24 | export function typesAreSame (typeA, typeB) {
25 | return typeA.kind === typeB.kind && typeA.name === typeB.name
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Anvil Foundry Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: CI Tests
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 | branches: [ main ]
11 |
12 | env:
13 | GITHUB_SHA: ${{ github.event.pull_request.head.sha }}
14 |
15 | jobs:
16 | build-and-cache-dist:
17 | name: Build and Cache dist
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v3
22 |
23 | - name: Setup Node.js
24 | id: setup-node
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version-file: '.nvmrc'
28 | cache: 'yarn'
29 |
30 | - name: Cache dist after Build
31 | uses: actions/cache@v3
32 | with:
33 | path: dist/
34 | key: ${{ runner.os }}-node-${{ steps.setup-node.outputs.node-version }}-${{ env.GITHUB_SHA }}
35 |
36 | - run: yarn install
37 | - run: yarn build
38 |
39 | test:
40 | needs: build-and-cache-dist
41 | runs-on: ubuntu-latest
42 | strategy:
43 | matrix:
44 | node-version: [14, 16, 18]
45 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
46 | steps:
47 | - uses: actions/checkout@v3
48 |
49 | - name: Load dist from cache
50 | uses: actions/cache@v3
51 | with:
52 | path: dist/
53 | key: ${{ runner.os }}-node-${{ steps.setup-node.outputs.node-version }}-${{ env.GITHUB_SHA }}
54 |
55 | - name: Use Node.js ${{ matrix.node-version }}
56 | uses: actions/setup-node@v3
57 | with:
58 | node-version: ${{ matrix.node-version }}
59 | cache: 'yarn'
60 | - run: yarn install
61 | - run: yarn test
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@anvilco/graphql-introspection-tools",
3 | "version": "2.1.1",
4 | "description": "A library to query and manipulate GraphQL Introspection Query results in some useful ways.",
5 | "author": "Chris Newhouse",
6 | "homepage": "https://github.com/anvilco/graphql-introspection-tools",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/anvilco/graphql-introspection-tools.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/anvilco/graphql-introspection-tools/issues"
14 | },
15 | "keywords": [
16 | "graphql",
17 | "schema",
18 | "introspection",
19 | "introspection-query",
20 | "introspection-query-manipulator",
21 | "query",
22 | "remove",
23 | "manipulate",
24 | "manipulator"
25 | ],
26 | "main": "index.js",
27 | "files": [
28 | "package.json",
29 | "README.md",
30 | "LICENSE.md",
31 | "CHANGELOG.md",
32 | "dist/"
33 | ],
34 | "engines": {
35 | "node": ">=14"
36 | },
37 | "scripts": {
38 | "build": "babel src --out-dir ./dist",
39 | "clean": "yarn rimraf ./dist",
40 | "prepare": "yarn clean && yarn build",
41 | "pub": "yarn prepare && npm publish",
42 | "pub:dry-run": "yarn prepare && npm publish --dry-run",
43 | "test": "yarn prepare && yarn mocha --config ./test/mocha-config.js",
44 | "test:watch": "nodemon -x 'yarn test' --ignore dist/",
45 | "test:debug:watch": "nodemon -x 'yarn test:debug' --ignore dist/",
46 | "test:debug": "yarn test --node-option inspect=0.0.0.0:9223"
47 | },
48 | "dependencies": {
49 | "lodash.defaults": "^4.2.0",
50 | "lodash.get": "^4.4.2",
51 | "lodash.unset": "^4.5.2"
52 | },
53 | "devDependencies": {
54 | "@babel/cli": "^7.17.6",
55 | "@babel/core": "^7.17.8",
56 | "@babel/preset-env": "^7.16.11",
57 | "@babel/register": "^7.17.0",
58 | "bdd-lazy-var": "^2.6.1",
59 | "chai": "^4.3.6",
60 | "eslint": "^8.10.0",
61 | "eslint-plugin-mocha": "^10.0.3",
62 | "graphql": "^16.3.0",
63 | "lodash.isequal": "^4.5.0",
64 | "mocha": "^10.2.0",
65 | "nodemon": "^3.0.1",
66 | "rimraf": "^5.0.1"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Microfiber - A.K.A. GraphQL Introspection Tools
4 |
5 | [![npm][npm]][npm-url]
6 | [![downloads][npm-downloads]][npm-url]
7 |
8 |
9 |
10 | A library to query and manipulate GraphQL Introspection Query results in some useful ways. What ways you ask?
11 |
12 | How about:
13 | - Digging through your Introspection Query Results for a specific Query, Mutation, Type, Field, Argument or Subscription.
14 | - Removing a specific Query, Mutation, Type, Field/InputField, Argument or Subscription from your Introspection Query Results.
15 | - Removing Queries, Mutations, Fields/InputFields or Arguments that refer to Type that does not exist in - or has been removed from - your Introspection Query Results.
16 |
17 | Yay!
18 |
19 | It's called `microfiber` because it is heavily used to do the cleaning and manipulation in [SpectaQL][spectaql]...it *cleans* the *spectacles*, get it?!
20 |
21 | But, we also wanted to have a more intuitive, literal name so that people could find it. Hence it's also known as `@anvilco/graphql-introspection-tools`.
22 |
23 | ---
24 |
25 | **Repository sponsored by [Anvil](www.useanvil.com/developers)**
26 |
27 | 
28 | 
29 |
30 | Anvil provides easy APIs for all things paperwork.
31 |
32 | 1. [PDF filling API](https://www.useanvil.com/products/pdf-filling-api/) - fill out a PDF template with a web request and structured JSON data.
33 | 2. [PDF generation API](https://www.useanvil.com/products/pdf-generation-api/) - send markdown or HTML and Anvil will render it to a PDF.
34 | 3. [Etch e-sign with API](https://www.useanvil.com/products/etch/) - customizable, embeddable, e-signature platform with an API to control the signing process end-to-end.
35 | 4. [Anvil Workflows (w/ API)](https://www.useanvil.com/products/workflows/) - Webforms + PDF + e-sign with a powerful no-code builder. Easily collect structured data, generate PDFs, and request signatures.
36 |
37 | Learn more on our [Anvil developer page](https://www.useanvil.com/developers/).
38 |
39 | ---
40 |
41 | ## Getting Started
42 |
43 | 1. Install `microfiber`
44 | ```sh
45 | npm install microfiber
46 | # OR
47 | yarn add microfiber
48 | ```
49 |
50 | 2. Clean your GraphQL Introspection Query Results
51 | ```node
52 | import { Microfiber } from 'microfiber'
53 |
54 | const introspectionQueryResults = {...}
55 |
56 | const microfiber = new Microfiber(introspectionQueryResults)
57 |
58 | // ...do some things to your schema with `microfiber`
59 |
60 | const cleanedIntrospectonQueryResults = microfiber.getResponse()
61 |
62 | // ...do something with your cleaned Introspection Query Results.
63 | ```
64 |
65 | ## Usage
66 |
67 | ### class Microfiber
68 |
69 | Most of the useful stuff in this library is done through creating a new Microfiber class instance with your Introspection Query Results, and querying or manipulating it via that instance. Here are most of the interesting bits to know about class behavior.
70 |
71 | ---
72 | #### constructor
73 | ```node
74 | const introspectionQueryResponse = {...}
75 | // Here are the publicly supported options and their sane defaults:
76 | const options = {
77 | // Some GraphQL implementations have non-standard Query, Mutation and/or Subscription
78 | // type names. This option will fix them if they're messed up in the Introspection Query
79 | // Results
80 | fixQueryAndMutationAndSubscriptionTypes: true,
81 |
82 | // Remove Types that are not referenced anywhere by anything
83 | removeUnusedTypes: true,
84 |
85 | // Remove things whose Types are not found due to being removed
86 | removeFieldsWithMissingTypes: true,
87 | removeArgsWithMissingTypes: true,
88 | removeInputFieldsWithMissingTypes: true,
89 | removePossibleTypesOfMissingTypes: true,
90 |
91 | // Remove all the types and things that are unreferenced immediately?
92 | cleanupSchemaImmediately: true,
93 | }
94 |
95 | const microfiber = new Microfiber(introspectionQueryResponse, options)
96 | ```
97 | ---
98 | #### cleanSchema
99 | Clean up the schema by removing:
100 | - Fields or Input Fields whose Type does not exist in the schema.
101 | - Args whose Type does not exist in the schema.
102 | - Possible Types in a Union that do not exist in the schema.
103 | - Queries or Mutations whose return Type does not exist in the schema.
104 |
105 | This method is usually called after altering the schema in any way so as to not leave any dangling/orphaned things around the schema.
106 | ```node
107 | microfiber.cleanSchema()
108 | ```
109 | ---
110 | #### getResponse
111 | Get out the Introspection Query Result that you have manipulated with Microfiber as an Object.
112 | ```node
113 | const cleanedResponse = microfiber.getResponse()
114 | ```
115 | ---
116 | #### getAllTypes
117 | Get all the Types from your schema as an Array of Objects. Supported options and their sane defaults are shown.
118 | ```node
119 | const allTypes = microfiber.getAllTypes({
120 | // Include reserved GraphQL types?
121 | includeReserved: false,
122 | // Include the Query type?
123 | includeQuery: false,
124 | // Include the Mutation type?
125 | includeMutation: false,
126 | // Include the Subscription type?
127 | includeSubscription: false,
128 | } = {})
129 | ```
130 | ---
131 | #### getType
132 | Get a specific Type from your schema. Supported params and their sane defaults are shown.
133 | ```node
134 | const type = microfiber.getType({ kind: 'OBJECT', name })
135 | ```
136 | ---
137 | #### getDirectives
138 | Get all the Directives from your schema.
139 | ```node
140 | const directives = microfiber.getDirectives()
141 | ```
142 | ---
143 | #### getDirective
144 | Get a specific Directive from your schema. Supported params and their sane defaults are shown.
145 | ```node
146 | const directive = microfiber.getDirective({ name })
147 | ```
148 | ---
149 | #### getQueryType
150 | Get the Query Type from your schema.
151 | ```node
152 | const queryType = microfiber.getQueryType()
153 | ```
154 | ---
155 | #### getQuery
156 | Get a specific Query from your schema.
157 | ```node
158 | const query = microfiber.getQuery({ name })
159 | ```
160 | ---
161 | #### getMutationType
162 | Get the Mutation Type from your schema.
163 | ```node
164 | const mutationType = microfiber.getMutationType()
165 | ```
166 | ---
167 | #### getMutation
168 | Get a specific Mutation from your schema.
169 | ```node
170 | const mutation = microfiber.getMutation({ name })
171 | ```
172 | ---
173 | #### getSubscriptionType
174 | Get the Subscription Type from your schema.
175 | ```node
176 | const subscriptionType = microfiber.getSubscription()
177 | ```
178 | ---
179 | #### getSubscription
180 | Get a specific Subscription from your schema.
181 | ```node
182 | const subscription = microfiber.getSubscription({ name })
183 | ```
184 | ---
185 | #### getField
186 | Get a specific Field from your schema. Supported params and their sane defaults are shown.
187 | ```node
188 | const field = microfiber.getField({ typeKind: 'OBJECT', typeName, fieldName })
189 | ```
190 | ---
191 | #### getInterfaceField
192 | Get a specific Field from an Interface in your schema. A convenience wrapper around `getField({ typeKind: 'INTERFACE', ...})`
193 | ```node
194 | const interfaceField = microfiber.getInterfaceField({ typeName, fieldName })
195 | ```
196 | ---
197 | #### getEnumValue
198 | Get a specific EnumValue from your schema. A convenience wrapper around `getField({ typeKind: 'ENUM', ...})`
199 | ```node
200 | const inputField = microfiber.getEnumValue({ typeName, fieldName })
201 | ```
202 | ---
203 | #### getInputField
204 | Get a specific InputField from your schema. A convenience wrapper around `getField({ typeKind: 'INPUT_OBJECT', ...})`
205 | ```node
206 | const inputField = microfiber.getInputField({ typeName, fieldName })
207 | ```
208 | ---
209 | #### getArg
210 | Get a specific Arg from your schema. Supported params and their sane defaults are shown.
211 | ```node
212 | const arg = microfiber.getArg({ typeKind: 'OBJECT', typeName, fieldName, argName })
213 | ```
214 | ---
215 | #### getDirectiveArg
216 | Get a specific Arg from a specifig Directive in your schema. Supported params and their sane defaults are shown.
217 | ```node
218 | const directiveArg = microfiber.getDirectiveArg({ directiveName, argName })
219 | ```
220 | ---
221 | #### removeDirective
222 | Get a specific Directive from your schema. Supported params and their sane defaults are shown.
223 | ```node
224 | const directiveArg = microfiber.removeDirective({
225 | name,
226 | // Clean up the schema afterwards?
227 | cleanup = true,
228 | })
229 | ```
230 | ---
231 | #### removeType
232 | Remove a Type from your schema, and optionally the references to that Type elsewhere in your schema. Supported params and their sane defaults are shown.
233 | ```node
234 | microfiber.removeType({
235 | kind: 'OBJECT',
236 | name,
237 | // Clean up the schema afterwards?
238 | cleanup: true,
239 | // Remove occurances of this Type from other places?
240 | removeFieldsOfType: constructorOptions.removeFieldsWithMissingTypes,
241 | removeInputFieldsOfType: constructorOptions.removeInputFieldsWithMissingTypes,
242 | removePossibleTypesOfType: constructorOptions.removePossibleTypesOfMissingTypes,
243 | removeArgsOfType: constructorOptions.removeArgsWithMissingTypes,
244 | })
245 | ```
246 | ---
247 | #### removeField
248 | Remove a specific Field from a specific Type in your schema. Supported params and their sane defaults are shown.
249 | ```node
250 | microfiber.removeField({
251 | typeKind: 'OBJECT',
252 | typeName,
253 | fieldName,
254 | // Clean up the schema afterwards?
255 | cleanup: true,
256 | })
257 | ```
258 | ---
259 | #### removeInputField
260 | Remove a specific Input Field from a specific Input Object in your schema. Supported params and their sane defaults are shown.
261 | ```node
262 | microfiber.removeInputField({
263 | typeName,
264 | fieldName,
265 | // Clean up the schema afterwards?
266 | cleanup: true,
267 | })
268 | ```
269 | ---
270 | #### removeArg
271 | Remove a specific Arg from a specific Field or Input Field in your schema. Supported params and their sane defaults are shown.
272 | ```node
273 | microfiber.removeArg({
274 | typeKind,
275 | typeName,
276 | fieldName,
277 | argName,
278 | // Clean up the schema afterwards?
279 | cleanup: true,
280 | })
281 | ```
282 | ---
283 | #### removeEnumValue
284 | Remove a specifc Enum value from an Enum Type in your schema. Supported params are shown.
285 | ```node
286 | microfiber.removeEnumValue({
287 | // The name of the Enum Type
288 | name,
289 | // The Enum value you want to remove
290 | value,
291 | })
292 | ```
293 | ---
294 | #### removePossibleType
295 | Remove a Possible Type from a specific Union Type in your schema. Supported params and sane defaults are shown.
296 | ```node
297 | microfiber.removePossibleType({
298 | // The name of the Union Type
299 | typeName,
300 | // The Kind of the possible Type you want to remove
301 | possibleTypeKind,
302 | // The name of the possible Type you want to remove
303 | possibleTypeName,
304 | // Clean up the schema afterwards?
305 | cleanup: true,
306 | })
307 | ```
308 | ---
309 | #### removeQuery
310 | Remove a specific Query from your schema. Supported params and their sane defaults are shown.
311 | ```node
312 | microfiber.removeQuery({
313 | name,
314 | // Clean up the schema afterwards?
315 | cleanup: true,
316 | })
317 | ```
318 | ---
319 | #### removeMutation
320 | Remove a specific Mutation from your schema. Supported params and their sane defaults are shown.
321 | ```node
322 | microfiber.removeMutation({
323 | name,
324 | // Clean up the schema afterwards?
325 | cleanup: true,
326 | })
327 | ```
328 | ---
329 | #### removeSubscription
330 | Remove a specific Subscription from your schema. Supported params and their sane defaults are shown.
331 | ```node
332 | microfiber.removeSubscription({
333 | name,
334 | // Clean up the schema afterwards?
335 | cleanup: true,
336 | })
337 | ```
338 |
339 | ### Other exports from this library
340 | There are some other exports from this library, not just the `Microfiber` class.
341 |
342 | ---
343 | #### KINDS
344 | An Object containing all the GraphQL Kind values you may encounter.
345 | ```node
346 | import { KINDS } from 'microfiber'
347 |
348 | console.log(KINDS)
349 |
350 | // {
351 | // SCALAR: 'SCALAR',
352 | // OBJECT: 'OBJECT',
353 | // INTERFACE: 'INTERFACE',
354 | // UNION: 'UNION',
355 | // ENUM: 'ENUM',
356 | // INPUT_OBJECT: 'INPUT_OBJECT',
357 | // LIST: 'LIST',
358 | // NON_NULL: 'NON_NULL'
359 | // }
360 | ```
361 | ---
362 | #### typesAreSame
363 | A function that compares 2 types and determines if they have the same Kind and Name.
364 | ```node
365 | import { typesAreSame } from 'microfiber'
366 |
367 | const typeA = { kind: 'OBJECT', name: 'Foo' }
368 | const typeB = { kind: 'OBJECT', name: 'Bar' }
369 |
370 | typesAreSame(typeA, typeB) // false
371 | typesAreSame(typeA, typeA) // true
372 | ```
373 | ---
374 | #### digUnderlyingType
375 | A function that digs through any Non-Null and List nesting and returns the underlying Type.
376 | ```node
377 | import { digUnderlyingType } from 'microfiber'
378 |
379 | const nonNullableString = {
380 | name: null,
381 | kind: 'NON_NULL',
382 | ofType: {
383 | name: null,
384 | kind: 'LIST',
385 | ofType: {
386 | name: 'String',
387 | kind: 'SCALAR',
388 | }
389 | }
390 | }
391 |
392 | digUnderlyingType(nonNullableString) // { name: 'String', kind: 'SCALAR' }
393 | ```
394 | ---
395 | #### isReservedType
396 | A function that returns a Boolean indicating whether a Type is special GraphQL reserved Type.
397 | ```node
398 | import { isReservedType } from 'microfiber'
399 |
400 | const myType = { name: 'Foo', ... }
401 | const reservedType = { name: '__Foo', ... }
402 |
403 | isReservedType(myType) // false
404 | isReservedType(reservedType) // true
405 | ```
406 |
407 | [npm]: https://badge.fury.io/js/microfiber.svg
408 | [npm-downloads]: https://img.shields.io/npm/dw/microfiber
409 | [npm-url]: https://www.npmjs.com/package/microfiber
410 | [spectaql]: https://github.com/anvilco/spectaql
411 |
--------------------------------------------------------------------------------
/src/microfiber.js:
--------------------------------------------------------------------------------
1 | import get from 'lodash.get'
2 | import unset from 'lodash.unset'
3 | import defaults from 'lodash.defaults'
4 |
5 | import {
6 | KINDS,
7 | typesAreSame,
8 | } from './etc'
9 |
10 |
11 | // TODO:
12 | //
13 | // remove types that have no fields/inputFields/possibleTypes
14 | //
15 | // optimize to only clean if "dirty" and when pulling schema out
16 |
17 | const defaultOpts = Object.freeze({
18 | // Perform an analysis of the schema right away.
19 | _analyze: true,
20 | // Perform some normalization of the Introspection Query Results
21 | _normalize: true,
22 |
23 | // Some GraphQL implementations have non-standard Query, Mutation and/or Subscription
24 | // type names. This option will fix them if they're messed up in the Introspection Query
25 | // Results
26 | fixQueryAndMutationAndSubscriptionTypes: true,
27 |
28 | // Remove Types that are not referenced anywhere by anything
29 | removeUnusedTypes: true,
30 |
31 | // Remove things whose Types are not found due to being removed
32 | removeFieldsWithMissingTypes: true,
33 | removeArgsWithMissingTypes: true,
34 | removeInputFieldsWithMissingTypes: true,
35 | removePossibleTypesOfMissingTypes: true,
36 | removeDirectiveArgumentsOfMissingTypes: true,
37 |
38 | // TODO: implement
39 | // removeQueriesWithMissingTypes: true,
40 | // TODO: implement
41 | // removeMutationsWithMissingTypes: true,
42 | // TODO: implement
43 | // removeSubscriptionsWithMissingTypes: true,
44 |
45 | // Remove all the types and things that are unreferenced immediately?
46 | cleanupSchemaImmediately: true,
47 | })
48 |
49 | // Map some opts to their corresponding removeType params for proper defaulting
50 | const optsToRemoveTypeParamsMap = Object.freeze({
51 | removeFieldsWithMissingTypes: 'removeFieldsOfType',
52 | removeArgsWithMissingTypes: 'removeArgsOfType',
53 | removeInputFieldsWithMissingTypes: 'removeInputFieldsOfType',
54 | removePossibleTypesOfMissingTypes: 'removePossibleTypesOfType',
55 | removeDirectiveArgumentsOfMissingTypes: 'removeDirectiveArgumentsOfType',
56 | })
57 |
58 |
59 | const kindToFieldPropertyMap = Object.freeze({
60 | [KINDS.OBJECT]: 'fields',
61 | [KINDS.INTERFACE]: 'fields',
62 | [KINDS.INPUT_OBJECT]: 'inputFields',
63 | [KINDS.ENUM]: 'enumValues',
64 | })
65 |
66 | export class Microfiber {
67 | constructor(response, opts = {}) {
68 | if (!response) {
69 | throw new Error('No response provided!')
70 | }
71 |
72 | opts = defaults({}, opts, defaultOpts)
73 | this.setOpts(opts)
74 |
75 | // The rest of the initialization can be handled by this public method
76 | this.setResponse(response)
77 | }
78 |
79 | setOpts(opts) {
80 | this.opts = opts || {}
81 | }
82 |
83 | // Set/change the response on the instance
84 | setResponse(responseIn) {
85 | const response = JSON.parse(JSON.stringify(responseIn))
86 |
87 | if (this.opts._normalize) {
88 | const normalizedResponse = Microfiber.normalizeIntrospectionResponse(response)
89 | if (normalizedResponse !== response) {
90 | this._wasNormalized = true
91 | }
92 | this.schema = get(normalizedResponse, '__schema')
93 | } else {
94 | this.schema = response
95 | }
96 |
97 | if (this.opts.fixQueryAndMutationAndSubscriptionTypes) {
98 | this._fixQueryAndMutationAndSubscriptionTypes()
99 | }
100 |
101 | // OK, time to validate
102 | this._validate()
103 |
104 | if (this.opts._analyze) {
105 | this._analyze()
106 | }
107 |
108 | if (this.opts.cleanupSchemaImmediately) {
109 | this.cleanSchema()
110 | }
111 | }
112 |
113 | // This is how you get OUT what you've put in and manipulated
114 | getResponse() {
115 | const clonedResponse = {
116 | __schema: this._cloneSchema()
117 | }
118 |
119 | if (this._wasNormalized) {
120 | return {
121 | data: clonedResponse,
122 | }
123 | }
124 |
125 | return clonedResponse
126 | }
127 |
128 | static normalizeIntrospectionResponse(response) {
129 | if (response && response.data) {
130 | return response.data
131 | }
132 |
133 | return response
134 | }
135 |
136 | static digUnderlyingType(type) {
137 | return digUnderlyingType(type)
138 | }
139 |
140 | getAllTypes({
141 | // Include reserved GraphQL types?
142 | includeReserved = false,
143 | // Include the Query type?
144 | includeQuery = false,
145 | // Include the Mutation type?
146 | includeMutation = false,
147 | // Include the Subscription type?
148 | includeSubscription = false,
149 | } = {}) {
150 | const queryType = this.getQueryType()
151 | const mutationType = this.getMutationType()
152 | const subscriptionType = this.getSubscriptionType()
153 |
154 | return this.schema.types.filter((type) => {
155 | if (!includeReserved && isReservedType(type)) {
156 | return false
157 | }
158 | if (queryType && !includeQuery && typesAreSame(type, queryType)) {
159 | return false
160 | }
161 | if (mutationType && !includeMutation && typesAreSame(type, mutationType)) {
162 | return false
163 | }
164 |
165 | if (subscriptionType && !includeSubscription && typesAreSame(type, subscriptionType)) {
166 | return false
167 | }
168 |
169 | return true
170 | })
171 | }
172 |
173 | getType({ kind = KINDS.OBJECT, name }) {
174 | return this.schema.types[this._getTypeIndex({ kind, name })]
175 | }
176 |
177 | getDirectives () {
178 | return this.schema.directives
179 | }
180 |
181 | getDirective({ name }) {
182 | if (!name) {
183 | return
184 | }
185 |
186 | return this.getDirectives()[this._getDirectiveIndex({ name })]
187 | }
188 |
189 | getQueryType() {
190 | if (!this.queryTypeName) {
191 | return false
192 | }
193 |
194 | return this.getType({ kind: KINDS.OBJECT, name: this.queryTypeName })
195 | }
196 |
197 | getQuery({ name }) {
198 | const queryType = this.getQueryType()
199 | if (!queryType) {
200 | return false
201 | }
202 |
203 | return this.getField({ typeKind: queryType.kind, typeName: queryType.name, fieldName: name })
204 | }
205 |
206 | getMutationType() {
207 | if (!this.mutationTypeName) {
208 | return false
209 | }
210 |
211 | return this.getType({ kind: KINDS.OBJECT, name: this.mutationTypeName })
212 | }
213 |
214 | getMutation({ name }) {
215 | const mutationType = this.getMutationType()
216 | if (!mutationType) {
217 | return false
218 | }
219 |
220 | return this.getField({ typeKind: mutationType.kind, typeName: mutationType.name, fieldName: name })
221 | }
222 |
223 | getSubscriptionType() {
224 | if (!this.subscriptionTypeName) {
225 | return false
226 | }
227 |
228 | return this.getType({ kind: KINDS.OBJECT, name: this.subscriptionTypeName })
229 | }
230 |
231 | getSubscription({ name }) {
232 | const subscriptionType = this.getSubscriptionType()
233 | if (!subscriptionType) {
234 | return false
235 | }
236 |
237 | return this.getField({ typeKind: subscriptionType.kind, typeName: subscriptionType.name, fieldName: name })
238 | }
239 |
240 | getField({ typeKind = KINDS.OBJECT, typeName, fieldName }) {
241 | const type = this.getType({ kind: typeKind, name: typeName })
242 | if (!type) {
243 | return
244 | }
245 | const fieldsProperty = kindToFieldPropertyMap[typeKind]
246 | if (!(fieldsProperty && type[fieldsProperty])) {
247 | return
248 | }
249 |
250 | return type[fieldsProperty].find((field) => field.name === fieldName)
251 | }
252 |
253 | getEnumValue({ typeName, fieldName }) {
254 | return this.getField({ typeKind: KINDS.ENUM, typeName, fieldName })
255 | }
256 |
257 | getInputField({ typeName, fieldName }) {
258 | return this.getField({ typeKind: KINDS.INPUT_OBJECT, typeName, fieldName })
259 | }
260 |
261 | getInterfaceField({ typeName, fieldName }) {
262 | return this.getField({ typeKind: KINDS.INTERFACE, typeName, fieldName })
263 | }
264 |
265 | getArg({ typeKind = KINDS.OBJECT, typeName, fieldName, argName }) {
266 | const field = this.getField({ typeKind, typeName, fieldName })
267 | if (!(field && field.args.length)) {
268 | return
269 | }
270 |
271 | return field.args.find((arg) => arg.name === argName)
272 | }
273 |
274 | getDirectiveArg({ directiveName, argName }) {
275 | const directive = this.getDirective({ name: directiveName })
276 | if (!(directive && directive.args.length)) {
277 | return
278 | }
279 |
280 | return directive.args.find((arg) => arg.name === argName)
281 | }
282 |
283 | removeDirective({ name, cleanup = true }) {
284 | if (!name) {
285 | return
286 | }
287 | this.schema.directives = this.schema.directives.filter((directive) => directive.name !== name)
288 | if (cleanup) {
289 | this.cleanSchema()
290 | }
291 | }
292 |
293 | removeType({
294 | kind = KINDS.OBJECT,
295 | name,
296 | // Clean up the schema afterwards?
297 | cleanup = true,
298 | // Remove occurances of this Type from other places?
299 | removeFieldsOfType,
300 | removeInputFieldsOfType,
301 | removePossibleTypesOfType,
302 | removeArgsOfType,
303 | removeDirectiveArgumentsOfType,
304 | }) {
305 | const typeKey = buildKey({ kind, name })
306 | if (!Object.prototype.hasOwnProperty.call(this.typeToIndexMap, typeKey)) {
307 | return false
308 | }
309 | const typeIndex = this.typeToIndexMap[typeKey]
310 | if (isUndef(typeIndex)) {
311 | return false
312 | }
313 |
314 | // Create an object of some of the opts, but mapped to keys that match the params
315 | // of this method. They will then be used as the default value for the params
316 | // so that constructor opts will be the default, but they can be overridden in
317 | // the call.
318 | const mappedOpts = mapProps({ props: this.opts, map: optsToRemoveTypeParamsMap })
319 | const mergedOpts = defaults(
320 | {
321 | removeFieldsOfType,
322 | removeInputFieldsOfType,
323 | removePossibleTypesOfType,
324 | removeArgsOfType,
325 | removeDirectiveArgumentsOfType,
326 | },
327 | mappedOpts,
328 | )
329 |
330 | // If we are going to clean up afterwards, then the others should not have to
331 | const shouldOthersClean = !cleanup
332 |
333 | const originalSchema = this._cloneSchema()
334 |
335 | try {
336 | // If we are removing a special type like a Query or Mutation or Subscription
337 | // then there's some special stuff to do
338 | if (typesAreSame(this.getQueryType() || {}, { kind, name })) {
339 | delete this.queryTypeName
340 | delete this.schema.queryType
341 | } else if (typesAreSame(this.getMutationType() || {}, { kind, name })) {
342 | delete this.mutationTypeName
343 | delete this.schema.mutationType
344 | } else if (typesAreSame(this.getSubscriptionType() || {}, { kind, name })) {
345 | delete this.subscriptionTypeName
346 | delete this.schema.subscriptionType
347 | }
348 |
349 | // Do this *after* the special stuff above, if necessary
350 | delete this.schema.types[typeIndex]
351 | delete this.typeToIndexMap[typeKey]
352 |
353 | if (mergedOpts.removeArgsOfType) {
354 | this._removeArgumentsOfType({ kind, name, cleanup: shouldOthersClean })
355 | }
356 |
357 | if (mergedOpts.removeFieldsOfType) {
358 | this._removeFieldsOfType({ kind, name, cleanup: shouldOthersClean })
359 | }
360 |
361 | if (mergedOpts.removeInputFieldsOfType) {
362 | this._removeInputFieldsOfType({ kind, name, cleanup: shouldOthersClean })
363 | }
364 |
365 | // AKA Unions
366 | if (mergedOpts.removePossibleTypesOfType) {
367 | this._removePossibleTypesOfType({ kind, name, cleanup: shouldOthersClean })
368 | }
369 |
370 | if (mergedOpts.removeDirectiveArgumentsOfType) {
371 | this._removeDirectiveArgumentsOfType({ kind, name, cleanup: shouldOthersClean })
372 | }
373 |
374 | if (cleanup) {
375 | this.cleanSchema()
376 | }
377 |
378 | return true
379 | } catch (err) {
380 | this.schema = originalSchema
381 | throw err
382 | }
383 | }
384 |
385 | removeField({
386 | typeKind = KINDS.OBJECT,
387 | typeName,
388 | fieldName,
389 | // Clean up the schema afterwards?
390 | cleanup = true,
391 | }) {
392 | const type = this.getType({ kind: typeKind, name: typeName })
393 | if (!type) {
394 | return false
395 | }
396 |
397 | const fieldsProperty = kindToFieldPropertyMap[typeKind]
398 | if (!(fieldsProperty && type[fieldsProperty])) {
399 | return false
400 | }
401 |
402 | // TODO: build a map for the locations of fields on types?
403 | type[fieldsProperty] = type[fieldsProperty].filter((field) => field.name !== fieldName)
404 |
405 | if (cleanup) {
406 | this.cleanSchema()
407 | }
408 | }
409 |
410 | removeInputField({
411 | typeName,
412 | fieldName,
413 | // Clean up the schema afterwards?
414 | cleanup = true,
415 | }) {
416 | return this.removeField({ typeKind: KINDS.INPUT_OBJECT, typeName, fieldName, cleanup })
417 | }
418 |
419 | removeArg({
420 | typeKind,
421 | typeName,
422 | fieldName,
423 | argName,
424 | // Clean up the schema afterwards?
425 | cleanup = true,
426 | }) {
427 | const field = this.getField({ typeKind, typeName, fieldName })
428 | // field.args should alwys be an array, never null
429 | if (!field) {
430 | return false
431 | }
432 |
433 | // TODO: build a map for the locations of args on fields?
434 | field.args = field.args.filter((arg) => arg.name !== argName)
435 |
436 | if (cleanup) {
437 | this.cleanSchema()
438 | }
439 | }
440 |
441 | // Remove just a single possible value for an Enum, but not the whole Enum
442 | removeEnumValue({
443 | // The name of the Enum Type
444 | name,
445 | // The Enum value you want to remove
446 | value,
447 | }) {
448 | const type = this.getType({ kind: KINDS.ENUM, name })
449 | if (!(type && type.enumValues)) {
450 | return false
451 | }
452 |
453 | type.enumValues = type.enumValues.filter((enumValue) => enumValue.name !== value)
454 | }
455 |
456 | removePossibleType({
457 | // The name of the Union Type
458 | typeName,
459 | // The Kind of the possible Type you want to remove
460 | possibleTypeKind,
461 | // The name of the possible Type you want to remove
462 | possibleTypeName,
463 | // Clean up the schema afterwards?
464 | cleanup = true,
465 | }) {
466 | const type = this.getType({ kind: KINDS.UNION, name: typeName })
467 | if (!(type && type.possibleTypes)) {
468 | return false
469 | }
470 |
471 | type.possibleTypes = type.possibleTypes.filter((possibleType) => possibleType.type !== possibleTypeKind && possibleType.name !== possibleTypeName)
472 | if (cleanup) {
473 | this.cleanSchema()
474 | }
475 | }
476 |
477 | removeQuery({
478 | name,
479 | // Clean up the schema afterwards?
480 | cleanup = true,
481 | }) {
482 | if (!this.queryTypeName) {
483 | return false
484 | }
485 |
486 | this.removeField({ typeKind: KINDS.OBJECT, typeName: this.queryTypeName, fieldName: name, cleanup })
487 | }
488 |
489 | removeMutation({
490 | name,
491 | // Clean up the schema afterwards?
492 | cleanup = true,
493 | }) {
494 | if (!this.mutationTypeName) {
495 | return false
496 | }
497 |
498 | this.removeField({ typeKind: KINDS.OBJECT, typeName: this.mutationTypeName, fieldName: name, cleanup })
499 | }
500 |
501 | removeSubscription({
502 | name,
503 | // Clean up the schema afterwards?
504 | cleanup = true,
505 | }) {
506 | if (!this.subscriptionTypeName) {
507 | return false
508 | }
509 |
510 | this.removeField({ typeKind: KINDS.OBJECT, typeName: this.subscriptionTypeName, fieldName: name, cleanup })
511 | }
512 |
513 | // Removes all the undefined gaps created by various removals
514 | cleanSchema() {
515 | // Used to compare the schema before and after it was cleaned
516 | const schemaToStart = JSON.stringify(this.schema)
517 | const typesEncountered = new Set()
518 | const types = []
519 |
520 | const interfacesEncounteredKeys = new Set()
521 | const interfacesByKey = {}
522 |
523 | // The Query, Mutation and Subscription Types should never be removed due to not being referenced
524 | // by anything
525 | if (this.queryTypeName) {
526 | typesEncountered.add(buildKey({ kind: KINDS.OBJECT, name: this.queryTypeName }))
527 | }
528 | if (this.mutationTypeName) {
529 | typesEncountered.add(buildKey({ kind: KINDS.OBJECT, name: this.mutationTypeName }))
530 | }
531 | if (this.subscriptionTypeName) {
532 | typesEncountered.add(buildKey({ kind: KINDS.OBJECT, name: this.subscriptionTypeName }))
533 | }
534 |
535 | for (const directive of this.schema.directives) {
536 | if (!directive) {
537 | continue
538 | }
539 | const args = []
540 | for (const arg of directive.args) {
541 | const argType = digUnderlyingType(arg.type)
542 | // Don't add it if its return type does not exist
543 | if (!this._hasType(argType)) {
544 | continue
545 | }
546 |
547 | // Keep track of this so we know what we can remove
548 | typesEncountered.add(buildKey(argType))
549 |
550 | args.push(arg)
551 | }
552 |
553 | directive.args = args
554 | }
555 |
556 | for (const type of this.schema.types) {
557 | if (!type) {
558 | continue
559 | }
560 |
561 | types.push(type)
562 |
563 | if (type.kind === KINDS.INTERFACE) {
564 | interfacesByKey[buildKey(type)] = type
565 | }
566 |
567 | if (type.fields) {
568 | const fields = []
569 | for (const field of type.fields) {
570 | if (isUndef(field)) {
571 | continue
572 | }
573 |
574 | const fieldType = digUnderlyingType(field.type)
575 |
576 | // Don't add it if its return type does not exist
577 | if (!this._hasType(fieldType)) {
578 | continue
579 | }
580 |
581 | if (fieldType.kind === KINDS.INTERFACE) {
582 | interfacesEncounteredKeys.add(buildKey(fieldType))
583 | }
584 |
585 | // Keep track of this so we know what we can remove
586 | typesEncountered.add(buildKey(fieldType))
587 |
588 | const args = []
589 | for (const arg of (field.args || [])) {
590 | if (isUndef(arg)) {
591 | continue
592 | }
593 |
594 | const argType = digUnderlyingType(arg.type)
595 | // Don't add it if its return type does not exist
596 | if (!this._hasType(argType)) {
597 | continue
598 | }
599 |
600 | // Keep track of this so we know what we can remove
601 | typesEncountered.add(buildKey(argType))
602 |
603 | args.push(arg)
604 | }
605 |
606 | // Args will always be an array. Possible empty, but never null
607 | field.args = args
608 | fields.push(field)
609 | }
610 |
611 | type.fields = fields
612 | }
613 |
614 | if (type.inputFields) {
615 | const inputFields = []
616 | // Don't add it in if it's undefined, or the type is gone
617 | for (const inputField of type.inputFields) {
618 | if (isUndef(inputField)) {
619 | continue
620 | }
621 |
622 | const inputFieldType = digUnderlyingType(inputField.type)
623 | // Don't add it if its return type does not exist
624 | if (!this._hasType(inputFieldType)) {
625 | continue
626 | }
627 |
628 | // Keep track of this so we know what we can remove
629 | typesEncountered.add(buildKey(inputFieldType))
630 |
631 | inputFields.push(inputField)
632 | }
633 |
634 | type.inputFields = inputFields
635 | }
636 |
637 | if (type.possibleTypes) {
638 | const possibleTypes = []
639 | for (const possibleType of type.possibleTypes) {
640 | if (isUndef(possibleType)) {
641 | continue
642 | }
643 |
644 | // possibleTypes array entries have no envelope for the type
645 | // so do not do possibleType.type here
646 | const possibleTypeType = digUnderlyingType(possibleType)
647 | // Don't add it if its return type does not exist
648 | if (!this._hasType(possibleTypeType)) {
649 | continue
650 | }
651 |
652 | // Interfaces themselves list all the things that have "implemented" them
653 | // in the "possibleTypes" array...but we don't want that to be an indication
654 | // that the thing has been used.
655 | if (type.kind !== KINDS.INTERFACE) {
656 | // Keep track of this so we know what we can remove
657 | typesEncountered.add(buildKey(possibleTypeType))
658 | }
659 |
660 | possibleTypes.push(possibleType)
661 | }
662 |
663 | type.possibleTypes = possibleTypes
664 | }
665 |
666 | if (type.interfaces) {
667 | const interfaces = []
668 | for (const interfayce of type.interfaces) {
669 | if (isUndef(interfayce)) {
670 | continue
671 | }
672 |
673 | if (!this._hasType(interfayce)) {
674 | continue
675 | }
676 |
677 | typesEncountered.add(buildKey(interfayce))
678 |
679 | interfaces.push(interfayce)
680 | }
681 |
682 | type.interfaces = interfaces
683 | }
684 | }
685 |
686 | for (const interfaceEncounteredKey of interfacesEncounteredKeys.values()) {
687 | const interfayce = interfacesByKey[interfaceEncounteredKey]
688 | for (const possibleType of interfayce?.possibleTypes || []) {
689 | if (!this._hasType(possibleType)) {
690 | continue
691 | }
692 |
693 | // Keep track of this so we know what we can remove
694 | typesEncountered.add(buildKey(possibleType))
695 | }
696 | }
697 |
698 | // Only include Types that we encountered - if the options say to do so
699 | const possiblyFilteredTypes = this.opts.removeUnusedTypes ? types.filter((type) => isReservedType(type) || typesEncountered.has(buildKey(type))) : types
700 |
701 | // Replace the Schema
702 | this.schema = {
703 | ...this.schema,
704 | types: possiblyFilteredTypes,
705 | }
706 |
707 | // Need to re-analyze it, too
708 | this._analyze()
709 |
710 | // If the schema was changed by this cleanup, we should run it again to see if other things
711 | // should be removed...and continue to do so until the schema is stable.
712 | if (schemaToStart !== JSON.stringify(this.schema)) {
713 | return this.cleanSchema()
714 | }
715 | }
716 |
717 | //******************************************************************
718 | //
719 | //
720 | // PRIVATE
721 | //
722 | //
723 |
724 | _validate() {
725 | if (!this.schema) {
726 | throw new Error('No schema property detected!')
727 | }
728 |
729 | if (!this.schema.types) {
730 | throw new Error('No types detected!')
731 | }
732 |
733 | // Must have a Query type...but not necessarily a Mutation type
734 | if (!get(this.schema, `queryType.name`)) {
735 | throw new Error(`No queryType detected!`)
736 | }
737 | }
738 |
739 | _analyze() {
740 | // Map the kind + name to the index in the types array
741 | this.typeToIndexMap = {}
742 | this.fieldsOfTypeMap = {}
743 | this.inputFieldsOfTypeMap = {}
744 | // AKA Unions
745 | this.possibleTypesOfTypeMap = {}
746 | this.argsOfTypeMap = {}
747 |
748 | this.directiveToIndexMap = {}
749 | this.directiveArgsOfTypeMap = {}
750 |
751 | // Need to keep track of these so that we never remove them for not being referenced
752 | this.queryTypeName = get(this.schema, 'queryType.name')
753 | this.mutationTypeName = get(this.schema, 'mutationType.name')
754 | this.subscriptionTypeName = get(this.schema, 'subscriptionType.name')
755 |
756 | for (let directivesIdx = 0; directivesIdx < this.schema.directives.length; directivesIdx++) {
757 | const directive = this.schema.directives[directivesIdx]
758 | if (isUndef(directive)) {
759 | continue
760 | }
761 |
762 | const directivesKey = buildKey({ kind: 'DIRECTIVE', name: directive.name })
763 | this.directiveToIndexMap[directivesKey] = directivesIdx
764 |
765 | const directivePath = `directives.${directivesIdx}`
766 |
767 | for (let argsIdx = 0; argsIdx < directive.args.length; argsIdx++) {
768 | const arg = directive.args[argsIdx]
769 | if (isUndef(arg)) {
770 | continue
771 | }
772 | const argType = digUnderlyingType(arg.type)
773 | const argsKey = buildKey(argType)
774 | if (!this.directiveArgsOfTypeMap[argsKey]) {
775 | this.directiveArgsOfTypeMap[argsKey] = []
776 | }
777 |
778 | const argPath = `${directivePath}.args.${argsIdx}`
779 | this.directiveArgsOfTypeMap[argsKey].push(argPath)
780 | }
781 | }
782 |
783 | for (let typesIdx = 0; typesIdx < this.schema.types.length; typesIdx++) {
784 | const type = this.schema.types[typesIdx]
785 | if (isUndef(type)) {
786 | continue
787 | }
788 |
789 | const {
790 | kind,
791 | name,
792 | } = type
793 | // These come in as null, not undefined
794 | const fields = type.fields || []
795 | const inputFields = type.inputFields || []
796 | const possibleTypes = type.possibleTypes || []
797 |
798 | const typesKey = buildKey({ kind, name })
799 | this.typeToIndexMap[typesKey] = typesIdx
800 |
801 | for (let fieldsIdx = 0; fieldsIdx < fields.length; fieldsIdx++) {
802 | const field = fields[fieldsIdx]
803 | if (isUndef(field)) {
804 | continue
805 | }
806 |
807 | const fieldType = digUnderlyingType(field.type)
808 | // This should always be arrays...maybe empty, never null
809 | const args = field.args || []
810 |
811 | const fieldsKey = buildKey(fieldType)
812 | if (!this.fieldsOfTypeMap[fieldsKey]) {
813 | this.fieldsOfTypeMap[fieldsKey] = []
814 | }
815 |
816 | const fieldPath = `types.${typesIdx}.fields.${fieldsIdx}`
817 | this.fieldsOfTypeMap[fieldsKey].push(fieldPath)
818 |
819 | for (let argsIdx = 0; argsIdx < args.length; argsIdx++) {
820 | const arg = args[argsIdx]
821 | if (isUndef(arg)) {
822 | continue
823 | }
824 | const argType = digUnderlyingType(arg.type)
825 |
826 | const argsKey = buildKey(argType)
827 | if (!this.argsOfTypeMap[argsKey]) {
828 | this.argsOfTypeMap[argsKey] = []
829 | }
830 |
831 | const argPath = `${fieldPath}.args.${argsIdx}`
832 | this.argsOfTypeMap[argsKey].push(argPath)
833 | }
834 | }
835 |
836 | for (let inputFieldsIdx = 0; inputFieldsIdx < inputFields.length; inputFieldsIdx++) {
837 | const inputField = inputFields[inputFieldsIdx]
838 | if (isUndef(inputField)) {
839 | continue
840 | }
841 | const inputFieldType = digUnderlyingType(inputField.type)
842 | const inputFieldsKey = buildKey(inputFieldType)
843 | if (!this.inputFieldsOfTypeMap[inputFieldsKey]) {
844 | this.inputFieldsOfTypeMap[inputFieldsKey] = []
845 | }
846 | const inputFieldPath = `types.${typesIdx}.inputFields.${inputFieldsIdx}`
847 | this.inputFieldsOfTypeMap[inputFieldsKey].push(inputFieldPath)
848 | }
849 |
850 | for (let possibleTypesIdx = 0; possibleTypesIdx < possibleTypes.length; possibleTypesIdx++) {
851 | const possibleType = possibleTypes[possibleTypesIdx]
852 | if (isUndef(possibleType)) {
853 | continue
854 | }
855 |
856 | const possibleTypeType = digUnderlyingType(possibleType)
857 | const possibleTypeKey = buildKey(possibleTypeType)
858 | if (!this.possibleTypesOfTypeMap[possibleTypeKey]) {
859 | this.possibleTypesOfTypeMap[possibleTypeKey] = []
860 | }
861 | const possibleTypePath = `types.${typesIdx}.possibleTypes.${possibleTypesIdx}`
862 | this.possibleTypesOfTypeMap[possibleTypeKey].push(possibleTypePath)
863 | }
864 | }
865 | }
866 |
867 | _fixQueryAndMutationAndSubscriptionTypes(response) {
868 | for (const [key, defaultTypeName] of [['queryType', 'Query'], ['mutationType', 'Mutation'], ['subscriptionType', 'Subscription']]) {
869 | const queryOrMutationOrSubscriptionTypeName = get(response, `__schema.${key}.name`)
870 | if (queryOrMutationOrSubscriptionTypeName && !this.getType({ kind: KINDS.OBJECT, name: queryOrMutationOrSubscriptionTypeName })) {
871 | this.schema[key] = { name: defaultTypeName }
872 | }
873 | }
874 | }
875 |
876 | _getTypeIndex({ kind, name }) {
877 | const key = buildKey({ kind, name })
878 | if (Object.prototype.hasOwnProperty.call(this.typeToIndexMap, key)) {
879 | return this.typeToIndexMap[key]
880 | }
881 |
882 | return false
883 | }
884 |
885 | _getDirectiveIndex({ name }) {
886 | const key = buildKey({ kind: 'DIRECTIVE', name })
887 | if (Object.prototype.hasOwnProperty.call(this.directiveToIndexMap, key)) {
888 | return this.directiveToIndexMap[key]
889 | }
890 |
891 | return false
892 | }
893 |
894 | _removeThingsOfType({ kind, name, map, cleanup = true }) {
895 | const key = buildKey({ kind, name })
896 | for (const path of (map[key] || [])) {
897 | unset(this.schema, path)
898 | }
899 |
900 | delete map[key]
901 |
902 | if (cleanup) {
903 | this.cleanSchema()
904 | }
905 | }
906 |
907 | _removeFieldsOfType({
908 | kind,
909 | name,
910 | // Clean up the schema afterwards?
911 | cleanup = true,
912 | }) {
913 | return this._removeThingsOfType({ kind, name, map: this.fieldsOfTypeMap, cleanup })
914 | }
915 |
916 | _removeInputFieldsOfType({
917 | kind,
918 | name,
919 | // Clean up the schema afterwards?
920 | cleanup = true,
921 | }) {
922 | return this._removeThingsOfType({ kind, name, map: this.inputFieldsOfTypeMap, cleanup })
923 | }
924 |
925 | // AKA Unions
926 | _removePossibleTypesOfType({
927 | kind,
928 | name,
929 | // Clean up the schema afterwards?
930 | cleanup = true,
931 | }) {
932 | return this._removeThingsOfType({ kind, name, map: this.possibleTypesOfTypeMap, cleanup })
933 | }
934 |
935 | _removeArgumentsOfType({
936 | kind,
937 | name,
938 | // Clean up the schema afterwards?
939 | cleanup = true,
940 | }) {
941 | return this._removeThingsOfType({ kind, name, map: this.argsOfTypeMap, cleanup })
942 | }
943 |
944 | _removeDirectiveArgumentsOfType({
945 | // kind,
946 | name,
947 | // Clean up the schema afterwards?
948 | cleanup = true,
949 | }) {
950 | return this._removeThingsOfType({ kind: 'DIRECTIVE', name, map: this.directiveArgsOfTypeMap, cleanup })
951 | }
952 |
953 | _cloneSchema() {
954 | return JSON.parse(JSON.stringify(this.schema))
955 | }
956 |
957 | _hasType({ kind, name }) {
958 | const key = buildKey({ kind, name })
959 | return Object.prototype.hasOwnProperty.call(this.typeToIndexMap, key)
960 | }
961 | }
962 |
963 | // A function that digs through any Non-Null and List nesting and returns the underlying Type.
964 | export function digUnderlyingType(type) {
965 | while ([KINDS.NON_NULL, KINDS.LIST].includes(type.kind)) {
966 | type = type.ofType
967 | }
968 | return type
969 | }
970 |
971 | // A function that returns a Boolean indicating whether a Type is special GraphQL reserved Type.
972 | export function isReservedType(type) {
973 | return type.name.startsWith('__')
974 | }
975 |
976 | function buildKey({ kind, name }) {
977 | return kind + ':' + name
978 | }
979 |
980 | function isUndef(item) {
981 | return typeof item === 'undefined'
982 | }
983 |
984 | function mapProps({ props, map }) {
985 | return Object.entries(map).reduce(
986 | (acc, [from, to]) => {
987 | if (Object.prototype.hasOwnProperty.call(props, from)) {
988 | acc[to] = props[from]
989 | }
990 | return acc
991 | },
992 | {},
993 | )
994 | }
995 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | import isEqual from 'lodash.isequal'
2 | import {
3 | Microfiber,
4 | KINDS,
5 | } from '../index'
6 |
7 | import {
8 | introspectionResponseFromSchemaSDL,
9 | } from './test-helpers'
10 |
11 |
12 | describe('index', function () {
13 | def('QueryType', () => `type Query {
14 | myTypes: [MyType!]
15 | myMyOtherInterfaceTypes: [MyOtherInterface]
16 | myOtherOtherInterfaceType: MyOtherOtherInterfaceType
17 | myUnions: [MyUnion]
18 | myOtherUnionType: MyOtherUnionType
19 | }`
20 | )
21 |
22 | def('MutationType', () => `type Mutation {
23 | createYetAnotherType(name: String!): YetAnotherType!
24 | }`
25 | )
26 |
27 | def('SubscriptionType', () => `type Subscription {
28 | subscribeToMyTypeFieldStringChanges(myTypeId: ID): RandomTypeOne!
29 | }`
30 | )
31 |
32 | def('schemaSDLBase', () => `
33 |
34 | # From the GraphQL docs:
35 | #
36 | # https://graphql.org/graphql-js/mutations-and-input-types/
37 | # Input types can't have fields that are other objects, only basic scalar types, list types,
38 | # and other input types.
39 |
40 | scalar SecretScalar
41 |
42 | enum SecretEnum {
43 | ENUM1
44 | ENUM2
45 | ENUM3
46 | }
47 |
48 | input InputWithSecretScalar {
49 | string: String
50 | secretScalar: [SecretScalar]
51 | }
52 |
53 | input InputWithSecretEnum {
54 | string: String
55 | otherString: String
56 | secretEnum: [SecretEnum]
57 | }
58 |
59 | union SecretUnion =
60 | MyType
61 | | MyOtherType
62 |
63 | ${$.QueryType}
64 |
65 | ${$.MutationType}
66 |
67 | ${$.SubscriptionType}
68 |
69 | interface MyInterface {
70 | id: String!
71 | }
72 |
73 | type MyType implements MyInterface {
74 | # required due to Interface
75 | id: String!
76 |
77 | # The control
78 | fieldString(argString: String): String
79 |
80 | # SecretScalar stuff
81 |
82 | # Fields returning SecretScalar
83 | fieldSecretScalar: SecretScalar
84 | fieldSecretScalarArray: [SecretScalar]
85 | fieldSecretScalarNonNullArray: [SecretScalar]!
86 | fieldSecretScalarNonNullArrayOfNonNulls: [SecretScalar!]!
87 |
88 | # Fields with args containing SecretScalars
89 | fieldStringWithSecretScalarArg(
90 | argString: String,
91 | argSecretScalar: SecretScalar
92 | ): String
93 |
94 | fieldStringWithSecretScalarArrayArg(
95 | argString: String,
96 | argSecretScalar: [SecretScalar]
97 | ): String
98 |
99 | fieldStringWithSecretScalarNonNullArrayArg(
100 | argString: String,
101 | argSecretScalar: [SecretScalar]!
102 | ): String
103 |
104 | fieldStringWithSecretScalarNonNullArrayOfNonNullsArg(
105 | argString: String,
106 | argSecretScalar: [SecretScalar!]!
107 | ): String
108 |
109 | # Fields with Inputs that contain SecretScalar
110 | fieldWithSecretScalarInputArg(input: InputWithSecretScalar): String
111 |
112 | # Fields with Inputs that contain SecretEnum
113 | fieldWithSecretEnumInputArg(input: InputWithSecretEnum): String
114 |
115 |
116 | # SecretEnum stuff
117 |
118 | # Fields returning SecretEnum
119 | fieldSecretEnum: SecretEnum
120 | fieldSecretEnumArray: [SecretEnum]
121 | fieldSecretEnumNonNullArray: [SecretEnum]!
122 | fieldSecretEnumNonNullArrayOfNonNulls: [SecretEnum!]!
123 |
124 | # Fields with args containing SecretEnum
125 | fieldStringWithSecretEnumArg(
126 | argString: String,
127 | argSecretEnum: SecretEnum
128 | ): String
129 |
130 | fieldStringWithSecretEnumArrayArg(
131 | argString: String,
132 | argSecretEnum: [SecretEnum]
133 | ): String
134 |
135 | fieldStringWithSecretEnumNonNullArrayArg(
136 | argString: String,
137 | argSecretEnum: [SecretEnum]!
138 | ): String
139 |
140 | fieldStringWithSecretEnumNonNullArrayOfNonNullsArg(
141 | argString: String,
142 | argSecretEnum: [SecretEnum!]!
143 | ): String
144 |
145 |
146 | # SecretUnion stuff
147 |
148 | # Fields returning SecretUnion
149 | fieldSecretUnion: SecretUnion
150 | fieldSecretUnionArray: [SecretUnion]
151 | fieldSecretUnionNonNullArray: [SecretUnion]!
152 | fieldSecretUnionNonNullArrayOfNonNulls: [SecretUnion!]!
153 | }
154 |
155 | type MyOtherType {
156 | fieldString(argString: String): String
157 | }
158 |
159 | type YetAnotherType {
160 | fieldString: String
161 | }
162 |
163 | type RandomTypeOne {
164 | fieldString: String
165 | }
166 |
167 | "Should not show up because it was not used anywhere"
168 | type NotUsed {
169 | referencedButNotUsed: ReferencedButNotUsed
170 | }
171 |
172 | "Should not show up because the only thing that references it was not used"
173 | type ReferencedButNotUsed {
174 | name: String
175 | }
176 |
177 | "I am definitely not used at all"
178 | type TotallyNotUsed {
179 | name: String
180 | }
181 |
182 | input DirectiveOption {
183 | key: String!
184 | value: String!
185 | }
186 |
187 | input AnotherDirectiveOption {
188 | key: String!
189 | value: String!
190 | }
191 |
192 | interface MyOtherInterface {
193 | id: String!
194 | }
195 |
196 | type MyOtherInterfaceType implements MyOtherInterface {
197 | id: String!
198 | foo: String
199 | }
200 |
201 | type MyOtherOtherInterfaceType implements MyOtherInterface {
202 | id: String!
203 | foo: String
204 | }
205 |
206 | type MyUnionType {
207 | foo: String
208 | }
209 |
210 | type MyOtherUnionType {
211 | bar: String
212 | }
213 |
214 | union MyUnion = MyUnionType | MyOtherUnionType
215 |
216 | directive @foo(option: DirectiveOption, anotherOption: AnotherDirectiveOption) on OBJECT
217 | `
218 | )
219 | def('schemaSDL', () => $.schemaSDLBase)
220 |
221 | def('metadataBase', () => ({
222 | 'OBJECT': {
223 | MyType: {
224 |
225 | },
226 | OtherType: {
227 |
228 | },
229 | Query: {
230 |
231 | },
232 | Mutation: {
233 |
234 | }
235 | },
236 | 'INPUT_OBJECT': {
237 | MyInput: {
238 |
239 | },
240 | }
241 | }))
242 |
243 | def('metadata', () => $.metadataBase)
244 |
245 | def('rawResponse', () => introspectionResponseFromSchemaSDL({
246 | schemaSDL: $.schemaSDL
247 | }))
248 |
249 | def('response', () => $.rawResponse)
250 |
251 | def('schema', () => $.response.__schema)
252 |
253 | it('works', function () {
254 | let microfiber = new Microfiber($.response, { cleanupSchemaImmediately: false })
255 | let response = microfiber.getResponse()
256 |
257 | //************************
258 | //
259 | // Sanity checks
260 | expect(isEqual($.response, response)).to.be.true
261 |
262 | microfiber = new Microfiber($.response)
263 | response = microfiber.getResponse()
264 | // Some cleanup occurred right away
265 | expect(isEqual($.response, response)).to.not.be.true
266 |
267 | let queryType = microfiber.getQueryType()
268 | expect(queryType).to.be.ok
269 | expect(queryType).to.eql(findType({ kind: KINDS.OBJECT, name: 'Query', response }))
270 | expect(microfiber.getQuery({ name: 'myTypes' })).be.ok
271 |
272 | let mutationType = microfiber.getMutationType()
273 | expect(mutationType).to.be.ok
274 | expect(mutationType).to.eql(findType({ kind: KINDS.OBJECT, name: 'Mutation', response }))
275 | expect(microfiber.getMutation({ name: 'createYetAnotherType' })).be.ok
276 |
277 | let subscriptionType = microfiber.getSubscriptionType()
278 | expect(subscriptionType).to.be.ok
279 | expect(subscriptionType).to.eql(findType({ kind: KINDS.OBJECT, name: 'Subscription', response }))
280 | expect(microfiber.getSubscription({ name: 'subscribeToMyTypeFieldStringChanges' })).be.ok
281 |
282 | expect(microfiber.getType({ kind: KINDS.INTERFACE, name: 'MyInterface' })).to.be.ok
283 | let myInterfaceId = microfiber.getField({ typeKind: KINDS.INTERFACE, typeName: 'MyInterface', fieldName: 'id' })
284 | expect(myInterfaceId).to.be.ok
285 | expect(microfiber.getInterfaceField({ typeName: 'MyInterface', fieldName: 'id' })).to.eql(myInterfaceId)
286 |
287 | expect(microfiber.getType({ kind: KINDS.INTERFACE, name: 'MyOtherInterface' })).to.be.ok
288 | expect(microfiber.getType({ name: 'MyOtherInterfaceType' })).to.be.ok
289 | expect(microfiber.getType({ name: 'MyOtherOtherInterfaceType' })).to.be.ok
290 |
291 | expect(microfiber.getType({ kind: KINDS.UNION, name: 'MyUnion' })).to.be.ok
292 | expect(microfiber.getType({ name: 'MyUnionType' })).to.be.ok
293 | expect(microfiber.getType({ name: 'MyOtherUnionType' })).to.be.ok
294 |
295 | expect(microfiber.getDirective({ name: 'foo' })).to.be.ok
296 | expect(microfiber.getDirectiveArg({ directiveName: 'foo', argName: 'option' })).to.be.ok
297 | expect(microfiber.getType({ kind: KINDS.INPUT_OBJECT, name: 'DirectiveOption' })).to.be.ok
298 | expect(microfiber.getDirectiveArg({ directiveName: 'foo', argName: 'anotherOption' })).to.be.ok
299 | expect(microfiber.getType({ kind: KINDS.INPUT_OBJECT, name: 'AnotherDirectiveOption' })).to.be.ok
300 |
301 | expect(microfiber.getType({ name: 'NotUsed' })).to.not.be.ok
302 | expect(microfiber.getType({ name: 'ReferencedButNotUsed' })).to.not.be.ok
303 | expect(microfiber.getType({ name: 'TotallyNotUsed' })).to.not.be.ok
304 |
305 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldString', response })).to.be.ok
306 | expect(findType({ kind: KINDS.SCALAR, name: 'SecretScalar', response })).to.be.ok
307 |
308 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretScalar', response })).to.be.ok
309 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretScalarArray', response })).to.be.ok
310 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretScalarNonNullArray', response })).to.be.ok
311 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretScalarNonNullArrayOfNonNulls', response })).to.be.ok
312 |
313 | let arg = microfiber.getArg({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarArg', argName: 'argSecretScalar', response })
314 | expect(arg).to.be.ok
315 | expect(arg).to.eql(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarArg', argName: 'argSecretScalar', response }))
316 | arg = microfiber.getArg({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarArg', argName: 'argSecretScalar', response })
317 | expect(arg).to.be.ok
318 | arg = microfiber.getArg({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarArrayArg', argName: 'argSecretScalar', response })
319 | expect(arg).to.be.ok
320 | arg = microfiber.getArg({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarNonNullArrayArg', argName: 'argSecretScalar', response })
321 | expect(arg).to.be.ok
322 | arg = microfiber.getArg({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarNonNullArrayOfNonNullsArg', argName: 'argSecretScalar', response })
323 | expect(arg).to.be.ok
324 |
325 | // Won't work because typeKind defaults to OBJECT
326 | expect(microfiber.getField({ typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })).to.not.be.ok
327 | // This works, though
328 | expect(microfiber.getField({ typeKind: KINDS.INPUT_OBJECT, typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })).to.be.ok
329 | expect(microfiber.getInputField({ typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })).to.be.ok
330 | expect(microfiber.getInputField({ typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })).to.eql(
331 | microfiber.getField({ typeKind: KINDS.INPUT_OBJECT, typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })
332 | )
333 | expect(microfiber.getInputField({ typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })).to.eql(
334 | findInputFieldOnInputType({ typeName: 'InputWithSecretScalar', fieldName: 'secretScalar', response })
335 | )
336 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretScalarInputArg' })).to.be.ok
337 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretScalarInputArg' })).to.eql(
338 | findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldWithSecretScalarInputArg', response })
339 | )
340 |
341 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'secretEnum' })).to.be.ok
342 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'secretEnum' })).to.eql(
343 | findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'secretEnum', response })
344 | )
345 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg', response })).to.be.ok
346 |
347 | //
348 | //
349 | //************************
350 |
351 | let secretEnum = findType({ kind: KINDS.ENUM, name: 'SecretEnum', response })
352 | expect(secretEnum).to.be.ok
353 | expect(secretEnum.enumValues).to.be.an('array').of.length(3)
354 | expect(secretEnum.enumValues.map((enumValue) => enumValue.name)).eql(['ENUM1', 'ENUM2', 'ENUM3'])
355 | expect(secretEnum).to.eql(microfiber.getType({ kind: KINDS.ENUM, name: 'SecretEnum' }))
356 |
357 | let secretEnumValue = microfiber.getEnumValue({ typeName: 'SecretEnum', fieldName: 'ENUM2' })
358 | expect(secretEnumValue).to.be.ok
359 | expect(secretEnumValue.name).to.eql('ENUM2')
360 |
361 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnum', response })).to.be.ok
362 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumArray', response })).to.be.ok
363 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumNonNullArray', response })).to.be.ok
364 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumNonNullArrayOfNonNulls', response })).to.be.ok
365 |
366 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumArg', argName: 'argSecretEnum', response })).to.be.ok
367 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumArrayArg', argName: 'argSecretEnum', response })).to.be.ok
368 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumNonNullArrayArg', argName: 'argSecretEnum', response })).to.be.ok
369 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumNonNullArrayOfNonNullsArg', argName: 'argSecretEnum', response })).to.be.ok
370 |
371 |
372 | expect(findType({ kind: KINDS.UNION, name: 'SecretUnion', response})).to.be.ok
373 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnion', response })).to.be.ok
374 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionArray', response })).to.be.ok
375 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionNonNullArray', response })).to.be.ok
376 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionNonNullArrayOfNonNulls', response })).to.be.ok
377 |
378 | // OK, let's do some things
379 |
380 | // Remove SecretScalar
381 | microfiber.removeType({ kind: KINDS.SCALAR, name: 'SecretScalar' })
382 | response = microfiber.getResponse()
383 | expect(isEqual($.response, response)).to.be.false
384 | expect(findType({ kind: KINDS.OBJECT, name: 'Query', response })).to.be.ok
385 | expect(findType({ kind: KINDS.OBJECT, name: 'Mutation', response })).to.be.ok
386 | expect(findType({ kind: KINDS.OBJECT, name: 'Subscription', response })).to.be.ok
387 |
388 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldString', response })).to.be.ok
389 | expect(findType({ kind: KINDS.SCALAR, name: 'SecretScalar', response })).to.not.be.ok
390 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretScalar', response })).to.not.be.ok
391 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretScalarArray', response })).to.not.be.ok
392 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretScalarNonNullArray', response })).to.not.be.ok
393 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretScalarNonNullArrayOfNonNulls', response })).to.not.be.ok
394 |
395 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarArg', argName: 'argSecretScalar', response })).to.not.be.ok
396 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarArrayArg', argName: 'argSecretScalar', response })).to.not.be.ok
397 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarNonNullArrayArg', argName: 'argSecretScalar', response })).to.not.be.ok
398 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretScalarNonNullArrayOfNonNullsArg', argName: 'argSecretScalar', response })).to.not.be.ok
399 |
400 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretScalar', fieldName: 'secretScalar', response })).to.not.be.ok
401 | expect(microfiber.getField({ typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })).to.not.be.ok
402 | expect(microfiber.getField({ typeKind: KINDS.INPUT_OBJECT, typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })).to.not.be.ok
403 | expect(microfiber.getInputField({ typeName: 'InputWithSecretScalar', fieldName: 'secretScalar' })).to.not.be.ok
404 | // these should still be ok
405 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretScalarInputArg' })).to.be.ok
406 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretScalarInputArg' })).to.eql(
407 | findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldWithSecretScalarInputArg', response })
408 | )
409 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'secretEnum', response })).to.be.ok
410 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg', response })).to.be.ok
411 |
412 | // Remove a specific SecretEnum value
413 |
414 | secretEnum = findType({ kind: KINDS.ENUM, name: 'SecretEnum', response })
415 | expect(secretEnum).to.be.ok
416 | expect(secretEnum.enumValues).to.be.an('array').of.length(3)
417 |
418 | microfiber.removeEnumValue({ name: 'SecretEnum', value: 'ENUM2' })
419 | response = microfiber.getResponse()
420 | expect(findType({ kind: KINDS.OBJECT, name: 'Query', response })).to.be.ok
421 | expect(findType({ kind: KINDS.OBJECT, name: 'Mutation', response })).to.be.ok
422 | expect(findType({ kind: KINDS.OBJECT, name: 'Subscription', response })).to.be.ok
423 |
424 | // Removed that value so only 2 left
425 | secretEnum = findType({ kind: KINDS.ENUM, name: 'SecretEnum', response })
426 | expect(secretEnum).to.be.ok
427 | expect(secretEnum.enumValues).to.be.an('array').of.length(2)
428 | expect(secretEnum.enumValues.map((enumValue) => enumValue.name)).eql(['ENUM1', 'ENUM3'])
429 |
430 | secretEnumValue = microfiber.getEnumValue({ typeName: 'SecretEnum', fieldName: 'ENUM2' })
431 | expect(secretEnumValue).to.not.be.ok
432 |
433 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnum', response })).to.be.ok
434 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumArray', response })).to.be.ok
435 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumNonNullArray', response })).to.be.ok
436 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumNonNullArrayOfNonNulls', response })).to.be.ok
437 |
438 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumArg', argName: 'argSecretEnum', response })).to.be.ok
439 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumArrayArg', argName: 'argSecretEnum', response })).to.be.ok
440 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumNonNullArrayArg', argName: 'argSecretEnum', response })).to.be.ok
441 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumNonNullArrayOfNonNullsArg', argName: 'argSecretEnum', response })).to.be.ok
442 |
443 |
444 | // Remove SecretEnum completely
445 |
446 | microfiber.removeType({ kind: KINDS.ENUM, name: 'SecretEnum' })
447 | response = microfiber.getResponse()
448 | expect(findType({ kind: KINDS.OBJECT, name: 'Query', response })).to.be.ok
449 | expect(findType({ kind: KINDS.OBJECT, name: 'Mutation', response })).to.be.ok
450 | expect(findType({ kind: KINDS.OBJECT, name: 'Subscription', response })).to.be.ok
451 |
452 | expect(findType({ kind: KINDS.ENUM, name: 'SecretEnum', response })).to.not.be.ok
453 | expect(microfiber.getType({ kind: KINDS.ENUM, name: 'SecretEnum' })).to.not.be.ok
454 | secretEnumValue = microfiber.getEnumValue({ typeName: 'SecretEnum', fieldName: 'ENUM1' })
455 | expect(secretEnumValue).to.not.be.ok
456 |
457 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnum', response })).to.not.be.ok
458 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumArray', response })).to.not.be.ok
459 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumNonNullArray', response })).to.not.be.ok
460 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretEnumNonNullArrayOfNonNulls', response })).to.not.be.ok
461 |
462 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumArg', argName: 'argSecretEnum', response })).to.not.be.ok
463 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumArrayArg', argName: 'argSecretEnum', response })).to.not.be.ok
464 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumNonNullArrayArg', argName: 'argSecretEnum', response })).to.not.be.ok
465 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldStringWithSecretEnumNonNullArrayOfNonNullsArg', argName: 'argSecretEnum', response })).to.not.be.ok
466 |
467 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg' })).to.be.ok
468 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'secretEnum', response })).to.not.be.ok
469 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg', response })).to.be.ok
470 |
471 | // Remove an Arg from a Field
472 |
473 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldString', argName: 'argString', response })).to.be.ok
474 | microfiber.removeArg({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldString', argName: 'argString' })
475 | response = microfiber.getResponse()
476 | expect(findType({ kind: KINDS.OBJECT, name: 'Query', response })).to.be.ok
477 | expect(findType({ kind: KINDS.OBJECT, name: 'Mutation', response })).to.be.ok
478 | expect(findType({ kind: KINDS.OBJECT, name: 'Subscription', response })).to.be.ok
479 | expect(findArgOnFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldString', argName: 'argString', response })).to.not.be.ok
480 |
481 | // Remove a Field from a Type
482 |
483 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldString', response })).to.be.ok
484 | microfiber.removeField({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldString' })
485 | response = microfiber.getResponse()
486 | expect(findType({ kind: KINDS.OBJECT, name: 'Query', response })).to.be.ok
487 | expect(findType({ kind: KINDS.OBJECT, name: 'Mutation', response })).to.be.ok
488 | expect(findType({ kind: KINDS.OBJECT, name: 'Subscription', response })).to.be.ok
489 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldString', response })).to.not.be.ok
490 |
491 | // Remove an Input Field from an Input Object
492 |
493 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg' })).to.be.ok
494 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg' })).to.eql(
495 | findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg', response })
496 | )
497 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'secretEnum' })).to.not.be.ok
498 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'secretEnum', response })).to.not.be.ok
499 |
500 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'string' })).to.be.ok
501 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'string', response })).to.be.ok
502 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'otherString' })).to.be.ok
503 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'otherString', response })).to.be.ok
504 |
505 | // This won't work due to typeKind defaulting to OBJECT
506 | microfiber.removeField({ typeName: 'InputWithSecretEnum', fieldName: 'string' })
507 | response = microfiber.getResponse()
508 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'string' })).to.be.ok
509 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'string', response })).to.be.ok
510 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'otherString' })).to.be.ok
511 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'otherString', response })).to.be.ok
512 |
513 | // But this will work...
514 | microfiber.removeField({ typeKind: KINDS.INPUT_OBJECT, typeName: 'InputWithSecretEnum', fieldName: 'string' })
515 | response = microfiber.getResponse()
516 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'string' })).to.not.be.ok
517 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'string', response })).to.not.be.ok
518 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'otherString' })).to.be.ok
519 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'otherString', response })).to.be.ok
520 |
521 | microfiber.removeInputField({ typeName: 'InputWithSecretEnum', fieldName: 'otherString' })
522 | response = microfiber.getResponse()
523 | expect(microfiber.getInputField({ typeName: 'InputWithSecretEnum', fieldName: 'otherString' })).to.not.be.ok
524 | expect(findInputFieldOnInputType({ typeName: 'InputWithSecretEnum', fieldName: 'otherString', response })).to.not.be.ok
525 | // That's the last of the inputFields...so it should be empty and not null
526 | expect(microfiber.getType({ kind: KINDS.INPUT_OBJECT, name: 'InputWithSecretEnum' })).to.be.ok
527 | expect(microfiber.getType({ kind: KINDS.INPUT_OBJECT, name: 'InputWithSecretEnum' }).inputFields).to.eql([])
528 |
529 |
530 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg' })).to.be.ok
531 | expect(microfiber.getField({ typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg' })).to.eql(
532 | findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldWithSecretEnumInputArg', response })
533 | )
534 |
535 | // Remove in Input Type completely
536 |
537 | // Remove possible type from a Union Type
538 |
539 | let unionType = findType({ kind: KINDS.UNION, name: 'SecretUnion', response })
540 | expect(unionType).to.be.ok
541 | expect(unionType.possibleTypes).to.be.an('array').of.length(2)
542 |
543 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnion', response })).to.be.ok
544 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionArray', response })).to.be.ok
545 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionNonNullArray', response })).to.be.ok
546 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionNonNullArrayOfNonNulls', response })).to.be.ok
547 |
548 | microfiber.removePossibleType({ typeName: 'SecretUnion', possibleTypeKind: KINDS.OBJECT, possibleTypeName: 'MyType' })
549 | response = microfiber.getResponse()
550 | expect(findType({ kind: KINDS.OBJECT, name: 'Query', response })).to.be.ok
551 | expect(findType({ kind: KINDS.OBJECT, name: 'Mutation', response })).to.be.ok
552 |
553 | unionType = microfiber.getType({ kind: KINDS.UNION, name: 'SecretUnion' })
554 | expect(unionType).to.be.ok
555 | expect(unionType).to.eql(
556 | findType({ kind: KINDS.UNION, name: 'SecretUnion', response })
557 | )
558 | expect(unionType.possibleTypes).to.be.an('array').of.length(1)
559 | // Only MyOtherType is left
560 | expect(unionType.possibleTypes.map((possibleType) => possibleType.name)).to.eql(['MyOtherType'])
561 |
562 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnion', response })).to.be.ok
563 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionArray', response })).to.be.ok
564 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionNonNullArray', response })).to.be.ok
565 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionNonNullArrayOfNonNulls', response })).to.be.ok
566 |
567 | // Remove a Union Type completely
568 |
569 | microfiber.removeType({ kind: KINDS.UNION, name: 'SecretUnion' })
570 | response = microfiber.getResponse()
571 | expect(findType({ kind: KINDS.OBJECT, name: 'Query', response })).to.be.ok
572 | expect(findType({ kind: KINDS.OBJECT, name: 'Mutation', response })).to.be.ok
573 |
574 | expect(findType({ kind: KINDS.UNION, name: 'SecretUnion', response})).to.not.be.ok
575 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnion', response })).to.not.be.ok
576 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionArray', response })).to.not.be.ok
577 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionNonNullArray', response })).to.not.be.ok
578 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'MyType', fieldName: 'fieldSecretUnionNonNullArrayOfNonNulls', response })).to.not.be.ok
579 |
580 | // Remove the myTypes Query...which should remove MyType as well now that there are no references to it
581 |
582 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'Query', fieldName: 'myTypes', response })).to.be.ok
583 | expect(findType({ kind: KINDS.OBJECT, name: 'MyType', response })).to.be.ok
584 |
585 | expect(microfiber.getType({ kind: KINDS.INTERFACE, name: 'MyInterface' })).to.be.ok
586 | myInterfaceId = microfiber.getField({ typeKind: KINDS.INTERFACE, typeName: 'MyInterface', fieldName: 'id' })
587 | expect(myInterfaceId).to.be.ok
588 | expect(microfiber.getInterfaceField({ typeName: 'MyInterface', fieldName: 'id' })).to.eql(myInterfaceId)
589 |
590 | microfiber.removeQuery({ name: 'myTypes' })
591 | response = microfiber.getResponse()
592 |
593 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'Query', fieldName: 'myTypes', response })).to.not.be.ok
594 | expect(findType({ kind: KINDS.OBJECT, name: 'MyType', response })).to.not.be.ok
595 | expect(microfiber.getType({ kind: KINDS.INTERFACE, name: 'MyInterface' })).to.not.be.ok
596 |
597 | // Remove YetAnotherType...which should remove the createYetAnotherType Mutation as well now that there are no
598 | // references to it
599 |
600 | expect(findType({ kind: KINDS.OBJECT, name: 'YetAnotherType', response })).to.be.ok
601 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'Mutation', fieldName: 'createYetAnotherType', response })).to.be.ok
602 |
603 | microfiber.removeType({ kind: KINDS.OBJECT, name: 'YetAnotherType' })
604 | response = microfiber.getResponse()
605 |
606 | expect(findType({ kind: KINDS.OBJECT, name: 'YetAnotherType', response })).to.not.be.ok
607 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'Mutation', fieldName: 'createYetAnotherType', response })).to.not.be.ok
608 |
609 | //********************************
610 | // Remove the "subscribeToMyTypeFieldStringChanges" Subscription, which should remove the "RandomTypeOne" now that nothing
611 | // references it
612 |
613 | // Make sure they're there to start with
614 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'Subscription', fieldName: 'subscribeToMyTypeFieldStringChanges', response })).to.be.ok
615 | expect(findType({ kind: KINDS.OBJECT, name: 'RandomTypeOne', response })).to.be.ok
616 |
617 | // Remove them, and make sure they're not there
618 | microfiber.removeSubscription({ name: 'subscribeToMyTypeFieldStringChanges' })
619 | response = microfiber.getResponse()
620 | expect(findFieldOnType({ typeKind: KINDS.OBJECT, typeName: 'Subscription', fieldName: 'subscribeToMyTypeFieldStringChanges', response })).to.not.be.ok
621 | expect(findType({ kind: KINDS.OBJECT, name: 'RandomTypeOne', response })).to.not.be.ok
622 |
623 | //
624 | //
625 | //********************************
626 |
627 |
628 | //*******************************
629 | // Remove the DirectiveOption Type, which should remove it from the directive Args
630 | //
631 |
632 | microfiber.removeType({ kind: KINDS.INPUT_OBJECT, name: 'DirectiveOption' })
633 | expect(microfiber.getDirective({ name: 'foo' })).to.be.ok
634 | expect(microfiber.getDirectiveArg({ directiveName: 'foo', argName: 'option' })).to.not.be.ok
635 | expect(microfiber.getType({ kind: KINDS.INPUT_OBJECT, name: 'DirectiveOption' })).to.not.be.ok
636 | expect(microfiber.getDirectiveArg({ directiveName: 'foo', argName: 'anotherOption' })).to.be.ok
637 | expect(microfiber.getType({ kind: KINDS.INPUT_OBJECT, name: 'AnotherDirectiveOption' })).to.be.ok
638 |
639 |
640 | // And now remove the "foo" directive, and the AnotherDirectiveOption should be cleaned up and gone
641 | microfiber.removeDirective({ name: 'foo' })
642 | expect(microfiber.getDirective({ name: 'foo' })).to.not.be.ok
643 | expect(microfiber.getDirectiveArg({ directiveName: 'foo', argName: 'option' })).to.not.be.ok
644 | expect(microfiber.getType({ kind: KINDS.INPUT_OBJECT, name: 'DirectiveOption' })).to.not.be.ok
645 | expect(microfiber.getDirectiveArg({ directiveName: 'foo', argName: 'anotherOption' })).to.not.be.ok
646 | expect(microfiber.getType({ kind: KINDS.INPUT_OBJECT, name: 'AnotherDirectiveOption' })).to.not.be.ok
647 |
648 |
649 | //
650 | //
651 | //*******************************
652 |
653 | //*******************************
654 | //
655 | // Stuff regarding INTERFACE usages
656 | //
657 |
658 | expect(microfiber.getType({ kind: KINDS.INTERFACE, name: 'MyOtherInterface' })).to.be.ok
659 | expect(microfiber.getType({ name: 'MyOtherInterfaceType' })).to.be.ok
660 | expect(microfiber.getType({ name: 'MyOtherOtherInterfaceType' })).to.be.ok
661 |
662 | // Remove the query that uses MyOtherOtherInterfaceType
663 | microfiber.removeQuery({ name: 'myOtherOtherInterfaceType' })
664 |
665 | // Both are still here because myMyOtherInterfaceTypes query returns the Interface
666 | expect(microfiber.getType({ kind: KINDS.INTERFACE, name: 'MyOtherInterface' })).to.be.ok
667 | expect(microfiber.getType({ name: 'MyOtherInterfaceType' })).to.be.ok
668 | expect(microfiber.getType({ name: 'MyOtherOtherInterfaceType' })).to.be.ok
669 |
670 | // Remove the query that uses MyOtherInterface
671 | microfiber.removeQuery({ name: 'myMyOtherInterfaceTypes' })
672 |
673 | // Everything is gone now.
674 | expect(microfiber.getType({ kind: KINDS.INTERFACE, name: 'MyOtherInterface' })).to.not.be.ok
675 | expect(microfiber.getType({ name: 'MyOtherInterfaceType' })).to.not.be.ok
676 | expect(microfiber.getType({ name: 'MyOtherOtherInterfaceType' })).to.not.be.ok
677 |
678 | //
679 | //
680 | //*******************************
681 |
682 |
683 | //*******************************
684 | //
685 | // Stuff regarding UNION usages
686 | //
687 |
688 | expect(microfiber.getType({ kind: KINDS.UNION, name: 'MyUnion' })).to.be.ok
689 | expect(microfiber.getType({ name: 'MyUnionType' })).to.be.ok
690 | expect(microfiber.getType({ name: 'MyOtherUnionType' })).to.be.ok
691 |
692 | // Remove the query that uses MyOtherUnionType
693 | microfiber.removeQuery({ name: 'myOtherUnionType' })
694 |
695 | expect(microfiber.getType({ kind: KINDS.UNION, name: 'MyUnion' })).to.be.ok
696 | expect(microfiber.getType({ name: 'MyUnionType' })).to.be.ok
697 | expect(microfiber.getType({ name: 'MyOtherUnionType' })).to.be.ok
698 |
699 | // Remove the query that uses MyUnion
700 | microfiber.removeQuery({ name: 'myUnions' })
701 |
702 | expect(microfiber.getType({ kind: KINDS.UNION, name: 'MyUnion' })).to.not.be.ok
703 | expect(microfiber.getType({ name: 'MyUnionType' })).to.not.be.ok
704 | expect(microfiber.getType({ name: 'MyOtherUnionType' })).to.not.be.ok
705 |
706 |
707 | //
708 | //
709 | //*******************************
710 |
711 | // Make sure that removing the Mutation type does some things
712 | expect(findType({ kind: mutationType.kind, name: mutationType.name, response })).to.be.ok
713 | expect(response.__schema.mutationType).to.be.ok
714 | microfiber.removeType({ kind: mutationType.kind, name: mutationType.name })
715 | response = microfiber.getResponse()
716 | expect(findType({ kind: mutationType.kind, name: mutationType.name, response })).to.not.be.ok
717 | expect(response.__schema.mutationType).to.not.be.ok
718 | })
719 | })
720 |
721 |
722 | function findType({ kind, name, response }) {
723 | return (response.__schema.types || []).find((type) => type.kind === kind && type.name === name)
724 | }
725 |
726 | function findFieldOnType({ typeKind, typeName, fieldName, response }) {
727 | const type = findType({ kind: typeKind, name: typeName, response })
728 | if (!type) {
729 | return false
730 | }
731 | return (type.fields || []).find((field) => field.name === fieldName)
732 | }
733 |
734 | function findArgOnFieldOnType({ typeKind, typeName, fieldName, argName, response }) {
735 | const field = findFieldOnType({ typeKind, typeName, fieldName, response })
736 | if (!field) {
737 | return false
738 | }
739 | return (field.args || []).find((arg) => arg.name === argName)
740 | }
741 |
742 | function findInputFieldOnInputType({ typeName, fieldName, response }) {
743 | const type = findType({ kind: KINDS.INPUT_OBJECT, name: typeName, response })
744 | if (!type) {
745 | return false
746 | }
747 |
748 | return (type.inputFields || []).find((inputField => inputField.name === fieldName ))
749 | }
750 |
--------------------------------------------------------------------------------