From c19f5c3c89039f8bdb7b90f343d3f19e754d3340 Mon Sep 17 00:00:00 2001 From: Emma Hamilton Date: Mon, 11 Mar 2024 12:01:23 +1000 Subject: [PATCH] Inline graphql-codegen deps (#128) --- .changeset/moody-laws-clap.md | 5 + packages/compiler/package.json | 3 +- packages/compiler/src/get-generated-types.ts | 13 +- packages/compiler/src/operation-types.ts | 14 +- .../src/test/__snapshots__/index.test.ts.snap | 8 +- packages/compiler/src/test/index.test.ts | 1 + packages/compiler/src/vendor/README.md | 1 + packages/compiler/src/vendor/auto-bind.ts | 32 + .../codegen-core/execute-plugin.ts | 0 .../src/{ => vendor}/codegen-core/index.ts | 0 .../vendor/typescript-operations/config.ts | 206 +++ .../src/vendor/typescript-operations/index.ts | 85 ++ .../ts-operation-variables-to-object.ts | 117 ++ .../ts-selection-set-processor.ts | 118 ++ .../vendor/typescript-operations/visitor.ts | 188 +++ .../visitor-plugin-common/avoid-optionals.ts | 28 + .../base-documents-visitor.ts | 800 +++++++++++ .../visitor-plugin-common/base-visitor.ts | 536 +++++++ .../declaration-kinds.ts | 30 + .../vendor/visitor-plugin-common/imports.ts | 115 ++ .../src/vendor/visitor-plugin-common/index.ts | 14 + .../vendor/visitor-plugin-common/mappers.ts | 262 ++++ .../vendor/visitor-plugin-common/naming.ts | 55 + .../vendor/visitor-plugin-common/scalars.ts | 24 + .../selection-set-processor/base.ts | 107 ++ .../pre-resolve-types.ts | 157 +++ .../selection-set-to-object.ts | 1234 +++++++++++++++++ .../src/vendor/visitor-plugin-common/types.ts | 126 ++ .../src/vendor/visitor-plugin-common/utils.ts | 777 +++++++++++ .../variables-to-object.ts | 204 +++ packages/compiler/src/watch.ts | 2 - packages/compiler/src/weakMemoize.ts | 11 + pnpm-lock.yaml | 452 ++---- 33 files changed, 5328 insertions(+), 397 deletions(-) create mode 100644 .changeset/moody-laws-clap.md create mode 100644 packages/compiler/src/vendor/README.md create mode 100644 packages/compiler/src/vendor/auto-bind.ts rename packages/compiler/src/{ => vendor}/codegen-core/execute-plugin.ts (100%) rename packages/compiler/src/{ => vendor}/codegen-core/index.ts (100%) create mode 100644 packages/compiler/src/vendor/typescript-operations/config.ts create mode 100644 packages/compiler/src/vendor/typescript-operations/index.ts create mode 100644 packages/compiler/src/vendor/typescript-operations/ts-operation-variables-to-object.ts create mode 100644 packages/compiler/src/vendor/typescript-operations/ts-selection-set-processor.ts create mode 100644 packages/compiler/src/vendor/typescript-operations/visitor.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/avoid-optionals.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/base-documents-visitor.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/base-visitor.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/declaration-kinds.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/imports.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/index.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/mappers.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/naming.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/scalars.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/selection-set-processor/base.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/selection-set-processor/pre-resolve-types.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/selection-set-to-object.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/types.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/utils.ts create mode 100644 packages/compiler/src/vendor/visitor-plugin-common/variables-to-object.ts create mode 100644 packages/compiler/src/weakMemoize.ts diff --git a/.changeset/moody-laws-clap.md b/.changeset/moody-laws-clap.md new file mode 100644 index 0000000..2de8504 --- /dev/null +++ b/.changeset/moody-laws-clap.md @@ -0,0 +1,5 @@ +--- +"@ts-gql/compiler": patch +--- + +Inline some dependencies to reduce the size of the package diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 00c8a88..de25548 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -27,8 +27,6 @@ "@babel/parser": "^7.9.6", "@babel/runtime": "^7.9.2", "@babel/types": "^7.9.6", - "@graphql-codegen/plugin-helpers": "^2.4.2", - "@graphql-codegen/typescript-operations": "^2.3.5", "@nodelib/fs.walk": "^1.2.4", "@ts-gql/config": "^0.9.2", "chokidar": "^3.4.0", @@ -41,6 +39,7 @@ "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || 14 || 15 || 16" }, "devDependencies": { + "@graphql-codegen/plugin-helpers": "^5.0.3", "@ts-gql/tag": "*", "@types/babel__code-frame": "^7.0.1", "@types/graceful-fs": "^4.1.3", diff --git a/packages/compiler/src/get-generated-types.ts b/packages/compiler/src/get-generated-types.ts index 9ecf5b8..42768df 100644 --- a/packages/compiler/src/get-generated-types.ts +++ b/packages/compiler/src/get-generated-types.ts @@ -27,6 +27,7 @@ import { DocumentValidationCache, } from "./validate-documents"; import { DocumentNode } from "graphql"; +import { weakMemoize } from "./weakMemoize"; function memoize(fn: (arg: string) => V): (arg: string) => V { const cache: { [key: string]: V } = {}; @@ -38,18 +39,6 @@ function memoize(fn: (arg: string) => V): (arg: string) => V { const walk = promisify(_walk); -function weakMemoize( - fn: (arg: Arg) => Return -): (arg: Arg) => Return { - const cache = new WeakMap(); - return (arg: Arg) => { - if (cache.has(arg)) return cache.get(arg)!; - const result = fn(arg); - cache.set(arg, result); - return result; - }; -} - function getPrintCompilerError() { let readFile = memoize((filename: string) => fs.readFile(filename, "utf8")); diff --git a/packages/compiler/src/operation-types.ts b/packages/compiler/src/operation-types.ts index 7eb61de..6405ed6 100644 --- a/packages/compiler/src/operation-types.ts +++ b/packages/compiler/src/operation-types.ts @@ -1,6 +1,6 @@ import * as fs from "./fs"; import { DocumentNode, ExecutableDefinitionNode } from "graphql"; -import { codegen } from "./codegen-core"; +import { codegen } from "./vendor/codegen-core"; import { hashString, parseTsGqlMeta } from "./utils"; import { FsOperation } from "./fs-operations"; import { @@ -11,6 +11,7 @@ import stripAnsi from "strip-ansi"; import { Config } from "@ts-gql/config"; import { lazyRequire } from "lazy-require.macro"; import { inlineIntoFirstOperationOrFragment } from "./inline-fragments"; +import { typescriptOperationsPlugin } from "./vendor/typescript-operations"; function getUsedFragments(node: ExecutableDefinitionNode) { const visit = lazyRequire().visit; @@ -23,12 +24,20 @@ function getUsedFragments(node: ExecutableDefinitionNode) { return [...usedFragments]; } +let plugin: + | typeof import("./vendor/typescript-operations").typescriptOperationsPlugin + | null = null; + async function generateOperationTypes( config: Config, operation: DocumentNode, filename: string, operationHash: string ): Promise { + if (!plugin) { + plugin = (await import("./vendor/typescript-operations")) + .typescriptOperationsPlugin; + } let result = codegen({ documents: [ { @@ -62,8 +71,7 @@ async function generateOperationTypes( }, ], pluginMap: { - "typescript-operations": - lazyRequire(), + "typescript-operations": { plugin }, }, }); diff --git a/packages/compiler/src/test/__snapshots__/index.test.ts.snap b/packages/compiler/src/test/__snapshots__/index.test.ts.snap index d012567..bd6b889 100644 --- a/packages/compiler/src/test/__snapshots__/index.test.ts.snap +++ b/packages/compiler/src/test/__snapshots__/index.test.ts.snap @@ -391,11 +391,11 @@ export const document = JSON.parse("{\\"kind\\":\\"Document\\",\\"definitions\\" "type": "output", }, { - "content": "// ts-gql-integrity:9d11274ae031b42219b854bcccba80db + "content": "// ts-gql-integrity:b66967fb35ee424fc53721db0fe28146 /* ts-gql-meta-begin { - "hash": "6272f8db7f5b43003cf2f82e304fb523" + "hash": "40967a7756b270276b96576086c615f3" } ts-gql-meta-end */ @@ -406,7 +406,7 @@ import { TypedDocumentNode } from "@ts-gql/tag"; type ThingQueryVariables = SchemaTypes.Exact<{ [key: string]: never; }>; -type ThingQuery = { readonly __typename: 'Query', readonly someObj: { readonly __typename: 'OutputThing', readonly arr: ReadonlyArray<{ readonly __typename: 'OutputThing', readonly other: string, readonly id: string }> } }; +type ThingQuery = { readonly __typename: 'Query', readonly json: MyGloballyDefinedJSONType | null, readonly someObj: { readonly __typename: 'OutputThing', readonly arr: ReadonlyArray<{ readonly __typename: 'OutputThing', readonly other: string, readonly id: string }> } }; @@ -424,7 +424,7 @@ declare module "./@schema" { } } -export const document = JSON.parse("{\\"kind\\":\\"Document\\",\\"definitions\\":[{\\"kind\\":\\"OperationDefinition\\",\\"operation\\":\\"query\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Thing\\"},\\"variableDefinitions\\":[],\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"someObj\\"},\\"arguments\\":[],\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"arr\\"},\\"arguments\\":[],\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"FragmentSpread\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Frag_a\\"},\\"directives\\":[]}]}},{\\"kind\\":\\"FragmentSpread\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Frag_b\\"},\\"directives\\":[]}]}}]}},{\\"kind\\":\\"FragmentDefinition\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Frag_a\\"},\\"typeCondition\\":{\\"kind\\":\\"NamedType\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"OutputThing\\"}},\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"other\\"},\\"arguments\\":[],\\"directives\\":[]}]}},{\\"kind\\":\\"FragmentDefinition\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Frag_b\\"},\\"typeCondition\\":{\\"kind\\":\\"NamedType\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"OutputThing\\"}},\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"arr\\"},\\"arguments\\":[],\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"id\\"},\\"arguments\\":[],\\"directives\\":[]}]}}]}}]}") +export const document = JSON.parse("{\\"kind\\":\\"Document\\",\\"definitions\\":[{\\"kind\\":\\"OperationDefinition\\",\\"operation\\":\\"query\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Thing\\"},\\"variableDefinitions\\":[],\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"json\\"},\\"arguments\\":[],\\"directives\\":[]},{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"someObj\\"},\\"arguments\\":[],\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"arr\\"},\\"arguments\\":[],\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"FragmentSpread\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Frag_a\\"},\\"directives\\":[]}]}},{\\"kind\\":\\"FragmentSpread\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Frag_b\\"},\\"directives\\":[]}]}}]}},{\\"kind\\":\\"FragmentDefinition\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Frag_a\\"},\\"typeCondition\\":{\\"kind\\":\\"NamedType\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"OutputThing\\"}},\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"other\\"},\\"arguments\\":[],\\"directives\\":[]}]}},{\\"kind\\":\\"FragmentDefinition\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"Frag_b\\"},\\"typeCondition\\":{\\"kind\\":\\"NamedType\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"OutputThing\\"}},\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"arr\\"},\\"arguments\\":[],\\"directives\\":[],\\"selectionSet\\":{\\"kind\\":\\"SelectionSet\\",\\"selections\\":[{\\"kind\\":\\"Field\\",\\"name\\":{\\"kind\\":\\"Name\\",\\"value\\":\\"id\\"},\\"arguments\\":[],\\"directives\\":[]}]}}]}}]}") ", "filename": "__generated__/ts-gql/Thing.ts", "type": "output", diff --git a/packages/compiler/src/test/index.test.ts b/packages/compiler/src/test/index.test.ts index 7b72855..9a1b1e1 100644 --- a/packages/compiler/src/test/index.test.ts +++ b/packages/compiler/src/test/index.test.ts @@ -139,6 +139,7 @@ test("something", async () => { `, graphql` query Thing { + json someObj { arr { ...Frag_a diff --git a/packages/compiler/src/vendor/README.md b/packages/compiler/src/vendor/README.md new file mode 100644 index 0000000..c7c9e51 --- /dev/null +++ b/packages/compiler/src/vendor/README.md @@ -0,0 +1 @@ +These packages are inlined from https://github.com/dotansimha/graphql-code-generator, mainly to remove the dependency on relay-compiler which adds quite a large dependency tree diff --git a/packages/compiler/src/vendor/auto-bind.ts b/packages/compiler/src/vendor/auto-bind.ts new file mode 100644 index 0000000..5b7d87d --- /dev/null +++ b/packages/compiler/src/vendor/auto-bind.ts @@ -0,0 +1,32 @@ +// https://github.com/sindresorhus/auto-bind/blob/5f9859a2c163a6567f0595b6268ef667c8248c6a/index.js +const getAllProperties = (object: object) => { + const properties = new Set<[Record, string | symbol]>(); + + do { + for (const key of Reflect.ownKeys(object)) { + properties.add([object, key]); + } + } while ( + (object = Reflect.getPrototypeOf(object)!) && + object !== Object.prototype + ); + + return properties; +}; + +export function autoBind>( + self: SelfType +): SelfType { + for (const [object, key] of getAllProperties(self.constructor.prototype)) { + if (key === "constructor") { + continue; + } + + const descriptor = Reflect.getOwnPropertyDescriptor(object, key); + if (descriptor && typeof descriptor.value === "function") { + (self as any)[key] = self[key as string].bind(self); + } + } + + return self; +} diff --git a/packages/compiler/src/codegen-core/execute-plugin.ts b/packages/compiler/src/vendor/codegen-core/execute-plugin.ts similarity index 100% rename from packages/compiler/src/codegen-core/execute-plugin.ts rename to packages/compiler/src/vendor/codegen-core/execute-plugin.ts diff --git a/packages/compiler/src/codegen-core/index.ts b/packages/compiler/src/vendor/codegen-core/index.ts similarity index 100% rename from packages/compiler/src/codegen-core/index.ts rename to packages/compiler/src/vendor/codegen-core/index.ts diff --git a/packages/compiler/src/vendor/typescript-operations/config.ts b/packages/compiler/src/vendor/typescript-operations/config.ts new file mode 100644 index 0000000..0eb9271 --- /dev/null +++ b/packages/compiler/src/vendor/typescript-operations/config.ts @@ -0,0 +1,206 @@ +import { + AvoidOptionalsConfig, + RawDocumentsConfig, +} from "../visitor-plugin-common"; + +/** + * @description This plugin generates TypeScript types based on your GraphQLSchema _and_ your GraphQL operations and fragments. + * It generates types for your GraphQL documents: Query, Mutation, Subscription and Fragment. + * + * Note: In most configurations, this plugin requires you to use `typescript as well, because it depends on its base types. + */ +export interface TypeScriptDocumentsPluginConfig extends RawDocumentsConfig { + /** + * @description The [GraphQL spec](https://spec.graphql.org/draft/#sel-FAHjBJFCAACE_Gh7d) + * allows arrays and a single primitive value for list input. This allows to + * deactivate that behavior to only accept arrays instead of single values. If + * set to `false`, the definition: `query foo(bar: [Int!]!): Foo` will output + * `bar: Array` instead of `bar: Array | Int` for the variable part. + * @default true + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript'], + * config: { + * arrayInputCoercion: false + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + arrayInputCoercion?: boolean; + /** + * @description This will cause the generator to avoid using TypeScript optionals (`?`) on types, + * so the following definition: `type A { myField: String }` will output `myField: Maybe` + * instead of `myField?: Maybe`. + * @default false + * + * @exampleMarkdown + * ## Override all definition types + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript'], + * config: { + * avoidOptionals: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + * + * ## Override only specific definition types + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript'], + * config: { + * avoidOptionals: { + * field: true + * inputValue: true + * object: true + * defaultValue: true + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + avoidOptionals?: boolean | AvoidOptionalsConfig; + /** + * @description Generates immutable types by adding `readonly` to properties and uses `ReadonlyArray`. + * @default false + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript'], + * config: { + * immutableTypes: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + immutableTypes?: boolean; + + /** + * @description Set to `true` in order to generate output without `export` modifier. + * This is useful if you are generating `.d.ts` file and want it to be globally available. + * @default false + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript'], + * config: { + * noExport: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + noExport?: boolean; + /** + * @description Allow to override the type value of `Maybe`. + * @default T | null + * + * @exampleMarkdown + * ## Allow undefined + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript'], + * config: { + * maybeValue: 'T | null | undefined' + * }, + * }, + * }, + * }; + * export default config; + * ``` + * + * ## Allow `null` in resolvers: + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript'], + * config: { + * maybeValue: 'T extends PromiseLike ? Promise : T | null' + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + maybeValue?: string; + + /** + * @description Adds undefined as a possible type for query variables + * @default false + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file.ts': { + * plugins: ['typescript'], + * config: { + * allowUndefinedQueryVariables: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + + allowUndefinedQueryVariables?: boolean; +} diff --git a/packages/compiler/src/vendor/typescript-operations/index.ts b/packages/compiler/src/vendor/typescript-operations/index.ts new file mode 100644 index 0000000..6482411 --- /dev/null +++ b/packages/compiler/src/vendor/typescript-operations/index.ts @@ -0,0 +1,85 @@ +import { PluginFunction, Types } from "@graphql-codegen/plugin-helpers"; +import { LoadedFragment } from "../visitor-plugin-common"; +import type { FragmentDefinitionNode, GraphQLSchema } from "graphql"; +import { Kind } from "graphql/language/kinds"; +import { concatAST } from "graphql/utilities/concatAST"; +import { TypeScriptDocumentsPluginConfig } from "./config"; +import { TypeScriptDocumentsVisitor } from "./visitor"; + +export type { TypeScriptDocumentsPluginConfig } from "./config"; + +import type { ASTNode } from "graphql"; +import { visit } from "graphql/language/visitor"; + +type VisitFn = typeof visit; +type NewVisitor = Partial[1]>; +type OldVisitor = { + enter?: Partial< + Record["enter"]> + >; + leave?: Partial< + Record["leave"]> + >; +} & NewVisitor; + +function oldVisit( + root: ASTNode, + { enter: enterVisitors, leave: leaveVisitors, ..._newVisitor }: OldVisitor +): any { + const newVisitor: any = _newVisitor; + if (typeof enterVisitors === "object") { + for (const key in enterVisitors) { + newVisitor[key] ||= {}; + newVisitor[key].enter = (enterVisitors as any)[key]; + } + } + if (typeof leaveVisitors === "object") { + for (const key in leaveVisitors) { + newVisitor[key] ||= {}; + newVisitor[key].leave = (leaveVisitors as any)[key]; + } + } + return visit(root, newVisitor); +} + +export const typescriptOperationsPlugin: PluginFunction< + TypeScriptDocumentsPluginConfig, + Types.ComplexPluginOutput +> = ( + schema: GraphQLSchema, + documents: Types.DocumentFile[], + config: TypeScriptDocumentsPluginConfig +) => { + const allAst = concatAST(documents.map((v) => v.document!)); + + const allFragments: LoadedFragment[] = [ + ...( + allAst.definitions.filter( + (d) => d.kind === Kind.FRAGMENT_DEFINITION + ) as FragmentDefinitionNode[] + ).map((fragmentDef) => ({ + node: fragmentDef, + name: fragmentDef.name.value, + onType: fragmentDef.typeCondition.name.value, + isExternal: false, + })), + ]; + + const visitor = new TypeScriptDocumentsVisitor(schema, config, allFragments); + + const visitorResult = oldVisit(allAst, { + leave: visitor as any, + }); + + let content = visitorResult.definitions.join("\n"); + + return { + prepend: [ + ...visitor.getImports(), + ...visitor.getGlobalDeclarations(visitor.config.noExport), + ], + content, + }; +}; + +export { TypeScriptDocumentsVisitor }; diff --git a/packages/compiler/src/vendor/typescript-operations/ts-operation-variables-to-object.ts b/packages/compiler/src/vendor/typescript-operations/ts-operation-variables-to-object.ts new file mode 100644 index 0000000..061dcaa --- /dev/null +++ b/packages/compiler/src/vendor/typescript-operations/ts-operation-variables-to-object.ts @@ -0,0 +1,117 @@ +import { + AvoidOptionalsConfig, + ConvertNameFn, + normalizeAvoidOptionals, + NormalizedScalarsMap, + OperationVariablesToObject, + ParsedDirectiveArgumentAndInputFieldMappings, + ParsedEnumValuesMap, +} from "../visitor-plugin-common"; +import type { TypeNode } from "graphql"; +import { Kind } from "graphql/language/kinds"; + +export class TypeScriptOperationVariablesToObject extends OperationVariablesToObject { + constructor( + _scalars: NormalizedScalarsMap, + _convertName: ConvertNameFn, + private _avoidOptionals: boolean | AvoidOptionalsConfig, + private _immutableTypes: boolean, + _namespacedImportName: string | null = null, + _enumNames: string[] = [], + _enumPrefix = true, + _enumSuffix = true, + _enumValues: ParsedEnumValuesMap = {}, + _applyCoercion: Boolean = false, + _directiveArgumentAndInputFieldMappings: ParsedDirectiveArgumentAndInputFieldMappings = {}, + private _maybeType = "Maybe" + ) { + super( + _scalars, + _convertName, + _namespacedImportName, + _enumNames, + _enumPrefix, + _enumSuffix, + _enumValues, + _applyCoercion, + _directiveArgumentAndInputFieldMappings + ); + } + + private clearOptional(str: string): string { + const prefix = this._namespacedImportName + ? `${this._namespacedImportName}.` + : ""; + const rgx = new RegExp(`^${this.wrapMaybe(`(.*?)`)}$`, "i"); + + if (str.startsWith(`${prefix}${this._maybeType}`)) { + return str.replace(rgx, "$1"); + } + + return str; + } + + public wrapAstTypeWithModifiers( + baseType: string, + typeNode: TypeNode, + applyCoercion = false + ): string { + if (typeNode.kind === Kind.NON_NULL_TYPE) { + const type = this.wrapAstTypeWithModifiers( + baseType, + typeNode.type, + applyCoercion + ); + + return this.clearOptional(type); + } + if (typeNode.kind === Kind.LIST_TYPE) { + const innerType = this.wrapAstTypeWithModifiers( + baseType, + typeNode.type, + applyCoercion + ); + const listInputCoercionExtension = applyCoercion ? ` | ${innerType}` : ""; + + return this.wrapMaybe( + `${ + this._immutableTypes ? "ReadonlyArray" : "Array" + }<${innerType}>${listInputCoercionExtension}` + ); + } + return this.wrapMaybe(baseType); + } + + protected formatFieldString( + fieldName: string, + isNonNullType: boolean, + hasDefaultValue: boolean + ): string { + return `${fieldName}${ + this.getAvoidOption(isNonNullType, hasDefaultValue) ? "?" : "" + }`; + } + + protected formatTypeString(fieldType: string): string { + return fieldType; + } + + protected wrapMaybe(type?: string) { + const prefix = this._namespacedImportName + ? `${this._namespacedImportName}.` + : ""; + return `${prefix}${this._maybeType}${type ? `<${type}>` : ""}`; + } + + protected getAvoidOption(isNonNullType: boolean, hasDefaultValue: boolean) { + const options = normalizeAvoidOptionals(this._avoidOptionals); + return ( + ((options.object || !options.defaultValue) && hasDefaultValue) || + (!options.object && !isNonNullType) + ); + } + + protected getPunctuation(): string { + return ";"; + } +} diff --git a/packages/compiler/src/vendor/typescript-operations/ts-selection-set-processor.ts b/packages/compiler/src/vendor/typescript-operations/ts-selection-set-processor.ts new file mode 100644 index 0000000..5034901 --- /dev/null +++ b/packages/compiler/src/vendor/typescript-operations/ts-selection-set-processor.ts @@ -0,0 +1,118 @@ +import { + BaseSelectionSetProcessor, + LinkField, + PrimitiveAliasedFields, + PrimitiveField, + ProcessResult, + SelectionSetProcessorConfig, +} from "../visitor-plugin-common"; +import { + GraphQLInterfaceType, + GraphQLObjectType, +} from "graphql/type/definition"; + +export class TypeScriptSelectionSetProcessor extends BaseSelectionSetProcessor { + transformPrimitiveFields( + schemaType: GraphQLObjectType | GraphQLInterfaceType, + fields: PrimitiveField[], + unsetTypes?: boolean + ): ProcessResult { + if (fields.length === 0) { + return []; + } + + const parentName = + (this.config.namespacedImportName + ? `${this.config.namespacedImportName}.` + : "") + + this.config.convertName(schemaType.name, { + useTypesPrefix: true, + }); + + if (unsetTypes) { + return [ + `MakeEmpty<${parentName}, ${fields + .map((field) => `'${field.fieldName}'`) + .join(" | ")}>`, + ]; + } + + let hasConditionals = false; + const conditilnalsList: string[] = []; + let resString = `Pick<${parentName}, ${fields + .map((field) => { + if (field.isConditional) { + hasConditionals = true; + conditilnalsList.push(field.fieldName); + } + return `'${field.fieldName}'`; + }) + .join(" | ")}>`; + + if (hasConditionals) { + const avoidOptional = + // TODO: check type and exec only if relevant + this.config.avoidOptionals === true || + (typeof this.config.avoidOptionals === "object" && + (this.config.avoidOptionals.field || + this.config.avoidOptionals.inputValue || + this.config.avoidOptionals.object)); + + const transform = avoidOptional ? "MakeMaybe" : "MakeOptional"; + resString = `${ + this.config.namespacedImportName + ? `${this.config.namespacedImportName}.` + : "" + }${transform}<${resString}, ${conditilnalsList + .map((field) => `'${field}'`) + .join(" | ")}>`; + } + return [resString]; + } + + transformTypenameField(type: string, name: string): ProcessResult { + return [`{ ${name}: ${type} }`]; + } + + transformAliasesPrimitiveFields( + schemaType: GraphQLObjectType | GraphQLInterfaceType, + fields: PrimitiveAliasedFields[] + ): ProcessResult { + if (fields.length === 0) { + return []; + } + + const parentName = + (this.config.namespacedImportName + ? `${this.config.namespacedImportName}.` + : "") + + this.config.convertName(schemaType.name, { + useTypesPrefix: true, + }); + + return [ + `{ ${fields + .map((aliasedField) => { + const value = + aliasedField.fieldName === "__typename" + ? `'${schemaType.name}'` + : `${parentName}['${aliasedField.fieldName}']`; + + return `${aliasedField.alias}: ${value}`; + }) + .join(", ")} }`, + ]; + } + + transformLinkFields(fields: LinkField[]): ProcessResult { + if (fields.length === 0) { + return []; + } + + return [ + `{ ${fields + .map((field) => `${field.alias || field.name}: ${field.selectionSet}`) + .join(", ")} }`, + ]; + } +} diff --git a/packages/compiler/src/vendor/typescript-operations/visitor.ts b/packages/compiler/src/vendor/typescript-operations/visitor.ts new file mode 100644 index 0000000..879f644 --- /dev/null +++ b/packages/compiler/src/vendor/typescript-operations/visitor.ts @@ -0,0 +1,188 @@ +import { + AvoidOptionalsConfig, + BaseDocumentsVisitor, + DeclarationKind, + generateFragmentImportStatement, + getConfigValue, + LoadedFragment, + normalizeAvoidOptionals, + ParsedDocumentsConfig, + PreResolveTypesProcessor, + SelectionSetProcessorConfig, + SelectionSetToObject, + wrapTypeWithModifiers, +} from "../visitor-plugin-common"; +import { autoBind } from "../auto-bind"; +import { isEnumType, isNonNullType } from "graphql/type/definition"; +import type { + GraphQLNamedType, + GraphQLOutputType, + GraphQLSchema, +} from "graphql"; +import { TypeScriptDocumentsPluginConfig } from "./config"; +import { TypeScriptOperationVariablesToObject } from "./ts-operation-variables-to-object"; +import { TypeScriptSelectionSetProcessor } from "./ts-selection-set-processor"; + +export interface TypeScriptDocumentsParsedConfig extends ParsedDocumentsConfig { + arrayInputCoercion: boolean; + avoidOptionals: AvoidOptionalsConfig; + immutableTypes: boolean; + noExport: boolean; + maybeValue: string; + allowUndefinedQueryVariables: boolean; +} + +export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< + TypeScriptDocumentsPluginConfig, + TypeScriptDocumentsParsedConfig +> { + constructor( + schema: GraphQLSchema, + config: TypeScriptDocumentsPluginConfig, + allFragments: LoadedFragment[] + ) { + super( + config, + { + arrayInputCoercion: getConfigValue(config.arrayInputCoercion, true), + noExport: getConfigValue(config.noExport, false), + avoidOptionals: normalizeAvoidOptionals( + getConfigValue(config.avoidOptionals, false) + ), + immutableTypes: getConfigValue(config.immutableTypes, false), + nonOptionalTypename: getConfigValue(config.nonOptionalTypename, false), + preResolveTypes: getConfigValue(config.preResolveTypes, true), + mergeFragmentTypes: getConfigValue(config.mergeFragmentTypes, false), + allowUndefinedQueryVariables: getConfigValue( + config.allowUndefinedQueryVariables, + false + ), + } as TypeScriptDocumentsParsedConfig, + schema + ); + + autoBind(this); + + const preResolveTypes = getConfigValue(config.preResolveTypes, true); + const defaultMaybeValue = "T | null"; + const maybeValue = getConfigValue(config.maybeValue, defaultMaybeValue); + + const wrapOptional = (type: string) => { + if (preResolveTypes === true) { + return maybeValue.replace("T", type); + } + const prefix = this.config.namespacedImportName + ? `${this.config.namespacedImportName}.` + : ""; + return `${prefix}Maybe<${type}>`; + }; + const wrapArray = (type: string) => { + const listModifier = this.config.immutableTypes + ? "ReadonlyArray" + : "Array"; + return `${listModifier}<${type}>`; + }; + + const formatNamedField = ( + name: string, + type: GraphQLOutputType | GraphQLNamedType | null, + isConditional = false, + isOptional = false + ): string => { + const optional = + isOptional || + isConditional || + (!this.config.avoidOptionals.field && !!type && !isNonNullType(type)); + return ( + (this.config.immutableTypes ? `readonly ${name}` : name) + + (optional ? "?" : "") + ); + }; + + const processorConfig: SelectionSetProcessorConfig = { + namespacedImportName: this.config.namespacedImportName, + convertName: this.convertName.bind(this), + enumPrefix: this.config.enumPrefix, + enumSuffix: this.config.enumSuffix, + scalars: this.scalars, + formatNamedField, + wrapTypeWithModifiers(baseType, type) { + return wrapTypeWithModifiers(baseType, type, { + wrapOptional, + wrapArray, + }); + }, + avoidOptionals: this.config.avoidOptionals, + printFieldsOnNewLines: this.config.printFieldsOnNewLines, + }; + const processor = new ( + preResolveTypes + ? PreResolveTypesProcessor + : TypeScriptSelectionSetProcessor + )(processorConfig); + this.setSelectionSetHandler( + new SelectionSetToObject( + processor, + this.scalars, + this.schema, + this.convertName.bind(this), + this.getFragmentSuffix.bind(this), + allFragments, + this.config + ) + ); + const enumsNames = Object.keys(schema.getTypeMap()).filter((typeName) => + isEnumType(schema.getType(typeName)) + ); + this.setVariablesTransformer( + new TypeScriptOperationVariablesToObject( + this.scalars, + this.convertName.bind(this), + this.config.avoidOptionals.object!, + this.config.immutableTypes, + this.config.namespacedImportName, + enumsNames, + this.config.enumPrefix, + this.config.enumSuffix, + this.config.enumValues, + this.config.arrayInputCoercion, + undefined, + "InputMaybe" + ) + ); + this._declarationBlockConfig = { + ignoreExport: this.config.noExport, + }; + } + + public getImports(): Array { + return !this.config.globalNamespace && + (this.config.inlineFragmentTypes === "combine" || + this.config.inlineFragmentTypes === "mask") + ? this.config.fragmentImports.map((fragmentImport) => + generateFragmentImportStatement(fragmentImport, "type") + ) + : []; + } + + protected getPunctuation(_declarationKind: DeclarationKind): string { + return ";"; + } + + protected applyVariablesWrapper( + variablesBlock: string, + operationType: string + ): string { + const prefix = this.config.namespacedImportName + ? `${this.config.namespacedImportName}.` + : ""; + const extraType = + this.config.allowUndefinedQueryVariables && operationType === "Query" + ? " | undefined" + : ""; + + return `${prefix}Exact<${ + variablesBlock === "{}" ? `{ [key: string]: never; }` : variablesBlock + }>${extraType}`; + } +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/avoid-optionals.ts b/packages/compiler/src/vendor/visitor-plugin-common/avoid-optionals.ts new file mode 100644 index 0000000..30066bb --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/avoid-optionals.ts @@ -0,0 +1,28 @@ +import { AvoidOptionalsConfig } from "./types"; + +export const DEFAULT_AVOID_OPTIONALS: AvoidOptionalsConfig = { + object: false, + inputValue: false, + field: false, + defaultValue: false, + resolvers: false, +}; + +export function normalizeAvoidOptionals( + avoidOptionals?: boolean | AvoidOptionalsConfig +): AvoidOptionalsConfig { + if (typeof avoidOptionals === "boolean") { + return { + object: avoidOptionals, + inputValue: avoidOptionals, + field: avoidOptionals, + defaultValue: avoidOptionals, + resolvers: avoidOptionals, + }; + } + + return { + ...DEFAULT_AVOID_OPTIONALS, + ...avoidOptionals, + }; +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/base-documents-visitor.ts b/packages/compiler/src/vendor/visitor-plugin-common/base-documents-visitor.ts new file mode 100644 index 0000000..1a8a587 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/base-documents-visitor.ts @@ -0,0 +1,800 @@ +import type { + FragmentDefinitionNode, + GraphQLSchema, + OperationDefinitionNode, + OperationTypeNode, + VariableDefinitionNode, +} from "graphql"; +import { BaseVisitor, ParsedConfig, RawConfig } from "./base-visitor"; +import { DEFAULT_SCALARS } from "./scalars"; +import { SelectionSetToObject } from "./selection-set-to-object"; +import { + DeclarationKind, + DeclarationKindConfig, + DirectiveArgumentAndInputFieldMappings, + EnumValuesMap, + NormalizedScalarsMap, + ParsedDirectiveArgumentAndInputFieldMappings, + ParsedEnumValuesMap, +} from "./types"; +import { + buildScalarsFromConfig, + DeclarationBlock, + DeclarationBlockConfig, + getConfigValue, +} from "./utils"; +import { OperationVariablesToObject } from "./variables-to-object"; +import { autoBind } from "../auto-bind"; + +function getRootType(operation: OperationTypeNode, schema: GraphQLSchema) { + switch (operation) { + case "query": + return schema.getQueryType(); + case "mutation": + return schema.getMutationType(); + case "subscription": + return schema.getSubscriptionType(); + } + throw new Error(`Unknown operation type: ${operation}`); +} + +interface ParsedTypesConfig extends ParsedConfig { + enumValues: ParsedEnumValuesMap; + declarationKind: DeclarationKindConfig; + addUnderscoreToArgsType: boolean; + onlyEnums: boolean; + onlyOperationTypes: boolean; + enumPrefix: boolean; + enumSuffix: boolean; + fieldWrapperValue: string; + wrapFieldDefinitions: boolean; + entireFieldWrapperValue: string; + wrapEntireDefinitions: boolean; + ignoreEnumValuesFromSchema: boolean; + directiveArgumentAndInputFieldMappings: ParsedDirectiveArgumentAndInputFieldMappings; +} + +export interface ParsedDocumentsConfig extends ParsedTypesConfig { + addTypename: boolean; + preResolveTypes: boolean; + extractAllFieldsToTypes: boolean; + globalNamespace: boolean; + operationResultSuffix: string; + dedupeOperationSuffix: boolean; + omitOperationSuffix: boolean; + namespacedImportName: string | null; + exportFragmentSpreadSubTypes: boolean; + skipTypeNameForRoot: boolean; + mergeFragmentTypes: boolean; +} + +interface RawTypesConfig extends RawConfig { + /** + * @description Adds `_` to generated `Args` types in order to avoid duplicate identifiers. + * + * @exampleMarkdown + * ## With Custom Values + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * addUnderscoreToArgsType: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + addUnderscoreToArgsType?: boolean; + /** + * @description Overrides the default value of enum values declared in your GraphQL schema. + * You can also map the entire enum to an external type by providing a string that of `module#type`. + * + * @exampleMarkdown + * ## With Custom Values + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * enumValues: { + * MyEnum: { + * A: 'foo' + * } + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + * + * ## With External Enum + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * enumValues: { + * MyEnum: './my-file#MyCustomEnum', + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + * + * ## Import All Enums from a file + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * enumValues: { + * MyEnum: './my-file', + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + enumValues?: EnumValuesMap; + /** + * @description Overrides the default output for various GraphQL elements. + * + * @exampleMarkdown + * ## Override all declarations + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * declarationKind: 'interface' + * }, + * }, + * }, + * }; + * export default config; + * ``` + * + * ## Override only specific declarations + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * declarationKind: { + * type: 'interface', + * input: 'interface' + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + declarationKind?: DeclarationKind | DeclarationKindConfig; + /** + * @default true + * @description Allow you to disable prefixing for generated enums, works in combination with `typesPrefix`. + * + * @exampleMarkdown + * ## Disable enum prefixes + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * typesPrefix: 'I', + * enumPrefix: false + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + enumPrefix?: boolean; + /** + * @default true + * @description Allow you to disable suffixing for generated enums, works in combination with `typesSuffix`. + * + * @exampleMarkdown + * ## Disable enum suffixes + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * typesSuffix: 'I', + * enumSuffix: false + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + enumSuffix?: boolean; + /** + * @description Allow you to add wrapper for field type, use T as the generic value. Make sure to set `wrapFieldDefinitions` to `true` in order to make this flag work. + * @default T + * + * @exampleMarkdown + * ## Allow Promise + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * wrapFieldDefinitions: true, + * fieldWrapperValue: 'T | Promise', + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + fieldWrapperValue?: string; + /** + * @description Set to `true` in order to wrap field definitions with `FieldWrapper`. + * This is useful to allow return types such as Promises and functions. + * @default false + * + * @exampleMarkdown + * ## Enable wrapping fields + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * wrapFieldDefinitions: true, + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + wrapFieldDefinitions?: boolean; + /** + * @description This will cause the generator to emit types for enums only + * @default false + * + * @exampleMarkdown + * ## Override all definition types + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * onlyEnums: true, + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + onlyEnums?: boolean; + /** + * @description This will cause the generator to emit types for operations only (basically only enums and scalars) + * @default false + * + * @exampleMarkdown + * ## Override all definition types + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * onlyOperationTypes: true, + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + onlyOperationTypes?: boolean; + /** + * @description This will cause the generator to ignore enum values defined in GraphQLSchema + * @default false + * + * @exampleMarkdown + * ## Ignore enum values from schema + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * ignoreEnumValuesFromSchema: true, + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + ignoreEnumValuesFromSchema?: boolean; + /** + * @name wrapEntireFieldDefinitions + * @type boolean + * @description Set to `true` in order to wrap field definitions with `EntireFieldWrapper`. + * This is useful to allow return types such as Promises and functions for fields. + * Differs from `wrapFieldDefinitions` in that this wraps the entire field definition if i.e. the field is an Array, while + * `wrapFieldDefinitions` will wrap every single value inside the array. + * @default true + * + * @example Enable wrapping entire fields + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * wrapEntireFieldDefinitions: false, + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + wrapEntireFieldDefinitions?: boolean; + /** + * @name entireFieldWrapperValue + * @type string + * @description Allow to override the type value of `EntireFieldWrapper`. This wrapper applies outside of Array and Maybe + * unlike `fieldWrapperValue`, that will wrap the inner type. + * @default T | Promise | (() => T | Promise) + * + * @example Only allow values + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * entireFieldWrapperValue: 'T', + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + entireFieldWrapperValue?: string; + /** + * @description Replaces a GraphQL scalar with a custom type based on the applied directive on an argument or input field. + * + * You can use both `module#type` and `module#namespace#type` syntax. + * Will NOT work with introspected schemas since directives are not exported. + * Only works with directives on ARGUMENT_DEFINITION or INPUT_FIELD_DEFINITION. + * + * **WARNING:** Using this option does only change the type definitions. + * + * For actually ensuring that a type is correct at runtime you will have to use schema transforms (e.g. with [@graphql-tools/utils mapSchema](https://graphql-tools.com/docs/schema-directives)) that apply those rules! + * Otherwise, you might end up with a runtime type mismatch which could cause unnoticed bugs or runtime errors. + * + * Please use this configuration option with care! + * + * @exampleMarkdown + * ## Custom Context Type\ + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * directiveArgumentAndInputFieldMappings: { + * AsNumber: 'number', + * AsComplex: './my-models#Complex', + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + directiveArgumentAndInputFieldMappings?: DirectiveArgumentAndInputFieldMappings; + /** + * @description Adds a suffix to the imported names to prevent name clashes. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * directiveArgumentAndInputFieldMappings: 'Model' + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + directiveArgumentAndInputFieldMappingTypeSuffix?: string; +} + +export interface RawDocumentsConfig extends RawTypesConfig { + /** + * @default true + * @description Uses primitive types where possible. + * Set to `false` in order to use `Pick` and take use the types generated by `typescript` plugin. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * preResolveTypes: false + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + preResolveTypes?: boolean; + /** + * @default false + * @description Avoid adding `__typename` for root types. This is ignored when a selection explicitly specifies `__typename`. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * skipTypeNameForRoot: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + skipTypeNameForRoot?: boolean; + /** + * @default false + * @description Puts all generated code under `global` namespace. Useful for Stencil integration. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * globalNamespace: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + globalNamespace?: boolean; + /** + * @default "" + * @description Adds a suffix to generated operation result type names + */ + operationResultSuffix?: string; + /** + * @default false + * @description Set this configuration to `true` if you wish to make sure to remove duplicate operation name suffix. + */ + dedupeOperationSuffix?: boolean; + /** + * @default false + * @description Set this configuration to `true` if you wish to disable auto add suffix of operation name, like `Query`, `Mutation`, `Subscription`, `Fragment`. + */ + omitOperationSuffix?: boolean; + /** + * @default false + * @description If set to true, it will export the sub-types created in order to make it easier to access fields declared under fragment spread. + */ + exportFragmentSpreadSubTypes?: boolean; + /** + * @default false + * @description If set to true, merge equal fragment interfaces. + */ + mergeFragmentTypes?: boolean; + + // The following are internal, and used by presets + /** + * @ignore + */ + namespacedImportName?: string; +} + +export class BaseDocumentsVisitor< + TRawConfig extends RawDocumentsConfig = RawDocumentsConfig, + TPluginConfig extends ParsedDocumentsConfig = ParsedDocumentsConfig +> extends BaseVisitor { + protected _unnamedCounter = 1; + protected _variablesTransfomer: OperationVariablesToObject; + protected _selectionSetToObject!: SelectionSetToObject; + protected _globalDeclarations: Set = new Set(); + + constructor( + rawConfig: TRawConfig, + additionalConfig: TPluginConfig, + protected _schema: GraphQLSchema, + defaultScalars: NormalizedScalarsMap = DEFAULT_SCALARS + ) { + super(rawConfig, { + exportFragmentSpreadSubTypes: getConfigValue( + rawConfig.exportFragmentSpreadSubTypes, + false + ), + enumPrefix: getConfigValue(rawConfig.enumPrefix, true), + enumSuffix: getConfigValue(rawConfig.enumSuffix, true), + preResolveTypes: getConfigValue(rawConfig.preResolveTypes, true), + dedupeOperationSuffix: getConfigValue( + rawConfig.dedupeOperationSuffix, + false + ), + omitOperationSuffix: getConfigValue(rawConfig.omitOperationSuffix, false), + skipTypeNameForRoot: getConfigValue(rawConfig.skipTypeNameForRoot, false), + namespacedImportName: getConfigValue( + rawConfig.namespacedImportName, + null + ), + addTypename: !rawConfig.skipTypename, + globalNamespace: !!rawConfig.globalNamespace, + operationResultSuffix: getConfigValue( + rawConfig.operationResultSuffix, + "" + ), + scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars), + ...((additionalConfig || {}) as any), + }); + + autoBind(this); + this._variablesTransfomer = new OperationVariablesToObject( + this.scalars, + this.convertName, + this.config.namespacedImportName + ); + } + + public getGlobalDeclarations(noExport = false): string[] { + return Array.from(this._globalDeclarations).map((t) => + noExport ? t : `export ${t}` + ); + } + + setSelectionSetHandler(handler: SelectionSetToObject): void { + this._selectionSetToObject = handler; + } + + setDeclarationBlockConfig(config: DeclarationBlockConfig): void { + this._declarationBlockConfig = config; + } + + setVariablesTransformer( + variablesTransfomer: OperationVariablesToObject + ): void { + this._variablesTransfomer = variablesTransfomer; + } + + public get schema(): GraphQLSchema { + return this._schema; + } + + public get addTypename(): boolean { + return this._parsedConfig.addTypename; + } + + private handleAnonymousOperation(node: OperationDefinitionNode): string { + const name = node.name?.value; + + if (name) { + return this.convertName(name, { + useTypesPrefix: false, + useTypesSuffix: false, + }); + } + + return this.convertName(String(this._unnamedCounter++), { + prefix: "Unnamed_", + suffix: "_", + useTypesPrefix: false, + useTypesSuffix: false, + }); + } + + FragmentDefinition(node: FragmentDefinitionNode): string { + const fragmentRootType = this._schema.getType( + node.typeCondition.name.value + )!; + const selectionSet = this._selectionSetToObject.createNext( + fragmentRootType, + node.selectionSet + ); + const fragmentSuffix = this.getFragmentSuffix(node); + return selectionSet.transformFragmentSelectionSetToTypes( + node.name.value, + fragmentSuffix, + this._declarationBlockConfig + ); + } + + protected applyVariablesWrapper( + variablesBlock: string, + _operationType?: string + ): string { + return variablesBlock; + } + + OperationDefinition(node: OperationDefinitionNode): string { + const name = this.handleAnonymousOperation(node); + const operationRootType = getRootType(node.operation, this._schema); + + if (!operationRootType) { + throw new Error( + `Unable to find root schema type for operation type "${node.operation}"!` + ); + } + + const selectionSet = this._selectionSetToObject.createNext( + operationRootType, + node.selectionSet + ); + const visitedOperationVariables = + this._variablesTransfomer.transform( + node.variableDefinitions || [] + ); + const operationType = + node.operation[0].toUpperCase() + node.operation.slice(1); + const operationTypeSuffix = this.getOperationSuffix(name, operationType); + const selectionSetObjects = selectionSet.transformSelectionSet( + this.convertName(name, { + suffix: operationTypeSuffix, + }) + ); + + const operationResult = new DeclarationBlock(this._declarationBlockConfig) + .export() + .asKind("type") + .withName( + this.convertName(name, { + suffix: + operationTypeSuffix + this._parsedConfig.operationResultSuffix, + }) + ) + .withContent(selectionSetObjects.mergedTypeString).string; + + const operationVariables = new DeclarationBlock({ + ...this._declarationBlockConfig, + blockTransformer: (t) => this.applyVariablesWrapper(t, operationType), + }) + .export() + .asKind("type") + .withName( + this.convertName(name, { + suffix: operationTypeSuffix + "Variables", + }) + ) + .withBlock(visitedOperationVariables!).string; + + const dependentTypesContent = this._parsedConfig.extractAllFieldsToTypes + ? selectionSetObjects.dependentTypes.map( + (i) => + new DeclarationBlock(this._declarationBlockConfig) + .export() + .asKind("type") + .withName(i.name) + .withContent(i.content).string + ) + : []; + + return [ + ...(dependentTypesContent.length > 0 + ? [dependentTypesContent.join("\n")] + : []), + operationVariables, + operationResult, + ] + .filter((r) => r) + .join("\n\n"); + } +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/base-visitor.ts b/packages/compiler/src/vendor/visitor-plugin-common/base-visitor.ts new file mode 100644 index 0000000..7dd0b8e --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/base-visitor.ts @@ -0,0 +1,536 @@ +import type { + ASTNode, + FragmentDefinitionNode, + OperationDefinitionNode, +} from "graphql"; +import { FragmentImport, ImportDeclaration } from "./imports"; +import { convertName } from "./naming"; +import { + ConvertFn, + ConvertOptions, + DeclarationKind, + LoadedFragment, + NamingConvention, + NormalizedScalarsMap, + ParsedScalarsMap, + ScalarsMap, +} from "./types"; +import { DeclarationBlockConfig } from "./utils"; +import { autoBind } from "../auto-bind"; + +export interface BaseVisitorConvertOptions { + useTypesPrefix?: boolean; + useTypesSuffix?: boolean; +} + +export type InlineFragmentTypeOptions = "inline" | "combine" | "mask"; + +export interface ParsedConfig { + scalars: ParsedScalarsMap; + convert: ConvertFn; + typesPrefix: string; + typesSuffix: string; + addTypename: boolean; + nonOptionalTypename: boolean; + extractAllFieldsToTypes: boolean; + externalFragments: LoadedFragment[]; + fragmentImports: ImportDeclaration[]; + immutableTypes: boolean; + useTypeImports: boolean; + dedupeFragments: boolean; + allowEnumStringTypes: boolean; + inlineFragmentTypes: InlineFragmentTypeOptions; + emitLegacyCommonJSImports: boolean; + printFieldsOnNewLines: boolean; +} + +export interface RawConfig { + /** + * @description Makes scalars strict. + * + * If scalars are found in the schema that are not defined in `scalars` + * an error will be thrown during codegen. + * @default false + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * strictScalars: true, + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + strictScalars?: boolean; + /** + * @description Allows you to override the type that unknown scalars will have. + * @default any + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * defaultScalarType: 'unknown' + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + defaultScalarType?: string; + /** + * @description Extends or overrides the built-in scalars and custom GraphQL scalars to a custom type. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * scalars: { + * ID: { + * input: 'string', + * output: 'string | number' + * } + * DateTime: 'Date', + * JSON: '{ [key: string]: any }', + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + scalars?: ScalarsMap; + /** + * @default change-case-all#pascalCase + * @description Allow you to override the naming convention of the output. + * You can either override all namings, or specify an object with specific custom naming convention per output. + * The format of the converter must be a valid `module#method`. + * Allowed values for specific output are: `typeNames`, `enumValues`. + * You can also use "keep" to keep all GraphQL names as-is. + * Additionally, you can set `transformUnderscore` to `true` if you want to override the default behavior, + * which is to preserve underscores. + * + * Available case functions in `change-case-all` are `camelCase`, `capitalCase`, `constantCase`, `dotCase`, `headerCase`, `noCase`, `paramCase`, `pascalCase`, `pathCase`, `sentenceCase`, `snakeCase`, `lowerCase`, `localeLowerCase`, `lowerCaseFirst`, `spongeCase`, `titleCase`, `upperCase`, `localeUpperCase` and `upperCaseFirst` + * [See more](https://github.com/btxtiger/change-case-all) + * + * @exampleMarkdown + * ## Override All Names + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * namingConvention: 'change-case-all#lowerCase', + * }, + * }, + * }, + * }; + * export default config; + * ``` + * + * ## Upper-case enum values + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * namingConvention: { + * typeNames: 'change-case-all#pascalCase', + * enumValues: 'change-case-all#upperCase', + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + * + * ## Keep names as is + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * namingConvention: 'keep', + * }, + * }, + * }, + * }; + * export default config; + * ``` + * + * ## Remove Underscores + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * namingConvention: { + * typeNames: 'change-case-all#pascalCase', + * transformUnderscore: true + * } + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + namingConvention?: NamingConvention; + /** + * @default "" + * @description Prefixes all the generated types. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * typesPrefix: 'I', + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + typesPrefix?: string; + /** + * @default "" + * @description Suffixes all the generated types. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * typesSuffix: 'I', + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + typesSuffix?: string; + /** + * @default false + * @description Does not add `__typename` to the generated types, unless it was specified in the selection set. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * skipTypename: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + skipTypename?: boolean; + /** + * @default false + * @description Automatically adds `__typename` field to the generated types, even when they are not specified + * in the selection set, and makes it non-optional + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * nonOptionalTypename: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + nonOptionalTypename?: boolean; + /** + * @name useTypeImports + * @type boolean + * @default false + * @description Will use `import type {}` rather than `import {}` when importing only types. This gives + * compatibility with TypeScript's "importsNotUsedAsValues": "error" option + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * useTypeImports: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + useTypeImports?: boolean; + + /* The following configuration are for preset configuration and should not be set manually (for most use cases...) */ + /** + * @ignore + */ + externalFragments?: LoadedFragment[]; + /** + * @ignore + */ + fragmentImports?: ImportDeclaration[]; + /** + * @ignore + */ + globalNamespace?: boolean; + /** + * @description Removes fragment duplicates for reducing data transfer. + * It is done by removing sub-fragments imports from fragment definition + * Instead - all of them are imported to the Operation node. + * @type boolean + * @default false + * @deprecated This option is no longer needed. It will be removed in the next major version. + */ + dedupeFragments?: boolean; + /** + * @ignore + */ + allowEnumStringTypes?: boolean; + /** + * @description Whether fragment types should be inlined into other operations. + * "inline" is the default behavior and will perform deep inlining fragment types within operation type definitions. + * "combine" is the previous behavior that uses fragment type references without inlining the types (and might cause issues with deeply nested fragment that uses list types). + * + * @type string + * @default inline + */ + inlineFragmentTypes?: InlineFragmentTypeOptions; + /** + * @default true + * @description Emit legacy common js imports. + * Default it will be `true` this way it ensure that generated code works with [non-compliant bundlers](https://github.com/dotansimha/graphql-code-generator/issues/8065). + */ + emitLegacyCommonJSImports?: boolean; + + /** + * @default false + * @description Extract all field types to their own types, instead of inlining them. + * This helps to reduce type duplication, and makes type errors more readable. + * It can also significantly reduce the size of the generated code, the generation time, + * and the typechecking time. + */ + extractAllFieldsToTypes?: boolean; + + /** + * @default false + * @description If you prefer to have each field in generated types printed on a new line, set this to true. + * This can be useful for improving readability of the resulting types, + * without resorting to running tools like Prettier on the output. + */ + printFieldsOnNewLines?: boolean; +} + +export class BaseVisitor< + TRawConfig extends RawConfig = RawConfig, + TPluginConfig extends ParsedConfig = ParsedConfig +> { + protected _parsedConfig: TPluginConfig; + protected _declarationBlockConfig: DeclarationBlockConfig = {}; + public readonly scalars: NormalizedScalarsMap; + + constructor(rawConfig: TRawConfig, additionalConfig: Partial) { + this._parsedConfig = { + convert: convertName, + typesPrefix: rawConfig.typesPrefix || "", + typesSuffix: rawConfig.typesSuffix || "", + externalFragments: rawConfig.externalFragments || [], + fragmentImports: rawConfig.fragmentImports || [], + addTypename: !rawConfig.skipTypename, + nonOptionalTypename: !!rawConfig.nonOptionalTypename, + useTypeImports: !!rawConfig.useTypeImports, + dedupeFragments: !!rawConfig.dedupeFragments, + allowEnumStringTypes: !!rawConfig.allowEnumStringTypes, + inlineFragmentTypes: rawConfig.inlineFragmentTypes ?? "inline", + emitLegacyCommonJSImports: + rawConfig.emitLegacyCommonJSImports === undefined + ? true + : !!rawConfig.emitLegacyCommonJSImports, + extractAllFieldsToTypes: rawConfig.extractAllFieldsToTypes ?? false, + printFieldsOnNewLines: rawConfig.printFieldsOnNewLines ?? false, + ...((additionalConfig || {}) as any), + }; + + this.scalars = {}; + for (const key of Object.keys(this.config.scalars || {})) { + this.scalars[key] = { + input: this.config.scalars[key]?.input?.type, + output: this.config.scalars[key]?.output?.type, + }; + } + + autoBind(this); + } + + protected getVisitorKindContextFromAncestors(ancestors: ASTNode[]): string[] { + if (!ancestors) { + return []; + } + + return ancestors.map((t) => t.kind).filter(Boolean); + } + + get config(): TPluginConfig { + return this._parsedConfig; + } + + public convertName( + node: ASTNode | string, + options?: BaseVisitorConvertOptions & ConvertOptions + ): string { + const useTypesPrefix = + typeof options?.useTypesPrefix === "boolean" + ? options.useTypesPrefix + : true; + const useTypesSuffix = + typeof options?.useTypesSuffix === "boolean" + ? options.useTypesSuffix + : true; + + let convertedName = ""; + + if (useTypesPrefix) { + convertedName += this.config.typesPrefix; + } + + convertedName += this.config.convert(node, options); + + if (useTypesSuffix) { + convertedName += this.config.typesSuffix; + } + + return convertedName; + } + + public getOperationSuffix( + node: FragmentDefinitionNode | OperationDefinitionNode | string, + operationType: string + ): string { + const { omitOperationSuffix = false, dedupeOperationSuffix = false } = this + .config as { [key: string]: any }; + const operationName = + typeof node === "string" ? node : node.name ? node.name.value : ""; + return omitOperationSuffix + ? "" + : dedupeOperationSuffix && + operationName.toLowerCase().endsWith(operationType.toLowerCase()) + ? "" + : operationType; + } + + public getFragmentSuffix(node: FragmentDefinitionNode | string): string { + return this.getOperationSuffix(node, "Fragment"); + } + + public getFragmentName(node: FragmentDefinitionNode | string): string { + return this.convertName(node, { + suffix: this.getFragmentSuffix(node), + useTypesPrefix: false, + }); + } + + public getFragmentVariableName( + node: FragmentDefinitionNode | string + ): string { + const { + omitOperationSuffix = false, + dedupeOperationSuffix = false, + fragmentVariableSuffix = "FragmentDoc", + fragmentVariablePrefix = "", + } = this.config as { [key: string]: any }; + + const fragmentName = typeof node === "string" ? node : node.name.value; + const suffix = omitOperationSuffix + ? "" + : dedupeOperationSuffix && + fragmentName.toLowerCase().endsWith("fragment") && + fragmentVariableSuffix.toLowerCase().startsWith("fragment") + ? fragmentVariableSuffix.substring("fragment".length) + : fragmentVariableSuffix; + + return this.convertName(node, { + prefix: fragmentVariablePrefix, + suffix, + useTypesPrefix: false, + }); + } + + protected getPunctuation(_declarationKind: DeclarationKind): string { + return ""; + } +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/declaration-kinds.ts b/packages/compiler/src/vendor/visitor-plugin-common/declaration-kinds.ts new file mode 100644 index 0000000..90f8179 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/declaration-kinds.ts @@ -0,0 +1,30 @@ +import { DeclarationKind, DeclarationKindConfig } from "./types"; + +export const DEFAULT_DECLARATION_KINDS: DeclarationKindConfig = { + directive: "type", + scalar: "type", + input: "type", + type: "type", + interface: "type", + arguments: "type", +}; + +export function normalizeDeclarationKind( + declarationKind?: DeclarationKind | DeclarationKindConfig +): DeclarationKindConfig { + if (typeof declarationKind === "string") { + return { + directive: declarationKind, + scalar: declarationKind, + input: declarationKind, + type: declarationKind, + interface: declarationKind, + arguments: declarationKind, + }; + } + + return { + ...DEFAULT_DECLARATION_KINDS, + ...declarationKind, + }; +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/imports.ts b/packages/compiler/src/vendor/visitor-plugin-common/imports.ts new file mode 100644 index 0000000..ace53d3 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/imports.ts @@ -0,0 +1,115 @@ +import { dirname, isAbsolute, join, relative, resolve, parse } from "path"; + +export type ImportDeclaration = { + outputPath: string; + importSource: ImportSource; + baseOutputDir: string; + baseDir: string; + typesImport: boolean; + emitLegacyCommonJSImports: boolean; +}; + +export type ImportSource = { + /** + * Source path, relative to the `baseOutputDir` + */ + path: string; + /** + * Namespace to import source as + */ + namespace?: string; + /** + * Entity names to import + */ + identifiers?: T[]; +}; + +export type FragmentImport = { + name: string; + kind: "type" | "document"; +}; + +export function generateFragmentImportStatement( + statement: ImportDeclaration, + kind: "type" | "document" | "both" +): string { + const { importSource: fragmentImportSource, ...rest } = statement; + const { identifiers, path, namespace } = fragmentImportSource; + const importSource: ImportSource = { + identifiers: identifiers! + .filter( + (fragmentImport) => kind === "both" || kind === fragmentImport.kind + ) + .map(({ name }) => name), + path, + namespace, + }; + return generateImportStatement({ + importSource, + ...rest, + typesImport: kind === "type" ? statement.typesImport : false, + }); +} + +export function generateImportStatement(statement: ImportDeclaration): string { + const { baseDir, importSource, outputPath, typesImport } = statement; + const importPath = resolveImportPath(baseDir, outputPath, importSource.path); + const importNames = importSource.identifiers?.length + ? `{ ${Array.from(new Set(importSource.identifiers)).join(", ")} }` + : "*"; + const importExtension = + importPath.startsWith("/") || importPath.startsWith(".") + ? statement.emitLegacyCommonJSImports + ? "" + : ".js" + : ""; + const importAlias = importSource.namespace + ? ` as ${importSource.namespace}` + : ""; + const importStatement = typesImport ? "import type" : "import"; + return `${importStatement} ${importNames}${importAlias} from '${importPath}${importExtension}';${ + importAlias ? "\n" : "" + }`; + // return `${importStatement} ${importNames}${importAlias} from '${importPath}';${importAlias ? '\n' : ''}`; +} + +function resolveImportPath( + baseDir: string, + outputPath: string, + sourcePath: string +) { + const shouldAbsolute = !sourcePath.startsWith("~"); + if (shouldAbsolute) { + const absGeneratedFilePath = resolve(baseDir, outputPath); + const absImportFilePath = resolve(baseDir, sourcePath); + return resolveRelativeImport(absGeneratedFilePath, absImportFilePath); + } + return sourcePath.replace(`~`, ""); +} + +export function resolveRelativeImport(from: string, to: string): string { + if (!isAbsolute(from)) { + throw new Error( + `Argument 'from' must be an absolute path, '${from}' given.` + ); + } + if (!isAbsolute(to)) { + throw new Error(`Argument 'to' must be an absolute path, '${to}' given.`); + } + return fixLocalFilePath(clearExtension(relative(dirname(from), to))); +} + +export function resolveImportSource( + source: string | ImportSource +): ImportSource { + return typeof source === "string" ? { path: source } : source; +} + +export function clearExtension(path: string): string { + const parsedPath = parse(path); + return join(parsedPath.dir, parsedPath.name).replace(/\\/g, "/"); +} + +export function fixLocalFilePath(path: string): string { + return path.startsWith("..") ? path : `./${path}`; +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/index.ts b/packages/compiler/src/vendor/visitor-plugin-common/index.ts new file mode 100644 index 0000000..198308a --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/index.ts @@ -0,0 +1,14 @@ +export * from "./avoid-optionals"; +export * from "./base-documents-visitor"; +export * from "./base-visitor"; +export * from "./declaration-kinds"; +export * from "./imports"; +export * from "./mappers"; +export * from "./naming"; +export * from "./scalars"; +export * from "./selection-set-processor/base"; +export * from "./selection-set-processor/pre-resolve-types"; +export * from "./selection-set-to-object"; +export * from "./types"; +export * from "./utils"; +export * from "./variables-to-object"; diff --git a/packages/compiler/src/vendor/visitor-plugin-common/mappers.ts b/packages/compiler/src/vendor/visitor-plugin-common/mappers.ts new file mode 100644 index 0000000..6169c02 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/mappers.ts @@ -0,0 +1,262 @@ +/* eslint-disable no-inner-declarations */ +import { + DirectiveArgumentAndInputFieldMappings, + ParsedDirectiveArgumentAndInputFieldMappings, +} from "./types"; + +export type ParsedMapper = InternalParsedMapper | ExternalParsedMapper; +export interface InternalParsedMapper { + isExternal: false; + type: string; +} +export interface ExternalParsedMapper { + isExternal: true; + type: string; + import: string; + source: string; + default: boolean; +} + +export function isExternalMapperType( + m: ParsedMapper +): m is ExternalParsedMapper { + return !!(m as ExternalParsedMapper).import; +} + +enum MapperKind { + Namespace, + Default, + Regular, +} + +interface Helpers { + items: string[]; + isNamespace: boolean; + isDefault: boolean; + hasAlias: boolean; +} + +function prepareLegacy(mapper: string): Helpers { + const isScoped = mapper.includes("\\#"); + if (mapper.includes("\\#")) { + mapper = mapper.replace("\\#", ""); + } + const items = mapper.split("#"); + const isNamespace = items.length === 3; + const isDefault = + items[1].trim() === "default" || items[1].startsWith("default "); + const hasAlias = items[1].includes(" as "); + const source = isScoped ? `#${items[0]}` : items[0]; + items[0] = source; + + return { + items, + isDefault, + isNamespace, + hasAlias, + }; +} + +function prepare(mapper: string): Helpers { + const isScoped = mapper.includes("\\#"); + if (mapper.includes("\\#")) { + mapper = mapper.replace("\\#", ""); + } + let [source, path] = mapper.split("#"); + const isNamespace = path.includes("."); + const isDefault = path.trim() === "default" || path.startsWith("default "); + const hasAlias = path.includes(" as "); + source = isScoped ? `#${source}` : source; + + return { + items: isNamespace ? [source, ...path.split(".")] : [source, path], + isDefault, + isNamespace, + hasAlias, + }; +} + +function isLegacyMode(mapper: string) { + if (mapper.includes("\\#")) { + mapper = mapper.replace("\\#", ""); + } + return mapper.split("#").length === 3; +} + +export function parseMapper( + mapper: string, + gqlTypeName: string | null = null, + suffix?: string +): ParsedMapper { + if (isExternalMapper(mapper)) { + const { isNamespace, isDefault, hasAlias, items } = isLegacyMode(mapper) + ? prepareLegacy(mapper) + : prepare(mapper); + + const mapperKind: MapperKind = isNamespace + ? MapperKind.Namespace + : isDefault + ? MapperKind.Default + : MapperKind.Regular; + + function handleAlias(isDefault = false) { + const [importedType, aliasType] = items[1].split(/\s+as\s+/); + + const type = maybeSuffix(aliasType); + + return { + importElement: isDefault ? type : `${importedType} as ${type}`, + type, + }; + } + + function maybeSuffix(type: string) { + if (suffix) { + return addSuffix(type, suffix); + } + + return type; + } + + function handle(): { + importElement: string; + type: string; + } { + switch (mapperKind) { + // ./my/module#Namespace#Identifier + case MapperKind.Namespace: { + const [, ns, identifier] = items; + + return { + type: `${ns}.${identifier}`, + importElement: ns, + }; + } + + case MapperKind.Default: { + // ./my/module#default as alias + if (hasAlias) { + return handleAlias(true); + } + + const type = maybeSuffix(String(gqlTypeName)); + + // ./my/module#default + return { + importElement: type, + type, + }; + } + + case MapperKind.Regular: { + // ./my/module#Identifier as alias + if (hasAlias) { + return handleAlias(); + } + + const identifier = items[1]; + + const type = maybeSuffix(identifier); + + // ./my/module#Identifier + return { + type, + importElement: suffix ? `${identifier} as ${type}` : type, + }; + } + } + } + + const { type, importElement } = handle(); + + return { + default: isDefault, + isExternal: true, + source: items[0], + type, + import: importElement.replace(/<(.*?)>/g, ""), + }; + } + + return { + isExternal: false, + type: mapper, + }; +} + +function addSuffix(element: string, suffix: string): string { + const generic = element.indexOf("<"); + if (generic === -1) { + return `${element}${suffix}`; + } + return `${element.slice(0, generic)}${suffix}${element.slice(generic)}`; +} + +export function isExternalMapper(value: string): boolean { + return value.includes("#"); +} + +export function transformDirectiveArgumentAndInputFieldMappings( + rawDirectiveArgumentAndInputFieldMappings: DirectiveArgumentAndInputFieldMappings, + directiveArgumentAndInputFieldMappingTypeSuffix?: string +): ParsedDirectiveArgumentAndInputFieldMappings { + const result: ParsedDirectiveArgumentAndInputFieldMappings = {}; + + for (const directive of Object.keys( + rawDirectiveArgumentAndInputFieldMappings + )) { + const mapperDef = rawDirectiveArgumentAndInputFieldMappings[directive]; + const parsedMapper = parseMapper( + mapperDef, + directive, + directiveArgumentAndInputFieldMappingTypeSuffix + ); + result[directive] = parsedMapper; + } + + return result; +} + +export function buildMapperImport( + source: string, + types: { identifier: string; asDefault?: boolean }[], + useTypeImports: boolean +): string | null { + if (!types || types.length === 0) { + return null; + } + + const defaultType = types.find((t) => t.asDefault === true); + let namedTypes = types.filter((t) => !t.asDefault); + + if (useTypeImports) { + if (defaultType) { + // default as Baz + namedTypes = [ + { identifier: `default as ${defaultType.identifier}` }, + ...namedTypes, + ]; + } + // { Foo, Bar as BarModel } + const namedImports = namedTypes.length + ? `{ ${namedTypes.map((t) => t.identifier).join(", ")} }` + : ""; + + // { default as Baz, Foo, Bar as BarModel } + return `import type ${[namedImports] + .filter(Boolean) + .join(", ")} from '${source}';`; + } + + // { Foo, Bar as BarModel } + const namedImports = namedTypes.length + ? `{ ${namedTypes.map((t) => t.identifier).join(", ")} }` + : ""; + // Baz + const defaultImport = defaultType ? defaultType.identifier : ""; + + // Baz, { Foo, Bar as BarModel } + return `import ${[defaultImport, namedImports] + .filter(Boolean) + .join(", ")} from '${source}';`; +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/naming.ts b/packages/compiler/src/vendor/visitor-plugin-common/naming.ts new file mode 100644 index 0000000..03a74b4 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/naming.ts @@ -0,0 +1,55 @@ +import type { ASTNode } from "graphql"; +import { ConvertFn } from "./types"; + +function getName(node: ASTNode | string | undefined): string | undefined { + if (node == null) { + return undefined; + } + + if (typeof node === "string") { + return node; + } + + switch (node.kind) { + case "OperationDefinition": + case "Variable": + case "Argument": + case "FragmentSpread": + case "FragmentDefinition": + case "ObjectField": + case "Directive": + case "NamedType": + case "ScalarTypeDefinition": + case "ObjectTypeDefinition": + case "FieldDefinition": + case "InputValueDefinition": + case "InterfaceTypeDefinition": + case "UnionTypeDefinition": + case "EnumTypeDefinition": + case "EnumValueDefinition": + case "InputObjectTypeDefinition": + case "DirectiveDefinition": { + return getName(node.name); + } + case "Name": { + return node.value; + } + case "Field": { + return getName(node.alias || node.name); + } + case "VariableDefinition": { + return getName(node.variable); + } + } + + return undefined; +} + +export const convertName: ConvertFn = (node, opts) => { + const prefix = opts?.prefix; + const suffix = opts?.suffix; + + const str = [prefix || "", getName(node), suffix || ""].join(""); + + return str; +}; diff --git a/packages/compiler/src/vendor/visitor-plugin-common/scalars.ts b/packages/compiler/src/vendor/visitor-plugin-common/scalars.ts new file mode 100644 index 0000000..edb9215 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/scalars.ts @@ -0,0 +1,24 @@ +import { NormalizedScalarsMap } from "./types"; + +export const DEFAULT_SCALARS: NormalizedScalarsMap = { + ID: { + input: "string", + output: "string", + }, + String: { + input: "string", + output: "string", + }, + Boolean: { + input: "boolean", + output: "boolean", + }, + Int: { + input: "number", + output: "number", + }, + Float: { + input: "number", + output: "number", + }, +}; diff --git a/packages/compiler/src/vendor/visitor-plugin-common/selection-set-processor/base.ts b/packages/compiler/src/vendor/visitor-plugin-common/selection-set-processor/base.ts new file mode 100644 index 0000000..5121236 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/selection-set-processor/base.ts @@ -0,0 +1,107 @@ +import type { + GraphQLInterfaceType, + GraphQLNamedType, + GraphQLObjectType, + GraphQLOutputType, + Location, +} from "graphql"; +import { + AvoidOptionalsConfig, + ConvertNameFn, + NormalizedScalarsMap, +} from "../types"; + +export type PrimitiveField = { isConditional: boolean; fieldName: string }; +export type PrimitiveAliasedFields = { + isConditional: boolean; + alias: string; + fieldName: string; +}; +export type LinkField = { + alias: string; + name: string; + type: string; + selectionSet: string; +}; +export type NameAndType = { name: string; type: string }; +export type ProcessResult = null | Array; + +export type SelectionSetProcessorConfig = { + namespacedImportName: string | null; + convertName: ConvertNameFn; + enumPrefix: boolean | null; + enumSuffix: boolean | null; + scalars: NormalizedScalarsMap; + formatNamedField( + name: string, + type?: GraphQLOutputType | GraphQLNamedType | null, + isConditional?: boolean, + isOptional?: boolean + ): string; + wrapTypeWithModifiers( + baseType: string, + type: GraphQLOutputType | GraphQLNamedType + ): string; + avoidOptionals?: AvoidOptionalsConfig | boolean; + printFieldsOnNewLines?: boolean; +}; + +export class BaseSelectionSetProcessor< + Config extends SelectionSetProcessorConfig +> { + typeCache = new Map>(); + + constructor(public config: Config) {} + + buildFieldsIntoObject(allObjectsMerged: string[]): string { + if (this.config.printFieldsOnNewLines) { + return `{\n ${allObjectsMerged.join(",\n ")}\n}`; + } + return `{ ${allObjectsMerged.join(", ")} }`; + } + + buildSelectionSetFromStrings(pieces: string[]): string { + if (pieces.length === 0) { + return null!; + } + if (pieces.length === 1) { + return pieces[0]; + } + return `(\n ${pieces.join(`\n & `)}\n)`; + } + + transformPrimitiveFields( + _schemaType: GraphQLObjectType | GraphQLInterfaceType, + _fields: PrimitiveField[], + _unsetTypes?: boolean + ): ProcessResult { + throw new Error( + `Please override "transformPrimitiveFields" as part of your BaseSelectionSetProcessor implementation!` + ); + } + + transformAliasesPrimitiveFields( + _schemaType: GraphQLObjectType | GraphQLInterfaceType, + _fields: PrimitiveAliasedFields[], + _unsetTypes?: boolean + ): ProcessResult { + throw new Error( + `Please override "transformAliasesPrimitiveFields" as part of your BaseSelectionSetProcessor implementation!` + ); + } + + transformLinkFields( + _fields: LinkField[], + _unsetTypes?: boolean + ): ProcessResult { + throw new Error( + `Please override "transformLinkFields" as part of your BaseSelectionSetProcessor implementation!` + ); + } + + transformTypenameField(_type: string, _name: string): ProcessResult { + throw new Error( + `Please override "transformTypenameField" as part of your BaseSelectionSetProcessor implementation!` + ); + } +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/selection-set-processor/pre-resolve-types.ts b/packages/compiler/src/vendor/visitor-plugin-common/selection-set-processor/pre-resolve-types.ts new file mode 100644 index 0000000..fa5ca38 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/selection-set-processor/pre-resolve-types.ts @@ -0,0 +1,157 @@ +import { + GraphQLInterfaceType, + GraphQLObjectType, + isEnumType, + isNonNullType, +} from "graphql/type/definition"; +import { + BaseSelectionSetProcessor, + LinkField, + PrimitiveAliasedFields, + PrimitiveField, + ProcessResult, + SelectionSetProcessorConfig, +} from "./base"; +import { getBaseType } from "../utils"; + +export class PreResolveTypesProcessor extends BaseSelectionSetProcessor { + transformTypenameField(type: string, name: string): ProcessResult { + return [ + { + type, + name, + }, + ]; + } + + transformPrimitiveFields( + schemaType: GraphQLObjectType | GraphQLInterfaceType, + fields: PrimitiveField[], + unsetTypes?: boolean + ): ProcessResult { + if (fields.length === 0) { + return []; + } + + return fields.map((field) => { + const fieldObj = schemaType.getFields()[field.fieldName]; + + const baseType = getBaseType(fieldObj.type); + let typeToUse = baseType.name; + + const innerType = + field.isConditional && isNonNullType(fieldObj.type) + ? fieldObj.type.ofType + : fieldObj.type; + + const name = this.config.formatNamedField( + field.fieldName, + innerType, + field.isConditional, + unsetTypes + ); + + if (unsetTypes) { + return { + name, + type: "never", + }; + } + + if (isEnumType(baseType)) { + typeToUse = + (this.config.namespacedImportName + ? `${this.config.namespacedImportName}.` + : "") + + this.config.convertName(baseType.name, { + useTypesPrefix: this.config.enumPrefix, + useTypesSuffix: this.config.enumSuffix, + }); + } else if (this.config.scalars[baseType.name]) { + typeToUse = this.config.scalars[baseType.name].output; + } + + const wrappedType = this.config.wrapTypeWithModifiers( + typeToUse, + fieldObj.type + ); + + return { + name, + type: wrappedType, + }; + }); + } + + transformAliasesPrimitiveFields( + schemaType: GraphQLObjectType | GraphQLInterfaceType, + fields: PrimitiveAliasedFields[], + unsetTypes?: boolean + ): ProcessResult { + if (fields.length === 0) { + return []; + } + + return fields.map((aliasedField) => { + if (aliasedField.fieldName === "__typename") { + const name = this.config.formatNamedField(aliasedField.alias, null); + return { + name, + type: `'${schemaType.name}'`, + }; + } + const fieldObj = schemaType.getFields()[aliasedField.fieldName]; + const baseType = getBaseType(fieldObj.type); + let typeToUse = + this.config.scalars[baseType.name]?.output || baseType.name; + + if (isEnumType(baseType)) { + typeToUse = + (this.config.namespacedImportName + ? `${this.config.namespacedImportName}.` + : "") + + this.config.convertName(baseType.name, { + useTypesPrefix: this.config.enumPrefix, + useTypesSuffix: this.config.enumSuffix, + }); + } + + const name = this.config.formatNamedField( + aliasedField.alias, + fieldObj.type, + aliasedField.isConditional, + unsetTypes + ); + if (unsetTypes) { + return { + type: "never", + name, + }; + } + + const wrappedType = this.config.wrapTypeWithModifiers( + typeToUse, + fieldObj.type + ); + + return { + name, + type: wrappedType, + }; + }); + } + + transformLinkFields( + fields: LinkField[], + unsetTypes?: boolean + ): ProcessResult { + if (fields.length === 0) { + return []; + } + + return fields.map((field) => ({ + name: field.alias || field.name, + type: unsetTypes ? "never" : field.selectionSet, + })); + } +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/selection-set-to-object.ts b/packages/compiler/src/vendor/visitor-plugin-common/selection-set-to-object.ts new file mode 100644 index 0000000..d27f854 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/selection-set-to-object.ts @@ -0,0 +1,1234 @@ +import { createHash } from "crypto"; +import { autoBind } from "../auto-bind"; +import type { + DirectiveNode, + FieldNode, + FragmentSpreadNode, + GraphQLSchema, + InlineFragmentNode, + SelectionNode, + SelectionSetNode, +} from "graphql"; +import { + SchemaMetaFieldDef, + TypeMetaFieldDef, +} from "graphql/type/introspection"; +import { Kind } from "graphql/language/kinds"; +import { + isInterfaceType, + isListType, + isNonNullType, + isObjectType, + GraphQLField, + GraphQLNamedType, + GraphQLObjectType, + GraphQLOutputType, + isUnionType, +} from "graphql/type/definition"; +import { isTypeSubTypeOf } from "graphql/utilities/typeComparators"; +import { ParsedDocumentsConfig } from "./base-documents-visitor"; +import { BaseVisitorConvertOptions } from "./base-visitor"; +import { + BaseSelectionSetProcessor, + LinkField, + NameAndType, + PrimitiveAliasedFields, + PrimitiveField, + ProcessResult, +} from "./selection-set-processor/base"; +import { + ConvertNameFn, + FragmentDirectives, + GetFragmentSuffixFn, + LoadedFragment, + NormalizedScalarsMap, +} from "./types"; +import { + DeclarationBlock, + DeclarationBlockConfig, + getBaseType, + getFieldNames, + getFieldNodeNameValue, + getPossibleTypes, + getRootTypes, + hasConditionalDirectives, + hasIncrementalDeliveryDirectives, + mergeSelectionSets, + separateSelectionSet, +} from "./utils"; + +type FragmentSpreadUsage = { + fragmentName: string; + typeName: string; + onType: string; + selectionNodes: Array; + fragmentDirectives?: DirectiveNode[]; +}; + +interface DependentType { + name: string; + content: string; + isUnionType?: boolean; +} + +type CollectedFragmentNode = ( + | SelectionNode + | FragmentSpreadUsage + | DirectiveNode +) & + FragmentDirectives; +type GroupedStringifiedTypes = Record< + string, + Array +>; + +const operationTypes: string[] = ["Query", "Mutation", "Subscription"]; + +function isMetadataFieldName(name: string) { + return ["__schema", "__type"].includes(name); +} + +const metadataFieldMap: Record> = { + __schema: SchemaMetaFieldDef, + __type: TypeMetaFieldDef, +}; + +export class SelectionSetToObject< + Config extends ParsedDocumentsConfig = ParsedDocumentsConfig +> { + protected _primitiveFields: PrimitiveField[] = []; + protected _primitiveAliasedFields: PrimitiveAliasedFields[] = []; + protected _linksFields: LinkField[] = []; + protected _queriedForTypename = false; + + constructor( + protected _processor: BaseSelectionSetProcessor, + protected _scalars: NormalizedScalarsMap, + protected _schema: GraphQLSchema, + protected _convertName: ConvertNameFn, + protected _getFragmentSuffix: GetFragmentSuffixFn, + protected _loadedFragments: LoadedFragment[], + protected _config: Config, + protected _parentSchemaType?: GraphQLNamedType, + protected _selectionSet?: SelectionSetNode + ) { + autoBind(this); + } + + public createNext( + parentSchemaType: GraphQLNamedType, + selectionSet: SelectionSetNode + ): SelectionSetToObject { + return new SelectionSetToObject( + this._processor, + this._scalars, + this._schema, + this._convertName.bind(this), + this._getFragmentSuffix.bind(this), + this._loadedFragments, + this._config, + parentSchemaType, + selectionSet + ); + } + + /** + * traverse the inline fragment nodes recursively for collecting the selectionSets on each type + */ + _collectInlineFragments( + parentType: GraphQLNamedType, + nodes: Array, + types: Map> + ): void { + if (isListType(parentType) || isNonNullType(parentType)) { + return this._collectInlineFragments( + parentType.ofType as GraphQLNamedType, + nodes, + types + ); + } + if (isObjectType(parentType)) { + for (const node of nodes) { + const typeOnSchema = node.typeCondition + ? this._schema.getType(node.typeCondition.name.value) + : parentType; + const { fields, inlines, spreads } = separateSelectionSet( + node.selectionSet.selections + ); + const spreadsUsage = this.buildFragmentSpreadsUsage(spreads); + const directives = (node.directives as DirectiveNode[]) || undefined; + + // When we collect the selection sets of inline fragments we need to + // make sure directives on the inline fragments are stored in a way + // that can be associated back to the fields in the fragment, to + // support things like making those fields optional when deferring a + // fragment (using @defer). + const fieldsWithFragmentDirectives: CollectedFragmentNode[] = + fields.map((field) => ({ + ...field, + fragmentDirectives: field.fragmentDirectives || directives, + })); + + if (isObjectType(typeOnSchema)) { + this._appendToTypeMap( + types, + typeOnSchema.name, + fieldsWithFragmentDirectives + ); + this._appendToTypeMap( + types, + typeOnSchema.name, + spreadsUsage[typeOnSchema.name] + ); + this._appendToTypeMap(types, typeOnSchema.name, directives); + this._collectInlineFragments(typeOnSchema, inlines, types); + } else if ( + isInterfaceType(typeOnSchema) && + parentType.getInterfaces().includes(typeOnSchema) + ) { + this._appendToTypeMap(types, parentType.name, fields); + this._appendToTypeMap( + types, + parentType.name, + spreadsUsage[parentType.name] + ); + this._collectInlineFragments(typeOnSchema, inlines, types); + } + } + } else if (isInterfaceType(parentType)) { + const possibleTypes = getPossibleTypes(this._schema, parentType); + + for (const node of nodes) { + const schemaType = node.typeCondition + ? this._schema.getType(node.typeCondition.name.value) + : parentType; + const { fields, inlines, spreads } = separateSelectionSet( + node.selectionSet.selections + ); + const spreadsUsage = this.buildFragmentSpreadsUsage(spreads); + + if ( + isObjectType(schemaType) && + possibleTypes.find( + (possibleType) => possibleType.name === schemaType.name + ) + ) { + this._appendToTypeMap(types, schemaType.name, fields); + this._appendToTypeMap( + types, + schemaType.name, + spreadsUsage[schemaType.name] + ); + this._collectInlineFragments(schemaType, inlines, types); + } else if ( + isInterfaceType(schemaType) && + schemaType.name === parentType.name + ) { + for (const possibleType of possibleTypes) { + this._appendToTypeMap(types, possibleType.name, fields); + this._appendToTypeMap( + types, + possibleType.name, + spreadsUsage[possibleType.name] + ); + this._collectInlineFragments(schemaType, inlines, types); + } + } else { + // it must be an interface type that is spread on an interface field + for (const possibleType of possibleTypes) { + if (!node.typeCondition) { + throw new Error( + "Invalid state. Expected type condition for interface spread on a interface field." + ); + } + const fragmentSpreadType = this._schema.getType( + node.typeCondition.name.value + ); + // the field should only be added to the valid selections + // in case the possible type actually implements the given interface + if ( + isTypeSubTypeOf(this._schema, possibleType, fragmentSpreadType!) + ) { + this._appendToTypeMap(types, possibleType.name, fields); + this._appendToTypeMap( + types, + possibleType.name, + spreadsUsage[possibleType.name] + ); + } + } + } + } + } else if (isUnionType(parentType)) { + const possibleTypes = parentType.getTypes(); + + for (const node of nodes) { + const schemaType = node.typeCondition + ? this._schema.getType(node.typeCondition.name.value) + : parentType; + const { fields, inlines, spreads } = separateSelectionSet( + node.selectionSet.selections + ); + const spreadsUsage = this.buildFragmentSpreadsUsage(spreads); + + if ( + isObjectType(schemaType) && + possibleTypes.find( + (possibleType) => possibleType.name === schemaType.name + ) + ) { + this._appendToTypeMap(types, schemaType.name, fields); + this._appendToTypeMap( + types, + schemaType.name, + spreadsUsage[schemaType.name] + ); + this._collectInlineFragments(schemaType, inlines, types); + } else if (isInterfaceType(schemaType)) { + const possibleInterfaceTypes = getPossibleTypes( + this._schema, + schemaType + ); + + for (const possibleType of possibleTypes) { + if ( + possibleInterfaceTypes.find( + (possibleInterfaceType) => + possibleInterfaceType.name === possibleType.name + ) + ) { + this._appendToTypeMap(types, possibleType.name, fields); + this._appendToTypeMap( + types, + possibleType.name, + spreadsUsage[possibleType.name] + ); + this._collectInlineFragments(schemaType, inlines, types); + } + } + } else { + for (const possibleType of possibleTypes) { + this._appendToTypeMap(types, possibleType.name, fields); + this._appendToTypeMap( + types, + possibleType.name, + spreadsUsage[possibleType.name] + ); + } + } + } + } + } + + protected _createInlineFragmentForFieldNodes( + parentType: GraphQLNamedType, + fieldNodes: FieldNode[] + ): InlineFragmentNode { + return { + kind: Kind.INLINE_FRAGMENT, + typeCondition: { + kind: Kind.NAMED_TYPE, + name: { + kind: Kind.NAME, + value: parentType.name, + }, + }, + directives: [], + selectionSet: { + kind: Kind.SELECTION_SET, + selections: fieldNodes, + }, + }; + } + + /** + * The `buildFragmentSpreadsUsage` method is used to collect fields from fragment spreads in the selection set. + * It creates a record of fragment spread usages, which includes the fragment name, type name, and the selection nodes + * inside the fragment. + */ + protected buildFragmentSpreadsUsage( + spreads: FragmentSpreadNode[] + ): Record { + const selectionNodesByTypeName: Record = {}; + + for (const spread of spreads) { + const fragmentSpreadObject = this._loadedFragments.find( + (lf) => lf.name === spread.name.value + ); + + if (fragmentSpreadObject) { + const schemaType = this._schema.getType(fragmentSpreadObject.onType); + const possibleTypesForFragment = getPossibleTypes( + this._schema, + schemaType! + ); + + for (const possibleType of possibleTypesForFragment) { + const fragmentSuffix = this._getFragmentSuffix(spread.name.value); + const usage = this.buildFragmentTypeName( + spread.name.value, + fragmentSuffix, + possibleTypesForFragment.length === 1 ? null! : possibleType.name + ); + + selectionNodesByTypeName[possibleType.name] ||= []; + + selectionNodesByTypeName[possibleType.name].push({ + fragmentName: spread.name.value, + typeName: usage, + onType: fragmentSpreadObject.onType, + selectionNodes: [ + ...fragmentSpreadObject.node.selectionSet.selections, + ], + fragmentDirectives: [...spread.directives!], + }); + } + } + } + + return selectionNodesByTypeName; + } + + protected flattenSelectionSet( + selections: ReadonlyArray, + parentSchemaType?: GraphQLObjectType + ): Map> { + const selectionNodesByTypeName = new Map< + string, + Array + >(); + const inlineFragmentSelections: InlineFragmentNode[] = []; + const fieldNodes: FieldNode[] = []; + const fragmentSpreads: FragmentSpreadNode[] = []; + for (const selection of selections) { + switch (selection.kind) { + case Kind.FIELD: + fieldNodes.push(selection); + break; + case Kind.INLINE_FRAGMENT: + inlineFragmentSelections.push(selection); + break; + case Kind.FRAGMENT_SPREAD: + fragmentSpreads.push(selection); + break; + } + } + + if (fieldNodes.length) { + inlineFragmentSelections.push( + this._createInlineFragmentForFieldNodes( + parentSchemaType ?? this._parentSchemaType!, + fieldNodes + ) + ); + } + + this._collectInlineFragments( + parentSchemaType ?? this._parentSchemaType!, + inlineFragmentSelections, + selectionNodesByTypeName + ); + const fragmentsUsage = this.buildFragmentSpreadsUsage(fragmentSpreads); + + for (const [typeName, records] of Object.entries(fragmentsUsage)) { + this._appendToTypeMap(selectionNodesByTypeName, typeName, records); + } + + return selectionNodesByTypeName; + } + + private _appendToTypeMap( + types: Map>, + typeName: string, + nodes: Array + ): void { + if (!types.has(typeName)) { + types.set(typeName, []); + } + + if (nodes && nodes.length > 0) { + types.get(typeName)!.push(...nodes); + } + } + + protected _buildGroupedSelections(parentName: string): { + grouped: GroupedStringifiedTypes; + dependentTypes: DependentType[]; + mustAddEmptyObject: boolean; + } { + if ( + !this._selectionSet?.selections || + this._selectionSet.selections.length === 0 + ) { + return { grouped: {}, mustAddEmptyObject: true, dependentTypes: [] }; + } + + const selectionNodesByTypeName = this.flattenSelectionSet( + this._selectionSet.selections + ); + + // in case there is not a selection for each type, we need to add a empty type. + let mustAddEmptyObject = false; + + const possibleTypes = getPossibleTypes( + this._schema, + this._parentSchemaType! + ); + + const dependentTypes: DependentType[] = []; + if ( + !this._config.mergeFragmentTypes || + this._config.inlineFragmentTypes === "mask" + ) { + const grouped = possibleTypes.reduce( + (prev, type) => { + const typeName = type.name; + const schemaType = this._schema.getType(typeName); + + if (!isObjectType(schemaType)) { + throw new TypeError( + `Invalid state! Schema type ${typeName} is not a valid GraphQL object!` + ); + } + + const allNodes = selectionNodesByTypeName.get(typeName) || []; + + prev[typeName] ||= []; + + // incrementalNodes are the ones flagged with @defer, meaning they become nullable + const { incrementalNodes, selectionNodes, fragmentSpreads } = + allNodes.reduce<{ + selectionNodes: (SelectionNode | FragmentSpreadUsage)[]; + incrementalNodes: FragmentSpreadUsage[]; + fragmentSpreads: string[]; + }>( + (acc, node) => { + if ( + "fragmentDirectives" in node && + hasIncrementalDeliveryDirectives(node.fragmentDirectives!) + ) { + acc.incrementalNodes.push(node); + } else { + acc.selectionNodes.push(node); + } + return acc; + }, + { selectionNodes: [], incrementalNodes: [], fragmentSpreads: [] } + ); + + const { fields, dependentTypes: subDependentTypes } = + this.buildSelectionSet(schemaType, selectionNodes, { + parentFieldName: this.buildParentFieldName(typeName, parentName), + }); + const transformedSet = this.selectionSetStringFromFields(fields); + + if (transformedSet) { + prev[typeName].push(transformedSet); + } + dependentTypes.push(...subDependentTypes); + if (!transformedSet && !fragmentSpreads.length) { + mustAddEmptyObject = true; + } + + for (const incrementalNode of incrementalNodes) { + if ( + this._config.inlineFragmentTypes === "mask" && + "fragmentName" in incrementalNode + ) { + const { + fields: incrementalFields, + dependentTypes: incrementalDependentTypes, + } = this.buildSelectionSet(schemaType, [incrementalNode], { + unsetTypes: true, + parentFieldName: parentName, + }); + const incrementalSet = + this.selectionSetStringFromFields(incrementalFields); + prev[typeName].push(incrementalSet!); + dependentTypes.push(...incrementalDependentTypes); + + continue; + } + const { + fields: initialFields, + dependentTypes: initialDependentTypes, + } = this.buildSelectionSet(schemaType, [incrementalNode], { + parentFieldName: parentName, + }); + + const { + fields: subsequentFields, + dependentTypes: subsequentDependentTypes, + } = this.buildSelectionSet(schemaType, [incrementalNode], { + unsetTypes: true, + parentFieldName: parentName, + }); + + const initialSet = this.selectionSetStringFromFields(initialFields); + const subsequentSet = + this.selectionSetStringFromFields(subsequentFields); + dependentTypes.push( + ...initialDependentTypes, + ...subsequentDependentTypes + ); + prev[typeName].push({ union: [initialSet!, subsequentSet!] }); + } + + return prev; + }, + {} + ); + + return { grouped, mustAddEmptyObject, dependentTypes }; + } + // Accumulate a map of selected fields to the typenames that + // share the exact same selected fields. When we find multiple + // typenames with the same set of fields, we can collapse the + // generated type to the selected fields and a string literal + // union of the typenames. + // + // E.g. { + // __typename: "foo" | "bar"; + // shared: string; + // } + const grouped = possibleTypes.reduce< + Record< + string, + { + fields: (string | NameAndType)[]; + types: { name: string; type: string }[]; + } + > + >((prev, type) => { + const typeName = type.name; + const schemaType = this._schema.getType(typeName); + + if (!isObjectType(schemaType)) { + throw new TypeError( + `Invalid state! Schema type ${typeName} is not a valid GraphQL object!` + ); + } + + const selectionNodes = selectionNodesByTypeName.get(typeName) || []; + + const { + typeInfo, + fields, + dependentTypes: subDependentTypes, + } = this.buildSelectionSet(schemaType, selectionNodes, { + parentFieldName: this.buildParentFieldName(typeName, parentName), + }); + dependentTypes.push(...subDependentTypes); + + const key = this.selectionSetStringFromFields(fields)!; + prev[key] = { + fields, + types: [ + ...(prev[key]?.types ?? []), + typeInfo || { name: "", type: type.name }, + ].filter(Boolean), + }; + + return prev; + }, {}); + + // For every distinct set of fields, create the corresponding + // string literal union of typenames. + const compacted = Object.keys(grouped).reduce>( + (acc, key) => { + const typeNames = grouped[key].types.map((t) => t.type); + // Don't create very large string literal unions. TypeScript + // will stop comparing some nested union types types when + // they contain props with more than some number of string + // literal union members (testing with TS 4.5 stops working + // at 25 for a naive test case: + // https://www.typescriptlang.org/play?ts=4.5.4&ssl=29&ssc=10&pln=29&pc=1#code/C4TwDgpgBAKg9nAMgQwE4HNoF4BQV9QA+UA3ngRQJYB21EqAXDsQEQCMLzULATJ6wGZ+3ACzCWAVnEA2cQHZxADnEBOcWwAM6jl3Z9dbIQbEGpB2QYUHlBtbp5b7O1j30ujLky7Os4wABb0nAC+ODigkFAAQlBYUOT4xGQUVLT0TKzO3G7cHqLiPtwWrFasNqx2mY6ZWXrqeexe3GyF7MXNpc3lzZXZ1dm1ruI8DTxNvGahFEkJKTR0jLMpRNx+gaicy6E4APQ7AALAAM4AtJTo1HCoEDgANhDAUMgMsAgoGNikwQDcdw9QACMXjE4shfmEItAAGI0bCzGbLfDzdIGYbiBrjVrtFidFjdFi9dj9di1Ng5dgNNjjFrqbFsXFsfFsQkOYaDckjYbjNZBHDbPaHU7nS7XP6PZBsF4wuixL6-e6PAGS6KyiXfIA + const max_types = 20; + for (let i = 0; i < typeNames.length; i += max_types) { + const selectedTypes = typeNames.slice(i, i + max_types); + const typenameUnion = grouped[key].types[0].name + ? this._processor.transformTypenameField( + selectedTypes.join(" | "), + grouped[key].types[0].name + )! + : []; + const transformedSet = this.selectionSetStringFromFields([ + ...typenameUnion, + ...grouped[key].fields, + ]); + + // The keys here will be used to generate intermediary + // fragment names. To avoid blowing up the type name on large + // unions, calculate a stable hash here instead. + // + // Also use fragment hashing if skipTypename is true, since we + // then don't have a typename for naming the fragment. + acc[ + selectedTypes.length <= 3 + ? // Remove quote marks to produce a valid type name + selectedTypes.map((t) => t.replace(/'/g, "")).join("_") + : createHash("sha256") + .update(selectedTypes.join() || transformedSet || "") + // Remove invalid characters to produce a valid type name + .digest("base64") + .replace(/[=+/]/g, "") + ] = [transformedSet!]; + } + return acc; + }, + {} + ); + + return { grouped: compacted, mustAddEmptyObject, dependentTypes }; + } + + protected selectionSetStringFromFields( + fields: (string | NameAndType)[] + ): string | null { + const allStrings = fields.filter( + (f: string | NameAndType): f is string => typeof f === "string" + ); + const allObjects = fields + .filter( + (f: string | NameAndType): f is NameAndType => typeof f !== "string" + ) + .map((t) => `${t.name}: ${t.type}`); + const mergedObjects = allObjects.length + ? this._processor.buildFieldsIntoObject(allObjects) + : null; + const transformedSet = this._processor.buildSelectionSetFromStrings( + [...allStrings, mergedObjects!].filter(Boolean) + ); + return transformedSet; + } + + protected buildSelectionSet( + parentSchemaType: GraphQLObjectType, + selectionNodes: Array, + options: { unsetTypes?: boolean; parentFieldName?: string } + ) { + const primitiveFields = new Map(); + const primitiveAliasFields = new Map(); + const linkFieldSelectionSets = new Map< + string, + { + selectedFieldType: GraphQLOutputType; + field: FieldNode; + } + >(); + let requireTypename = false; + + // usages via fragment typescript type + const fragmentsSpreadUsages: string[] = []; + + // ensure we mutate no function params + selectionNodes = [...selectionNodes]; + let inlineFragmentConditional = false; + for (const selectionNode of selectionNodes) { + if ("kind" in selectionNode) { + if (selectionNode.kind === "Field") { + if (selectionNode.selectionSet) { + let selectedField: GraphQLField; + + const fields = parentSchemaType.getFields(); + selectedField = fields[selectionNode.name.value]; + + if (isMetadataFieldName(selectionNode.name.value)) { + selectedField = metadataFieldMap[selectionNode.name.value]; + } + + if (!selectedField) { + continue; + } + + const fieldName = getFieldNodeNameValue(selectionNode); + let linkFieldNode = linkFieldSelectionSets.get(fieldName); + if (linkFieldNode) { + linkFieldNode = { + ...linkFieldNode, + field: { + ...linkFieldNode.field, + selectionSet: mergeSelectionSets( + linkFieldNode.field.selectionSet!, + selectionNode.selectionSet + ), + }, + }; + } else { + linkFieldNode = { + selectedFieldType: selectedField.type, + field: selectionNode, + }; + } + linkFieldSelectionSets.set(fieldName, linkFieldNode); + } else if (selectionNode.alias) { + primitiveAliasFields.set(selectionNode.alias.value, selectionNode); + } else if (selectionNode.name.value === "__typename") { + requireTypename = true; + } else { + primitiveFields.set(selectionNode.name.value, selectionNode); + } + } else if (selectionNode.kind === "Directive") { + if (["skip", "include"].includes(selectionNode?.name?.value)) { + inlineFragmentConditional = true; + } + } else { + throw new TypeError("Unexpected type."); + } + continue; + } + + if ( + this._config.inlineFragmentTypes === "combine" || + this._config.inlineFragmentTypes === "mask" + ) { + fragmentsSpreadUsages.push(selectionNode.typeName); + continue; + } + + // Handle Fragment Spreads by generating inline types. + + const fragmentType = this._schema.getType(selectionNode.onType); + + if (fragmentType == null) { + throw new TypeError( + `Unexpected error: Type ${selectionNode.onType} does not exist within schema.` + ); + } + + if ( + parentSchemaType.name === selectionNode.onType || + parentSchemaType + .getInterfaces() + .find((iinterface) => iinterface.name === selectionNode.onType) != + null || + (isUnionType(fragmentType) && + fragmentType + .getTypes() + .find((objectType) => objectType.name === parentSchemaType.name)) + ) { + // also process fields from fragment that apply for this parentType + const flatten = this.flattenSelectionSet( + selectionNode.selectionNodes, + parentSchemaType + ); + const typeNodes = flatten.get(parentSchemaType.name) ?? []; + selectionNodes.push(...typeNodes); + for (const iinterface of parentSchemaType.getInterfaces()) { + const typeNodes = flatten.get(iinterface.name) ?? []; + selectionNodes.push(...typeNodes); + } + } + } + + const linkFields: LinkField[] = []; + const linkFieldsInterfaces: DependentType[] = []; + for (const { + field, + selectedFieldType, + } of linkFieldSelectionSets.values()) { + const realSelectedFieldType = getBaseType(selectedFieldType as any); + const selectionSet = this.createNext( + realSelectedFieldType, + field.selectionSet! + ); + const fieldName = field.alias?.value ?? field.name.value; + const selectionSetObjects = selectionSet.transformSelectionSet( + options.parentFieldName + ? `${options.parentFieldName}_${fieldName}` + : fieldName + ); + + linkFieldsInterfaces.push(...selectionSetObjects.dependentTypes); + const isConditional = + hasConditionalDirectives(field) || inlineFragmentConditional; + const isOptional = options.unsetTypes; + linkFields.push({ + alias: field.alias + ? this._processor.config.formatNamedField( + field.alias.value, + selectedFieldType, + isConditional, + isOptional + ) + : undefined, + name: this._processor.config.formatNamedField( + field.name.value, + selectedFieldType, + isConditional, + isOptional + ), + type: realSelectedFieldType.name, + selectionSet: this._processor.config.wrapTypeWithModifiers( + selectionSetObjects.mergedTypeString.split(`\n`).join(`\n `), + selectedFieldType + ), + }); + } + + const typeInfoField = this.buildTypeNameField( + parentSchemaType, + this._config.nonOptionalTypename, + this._config.addTypename, + requireTypename, + this._config.skipTypeNameForRoot + ); + const transformed: ProcessResult = [ + // Only add the typename field if we're not merging fragment + // types. If we are merging, we need to wait until we know all + // the involved typenames. + ...(typeInfoField && + (!this._config.mergeFragmentTypes || + this._config.inlineFragmentTypes === "mask") + ? this._processor.transformTypenameField( + typeInfoField.type, + typeInfoField.name + )! + : []), + ...this._processor.transformPrimitiveFields( + parentSchemaType, + Array.from(primitiveFields.values()).map((field) => ({ + isConditional: hasConditionalDirectives(field), + fieldName: field.name.value, + })), + options.unsetTypes + )!, + ...this._processor.transformAliasesPrimitiveFields( + parentSchemaType, + Array.from(primitiveAliasFields.values()).map((field) => ({ + alias: field.alias!.value, + fieldName: field.name.value, + isConditional: hasConditionalDirectives(field), + })), + options.unsetTypes + )!, + ...this._processor.transformLinkFields(linkFields, options.unsetTypes)!, + ].filter(Boolean); + + const allStrings: string[] = transformed.filter( + (t) => typeof t === "string" + ) as string[]; + + const allObjectsMerged: string[] = transformed + .filter((t): t is NameAndType => typeof t !== "string") + .map((t: NameAndType) => `${t.name}: ${t.type}`); + + let mergedObjectsAsString: string; + + if (allObjectsMerged.length > 0) { + mergedObjectsAsString = + this._processor.buildFieldsIntoObject(allObjectsMerged); + } + + const fields = [...allStrings, mergedObjectsAsString!].filter(Boolean); + + if (fragmentsSpreadUsages.length) { + if (this._config.inlineFragmentTypes === "combine") { + fields.push(...fragmentsSpreadUsages); + } else if (this._config.inlineFragmentTypes === "mask") { + fields.push( + `{ ' $fragmentRefs'?: { ${fragmentsSpreadUsages + .map( + (name) => + `'${name}': ${ + options.unsetTypes ? `Incremental<${name}>` : name + }` + ) + .join(`;`)} } }` + ); + } + } + + return { + typeInfo: typeInfoField, + fields, + dependentTypes: linkFieldsInterfaces, + }; + } + + protected buildTypeNameField( + type: GraphQLObjectType, + nonOptionalTypename: boolean = this._config.nonOptionalTypename, + addTypename: boolean = this._config.addTypename, + queriedForTypename: boolean = this._queriedForTypename, + skipTypeNameForRoot: boolean = this._config.skipTypeNameForRoot + ): { name: string; type: string } { + const rootTypes = getRootTypes(this._schema); + if (rootTypes.has(type) && skipTypeNameForRoot && !queriedForTypename) { + return null!; + } + + if (nonOptionalTypename || addTypename || queriedForTypename) { + const optionalTypename = !queriedForTypename && !nonOptionalTypename; + return { + name: `${this._processor.config.formatNamedField("__typename")}${ + optionalTypename ? "?" : "" + }`, + type: `'${type.name}'`, + }; + } + + return null!; + } + + protected getUnknownType(): string { + return "never"; + } + + protected getEmptyObjectType(): string { + return `{}`; + } + + private getEmptyObjectTypeString(mustAddEmptyObject: boolean): string { + return mustAddEmptyObject ? this.getEmptyObjectType() : ``; + } + + public transformSelectionSet(fieldName: string) { + const possibleTypesList = getPossibleTypes( + this._schema, + this._parentSchemaType! + ); + const possibleTypes = possibleTypesList.map((v) => v.name).sort(); + const fieldSelections = [ + ...getFieldNames({ + selections: this._selectionSet!.selections, + loadedFragments: this._loadedFragments, + }), + ].sort(); + + // Optimization: Do not create new dependentTypes if fragment typename exists in cache + // 2-layer cache: LOC => Field Selection Type Combination => cachedTypeString + const objMap = + this._processor.typeCache.get(this._selectionSet!.loc!) ?? + new Map(); + this._processor.typeCache.set(this._selectionSet!.loc!, objMap); + + const cacheHashKey = `${fieldSelections.join(",")} @ ${possibleTypes.join( + "|" + )}`; + const [cachedTypeString] = objMap.get(cacheHashKey) ?? []; + if (cachedTypeString) { + // reuse previously generated type, as it is identical + return { + mergedTypeString: cachedTypeString, + // there are no new dependent types, as this is a nth use of the same type + dependentTypes: [], + }; + } + const result = this.transformSelectionSetUncached(fieldName); + objMap.set(cacheHashKey, [result.mergedTypeString, fieldName]); + if (this._selectionSet?.loc) { + this._processor.typeCache.set(this._selectionSet.loc, objMap); + } + return result; + } + + private transformSelectionSetUncached(fieldName: string): { + mergedTypeString: string; + dependentTypes: DependentType[]; + isUnionType?: boolean; + } { + const { + grouped, + mustAddEmptyObject, + dependentTypes: subDependentTypes, + } = this._buildGroupedSelections(fieldName); + + // This might happen in case we have an interface, that is being queries, without any GraphQL + // "type" that implements it. It will lead to a runtime error, but we aim to try to reflect that in + // build time as well. + if (Object.keys(grouped).length === 0) { + return { + mergedTypeString: this.getUnknownType(), + dependentTypes: subDependentTypes, + }; + } + + const dependentTypes = Object.keys(grouped) + .map((typeName) => { + const relevant = grouped[typeName].filter(Boolean); + return relevant.map((objDefinition) => { + const name = fieldName ? `${fieldName}_${typeName}` : typeName; + return { + name, + content: + typeof objDefinition === "string" + ? objDefinition + : objDefinition.union.join(" | "), + isUnionType: !!( + typeof objDefinition !== "string" && + objDefinition.union.length > 1 + ), + }; + }); + }) + .filter((pairs) => pairs.length > 0); + + const typeParts = [ + ...dependentTypes.map((pair) => + pair + .map(({ name, content, isUnionType }) => + // unions need to be wrapped, as intersections have higher precedence + this._config.extractAllFieldsToTypes + ? name + : isUnionType + ? `(${content})` + : content + ) + .join(" & ") + ), + this.getEmptyObjectTypeString(mustAddEmptyObject), + ].filter(Boolean); + + const content = typeParts.join(" | "); + + if (typeParts.length > 1 && this._config.extractAllFieldsToTypes) { + return { + mergedTypeString: fieldName, + dependentTypes: [ + ...subDependentTypes, + ...dependentTypes.flat(1), + { name: fieldName, content, isUnionType: true }, + ], + }; + } + + return { + mergedTypeString: content, + dependentTypes: [...subDependentTypes, ...dependentTypes.flat(1)], + isUnionType: typeParts.length > 1, + }; + } + + public transformFragmentSelectionSetToTypes( + fragmentName: string, + fragmentSuffix: string, + declarationBlockConfig: DeclarationBlockConfig + ): string { + const mergedTypeString = this.buildFragmentTypeName( + fragmentName, + fragmentSuffix + ); + const { grouped, dependentTypes } = + this._buildGroupedSelections(mergedTypeString); + + const subTypes: DependentType[] = Object.keys(grouped).flatMap( + (typeName) => { + const possibleFields = grouped[typeName].filter(Boolean); + const declarationName = this.buildFragmentTypeName( + fragmentName, + fragmentSuffix, + typeName + ); + + if (possibleFields.length === 0) { + if (!this._config.addTypename) { + return [ + { name: declarationName, content: this.getEmptyObjectType() }, + ]; + } + + return []; + } + + const flatFields = possibleFields.map((selectionObject) => { + if (typeof selectionObject === "string") + return { value: selectionObject }; + return { + value: selectionObject.union.join(" | "), + isUnionType: true, + }; + }); + + const content = + flatFields.length > 1 + ? flatFields + .map(({ value, isUnionType }) => + isUnionType ? `(${value})` : value + ) + .join(" & ") + : flatFields.map(({ value }) => value).join(" & "); + return { + name: declarationName, + content, + isUnionType: false, + }; + } + ); + + const fragmentMaskPartial = + this._config.inlineFragmentTypes === "mask" + ? ` & { ' $fragmentName'?: '${mergedTypeString}' }` + : ""; + + // TODO: unify with line 308 from base-documents-visitor + const dependentTypesContent = this._config.extractAllFieldsToTypes + ? dependentTypes.map( + (i) => + new DeclarationBlock(declarationBlockConfig) + .export(true) + .asKind("type") + .withName(i.name) + .withContent(i.content).string + ) + : []; + + if (subTypes.length === 1) { + return [ + ...dependentTypesContent, + new DeclarationBlock(declarationBlockConfig) + .export() + .asKind("type") + .withName(mergedTypeString) + .withContent(subTypes[0].content + fragmentMaskPartial).string, + ].join("\n"); + } + + return [ + ...dependentTypesContent, + ...subTypes.map( + (t) => + new DeclarationBlock(declarationBlockConfig) + .export(this._config.exportFragmentSpreadSubTypes) + .asKind("type") + .withName(t.name) + .withContent( + `${t.content}${ + this._config.inlineFragmentTypes === "mask" + ? ` & { ' $fragmentName'?: '${t.name}' }` + : "" + }` + ).string + ), + new DeclarationBlock(declarationBlockConfig) + .export() + .asKind("type") + .withName(mergedTypeString) + .withContent(subTypes.map((t) => t.name).join(" | ")).string, + ].join("\n"); + } + + protected buildFragmentTypeName( + name: string, + suffix: string, + typeName = "" + ): string { + return this._convertName(name, { + useTypesPrefix: true, + suffix: + typeName && suffix + ? `_${typeName}_${suffix}` + : typeName + ? `_${typeName}` + : suffix, + }); + } + + protected buildParentFieldName(typeName: string, parentName: string): string { + // queries/mutations/fragments are guaranteed to be unique type names, + // so we can skip affixing the field names with typeName + return operationTypes.includes(typeName) + ? parentName + : `${parentName}_${typeName}`; + } +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/types.ts b/packages/compiler/src/vendor/visitor-plugin-common/types.ts new file mode 100644 index 0000000..38f45dc --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/types.ts @@ -0,0 +1,126 @@ +import type { ASTNode, FragmentDefinitionNode, DirectiveNode } from "graphql"; +import { ParsedMapper } from "./mappers"; + +/** + * A map between the GraphQL directive name and the identifier that should be used + */ +export type DirectiveArgumentAndInputFieldMappings = { [name: string]: string }; + +/** + * Parsed directives map - a mapping between GraphQL directive name and the parsed mapper object, + * including all required information for generating code for that mapping. + */ +export type ParsedDirectiveArgumentAndInputFieldMappings = { + [name: string]: ParsedMapper; +}; + +/** + * Scalars map or a string, a map between the GraphQL scalar name and the identifier that should be used + */ +export type ScalarsMap = + | string + | { [name: string]: string | { input: string; output: string } }; +/** + * A normalized map between GraphQL scalar name and the identifier name + */ +export type NormalizedScalarsMap = { + [name: string]: { + input: string; + output: string; + }; +}; +/** + * Parsed scalars map - a mapping between GraphQL scalar name and the parsed mapper object, + * including all required information for generting code for that mapping. + */ +export type ParsedScalarsMap = { + [name: string]: { + input: ParsedMapper; + output: ParsedMapper; + }; +}; +/** + * A raw configuration for enumValues map - can be represented with a single string value for a file path, + * a map between enum name and a file path, or a map between enum name and an object with explicit enum values. + */ +export type EnumValuesMap = + | string + | { + [enumName: string]: + | string + | ({ [key: string]: string | number } & AdditionalProps); + }; +export type ParsedEnumValuesMap = { + [enumName: string]: { + // If values are explictly set, this will include the mapped values + mappedValues?: { [valueName: string]: string | number }; + // The GraphQL enum name + typeIdentifier: string; + // The actual identifier that you should use in the code (original or aliased) + sourceIdentifier?: string; + // In case of external enum, this will contain the source file path + sourceFile?: string; + // If the identifier is external (imported) - this will contain the imported expression (including alias), otherwise null + importIdentifier?: string; + // Is defualt import is used to import the enum + isDefault?: boolean; + }; +}; +export type ConvertNameFn = ConvertFn; +export type GetFragmentSuffixFn = ( + node: FragmentDefinitionNode | string +) => string; + +export interface ConvertOptions { + prefix?: string; + suffix?: string; + transformUnderscore?: boolean; +} + +export type ConvertFn = ( + node: ASTNode | string, + options?: ConvertOptions & T +) => string; +export type NamingConvention = "keep"; + +export type LoadedFragment = { + name: string; + onType: string; + node: FragmentDefinitionNode; + isExternal: boolean; + importFrom?: string | null; +} & AdditionalFields; + +export type DeclarationKind = "type" | "interface" | "class" | "abstract class"; + +export interface DeclarationKindConfig { + directive?: DeclarationKind; + scalar?: DeclarationKind; + input?: DeclarationKind; + type?: DeclarationKind; + interface?: DeclarationKind; + arguments?: DeclarationKind; +} + +export interface AvoidOptionalsConfig { + field?: boolean; + object?: boolean; + inputValue?: boolean; + defaultValue?: boolean; + resolvers?: boolean; +} + +export interface ParsedImport { + moduleName: string | null; + propName: string; +} + +export type FragmentDirectives = { + fragmentDirectives?: Array; +}; + +export interface ResolversNonOptionalTypenameConfig { + unionMember?: boolean; + interfaceImplementingType?: boolean; + excludeTypes?: string[]; +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/utils.ts b/packages/compiler/src/vendor/visitor-plugin-common/utils.ts new file mode 100644 index 0000000..b982af6 --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/utils.ts @@ -0,0 +1,777 @@ +import { + FieldNode, + FragmentSpreadNode, + InlineFragmentNode, + NamedTypeNode, + NameNode, + SelectionNode, + SelectionSetNode, + StringValueNode, + TypeNode, + DirectiveNode, +} from "graphql/language/ast"; +import { Kind } from "graphql/language/kinds"; +import { + GraphQLInputObjectType, + GraphQLNamedType, + GraphQLObjectType, + GraphQLOutputType, + isAbstractType, + isInputObjectType, + isListType, + isNonNullType, + isObjectType, + isScalarType, +} from "graphql/type/definition"; +import { GraphQLSchema } from "graphql/type/schema"; +import { RawConfig } from "./base-visitor"; +import { parseMapper } from "./mappers"; +import { DEFAULT_SCALARS } from "./scalars"; +import { + NormalizedScalarsMap, + ParsedScalarsMap, + ScalarsMap, + FragmentDirectives, + LoadedFragment, +} from "./types"; +import { weakMemoize } from "../../weakMemoize"; + +export const getConfigValue = ( + value: T | null | undefined, + defaultValue: T +): T => { + if (value === null || value === undefined) { + return defaultValue; + } + + return value; +}; + +export function quoteIfNeeded(array: string[], joinWith = " & "): string { + if (array.length === 0) { + return ""; + } + if (array.length === 1) { + return array[0]; + } + return `(${array.join(joinWith)})`; +} + +export function block(array: string[]) { + return array && array.length !== 0 ? "{\n" + array.join("\n") + "\n}" : ""; +} + +export function wrapWithSingleQuotes( + value: string | number | NameNode, + skipNumericCheck = false +): string { + if (skipNumericCheck) { + if (typeof value === "number") { + return String(value); + } + return `'${value}'`; + } + + if ( + typeof value === "number" || + (typeof value === "string" && + !Number.isNaN(parseInt(value)) && + parseFloat(value).toString() === value) + ) { + return String(value); + } + + return `'${value}'`; +} + +export function breakLine(str: string): string { + return str + "\n"; +} + +export function indent(str: string, count = 1): string { + return new Array(count).fill(" ").join("") + str; +} + +export function indentMultiline(str: string, count = 1): string { + const indentation = new Array(count).fill(" ").join(""); + const replaceWith = "\n" + indentation; + + return indentation + str.replace(/\n/g, replaceWith); +} + +export interface DeclarationBlockConfig { + blockWrapper?: string; + blockTransformer?: (block: string) => string; + enumNameValueSeparator?: string; + ignoreExport?: boolean; +} + +export function transformComment( + comment: string | StringValueNode, + indentLevel = 0, + disabled = false +): string { + if (!comment || comment === "" || disabled) { + return ""; + } + + if (isStringValueNode(comment)) { + comment = comment.value; + } + + comment = comment.split("*/").join("*\\/"); + let lines = comment.split("\n"); + if (lines.length === 1) { + return indent(`/** ${lines[0]} */\n`, indentLevel); + } + lines = ["/**", ...lines.map((line) => ` * ${line}`), " */\n"]; + return stripTrailingSpaces( + lines.map((line) => indent(line, indentLevel)).join("\n") + ); +} + +export class DeclarationBlock { + _decorator: string | null = null; + _export = false; + _name: NameNode | string | null = null; + _kind: string | null = null; + _methodName: string | null = null; + _content: string | null = null; + _block: string | null = null; + _nameGenerics: string | null = null; + _comment: string | null = null; + _ignoreBlockWrapper = false; + + constructor(private _config: DeclarationBlockConfig) { + this._config = { + blockWrapper: "", + blockTransformer: (block) => block, + enumNameValueSeparator: ":", + ...this._config, + }; + } + + withDecorator(decorator: string): DeclarationBlock { + this._decorator = decorator; + + return this; + } + + export(exp = true): DeclarationBlock { + if (!this._config.ignoreExport) { + this._export = exp; + } + + return this; + } + + asKind(kind: string): DeclarationBlock { + this._kind = kind; + + return this; + } + + withComment( + comment: string | StringValueNode | null, + disabled = false + ): DeclarationBlock { + const nonEmptyComment = !!(isStringValueNode(comment) + ? comment.value + : comment); + + if (nonEmptyComment && !disabled) { + this._comment = transformComment(comment!, 0); + } + + return this; + } + + withMethodCall( + methodName: string, + ignoreBlockWrapper = false + ): DeclarationBlock { + this._methodName = methodName; + this._ignoreBlockWrapper = ignoreBlockWrapper; + + return this; + } + + withBlock(block: string): DeclarationBlock { + this._block = block; + + return this; + } + + withContent(content: string): DeclarationBlock { + this._content = content; + + return this; + } + + withName( + name: string | NameNode, + generics: string | null = null + ): DeclarationBlock { + this._name = name; + this._nameGenerics = generics; + + return this; + } + + public get string(): string { + let result = ""; + + if (this._decorator) { + result += this._decorator + "\n"; + } + + if (this._export) { + result += "export "; + } + + if (this._kind) { + let extra = ""; + let name = ""; + + if (["type", "const", "var", "let"].includes(this._kind)) { + extra = "= "; + } + + if (this._name) { + name = this._name + (this._nameGenerics || "") + " "; + } + + result += this._kind + " " + name + extra; + } + + if (this._block) { + if (this._content) { + result += this._content; + } + + const blockWrapper = this._ignoreBlockWrapper + ? "" + : this._config.blockWrapper; + const before = "{" + blockWrapper; + const after = blockWrapper + "}"; + const block = [before, this._block, after] + .filter((val) => !!val) + .join("\n"); + + if (this._methodName) { + result += `${this._methodName}(${this._config.blockTransformer!( + block + )})`; + } else { + result += this._config.blockTransformer!(block); + } + } else if (this._content) { + result += this._content; + } else if (this._kind) { + result += this._config.blockTransformer!("{}"); + } + + return stripTrailingSpaces( + (this._comment || "") + + result + + (this._kind === "interface" || + this._kind === "enum" || + this._kind === "namespace" || + this._kind === "function" + ? "" + : ";") + + "\n" + ); + } +} + +export function getBaseTypeNode(typeNode: TypeNode): NamedTypeNode { + if ( + typeNode.kind === Kind.LIST_TYPE || + typeNode.kind === Kind.NON_NULL_TYPE + ) { + return getBaseTypeNode(typeNode.type); + } + + return typeNode; +} + +export function buildScalarsFromConfig( + schema: GraphQLSchema | undefined, + config: RawConfig, + defaultScalarsMapping: NormalizedScalarsMap = DEFAULT_SCALARS, + defaultScalarType = "any" +): ParsedScalarsMap { + return buildScalars( + schema, + config.scalars, + defaultScalarsMapping, + config.strictScalars ? null : config.defaultScalarType || defaultScalarType + ); +} + +export function buildScalars( + schema: GraphQLSchema | undefined, + scalarsMapping: ScalarsMap | undefined, + defaultScalarsMapping: NormalizedScalarsMap = DEFAULT_SCALARS, + defaultScalarType: string | null = "any" +): ParsedScalarsMap { + const result: ParsedScalarsMap = {}; + + function normalizeScalarType( + type: string | { input: string; output: string } + ): { input: string; output: string } { + if (typeof type === "string") { + return { + input: type, + output: type, + }; + } + + return { + input: type.input, + output: type.output, + }; + } + + for (const name of Object.keys(defaultScalarsMapping)) { + result[name] = { + input: parseMapper(defaultScalarsMapping[name].input), + output: parseMapper(defaultScalarsMapping[name].output), + }; + } + + if (schema) { + const typeMap = schema.getTypeMap(); + + Object.keys(typeMap) + .map((typeName) => typeMap[typeName]) + .filter(isScalarType) + .map((scalarType) => { + const { name } = scalarType; + if (typeof scalarsMapping === "string") { + const inputMapper = parseMapper(scalarsMapping + "#" + name, name); + + const outputMapper = parseMapper(scalarsMapping + "#" + name, name); + + result[name] = { + input: inputMapper, + output: outputMapper, + }; + } else if (scalarsMapping?.[name]) { + const mappedScalar = scalarsMapping[name]; + if (typeof mappedScalar === "string") { + const normalizedScalar = normalizeScalarType(scalarsMapping[name]); + result[name] = { + input: parseMapper(normalizedScalar.input, name), + output: parseMapper(normalizedScalar.output, name), + }; + } else if ( + typeof mappedScalar === "object" && + mappedScalar.input && + mappedScalar.output + ) { + result[name] = { + input: parseMapper(mappedScalar.input, name), + output: parseMapper(mappedScalar.output, name), + }; + } else { + result[name] = { + input: { + isExternal: false, + type: JSON.stringify(mappedScalar), + }, + output: { + isExternal: false, + type: JSON.stringify(mappedScalar), + }, + }; + } + } else if (scalarType.extensions?.codegenScalarType) { + result[name] = { + input: { + isExternal: false, + type: scalarType.extensions.codegenScalarType as string, + }, + output: { + isExternal: false, + type: scalarType.extensions.codegenScalarType as string, + }, + }; + } else if (!defaultScalarsMapping[name]) { + if (defaultScalarType === null) { + throw new Error( + `Unknown scalar type ${name}. Please override it using the "scalars" configuration field!` + ); + } + result[name] = { + input: { + isExternal: false, + type: defaultScalarType, + }, + output: { + isExternal: false, + type: defaultScalarType, + }, + }; + } + }); + } else if (scalarsMapping) { + if (typeof scalarsMapping === "string") { + throw new Error( + "Cannot use string scalars mapping when building without a schema" + ); + } + for (const name of Object.keys(scalarsMapping)) { + if (typeof scalarsMapping[name] === "string") { + const normalizedScalar = normalizeScalarType(scalarsMapping[name]); + result[name] = { + input: parseMapper(normalizedScalar.input, name), + output: parseMapper(normalizedScalar.output, name), + }; + } else { + const normalizedScalar = normalizeScalarType(scalarsMapping[name]); + result[name] = { + input: { + isExternal: false, + type: JSON.stringify(normalizedScalar.input), + }, + output: { + isExternal: false, + type: JSON.stringify(normalizedScalar.output), + }, + }; + } + } + } + + return result; +} + +function isStringValueNode(node: any): node is StringValueNode { + return node && typeof node === "object" && node.kind === Kind.STRING; +} + +export function stripMapperTypeInterpolation(identifier: string): string { + return identifier.trim().replace(/<{.*}>/, ""); +} + +export const OMIT_TYPE = + "export type Omit = Pick>;"; +export const REQUIRE_FIELDS_TYPE = `export type RequireFields = Omit & { [P in K]-?: NonNullable };`; + +/** + * merge selection sets into a new selection set without mutating the inputs. + */ +export function mergeSelectionSets( + selectionSet1: SelectionSetNode, + selectionSet2: SelectionSetNode +): SelectionSetNode { + const newSelections = [...selectionSet1.selections]; + + for (let selection2 of selectionSet2.selections) { + if ( + selection2.kind === "FragmentSpread" || + selection2.kind === "InlineFragment" + ) { + newSelections.push(selection2); + continue; + } + + if (selection2.kind !== "Field") { + throw new TypeError("Invalid state."); + } + + const match = newSelections.find( + (selection1) => + selection1.kind === "Field" && + getFieldNodeNameValue(selection1) === + getFieldNodeNameValue(selection2 as FieldNode) + ); + + if ( + match && + // recursively merge all selection sets + match.kind === "Field" && + match.selectionSet && + selection2.selectionSet + ) { + selection2 = { + ...selection2, + selectionSet: mergeSelectionSets( + match.selectionSet, + selection2.selectionSet + ), + }; + } + + newSelections.push(selection2); + } + + return { + kind: Kind.SELECTION_SET, + selections: newSelections, + }; +} + +export const getFieldNodeNameValue = (node: FieldNode): string => { + return (node.alias || node.name).value; +}; + +export function separateSelectionSet( + selections: ReadonlyArray +): { + fields: (FieldNode & FragmentDirectives)[]; + spreads: FragmentSpreadNode[]; + inlines: InlineFragmentNode[]; +} { + return { + fields: selections.filter((s) => s.kind === Kind.FIELD) as FieldNode[], + inlines: selections.filter( + (s) => s.kind === Kind.INLINE_FRAGMENT + ) as InlineFragmentNode[], + spreads: selections.filter( + (s) => s.kind === Kind.FRAGMENT_SPREAD + ) as FragmentSpreadNode[], + }; +} + +export function getPossibleTypes( + schema: GraphQLSchema, + type: GraphQLNamedType +): GraphQLObjectType[] { + if (isListType(type) || isNonNullType(type)) { + return getPossibleTypes(schema, type.ofType as GraphQLNamedType); + } + if (isObjectType(type)) { + return [type]; + } + if (isAbstractType(type)) { + return schema.getPossibleTypes(type) as Array; + } + + return []; +} + +export function hasConditionalDirectives(field: FieldNode): boolean { + const CONDITIONAL_DIRECTIVES = ["skip", "include"]; + return ( + field.directives?.some((directive) => + CONDITIONAL_DIRECTIVES.includes(directive.name.value) + ) ?? false + ); +} + +export function hasIncrementalDeliveryDirectives( + directives: DirectiveNode[] +): boolean { + const INCREMENTAL_DELIVERY_DIRECTIVES = ["defer"]; + return directives?.some((directive) => + INCREMENTAL_DELIVERY_DIRECTIVES.includes(directive.name.value) + ); +} + +type WrapModifiersOptions = { + wrapOptional(type: string): string; + wrapArray(type: string): string; +}; + +export function wrapTypeWithModifiers( + baseType: string, + type: GraphQLOutputType | GraphQLNamedType, + options: WrapModifiersOptions +): string { + let currentType = type; + const modifiers: Array<(type: string) => string> = []; + while (currentType) { + if (isNonNullType(currentType)) { + currentType = currentType.ofType; + } else { + modifiers.push(options.wrapOptional); + } + + if (isListType(currentType)) { + modifiers.push(options.wrapArray); + currentType = currentType.ofType; + } else { + break; + } + } + + return modifiers.reduceRight( + (result, modifier) => modifier(result), + baseType + ); +} + +export function removeDescription( + nodes: readonly T[] +) { + return nodes.map((node) => ({ ...node, description: undefined })); +} + +export function wrapTypeNodeWithModifiers( + baseType: string, + typeNode: TypeNode +): string { + switch (typeNode.kind) { + case Kind.NAMED_TYPE: { + return `Maybe<${baseType}>`; + } + case Kind.NON_NULL_TYPE: { + const innerType = wrapTypeNodeWithModifiers(baseType, typeNode.type); + return clearOptional(innerType); + } + case Kind.LIST_TYPE: { + const innerType = wrapTypeNodeWithModifiers(baseType, typeNode.type); + return `Maybe>`; + } + } +} + +function clearOptional(str: string): string { + const rgx = new RegExp(`^Maybe<(.*?)>$`, "i"); + + if (str.startsWith(`Maybe`)) { + return str.replace(rgx, "$1"); + } + + return str; +} + +function stripTrailingSpaces(str: string): string { + return str.replace(/ +\n/g, "\n"); +} + +const isOneOfTypeCache = new WeakMap(); +export function isOneOfInputObjectType( + namedType: GraphQLNamedType | null | undefined +): namedType is GraphQLInputObjectType { + if (!namedType) { + return false; + } + let isOneOfType = isOneOfTypeCache.get(namedType); + + if (isOneOfType !== undefined) { + return isOneOfType; + } + + isOneOfType = + isInputObjectType(namedType) && + !!( + (namedType as unknown as Record<"isOneOf", boolean | undefined>) + .isOneOf || + namedType.astNode?.directives?.some((d) => d.name.value === "oneOf") + ); + + isOneOfTypeCache.set(namedType, isOneOfType); + + return isOneOfType; +} + +export function groupBy( + array: Array, + key: (item: T) => string | number +): { [key: string]: Array } { + return array.reduce<{ [key: string]: Array }>((acc, item) => { + const group = (acc[key(item)] ??= []); + group.push(item); + return acc; + }, {}); +} + +export function flatten(array: Array>): Array { + return ([] as Array).concat(...array); +} + +export function unique( + array: Array, + key: (item: T) => string | number = (item) => (item as any).toString() +): Array { + return Object.values( + array.reduce((acc, item) => ({ [key(item)]: item, ...acc }), {}) + ); +} + +function getFullPathFieldName(selection: FieldNode, parentName: string) { + const fullName = + "alias" in selection && selection.alias + ? `${selection.alias.value}@${selection.name.value}` + : selection.name.value; + return parentName ? `${parentName}.${fullName}` : fullName; +} + +export const getFieldNames = ({ + selections, + fieldNames = new Set(), + parentName = "", + loadedFragments, +}: { + selections: readonly SelectionNode[]; + fieldNames?: Set; + parentName?: string; + loadedFragments: LoadedFragment[]; +}) => { + for (const selection of selections) { + switch (selection.kind) { + case Kind.FIELD: { + const fieldName = getFullPathFieldName(selection, parentName); + fieldNames.add(fieldName); + if (selection.selectionSet) { + getFieldNames({ + selections: selection.selectionSet.selections, + fieldNames, + parentName: fieldName, + loadedFragments, + }); + } + break; + } + case Kind.FRAGMENT_SPREAD: { + getFieldNames({ + selections: loadedFragments + .filter((def) => def.name === selection.name.value) + .flatMap((s) => s.node.selectionSet.selections), + fieldNames, + parentName, + loadedFragments, + }); + break; + } + case Kind.INLINE_FRAGMENT: { + getFieldNames({ + selections: selection.selectionSet.selections, + fieldNames, + parentName, + loadedFragments, + }); + break; + } + } + } + return fieldNames; +}; + +export const getRootTypes = weakMemoize((schema: GraphQLSchema) => { + const set = new Set(); + const queryType = schema.getQueryType(); + if (queryType) { + set.add(queryType); + } + const mutationType = schema.getMutationType(); + if (mutationType) { + set.add(mutationType); + } + const subscriptionType = schema.getSubscriptionType(); + if (subscriptionType) { + set.add(subscriptionType); + } + return set; +}); + +export function getBaseType(type: GraphQLOutputType): GraphQLNamedType { + if (isNonNullType(type) || isListType(type)) { + return getBaseType(type.ofType); + } + return type; +} diff --git a/packages/compiler/src/vendor/visitor-plugin-common/variables-to-object.ts b/packages/compiler/src/vendor/visitor-plugin-common/variables-to-object.ts new file mode 100644 index 0000000..c9d76fe --- /dev/null +++ b/packages/compiler/src/vendor/visitor-plugin-common/variables-to-object.ts @@ -0,0 +1,204 @@ +import type { + DirectiveNode, + NameNode, + TypeNode, + ValueNode, + VariableNode, +} from "graphql/language/ast"; +import { Kind } from "graphql/language/kinds"; +import { BaseVisitorConvertOptions } from "./base-visitor"; +import { + ConvertNameFn, + NormalizedScalarsMap, + ParsedDirectiveArgumentAndInputFieldMappings, + ParsedEnumValuesMap, +} from "./types"; +import { getBaseTypeNode, indent } from "./utils"; +import { autoBind } from "../auto-bind"; + +export interface InterfaceOrVariable { + name?: NameNode; + variable?: VariableNode; + type: TypeNode; + defaultValue?: ValueNode; + directives?: ReadonlyArray; +} + +export class OperationVariablesToObject { + constructor( + protected _scalars: NormalizedScalarsMap, + protected _convertName: ConvertNameFn, + protected _namespacedImportName: string | null = null, + protected _enumNames: string[] = [], + protected _enumPrefix = true, + protected _enumSuffix = true, + protected _enumValues: ParsedEnumValuesMap = {}, + protected _applyCoercion: Boolean = false, + protected _directiveArgumentAndInputFieldMappings: ParsedDirectiveArgumentAndInputFieldMappings = {} + ) { + autoBind(this); + } + + getName( + node: TDefinitionType + ): string | null { + if (node.name) { + if (typeof node.name === "string") { + return node.name; + } + + return node.name.value; + } + if (node.variable) { + return node.variable.name.value; + } + + return null; + } + + transform( + variablesNode: ReadonlyArray + ): string | null { + if (!variablesNode || variablesNode.length === 0) { + return null; + } + + return ( + variablesNode + .map((variable) => indent(this.transformVariable(variable))) + .join(`${this.getPunctuation()}\n`) + this.getPunctuation() + ); + } + + protected getScalar(name: string): string { + const prefix = this._namespacedImportName + ? `${this._namespacedImportName}.` + : ""; + + return `${prefix}Scalars['${name}']`; + } + + protected getDirectiveMapping(name: string): string { + return `DirectiveArgumentAndInputFieldMappings['${name}']`; + } + + protected getDirectiveOverrideType( + directives: ReadonlyArray + ): string | null { + if (!this._directiveArgumentAndInputFieldMappings) return null; + + const type = directives + .map((directive) => { + const directiveName = directive.name.value; + if (this._directiveArgumentAndInputFieldMappings[directiveName]) { + return this.getDirectiveMapping(directiveName); + } + return null; + }) + .reverse() + .find((a) => !!a); + + return type || null; + } + + protected transformVariable( + variable: TDefinitionType + ): string { + let typeValue: string; + const prefix = this._namespacedImportName + ? `${this._namespacedImportName}.` + : ""; + + if (typeof variable.type === "string") { + typeValue = variable.type; + } else { + const baseType = getBaseTypeNode(variable.type); + const overrideType = variable.directives + ? this.getDirectiveOverrideType(variable.directives) + : null; + const typeName = baseType.name.value; + + if (overrideType) { + typeValue = overrideType; + } else if (this._scalars[typeName]) { + typeValue = this.getScalar(typeName); + } else if (this._enumValues[typeName]?.sourceFile) { + typeValue = + this._enumValues[typeName].typeIdentifier || + this._enumValues[typeName].sourceIdentifier!; + } else { + typeValue = `${prefix}${this._convertName(baseType, { + useTypesPrefix: this._enumNames.includes(typeName) + ? this._enumPrefix + : true, + useTypesSuffix: this._enumNames.includes(typeName) + ? this._enumSuffix + : true, + })}`; + } + } + + const fieldName = this.getName(variable)!; + const fieldType = this.wrapAstTypeWithModifiers( + typeValue, + variable.type, + this._applyCoercion + ); + + const hasDefaultValue = + variable.defaultValue != null && + typeof variable.defaultValue !== "undefined"; + const isNonNullType = variable.type.kind === Kind.NON_NULL_TYPE; + + const formattedFieldString = this.formatFieldString( + fieldName, + isNonNullType, + hasDefaultValue + ); + const formattedTypeString = this.formatTypeString( + fieldType, + isNonNullType, + hasDefaultValue + ); + + return `${formattedFieldString}: ${formattedTypeString}`; + } + + public wrapAstTypeWithModifiers( + _baseType: string, + _typeNode: TypeNode, + _applyCoercion?: Boolean + ): string { + throw new Error( + `You must override "wrapAstTypeWithModifiers" of OperationVariablesToObject!` + ); + } + + protected formatFieldString( + fieldName: string, + _isNonNullType: boolean, + _hasDefaultValue: boolean + ): string { + return fieldName; + } + + protected formatTypeString( + fieldType: string, + isNonNullType: boolean, + hasDefaultValue: boolean + ): string { + const prefix = this._namespacedImportName + ? `${this._namespacedImportName}.` + : ""; + + if (hasDefaultValue) { + return `${prefix}Maybe<${fieldType}>`; + } + + return fieldType; + } + + protected getPunctuation(): string { + return ","; + } +} diff --git a/packages/compiler/src/watch.ts b/packages/compiler/src/watch.ts index 8b245b3..6cd5613 100644 --- a/packages/compiler/src/watch.ts +++ b/packages/compiler/src/watch.ts @@ -10,8 +10,6 @@ export const watch = async (cwd: string) => { // we're requiring these here because we lazily require them in the code that generates the types // which is what we want so builds that don't have to regenerate files are fast // but for watch, a slightly slower start up is better than a slightly slower first build which requires type generation - require("@graphql-codegen/typescript"); - require("@graphql-codegen/typescript-operations"); require("@babel/code-frame"); require("graphql/validation"); diff --git a/packages/compiler/src/weakMemoize.ts b/packages/compiler/src/weakMemoize.ts new file mode 100644 index 0000000..b47d323 --- /dev/null +++ b/packages/compiler/src/weakMemoize.ts @@ -0,0 +1,11 @@ +export function weakMemoize( + fn: (arg: Arg) => Return +): (arg: Arg) => Return { + const cache = new WeakMap(); + return (arg: Arg) => { + if (cache.has(arg)) return cache.get(arg)!; + const result = fn(arg); + cache.set(arg, result); + return result; + }; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e83c3b..cb3989a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,8 +76,7 @@ importers: '@babel/parser': ^7.9.6 '@babel/runtime': ^7.9.2 '@babel/types': ^7.9.6 - '@graphql-codegen/plugin-helpers': ^2.4.2 - '@graphql-codegen/typescript-operations': ^2.3.5 + '@graphql-codegen/plugin-helpers': ^5.0.3 '@nodelib/fs.walk': ^1.2.4 '@ts-gql/config': ^0.9.2 '@ts-gql/tag': '*' @@ -97,8 +96,6 @@ importers: '@babel/parser': 7.19.4 '@babel/runtime': 7.19.4 '@babel/types': 7.19.4 - '@graphql-codegen/plugin-helpers': 2.7.1_graphql@16.6.0 - '@graphql-codegen/typescript-operations': 2.5.3_graphql@16.6.0 '@nodelib/fs.walk': 1.2.8 '@ts-gql/config': link:../config chokidar: 3.5.3 @@ -107,6 +104,7 @@ importers: slash: 3.0.0 strip-ansi: 6.0.1 devDependencies: + '@graphql-codegen/plugin-helpers': 5.0.3_graphql@16.6.0 '@ts-gql/tag': link:../tag '@types/babel__code-frame': 7.0.3 '@types/graceful-fs': 4.1.5 @@ -302,35 +300,6 @@ packages: zen-observable-ts: 1.2.5 dev: false - /@ardatan/relay-compiler/12.0.0_graphql@16.6.0: - resolution: {integrity: sha512-9anThAaj1dQr6IGmzBMcfzOQKTa5artjuPmw8NYK/fiGEMjADbSguBY2FMDykt+QhilR3wc9VA/3yVju7JHg7Q==} - hasBin: true - peerDependencies: - graphql: '*' - dependencies: - '@babel/core': 7.19.3 - '@babel/generator': 7.19.5 - '@babel/parser': 7.19.4 - '@babel/runtime': 7.19.4 - '@babel/traverse': 7.19.4 - '@babel/types': 7.19.4 - babel-preset-fbjs: 3.4.0_@babel+core@7.19.3 - chalk: 4.1.2 - fb-watchman: 2.0.2 - fbjs: 3.0.4 - glob: 7.2.3 - graphql: 16.6.0 - immutable: 3.7.6 - invariant: 2.2.4 - nullthrows: 1.1.1 - relay-runtime: 12.0.0 - signedsource: 1.0.0 - yargs: 15.4.1 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - /@babel/code-frame/7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} @@ -877,16 +846,6 @@ packages: '@babel/helper-plugin-utils': 7.19.0 dev: false - /@babel/plugin-syntax-flow/7.18.6_@babel+core@7.19.3: - resolution: {integrity: sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - dev: false - /@babel/plugin-syntax-import-assertions/7.18.6_@babel+core@7.19.3: resolution: {integrity: sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==} engines: {node: '>=6.9.0'} @@ -1125,17 +1084,6 @@ packages: '@babel/helper-plugin-utils': 7.19.0 dev: false - /@babel/plugin-transform-flow-strip-types/7.19.0_@babel+core@7.19.3: - resolution: {integrity: sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.19.3 - '@babel/helper-plugin-utils': 7.19.0 - '@babel/plugin-syntax-flow': 7.18.6_@babel+core@7.19.3 - dev: false - /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.19.3: resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==} engines: {node: '>=6.9.0'} @@ -1859,115 +1807,32 @@ packages: - supports-color dev: false - /@graphql-codegen/plugin-helpers/2.7.1_graphql@16.6.0: - resolution: {integrity: sha512-wpEShhwbQp8pqXolnSCNaj0pU91LbuBvYHpYqm96TUqyeKQYAYRVmw3JIt0g8UQpKYhg8lYIDwWdcINOYqkGLg==} + /@graphql-codegen/plugin-helpers/5.0.3_graphql@16.6.0: + resolution: {integrity: sha512-yZ1rpULIWKBZqCDlvGIJRSyj1B2utkEdGmXZTBT/GVayP4hyRYlkd36AJV/LfEsVD8dnsKL5rLz2VTYmRNlJ5Q==} peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 dependencies: - '@graphql-tools/utils': 8.12.0_graphql@16.6.0 - change-case-all: 1.0.14 + '@graphql-tools/utils': 10.1.0_graphql@16.6.0 + change-case-all: 1.0.15 common-tags: 1.8.2 graphql: 16.6.0 import-from: 4.0.0 lodash: 4.17.21 - tslib: 2.4.0 - dev: false - - /@graphql-codegen/schema-ast/2.5.1_graphql@16.6.0: - resolution: {integrity: sha512-tewa5DEKbglWn7kYyVBkh3J8YQ5ALqAMVmZwiVFIGOao5u66nd+e4HuFqp0u+Jpz4SJGGi0ap/oFrEvlqLjd2A==} - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - dependencies: - '@graphql-codegen/plugin-helpers': 2.7.1_graphql@16.6.0 - '@graphql-tools/utils': 8.12.0_graphql@16.6.0 - graphql: 16.6.0 - tslib: 2.4.0 - dev: false - - /@graphql-codegen/typescript-operations/2.5.3_graphql@16.6.0: - resolution: {integrity: sha512-s+pA+Erm0HeBb/D5cNrflwRM5KWhkiA5cbz4uA99l3fzFPveoQBPfRCBu0XAlJLP/kBDy64+o4B8Nfc7wdRtmA==} - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - dependencies: - '@graphql-codegen/plugin-helpers': 2.7.1_graphql@16.6.0 - '@graphql-codegen/typescript': 2.7.3_graphql@16.6.0 - '@graphql-codegen/visitor-plugin-common': 2.12.1_graphql@16.6.0 - auto-bind: 4.0.0 - graphql: 16.6.0 - tslib: 2.4.0 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /@graphql-codegen/typescript/2.7.3_graphql@16.6.0: - resolution: {integrity: sha512-EzX/acijXtbG/AwPzho2ZZWaNo00+xAbsRDP+vnT2PwQV3AYq3/5bFvjq1XfAGWbTntdmlYlIwC9hf5bI85WVA==} - peerDependencies: - graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - dependencies: - '@graphql-codegen/plugin-helpers': 2.7.1_graphql@16.6.0 - '@graphql-codegen/schema-ast': 2.5.1_graphql@16.6.0 - '@graphql-codegen/visitor-plugin-common': 2.12.1_graphql@16.6.0 - auto-bind: 4.0.0 - graphql: 16.6.0 - tslib: 2.4.0 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /@graphql-codegen/visitor-plugin-common/2.12.1_graphql@16.6.0: - resolution: {integrity: sha512-dIUrX4+i/uazyPQqXyQ8cqykgNFe1lknjnfDWFo0gnk2W8+ruuL2JpSrj/7efzFHxbYGMQrCABDCUTVLi3DcVA==} - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - dependencies: - '@graphql-codegen/plugin-helpers': 2.7.1_graphql@16.6.0 - '@graphql-tools/optimize': 1.3.1_graphql@16.6.0 - '@graphql-tools/relay-operation-optimizer': 6.5.6_graphql@16.6.0 - '@graphql-tools/utils': 8.12.0_graphql@16.6.0 - auto-bind: 4.0.0 - change-case-all: 1.0.14 - dependency-graph: 0.11.0 - graphql: 16.6.0 - graphql-tag: 2.12.6_graphql@16.6.0 - parse-filepath: 1.0.2 - tslib: 2.4.0 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /@graphql-tools/optimize/1.3.1_graphql@16.6.0: - resolution: {integrity: sha512-5j5CZSRGWVobt4bgRRg7zhjPiSimk+/zIuColih8E8DxuFOaJ+t0qu7eZS5KXWBkjcd4BPNuhUPpNlEmHPqVRQ==} - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - dependencies: - graphql: 16.6.0 - tslib: 2.4.0 - dev: false - - /@graphql-tools/relay-operation-optimizer/6.5.6_graphql@16.6.0: - resolution: {integrity: sha512-2KjaWYxD/NC6KtckbDEAbN46QO+74d1SBaZQ26qQjWhyoAjon12xlMW4HWxHEN0d0xuz0cnOVUVc+t4wVXePUg==} - peerDependencies: - graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 - dependencies: - '@ardatan/relay-compiler': 12.0.0_graphql@16.6.0 - '@graphql-tools/utils': 8.12.0_graphql@16.6.0 - graphql: 16.6.0 - tslib: 2.4.0 - transitivePeerDependencies: - - encoding - - supports-color - dev: false + tslib: 2.6.2 + dev: true - /@graphql-tools/utils/8.12.0_graphql@16.6.0: - resolution: {integrity: sha512-TeO+MJWGXjUTS52qfK4R8HiPoF/R7X+qmgtOYd8DTH0l6b+5Y/tlg5aGeUJefqImRq7nvi93Ms40k/Uz4D5CWw==} + /@graphql-tools/utils/10.1.0_graphql@16.6.0: + resolution: {integrity: sha512-wLPqhgeZ9BZJPRoaQbsDN/CtJDPd/L4qmmtPkjI3NuYJ39x+Eqz1Sh34EAGMuDh+xlOHqBwHczkZUpoK9tvzjw==} + engines: {node: '>=16.0.0'} peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: + '@graphql-typed-document-node/core': 3.1.1_graphql@16.6.0 + cross-inspect: 1.0.0 + dset: 3.1.3 graphql: 16.6.0 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /@graphql-typed-document-node/core/3.1.1: resolution: {integrity: sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==} @@ -3171,20 +3036,11 @@ packages: engines: {node: '>=0.10.0'} dev: false - /asap/2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - dev: false - /at-least-node/1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} dev: false - /auto-bind/4.0.0: - resolution: {integrity: sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==} - engines: {node: '>=8'} - dev: false - /babel-jest/29.1.2_@babel+core@7.19.3: resolution: {integrity: sha512-IuG+F3HTHryJb7gacC7SQ59A9kO56BctUsT67uJHp1mMCHUOMXpDwOHWGifWqdWVknN2WNkCVQELPjXx0aLJ9Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3284,10 +3140,6 @@ packages: - supports-color dev: false - /babel-plugin-syntax-trailing-function-commas/7.0.0-beta.0: - resolution: {integrity: sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==} - dev: false - /babel-preset-current-node-syntax/1.0.1_@babel+core@7.19.3: resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} peerDependencies: @@ -3308,43 +3160,6 @@ packages: '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.19.3 dev: false - /babel-preset-fbjs/3.4.0_@babel+core@7.19.3: - resolution: {integrity: sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.19.3 - '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-proposal-object-rest-spread': 7.19.4_@babel+core@7.19.3 - '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.19.3 - '@babel/plugin-syntax-flow': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-syntax-jsx': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.19.3 - '@babel/plugin-transform-arrow-functions': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-block-scoping': 7.19.4_@babel+core@7.19.3 - '@babel/plugin-transform-classes': 7.19.0_@babel+core@7.19.3 - '@babel/plugin-transform-computed-properties': 7.18.9_@babel+core@7.19.3 - '@babel/plugin-transform-destructuring': 7.19.4_@babel+core@7.19.3 - '@babel/plugin-transform-flow-strip-types': 7.19.0_@babel+core@7.19.3 - '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.19.3 - '@babel/plugin-transform-function-name': 7.18.9_@babel+core@7.19.3 - '@babel/plugin-transform-literals': 7.18.9_@babel+core@7.19.3 - '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-modules-commonjs': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-parameters': 7.18.8_@babel+core@7.19.3 - '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-react-display-name': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-react-jsx': 7.19.0_@babel+core@7.19.3 - '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.19.3 - '@babel/plugin-transform-spread': 7.19.0_@babel+core@7.19.3 - '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.19.3 - babel-plugin-syntax-trailing-function-commas: 7.0.0-beta.0 - transitivePeerDependencies: - - supports-color - dev: false - /babel-preset-jest/29.0.2_@babel+core@7.19.3: resolution: {integrity: sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3451,8 +3266,8 @@ packages: resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} dependencies: pascal-case: 3.1.2 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /camelcase-keys/6.2.2: resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} @@ -3481,9 +3296,9 @@ packages: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} dependencies: no-case: 3.0.4 - tslib: 2.4.0 + tslib: 2.6.2 upper-case-first: 2.0.2 - dev: false + dev: true /chalk/2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -3501,8 +3316,8 @@ packages: supports-color: 7.2.0 dev: false - /change-case-all/1.0.14: - resolution: {integrity: sha512-CWVm2uT7dmSHdO/z1CXT/n47mWonyypzBbuCy5tN7uMg22BsfkhwT6oHmFCAk+gL1LOOxhdbB9SZz3J1KTY3gA==} + /change-case-all/1.0.15: + resolution: {integrity: sha512-3+GIFhk3sNuvFAJKU46o26OdzudQlPNBCu1ZQi3cMeMHhty1bhDxu2WrEilVNYaGvqUtR1VSigFcJOiS13dRhQ==} dependencies: change-case: 4.1.2 is-lower-case: 2.0.2 @@ -3514,7 +3329,7 @@ packages: title-case: 3.0.3 upper-case: 2.0.2 upper-case-first: 2.0.2 - dev: false + dev: true /change-case/4.1.2: resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} @@ -3530,8 +3345,8 @@ packages: path-case: 3.0.4 sentence-case: 3.0.4 snake-case: 3.0.4 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /char-regex/1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} @@ -3638,7 +3453,7 @@ packages: /common-tags/1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} - dev: false + dev: true /commondir/1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -3651,9 +3466,9 @@ packages: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} dependencies: no-case: 3.0.4 - tslib: 2.4.0 + tslib: 2.6.2 upper-case: 2.0.2 - dev: false + dev: true /convert-source-map/1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -3686,13 +3501,12 @@ packages: yaml: 1.10.2 dev: false - /cross-fetch/3.1.5: - resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} + /cross-inspect/1.0.0: + resolution: {integrity: sha512-4PFfn4b5ZN6FMNGSZlyb7wUhuN8wvj8t/VQHZdM4JsDcruGJ8L2kf9zao98QIrBPFCpdk27qst/AGTl7pL3ypQ==} + engines: {node: '>=16.0.0'} dependencies: - node-fetch: 2.6.7 - transitivePeerDependencies: - - encoding - dev: false + tslib: 2.6.2 + dev: true /cross-spawn/5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -3836,11 +3650,6 @@ packages: slash: 3.0.0 dev: true - /dependency-graph/0.11.0: - resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} - engines: {node: '>= 0.6.0'} - dev: false - /detect-indent/6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -3880,14 +3689,19 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /dotenv/8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} dev: false + /dset/3.1.3: + resolution: {integrity: sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==} + engines: {node: '>=4'} + dev: true + /duplexer3/0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} dev: false @@ -4220,24 +4034,6 @@ packages: bser: 2.1.1 dev: false - /fbjs-css-vars/1.0.2: - resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==} - dev: false - - /fbjs/3.0.4: - resolution: {integrity: sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ==} - dependencies: - cross-fetch: 3.1.5 - fbjs-css-vars: 1.0.2 - loose-envify: 1.4.0 - object-assign: 4.1.1 - promise: 7.3.1 - setimmediate: 1.0.5 - ua-parser-js: 0.7.31 - transitivePeerDependencies: - - encoding - dev: false - /file-entry-cache/6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -4585,8 +4381,8 @@ packages: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} dependencies: capital-case: 1.0.4 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /hoist-non-react-statics/3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -4635,11 +4431,6 @@ packages: resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} engines: {node: '>= 4'} - /immutable/3.7.6: - resolution: {integrity: sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==} - engines: {node: '>=0.8.0'} - dev: false - /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -4650,7 +4441,7 @@ packages: /import-from/4.0.0: resolution: {integrity: sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==} engines: {node: '>=12.2'} - dev: false + dev: true /import-local/3.1.0: resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} @@ -4692,20 +4483,6 @@ packages: side-channel: 1.0.4 dev: false - /invariant/2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - dependencies: - loose-envify: 1.4.0 - dev: false - - /is-absolute/1.0.0: - resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} - engines: {node: '>=0.10.0'} - dependencies: - is-relative: 1.0.0 - is-windows: 1.0.2 - dev: false - /is-arrayish/0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -4794,8 +4571,8 @@ packages: /is-lower-case/2.0.2: resolution: {integrity: sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /is-module/1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} @@ -4846,13 +4623,6 @@ packages: has-tostringtag: 1.0.0 dev: false - /is-relative/1.0.0: - resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} - engines: {node: '>=0.10.0'} - dependencies: - is-unc-path: 1.0.0 - dev: false - /is-shared-array-buffer/1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: @@ -4884,18 +4654,11 @@ packages: has-symbols: 1.0.3 dev: false - /is-unc-path/1.0.0: - resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==} - engines: {node: '>=0.10.0'} - dependencies: - unc-path-regex: 0.1.2 - dev: false - /is-upper-case/2.0.2: resolution: {integrity: sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /is-weakref/1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -5569,7 +5332,7 @@ packages: /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false + dev: true /loose-envify/1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} @@ -5580,14 +5343,14 @@ packages: /lower-case-first/2.0.2: resolution: {integrity: sha512-EVm/rR94FJTZi3zefZ82fLWab+GX14LJN4HrWBcuo6Evmsl9hEfnqxgcHCKb9q+mNf6EVdsjx/qucYFIIB84pg==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /lower-case/2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /lowercase-keys/1.0.1: resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} @@ -5638,11 +5401,6 @@ packages: tmpl: 1.0.5 dev: false - /map-cache/0.2.2: - resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} - engines: {node: '>=0.10.0'} - dev: false - /map-obj/1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -5806,8 +5564,8 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /node-fetch/2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} @@ -5876,10 +5634,6 @@ packages: path-key: 3.1.1 dev: false - /nullthrows/1.1.1: - resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} - dev: false - /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -6013,8 +5767,8 @@ packages: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: dot-case: 3.0.4 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -6022,15 +5776,6 @@ packages: dependencies: callsites: 3.1.0 - /parse-filepath/1.0.2: - resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} - engines: {node: '>=0.8'} - dependencies: - is-absolute: 1.0.0 - map-cache: 0.2.2 - path-root: 0.1.1 - dev: false - /parse-github-url/1.0.2: resolution: {integrity: sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==} engines: {node: '>=0.10.0'} @@ -6060,15 +5805,15 @@ packages: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /path-case/3.0.4: resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} dependencies: dot-case: 3.0.4 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /path-exists/4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -6087,18 +5832,6 @@ packages: /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - /path-root-regex/0.1.2: - resolution: {integrity: sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==} - engines: {node: '>=0.10.0'} - dev: false - - /path-root/0.1.1: - resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==} - engines: {node: '>=0.10.0'} - dependencies: - path-root-regex: 0.1.2 - dev: false - /path-type/3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} @@ -6184,12 +5917,6 @@ packages: react-is: 18.2.0 dev: false - /promise/7.3.1: - resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} - dependencies: - asap: 2.0.6 - dev: false - /prompts/2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -6396,16 +6123,6 @@ packages: jsesc: 0.5.0 dev: false - /relay-runtime/12.0.0: - resolution: {integrity: sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug==} - dependencies: - '@babel/runtime': 7.19.4 - fbjs: 3.0.4 - invariant: 2.2.4 - transitivePeerDependencies: - - encoding - dev: false - /require-directory/2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -6541,18 +6258,14 @@ packages: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} dependencies: no-case: 3.0.4 - tslib: 2.4.0 + tslib: 2.6.2 upper-case-first: 2.0.2 - dev: false + dev: true /set-blocking/2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: false - /setimmediate/1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - dev: false - /shebang-command/1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} engines: {node: '>=0.10.0'} @@ -6588,10 +6301,6 @@ packages: /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - /signedsource/1.0.0: - resolution: {integrity: sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==} - dev: false - /sisteransi/1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: false @@ -6622,8 +6331,8 @@ packages: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} @@ -6686,8 +6395,8 @@ packages: /sponge-case/1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /sprintf-js/1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -6833,8 +6542,8 @@ packages: /swap-case/2.0.2: resolution: {integrity: sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /symbol-observable/4.0.0: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} @@ -6909,8 +6618,8 @@ packages: /title-case/3.0.3: resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /tmp/0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} @@ -6975,6 +6684,10 @@ packages: /tslib/2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + /tslib/2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: true + /tsutils/3.21.0_typescript@5.1.6: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -7047,10 +6760,6 @@ packages: hasBin: true dev: false - /ua-parser-js/0.7.31: - resolution: {integrity: sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==} - dev: false - /unbox-primitive/1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -7060,11 +6769,6 @@ packages: which-boxed-primitive: 1.0.2 dev: false - /unc-path-regex/0.1.2: - resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} - engines: {node: '>=0.10.0'} - dev: false - /unicode-canonical-property-names-ecmascript/2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -7125,14 +6829,14 @@ packages: /upper-case-first/2.0.2: resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /upper-case/2.0.2: resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} dependencies: - tslib: 2.4.0 - dev: false + tslib: 2.6.2 + dev: true /uri-js/4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}