├── .all-contributorsrc
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .huskyrc.js
├── .prettierignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── babel.config.js
├── docs
└── preview.png
├── logo.svg
├── package-lock.json
├── package.json
└── src
├── astToBody.js
├── constants.js
├── index.js
├── index.test.js
├── test-utils.js
└── utils.js
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "graphql-args",
3 | "projectOwner": "smeijer",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "angular",
12 | "contributors": [
13 | {
14 | "login": "smeijer",
15 | "name": "Stephan Meijer",
16 | "avatar_url": "https://avatars1.githubusercontent.com/u/1196524?v=4",
17 | "profile": "https://github.com/smeijer",
18 | "contributions": [
19 | "ideas",
20 | "code",
21 | "infra",
22 | "maintenance"
23 | ]
24 | },
25 | ],
26 | "contributorsPerLine": 7,
27 | "skipCi": true
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 |
3 | on:
4 | push:
5 | branches:
6 | # Match SemVer major release branches, e.g. "2.x" or ".x"
7 | - '[0-9]+.x'
8 | - 'main'
9 | - 'next'
10 | - 'alpha'
11 | - 'beta'
12 | - '!all-contributors/**'
13 | pull_request:
14 | types: [opened, synchronize, reopened]
15 |
16 | jobs:
17 | setup:
18 | name: Setup
19 | runs-on: ubuntu-latest
20 | timeout-minutes: 5
21 | steps:
22 | - uses: actions/checkout@v4
23 | - uses: bahmutov/npm-install@v1
24 |
25 | test:
26 | name: Test
27 | runs-on: ubuntu-latest
28 | needs: [setup]
29 | timeout-minutes: 5
30 | steps:
31 | - uses: actions/checkout@v4
32 | - uses: bahmutov/npm-install@v1
33 | - name: Test
34 | run: npm run test
35 |
36 | release:
37 | needs: [test]
38 | if: github.event_name == 'push' && github.repository == 'smeijer/graphql-args'
39 | runs-on: ubuntu-latest
40 | environment: npm
41 | steps:
42 | - name: Cancel previous runs
43 | uses: styfle/cancel-workflow-action@0.9.0
44 |
45 | - uses: actions/checkout@v4
46 | - uses: bahmutov/npm-install@v1
47 |
48 | - name: Build
49 | run: npm run build
50 |
51 | - name: Release
52 | uses: cycjimmy/semantic-release-action@v2
53 | with:
54 | semantic_version: 17
55 | branches: |
56 | [
57 | '+([0-9])?(.{+([0-9]),x}).x',
58 | 'main',
59 | 'next',
60 | { name: 'alpha', prerelease: true },
61 | { name: 'beta', prerelease: true }
62 | ]
63 | env:
64 | GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
65 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/.huskyrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | hooks: {
3 | 'pre-commit': 'pretty-quick --staged',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: 'all',
4 | singleQuote: true,
5 | printWidth: 80,
6 | tabWidth: 2,
7 | };
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Stephan Meijer
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # graphql-args
2 |
3 |
4 |
5 | [](#contributors-)
6 |
7 |
8 |
9 | **extract query arguments from the graphql ast**
10 |
11 | 
12 |
13 | `graphql-args` provides a way to extract query fields and arguments from the 4th resolver argument.
14 |
15 | ## Installation
16 |
17 | With npm:
18 |
19 | ```sh
20 | npm install --save-dev graphql-args
21 | ```
22 |
23 | With yarn:
24 |
25 | ```sh
26 | yarn add -D graphql-args
27 | ```
28 |
29 | ## Intro
30 |
31 | The examples below use the following query:
32 |
33 | ```gql
34 | query($where: CommentFilterInput!) {
35 | blog {
36 | id
37 | title
38 |
39 | author(id: "author_1") {
40 | name
41 | }
42 |
43 | comment(where: { id: "comment_1" }) {
44 | id
45 | }
46 |
47 | comments(where: $where) {
48 | id
49 | message
50 | author
51 |
52 | likes(where: { type: "heart" }) {
53 | actor
54 | }
55 | }
56 | }
57 | }
58 | ```
59 |
60 | with the following variables:
61 |
62 | ```js
63 | {
64 | where: {
65 | id: 'comment_2',
66 | },
67 | }
68 | ```
69 |
70 | `ast` is the fourth argument of graphql resolvers:
71 |
72 | ```js
73 | import { getArgs, getFields, parse } from 'graphql-args';
74 |
75 | const resolvers = {
76 | blog(parent, args, context, ast) {
77 | /**
78 | * This is the place where the examples fit in. The
79 | * wrapping resolver code is left out from the examples
80 | * for brevity.
81 | **/
82 | },
83 | };
84 | ```
85 |
86 | ## API
87 |
88 | ### getArgs
89 |
90 | `getArgs` reads the `ast` and returns the query arguments at a given path, as plain object.
91 |
92 | ```js
93 | import { getArgs } from 'graphql-args';
94 | ```
95 |
96 | #### getArgs(ast, 'path'): object
97 |
98 | ```js
99 | getArgs(ast, 'comments');
100 | » { where: { id: 'comment_2' } }
101 | ```
102 |
103 | #### getArgs(ast, 'nested.path'): object
104 |
105 | ```js
106 | getArgs(ast, 'comments.likes');
107 | » { where: { type: 'heart' } }
108 | ```
109 |
110 | #### getArgs(ast): get('path'): object
111 |
112 | In the scenario where you need to get arguments from multiple paths, and don't want to parse the `ast` more than once, the curry behavior of `getArgs` can be used.
113 |
114 | ```js
115 | const get = getArgs(ast);
116 |
117 | get('comments');
118 | » { where: { id: 'comment_2' } }
119 |
120 | get('comments.likes');
121 | » { where: { type: 'heart' } }
122 | ```
123 |
124 | Alternatively, `parse` can be used to achieve the same result:
125 |
126 | ```js
127 | const { getArgs } = parse(ast);
128 |
129 | getArgs('comments');
130 | » { where: { id: 'comment_2' } }
131 |
132 | getArgs('comments.likes');
133 | » { where: { type: 'heart' } }
134 | ```
135 |
136 | ### getFields
137 |
138 | `getFields` reads the `ast` and returns the query document at a given path, as plain object with the property values set to `true`. The depth of the object can be controlled with the `{ depth: number }` option.
139 |
140 | ```js
141 | import { getFields } from 'graphql-args';
142 | ```
143 |
144 | #### getFields(ast, 'path'): object
145 |
146 | By default, a flat object is being returned, only containing the first level properties
147 |
148 | ```js
149 | getFields(ast, 'comments');
150 | » { id: true, author: true, likes: true, message: true }
151 | ```
152 |
153 | #### getFields(ast, 'path', { depth: number }): object
154 |
155 | By specifying a depth, we control the nesting of the fields. Set `depth` to `0` or `-1` for unlimited depth. Depth defaults to `1` level.
156 |
157 | ```js
158 | getFields(ast, 'comments', { depth: 2 });
159 | » { id: true, author: true, likes: { actor: true }, message: true }
160 | ```
161 |
162 | #### getFields(ast, 'nested.path'): object
163 |
164 | ```js
165 | getFields(ast, 'comments.likes');
166 | » { actor: true }
167 | ```
168 |
169 | #### getFields(ast): get('path'): object
170 |
171 | In the scenario where you need to get fields from multiple paths, and don't want to parse the ``ast` more than once, the curry behavior of `getFields` can be used.
172 |
173 | ```js
174 | const get = getFields(ast);
175 |
176 | get('comments');
177 | » { id: true, author: true, likes: true, message: true }
178 |
179 | get('comments.likes');
180 | » { actor: true }
181 | ```
182 |
183 | Alternatively, `parse` can be used to achieve the same result:
184 |
185 | ```js
186 | const { getFields } = parse(ast);
187 |
188 | getFields('comments');
189 | » { id: true, author: true, likes: true, message: true }
190 |
191 | getFields('comments.likes');
192 | » { actor: true }
193 | ```
194 |
195 | ### parse(ast) => { getArgs, getFields }
196 |
197 | In case you'd need to query multiple paths, `parse` can be used as optimization. By using `parse`, the `ast` is only processed once. This is a small optimization, and the performance gain might be negligible. Use it when you need every last bit of performance juice, or when you just like the style.
198 |
199 | Using `parse` doesn't hurt, but in most cases, using the direct methods is what you're looking for. Simply because it's less verbose.
200 |
201 | ```js
202 | import { parse } from 'graphql-args';
203 |
204 | const { getArgs, getFields } = parse(ast);
205 |
206 | getArgs('comments');
207 | » { where: { id: 'comment_2' } }
208 |
209 | getFields('comments', { depth: 2 });
210 | » { id: true, author: true, likes: { actor: true }, message: true }
211 | ```
212 |
213 | ## Prior Art
214 |
215 | Part of this library was created when I encountered a few shortcomings in [cult-of-coders/grapher](https://github.com/cult-of-coders/grapher). While waiting for my [PR](https://github.com/cult-of-coders/grapher/pull/435) to get merged, I decided to extract some code, and adjust it to my needs.
216 |
217 | ## Contributors ✨
218 |
219 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
220 |
221 |
222 |
223 |
224 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
236 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | // babel.config.js
2 | module.exports = (api) => {
3 | if (!api.env('test')) {
4 | return {};
5 | }
6 |
7 | return {
8 | presets: [
9 | [
10 | '@babel/preset-env',
11 | {
12 | targets: {
13 | node: 'current',
14 | },
15 | },
16 | ],
17 | ],
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/docs/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smeijer/graphql-args/75825573c9e61e72e9491c52657f05375ccfc6b9/docs/preview.png
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graphql-args",
3 | "version": "1.4.0",
4 | "description": "A lib that parses the resolver ast, to return the requested object fields and provided params, at any nested level.",
5 | "keywords": [
6 | "nodejs",
7 | "graphql",
8 | "mongodb"
9 | ],
10 | "source": "src/index.js",
11 | "main": "dist/index.js",
12 | "license": "MIT",
13 | "author": "Stephan Meijer ",
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/smeijer/graphql-args.git"
17 | },
18 | "scripts": {
19 | "test": "jest",
20 | "build": "rimraf ./dist && microbundle -i src/index.js -o dist/index.js --no-pkg-main -f umd --target node",
21 | "watch": "rimraf ./dist && microbundle -i src/index.js -o dist/index.js --no-pkg-main -f umd --sourcemap true --compress false --target node --watch --raw",
22 | "prettier": "prettier . --write",
23 | "prepare": "npm run build"
24 | },
25 | "files": [
26 | "docs",
27 | "dist",
28 | "types"
29 | ],
30 | "devDependencies": {
31 | "graphql": "^15.3.0",
32 | "husky": "^4.3.0",
33 | "jest": "^26.4.2",
34 | "microbundle": "^0.12.4",
35 | "prettier": "^2.1.2",
36 | "pretty-quick": "^3.0.2",
37 | "rimraf": "^3.0.2"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/astToBody.js:
--------------------------------------------------------------------------------
1 | import { Kind } from 'graphql';
2 | import { Symbols } from './constants';
3 |
4 | export function astToBody(ast) {
5 | const body = extractSelectionSet(
6 | { selections: ast.fieldNodes },
7 | ast.variableValues,
8 | ast.fragments,
9 | );
10 |
11 | return Object.values(body)[0];
12 | }
13 |
14 | function extractArgumentValue(value, variables) {
15 | if (!value) return null;
16 | const kind = value.kind;
17 | switch (kind) {
18 | case Kind.VARIABLE:
19 | return variables[value.name.value];
20 | case Kind.INT:
21 | return parseInt(value.value);
22 | case Kind.FLOAT:
23 | return parseFloat(value.value);
24 | case Kind.BOOLEAN:
25 | return Boolean(value.value);
26 | case Kind.NULL:
27 | return null;
28 | case Kind.STRING:
29 | case Kind.ENUM:
30 | return value.value;
31 | case Kind.LIST:
32 | return value.values.map((v) => extractArgumentValue(v, variables));
33 | case Kind.OBJECT:
34 | return Object.keys(value.fields).reduce((acc, key) => {
35 | acc[value.fields[key].name.value] = extractArgumentValue(
36 | value.fields[key].value,
37 | variables,
38 | );
39 | return acc;
40 | }, {});
41 | default:
42 | return value.value;
43 | }
44 | }
45 |
46 | function extractArguments(args, variables) {
47 | if (!Array.isArray(args) || args.length === 0) {
48 | return {};
49 | }
50 |
51 | // recursively walk down the ast, to collect all arguments. The recursive
52 | // behavior covers input types (arguments of type object/array etc. supports all GraphQL type).
53 | // think of queries like `comments({ where: { blog: { id: 123 } } })`
54 | return args.reduce((acc, field) => {
55 | acc[field.name.value] = extractArgumentValue(field.value, variables);
56 | return acc;
57 | }, {});
58 | }
59 |
60 | function extractSelectionSet(set, variables, fragments) {
61 | let body = {};
62 |
63 | set.selections.forEach((el) => {
64 | if (el.kind === 'FragmentSpread') {
65 | if (fragments === null) {
66 | return;
67 | }
68 | const fragment = fragments[el.name.value];
69 | const selectionSet = extractSelectionSet(
70 | fragment.selectionSet,
71 | variables,
72 | fragments,
73 | );
74 |
75 | Object.assign(body, selectionSet);
76 | } else if (!el.selectionSet) {
77 | body[el.name.value] = true;
78 | } else {
79 | body[el.name.value] = extractSelectionSet(
80 | el.selectionSet,
81 | variables,
82 | fragments,
83 | );
84 | body[el.name.value][Symbols.ARGUMENTS] = extractArguments(
85 | el.arguments,
86 | variables,
87 | );
88 | }
89 | });
90 |
91 | return body;
92 | }
93 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const Symbols = {
2 | ARGUMENTS: Symbol('arguments'),
3 | };
4 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import { astToBody } from './astToBody';
2 | import { Symbols } from './constants';
3 | import { get, clean } from './utils';
4 | export { getAst } from './test-utils';
5 |
6 | export function parse(ast) {
7 | const body = astToBody(ast);
8 |
9 | return {
10 | getArgs: (path) => {
11 | const node = get(path, body);
12 |
13 | if (!node) {
14 | return {};
15 | }
16 |
17 | return node[Symbols.ARGUMENTS] || {};
18 | },
19 |
20 | getFields: (path, options = {}) => {
21 | const node = get(path, body) || {};
22 | return clean(node, options.depth);
23 | },
24 | };
25 | }
26 |
27 | export function getArgs(ast, path) {
28 | if (typeof path === 'undefined') {
29 | return parse(ast).getArgs;
30 | }
31 |
32 | return parse(ast).getArgs(path);
33 | }
34 |
35 | export function getFields(ast, path, options) {
36 | if (typeof path === 'undefined') {
37 | return parse(ast).getFields;
38 | }
39 |
40 | return parse(ast).getFields(path, options);
41 | }
42 |
--------------------------------------------------------------------------------
/src/index.test.js:
--------------------------------------------------------------------------------
1 | import { parse, getFields, getArgs, getAst } from './index';
2 | import { ast, example } from './test-utils';
3 |
4 | test('getArgs can retrieve the root arguments', () => {
5 | const args = getArgs(ast, '');
6 |
7 | expect(args).toEqual({
8 | id: 'blog_1',
9 | });
10 | });
11 |
12 | test('getArgs can retrieve the nested arguments', () => {
13 | const args = getArgs(ast, 'author');
14 |
15 | expect(args).toEqual({
16 | id: 'author_1',
17 | });
18 | });
19 |
20 | test('getArgs can retrieve the nested input type arguments', () => {
21 | const args = getArgs(ast, 'comment');
22 |
23 | expect(args).toEqual({
24 | where: { id: 'comment_1' },
25 | });
26 | });
27 |
28 | test('getArgs can retrieve the nested input type arguments using variables', () => {
29 | const args = getArgs(ast, 'comments');
30 |
31 | expect(args).toEqual({
32 | where: { id: 'comment_2' },
33 | });
34 | });
35 |
36 | test('getArgs can retrieve the deeply nested arguments', () => {
37 | const args = getArgs(ast, 'comments.likes');
38 |
39 | expect(args).toEqual({
40 | where: { type: 'heart' },
41 | });
42 | });
43 |
44 | test('getArgs can use `parse` as optimization', () => {
45 | const { getArgs } = parse(ast);
46 | const args = getArgs('comment');
47 |
48 | expect(args).toEqual({
49 | where: { id: 'comment_1' },
50 | });
51 | });
52 |
53 | test('getArgs returns a creator instance when called with single argument', () => {
54 | const get = getArgs(ast);
55 | const args = get('comment');
56 |
57 | expect(args).toEqual({
58 | where: { id: 'comment_1' },
59 | });
60 | });
61 |
62 | test('getArgs can retrieve the mixed arguments', () => {
63 | const args = getArgs(ast, 'mixed');
64 |
65 | expect(args).toEqual({
66 | where: {
67 | e: 'App',
68 | w: { id: 'comment_2' },
69 | id: 'mixed_id',
70 | args: [1, true, 'true', null, { a: 'b' }],
71 | x: { a: { b: 'c' } },
72 | array: [1, 2, 3, 4, 3.14],
73 | },
74 | });
75 | });
76 |
77 | test('getFields can return the root level', () => {
78 | const fields = getFields(ast, '');
79 |
80 | expect(fields).toMatchObject({
81 | author: true,
82 | comment: true,
83 | comments: true,
84 | id: true,
85 | title: true,
86 | });
87 | });
88 |
89 | test('getFields can limit the depth', () => {
90 | const fields = getFields(ast, '', { depth: 2 });
91 |
92 | expect(fields).toMatchObject({
93 | author: { name: true },
94 | comment: { id: true },
95 | comments: {
96 | author: true,
97 | id: true,
98 | // likes is flattened, original has { actor: true }
99 | likes: true,
100 | message: true,
101 | },
102 | id: true,
103 | title: true,
104 | });
105 | });
106 |
107 | test('getFields can return the entire object', () => {
108 | const fields = getFields(ast, '', { depth: -1 });
109 |
110 | expect(fields).toMatchObject({
111 | author: { name: true },
112 | comment: { id: true },
113 | comments: {
114 | author: true,
115 | id: true,
116 | likes: { actor: { email: true, name: true } },
117 | message: true,
118 | },
119 | id: true,
120 | title: true,
121 | });
122 | });
123 |
124 | test('getFields can retrieve nested objects', () => {
125 | const fields = getFields(ast, 'author');
126 |
127 | expect(fields).toMatchObject({
128 | name: true,
129 | });
130 | });
131 |
132 | test('getFields can retrieve the deeply nested objects', () => {
133 | const fields = getFields(ast, 'comments.likes');
134 |
135 | expect(fields).toEqual({
136 | actor: true,
137 | });
138 | });
139 |
140 | test('getFields can use `parse` as optimization', () => {
141 | const { getFields } = parse(ast);
142 | const fields = getFields('comments');
143 |
144 | expect(fields).toEqual({
145 | id: true,
146 | author: true,
147 | likes: true,
148 | message: true,
149 | });
150 | });
151 |
152 | test('getFields creator instance correctly handles nested objects with depth', () => {
153 | const { getFields } = parse(ast);
154 | const fields = getFields('comments', { depth: 2 });
155 |
156 | expect(fields).toEqual({
157 | id: true,
158 | author: true,
159 | likes: { actor: true },
160 | message: true,
161 | });
162 | });
163 |
164 | describe('AppSync environment', () => {
165 | test('It should create an ast without fragments', () => {
166 | expect(getAst(example, true).fragments).toBe(null);
167 | });
168 | test('It should should ignore fragments', () => {
169 | const { getFields } = parse(getAst(example, true));
170 | const fields = getFields('comments.likes', { depth: -1 });
171 | expect(fields).toEqual({
172 | actor: {},
173 | });
174 | });
175 | });
176 |
--------------------------------------------------------------------------------
/src/test-utils.js:
--------------------------------------------------------------------------------
1 | import { parse } from 'graphql';
2 |
3 | export function getAst({ query, variables }, isAppSyncSelectionSet = false) {
4 | const ast = parse(query);
5 |
6 | const operation = ast.definitions.find(
7 | (x) => x.kind === 'OperationDefinition',
8 | );
9 |
10 | const fragments = ast.definitions
11 | .filter((x) => x.kind === 'FragmentDefinition')
12 | .reduce((acc, fragment) => {
13 | acc[fragment.name.value] = fragment;
14 | return acc;
15 | }, {});
16 |
17 | return {
18 | fieldNodes: operation.selectionSet.selections,
19 | variableValues: variables,
20 | fragments: !isAppSyncSelectionSet ? fragments : null,
21 | };
22 | }
23 |
24 | export const example = {
25 | query: /* GraphQL */ `
26 | query($where: CommentFilterInput!) {
27 | blog(id: "blog_1") {
28 | id
29 | title
30 |
31 | author(id: "author_1") {
32 | name
33 | }
34 |
35 | comment(where: { id: "comment_1" }) {
36 | id
37 | }
38 |
39 | comments(where: $where) {
40 | id
41 | message
42 | author
43 |
44 | likes(where: { type: "heart" }) {
45 | actor {
46 | ...Person
47 | }
48 | }
49 | }
50 | mixed(
51 | where: {
52 | e: App
53 | w: $where
54 | id: "mixed_id"
55 | args: [1, true, "true", null, { a: "b" }]
56 | x: { a: { b: "c" } }
57 | array: [1, 2, 3, 4, 3.14]
58 | }
59 | ) {
60 | id
61 | name
62 | }
63 | }
64 | }
65 |
66 | fragment Person on Actor {
67 | name
68 | email
69 | }
70 | `,
71 | variables: {
72 | where: {
73 | id: 'comment_2',
74 | },
75 | },
76 | };
77 |
78 | export const ast = getAst(example);
79 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export function get(path, object) {
2 | if (!path) {
3 | return object;
4 | }
5 |
6 | const parts = Array.isArray(path) ? path : path.split('.');
7 |
8 | while (object && parts.length > 0) {
9 | object = object[parts.shift()];
10 | }
11 |
12 | if (!object) {
13 | return undefined;
14 | }
15 |
16 | return object;
17 | }
18 |
19 | export function clean(obj, maxDepth = 1, depth = 1) {
20 | if (Array.isArray(obj)) {
21 | return obj.map((item) => clean(item, maxDepth, depth + 1));
22 | }
23 |
24 | if (typeof obj === 'object') {
25 | // symbols are excluded from Object.keys({})
26 | return Object.keys(obj).reduce((acc, key) => {
27 | if (key === '__typename') {
28 | return acc;
29 | }
30 |
31 | acc[key] =
32 | depth < maxDepth || maxDepth < 1
33 | ? clean(obj[key], maxDepth, depth + 1)
34 | : true;
35 |
36 | return acc;
37 | }, {});
38 | }
39 |
40 | return obj;
41 | }
42 |
--------------------------------------------------------------------------------