From 1225c4d86f2dbe4dc5ea8b07ab6a0f4b4c618ed3 Mon Sep 17 00:00:00 2001 From: bloodyowl Date: Fri, 22 Mar 2024 15:35:50 +0100 Subject: [PATCH] Vendor printer & keep fragments in sent query --- src/client.ts | 19 +++++- src/graphql/print.ts | 148 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/graphql/print.ts diff --git a/src/client.ts b/src/client.ts index 8ca31b2..f0be19e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -9,7 +9,7 @@ import { } from "@swan-io/request"; import { ClientCache } from "./cache/cache"; import { TypedDocumentNode } from "./types"; -import { DocumentNode, GraphQLError, print } from "@0no-co/graphql.web"; +import { DocumentNode, GraphQLError } from "@0no-co/graphql.web"; import { addTypenames, getExecutableOperationName, @@ -24,6 +24,7 @@ import { } from "./errors"; import { writeOperationToCache } from "./cache/write"; import { readOperationFromCache } from "./cache/read"; +import { print } from "./graphql/print"; type RequestConfig = { url: string; @@ -94,6 +95,7 @@ export class Client { subscribers: Set<() => void>; transformedDocuments: Map; + transformedDocumentsForRequest: Map; constructor(config: ClientConfig) { this.url = config.url; @@ -103,6 +105,7 @@ export class Client { this.parseResponse = config.parseResponse ?? defaultParseResponse; this.subscribers = new Set(); this.transformedDocuments = new Map(); + this.transformedDocumentsForRequest = new Map(); } getTransformedDocument(document: DocumentNode) { @@ -115,6 +118,16 @@ export class Client { } } + getTransformedDocumentsForRequest(document: DocumentNode) { + if (this.transformedDocumentsForRequest.has(document)) { + return this.transformedDocumentsForRequest.get(document) as DocumentNode; + } else { + const transformedDocument = addTypenames(document); + this.transformedDocumentsForRequest.set(document, transformedDocument); + return transformedDocument; + } + } + subscribe(func: () => void) { this.subscribers.add(func); return () => this.subscribers.delete(func); @@ -125,6 +138,8 @@ export class Client { variables: Variables ) { const transformedDocument = this.getTransformedDocument(document); + const transformedDocumentsForRequest = + this.getTransformedDocumentsForRequest(document); const operationName = getExecutableOperationName(transformedDocument).getWithDefault( @@ -137,7 +152,7 @@ export class Client { url: this.url, headers: this.headers, operationName, - document: transformedDocument, + document: transformedDocumentsForRequest, variables: variablesAsRecord, }) .mapOkToResult(this.parseResponse) diff --git a/src/graphql/print.ts b/src/graphql/print.ts new file mode 100644 index 0000000..36c7365 --- /dev/null +++ b/src/graphql/print.ts @@ -0,0 +1,148 @@ +import { ASTNode } from "@0no-co/graphql.web"; + +export const printString = (string: string) => { + return JSON.stringify(string); +}; + +export const printBlockString = (string: string) => { + return '"""' + string.replace(/"""/g, '\\"""') + '"""'; +}; + +const hasItems = ( + array: ReadonlyArray | undefined | null +): array is ReadonlyArray => !!(array && array.length); + +const MAX_LINE_LENGTH = 80; + +const nodes: { + [NodeT in ASTNode as NodeT["kind"]]?: (node: NodeT) => string; +} = { + OperationDefinition(node) { + if ( + node.operation === "query" && + !node.name && + !hasItems(node.variableDefinitions) && + !hasItems(node.directives) + ) { + return nodes.SelectionSet!(node.selectionSet); + } + let out: string = node.operation; + if (node.name) out += " " + node.name.value; + if (hasItems(node.variableDefinitions)) { + if (!node.name) out += " "; + out += + "(" + + node.variableDefinitions.map(nodes.VariableDefinition!).join(", ") + + ")"; + } + if (hasItems(node.directives)) + out += " " + node.directives.map(nodes.Directive!).join(" "); + return out + " " + nodes.SelectionSet!(node.selectionSet); + }, + VariableDefinition(node) { + let out = nodes.Variable!(node.variable) + ": " + print(node.type); + if (node.defaultValue) out += " = " + print(node.defaultValue); + if (hasItems(node.directives)) + out += " " + node.directives.map(nodes.Directive!).join(" "); + return out; + }, + Field(node) { + let out = (node.alias ? node.alias.value + ": " : "") + node.name.value; + if (hasItems(node.arguments)) { + const args = node.arguments.map(nodes.Argument!); + const argsLine = out + "(" + args.join(", ") + ")"; + out = + argsLine.length > MAX_LINE_LENGTH + ? out + "(" + args.join(" ") + ")" + : argsLine; + } + if (hasItems(node.directives)) + out += " " + node.directives.map(nodes.Directive!).join(" "); + return node.selectionSet + ? out + " " + nodes.SelectionSet!(node.selectionSet) + : out; + }, + StringValue(node) { + return node.block ? printBlockString(node.value) : printString(node.value); + }, + BooleanValue(node) { + return "" + node.value; + }, + NullValue(_node) { + return "null"; + }, + IntValue(node) { + return node.value; + }, + FloatValue(node) { + return node.value; + }, + EnumValue(node) { + return node.value; + }, + Name(node) { + return node.value; + }, + Variable(node) { + return "$" + node.name.value; + }, + ListValue(node) { + return "[" + node.values.map(print).join(", ") + "]"; + }, + ObjectValue(node) { + return "{" + node.fields.map(nodes.ObjectField!).join(", ") + "}"; + }, + ObjectField(node) { + return node.name.value + ": " + print(node.value); + }, + Document(node) { + return hasItems(node.definitions) + ? node.definitions.map(print).join(" ") + : ""; + }, + SelectionSet(node) { + return "{" + node.selections.map(print).join(" ") + "}"; + }, + Argument(node) { + return node.name.value + ": " + print(node.value); + }, + FragmentSpread(node) { + let out = "..." + node.name.value; + if (hasItems(node.directives)) + out += " " + node.directives.map(nodes.Directive!).join(" "); + return out; + }, + InlineFragment(node) { + let out = "..."; + if (node.typeCondition) out += " on " + node.typeCondition.name.value; + if (hasItems(node.directives)) + out += " " + node.directives.map(nodes.Directive!).join(" "); + return out + " " + print(node.selectionSet); + }, + FragmentDefinition(node) { + let out = "fragment " + node.name.value; + out += " on " + node.typeCondition.name.value; + if (hasItems(node.directives)) + out += " " + node.directives.map(nodes.Directive!).join(" "); + return out + " " + print(node.selectionSet); + }, + Directive(node) { + let out = "@" + node.name.value; + if (hasItems(node.arguments)) + out += "(" + node.arguments.map(nodes.Argument!).join(", ") + ")"; + return out; + }, + NamedType(node) { + return node.name.value; + }, + ListType(node) { + return "[" + print(node.type) + "]"; + }, + NonNullType(node) { + return print(node.type) + "!"; + }, +}; + +export const print = (node: ASTNode): string => { + return nodes[node.kind] ? (nodes as any)[node.kind]!(node) : ""; +};