diff --git a/.changeset/shy-oranges-itch.md b/.changeset/shy-oranges-itch.md new file mode 100644 index 0000000..b328857 --- /dev/null +++ b/.changeset/shy-oranges-itch.md @@ -0,0 +1,5 @@ +--- +'benchmark': patch +--- + +Establish type compatibility with `graphql` package. diff --git a/package.json b/package.json index d1d0cce..1a3655e 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "client-side graphql" ], "scripts": { - "test": "vitest", + "test": "vitest run", "check": "tsc", "lint": "eslint --ext=js,ts .", "build": "rollup -c scripts/rollup.config.mjs", diff --git a/src/ast.ts b/src/ast.ts index c71bc4a..a51909a 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,21 +1,31 @@ -import type { Kind } from './kind'; - -export interface Location { - readonly start: number; - readonly end: number; - readonly source: Source; -} - -export interface Source { - body: string; - name: string; - locationOffset: { - line: number; - column: number; - }; -} - -export declare type ASTNode = +import type { Kind, OperationTypeNode } from './kind'; +import type { Location } from './types'; + +import type { + TypeSystemDefinitionNode, + TypeSystemExtensionNode, + SchemaDefinitionNode, + OperationTypeDefinitionNode, + ScalarTypeDefinitionNode, + ObjectTypeDefinitionNode, + FieldDefinitionNode, + InputValueDefinitionNode, + InterfaceTypeDefinitionNode, + UnionTypeDefinitionNode, + EnumTypeDefinitionNode, + EnumValueDefinitionNode, + InputObjectTypeDefinitionNode, + DirectiveDefinitionNode, + SchemaExtensionNode, + ScalarTypeExtensionNode, + ObjectTypeExtensionNode, + InterfaceTypeExtensionNode, + UnionTypeExtensionNode, + EnumTypeExtensionNode, + InputObjectTypeExtensionNode, +} from './schemaAst'; + +export type ASTNode = | NameNode | DocumentNode | OperationDefinitionNode @@ -39,20 +49,44 @@ export declare type ASTNode = | DirectiveNode | NamedTypeNode | ListTypeNode - | NonNullTypeNode; + | NonNullTypeNode + | SchemaDefinitionNode + | OperationTypeDefinitionNode + | ScalarTypeDefinitionNode + | ObjectTypeDefinitionNode + | FieldDefinitionNode + | InputValueDefinitionNode + | InterfaceTypeDefinitionNode + | UnionTypeDefinitionNode + | EnumTypeDefinitionNode + | EnumValueDefinitionNode + | InputObjectTypeDefinitionNode + | DirectiveDefinitionNode + | SchemaExtensionNode + | ScalarTypeExtensionNode + | ObjectTypeExtensionNode + | InterfaceTypeExtensionNode + | UnionTypeExtensionNode + | EnumTypeExtensionNode + | InputObjectTypeExtensionNode; export interface NameNode { readonly kind: Kind.NAME; readonly value: string; + readonly loc?: Location; } export interface DocumentNode { readonly kind: Kind.DOCUMENT; - readonly definitions: ReadonlyArray; + readonly definitions: ReadonlyArray; readonly loc?: Location; } -export type DefinitionNode = OperationDefinitionNode | FragmentDefinitionNode; +export type DefinitionNode = + | ExecutableDefinitionNode + | TypeSystemDefinitionNode + | TypeSystemExtensionNode; + export type ExecutableDefinitionNode = OperationDefinitionNode | FragmentDefinitionNode; export interface OperationDefinitionNode { @@ -62,12 +96,7 @@ export interface OperationDefinitionNode { readonly variableDefinitions?: ReadonlyArray; readonly directives?: ReadonlyArray; readonly selectionSet: SelectionSetNode; -} - -export const enum OperationTypeNode { - QUERY = 'query', - MUTATION = 'mutation', - SUBSCRIPTION = 'subscription', + readonly loc?: Location; } export interface VariableDefinitionNode { @@ -76,16 +105,19 @@ export interface VariableDefinitionNode { readonly type: TypeNode; readonly defaultValue?: ConstValueNode; readonly directives?: ReadonlyArray; + readonly loc?: Location; } export interface VariableNode { readonly kind: Kind.VARIABLE; readonly name: NameNode; + readonly loc?: Location; } export interface SelectionSetNode { - kind: Kind.SELECTION_SET; - selections: ReadonlyArray; + readonly kind: Kind.SELECTION_SET; + readonly selections: ReadonlyArray; + readonly loc?: Location; } export declare type SelectionNode = FieldNode | FragmentSpreadNode | InlineFragmentNode; @@ -97,24 +129,28 @@ export interface FieldNode { readonly arguments?: ReadonlyArray; readonly directives?: ReadonlyArray; readonly selectionSet?: SelectionSetNode; + readonly loc?: Location; } export interface ArgumentNode { readonly kind: Kind.ARGUMENT; readonly name: NameNode; readonly value: ValueNode; + readonly loc?: Location; } export interface ConstArgumentNode { readonly kind: Kind.ARGUMENT; readonly name: NameNode; readonly value: ConstValueNode; + readonly loc?: Location; } export interface FragmentSpreadNode { readonly kind: Kind.FRAGMENT_SPREAD; readonly name: NameNode; readonly directives?: ReadonlyArray; + readonly loc?: Location; } export interface InlineFragmentNode { @@ -122,6 +158,7 @@ export interface InlineFragmentNode { readonly typeCondition?: NamedTypeNode; readonly directives?: ReadonlyArray; readonly selectionSet: SelectionSetNode; + readonly loc?: Location; } export interface FragmentDefinitionNode { @@ -130,6 +167,7 @@ export interface FragmentDefinitionNode { readonly typeCondition: NamedTypeNode; readonly directives?: ReadonlyArray; readonly selectionSet: SelectionSetNode; + readonly loc?: Location; } export type ValueNode = @@ -156,74 +194,89 @@ export type ConstValueNode = export interface IntValueNode { readonly kind: Kind.INT; readonly value: string; + readonly loc?: Location; } export interface FloatValueNode { readonly kind: Kind.FLOAT; readonly value: string; + readonly loc?: Location; } + export interface StringValueNode { readonly kind: Kind.STRING; readonly value: string; readonly block?: boolean; + readonly loc?: Location; } export interface BooleanValueNode { readonly kind: Kind.BOOLEAN; readonly value: boolean; + readonly loc?: Location; } export interface NullValueNode { readonly kind: Kind.NULL; + readonly loc?: Location; } export interface EnumValueNode { readonly kind: Kind.ENUM; readonly value: string; + readonly loc?: Location; } export interface ListValueNode { readonly kind: Kind.LIST; readonly values: ReadonlyArray; + readonly loc?: Location; } export interface ConstListValueNode { readonly kind: Kind.LIST; readonly values: ReadonlyArray; + readonly loc?: Location; } export interface ObjectValueNode { readonly kind: Kind.OBJECT; readonly fields: ReadonlyArray; + readonly loc?: Location; } export interface ConstObjectValueNode { readonly kind: Kind.OBJECT; readonly fields: ReadonlyArray; + readonly loc?: Location; } export interface ObjectFieldNode { readonly kind: Kind.OBJECT_FIELD; readonly name: NameNode; readonly value: ValueNode; + readonly loc?: Location; } export interface ConstObjectFieldNode { readonly kind: Kind.OBJECT_FIELD; readonly name: NameNode; readonly value: ConstValueNode; + readonly loc?: Location; } export interface DirectiveNode { readonly kind: Kind.DIRECTIVE; readonly name: NameNode; readonly arguments?: ReadonlyArray; + readonly loc?: Location; } export interface ConstDirectiveNode { readonly kind: Kind.DIRECTIVE; readonly name: NameNode; readonly arguments?: ReadonlyArray; + readonly loc?: Location; } export declare type TypeNode = NamedTypeNode | ListTypeNode | NonNullTypeNode; @@ -231,14 +284,17 @@ export declare type TypeNode = NamedTypeNode | ListTypeNode | NonNullTypeNode; export interface NamedTypeNode { readonly kind: Kind.NAMED_TYPE; readonly name: NameNode; + readonly loc?: Location; } export interface ListTypeNode { readonly kind: Kind.LIST_TYPE; readonly type: TypeNode; + readonly loc?: Location; } export interface NonNullTypeNode { readonly kind: Kind.NON_NULL_TYPE; readonly type: NamedTypeNode | ListTypeNode; + readonly loc?: Location; } diff --git a/src/error.ts b/src/error.ts index a7f22ec..6d37492 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,10 +1,10 @@ -import { Maybe, Extensions } from './types'; -import { ASTNode, Source } from './ast'; +import { Maybe, Extensions, Source } from './types'; +import { ASTNode } from './ast'; export class GraphQLError extends Error { - readonly locations: undefined; + readonly locations: ReadonlyArray | undefined; readonly path: ReadonlyArray | undefined; - readonly nodes: ReadonlyArray | undefined; + readonly nodes: ReadonlyArray | undefined; readonly source: Source | undefined; readonly positions: ReadonlyArray | undefined; readonly originalError: Error | undefined; @@ -41,11 +41,15 @@ export class GraphQLError extends Error { this.extensions = extensions || {}; } - toJSON() { + toJSON(): any { return { ...this }; } toString() { return this.message; } + + get [Symbol.toStringTag]() { + return 'GraphQLError'; + } } diff --git a/src/index.ts b/src/index.ts index 9174101..aa1064d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,7 @@ +export type { Source, Location } from './types'; + export * from './ast'; +export * from './schemaAst'; export * from './kind'; export * from './error'; export * from './parser'; diff --git a/src/kind.d.ts b/src/kind.d.ts new file mode 100644 index 0000000..ae9fd06 --- /dev/null +++ b/src/kind.d.ts @@ -0,0 +1,62 @@ +export declare enum Kind { + /** Name */ + NAME = 'Name', + /** Document */ + DOCUMENT = 'Document', + OPERATION_DEFINITION = 'OperationDefinition', + VARIABLE_DEFINITION = 'VariableDefinition', + SELECTION_SET = 'SelectionSet', + FIELD = 'Field', + ARGUMENT = 'Argument', + /** Fragments */ + FRAGMENT_SPREAD = 'FragmentSpread', + INLINE_FRAGMENT = 'InlineFragment', + FRAGMENT_DEFINITION = 'FragmentDefinition', + /** Values */ + VARIABLE = 'Variable', + INT = 'IntValue', + FLOAT = 'FloatValue', + STRING = 'StringValue', + BOOLEAN = 'BooleanValue', + NULL = 'NullValue', + ENUM = 'EnumValue', + LIST = 'ListValue', + OBJECT = 'ObjectValue', + OBJECT_FIELD = 'ObjectField', + /** Directives */ + DIRECTIVE = 'Directive', + /** Types */ + NAMED_TYPE = 'NamedType', + LIST_TYPE = 'ListType', + NON_NULL_TYPE = 'NonNullType', + /** Type System Definitions */ + SCHEMA_DEFINITION = 'SchemaDefinition', + OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition', + /** Type Definitions */ + SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition', + OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition', + FIELD_DEFINITION = 'FieldDefinition', + INPUT_VALUE_DEFINITION = 'InputValueDefinition', + INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition', + UNION_TYPE_DEFINITION = 'UnionTypeDefinition', + ENUM_TYPE_DEFINITION = 'EnumTypeDefinition', + ENUM_VALUE_DEFINITION = 'EnumValueDefinition', + INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition', + /** Directive Definitions */ + DIRECTIVE_DEFINITION = 'DirectiveDefinition', + /** Type System Extensions */ + SCHEMA_EXTENSION = 'SchemaExtension', + /** Type Extensions */ + SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension', + OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension', + INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension', + UNION_TYPE_EXTENSION = 'UnionTypeExtension', + ENUM_TYPE_EXTENSION = 'EnumTypeExtension', + INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension', +} + +export declare enum OperationTypeNode { + QUERY = 'query', + MUTATION = 'mutation', + SUBSCRIPTION = 'subscription', +} diff --git a/src/kind.js b/src/kind.js new file mode 100644 index 0000000..4c0ca04 --- /dev/null +++ b/src/kind.js @@ -0,0 +1,47 @@ +export const Kind = { + NAME: 'Name', + DOCUMENT: 'Document', + OPERATION_DEFINITION: 'OperationDefinition', + VARIABLE_DEFINITION: 'VariableDefinition', + SELECTION_SET: 'SelectionSet', + FIELD: 'Field', + ARGUMENT: 'Argument', + FRAGMENT_SPREAD: 'FragmentSpread', + INLINE_FRAGMENT: 'InlineFragment', + FRAGMENT_DEFINITION: 'FragmentDefinition', + VARIABLE: 'Variable', + OBJECT: 'ObjectValue', + OBJECT_FIELD: 'ObjectField', + DIRECTIVE: 'Directive', + NAMED_TYPE: 'NamedType', + LIST_TYPE: 'ListType', + NON_NULL_TYPE: 'NonNullType', + + /* + SCHEMA_DEFINITION: 'SchemaDefinition', + OPERATION_TYPE_DEFINITION: 'OperationTypeDefinition', + SCALAR_TYPE_DEFINITION: 'ScalarTypeDefinition', + OBJECT_TYPE_DEFINITION: 'ObjectTypeDefinition', + FIELD_DEFINITION: 'FieldDefinition', + INPUT_VALUE_DEFINITION: 'InputValueDefinition', + INTERFACE_TYPE_DEFINITION: 'InterfaceTypeDefinition', + UNION_TYPE_DEFINITION: 'UnionTypeDefinition', + ENUM_TYPE_DEFINITION: 'EnumTypeDefinition', + ENUM_VALUE_DEFINITION: 'EnumValueDefinition', + INPUT_OBJECT_TYPE_DEFINITION: 'InputObjectTypeDefinition', + DIRECTIVE_DEFINITION: 'DirectiveDefinition', + SCHEMA_EXTENSION: 'SchemaExtension', + SCALAR_TYPE_EXTENSION: 'ScalarTypeExtension', + OBJECT_TYPE_EXTENSION: 'ObjectTypeExtension', + INTERFACE_TYPE_EXTENSION: 'InterfaceTypeExtension', + UNION_TYPE_EXTENSION: 'UnionTypeExtension', + ENUM_TYPE_EXTENSION: 'EnumTypeExtension', + INPUT_OBJECT_TYPE_EXTENSION: 'InputObjectTypeExtension', + */ +}; + +export const OperationTypeNode = { + QUERY: 'query', + MUTATION: 'mutation', + SUBSCRIPTION: 'subscription', +}; diff --git a/src/kind.ts b/src/kind.ts deleted file mode 100644 index cbe6d74..0000000 --- a/src/kind.ts +++ /dev/null @@ -1,32 +0,0 @@ -export enum Kind { - /** Name */ - NAME = 'Name', - /** Document */ - DOCUMENT = 'Document', - OPERATION_DEFINITION = 'OperationDefinition', - VARIABLE_DEFINITION = 'VariableDefinition', - SELECTION_SET = 'SelectionSet', - FIELD = 'Field', - ARGUMENT = 'Argument', - /** Fragments */ - FRAGMENT_SPREAD = 'FragmentSpread', - INLINE_FRAGMENT = 'InlineFragment', - FRAGMENT_DEFINITION = 'FragmentDefinition', - /** Values */ - VARIABLE = 'Variable', - INT = 'IntValue', - FLOAT = 'FloatValue', - STRING = 'StringValue', - BOOLEAN = 'BooleanValue', - NULL = 'NullValue', - ENUM = 'EnumValue', - LIST = 'ListValue', - OBJECT = 'ObjectValue', - OBJECT_FIELD = 'ObjectField', - /** Directives */ - DIRECTIVE = 'Directive', - /** Types */ - NAMED_TYPE = 'NamedType', - LIST_TYPE = 'ListType', - NON_NULL_TYPE = 'NonNullType', -} diff --git a/src/parser.ts b/src/parser.ts index 77fd94f..fb0838f 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -4,14 +4,15 @@ * in graphql.js it will only parse the query language, but not the schema * language. */ -import { Kind } from './kind'; +import { Kind, OperationTypeNode } from './kind'; import { GraphQLError } from './error'; +import { Source } from './types'; import type * as ast from './ast'; let input: string; let idx: number; -function error(kind: Kind) { +function error(kind: string) { return new GraphQLError(`Syntax Error: Unexpected token at ${idx} in ${kind}`); } @@ -58,14 +59,13 @@ function name(): ast.NameNode | undefined { let match: string | undefined; if ((match = advance(nameRe))) { return { - kind: Kind.NAME, + kind: 'Name' as Kind.NAME, value: match, }; } } -const nullRe = /null/y; -const boolRe = /true|false/y; +const constRe = /null|true|false/y; const variableRe = /\$[_\w][_\d\w]*/y; const intRe = /[-]?\d+/y; const floatRe = /(?:[-]?\d+)?(?:\.\d+)(?:[eE][+-]?\d+)?/y; @@ -79,45 +79,48 @@ function value(constant: boolean): ast.ValueNode; function value(constant: boolean): ast.ValueNode | undefined { let out: ast.ValueNode | undefined; let match: string | undefined; - if (advance(nullRe)) { - out = { kind: Kind.NULL }; - } else if ((match = advance(boolRe))) { - out = { - kind: Kind.BOOLEAN, - value: match === 'true', - }; + if ((match = advance(constRe))) { + out = + match === 'null' + ? { + kind: 'NullValue' as Kind.NULL, + } + : { + kind: 'BooleanValue' as Kind.BOOLEAN, + value: match === 'true', + }; } else if (!constant && (match = advance(variableRe))) { out = { - kind: Kind.VARIABLE, + kind: 'Variable' as Kind.VARIABLE, name: { - kind: Kind.NAME, + kind: 'Name' as Kind.NAME, value: match.slice(1), }, }; } else if ((match = advance(floatRe))) { out = { - kind: Kind.FLOAT, + kind: 'FloatValue' as Kind.FLOAT, value: match, }; } else if ((match = advance(intRe))) { out = { - kind: Kind.INT, + kind: 'IntValue' as Kind.INT, value: match, }; } else if ((match = advance(nameRe))) { out = { - kind: Kind.ENUM, + kind: 'EnumValue' as Kind.ENUM, value: match, }; } else if ((match = advance(blockStringRe))) { out = { - kind: Kind.STRING, + kind: 'StringValue' as Kind.STRING, value: blockString(match.slice(3, -3)), block: true, }; } else if ((match = advance(stringRe))) { out = { - kind: Kind.STRING, + kind: 'StringValue' as Kind.STRING, value: complexStringRe.test(match) ? (JSON.parse(match) as string) : match.slice(1, -1), block: false, }; @@ -136,10 +139,10 @@ function list(constant: boolean): ast.ListValueNode | undefined { ignored(); const values: ast.ValueNode[] = []; while ((match = value(constant))) values.push(match); - if (input.charCodeAt(idx++) !== 93 /*']'*/) throw error(Kind.LIST); + if (input.charCodeAt(idx++) !== 93 /*']'*/) throw error('ListValue'); ignored(); return { - kind: Kind.LIST, + kind: 'ListValue' as Kind.LIST, values, }; } @@ -153,20 +156,20 @@ function object(constant: boolean): ast.ObjectValueNode | undefined { let _name: ast.NameNode | undefined; while ((_name = name())) { ignored(); - if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error(Kind.OBJECT_FIELD); + if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('ObjectField' as Kind.OBJECT_FIELD); ignored(); const _value = value(constant); - if (!_value) throw error(Kind.OBJECT_FIELD); + if (!_value) throw error('ObjectField'); fields.push({ - kind: Kind.OBJECT_FIELD, + kind: 'ObjectField' as Kind.OBJECT_FIELD, name: _name, value: _value, }); } - if (input.charCodeAt(idx++) !== 125 /*'}'*/) throw error(Kind.OBJECT); + if (input.charCodeAt(idx++) !== 125 /*'}'*/) throw error('ObjectValue'); ignored(); return { - kind: Kind.OBJECT, + kind: 'ObjectValue' as Kind.OBJECT, fields, }; } @@ -181,17 +184,17 @@ function arguments_(constant: boolean): ast.ArgumentNode[] { let _name: ast.NameNode | undefined; while ((_name = name())) { ignored(); - if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error(Kind.ARGUMENT); + if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('Argument'); ignored(); const _value = value(constant); - if (!_value) throw error(Kind.ARGUMENT); + if (!_value) throw error('Argument'); args.push({ - kind: Kind.ARGUMENT, + kind: 'Argument' as Kind.ARGUMENT, name: _name, value: _value, }); } - if (!args.length || input.charCodeAt(idx++) !== 41 /*')'*/) throw error(Kind.ARGUMENT); + if (!args.length || input.charCodeAt(idx++) !== 41 /*')'*/) throw error('Argument'); ignored(); } return args; @@ -206,10 +209,10 @@ function directives(constant: boolean): ast.DirectiveNode[] { while (input.charCodeAt(idx) === 64 /*'@'*/) { idx++; const _name = name(); - if (!_name) throw error(Kind.DIRECTIVE); + if (!_name) throw error('Directive'); ignored(); directives.push({ - kind: Kind.DIRECTIVE, + kind: 'Directive' as Kind.DIRECTIVE, name: _name, arguments: arguments_(constant), }); @@ -228,11 +231,11 @@ function field(): ast.FieldNode | undefined { ignored(); _alias = _name; _name = name(); - if (!_name) throw error(Kind.FIELD); + if (!_name) throw error('Field'); ignored(); } return { - kind: Kind.FIELD, + kind: 'Field' as Kind.FIELD, alias: _alias, name: _name, arguments: arguments_(false), @@ -249,18 +252,18 @@ function type(): ast.TypeNode | undefined { idx++; ignored(); const _type = type(); - if (!_type || input.charCodeAt(idx++) !== 93 /*']'*/) throw error(Kind.LIST_TYPE); + if (!_type || input.charCodeAt(idx++) !== 93 /*']'*/) throw error('ListType'); match = { - kind: Kind.LIST_TYPE, + kind: 'ListType' as Kind.LIST_TYPE, type: _type, }; } else if ((match = name())) { match = { - kind: Kind.NAMED_TYPE, + kind: 'NamedType' as Kind.NAMED_TYPE, name: match, }; } else { - throw error(Kind.NAMED_TYPE); + throw error('NamedType'); } ignored(); @@ -268,7 +271,7 @@ function type(): ast.TypeNode | undefined { idx++; ignored(); return { - kind: Kind.NON_NULL_TYPE, + kind: 'NonNullType' as Kind.NON_NULL_TYPE, type: match, }; } else { @@ -281,10 +284,10 @@ function typeCondition(): ast.NamedTypeNode | undefined { if (advance(typeConditionRe)) { ignored(); const _name = name(); - if (!_name) throw error(Kind.NAMED_TYPE); + if (!_name) throw error('NamedType'); ignored(); return { - kind: Kind.NAMED_TYPE, + kind: 'NamedType' as Kind.NAMED_TYPE, name: _name, }; } @@ -300,7 +303,7 @@ function fragmentSpread(): ast.FragmentSpreadNode | ast.InlineFragmentNode | und let _name: ast.NameNode | undefined; if ((_name = name()) && _name.value !== 'on') { return { - kind: Kind.FRAGMENT_SPREAD, + kind: 'FragmentSpread' as Kind.FRAGMENT_SPREAD, name: _name, directives: directives(false), }; @@ -309,9 +312,9 @@ function fragmentSpread(): ast.FragmentSpreadNode | ast.InlineFragmentNode | und const _typeCondition = typeCondition(); const _directives = directives(false); const _selectionSet = selectionSet(); - if (!_selectionSet) throw error(Kind.INLINE_FRAGMENT); + if (!_selectionSet) throw error('InlineFragment'); return { - kind: Kind.INLINE_FRAGMENT, + kind: 'InlineFragment' as Kind.INLINE_FRAGMENT, typeCondition: _typeCondition, directives: _directives, selectionSet: _selectionSet, @@ -328,11 +331,10 @@ function selectionSet(): ast.SelectionSetNode | undefined { ignored(); const selections: ast.SelectionNode[] = []; while ((match = fragmentSpread() || field())) selections.push(match); - if (!selections.length || input.charCodeAt(idx++) !== 125 /*'}'*/) - throw error(Kind.SELECTION_SET); + if (!selections.length || input.charCodeAt(idx++) !== 125 /*'}'*/) throw error('SelectionSet'); ignored(); return { - kind: Kind.SELECTION_SET, + kind: 'SelectionSet' as Kind.SELECTION_SET, selections, }; } @@ -347,23 +349,23 @@ function variableDefinitions(): ast.VariableDefinitionNode[] { ignored(); while ((match = advance(variableRe))) { ignored(); - if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error(Kind.VARIABLE_DEFINITION); + if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('VariableDefinition'); const _type = type(); - if (!_type) throw error(Kind.VARIABLE_DEFINITION); + if (!_type) throw error('VariableDefinition'); let _defaultValue: ast.ValueNode | undefined; if (input.charCodeAt(idx) === 61 /*'='*/) { idx++; ignored(); _defaultValue = value(true); - if (!_defaultValue) throw error(Kind.VARIABLE_DEFINITION); + if (!_defaultValue) throw error('VariableDefinition'); } ignored(); vars.push({ - kind: Kind.VARIABLE_DEFINITION, + kind: 'VariableDefinition' as Kind.VARIABLE_DEFINITION, variable: { - kind: Kind.VARIABLE, + kind: 'Variable' as Kind.VARIABLE, name: { - kind: Kind.NAME, + kind: 'Name' as Kind.NAME, value: match.slice(1), }, }, @@ -372,7 +374,7 @@ function variableDefinitions(): ast.VariableDefinitionNode[] { directives: directives(true), }); } - if (input.charCodeAt(idx++) !== 41 /*')'*/) throw error(Kind.VARIABLE_DEFINITION); + if (input.charCodeAt(idx++) !== 41 /*')'*/) throw error('VariableDefinition'); ignored(); } return vars; @@ -383,15 +385,15 @@ function fragmentDefinition(): ast.FragmentDefinitionNode | undefined { if (advance(fragmentDefinitionRe)) { ignored(); const _name = name(); - if (!_name) throw error(Kind.FRAGMENT_DEFINITION); + if (!_name) throw error('FragmentDefinition'); ignored(); const _typeCondition = typeCondition(); - if (!_typeCondition) throw error(Kind.FRAGMENT_DEFINITION); + if (!_typeCondition) throw error('FragmentDefinition'); const _directives = directives(false); const _selectionSet = selectionSet(); - if (!_selectionSet) throw error(Kind.FRAGMENT_DEFINITION); + if (!_selectionSet) throw error('FragmentDefinition'); return { - kind: Kind.FRAGMENT_DEFINITION, + kind: 'FragmentDefinition' as Kind.FRAGMENT_DEFINITION, name: _name, typeCondition: _typeCondition, directives: _directives, @@ -415,8 +417,8 @@ function operationDefinition(): ast.OperationDefinitionNode | undefined { const _selectionSet = selectionSet(); if (_selectionSet) { return { - kind: Kind.OPERATION_DEFINITION, - operation: (_operation || 'query') as ast.OperationTypeNode, + kind: 'OperationDefinition' as Kind.OPERATION_DEFINITION, + operation: (_operation || 'query') as OperationTypeNode, name: _name, variableDefinitions: _variableDefinitions, directives: _directives, @@ -426,32 +428,49 @@ function operationDefinition(): ast.OperationDefinitionNode | undefined { } function document(): ast.DocumentNode { - let match: ast.DefinitionNode | void; + let match: ast.ExecutableDefinitionNode | void; ignored(); - const definitions: ast.DefinitionNode[] = []; + const definitions: ast.ExecutableDefinitionNode[] = []; while ((match = fragmentDefinition() || operationDefinition())) definitions.push(match); - if (idx !== input.length) throw error(Kind.DOCUMENT); + if (idx !== input.length) throw error('Document'); return { - kind: Kind.DOCUMENT, + kind: 'Document' as Kind.DOCUMENT, definitions, }; } -export function parse(string: string): ast.DocumentNode { - input = string; +type ParseOptions = { + [option: string]: any; +}; + +export function parse( + string: string | Source, + _options?: ParseOptions | undefined +): ast.DocumentNode { + input = typeof string.body === 'string' ? string.body : string; idx = 0; return document(); } -export function parseValue(string: string): ast.ValueNode | undefined { - input = string; +export function parseValue( + string: string | Source, + _options?: ParseOptions | undefined +): ast.ValueNode { + input = typeof string.body === 'string' ? string.body : string; idx = 0; ignored(); - return value(false); + const _value = value(false); + if (!_value) throw error('ValueNode'); + return _value; } -export function parseType(string: string): ast.TypeNode | undefined { - input = string; +export function parseType( + string: string | Source, + _options?: ParseOptions | undefined +): ast.TypeNode { + input = typeof string.body === 'string' ? string.body : string; idx = 0; - return type(); + const _type = type(); + if (!_type) throw error('TypeNode'); + return _type; } diff --git a/src/printer.ts b/src/printer.ts index 87028f7..52cfdc8 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -1,4 +1,3 @@ -import { Kind } from './kind'; import { ASTNode } from './ast'; export function printString(string: string) { @@ -17,7 +16,7 @@ const MAX_LINE_LENGTH = 80; export function print(node: ASTNode): string { let out: string; switch (node.kind) { - case Kind.OPERATION_DEFINITION: + case 'OperationDefinition': if ( node.operation === 'query' && !node.name && @@ -35,13 +34,13 @@ export function print(node: ASTNode): string { if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' '); return out + ' ' + print(node.selectionSet); - case Kind.VARIABLE_DEFINITION: + case 'VariableDefinition': out = print(node.variable) + ': ' + print(node.type); if (node.defaultValue) out += ' = ' + print(node.defaultValue); if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' '); return out; - case Kind.FIELD: + case 'Field': out = (node.alias ? print(node.alias) + ': ' : '') + node.name.value; if (hasItems(node.arguments)) { const args = node.arguments.map(print); @@ -54,71 +53,74 @@ export function print(node: ASTNode): string { if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' '); return node.selectionSet ? out + ' ' + print(node.selectionSet) : out; - case Kind.STRING: + case 'StringValue': return node.block ? printBlockString(node.value) : printString(node.value); - case Kind.BOOLEAN: + case 'BooleanValue': return '' + node.value; - case Kind.NULL: + case 'NullValue': return 'null'; - case Kind.INT: - case Kind.FLOAT: - case Kind.ENUM: - case Kind.NAME: + case 'IntValue': + case 'FloatValue': + case 'EnumValue': + case 'Name': return node.value; - case Kind.LIST: + case 'ListValue': return '[' + node.values.map(print).join(', ') + ']'; - case Kind.OBJECT: + case 'ObjectValue': return '{' + node.fields.map(print).join(', ') + '}'; - case Kind.OBJECT_FIELD: + case 'ObjectField': return node.name.value + ': ' + print(node.value); - case Kind.VARIABLE: + case 'Variable': return '$' + node.name.value; - case Kind.DOCUMENT: + case 'Document': return hasItems(node.definitions) ? node.definitions.map(print).join('\n\n') : ''; - case Kind.SELECTION_SET: + case 'SelectionSet': return '{\n ' + node.selections.map(print).join('\n').replace(/\n/g, '\n ') + '\n}'; - case Kind.ARGUMENT: + case 'Argument': return node.name.value + ': ' + print(node.value); - case Kind.FRAGMENT_SPREAD: + case 'FragmentSpread': out = '...' + node.name.value; if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' '); return out; - case Kind.INLINE_FRAGMENT: + case 'InlineFragment': out = '...'; if (node.typeCondition) out += ' on ' + node.typeCondition.name.value; if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' '); return out + ' ' + print(node.selectionSet); - case Kind.FRAGMENT_DEFINITION: + case 'FragmentDefinition': out = 'fragment ' + node.name.value; out += ' on ' + node.typeCondition.name.value; if (hasItems(node.directives)) out += ' ' + node.directives.map(print).join(' '); return out + ' ' + print(node.selectionSet); - case Kind.DIRECTIVE: + case 'Directive': out = '@' + node.name.value; if (hasItems(node.arguments)) out += '(' + node.arguments.map(print).join(', ') + ')'; return out; - case Kind.NAMED_TYPE: + case 'NamedType': return node.name.value; - case Kind.LIST_TYPE: + case 'ListType': return '[' + print(node.type) + ']'; - case Kind.NON_NULL_TYPE: + case 'NonNullType': return print(node.type) + '!'; + + default: + return ''; } } diff --git a/src/schemaAst.ts b/src/schemaAst.ts new file mode 100644 index 0000000..dc00639 --- /dev/null +++ b/src/schemaAst.ts @@ -0,0 +1,198 @@ +import type { Location } from './types'; +import type { Kind, OperationTypeNode } from './kind'; + +import type { + StringValueNode, + ConstDirectiveNode, + ConstValueNode, + NamedTypeNode, + TypeNode, + NameNode, +} from './ast'; + +/** Type System Definition */ +export declare type TypeSystemDefinitionNode = + | SchemaDefinitionNode + | TypeDefinitionNode + | DirectiveDefinitionNode; + +export interface SchemaDefinitionNode { + readonly kind: Kind.SCHEMA_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly directives?: ReadonlyArray; + readonly operationTypes: ReadonlyArray; +} + +export interface OperationTypeDefinitionNode { + readonly kind: Kind.OPERATION_TYPE_DEFINITION; + readonly loc?: Location; + readonly operation: OperationTypeNode; + readonly type: NamedTypeNode; +} + +/** Type Definition */ +export declare type TypeDefinitionNode = + | ScalarTypeDefinitionNode + | ObjectTypeDefinitionNode + | InterfaceTypeDefinitionNode + | UnionTypeDefinitionNode + | EnumTypeDefinitionNode + | InputObjectTypeDefinitionNode; + +export interface ScalarTypeDefinitionNode { + readonly kind: Kind.SCALAR_TYPE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly directives?: ReadonlyArray; +} + +export interface ObjectTypeDefinitionNode { + readonly kind: Kind.OBJECT_TYPE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly interfaces?: ReadonlyArray; + readonly directives?: ReadonlyArray; + readonly fields?: ReadonlyArray; +} + +export interface FieldDefinitionNode { + readonly kind: Kind.FIELD_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly arguments?: ReadonlyArray; + readonly type: TypeNode; + readonly directives?: ReadonlyArray; +} + +export interface InputValueDefinitionNode { + readonly kind: Kind.INPUT_VALUE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly type: TypeNode; + readonly defaultValue?: ConstValueNode; + readonly directives?: ReadonlyArray; +} + +export interface InterfaceTypeDefinitionNode { + readonly kind: Kind.INTERFACE_TYPE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly interfaces?: ReadonlyArray; + readonly directives?: ReadonlyArray; + readonly fields?: ReadonlyArray; +} + +export interface UnionTypeDefinitionNode { + readonly kind: Kind.UNION_TYPE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly directives?: ReadonlyArray; + readonly types?: ReadonlyArray; +} + +export interface EnumTypeDefinitionNode { + readonly kind: Kind.ENUM_TYPE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly directives?: ReadonlyArray; + readonly values?: ReadonlyArray; +} + +export interface EnumValueDefinitionNode { + readonly kind: Kind.ENUM_VALUE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly directives?: ReadonlyArray; +} + +export interface InputObjectTypeDefinitionNode { + readonly kind: Kind.INPUT_OBJECT_TYPE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly directives?: ReadonlyArray; + readonly fields?: ReadonlyArray; +} +/** Directive Definitions */ +export interface DirectiveDefinitionNode { + readonly kind: Kind.DIRECTIVE_DEFINITION; + readonly loc?: Location; + readonly description?: StringValueNode; + readonly name: NameNode; + readonly arguments?: ReadonlyArray; + readonly repeatable: boolean; + readonly locations: ReadonlyArray; +} +/** Type System Extensions */ +export declare type TypeSystemExtensionNode = SchemaExtensionNode | TypeExtensionNode; +export interface SchemaExtensionNode { + readonly kind: Kind.SCHEMA_EXTENSION; + readonly loc?: Location; + readonly directives?: ReadonlyArray; + readonly operationTypes?: ReadonlyArray; +} +/** Type Extensions */ +export declare type TypeExtensionNode = + | ScalarTypeExtensionNode + | ObjectTypeExtensionNode + | InterfaceTypeExtensionNode + | UnionTypeExtensionNode + | EnumTypeExtensionNode + | InputObjectTypeExtensionNode; +export interface ScalarTypeExtensionNode { + readonly kind: Kind.SCALAR_TYPE_EXTENSION; + readonly loc?: Location; + readonly name: NameNode; + readonly directives?: ReadonlyArray; +} + +export interface ObjectTypeExtensionNode { + readonly kind: Kind.OBJECT_TYPE_EXTENSION; + readonly loc?: Location; + readonly name: NameNode; + readonly interfaces?: ReadonlyArray; + readonly directives?: ReadonlyArray; + readonly fields?: ReadonlyArray; +} + +export interface InterfaceTypeExtensionNode { + readonly kind: Kind.INTERFACE_TYPE_EXTENSION; + readonly loc?: Location; + readonly name: NameNode; + readonly interfaces?: ReadonlyArray; + readonly directives?: ReadonlyArray; + readonly fields?: ReadonlyArray; +} + +export interface UnionTypeExtensionNode { + readonly kind: Kind.UNION_TYPE_EXTENSION; + readonly loc?: Location; + readonly name: NameNode; + readonly directives?: ReadonlyArray; + readonly types?: ReadonlyArray; +} + +export interface EnumTypeExtensionNode { + readonly kind: Kind.ENUM_TYPE_EXTENSION; + readonly loc?: Location; + readonly name: NameNode; + readonly directives?: ReadonlyArray; + readonly values?: ReadonlyArray; +} + +export interface InputObjectTypeExtensionNode { + readonly kind: Kind.INPUT_OBJECT_TYPE_EXTENSION; + readonly loc?: Location; + readonly name: NameNode; + readonly directives?: ReadonlyArray; + readonly fields?: ReadonlyArray; +} diff --git a/src/types.ts b/src/types.ts index 0849490..fa5c1ef 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,3 +3,22 @@ export type Maybe = T | undefined | null; export interface Extensions { [extension: string]: unknown; } + +export type Source = + | any + | { + body: string; + name: string; + locationOffset: { + line: number; + column: number; + }; + }; + +export type Location = + | any + | { + start: number; + end: number; + source: Source; + }; diff --git a/src/values.ts b/src/values.ts index 16261af..79f4d9c 100644 --- a/src/values.ts +++ b/src/values.ts @@ -1,5 +1,4 @@ import { TypeNode, ValueNode } from './ast'; -import { Kind } from './kind'; import { Maybe } from './types'; export function valueFromASTUntyped( @@ -7,28 +6,28 @@ export function valueFromASTUntyped( variables?: Maybe> ): unknown { switch (node.kind) { - case Kind.NULL: + case 'NullValue': return null; - case Kind.INT: + case 'IntValue': return parseInt(node.value, 10); - case Kind.FLOAT: + case 'FloatValue': return parseFloat(node.value); - case Kind.STRING: - case Kind.ENUM: - case Kind.BOOLEAN: + case 'StringValue': + case 'EnumValue': + case 'BooleanValue': return node.value; - case Kind.LIST: { + case 'ListValue': { const values: unknown[] = []; for (const value of node.values) values.push(valueFromASTUntyped(value, variables)); return values; } - case Kind.OBJECT: { + case 'ObjectValue': { const obj = Object.create(null); for (const field of node.fields) obj[field.name.value] = valueFromASTUntyped(field.value, variables); return obj; } - case Kind.VARIABLE: + case 'Variable': return variables && variables[node.name.value]; } } @@ -38,15 +37,15 @@ export function valueFromTypeNode( type: TypeNode, variables?: Maybe> ): unknown { - if (node.kind === Kind.VARIABLE) { + if (node.kind === 'Variable') { const variableName = node.name.value; return variables ? valueFromTypeNode(variables[variableName], type, variables) : undefined; - } else if (type.kind === Kind.NON_NULL_TYPE) { - return node.kind !== Kind.NULL ? valueFromTypeNode(node, type, variables) : undefined; - } else if (node.kind === Kind.NULL) { + } else if (type.kind === 'NonNullType') { + return node.kind !== 'NullValue' ? valueFromTypeNode(node, type, variables) : undefined; + } else if (node.kind === 'NullValue') { return null; - } else if (type.kind === Kind.LIST_TYPE) { - if (node.kind === Kind.LIST) { + } else if (type.kind === 'ListType') { + if (node.kind === 'ListValue') { const values: unknown[] = []; for (const value of node.values) { const coerced = valueFromTypeNode(value, type.type, variables); @@ -58,7 +57,7 @@ export function valueFromTypeNode( } return values; } - } else if (type.kind === Kind.NAMED_TYPE) { + } else if (type.kind === 'NamedType') { switch (type.name.value) { case 'Int': case 'Float': diff --git a/tsconfig.json b/tsconfig.json index 1b82018..e3df0d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,9 +5,9 @@ "noEmit": true, "esModuleInterop": true, "noUnusedLocals": true, - "rootDir": "./src", + "rootDir": ".", "baseUrl": ".", - "outDir": "dist/cjs", + "allowJs": true, "lib": ["dom", "esnext"], "jsx": "react", "declaration": false, @@ -20,5 +20,5 @@ "skipLibCheck": true, "isolatedModules": true }, - "include": ["src"] + "include": ["src", "typings"] } diff --git a/typings/document.ts b/typings/document.ts new file mode 100644 index 0000000..790bccd --- /dev/null +++ b/typings/document.ts @@ -0,0 +1,18 @@ +import type * as graphql from 'graphql'; +import type * as graphqlWeb from '../src/index'; + +export function documentInput(input: graphqlWeb.DocumentNode): graphql.DocumentNode { + return input; +} + +export function documentOutput(input: graphql.DocumentNode): graphqlWeb.DocumentNode { + return input; +} + +export function nodeInput(input: graphqlWeb.ASTNode): graphql.ASTNode { + return input; +} + +export function nodeOutput(input: graphql.ASTNode): graphqlWeb.ASTNode { + return input; +} diff --git a/typings/error.ts b/typings/error.ts new file mode 100644 index 0000000..ca3394b --- /dev/null +++ b/typings/error.ts @@ -0,0 +1,10 @@ +import type * as graphql from 'graphql'; +import type * as graphqlWeb from '../src/index'; + +export function errorInput(input: graphqlWeb.GraphQLError): graphql.GraphQLError { + return input; +} + +export function errorOutput(input: graphql.GraphQLError): graphqlWeb.GraphQLError { + return input; +} diff --git a/typings/parser.ts b/typings/parser.ts new file mode 100644 index 0000000..02d2bef --- /dev/null +++ b/typings/parser.ts @@ -0,0 +1,18 @@ +import type * as graphql from 'graphql'; +import type * as graphqlWeb from '../src/index'; + +export function parseInput(input: typeof graphqlWeb.parse): typeof graphql.parse { + return input; +} + +export function parseOutput(input: typeof graphql.parse): typeof graphqlWeb.parse { + return input; +} + +export function parseValueInput(input: typeof graphqlWeb.parseValue): typeof graphql.parseValue { + return input; +} + +export function parseValueOutput(input: typeof graphql.parseValue): typeof graphqlWeb.parseValue { + return input; +} diff --git a/typings/visitor.ts b/typings/visitor.ts new file mode 100644 index 0000000..1e16d9e --- /dev/null +++ b/typings/visitor.ts @@ -0,0 +1,10 @@ +import type * as graphql from 'graphql'; +import type * as graphqlWeb from '../src/index'; + +export function visitInput(input: typeof graphqlWeb.visit): typeof graphql.visit { + return input; +} + +export function visitOutput(input: typeof graphql.visit): typeof graphqlWeb.visit { + return input; +}