graphql-buddy 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.github/workflows/ci.yml +32 -0
  2. package/.nvmrc +1 -0
  3. package/.prettierignore +1 -0
  4. package/.prettierrc +7 -0
  5. package/LICENSE +21 -0
  6. package/README.md +89 -0
  7. package/dist/api.d.ts +46 -0
  8. package/dist/api.js +13 -0
  9. package/dist/api.js.map +1 -0
  10. package/dist/chunk-Z7B4AQ2U.js +124 -0
  11. package/dist/chunk-Z7B4AQ2U.js.map +1 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +122 -0
  14. package/dist/cli.js.map +1 -0
  15. package/eslint.config.ts +168 -0
  16. package/package.json +69 -0
  17. package/pnpm-workspace.yaml +3 -0
  18. package/src/api/commands/bundle.test.ts +138 -0
  19. package/src/api/commands/bundle.ts +29 -0
  20. package/src/api/commands/index.ts +2 -0
  21. package/src/api/commands/validate.test.ts +35 -0
  22. package/src/api/commands/validate.ts +49 -0
  23. package/src/api/index.ts +2 -0
  24. package/src/api/shared/index.ts +1 -0
  25. package/src/api/shared/loadSchema.ts +88 -0
  26. package/src/cli/commands/bundle.ts +62 -0
  27. package/src/cli/commands/index.ts +2 -0
  28. package/src/cli/commands/validate.ts +59 -0
  29. package/src/cli/index.ts +52 -0
  30. package/src/cli/shared/index.ts +1 -0
  31. package/src/cli/shared/parsePathAliases.test.ts +43 -0
  32. package/src/cli/shared/parsePathAliases.ts +25 -0
  33. package/test_fixtures/missing_scalar/schema.graphql +16 -0
  34. package/test_fixtures/multiple_files/Bar.graphql +5 -0
  35. package/test_fixtures/multiple_files/Baz.graphql +5 -0
  36. package/test_fixtures/multiple_files/Foo.graphql +9 -0
  37. package/test_fixtures/multiple_files/Mutation.graphql +9 -0
  38. package/test_fixtures/multiple_files/Query.graphql +5 -0
  39. package/test_fixtures/single_file/schema.graphql +16 -0
  40. package/test_fixtures/single_file_with_extra_deps/schema.graphql +46 -0
  41. package/tsconfig.json +20 -0
  42. package/tsup.config.ts +14 -0
  43. package/vite.config.ts +14 -0
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "graphql-buddy",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "author": "Hunter John Larco",
7
+ "description": "CLI tools for manipulating graphql // designed for build system tooling",
8
+ "bin": {
9
+ "graphql-buddy": "./dist/cli.js"
10
+ },
11
+ "keywords": [
12
+ "graphql",
13
+ "parse",
14
+ "build",
15
+ "build-tool",
16
+ "bundler"
17
+ ],
18
+ "main": "./dist/api.js",
19
+ "types": "./dist/api.d.ts",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/hunterlarco/graphql-buddy.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/hunterlarco/graphql-buddy/issues"
26
+ },
27
+ "funding": "https://github.com/sponsors/hunterlarco",
28
+ "scripts": {
29
+ "build": "pnpm run typecheck && pnpm run bundle",
30
+ "typecheck": "tsc --noEmit",
31
+ "bundle": "tsup",
32
+ "start": "pnpm run build && node dist/cli.js",
33
+ "test": "vitest run",
34
+ "test:dev": "vitest dev",
35
+ "format": "pnpm run format:eslint && pnpm run format:prettier",
36
+ "format:prettier": "prettier . --write",
37
+ "format:eslint": "eslint . --fix",
38
+ "lint": "concurrently npm:lint:*",
39
+ "lint:prettier": "prettier . --check",
40
+ "lint:eslint": "eslint . --max-warnings=0"
41
+ },
42
+ "dependencies": {
43
+ "@commander-js/extra-typings": "14.0.0",
44
+ "@graphql-tools/graphql-file-loader": "8.1.2",
45
+ "@graphql-tools/load": "8.1.2",
46
+ "@graphql-tools/utils": "10.9.1",
47
+ "commander": "14.0.1",
48
+ "glob": "11.0.3",
49
+ "graphql": "16.11.0",
50
+ "stringify-object": "6.0.0",
51
+ "tslib": "2.8.1"
52
+ },
53
+ "devDependencies": {
54
+ "@eslint/compat": "1.4.0",
55
+ "@eslint/js": "9.36.0",
56
+ "@types/node": "24.5.2",
57
+ "@types/stringify-object": "4.0.5",
58
+ "concurrently": "9.2.1",
59
+ "eslint": "9.36.0",
60
+ "eslint-import-resolver-typescript": "4.4.4",
61
+ "eslint-plugin-import": "2.32.0",
62
+ "jiti": "2.6.0",
63
+ "prettier": "3.6.2",
64
+ "tsup": "8.5.0",
65
+ "typescript": "5.9.2",
66
+ "typescript-eslint": "8.44.1",
67
+ "vitest": "3.2.4"
68
+ }
69
+ }
@@ -0,0 +1,3 @@
1
+ onlyBuiltDependencies:
2
+ - esbuild
3
+ - unrs-resolver
@@ -0,0 +1,138 @@
1
+ import * as nodePath from 'node:path';
2
+
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import * as bundle from './bundle';
6
+
7
+ describe(`bundle`, () => {
8
+ it(`handles a single file with no extra deps or aliases.`, async () => {
9
+ expect(
10
+ await bundle.bundle({
11
+ schema: {
12
+ files: [`*.graphql`],
13
+ base: nodePath.resolve(
14
+ __dirname,
15
+ `../../../test_fixtures/single_file`,
16
+ ),
17
+ },
18
+ }),
19
+ ).toStrictEqual(
20
+ `
21
+ type Query {
22
+ foo: Foo
23
+ bar: Bar
24
+ }
25
+
26
+ type Foo {
27
+ id: ID!
28
+ displayName: String
29
+ }
30
+
31
+ type Bar {
32
+ id: ID!
33
+ foo: Foo
34
+ }
35
+ `.trim(),
36
+ );
37
+ });
38
+
39
+ it(`handles multiple files with no extra deps or aliases.`, async () => {
40
+ expect(
41
+ await bundle.bundle({
42
+ schema: {
43
+ files: [`*.graphql`],
44
+ base: nodePath.resolve(
45
+ __dirname,
46
+ `../../../test_fixtures/multiple_files`,
47
+ ),
48
+ },
49
+ }),
50
+ ).toStrictEqual(
51
+ `
52
+ type Bar {
53
+ baz: Baz
54
+ }
55
+
56
+ type Baz {
57
+ dateCreated: DateTime!
58
+ }
59
+
60
+ scalar DateTime
61
+
62
+ type Foo {
63
+ id: ID!
64
+ name: String
65
+ bar: Bar
66
+ }
67
+
68
+ type Mutation {
69
+ addFoo(foo: FooInput!): Foo!
70
+ }
71
+
72
+ input FooInput {
73
+ name: String
74
+ }
75
+
76
+ type Query {
77
+ allFoo: [Foo!]!
78
+ }
79
+ `.trim(),
80
+ );
81
+ });
82
+
83
+ it(`shakes extra deps from a single file.`, async () => {
84
+ expect(
85
+ await bundle.bundle({
86
+ schema: {
87
+ files: [`*.graphql`],
88
+ base: nodePath.resolve(
89
+ __dirname,
90
+ `../../../test_fixtures/single_file_with_extra_deps`,
91
+ ),
92
+ },
93
+ }),
94
+ ).toStrictEqual(
95
+ `
96
+ type Query {
97
+ foo: Foo
98
+ }
99
+
100
+ type Mutation {
101
+ addFooToBar(displayName: String!): Bar
102
+ }
103
+
104
+ type Subscription {
105
+ foo: FooEvent
106
+ }
107
+
108
+ type Foo {
109
+ id: ID!
110
+ displayName: String
111
+ }
112
+
113
+ type Bar {
114
+ id: ID!
115
+ foo: Foo
116
+ }
117
+
118
+ type FooEvent {
119
+ foo: Foo!
120
+ }
121
+ `.trim(),
122
+ );
123
+ });
124
+
125
+ it(`throws for invalid schema.`, async () => {
126
+ await expect(
127
+ bundle.bundle({
128
+ schema: {
129
+ files: [`*.graphql`],
130
+ base: nodePath.resolve(
131
+ __dirname,
132
+ `../../../test_fixtures/missing_scalar`,
133
+ ),
134
+ },
135
+ }),
136
+ ).rejects.toThrowError(`Unknown type "DateTime"`);
137
+ });
138
+ });
@@ -0,0 +1,29 @@
1
+ import * as graphqlToolsUtils from '@graphql-tools/utils';
2
+ import * as graphql from 'graphql';
3
+
4
+ import * as shared from '../shared';
5
+
6
+ export type BundleOptions = {
7
+ schema: shared.LoadSchemaOptions;
8
+
9
+ // Defaults to true.
10
+ shake?: boolean | null;
11
+ };
12
+
13
+ /**
14
+ * Bundles multiple schema together into a single file.
15
+ *
16
+ * @param options - Bundle options.
17
+ *
18
+ * @returns The merged schema as text.
19
+ */
20
+ export const bundle = async (options: BundleOptions): Promise<string> => {
21
+ const shake = options.shake ?? true;
22
+
23
+ let schema = await shared.loadSchema(options.schema);
24
+ if (shake) {
25
+ schema = graphqlToolsUtils.pruneSchema(schema);
26
+ }
27
+
28
+ return graphql.printSchema(schema);
29
+ };
@@ -0,0 +1,2 @@
1
+ export * from './bundle';
2
+ export * from './validate';
@@ -0,0 +1,35 @@
1
+ import * as nodePath from 'node:path';
2
+
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import * as validate from './validate';
6
+
7
+ describe(`validate`, () => {
8
+ it(`creates a validation stamp when validation passes.`, async () => {
9
+ await expect(
10
+ validate.validate({
11
+ schema: {
12
+ files: [`*.graphql`],
13
+ base: nodePath.resolve(
14
+ __dirname,
15
+ `../../../test_fixtures/multiple_files`,
16
+ ),
17
+ },
18
+ }),
19
+ ).resolves.toStrictEqual(expect.any(String));
20
+ });
21
+
22
+ it(`throws for invalid schema.`, async () => {
23
+ await expect(
24
+ validate.validate({
25
+ schema: {
26
+ files: [`*.graphql`],
27
+ base: nodePath.resolve(
28
+ __dirname,
29
+ `../../../test_fixtures/missing_scalar`,
30
+ ),
31
+ },
32
+ }),
33
+ ).rejects.toThrowError(`Unknown type "DateTime"`);
34
+ });
35
+ });
@@ -0,0 +1,49 @@
1
+ import * as stringifyObject from 'stringify-object';
2
+
3
+ import * as shared from '../shared';
4
+
5
+ export type ValidateOptions = {
6
+ schema: shared.LoadSchemaOptions;
7
+ };
8
+
9
+ /**
10
+ * Validates that the provided GraphQL schema contain valid syntax and have no
11
+ * unknown symbols.
12
+ *
13
+ * @param options - Validate options.
14
+ *
15
+ * @returns If validation is successful, returns a validation stamp: a text blob
16
+ * describing the successful outcome and options used during validation. This
17
+ * is often useful in build systems where a build step *must* emit a file. If
18
+ * validation fails, a runtime error will be thrown.
19
+ */
20
+ export const validate = async (options: ValidateOptions): Promise<string> => {
21
+ // We manually normalize the `LoadSchemaOptions` so that we can write the most
22
+ // accurate data in our validation stamp. For example, normalization expands
23
+ // globs and resolves absolute paths. These are critical steps to ensure the
24
+ // validation stamp describes without ambiguity which schema were loaded.
25
+ const normalizedSchemaOptions = await shared.normalizeLoadSchemaOptions(
26
+ options.schema,
27
+ );
28
+
29
+ await shared.loadSchema(normalizedSchemaOptions);
30
+
31
+ return createValidationStamp({
32
+ ...options,
33
+ schema: normalizedSchemaOptions,
34
+ });
35
+ };
36
+
37
+ const createValidationStamp = (
38
+ options: Omit<ValidateOptions, `schema`> & {
39
+ schema: shared.NormalizedLoadSchemaOptions;
40
+ },
41
+ ): string =>
42
+ stringifyObject.default(
43
+ { options, outcome: { valid: true } },
44
+ {
45
+ indent: ` `,
46
+ filter: (container, property) =>
47
+ !(container === options.schema && property === `normalized`),
48
+ },
49
+ );
@@ -0,0 +1,2 @@
1
+ export * from './commands';
2
+ export * from './shared';
@@ -0,0 +1 @@
1
+ export * from './loadSchema';
@@ -0,0 +1,88 @@
1
+ import * as nodeFs from 'node:fs/promises';
2
+ import * as nodePath from 'node:path';
3
+
4
+ import * as graphqlFileLoader from '@graphql-tools/graphql-file-loader';
5
+ import * as graphqlLoad from '@graphql-tools/load';
6
+ import * as glob from 'glob';
7
+
8
+ import type * as graphql from 'graphql';
9
+
10
+ export type LoadSchemaOptions = {
11
+ normalized?: false | null;
12
+
13
+ // Relative paths will be resolved using `base`.
14
+ //
15
+ // Also accepts glob patterns.
16
+ files: Array<string>;
17
+
18
+ // Directory from which `files` are resolved.
19
+ //
20
+ // Defaults to `process.cwd()`.
21
+ base?: string | null;
22
+
23
+ // See @graphql-tools/import#PathAliases
24
+ //
25
+ // Note that relative paths will be resolved relative to `base` to maintain
26
+ // parity with `files`.
27
+ pathAliases?: Map<string, string> | null;
28
+ };
29
+
30
+ export const loadSchema = async (
31
+ options: LoadSchemaOptions | NormalizedLoadSchemaOptions,
32
+ ): Promise<graphql.GraphQLSchema> => {
33
+ const { files, pathAliases } = options.normalized
34
+ ? options
35
+ : await normalizeLoadSchemaOptions(options);
36
+
37
+ // @graphql-tools/graphql-file-loader silently skips input files which are not
38
+ // found. I suspect this is so that it can delegate missing inputs to other
39
+ // loaders. For our purposes, this is undesirable because we want to inform
40
+ // clients if expected inputs are missing. For that reason, we manually
41
+ // validate that the files exist first.
42
+ for (const file of files) {
43
+ try {
44
+ await nodeFs.access(file, nodeFs.constants.F_OK);
45
+ } catch {
46
+ throw new Error(`File does not exist: ${file}`);
47
+ }
48
+ }
49
+
50
+ return graphqlLoad.loadSchema(files, {
51
+ loaders: [new graphqlFileLoader.GraphQLFileLoader()],
52
+ pathAliases: {
53
+ mappings: Object.fromEntries(pathAliases.entries()),
54
+ },
55
+ });
56
+ };
57
+
58
+ export type NormalizedLoadSchemaOptions = {
59
+ normalized: true;
60
+
61
+ // Absolute paths.
62
+ files: Array<string>;
63
+
64
+ // See @graphql-tools/import#PathAliases
65
+ pathAliases: Map<string, string>;
66
+ };
67
+
68
+ export const normalizeLoadSchemaOptions = async (
69
+ options: LoadSchemaOptions,
70
+ ): Promise<NormalizedLoadSchemaOptions> => {
71
+ const base = options.base ?? process.cwd();
72
+ const files = await glob.glob(
73
+ options.files.map((file) => nodePath.resolve(base, file)),
74
+ );
75
+
76
+ const pathAliases = new Map<string, string>();
77
+ if (options.pathAliases != null) {
78
+ for (const [alias, source] of options.pathAliases.entries()) {
79
+ pathAliases.set(alias, nodePath.resolve(base, source));
80
+ }
81
+ }
82
+
83
+ return {
84
+ normalized: true,
85
+ files,
86
+ pathAliases,
87
+ };
88
+ };
@@ -0,0 +1,62 @@
1
+ import * as nodeFs from 'node:fs/promises';
2
+ import * as nodePath from 'node:path';
3
+
4
+ import * as commander from '@commander-js/extra-typings';
5
+
6
+ import * as api from '../../api';
7
+ import * as shared from '../shared';
8
+
9
+ export const createBundleCommand = () =>
10
+ new commander.Command()
11
+ .name(`bundle`)
12
+ .description(`Bundles multiple GraphQL inputs into a single file.`)
13
+ .argument(`<schema...>`, `Input GraphQL schema files to bundle.`)
14
+ .option(
15
+ `-b, --base <directory>`,
16
+ `Base directory from which paths are resolved.`,
17
+ )
18
+ .option(
19
+ `-a, --alias <pattern...>`,
20
+ `Path alias options for @graphql-tools/load.`,
21
+ )
22
+ .option(
23
+ `--out <file>`,
24
+ `Where to write the bundled schema.`,
25
+ `bundle.graphql`,
26
+ )
27
+ .option(`--no-shake`, `Prevents pruning of unused types.`)
28
+ .option(`-s, --silent`, `Only log critical information.`, false)
29
+ .action((schema, options) =>
30
+ bundle({
31
+ schema,
32
+ ...options,
33
+ }),
34
+ );
35
+
36
+ const bundle = async (options: {
37
+ schema: Array<string>;
38
+ base?: string;
39
+ alias?: Array<string>;
40
+ out: string;
41
+ shake: boolean;
42
+ silent: boolean;
43
+ }): Promise<void> => {
44
+ const { schema, base, alias, shake, silent } = options;
45
+ const out = nodePath.resolve(base ?? process.cwd(), options.out);
46
+
47
+ await nodeFs.writeFile(
48
+ out,
49
+ await api.bundle({
50
+ schema: {
51
+ files: schema,
52
+ base,
53
+ pathAliases: shared.parsePathAliases(alias ?? []),
54
+ },
55
+ shake,
56
+ }),
57
+ );
58
+
59
+ if (!silent) {
60
+ console.log(`✅ bundler encountered no errors.`);
61
+ }
62
+ };
@@ -0,0 +1,2 @@
1
+ export * from './bundle';
2
+ export * from './validate';
@@ -0,0 +1,59 @@
1
+ import * as nodeFs from 'node:fs/promises';
2
+ import * as nodePath from 'node:path';
3
+
4
+ import * as commander from '@commander-js/extra-typings';
5
+
6
+ import * as api from '../../api';
7
+ import * as shared from '../shared';
8
+
9
+ export const createValidateCommand = () =>
10
+ new commander.Command()
11
+ .name(`validate`)
12
+ .description(`Checks that all provided GraphQL files form a valid schema.`)
13
+ .argument(`<schema...>`, `Input GraphQL schema files to validate.`)
14
+ .option(
15
+ `-b, --base <directory>`,
16
+ `Base directory from which paths are resolved.`,
17
+ )
18
+ .option(
19
+ `-a, --alias <pattern...>`,
20
+ `Path alias options for @graphql-tools/load.`,
21
+ )
22
+ .option(
23
+ `--stamp <file>`,
24
+ `Creates a validation stamp to confirm valid GraphQL schema.`,
25
+ )
26
+ .option(`-s, --silent`, `Only log critical information.`, false)
27
+ .action((schema, options) =>
28
+ validate({
29
+ schema,
30
+ ...options,
31
+ }),
32
+ );
33
+
34
+ const validate = async (options: {
35
+ schema: Array<string>;
36
+ base?: string;
37
+ alias?: Array<string>;
38
+ stamp?: string;
39
+ silent: boolean;
40
+ }): Promise<void> => {
41
+ const { schema, base, alias, stamp, silent } = options;
42
+
43
+ const validationStamp = await api.validate({
44
+ schema: {
45
+ files: schema,
46
+ base,
47
+ pathAliases: shared.parsePathAliases(alias ?? []),
48
+ },
49
+ });
50
+
51
+ if (stamp != null) {
52
+ const destination = nodePath.resolve(base ?? process.cwd(), stamp);
53
+ await nodeFs.writeFile(destination, validationStamp);
54
+ }
55
+
56
+ if (!silent) {
57
+ console.log(`✅ validation encountered no errors.`);
58
+ }
59
+ };
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as commander from '@commander-js/extra-typings';
4
+ import * as graphql from 'graphql';
5
+
6
+ import * as commands from './commands';
7
+
8
+ const main = () => {
9
+ process.setUncaughtExceptionCaptureCallback((error) => {
10
+ // When thrown natively, GraphQLError prints like any other error with
11
+ // minimal context, however, when logged with `.toString()` it prints
12
+ // helpful debugging information critical for debugging syntax errors (such
13
+ // as source file, location, symbol, etc...).
14
+ //
15
+ // For example:
16
+ //
17
+ // ```
18
+ // Syntax Error: Expected ":", found Name "ID".
19
+ //
20
+ // src/services/api/schema/CurrentUser.graphql:2:6
21
+ // 1 | type CurrentUser {
22
+ // 2 | id ID!
23
+ // | ^
24
+ // 3 | dateCreated: DateTime!
25
+ // ```
26
+ //
27
+ // Versus the naive output:
28
+ //
29
+ // ```
30
+ // return new _GraphQLError.GraphQLError(`Syntax Error: ${description}`, {
31
+ // GraphQLError: Syntax Error: Expected ":", found Name "ID".
32
+ // at syntaxError (node_modules/graphql/error/syntaxError.js:15:10)
33
+ // ```
34
+ if (error instanceof graphql.GraphQLError) {
35
+ console.error(error.toString());
36
+ return;
37
+ }
38
+
39
+ console.error(error);
40
+ });
41
+
42
+ commander.program
43
+ .name(`graphql-buddy`)
44
+ .description(
45
+ `CLI tools for manipulating graphql // designed for build system tooling.`,
46
+ )
47
+ .addCommand(commands.createBundleCommand())
48
+ .addCommand(commands.createValidateCommand())
49
+ .parse();
50
+ };
51
+
52
+ main();
@@ -0,0 +1 @@
1
+ export * from './parsePathAliases';
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import * as parsePathAliases from './parsePathAliases';
4
+
5
+ describe(`parsePathAliases`, () => {
6
+ it(`returns empty when no encoded aliases are passed.`, () => {
7
+ expect(parsePathAliases.parsePathAliases([])).toStrictEqual(new Map());
8
+ });
9
+
10
+ it(`splits aliases using "=" delimiter.`, () => {
11
+ expect(
12
+ parsePathAliases.parsePathAliases([
13
+ `@/*=src/*`,
14
+ `custom-foo=path/to/foo`,
15
+ ]),
16
+ ).toStrictEqual(
17
+ new Map([
18
+ [`@/*`, `src/*`],
19
+ [`custom-foo`, `path/to/foo`],
20
+ ]),
21
+ );
22
+ });
23
+
24
+ it(`throws when aliases conflict.`, () => {
25
+ expect(() =>
26
+ parsePathAliases.parsePathAliases([
27
+ `@/*=src/*`,
28
+ `custom-foo=path/to/foo`,
29
+ `@/*=gen/*`,
30
+ ]),
31
+ ).toThrowError(`Alias "@/*" is set multiple times.`);
32
+ });
33
+
34
+ it(`throws when aliases are malformed.`, () => {
35
+ expect(() => parsePathAliases.parsePathAliases([`@/*`])).toThrowError(
36
+ `Expected an alias in the form "alias=source"`,
37
+ );
38
+
39
+ expect(() =>
40
+ parsePathAliases.parsePathAliases([`foo=bar=baz`]),
41
+ ).toThrowError(`Expected an alias in the form "alias=source"`);
42
+ });
43
+ });
@@ -0,0 +1,25 @@
1
+ export type PathAliases = Map<string, string>;
2
+
3
+ export const parsePathAliases = (
4
+ encodedAliases: Array<string>,
5
+ ): PathAliases => {
6
+ const decodedAliases = new Map<string, string>();
7
+
8
+ for (const encodedAlias of encodedAliases) {
9
+ const parts = encodedAlias.split(`=`);
10
+ if (parts.length !== 2) {
11
+ throw new Error(
12
+ `Expected an alias in the form "alias=source" but found "${encodedAlias}"`,
13
+ );
14
+ }
15
+
16
+ const [alias, source] = parts;
17
+ if (decodedAliases.has(alias)) {
18
+ throw new Error(`Alias "${alias}" is set multiple times.`);
19
+ }
20
+
21
+ decodedAliases.set(alias, source);
22
+ }
23
+
24
+ return decodedAliases;
25
+ };
@@ -0,0 +1,16 @@
1
+ type Query {
2
+ foo: Foo
3
+ bar: Bar
4
+ }
5
+
6
+ type Foo {
7
+ id: ID!
8
+
9
+ dateCreated: DateTime
10
+ }
11
+
12
+ type Bar {
13
+ id: ID!
14
+
15
+ foo: Foo
16
+ }