Comparing version 0.0.0-main-0765b498 to 0.0.0-main-0c545134
{ | ||
"name": "grats", | ||
"version": "0.0.2", | ||
"version": "0.0.5", | ||
"main": "dist/src/index.js", | ||
@@ -12,3 +12,3 @@ "bin": "dist/src/cli.js", | ||
"scripts": { | ||
"test": "ts-node --esm src/tests/test.ts", | ||
"test": "ts-node src/tests/test.ts", | ||
"integration-tests": "node src/tests/integration.mjs", | ||
@@ -38,3 +38,7 @@ "build": "tsc --build", | ||
}, | ||
"packageManager": "pnpm@8.1.1" | ||
"packageManager": "pnpm@8.1.1", | ||
"engines": { | ||
"node": ">=16 <=21", | ||
"pnpm": "^8" | ||
} | ||
} |
#!/usr/bin/env node | ||
export {}; | ||
import { Location } from "graphql"; | ||
export declare function formatLoc(loc: Location): string; |
@@ -40,2 +40,3 @@ #!/usr/bin/env node | ||
exports.__esModule = true; | ||
exports.formatLoc = void 0; | ||
var graphql_1 = require("graphql"); | ||
@@ -49,2 +50,3 @@ var _1 = require("./"); | ||
var package_json_1 = require("../package.json"); | ||
var Locate_1 = require("./Locate"); | ||
var program = new commander_1.Command(); | ||
@@ -66,4 +68,33 @@ program | ||
}); | ||
program | ||
.command("locate") | ||
.argument("<ENTITY>", "GraphQL entity to locate. E.g. `User` or `User.id`") | ||
.option("--tsconfig <TSCONFIG>", "Path to tsconfig.json. Defaults to auto-detecting based on the current working directory") | ||
.action(function (entity, _a) { | ||
var tsconfig = _a.tsconfig; | ||
var schema = buildSchema(tsconfig); | ||
var loc = (0, Locate_1.locate)(schema, entity); | ||
if (loc.kind === "ERROR") { | ||
console.error(loc.err); | ||
process.exit(1); | ||
} | ||
console.log(formatLoc(loc.value)); | ||
}); | ||
program.parse(); | ||
function build(output, tsconfig) { | ||
var schema = buildSchema(tsconfig); | ||
var sortedSchema = (0, graphql_1.lexicographicSortSchema)(schema); | ||
var schemaStr = (0, utils_1.printSchemaWithDirectives)(sortedSchema, { | ||
assumeValid: true | ||
}); | ||
if (output) { | ||
var absOutput = (0, path_1.resolve)(process.cwd(), output); | ||
(0, fs_1.writeFileSync)(absOutput, schemaStr); | ||
console.error("Grats: Wrote schema to `".concat(absOutput, "`.")); | ||
} | ||
else { | ||
console.log(schemaStr); | ||
} | ||
} | ||
function buildSchema(tsconfig) { | ||
if (tsconfig && !(0, fs_1.existsSync)(tsconfig)) { | ||
@@ -81,12 +112,9 @@ console.error("Grats: Could not find tsconfig.json at `".concat(tsconfig, "`.")); | ||
} | ||
var schema = (0, graphql_1.lexicographicSortSchema)(schemaResult.value); | ||
var schemaStr = (0, utils_1.printSchemaWithDirectives)(schema, { assumeValid: true }); | ||
if (output) { | ||
var absOutput = (0, path_1.resolve)(process.cwd(), output); | ||
(0, fs_1.writeFileSync)(absOutput, schemaStr); | ||
console.error("Grats: Wrote schema to `".concat(absOutput, "`.")); | ||
} | ||
else { | ||
console.log(schemaStr); | ||
} | ||
return schemaResult.value; | ||
} | ||
// Format a location for printing to the console. Tools like VS Code and iTerm | ||
// will automatically turn this into a clickable link. | ||
function formatLoc(loc) { | ||
return "".concat(loc.source.name, ":").concat(loc.startToken.line + 1, ":").concat(loc.startToken.column + 1); | ||
} | ||
exports.formatLoc = formatLoc; |
@@ -34,4 +34,4 @@ /** | ||
export declare function inputFieldUntyped(): string; | ||
export declare function typeTagOnUnamedClass(): string; | ||
export declare function typeTagOnAliasOfNonObject(): string; | ||
export declare function typeTagOnUnnamedClass(): string; | ||
export declare function typeTagOnAliasOfNonObjectOrUnknown(): string; | ||
export declare function typeNameNotDeclaration(): string; | ||
@@ -66,5 +66,27 @@ export declare function typeNameMissingInitializer(): string; | ||
export declare function pluralTypeMissingParameter(): string; | ||
export declare function expectedIdentifer(): string; | ||
export declare function expectedIdentifier(): string; | ||
export declare function killsParentOnExceptionWithWrongConfig(): string; | ||
export declare function killsParentOnExceptionOnNullable(): string; | ||
export declare function nonNullTypeCannotBeOptional(): string; | ||
export declare function mergedInterfaces(interfaceName: string): string; | ||
export declare function implementsTagMissingValue(): string; | ||
export declare function implementsTagOnClass(): string; | ||
export declare function implementsTagOnInterface(): string; | ||
export declare function implementsTagOnTypeAlias(): string; | ||
export declare function duplicateTag(tagName: string): string; | ||
export declare function duplicateInterfaceTag(): string; | ||
export declare function parameterWithoutModifiers(): string; | ||
export declare function parameterPropertyNotPublic(): string; | ||
export declare function parameterPropertyMissingType(): string; | ||
export declare function invalidTypePassedToFieldFunction(): string; | ||
export declare function unresolvedTypeReference(): string; | ||
export declare function expectedTypeAnnotationOnContext(): string; | ||
export declare function expectedTypeAnnotationOfReferenceOnContext(): string; | ||
export declare function expectedTypeAnnotationOnContextToBeResolvable(): string; | ||
export declare function expectedTypeAnnotationOnContextToHaveDeclaration(): string; | ||
export declare function unexpectedParamSpreadForContextParam(): string; | ||
export declare function multipleContextTypes(): string; | ||
export declare function graphQLNameHasLeadingNewlines(name: string, tagName: string): string; | ||
export declare function graphQLTagNameHasWhitespace(tagName: string): string; | ||
export declare function subscriptionFieldNotAsyncIterable(): string; | ||
export declare function nonSubscriptionFieldAsyncIterable(): string; |
"use strict"; | ||
exports.__esModule = true; | ||
exports.defaultArgPropertyMissingName = exports.defaultArgElementIsNotAssignment = exports.defaultValueIsNotLiteral = exports.ambiguousNumberType = exports.expectedOneNonNullishType = exports.propertyFieldMissingType = exports.cannotResolveSymbolForDescription = exports.promiseMissingTypeArg = exports.methodMissingType = exports.gqlEntityMissingName = exports.enumVariantMissingInitializer = exports.enumVariantNotStringLiteral = exports.enumTagOnInvalidNode = exports.argNotTyped = exports.argNameNotLiteral = exports.argIsNotProperty = exports.argumentParamIsNotObject = exports.argumentParamIsMissingType = exports.typeNameDoesNotMatchExpected = exports.typeNameTypeNotStringLiteral = exports.typeNameMissingTypeAnnotation = exports.typeNameInitializerWrong = exports.typeNameInitializeNotString = exports.typeNameMissingInitializer = exports.typeNameNotDeclaration = exports.typeTagOnAliasOfNonObject = exports.typeTagOnUnamedClass = exports.inputFieldUntyped = exports.inputTypeFieldNotProperty = exports.inputTypeNotLiteral = exports.functionFieldNotNamedExport = exports.functionFieldDefaultExport = exports.functionFieldNotNamed = exports.functionFieldParentTypeNotValid = exports.functionFieldParentTypeMissing = exports.functionFieldNotTopLevel = exports.invalidReturnTypeForFunctionField = exports.invalidParentArgForFunctionField = exports.expectedUnionTypeReference = exports.expectedUnionTypeNode = exports.invalidUnionTagUsage = exports.invalidInputTagUsage = exports.invalidEnumTagUsage = exports.invalidInterfaceTagUsage = exports.invalidScalarTagUsage = exports.invalidTypeTagUsage = exports.invalidGratsTag = exports.wrongCasingForGratsTag = exports.killsParentOnExceptionOnWrongNode = exports.fieldTagOnWrongNode = void 0; | ||
exports.nonNullTypeCannotBeOptional = exports.killsParentOnExceptionOnNullable = exports.killsParentOnExceptionWithWrongConfig = exports.expectedIdentifer = exports.pluralTypeMissingParameter = exports.unknownGraphQLType = exports.unsupportedTypeLiteral = exports.defaultArgPropertyMissingInitializer = void 0; | ||
exports.defaultArgPropertyMissingName = exports.defaultArgElementIsNotAssignment = exports.defaultValueIsNotLiteral = exports.ambiguousNumberType = exports.expectedOneNonNullishType = exports.propertyFieldMissingType = exports.cannotResolveSymbolForDescription = exports.promiseMissingTypeArg = exports.methodMissingType = exports.gqlEntityMissingName = exports.enumVariantMissingInitializer = exports.enumVariantNotStringLiteral = exports.enumTagOnInvalidNode = exports.argNotTyped = exports.argNameNotLiteral = exports.argIsNotProperty = exports.argumentParamIsNotObject = exports.argumentParamIsMissingType = exports.typeNameDoesNotMatchExpected = exports.typeNameTypeNotStringLiteral = exports.typeNameMissingTypeAnnotation = exports.typeNameInitializerWrong = exports.typeNameInitializeNotString = exports.typeNameMissingInitializer = exports.typeNameNotDeclaration = exports.typeTagOnAliasOfNonObjectOrUnknown = exports.typeTagOnUnnamedClass = exports.inputFieldUntyped = exports.inputTypeFieldNotProperty = exports.inputTypeNotLiteral = exports.functionFieldNotNamedExport = exports.functionFieldDefaultExport = exports.functionFieldNotNamed = exports.functionFieldParentTypeNotValid = exports.functionFieldParentTypeMissing = exports.functionFieldNotTopLevel = exports.invalidReturnTypeForFunctionField = exports.invalidParentArgForFunctionField = exports.expectedUnionTypeReference = exports.expectedUnionTypeNode = exports.invalidUnionTagUsage = exports.invalidInputTagUsage = exports.invalidEnumTagUsage = exports.invalidInterfaceTagUsage = exports.invalidScalarTagUsage = exports.invalidTypeTagUsage = exports.invalidGratsTag = exports.wrongCasingForGratsTag = exports.killsParentOnExceptionOnWrongNode = exports.fieldTagOnWrongNode = void 0; | ||
exports.nonSubscriptionFieldAsyncIterable = exports.subscriptionFieldNotAsyncIterable = exports.graphQLTagNameHasWhitespace = exports.graphQLNameHasLeadingNewlines = exports.multipleContextTypes = exports.unexpectedParamSpreadForContextParam = exports.expectedTypeAnnotationOnContextToHaveDeclaration = exports.expectedTypeAnnotationOnContextToBeResolvable = exports.expectedTypeAnnotationOfReferenceOnContext = exports.expectedTypeAnnotationOnContext = exports.unresolvedTypeReference = exports.invalidTypePassedToFieldFunction = exports.parameterPropertyMissingType = exports.parameterPropertyNotPublic = exports.parameterWithoutModifiers = exports.duplicateInterfaceTag = exports.duplicateTag = exports.implementsTagOnTypeAlias = exports.implementsTagOnInterface = exports.implementsTagOnClass = exports.implementsTagMissingValue = exports.mergedInterfaces = exports.nonNullTypeCannotBeOptional = exports.killsParentOnExceptionOnNullable = exports.killsParentOnExceptionWithWrongConfig = exports.expectedIdentifier = exports.pluralTypeMissingParameter = exports.unknownGraphQLType = exports.unsupportedTypeLiteral = exports.defaultArgPropertyMissingInitializer = void 0; | ||
var Extractor_1 = require("./Extractor"); | ||
// TODO: Move these to short URLS that are easier to keep from breaking. | ||
var DOC_URLS = { | ||
mergedInterfaces: "https://grats.capt.dev/docs/dockblock-tags/interfaces/#merged-interfaces", | ||
parameterProperties: "https://grats.capt.dev/docs/dockblock-tags/fields#class-based-fields", | ||
typeImplementsInterface: "TODO" | ||
}; | ||
/** | ||
@@ -66,3 +72,3 @@ * Error messages for Grats | ||
function invalidParentArgForFunctionField() { | ||
return "Expected `@".concat(Extractor_1.FIELD_TAG, "` function to have a first argument representing the type to extend."); | ||
return "Expected `@".concat(Extractor_1.FIELD_TAG, "` function to have a first argument representing the type to extend. If you don't need access to the parent object in the function, you can name the variable `_` to indicate that it is unused. e.g. `function myField(_: ParentType) {}`"); | ||
} | ||
@@ -110,10 +116,10 @@ exports.invalidParentArgForFunctionField = invalidParentArgForFunctionField; | ||
exports.inputFieldUntyped = inputFieldUntyped; | ||
function typeTagOnUnamedClass() { | ||
function typeTagOnUnnamedClass() { | ||
return "Unexpected `@".concat(Extractor_1.TYPE_TAG, "` annotation on unnamed class declaration."); | ||
} | ||
exports.typeTagOnUnamedClass = typeTagOnUnamedClass; | ||
function typeTagOnAliasOfNonObject() { | ||
return "Expected `@".concat(Extractor_1.TYPE_TAG, "` type to be a type literal. For example: `type Foo = { bar: string }`"); | ||
exports.typeTagOnUnnamedClass = typeTagOnUnnamedClass; | ||
function typeTagOnAliasOfNonObjectOrUnknown() { | ||
return "Expected `@".concat(Extractor_1.TYPE_TAG, "` type to be a type literal or `unknown`. For example: `type Foo = { bar: string }` or `type Query = unknown`."); | ||
} | ||
exports.typeTagOnAliasOfNonObject = typeTagOnAliasOfNonObject; | ||
exports.typeTagOnAliasOfNonObjectOrUnknown = typeTagOnAliasOfNonObjectOrUnknown; | ||
function typeNameNotDeclaration() { | ||
@@ -148,7 +154,7 @@ return "Expected `__typename` to be a property declaration."; | ||
function argumentParamIsMissingType() { | ||
return "Expected GraphQL field arguments to have a TypeScript type. If there are no arguments, you can use `args: never`."; | ||
return "Expected GraphQL field arguments to have a TypeScript type. If there are no arguments, you can use `args: unknown`."; | ||
} | ||
exports.argumentParamIsMissingType = argumentParamIsMissingType; | ||
function argumentParamIsNotObject() { | ||
return "Expected GraphQL field arguments to be typed using a literal object: `{someField: string}`."; | ||
return "Expected GraphQL field arguments to be typed using a literal object: `{someField: string}`. If there are no arguments, you can use `args: unknown`."; | ||
} | ||
@@ -236,6 +242,6 @@ exports.argumentParamIsNotObject = argumentParamIsNotObject; | ||
exports.pluralTypeMissingParameter = pluralTypeMissingParameter; | ||
function expectedIdentifer() { | ||
function expectedIdentifier() { | ||
return "Expected an identifier."; | ||
} | ||
exports.expectedIdentifer = expectedIdentifer; | ||
exports.expectedIdentifier = expectedIdentifier; | ||
function killsParentOnExceptionWithWrongConfig() { | ||
@@ -253,1 +259,103 @@ return "Unexpected `@".concat(Extractor_1.KILLS_PARENT_ON_EXCEPTION_TAG, "` tag. `@").concat(Extractor_1.KILLS_PARENT_ON_EXCEPTION_TAG, "` is only supported when the Grats config `nullableByDefault` is enabled."); | ||
exports.nonNullTypeCannotBeOptional = nonNullTypeCannotBeOptional; | ||
function mergedInterfaces(interfaceName) { | ||
return [ | ||
"Unexpected merged interface `".concat(interfaceName, "`."), | ||
"If an interface is declared multiple times in a scope, TypeScript merges them.", | ||
"To avoid ambiguity Grats does not support using merged interfaces as GraphQL interfaces.", | ||
"Consider using a unique name for your TypeScript interface and renaming it.\n\n", | ||
"Learn more: ".concat(DOC_URLS.mergedInterfaces), | ||
].join(" "); | ||
} | ||
exports.mergedInterfaces = mergedInterfaces; | ||
function implementsTagMissingValue() { | ||
return "Expected `@".concat(Extractor_1.IMPLEMENTS_TAG_DEPRECATED, "` to be followed by one or more interface names."); | ||
} | ||
exports.implementsTagMissingValue = implementsTagMissingValue; | ||
function implementsTagOnClass() { | ||
return "`@".concat(Extractor_1.IMPLEMENTS_TAG_DEPRECATED, "` has been deprecated. Instead use `class MyType implements MyInterface`."); | ||
} | ||
exports.implementsTagOnClass = implementsTagOnClass; | ||
function implementsTagOnInterface() { | ||
return "`@".concat(Extractor_1.IMPLEMENTS_TAG_DEPRECATED, "` has been deprecated. Instead use `interface MyType extends MyInterface`."); | ||
} | ||
exports.implementsTagOnInterface = implementsTagOnInterface; | ||
function implementsTagOnTypeAlias() { | ||
return "`@".concat(Extractor_1.IMPLEMENTS_TAG_DEPRECATED, "` has been deprecated. Types which implement GraphQL interfaces should be defined using TypeScript class or interface declarations. Learn more: ").concat(DOC_URLS.typeImplementsInterface, "."); | ||
} | ||
exports.implementsTagOnTypeAlias = implementsTagOnTypeAlias; | ||
function duplicateTag(tagName) { | ||
return "Unexpected duplicate `@".concat(tagName, "` tag. Grats does not accept multiple instances of the same tag."); | ||
} | ||
exports.duplicateTag = duplicateTag; | ||
function duplicateInterfaceTag() { | ||
return "Unexpected duplicate `@".concat(Extractor_1.IMPLEMENTS_TAG_DEPRECATED, "` tag. To declare that a type or interface implements multiple interfaces list them as comma separated values: `@").concat(Extractor_1.IMPLEMENTS_TAG_DEPRECATED, " interfaceA, interfaceB`."); | ||
} | ||
exports.duplicateInterfaceTag = duplicateInterfaceTag; | ||
function parameterWithoutModifiers() { | ||
return [ | ||
"Expected `@".concat(Extractor_1.FIELD_TAG, "` constructor parameter to be a parameter property. This requires a modifier such as `public` or `readonly` before the parameter name.\n\n"), | ||
"Learn more: ".concat(DOC_URLS.parameterProperties), | ||
].join(""); | ||
} | ||
exports.parameterWithoutModifiers = parameterWithoutModifiers; | ||
function parameterPropertyNotPublic() { | ||
return [ | ||
"Expected `@".concat(Extractor_1.FIELD_TAG, "` parameter property to be public. Valid modifiers for `@").concat(Extractor_1.FIELD_TAG, "` parameter properties are `public` and `readonly`.\n\n"), | ||
"Learn more: ".concat(DOC_URLS.parameterProperties), | ||
].join(""); | ||
} | ||
exports.parameterPropertyNotPublic = parameterPropertyNotPublic; | ||
function parameterPropertyMissingType() { | ||
return "Expected `@".concat(Extractor_1.FIELD_TAG, "` parameter property to have a type annotation."); | ||
} | ||
exports.parameterPropertyMissingType = parameterPropertyMissingType; | ||
function invalidTypePassedToFieldFunction() { | ||
return "Unexpected type passed to `@".concat(Extractor_1.FIELD_TAG, "` function. `@").concat(Extractor_1.FIELD_TAG, "` functions can only be used to extend `@").concat(Extractor_1.TYPE_TAG, "` and `@").concat(Extractor_1.INTERFACE_TAG, "` types."); | ||
} | ||
exports.invalidTypePassedToFieldFunction = invalidTypePassedToFieldFunction; | ||
function unresolvedTypeReference() { | ||
return "This type is not a valid GraphQL type. Did you mean to annotate it's definition with a `/** @gql */` tag such as `/** @gqlType */` or `/** @gqlInput **/`?"; | ||
} | ||
exports.unresolvedTypeReference = unresolvedTypeReference; | ||
function expectedTypeAnnotationOnContext() { | ||
return "Expected context parameter to have a type annotation. Grats validates that your context parameter is type-safe by checking all context values reference the same type declaration."; | ||
} | ||
exports.expectedTypeAnnotationOnContext = expectedTypeAnnotationOnContext; | ||
function expectedTypeAnnotationOfReferenceOnContext() { | ||
return "Expected context parameter's type to be a type reference Grats validates that your context parameter is type-safe by checking all context values reference the same type declaration."; | ||
} | ||
exports.expectedTypeAnnotationOfReferenceOnContext = expectedTypeAnnotationOfReferenceOnContext; | ||
function expectedTypeAnnotationOnContextToBeResolvable() { | ||
// TODO: Provide guidance? | ||
// TODO: I don't think we have a test case that triggers this error. | ||
return "Unable to resolve context parameter type. Grats validates that your context parameter is type-safe by checking all context values reference the same type declaration."; | ||
} | ||
exports.expectedTypeAnnotationOnContextToBeResolvable = expectedTypeAnnotationOnContextToBeResolvable; | ||
function expectedTypeAnnotationOnContextToHaveDeclaration() { | ||
return "Unable to locate the declaration of the context parameter's type. Grats validates that your context parameter is type-safe by checking all context values reference the same type declaration. Did you forget to import or define this type?"; | ||
} | ||
exports.expectedTypeAnnotationOnContextToHaveDeclaration = expectedTypeAnnotationOnContextToHaveDeclaration; | ||
function unexpectedParamSpreadForContextParam() { | ||
return "Unexpected spread parameter in context parameter position. Grats expects the context parameter to be a single, explicitly typed, argument."; | ||
} | ||
exports.unexpectedParamSpreadForContextParam = unexpectedParamSpreadForContextParam; | ||
function multipleContextTypes() { | ||
return "Context argument's type does not match. Grats expects all resolvers that read the context argument to use the same type for that argument. Did you use the incorrect type in one of your resolvers?"; | ||
} | ||
exports.multipleContextTypes = multipleContextTypes; | ||
function graphQLNameHasLeadingNewlines(name, tagName) { | ||
return "Expected the GraphQL name `".concat(name, "` to be on the same line as it's `@").concat(tagName, "` tag."); | ||
} | ||
exports.graphQLNameHasLeadingNewlines = graphQLNameHasLeadingNewlines; | ||
function graphQLTagNameHasWhitespace(tagName) { | ||
return "Expected text following a `@".concat(tagName, "` tag to be a GraphQL name. If you intended this text to be a description, place it at the top of the docblock before any `@tags`."); | ||
} | ||
exports.graphQLTagNameHasWhitespace = graphQLTagNameHasWhitespace; | ||
function subscriptionFieldNotAsyncIterable() { | ||
return "Expected fields on `Subscription` to return an AsyncIterable."; | ||
} | ||
exports.subscriptionFieldNotAsyncIterable = subscriptionFieldNotAsyncIterable; | ||
function nonSubscriptionFieldAsyncIterable() { | ||
return "Unexpected AsyncIterable. Only fields on `Subscription` should return an AsyncIterable."; | ||
} | ||
exports.nonSubscriptionFieldAsyncIterable = nonSubscriptionFieldAsyncIterable; |
@@ -1,6 +0,7 @@ | ||
import { DefinitionNode, FieldDefinitionNode, InputValueDefinitionNode, ListTypeNode, NamedTypeNode, Location as GraphQLLocation, NameNode, Token, TypeNode, NonNullTypeNode, StringValueNode, ConstValueNode, ConstDirectiveNode, ConstArgumentNode, EnumValueDefinitionNode, ConstObjectFieldNode, ConstObjectValueNode, ConstListValueNode } from "graphql"; | ||
import { FieldDefinitionNode, InputValueDefinitionNode, NamedTypeNode, NameNode, TypeNode, StringValueNode, ConstValueNode, ConstDirectiveNode, EnumValueDefinitionNode, ConstObjectFieldNode, ConstObjectValueNode, ConstListValueNode } from "graphql"; | ||
import { DiagnosticsResult } from "./utils/DiagnosticError"; | ||
import * as ts from "typescript"; | ||
import { TypeContext } from "./TypeContext"; | ||
import { GratsDefinitionNode, TypeContext } from "./TypeContext"; | ||
import { ConfigOptions } from "./lib"; | ||
import { GraphQLConstructor } from "./GraphQLConstructor"; | ||
export declare const LIBRARY_IMPORT_NAME = "grats"; | ||
@@ -16,2 +17,3 @@ export declare const LIBRARY_NAME = "Grats"; | ||
export declare const INPUT_TAG = "gqlInput"; | ||
export declare const IMPLEMENTS_TAG_DEPRECATED = "gqlImplements"; | ||
export declare const KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException"; | ||
@@ -31,3 +33,3 @@ export declare const ALL_TAGS: string[]; | ||
export declare class Extractor { | ||
definitions: DefinitionNode[]; | ||
definitions: GratsDefinitionNode[]; | ||
sourceFile: ts.SourceFile; | ||
@@ -37,4 +39,5 @@ ctx: TypeContext; | ||
errors: ts.Diagnostic[]; | ||
gql: GraphQLConstructor; | ||
constructor(sourceFile: ts.SourceFile, ctx: TypeContext, buildOptions: ConfigOptions); | ||
extract(): DiagnosticsResult<DefinitionNode[]>; | ||
extract(): DiagnosticsResult<GratsDefinitionNode[]>; | ||
extractType(node: ts.Node, tag: ts.JSDocTag): void; | ||
@@ -48,3 +51,3 @@ extractScalar(node: ts.Node, tag: ts.JSDocTag): void; | ||
report(node: ts.Node, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): null; | ||
reportUnhandled(node: ts.Node, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): null; | ||
reportUnhandled(node: ts.Node, positionKind: "type" | "field" | "field type" | "input" | "input field" | "union member" | "constant value" | "union" | "enum value", message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): null; | ||
related(node: ts.Node, message: string): ts.DiagnosticRelatedInformation; | ||
@@ -56,4 +59,2 @@ diagnosticAnnotatedLocation(node: ts.Node): { | ||
}; | ||
loc(node: ts.Node): GraphQLLocation; | ||
gqlDummyToken(pos: number): Token; | ||
/** TypeScript traversals */ | ||
@@ -76,5 +77,10 @@ unionTypeAliasDeclaration(node: ts.TypeAliasDeclaration, tag: ts.JSDocTag): null | undefined; | ||
isValidTypenamePropertyType(node: ts.TypeNode, expectedName: string): boolean; | ||
collectInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<NamedTypeNode> | null; | ||
interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): void; | ||
collectInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration): Array<NamedTypeNode> | null; | ||
reportTagInterfaces(node: ts.TypeAliasDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration): null | undefined; | ||
collectHeritageInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<NamedTypeNode> | null; | ||
symbolHasGqlTag(node: ts.Node): boolean; | ||
hasGqlTag(node: ts.Node): boolean; | ||
interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): null | undefined; | ||
collectFields(node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeLiteralNode): Array<FieldDefinitionNode>; | ||
constructorParam(node: ts.ParameterDeclaration): FieldDefinitionNode | null; | ||
collectArgs(argsParam: ts.ParameterDeclaration): ReadonlyArray<InputValueDefinitionNode> | null; | ||
@@ -84,3 +90,3 @@ collectArgDefaults(node: ts.ObjectBindingPattern): ArgDefaults; | ||
collectArrayLiteral(node: ts.ArrayLiteralExpression): ConstListValueNode | null; | ||
cellectObjectLiteral(node: ts.ObjectLiteralExpression): ConstObjectValueNode | null; | ||
collectObjectLiteral(node: ts.ObjectLiteralExpression): ConstObjectValueNode | null; | ||
collectObjectField(node: ts.ObjectLiteralElementLike): ConstObjectFieldNode | null; | ||
@@ -92,7 +98,11 @@ collectArg(node: ts.TypeElement, defaults?: Map<string, ts.Expression> | null): InputValueDefinitionNode | null; | ||
collectEnumValues(node: ts.EnumDeclaration): ReadonlyArray<EnumValueDefinitionNode>; | ||
entityName(node: ts.ClassDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.PropertyDeclaration | ts.InterfaceDeclaration | ts.PropertySignature | ts.EnumDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration, tag: ts.JSDocTag): NameNode | null; | ||
entityName(node: ts.ClassDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.PropertyDeclaration | ts.InterfaceDeclaration | ts.PropertySignature | ts.EnumDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration | ts.ParameterDeclaration, tag: ts.JSDocTag): NameNode | null; | ||
validateContextParameter(node: ts.ParameterDeclaration): null | undefined; | ||
methodDeclaration(node: ts.MethodDeclaration | ts.MethodSignature): FieldDefinitionNode | null; | ||
collectMethodType(node: ts.TypeNode): TypeNode | null; | ||
collectReturnType(node: ts.TypeNode): { | ||
type: TypeNode; | ||
isStream: boolean; | ||
} | null; | ||
collectPropertyType(node: ts.TypeNode): TypeNode | null; | ||
maybeUnwrapePromise(node: ts.TypeNode): ts.TypeNode | null; | ||
maybeUnwrapPromise(node: ts.TypeNode): ts.TypeNode | null; | ||
collectDescription(node: ts.Node): StringValueNode | null; | ||
@@ -107,14 +117,5 @@ collectDeprecated(node: ts.Node): ConstDirectiveNode | null; | ||
handleErrorBubbling(parentNode: ts.Node, type: TypeNode): TypeNode; | ||
methodNameDirective(nameNode: ts.Node, name: string): ConstDirectiveNode; | ||
exportDirective(nameNode: ts.Node, filename: string, functionName: string): ConstDirectiveNode; | ||
/** GraphQL AST node helper methods */ | ||
gqlName(node: ts.Node, value: string): NameNode; | ||
gqlNamedType(node: ts.Node, value: string): NamedTypeNode; | ||
gqlNonNullType(node: ts.Node, type: TypeNode): NonNullTypeNode; | ||
gqlNullableType(type: TypeNode): NamedTypeNode | ListTypeNode; | ||
gqlListType(node: ts.Node, type: TypeNode): ListTypeNode; | ||
gqlConstArgument(node: ts.Node, name: NameNode, value: ConstValueNode): ConstArgumentNode; | ||
gqlConstDirective(node: ts.Node, name: NameNode, args: ReadonlyArray<ConstArgumentNode>): ConstDirectiveNode; | ||
gqlString(node: ts.Node, value: string): StringValueNode; | ||
exportDirective(nameNode: ts.Node, jsModulePath: string, tsModulePath: string, functionName: string): ConstDirectiveNode; | ||
fieldNameDirective(nameNode: ts.Node, name: string): ConstDirectiveNode; | ||
} | ||
export {}; |
@@ -30,3 +30,3 @@ "use strict"; | ||
exports.__esModule = true; | ||
exports.Extractor = exports.ALL_TAGS = exports.KILLS_PARENT_ON_EXCEPTION_TAG = exports.INPUT_TAG = exports.UNION_TAG = exports.ENUM_TAG = exports.INTERFACE_TAG = exports.SCALAR_TAG = exports.FIELD_TAG = exports.TYPE_TAG = exports.ISSUE_URL = exports.LIBRARY_NAME = exports.LIBRARY_IMPORT_NAME = void 0; | ||
exports.Extractor = exports.ALL_TAGS = exports.KILLS_PARENT_ON_EXCEPTION_TAG = exports.IMPLEMENTS_TAG_DEPRECATED = exports.INPUT_TAG = exports.UNION_TAG = exports.ENUM_TAG = exports.INTERFACE_TAG = exports.SCALAR_TAG = exports.FIELD_TAG = exports.TYPE_TAG = exports.ISSUE_URL = exports.LIBRARY_NAME = exports.LIBRARY_IMPORT_NAME = void 0; | ||
var graphql_1 = require("graphql"); | ||
@@ -36,5 +36,6 @@ var DiagnosticError_1 = require("./utils/DiagnosticError"); | ||
var TypeContext_1 = require("./TypeContext"); | ||
var serverDirectives_1 = require("./serverDirectives"); | ||
var E = require("./Errors"); | ||
var JSDoc_1 = require("./utils/JSDoc"); | ||
var GraphQLConstructor_1 = require("./GraphQLConstructor"); | ||
var serverDirectives_1 = require("./serverDirectives"); | ||
exports.LIBRARY_IMPORT_NAME = "grats"; | ||
@@ -50,3 +51,5 @@ exports.LIBRARY_NAME = "Grats"; | ||
exports.INPUT_TAG = "gqlInput"; | ||
exports.IMPLEMENTS_TAG_DEPRECATED = "gqlImplements"; | ||
exports.KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException"; | ||
// All the tags that start with gql | ||
exports.ALL_TAGS = [ | ||
@@ -79,2 +82,3 @@ exports.TYPE_TAG, | ||
this.configOptions = buildOptions; | ||
this.gql = new GraphQLConstructor_1.GraphQLConstructor(sourceFile); | ||
} | ||
@@ -112,3 +116,4 @@ // Traverse all nodes, checking each one for its JSDoc tags. | ||
} | ||
else if (!(ts.isMethodDeclaration(node) || | ||
else if (!(ts.isParameter(node) || | ||
ts.isMethodDeclaration(node) || | ||
ts.isPropertyDeclaration(node) || | ||
@@ -119,3 +124,3 @@ ts.isMethodSignature(node) || | ||
// Note: Keep this in sync with `collectFields` | ||
_this.reportUnhandled(node, E.fieldTagOnWrongNode()); | ||
_this.reportUnhandled(node, "field", E.fieldTagOnWrongNode()); | ||
} | ||
@@ -237,4 +242,4 @@ break; | ||
// Gives the user a path forward if they think we should be able to infer this type. | ||
Extractor.prototype.reportUnhandled = function (node, message, relatedInformation) { | ||
var suggestion = "If you think ".concat(exports.LIBRARY_NAME, " should be able to infer this type, please report an issue at ").concat(exports.ISSUE_URL, "."); | ||
Extractor.prototype.reportUnhandled = function (node, positionKind, message, relatedInformation) { | ||
var suggestion = "If you think ".concat(exports.LIBRARY_NAME, " should be able to infer this ").concat(positionKind, ", please report an issue at ").concat(exports.ISSUE_URL, "."); | ||
var completedMessage = "".concat(message, "\n\n").concat(suggestion); | ||
@@ -258,15 +263,2 @@ return this.report(node, completedMessage, relatedInformation); | ||
}; | ||
// TODO: This is potentially quite expensive, and we only need it if we report | ||
// an error at one of these locations. We could consider some trick to return a | ||
// proxy object that would lazily compute the line/column info. | ||
Extractor.prototype.loc = function (node) { | ||
var source = new graphql_1.Source(this.sourceFile.text, this.sourceFile.fileName); | ||
var startToken = this.gqlDummyToken(node.getStart()); | ||
var endToken = this.gqlDummyToken(node.getEnd()); | ||
return new graphql_1.Location(startToken, endToken, source); | ||
}; | ||
Extractor.prototype.gqlDummyToken = function (pos) { | ||
var _a = this.sourceFile.getLineAndCharacterOfPosition(pos), line = _a.line, character = _a.character; | ||
return new graphql_1.Token(graphql_1.TokenKind.SOF, pos, pos, line, character, undefined); | ||
}; | ||
/** TypeScript traversals */ | ||
@@ -287,5 +279,5 @@ Extractor.prototype.unionTypeAliasDeclaration = function (node, tag) { | ||
if (!ts.isTypeReferenceNode(member)) { | ||
return this.reportUnhandled(member, E.expectedUnionTypeReference()); | ||
return this.reportUnhandled(member, "union member", E.expectedUnionTypeReference()); | ||
} | ||
var namedType = this.gqlNamedType(member.typeName, TypeContext_1.UNRESOLVED_REFERENCE_NAME); | ||
var namedType = this.gql.namedType(member.typeName, TypeContext_1.UNRESOLVED_REFERENCE_NAME); | ||
this.ctx.markUnresolvedType(member.typeName, namedType.name); | ||
@@ -302,10 +294,4 @@ types.push(namedType); | ||
} | ||
this.ctx.recordTypeName(node.name, name.value); | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.UNION_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description !== null && description !== void 0 ? description : undefined, | ||
name: name, | ||
types: types | ||
}); | ||
this.ctx.recordTypeName(node.name, name, "UNION"); | ||
this.definitions.push(this.gql.unionTypeDefinition(node, name, types, description)); | ||
}; | ||
@@ -329,5 +315,6 @@ Extractor.prototype.functionDeclarationExtendType = function (node, tag) { | ||
} | ||
var type = this.collectMethodType(node.type); | ||
if (type == null) | ||
var returnType = this.collectReturnType(node.type); | ||
if (returnType == null) | ||
return null; | ||
var type = returnType.type, isStream = returnType.isStream; | ||
var args = null; | ||
@@ -338,2 +325,6 @@ var argsParam = node.parameters[1]; | ||
} | ||
var context = node.parameters[2]; | ||
if (context != null) { | ||
this.validateContextParameter(context); | ||
} | ||
var description = this.collectDescription(funcName); | ||
@@ -344,8 +335,8 @@ if (!ts.isSourceFile(node.parent)) { | ||
// TODO: Does this work in the browser? | ||
var filename = this.ctx.getDestFilePath(node.parent); | ||
var _a = this.ctx.getDestFilePath(node.parent), jsModulePath = _a.jsModulePath, tsModulePath = _a.tsModulePath; | ||
var directives = [ | ||
this.exportDirective(funcName, filename, funcName.text), | ||
this.exportDirective(funcName, jsModulePath, tsModulePath, funcName.text), | ||
]; | ||
if (funcName.text !== name.value) { | ||
directives.push(this.methodNameDirective(funcName, funcName.text)); | ||
if (isStream) { | ||
directives.push(this.gql.constDirective(node.type, this.gql.name(node.type, serverDirectives_1.ASYNC_ITERABLE_TYPE_DIRECTIVE), null)); | ||
} | ||
@@ -356,18 +347,4 @@ var deprecated = this.collectDeprecated(node); | ||
} | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.OBJECT_TYPE_EXTENSION, | ||
loc: this.loc(node), | ||
name: typeName, | ||
fields: [ | ||
{ | ||
kind: graphql_1.Kind.FIELD_DEFINITION, | ||
loc: this.loc(node), | ||
description: description || undefined, | ||
name: name, | ||
arguments: args || undefined, | ||
type: this.handleErrorBubbling(node, type), | ||
directives: directives.length === 0 ? undefined : directives | ||
}, | ||
] | ||
}); | ||
var field = this.gql.fieldDefinition(node, name, this.handleErrorBubbling(node, type), args, directives, description); | ||
this.definitions.push(this.gql.abstractFieldDefinition(node, typeName, field)); | ||
}; | ||
@@ -382,3 +359,3 @@ Extractor.prototype.typeReferenceFromParam = function (typeParam) { | ||
var nameNode = typeParam.type.typeName; | ||
var typeName = this.gqlName(nameNode, TypeContext_1.UNRESOLVED_REFERENCE_NAME); | ||
var typeName = this.gql.name(nameNode, TypeContext_1.UNRESOLVED_REFERENCE_NAME); | ||
this.ctx.markUnresolvedType(nameNode, typeName); | ||
@@ -412,9 +389,4 @@ return typeName; | ||
var description = this.collectDescription(node.name); | ||
this.ctx.recordTypeName(node.name, name.value); | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.SCALAR_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description !== null && description !== void 0 ? description : undefined, | ||
name: name | ||
}); | ||
this.ctx.recordTypeName(node.name, name, "SCALAR"); | ||
this.definitions.push(this.gql.scalarTypeDefinition(node, name, description)); | ||
}; | ||
@@ -426,13 +398,6 @@ Extractor.prototype.inputTypeAliasDeclaration = function (node, tag) { | ||
var description = this.collectDescription(node.name); | ||
this.ctx.recordTypeName(node.name, name.value); | ||
this.ctx.recordTypeName(node.name, name, "INPUT_OBJECT"); | ||
var fields = this.collectInputFields(node); | ||
var deprecatedDirective = this.collectDeprecated(node); | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description !== null && description !== void 0 ? description : undefined, | ||
name: name, | ||
fields: fields !== null && fields !== void 0 ? fields : undefined, | ||
directives: deprecatedDirective == null ? undefined : [deprecatedDirective] | ||
}); | ||
this.definitions.push(this.gql.inputObjectTypeDefinition(node, name, fields, deprecatedDirective == null ? null : [deprecatedDirective], description)); | ||
}; | ||
@@ -443,3 +408,3 @@ Extractor.prototype.collectInputFields = function (node) { | ||
if (!ts.isTypeLiteralNode(node.type)) { | ||
return this.reportUnhandled(node, E.inputTypeNotLiteral()); | ||
return this.reportUnhandled(node, "input", E.inputTypeNotLiteral()); | ||
} | ||
@@ -450,3 +415,3 @@ try { | ||
if (!ts.isPropertySignature(member)) { | ||
this.reportUnhandled(member, E.inputTypeFieldNotProperty()); | ||
this.reportUnhandled(member, "input field", E.inputTypeFieldNotProperty()); | ||
continue; | ||
@@ -478,18 +443,10 @@ } | ||
return null; | ||
var type = node.questionToken == null ? inner : this.gqlNullableType(inner); | ||
var type = node.questionToken == null ? inner : this.gql.nullableType(inner); | ||
var description = this.collectDescription(node.name); | ||
var deprecatedDirective = this.collectDeprecated(node); | ||
return { | ||
kind: graphql_1.Kind.INPUT_VALUE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description !== null && description !== void 0 ? description : undefined, | ||
name: this.gqlName(id, id.text), | ||
type: type, | ||
defaultValue: undefined, | ||
directives: deprecatedDirective == null ? undefined : [deprecatedDirective] | ||
}; | ||
return this.gql.inputValueDefinition(node, this.gql.name(id, id.text), type, deprecatedDirective == null ? null : [deprecatedDirective], null, description); | ||
}; | ||
Extractor.prototype.typeClassDeclaration = function (node, tag) { | ||
if (node.name == null) { | ||
return this.report(node, E.typeTagOnUnamedClass()); | ||
return this.report(node, E.typeTagOnUnnamedClass()); | ||
} | ||
@@ -502,13 +459,5 @@ var name = this.entityName(node, tag); | ||
var interfaces = this.collectInterfaces(node); | ||
this.ctx.recordTypeName(node.name, name.value); | ||
this.ctx.recordTypeName(node.name, name, "TYPE"); | ||
this.checkForTypenameProperty(node, name.value); | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description !== null && description !== void 0 ? description : undefined, | ||
directives: undefined, | ||
name: name, | ||
fields: fields, | ||
interfaces: interfaces !== null && interfaces !== void 0 ? interfaces : undefined | ||
}); | ||
this.definitions.push(this.gql.objectTypeDefinition(node, name, fields, interfaces, description)); | ||
}; | ||
@@ -522,13 +471,5 @@ Extractor.prototype.typeInterfaceDeclaration = function (node, tag) { | ||
var interfaces = this.collectInterfaces(node); | ||
this.ctx.recordTypeName(node.name, name.value); | ||
this.ctx.recordTypeName(node.name, name, "INTERFACE"); | ||
this.checkForTypenameProperty(node, name.value); | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description !== null && description !== void 0 ? description : undefined, | ||
directives: undefined, | ||
name: name, | ||
fields: fields, | ||
interfaces: interfaces !== null && interfaces !== void 0 ? interfaces : undefined | ||
}); | ||
this.definitions.push(this.gql.objectTypeDefinition(node, name, fields, interfaces, description)); | ||
}; | ||
@@ -539,21 +480,20 @@ Extractor.prototype.typeTypeAliasDeclaration = function (node, tag) { | ||
return null; | ||
if (!ts.isTypeLiteralNode(node.type)) { | ||
this.reportUnhandled(node.type, E.typeTagOnAliasOfNonObject()); | ||
return; | ||
var fields = []; | ||
var interfaces = null; | ||
if (ts.isTypeLiteralNode(node.type)) { | ||
fields = this.collectFields(node.type); | ||
interfaces = this.collectInterfaces(node); | ||
this.checkForTypenameProperty(node.type, name.value); | ||
} | ||
else if (node.type.kind === ts.SyntaxKind.UnknownKeyword) { | ||
// This is fine, we just don't know what it is. This should be the expected | ||
// case for operation types such as `Query`, `Mutation`, and `Subscription` | ||
// where there is not strong convention around. | ||
} | ||
else { | ||
return this.report(node.type, E.typeTagOnAliasOfNonObjectOrUnknown()); | ||
} | ||
var description = this.collectDescription(node.name); | ||
var fields = this.collectFields(node.type); | ||
this.ctx.recordTypeName(node.name, name.value); | ||
this.checkForTypenameProperty(node.type, name.value); | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description !== null && description !== void 0 ? description : undefined, | ||
directives: undefined, | ||
name: name, | ||
fields: fields, | ||
// I don't believe there is a reasonable way to specify that a type | ||
// implements an interface. | ||
interfaces: undefined | ||
}); | ||
this.ctx.recordTypeName(node.name, name, "TYPE"); | ||
this.definitions.push(this.gql.objectTypeDefinition(node, name, fields, interfaces, description)); | ||
}; | ||
@@ -625,15 +565,41 @@ Extractor.prototype.checkForTypenameProperty = function (node, expectedName) { | ||
Extractor.prototype.collectInterfaces = function (node) { | ||
this.reportTagInterfaces(node); | ||
return ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) | ||
? this.collectHeritageInterfaces(node) | ||
: null; | ||
}; | ||
Extractor.prototype.reportTagInterfaces = function (node) { | ||
var tag = this.findTag(node, exports.IMPLEMENTS_TAG_DEPRECATED); | ||
if (tag == null) | ||
return null; | ||
if (node.kind === ts.SyntaxKind.ClassDeclaration) { | ||
this.report(tag, E.implementsTagOnClass()); | ||
} | ||
if (node.kind === ts.SyntaxKind.InterfaceDeclaration) { | ||
this.report(tag, E.implementsTagOnInterface()); | ||
} | ||
if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) { | ||
this.report(tag, E.implementsTagOnTypeAlias()); | ||
} | ||
}; | ||
Extractor.prototype.collectHeritageInterfaces = function (node) { | ||
var _this = this; | ||
if (node.heritageClauses == null) | ||
return null; | ||
var maybeInterfaces = node.heritageClauses.flatMap(function (clause) { | ||
if (clause.token !== ts.SyntaxKind.ImplementsKeyword) | ||
return []; | ||
return clause.types.map(function (type) { | ||
if (!ts.isIdentifier(type.expression)) { | ||
// TODO: Are there valid cases we want to cover here? | ||
return null; | ||
} | ||
var namedType = _this.gqlNamedType(type.expression, TypeContext_1.UNRESOLVED_REFERENCE_NAME); | ||
_this.ctx.markUnresolvedType(type.expression, namedType.name); | ||
var maybeInterfaces = node.heritageClauses | ||
.filter(function (clause) { | ||
if (node.kind === ts.SyntaxKind.ClassDeclaration) { | ||
return clause.token === ts.SyntaxKind.ImplementsKeyword; | ||
} | ||
// Interfaces can only have extends clauses, and those are allowed. | ||
return true; | ||
}) | ||
.flatMap(function (clause) { | ||
return clause.types | ||
.map(function (type) { return type.expression; }) | ||
.filter(function (expression) { return ts.isIdentifier(expression); }) | ||
.filter(function (expression) { return _this.symbolHasGqlTag(expression); }) | ||
.map(function (expression) { | ||
var namedType = _this.gql.namedType(expression, TypeContext_1.UNRESOLVED_REFERENCE_NAME); | ||
_this.ctx.markUnresolvedType(expression, namedType.name); | ||
return namedType; | ||
@@ -648,3 +614,18 @@ }); | ||
}; | ||
Extractor.prototype.symbolHasGqlTag = function (node) { | ||
var symbol = this.ctx.checker.getSymbolAtLocation(node); | ||
if (symbol == null) | ||
return false; | ||
var declaration = this.ctx.findSymbolDeclaration(symbol); | ||
if (declaration == null) | ||
return false; | ||
return this.hasGqlTag(declaration); | ||
}; | ||
Extractor.prototype.hasGqlTag = function (node) { | ||
return ts.getJSDocTags(node).some(function (tag) { | ||
return exports.ALL_TAGS.includes(tag.tagName.text); | ||
}); | ||
}; | ||
Extractor.prototype.interfaceInterfaceDeclaration = function (node, tag) { | ||
var _this = this; | ||
var name = this.entityName(node, tag); | ||
@@ -654,16 +635,24 @@ if (name == null || name.value == null) { | ||
} | ||
// Prevent using merged interfaces as GraphQL interfaces. | ||
// https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces | ||
var symbol = this.ctx.checker.getSymbolAtLocation(node.name); | ||
if (symbol != null && | ||
symbol.declarations != null && | ||
symbol.declarations.length > 1) { | ||
var otherLocations = symbol.declarations | ||
.filter(function (d) { return d !== node && ts.isInterfaceDeclaration(d); }) | ||
.map(function (d) { | ||
var _a; | ||
var locNode = (_a = ts.getNameOfDeclaration(d)) !== null && _a !== void 0 ? _a : d; | ||
return _this.related(locNode, "Other declaration"); | ||
}); | ||
if (otherLocations.length > 0) { | ||
return this.report(node.name, E.mergedInterfaces(name.value), otherLocations); | ||
} | ||
} | ||
var description = this.collectDescription(node.name); | ||
var interfaces = this.collectInterfaces(node); | ||
var fields = this.collectFields(node); | ||
this.ctx.recordTypeName(node.name, name.value); | ||
// While GraphQL supports interfaces that extend other interfaces, | ||
// TypeScript does not. So we can't support that here either. | ||
// In the future we could support classes that act as interfaces through | ||
// inheritance. | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.INTERFACE_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description || undefined, | ||
name: name, | ||
fields: fields | ||
}); | ||
this.ctx.recordTypeName(node.name, name, "INTERFACE"); | ||
this.definitions.push(this.gql.interfaceTypeDefinition(node, name, fields, interfaces, description)); | ||
}; | ||
@@ -674,5 +663,26 @@ Extractor.prototype.collectFields = function (node) { | ||
ts.forEachChild(node, function (node) { | ||
var e_4, _a; | ||
if (ts.isConstructorDeclaration(node)) { | ||
try { | ||
// Handle parameter properties | ||
// https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties | ||
for (var _b = __values(node.parameters), _c = _b.next(); !_c.done; _c = _b.next()) { | ||
var param = _c.value; | ||
var field = _this.constructorParam(param); | ||
if (field != null) { | ||
fields.push(field); | ||
} | ||
} | ||
} | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
finally { | ||
try { | ||
if (_c && !_c.done && (_a = _b["return"])) _a.call(_b); | ||
} | ||
finally { if (e_4) throw e_4.error; } | ||
} | ||
} | ||
if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) { | ||
var field = _this.methodDeclaration(node); | ||
if (field) { | ||
if (field != null) { | ||
fields.push(field); | ||
@@ -691,4 +701,54 @@ } | ||
}; | ||
Extractor.prototype.constructorParam = function (node) { | ||
var tag = this.findTag(node, exports.FIELD_TAG); | ||
if (tag == null) | ||
return null; | ||
if (node.modifiers == null) { | ||
return this.report(node, E.parameterWithoutModifiers()); | ||
} | ||
var isParameterProperty = node.modifiers.some(function (modifier) { | ||
return modifier.kind === ts.SyntaxKind.PublicKeyword || | ||
modifier.kind === ts.SyntaxKind.PrivateKeyword || | ||
modifier.kind === ts.SyntaxKind.ProtectedKeyword || | ||
modifier.kind === ts.SyntaxKind.ReadonlyKeyword; | ||
}); | ||
if (!isParameterProperty) { | ||
return this.report(node, E.parameterWithoutModifiers()); | ||
} | ||
var notPublic = node.modifiers.find(function (modifier) { | ||
return modifier.kind === ts.SyntaxKind.PrivateKeyword || | ||
modifier.kind === ts.SyntaxKind.ProtectedKeyword; | ||
}); | ||
if (notPublic != null) { | ||
return this.report(notPublic, E.parameterPropertyNotPublic()); | ||
} | ||
var name = this.entityName(node, tag); | ||
if (name == null) | ||
return null; | ||
if (node.type == null) { | ||
return this.report(node, E.parameterPropertyMissingType()); | ||
} | ||
var id = node.name; | ||
if (ts.isArrayBindingPattern(id) || ts.isObjectBindingPattern(id)) { | ||
// TypeScript triggers an error if a binding pattern is used for a | ||
// parameter property, so we don't need to report them. | ||
// https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8BQ1rHgOwgFwCcBXYPeAgCgAciAjEAS2BQDNEBfAShXdXaA | ||
return null; | ||
} | ||
var directives = []; | ||
if (id.text !== name.value) { | ||
directives = [this.fieldNameDirective(node.name, id.text)]; | ||
} | ||
var type = this.collectType(node.type); | ||
if (type == null) | ||
return null; | ||
var deprecated = this.collectDeprecated(node); | ||
if (deprecated != null) { | ||
directives.push(deprecated); | ||
} | ||
var description = this.collectDescription(node.name); | ||
return this.gql.fieldDefinition(node, name, this.handleErrorBubbling(node, type), null, directives, description); | ||
}; | ||
Extractor.prototype.collectArgs = function (argsParam) { | ||
var e_4, _a; | ||
var e_5, _a; | ||
var args = []; | ||
@@ -699,3 +759,3 @@ var argsType = argsParam.type; | ||
} | ||
if (argsType.kind === ts.SyntaxKind.NeverKeyword) { | ||
if (argsType.kind === ts.SyntaxKind.UnknownKeyword) { | ||
return []; | ||
@@ -719,3 +779,3 @@ } | ||
} | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
catch (e_5_1) { e_5 = { error: e_5_1 }; } | ||
finally { | ||
@@ -725,3 +785,3 @@ try { | ||
} | ||
finally { if (e_4) throw e_4.error; } | ||
finally { if (e_5) throw e_5.error; } | ||
} | ||
@@ -731,3 +791,3 @@ return args; | ||
Extractor.prototype.collectArgDefaults = function (node) { | ||
var e_5, _a; | ||
var e_6, _a; | ||
var defaults = new Map(); | ||
@@ -744,3 +804,3 @@ try { | ||
} | ||
catch (e_5_1) { e_5 = { error: e_5_1 }; } | ||
catch (e_6_1) { e_6 = { error: e_6_1 }; } | ||
finally { | ||
@@ -750,3 +810,3 @@ try { | ||
} | ||
finally { if (e_5) throw e_5.error; } | ||
finally { if (e_6) throw e_6.error; } | ||
} | ||
@@ -757,19 +817,20 @@ return defaults; | ||
if (ts.isStringLiteral(node)) { | ||
return { kind: graphql_1.Kind.STRING, loc: this.loc(node), value: node.text }; | ||
return this.gql.string(node, node.text); | ||
} | ||
else if (ts.isNumericLiteral(node)) { | ||
var kind = node.text.includes(".") ? graphql_1.Kind.FLOAT : graphql_1.Kind.INT; | ||
return { kind: kind, loc: this.loc(node), value: node.text }; | ||
return node.text.includes(".") | ||
? this.gql.float(node, node.text) | ||
: this.gql.int(node, node.text); | ||
} | ||
else if (this.isNullish(node)) { | ||
return { kind: graphql_1.Kind.NULL, loc: this.loc(node) }; | ||
return this.gql["null"](node); | ||
} | ||
else if (node.kind === ts.SyntaxKind.TrueKeyword) { | ||
return { kind: graphql_1.Kind.BOOLEAN, loc: this.loc(node), value: true }; | ||
return this.gql.boolean(node, true); | ||
} | ||
else if (node.kind === ts.SyntaxKind.FalseKeyword) { | ||
return { kind: graphql_1.Kind.BOOLEAN, loc: this.loc(node), value: false }; | ||
return this.gql.boolean(node, false); | ||
} | ||
else if (ts.isObjectLiteralExpression(node)) { | ||
return this.cellectObjectLiteral(node); | ||
return this.collectObjectLiteral(node); | ||
} | ||
@@ -779,6 +840,6 @@ else if (ts.isArrayLiteralExpression(node)) { | ||
} | ||
return this.reportUnhandled(node, E.defaultValueIsNotLiteral()); | ||
return this.reportUnhandled(node, "constant value", E.defaultValueIsNotLiteral()); | ||
}; | ||
Extractor.prototype.collectArrayLiteral = function (node) { | ||
var e_6, _a; | ||
var e_7, _a; | ||
var values = []; | ||
@@ -798,3 +859,3 @@ var errors = false; | ||
} | ||
catch (e_6_1) { e_6 = { error: e_6_1 }; } | ||
catch (e_7_1) { e_7 = { error: e_7_1 }; } | ||
finally { | ||
@@ -804,3 +865,3 @@ try { | ||
} | ||
finally { if (e_6) throw e_6.error; } | ||
finally { if (e_7) throw e_7.error; } | ||
} | ||
@@ -810,10 +871,6 @@ if (errors) { | ||
} | ||
return { | ||
kind: graphql_1.Kind.LIST, | ||
loc: this.loc(node), | ||
values: values | ||
}; | ||
return this.gql.list(node, values); | ||
}; | ||
Extractor.prototype.cellectObjectLiteral = function (node) { | ||
var e_7, _a; | ||
Extractor.prototype.collectObjectLiteral = function (node) { | ||
var e_8, _a; | ||
var fields = []; | ||
@@ -833,3 +890,3 @@ var errors = false; | ||
} | ||
catch (e_7_1) { e_7 = { error: e_7_1 }; } | ||
catch (e_8_1) { e_8 = { error: e_8_1 }; } | ||
finally { | ||
@@ -839,3 +896,3 @@ try { | ||
} | ||
finally { if (e_7) throw e_7.error; } | ||
finally { if (e_8) throw e_8.error; } | ||
} | ||
@@ -845,14 +902,10 @@ if (errors) { | ||
} | ||
return { | ||
kind: graphql_1.Kind.OBJECT, | ||
loc: this.loc(node), | ||
fields: fields | ||
}; | ||
return this.gql.object(node, fields); | ||
}; | ||
Extractor.prototype.collectObjectField = function (node) { | ||
if (!ts.isPropertyAssignment(node)) { | ||
return this.reportUnhandled(node, E.defaultArgElementIsNotAssignment()); | ||
return this.reportUnhandled(node, "constant value", E.defaultArgElementIsNotAssignment()); | ||
} | ||
if (node.name == null) { | ||
return this.reportUnhandled(node, E.defaultArgPropertyMissingName()); | ||
return this.reportUnhandled(node, "field", E.defaultArgPropertyMissingName()); | ||
} | ||
@@ -869,8 +922,3 @@ var name = this.expectIdentifier(node.name); | ||
return null; | ||
return { | ||
kind: graphql_1.Kind.OBJECT_FIELD, | ||
loc: this.loc(node), | ||
name: this.gqlName(node.name, name.text), | ||
value: value | ||
}; | ||
return this.gql.constObjectField(node, this.gql.name(node.name, name.text), value); | ||
}; | ||
@@ -899,3 +947,3 @@ Extractor.prototype.collectArg = function (node, defaults) { | ||
*/ | ||
type = this.gqlNullableType(type); | ||
type = this.gql.nullableType(type); | ||
} | ||
@@ -911,11 +959,3 @@ var description = this.collectDescription(node.name); | ||
var deprecatedDirective = this.collectDeprecated(node); | ||
return { | ||
kind: graphql_1.Kind.INPUT_VALUE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description || undefined, | ||
name: this.gqlName(node.name, node.name.text), | ||
type: type, | ||
defaultValue: defaultValue || undefined, | ||
directives: deprecatedDirective != null ? [deprecatedDirective] : undefined | ||
}; | ||
return this.gql.inputValueDefinition(node, this.gql.name(node.name, node.name.text), type, deprecatedDirective == null ? null : [deprecatedDirective], defaultValue, description); | ||
}; | ||
@@ -929,10 +969,4 @@ Extractor.prototype.enumEnumDeclaration = function (node, tag) { | ||
var values = this.collectEnumValues(node); | ||
this.ctx.recordTypeName(node.name, name.value); | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.ENUM_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description || undefined, | ||
name: name, | ||
values: values | ||
}); | ||
this.ctx.recordTypeName(node.name, name, "ENUM"); | ||
this.definitions.push(this.gql.enumTypeDefinition(node, name, values, description)); | ||
}; | ||
@@ -948,13 +982,7 @@ Extractor.prototype.enumTypeAliasDeclaration = function (node, tag) { | ||
var description = this.collectDescription(node.name); | ||
this.ctx.recordTypeName(node.name, name.value); | ||
this.definitions.push({ | ||
kind: graphql_1.Kind.ENUM_TYPE_DEFINITION, | ||
loc: this.loc(node), | ||
description: description || undefined, | ||
name: name, | ||
values: values | ||
}); | ||
this.ctx.recordTypeName(node.name, name, "ENUM"); | ||
this.definitions.push(this.gql.enumTypeDefinition(node, name, values, description)); | ||
}; | ||
Extractor.prototype.enumTypeAliasVariants = function (node) { | ||
var e_8, _a; | ||
var e_9, _a; | ||
var _b; | ||
@@ -969,12 +997,7 @@ // Semantically we only support deriving enums from type aliases that | ||
return [ | ||
{ | ||
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION, | ||
name: this.gqlName(node.type.literal, node.type.literal.text), | ||
description: undefined, | ||
loc: this.loc(node) | ||
}, | ||
this.gql.enumValueDefinition(node, this.gql.name(node.type.literal, node.type.literal.text), undefined, null), | ||
]; | ||
} | ||
if (!ts.isUnionTypeNode(node.type)) { | ||
this.reportUnhandled(node.type, E.enumTagOnInvalidNode()); | ||
this.reportUnhandled(node.type, "union", E.enumTagOnInvalidNode()); | ||
return null; | ||
@@ -998,9 +1021,3 @@ } | ||
var memberDescription = this.collectDescription(declaration.name); | ||
values.push({ | ||
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION, | ||
name: this.gqlName(declaration.type.literal, declaration.type.literal.text), | ||
directives: deprecatedDirective ? [deprecatedDirective] : [], | ||
description: memberDescription || undefined, | ||
loc: this.loc(node) | ||
}); | ||
values.push(this.gql.enumValueDefinition(node, this.gql.name(declaration.type.literal, declaration.type.literal.text), deprecatedDirective ? [deprecatedDirective] : [], memberDescription)); | ||
continue; | ||
@@ -1014,3 +1031,3 @@ } | ||
!ts.isStringLiteral(member.literal)) { | ||
this.reportUnhandled(member, E.enumVariantNotStringLiteral()); | ||
this.reportUnhandled(member, "union member", E.enumVariantNotStringLiteral()); | ||
continue; | ||
@@ -1020,11 +1037,6 @@ } | ||
// does not allow comments attached to string literal types. | ||
values.push({ | ||
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION, | ||
name: this.gqlName(member.literal, member.literal.text), | ||
description: undefined, | ||
loc: this.loc(member) | ||
}); | ||
values.push(this.gql.enumValueDefinition(node, this.gql.name(member.literal, member.literal.text), undefined, null)); | ||
} | ||
} | ||
catch (e_8_1) { e_8 = { error: e_8_1 }; } | ||
catch (e_9_1) { e_9 = { error: e_9_1 }; } | ||
finally { | ||
@@ -1034,3 +1046,3 @@ try { | ||
} | ||
finally { if (e_8) throw e_8.error; } | ||
finally { if (e_9) throw e_9.error; } | ||
} | ||
@@ -1040,3 +1052,3 @@ return values; | ||
Extractor.prototype.collectEnumValues = function (node) { | ||
var e_9, _a; | ||
var e_10, _a; | ||
var values = []; | ||
@@ -1048,3 +1060,3 @@ try { | ||
!ts.isStringLiteral(member.initializer)) { | ||
this.reportUnhandled(member, E.enumVariantMissingInitializer()); | ||
this.reportUnhandled(member, "enum value", E.enumVariantMissingInitializer()); | ||
continue; | ||
@@ -1054,12 +1066,6 @@ } | ||
var deprecated = this.collectDeprecated(member); | ||
values.push({ | ||
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION, | ||
loc: this.loc(member), | ||
description: description || undefined, | ||
name: this.gqlName(member.initializer, member.initializer.text), | ||
directives: deprecated ? [deprecated] : undefined | ||
}); | ||
values.push(this.gql.enumValueDefinition(member, this.gql.name(member.initializer, member.initializer.text), deprecated ? [deprecated] : undefined, description)); | ||
} | ||
} | ||
catch (e_9_1) { e_9 = { error: e_9_1 }; } | ||
catch (e_10_1) { e_10 = { error: e_10_1 }; } | ||
finally { | ||
@@ -1069,3 +1075,3 @@ try { | ||
} | ||
finally { if (e_9) throw e_9.error; } | ||
finally { if (e_10) throw e_10.error; } | ||
} | ||
@@ -1079,3 +1085,24 @@ return values; | ||
// FIXME: Use the _value_'s location not the tag's | ||
return this.gqlName(tag, commentName); | ||
var locNode = tag; | ||
// Test for leading newlines using the raw text | ||
var hasLeadingNewlines = /\n/.test(tag.getText().trimEnd()); | ||
var hasInternalWhitespace = /\s/.test(commentName); | ||
var validationMessage = graphQLNameValidationMessage(commentName); | ||
if (hasLeadingNewlines && validationMessage == null) { | ||
// TODO: Offer quick fix. | ||
return this.report(locNode, E.graphQLNameHasLeadingNewlines(commentName, tag.tagName.text)); | ||
} | ||
if (hasLeadingNewlines || hasInternalWhitespace) { | ||
return this.report(locNode, E.graphQLTagNameHasWhitespace(tag.tagName.text)); | ||
} | ||
// No whitespace, but still invalid. We will assume they meant this to | ||
// be a GraphQL name but didn't provide a valid identifier. | ||
// | ||
// NOTE: We can't let GraphQL validation handle this, because it throws rather | ||
// than returning a validation message. Presumably because it expects token | ||
// validation to be done during lexing/parsing. | ||
if (validationMessage !== null) { | ||
return this.report(locNode, validationMessage); | ||
} | ||
return this.gql.name(locNode, commentName); | ||
} | ||
@@ -1089,4 +1116,43 @@ } | ||
return null; | ||
return this.gqlName(id, id.text); | ||
return this.gql.name(id, id.text); | ||
}; | ||
// Ensure the type of the ctx param resolves to the declaration | ||
// annotated with `@gqlContext`. | ||
Extractor.prototype.validateContextParameter = function (node) { | ||
if (node.type == null) { | ||
return this.report(node, E.expectedTypeAnnotationOnContext()); | ||
} | ||
if (node.type.kind === ts.SyntaxKind.UnknownKeyword) { | ||
// If the user just needs to define the argument to get to a later parameter, | ||
// they can use `ctx: unknown` to safely avoid triggering a Grats error. | ||
return; | ||
} | ||
if (!ts.isTypeReferenceNode(node.type)) { | ||
return this.report(node.type, E.expectedTypeAnnotationOfReferenceOnContext()); | ||
} | ||
// Check for ... | ||
if (node.dotDotDotToken != null) { | ||
return this.report(node.dotDotDotToken, E.unexpectedParamSpreadForContextParam()); | ||
} | ||
var symbol = this.ctx.checker.getSymbolAtLocation(node.type.typeName); | ||
if (symbol == null) { | ||
return this.report(node.type.typeName, E.expectedTypeAnnotationOnContextToBeResolvable()); | ||
} | ||
var declaration = this.ctx.findSymbolDeclaration(symbol); | ||
if (declaration == null) { | ||
return this.report(node.type.typeName, E.expectedTypeAnnotationOnContextToHaveDeclaration()); | ||
} | ||
if (this.ctx.gqlContext == null) { | ||
// This is the first typed context value we've seen... | ||
this.ctx.gqlContext = { | ||
declaration: declaration, | ||
firstReference: node.type.typeName | ||
}; | ||
} | ||
else if (this.ctx.gqlContext.declaration !== declaration) { | ||
return this.report(node.type.typeName, E.multipleContextTypes(), [ | ||
this.related(this.ctx.gqlContext.firstReference, "A different type reference was used here"), | ||
]); | ||
} | ||
}; | ||
Extractor.prototype.methodDeclaration = function (node) { | ||
@@ -1102,3 +1168,6 @@ var tag = this.findTag(node, exports.FIELD_TAG); | ||
} | ||
var type = this.collectMethodType(node.type); | ||
var returnType = this.collectReturnType(node.type); | ||
if (returnType == null) | ||
return null; | ||
var type = returnType.type, isStream = returnType.isStream; | ||
// We already reported an error | ||
@@ -1112,2 +1181,6 @@ if (type == null) | ||
} | ||
var context = node.parameters[1]; | ||
if (context != null) { | ||
this.validateContextParameter(context); | ||
} | ||
var description = this.collectDescription(node.name); | ||
@@ -1119,4 +1192,7 @@ var id = this.expectIdentifier(node.name); | ||
if (id.text !== name.value) { | ||
directives = [this.methodNameDirective(node.name, id.text)]; | ||
directives = [this.fieldNameDirective(node.name, id.text)]; | ||
} | ||
if (isStream) { | ||
directives.push(this.gql.constDirective(node.type, this.gql.name(node.type, serverDirectives_1.ASYNC_ITERABLE_TYPE_DIRECTIVE), null)); | ||
} | ||
var deprecated = this.collectDeprecated(node); | ||
@@ -1126,21 +1202,31 @@ if (deprecated != null) { | ||
} | ||
return { | ||
kind: graphql_1.Kind.FIELD_DEFINITION, | ||
loc: this.loc(node), | ||
description: description || undefined, | ||
name: name, | ||
arguments: args || undefined, | ||
type: this.handleErrorBubbling(node, type), | ||
directives: directives.length === 0 ? undefined : directives | ||
}; | ||
return this.gql.fieldDefinition(node, name, this.handleErrorBubbling(node, type), args, directives, description); | ||
}; | ||
Extractor.prototype.collectMethodType = function (node) { | ||
var inner = this.maybeUnwrapePromise(node); | ||
Extractor.prototype.collectReturnType = function (node) { | ||
if (ts.isTypeReferenceNode(node)) { | ||
var identifier = this.expectIdentifier(node.typeName); | ||
if (identifier == null) | ||
return null; | ||
if (identifier.text == "AsyncIterable") { | ||
if (node.typeArguments == null || node.typeArguments.length === 0) { | ||
// TODO: Better error? | ||
return this.report(node, E.promiseMissingTypeArg()); | ||
} | ||
var t_1 = this.collectType(node.typeArguments[0]); | ||
if (t_1 == null) | ||
return null; | ||
return { type: t_1, isStream: true }; | ||
} | ||
} | ||
var inner = this.maybeUnwrapPromise(node); | ||
if (inner == null) | ||
return null; | ||
return this.collectType(inner); | ||
var t = this.collectType(inner); | ||
if (t == null) | ||
return null; | ||
return { type: t, isStream: false }; | ||
}; | ||
Extractor.prototype.collectPropertyType = function (node) { | ||
// TODO: Handle function types here. | ||
var inner = this.maybeUnwrapePromise(node); | ||
var inner = this.maybeUnwrapPromise(node); | ||
if (inner == null) | ||
@@ -1150,3 +1236,3 @@ return null; | ||
}; | ||
Extractor.prototype.maybeUnwrapePromise = function (node) { | ||
Extractor.prototype.maybeUnwrapPromise = function (node) { | ||
if (ts.isTypeReferenceNode(node)) { | ||
@@ -1157,3 +1243,3 @@ var identifier = this.expectIdentifier(node.typeName); | ||
if (identifier.text === "Promise") { | ||
if (node.typeArguments == null) { | ||
if (node.typeArguments == null || node.typeArguments.length === 0) { | ||
return this.report(node, E.promiseMissingTypeArg()); | ||
@@ -1174,8 +1260,3 @@ } | ||
if (description) { | ||
return { | ||
kind: graphql_1.Kind.STRING, | ||
loc: this.loc(node), | ||
value: description, | ||
block: true | ||
}; | ||
return this.gql.string(node, description.trim(), true); | ||
} | ||
@@ -1193,12 +1274,6 @@ return null; | ||
// FIXME: Use the _value_'s location not the tag's | ||
reason = this.gqlConstArgument(tag, this.gqlName(tag, "reason"), this.gqlString(tag, reasonComment)); | ||
reason = this.gql.constArgument(tag, this.gql.name(tag, "reason"), this.gql.string(tag, reasonComment)); | ||
} | ||
} | ||
var args = reason == null ? undefined : [reason]; | ||
return { | ||
kind: graphql_1.Kind.DIRECTIVE, | ||
loc: this.loc(tag.tagName), | ||
name: this.gqlName(tag.tagName, DEPRECATED_TAG), | ||
arguments: args | ||
}; | ||
return this.gql.constDirective(tag.tagName, this.gql.name(node, DEPRECATED_TAG), reason == null ? null : [reason]); | ||
}; | ||
@@ -1220,3 +1295,3 @@ Extractor.prototype.property = function (node) { | ||
return null; | ||
var type = node.questionToken == null ? inner : this.gqlNullableType(inner); | ||
var type = node.questionToken == null ? inner : this.gql.nullableType(inner); | ||
var description = this.collectDescription(node.name); | ||
@@ -1232,13 +1307,5 @@ var directives = []; | ||
if (id.text !== name.value) { | ||
directives = [this.methodNameDirective(node.name, id.text)]; | ||
directives = [this.fieldNameDirective(node.name, id.text)]; | ||
} | ||
return { | ||
kind: graphql_1.Kind.FIELD_DEFINITION, | ||
loc: this.loc(node), | ||
description: description || undefined, | ||
name: name, | ||
arguments: undefined, | ||
type: this.handleErrorBubbling(node, type), | ||
directives: directives.length === 0 ? undefined : directives | ||
}; | ||
return this.gql.fieldDefinition(node, name, this.handleErrorBubbling(node, type), null, directives, description); | ||
}; | ||
@@ -1259,3 +1326,3 @@ // TODO: Support separate modes for input and output types | ||
return null; | ||
return this.gqlNonNullType(node, this.gqlListType(node, element)); | ||
return this.gql.nonNullType(node, this.gql.listType(node, element)); | ||
} | ||
@@ -1280,5 +1347,5 @@ else if (ts.isUnionTypeNode(node)) { | ||
if (node.types.length > 1) { | ||
return this.gqlNullableType(type); | ||
return this.gql.nullableType(type); | ||
} | ||
return this.gqlNonNullType(node, type); | ||
return this.gql.nonNullType(node, type); | ||
} | ||
@@ -1289,6 +1356,6 @@ else if (ts.isParenthesizedTypeNode(node)) { | ||
else if (node.kind === ts.SyntaxKind.StringKeyword) { | ||
return this.gqlNonNullType(node, this.gqlNamedType(node, "String")); | ||
return this.gql.nonNullType(node, this.gql.namedType(node, "String")); | ||
} | ||
else if (node.kind === ts.SyntaxKind.BooleanKeyword) { | ||
return this.gqlNonNullType(node, this.gqlNamedType(node, "Boolean")); | ||
return this.gql.nonNullType(node, this.gql.namedType(node, "Boolean")); | ||
} | ||
@@ -1302,3 +1369,3 @@ else if (node.kind === ts.SyntaxKind.NumberKeyword) { | ||
// TODO: Better error message. This is okay if it's a type reference, but everything else is not. | ||
this.reportUnhandled(node, E.unknownGraphQLType()); | ||
this.reportUnhandled(node, "type", E.unknownGraphQLType()); | ||
return null; | ||
@@ -1321,3 +1388,3 @@ }; | ||
return null; | ||
return this.gqlNonNullType(node, this.gqlListType(node, element)); | ||
return this.gql.nonNullType(node, this.gql.listType(node, element)); | ||
} | ||
@@ -1329,5 +1396,5 @@ default: { | ||
// A later pass will resolve the type. | ||
var namedType = this.gqlNamedType(node, TypeContext_1.UNRESOLVED_REFERENCE_NAME); | ||
var namedType = this.gql.namedType(node, TypeContext_1.UNRESOLVED_REFERENCE_NAME); | ||
this.ctx.markUnresolvedType(node.typeName, namedType.name); | ||
return this.gqlNonNullType(node, namedType); | ||
return this.gql.nonNullType(node, namedType); | ||
} | ||
@@ -1352,12 +1419,25 @@ } | ||
} | ||
return this.report(node, E.expectedIdentifer()); | ||
return this.report(node, E.expectedIdentifier()); | ||
}; | ||
Extractor.prototype.findTag = function (node, tagName) { | ||
var _a; | ||
return ((_a = ts | ||
var _this = this; | ||
var tags = ts | ||
.getJSDocTags(node) | ||
.find(function (tag) { return tag.tagName.escapedText === tagName; })) !== null && _a !== void 0 ? _a : null); | ||
.filter(function (tag) { return tag.tagName.escapedText === tagName; }); | ||
if (tags.length === 0) { | ||
return null; | ||
} | ||
if (tags.length > 1) { | ||
var additionalTags = tags.slice(1).map(function (tag) { | ||
return _this.related(tag, "Additional tag"); | ||
}); | ||
var message = tagName === exports.IMPLEMENTS_TAG_DEPRECATED | ||
? E.duplicateInterfaceTag() | ||
: E.duplicateTag(tagName); | ||
return this.report(tags[0], message, additionalTags); | ||
} | ||
return tags[0]; | ||
}; | ||
// It is a GraphQL best practice to model all fields as nullable. This allows | ||
// the server to handle field level exections by simply returning null for | ||
// the server to handle field level executions by simply returning null for | ||
// that field. | ||
@@ -1378,55 +1458,30 @@ // https://graphql.org/learn/best-practices/#nullability | ||
if (this.configOptions.nullableByDefault) { | ||
return this.gqlNullableType(type); | ||
return this.gql.nullableType(type); | ||
} | ||
return type; | ||
}; | ||
Extractor.prototype.methodNameDirective = function (nameNode, name) { | ||
return this.gqlConstDirective(nameNode, this.gqlName(nameNode, serverDirectives_1.METHOD_NAME_DIRECTIVE), [ | ||
this.gqlConstArgument(nameNode, this.gqlName(nameNode, serverDirectives_1.METHOD_NAME_ARG), this.gqlString(nameNode, name)), | ||
/* Grats directives */ | ||
Extractor.prototype.exportDirective = function (nameNode, jsModulePath, tsModulePath, functionName) { | ||
return this.gql.constDirective(nameNode, this.gql.name(nameNode, serverDirectives_1.EXPORTED_DIRECTIVE), [ | ||
this.gql.constArgument(nameNode, this.gql.name(nameNode, serverDirectives_1.JS_MODULE_PATH_ARG), this.gql.string(nameNode, jsModulePath)), | ||
this.gql.constArgument(nameNode, this.gql.name(nameNode, serverDirectives_1.TS_MODULE_PATH_ARG), this.gql.string(nameNode, tsModulePath)), | ||
this.gql.constArgument(nameNode, this.gql.name(nameNode, serverDirectives_1.EXPORTED_FUNCTION_NAME_ARG), this.gql.string(nameNode, functionName)), | ||
]); | ||
}; | ||
Extractor.prototype.exportDirective = function (nameNode, filename, functionName) { | ||
return this.gqlConstDirective(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_DIRECTIVE), [ | ||
this.gqlConstArgument(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_FILENAME_ARG), this.gqlString(nameNode, filename)), | ||
this.gqlConstArgument(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_FUNCTION_NAME_ARG), this.gqlString(nameNode, functionName)), | ||
Extractor.prototype.fieldNameDirective = function (nameNode, name) { | ||
return this.gql.constDirective(nameNode, this.gql.name(nameNode, serverDirectives_1.METHOD_NAME_DIRECTIVE), [ | ||
this.gql.constArgument(nameNode, this.gql.name(nameNode, serverDirectives_1.METHOD_NAME_ARG), this.gql.string(nameNode, name)), | ||
]); | ||
}; | ||
/** GraphQL AST node helper methods */ | ||
Extractor.prototype.gqlName = function (node, value) { | ||
return { kind: graphql_1.Kind.NAME, loc: this.loc(node), value: value }; | ||
}; | ||
Extractor.prototype.gqlNamedType = function (node, value) { | ||
return { | ||
kind: graphql_1.Kind.NAMED_TYPE, | ||
loc: this.loc(node), | ||
name: this.gqlName(node, value) | ||
}; | ||
}; | ||
Extractor.prototype.gqlNonNullType = function (node, type) { | ||
if (type.kind === graphql_1.Kind.NON_NULL_TYPE) { | ||
return type; | ||
} | ||
return { kind: graphql_1.Kind.NON_NULL_TYPE, loc: this.loc(node), type: type }; | ||
}; | ||
Extractor.prototype.gqlNullableType = function (type) { | ||
var inner = type; | ||
while (inner.kind === graphql_1.Kind.NON_NULL_TYPE) { | ||
inner = inner.type; | ||
} | ||
return inner; | ||
}; | ||
Extractor.prototype.gqlListType = function (node, type) { | ||
return { kind: graphql_1.Kind.LIST_TYPE, loc: this.loc(node), type: type }; | ||
}; | ||
Extractor.prototype.gqlConstArgument = function (node, name, value) { | ||
return { kind: graphql_1.Kind.ARGUMENT, loc: this.loc(node), name: name, value: value }; | ||
}; | ||
Extractor.prototype.gqlConstDirective = function (node, name, args) { | ||
return { kind: graphql_1.Kind.DIRECTIVE, loc: this.loc(node), name: name, arguments: args }; | ||
}; | ||
Extractor.prototype.gqlString = function (node, value) { | ||
return { kind: graphql_1.Kind.STRING, loc: this.loc(node), value: value }; | ||
}; | ||
return Extractor; | ||
}()); | ||
exports.Extractor = Extractor; | ||
function graphQLNameValidationMessage(name) { | ||
try { | ||
(0, graphql_1.assertName)(name); | ||
return null; | ||
} | ||
catch (e) { | ||
return e.message; | ||
} | ||
} |
import * as ts from "typescript"; | ||
export declare function getRelativeOutputPath(options: ts.ParsedCommandLine, sourceFile: ts.SourceFile): string; | ||
export declare function getRelativeOutputPath(options: ts.ParsedCommandLine, sourceFile: ts.SourceFile): { | ||
jsModulePath: string; | ||
tsModulePath: string; | ||
}; | ||
export declare function resolveRelativePath(relativePath: string): string; |
@@ -13,3 +13,3 @@ "use strict"; | ||
// step and the runtime can agree on. This path is that thing. | ||
var gratsRoot = __dirname; | ||
var gratsRoot = (0, path_1.join)(__dirname, "../.."); | ||
function getRelativeOutputPath(options, sourceFile) { | ||
@@ -25,3 +25,5 @@ var fileNames = ts.getOutputFileNames(options, sourceFile.fileName, true); | ||
} | ||
return (0, path_1.relative)(gratsRoot, fileNames[0]); | ||
var jsModulePath = (0, path_1.relative)(gratsRoot, fileNames[0]); | ||
var tsModulePath = (0, path_1.relative)(gratsRoot, sourceFile.fileName); | ||
return { jsModulePath: jsModulePath, tsModulePath: tsModulePath }; | ||
} | ||
@@ -28,0 +30,0 @@ exports.getRelativeOutputPath = getRelativeOutputPath; |
@@ -44,2 +44,3 @@ "use strict"; | ||
var serverDirectives_1 = require("./serverDirectives"); | ||
var helpers_1 = require("./utils/helpers"); | ||
var serverDirectives_2 = require("./serverDirectives"); | ||
@@ -91,2 +92,3 @@ __createBinding(exports, serverDirectives_2, "applyServerDirectives"); | ||
var definitions = Array.from(serverDirectives_1.DIRECTIVES_AST.definitions); | ||
var errors = []; | ||
try { | ||
@@ -103,3 +105,4 @@ for (var _c = __values(program.getSourceFiles()), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
if (typeErrors.length > 0) { | ||
return (0, DiagnosticError_1.err)(typeErrors); | ||
(0, helpers_1.extend)(errors, typeErrors); | ||
continue; | ||
} | ||
@@ -114,3 +117,4 @@ } | ||
// the first one. | ||
return (0, DiagnosticError_1.err)([syntaxErrors[0]]); | ||
errors.push(syntaxErrors[0]); | ||
continue; | ||
} | ||
@@ -120,4 +124,6 @@ } | ||
var extractedResult = extractor.extract(); | ||
if (extractedResult.kind === "ERROR") | ||
return extractedResult; | ||
if (extractedResult.kind === "ERROR") { | ||
(0, helpers_1.extend)(errors, extractedResult.err); | ||
continue; | ||
} | ||
try { | ||
@@ -145,6 +151,23 @@ for (var _e = (e_2 = void 0, __values(extractedResult.value)), _f = _e.next(); !_f.done; _f = _e.next()) { | ||
} | ||
var docResult = ctx.resolveTypes({ kind: graphql_1.Kind.DOCUMENT, definitions: definitions }); | ||
if (errors.length > 0) { | ||
return (0, DiagnosticError_1.err)(errors); | ||
} | ||
// If you define a field on an interface using the functional style, we need to add | ||
// that field to each concrete type as well. This must be done after all types are created, | ||
// but before we validate the schema. | ||
var definitionsResult = ctx.handleAbstractDefinitions(definitions); | ||
if (definitionsResult.kind === "ERROR") { | ||
return definitionsResult; | ||
} | ||
var docResult = ctx.resolveTypes({ | ||
kind: graphql_1.Kind.DOCUMENT, | ||
definitions: definitionsResult.value | ||
}); | ||
if (docResult.kind === "ERROR") | ||
return docResult; | ||
var doc = docResult.value; | ||
var subscriptionsValidationResult = ctx.validateAsyncIterableFields(doc); | ||
if (subscriptionsValidationResult.kind === "ERROR") { | ||
return subscriptionsValidationResult; | ||
} | ||
// TODO: Currently this does not detect definitions that shadow builtins | ||
@@ -151,0 +174,0 @@ // (`String`, `Int`, etc). However, if we pass a second param (extending an |
@@ -5,5 +5,7 @@ import { DocumentNode, GraphQLSchema } from "graphql"; | ||
export declare const EXPORTED_DIRECTIVE = "exported"; | ||
export declare const EXPORTED_FILENAME_ARG = "filename"; | ||
export declare const JS_MODULE_PATH_ARG = "jsModulePath"; | ||
export declare const TS_MODULE_PATH_ARG = "tsModulePath"; | ||
export declare const EXPORTED_FUNCTION_NAME_ARG = "functionName"; | ||
export declare const ASYNC_ITERABLE_TYPE_DIRECTIVE = "asyncIterable"; | ||
export declare const DIRECTIVES_AST: DocumentNode; | ||
export declare function applyServerDirectives(schema: GraphQLSchema): GraphQLSchema; |
@@ -50,13 +50,17 @@ "use strict"; | ||
exports.__esModule = true; | ||
exports.applyServerDirectives = exports.DIRECTIVES_AST = exports.EXPORTED_FUNCTION_NAME_ARG = exports.EXPORTED_FILENAME_ARG = exports.EXPORTED_DIRECTIVE = exports.METHOD_NAME_ARG = exports.METHOD_NAME_DIRECTIVE = void 0; | ||
exports.applyServerDirectives = exports.DIRECTIVES_AST = exports.ASYNC_ITERABLE_TYPE_DIRECTIVE = exports.EXPORTED_FUNCTION_NAME_ARG = exports.TS_MODULE_PATH_ARG = exports.JS_MODULE_PATH_ARG = exports.EXPORTED_DIRECTIVE = exports.METHOD_NAME_ARG = exports.METHOD_NAME_DIRECTIVE = void 0; | ||
var utils_1 = require("@graphql-tools/utils"); | ||
var graphql_1 = require("graphql"); | ||
var gratsRoot_1 = require("./gratsRoot"); | ||
// TODO: Rename to be generic since it can apply to properties as well as methods. | ||
exports.METHOD_NAME_DIRECTIVE = "methodName"; | ||
exports.METHOD_NAME_ARG = "name"; | ||
exports.EXPORTED_DIRECTIVE = "exported"; | ||
exports.EXPORTED_FILENAME_ARG = "filename"; | ||
exports.JS_MODULE_PATH_ARG = "jsModulePath"; | ||
exports.TS_MODULE_PATH_ARG = "tsModulePath"; | ||
exports.EXPORTED_FUNCTION_NAME_ARG = "functionName"; | ||
exports.DIRECTIVES_AST = (0, graphql_1.parse)("\n directive @".concat(exports.METHOD_NAME_DIRECTIVE, "(").concat(exports.METHOD_NAME_ARG, ": String!) on FIELD_DEFINITION\n directive @").concat(exports.EXPORTED_DIRECTIVE, "(\n ").concat(exports.EXPORTED_FILENAME_ARG, ": String!,\n ").concat(exports.EXPORTED_FUNCTION_NAME_ARG, ": String!\n ) on FIELD_DEFINITION\n")); | ||
exports.ASYNC_ITERABLE_TYPE_DIRECTIVE = "asyncIterable"; | ||
exports.DIRECTIVES_AST = (0, graphql_1.parse)("\n directive @".concat(exports.ASYNC_ITERABLE_TYPE_DIRECTIVE, " on FIELD_DEFINITION\n directive @").concat(exports.METHOD_NAME_DIRECTIVE, "(").concat(exports.METHOD_NAME_ARG, ": String!) on FIELD_DEFINITION\n directive @").concat(exports.EXPORTED_DIRECTIVE, "(\n ").concat(exports.JS_MODULE_PATH_ARG, ": String!,\n ").concat(exports.TS_MODULE_PATH_ARG, ": String!,\n ").concat(exports.EXPORTED_FUNCTION_NAME_ARG, ": String!\n ) on FIELD_DEFINITION\n")); | ||
function applyServerDirectives(schema) { | ||
// TODO: Throw if the schema is missing our directives! | ||
var _a; | ||
@@ -67,3 +71,3 @@ // TODO: Do we really need all of mapSchema here or can we create our own | ||
_a[utils_1.MapperKind.OBJECT_FIELD] = function (fieldConfig) { | ||
var _a, _b; | ||
var _a, _b, _c; | ||
var newFieldConfig = fieldConfig; | ||
@@ -78,2 +82,6 @@ var methodNameDirective = (_a = (0, utils_1.getDirective)(schema, fieldConfig, exports.METHOD_NAME_DIRECTIVE)) === null || _a === void 0 ? void 0 : _a[0]; | ||
} | ||
var asyncIterableDirective = (_c = (0, utils_1.getDirective)(schema, fieldConfig, exports.ASYNC_ITERABLE_TYPE_DIRECTIVE)) === null || _c === void 0 ? void 0 : _c[0]; | ||
if (asyncIterableDirective != null) { | ||
newFieldConfig = __assign(__assign({}, newFieldConfig), { subscribe: newFieldConfig.resolve, resolve: function (payload) { return payload; } }); | ||
} | ||
return newFieldConfig; | ||
@@ -109,16 +117,21 @@ }, | ||
// TODO: Does this work in the browser? | ||
var filename = (0, gratsRoot_1.resolveRelativePath)(methodNameDirective[exports.EXPORTED_FILENAME_ARG]); | ||
var jsModulePath = (0, gratsRoot_1.resolveRelativePath)(methodNameDirective[exports.JS_MODULE_PATH_ARG]); | ||
var tsModulePath = (0, gratsRoot_1.resolveRelativePath)(methodNameDirective[exports.TS_MODULE_PATH_ARG]); | ||
var functionName = methodNameDirective[exports.EXPORTED_FUNCTION_NAME_ARG]; | ||
var mod = undefined; | ||
var modPromise = undefined; | ||
return __assign(__assign({}, fieldConfig), { resolve: function (source, args, context, info) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var mod, e_1, resolve; | ||
var e_1, resolve; | ||
return __generator(this, function (_a) { | ||
var _b; | ||
switch (_a.label) { | ||
case 0: | ||
mod = {}; | ||
if (modPromise == null) { | ||
modPromise = importWithFallback(jsModulePath, tsModulePath); | ||
} | ||
if (!(mod == null)) return [3 /*break*/, 4]; | ||
_a.label = 1; | ||
case 1: | ||
_a.trys.push([1, 3, , 4]); | ||
return [4 /*yield*/, (_b = filename, Promise.resolve().then(function () { return require(_b); }))]; | ||
return [4 /*yield*/, modPromise]; | ||
case 2: | ||
@@ -129,3 +142,3 @@ mod = _a.sent(); | ||
e_1 = _a.sent(); | ||
console.error("Grats Error: Failed to import module `".concat(filename, "`. You may need to rerun Grats.")); | ||
console.error(loadModuleErrorMessage(jsModulePath, tsModulePath)); | ||
throw e_1; | ||
@@ -135,3 +148,4 @@ case 4: | ||
if (typeof resolve !== "function") { | ||
throw new Error("Grats Error: Expected `".concat(filename, "` to have a named export `").concat(functionName, "` that is a function, but it was `").concat(typeof resolve, "`. You may need to rerun Grats.")); | ||
// TODO: Better error message that indicates if it was loaded from JS or TS. | ||
throw new Error("Grats Error: Expected `".concat(tsModulePath, "` to have a named export `").concat(functionName, "` that is a function, but it was `").concat(typeof resolve, "`. You may need to rerun Grats or regenerate the JavaScript version of your module by rerunning the TypeScript compiler.")); | ||
} | ||
@@ -144,1 +158,33 @@ return [2 /*return*/, resolve(source, args, context, info)]; | ||
} | ||
// When people use Grats with loaders like `esbuild-register` or `ts-node`, the | ||
// compiled JavaScript version of the file may not exist on disk. In these specific | ||
// cases, esbuild or ts-node can load the file via its TypeScript source, so we try | ||
// falling back to that. | ||
function importWithFallback(jsModulePath, tsModulePath) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var e_2; | ||
return __generator(this, function (_a) { | ||
var _b, _c; | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 2, , 4]); | ||
return [4 /*yield*/, (_b = tsModulePath, Promise.resolve().then(function () { return require(_b); }))]; | ||
case 1: | ||
// We start with the .ts version because if both exist, and can be loaded, the .ts version is | ||
// going to be more up to date. The downside is that this causes some extra work to be done in | ||
// in prod. This should be manageable since we cache the loaded module for each field. | ||
// It's important that we await here so that we catch the error if the module doesn't exist or | ||
// cannot be parsed. | ||
return [2 /*return*/, _a.sent()]; | ||
case 2: | ||
e_2 = _a.sent(); | ||
return [4 /*yield*/, (_c = jsModulePath, Promise.resolve().then(function () { return require(_c); }))]; | ||
case 3: return [2 /*return*/, _a.sent()]; | ||
case 4: return [2 /*return*/]; | ||
} | ||
}); | ||
}); | ||
} | ||
function loadModuleErrorMessage(jsPath, tsPath) { | ||
return "Grats Error: Failed to import module. Tried loading from two locations:\n* `".concat(jsPath, "`\n* `").concat(tsPath, "`\n\nThis can happen for a few reasons:\n\n* You resolver has moved and you need to rerun Grats to regenerate your schema.\n* Your TypeScript code has changed and you need to rerun `tsc` to generate the JavaScript variant of the file.\n* You compiled your TypeScript with a different TSConfig than what you ran Grats with.\n* The Grats NPM module moved between when you ran Grats and when you ran your server."); | ||
} |
@@ -65,4 +65,7 @@ "use strict"; | ||
var utils_1 = require("@graphql-tools/utils"); | ||
var ts = require("typescript"); | ||
var graphql_1 = require("graphql"); | ||
var commander_1 = require("commander"); | ||
var Locate_1 = require("../Locate"); | ||
var DiagnosticError_1 = require("../utils/DiagnosticError"); | ||
var program = new commander_1.Command(); | ||
@@ -72,3 +75,3 @@ program | ||
.description("Run Grats' internal tests") | ||
.option("-w, --write", "Write the actual ouput of the test to the expected output files. Useful for updating tests.") | ||
.option("-w, --write", "Write the actual output of the test to the expected output files. Useful for updating tests.") | ||
.option("-f, --filter <FILTER_REGEX>", "A regex to filter the tests to run. Only tests with a file path matching the regex will be run.") | ||
@@ -121,2 +124,3 @@ .action(function (_a) { | ||
}); | ||
var gratsDir = path.join(__dirname, "../.."); | ||
var fixturesDir = path.join(__dirname, "fixtures"); | ||
@@ -146,9 +150,26 @@ var integrationFixturesDir = path.join(__dirname, "integrationFixtures"); | ||
}; | ||
var schemaResult = (0, lib_1.buildSchemaResult)(parsedOptions); | ||
// https://stackoverflow.com/a/66604532/1263117 | ||
var compilerHost = ts.createCompilerHost(parsedOptions.options, | ||
/* setParentNodes this is needed for finding jsDocs */ | ||
true); | ||
var schemaResult = (0, lib_1.buildSchemaResultWithHost)(parsedOptions, compilerHost); | ||
if (schemaResult.kind === "ERROR") { | ||
return schemaResult.err.formatDiagnosticsWithContext(); | ||
} | ||
return (0, utils_1.printSchemaWithDirectives)(schemaResult.value, { | ||
assumeValid: true | ||
}); | ||
var LOCATION_REGEX = /^\/\/ Locate: (.*)/; | ||
var locationMatch = code.match(LOCATION_REGEX); | ||
if (locationMatch != null) { | ||
var locResult = (0, Locate_1.locate)(schemaResult.value, locationMatch[1].trim()); | ||
if (locResult.kind === "ERROR") { | ||
return locResult.err; | ||
} | ||
return new DiagnosticError_1.ReportableDiagnostics(compilerHost, [ | ||
(0, DiagnosticError_1.diagnosticAtGraphQLLocation)("Located here", locResult.value), | ||
]).formatDiagnosticsWithContext(); | ||
} | ||
else { | ||
return (0, utils_1.printSchemaWithDirectives)(schemaResult.value, { | ||
assumeValid: true | ||
}); | ||
} | ||
} | ||
@@ -171,5 +192,2 @@ }, | ||
} | ||
if (server.Query == null || typeof server.Query !== "function") { | ||
throw new Error("Expected `".concat(filePath, "` to export a Query type as `Query`")); | ||
} | ||
options = { | ||
@@ -180,3 +198,8 @@ nullableByDefault: true | ||
parsedOptions = { | ||
options: {}, | ||
options: { | ||
// Required to enable ts-node to locate function exports | ||
rootDir: gratsDir, | ||
outDir: "dist", | ||
configFilePath: "tsconfig.json" | ||
}, | ||
raw: { | ||
@@ -196,3 +219,3 @@ grats: options | ||
source: server.query, | ||
rootValue: new server.Query() | ||
rootValue: server.Query != null ? new server.Query() : null | ||
})]; | ||
@@ -199,0 +222,0 @@ case 2: |
@@ -1,6 +0,26 @@ | ||
import { DocumentNode, NameNode } from "graphql"; | ||
import { DefinitionNode, DocumentNode, FieldDefinitionNode, InterfaceTypeDefinitionNode, InterfaceTypeExtensionNode, Location, NameNode, ObjectTypeDefinitionNode, ObjectTypeExtensionNode } from "graphql"; | ||
import * as ts from "typescript"; | ||
import { DiagnosticResult, DiagnosticsResult } from "./utils/DiagnosticError"; | ||
import { InterfaceMap } from "./InterfaceGraph"; | ||
export declare const UNRESOLVED_REFERENCE_NAME = "__UNRESOLVED_REFERENCE__"; | ||
type NameDefinition = { | ||
name: NameNode; | ||
kind: "TYPE" | "INTERFACE" | "UNION" | "SCALAR" | "INPUT_OBJECT" | "ENUM"; | ||
}; | ||
export type GratsDefinitionNode = DefinitionNode | AbstractFieldDefinitionNode; | ||
export type AbstractFieldDefinitionNode = { | ||
readonly kind: "AbstractFieldDefinition"; | ||
readonly loc: Location; | ||
readonly onType: NameNode; | ||
readonly field: FieldDefinitionNode; | ||
}; | ||
/** | ||
* Information about the GraphQL context type. We track the first value we see, | ||
* and then validate that any other values we see are the same. | ||
*/ | ||
type GqlContext = { | ||
declaration: ts.Node; | ||
firstReference: ts.Node; | ||
}; | ||
/** | ||
* Used to track TypeScript references. | ||
@@ -21,13 +41,25 @@ * | ||
_options: ts.ParsedCommandLine; | ||
_symbolToName: Map<ts.Symbol, string>; | ||
_symbolToName: Map<ts.Symbol, NameDefinition>; | ||
_unresolvedTypes: Map<NameNode, ts.Symbol>; | ||
gqlContext: GqlContext | null; | ||
hasTypename: Set<string>; | ||
constructor(options: ts.ParsedCommandLine, checker: ts.TypeChecker, host: ts.CompilerHost); | ||
recordTypeName(node: ts.Node, name: string): void; | ||
recordTypeName(node: ts.Node, name: NameNode, kind: NameDefinition["kind"]): void; | ||
recordHasTypenameField(name: string): void; | ||
markUnresolvedType(node: ts.Node, name: NameNode): void; | ||
findSymbolDeclaration(startSymbol: ts.Symbol): ts.Declaration | null; | ||
resolveSymbol(startSymbol: ts.Symbol): ts.Symbol; | ||
resolveTypes(doc: DocumentNode): DiagnosticsResult<DocumentNode>; | ||
validateAsyncIterableFields(doc: DocumentNode): DiagnosticsResult<void>; | ||
validateField(t: ObjectTypeDefinitionNode | ObjectTypeExtensionNode | InterfaceTypeDefinitionNode | InterfaceTypeExtensionNode): ts.Diagnostic | void; | ||
handleAbstractDefinitions(docs: GratsDefinitionNode[]): DiagnosticsResult<DefinitionNode[]>; | ||
addAbstractFieldDefinition(doc: AbstractFieldDefinitionNode, interfaceGraph: InterfaceMap): DiagnosticsResult<DefinitionNode[]>; | ||
resolveNamedType(unresolved: NameNode): DiagnosticResult<NameNode>; | ||
validateInterfaceImplementorsHaveTypenameField(): DiagnosticResult<null>; | ||
getDestFilePath(sourceFile: ts.SourceFile): string; | ||
err(loc: Location, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): ts.Diagnostic; | ||
relatedInformation(loc: Location, message: string): ts.DiagnosticRelatedInformation; | ||
getDestFilePath(sourceFile: ts.SourceFile): { | ||
jsModulePath: string; | ||
tsModulePath: string; | ||
}; | ||
} | ||
export {}; |
@@ -13,2 +13,13 @@ "use strict"; | ||
}; | ||
var __values = (this && this.__values) || function(o) { | ||
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; | ||
if (m) return m.call(o); | ||
if (o && typeof o.length === "number") return { | ||
next: function () { | ||
if (o && i >= o.length) o = void 0; | ||
return { value: o && o[i++], done: !o }; | ||
} | ||
}; | ||
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); | ||
}; | ||
exports.__esModule = true; | ||
@@ -20,2 +31,7 @@ exports.TypeContext = exports.UNRESOLVED_REFERENCE_NAME = void 0; | ||
var gratsRoot_1 = require("./gratsRoot"); | ||
var serverDirectives_1 = require("./serverDirectives"); | ||
var Extractor_1 = require("./Extractor"); | ||
var E = require("./Errors"); | ||
var InterfaceGraph_1 = require("./InterfaceGraph"); | ||
var helpers_1 = require("./utils/helpers"); | ||
exports.UNRESOLVED_REFERENCE_NAME = "__UNRESOLVED_REFERENCE__"; | ||
@@ -38,2 +54,5 @@ /** | ||
this._unresolvedTypes = new Map(); | ||
// The resolver context declaration, if it has been encountered. | ||
// Gets mutated by Extractor. | ||
this.gqlContext = null; | ||
this.hasTypename = new Set(); | ||
@@ -44,3 +63,3 @@ this._options = options; | ||
} | ||
TypeContext.prototype.recordTypeName = function (node, name) { | ||
TypeContext.prototype.recordTypeName = function (node, name, kind) { | ||
var symbol = this.checker.getSymbolAtLocation(node); | ||
@@ -55,3 +74,3 @@ if (symbol == null) { | ||
} | ||
this._symbolToName.set(symbol, name); | ||
this._symbolToName.set(symbol, { name: name, kind: kind }); | ||
}; | ||
@@ -67,13 +86,30 @@ TypeContext.prototype.recordHasTypenameField = function (name) { | ||
} | ||
if (symbol.flags & ts.SymbolFlags.Alias) { | ||
// Follow any aliases to get the real type declaration. | ||
this._unresolvedTypes.set(name, this.resolveSymbol(symbol)); | ||
}; | ||
TypeContext.prototype.findSymbolDeclaration = function (startSymbol) { | ||
var _a; | ||
var symbol = this.resolveSymbol(startSymbol); | ||
var declaration = (_a = symbol.declarations) === null || _a === void 0 ? void 0 : _a[0]; | ||
return declaration !== null && declaration !== void 0 ? declaration : null; | ||
}; | ||
// Follow symbol aliases until we find the original symbol. Accounts for | ||
// cyclical aliases. | ||
TypeContext.prototype.resolveSymbol = function (startSymbol) { | ||
var symbol = startSymbol; | ||
var visitedSymbols = new Set(); | ||
while (ts.SymbolFlags.Alias & symbol.flags) { | ||
if (visitedSymbols.has(symbol)) { | ||
throw new Error("Cyclical alias detected. Breaking resolution."); | ||
} | ||
visitedSymbols.add(symbol); | ||
symbol = this.checker.getAliasedSymbol(symbol); | ||
} | ||
this._unresolvedTypes.set(name, symbol); | ||
return symbol; | ||
}; | ||
TypeContext.prototype.resolveTypes = function (doc) { | ||
var _a; | ||
var _this = this; | ||
var errors = []; | ||
var newDoc = (0, graphql_1.visit)(doc, { | ||
Name: function (t) { | ||
var newDoc = (0, graphql_1.visit)(doc, (_a = {}, | ||
_a[graphql_1.Kind.NAME] = function (t) { | ||
var namedTypeResult = _this.resolveNamedType(t); | ||
@@ -85,4 +121,4 @@ if (namedTypeResult.kind === "ERROR") { | ||
return namedTypeResult.value; | ||
} | ||
}); | ||
}, | ||
_a)); | ||
if (errors.length > 0) { | ||
@@ -93,2 +129,200 @@ return (0, DiagnosticError_1.err)(errors); | ||
}; | ||
// Ensure that all fields on `Subscription` return an AsyncIterable, and that no other | ||
// fields do. | ||
TypeContext.prototype.validateAsyncIterableFields = function (doc) { | ||
var _a; | ||
var _this = this; | ||
var errors = []; | ||
var visitNode = function (t) { | ||
var validateFieldsResult = _this.validateField(t); | ||
if (validateFieldsResult != null) { | ||
errors.push(validateFieldsResult); | ||
} | ||
}; | ||
(0, graphql_1.visit)(doc, (_a = {}, | ||
_a[graphql_1.Kind.INTERFACE_TYPE_DEFINITION] = visitNode, | ||
_a[graphql_1.Kind.INTERFACE_TYPE_EXTENSION] = visitNode, | ||
_a[graphql_1.Kind.OBJECT_TYPE_DEFINITION] = visitNode, | ||
_a[graphql_1.Kind.OBJECT_TYPE_EXTENSION] = visitNode, | ||
_a)); | ||
if (errors.length > 0) { | ||
return (0, DiagnosticError_1.err)(errors); | ||
} | ||
return (0, DiagnosticError_1.ok)(undefined); | ||
}; | ||
TypeContext.prototype.validateField = function (t) { | ||
var e_1, _a; | ||
var _b; | ||
if (t.fields == null) | ||
return; | ||
// Note: We assume the default name is used here. When custom operation types are supported | ||
// we'll need to update this. | ||
var isSubscription = t.name.value === "Subscription" && | ||
(t.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION || | ||
t.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION); | ||
try { | ||
for (var _c = __values(t.fields), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
var field = _d.value; | ||
var asyncDirective = (_b = field.directives) === null || _b === void 0 ? void 0 : _b.find(function (directive) { return directive.name.value === serverDirectives_1.ASYNC_ITERABLE_TYPE_DIRECTIVE; }); | ||
if (isSubscription && asyncDirective == null) { | ||
if (field.type.loc == null) { | ||
throw new Error("Expected field type to have a location."); | ||
} | ||
return this.err(field.type.loc, E.subscriptionFieldNotAsyncIterable()); | ||
} | ||
if (!isSubscription && asyncDirective != null) { | ||
if (asyncDirective.loc == null) { | ||
throw new Error("Expected asyncDirective to have a location."); | ||
} | ||
return this.err(asyncDirective.loc, // Directive location is the AsyncIterable type. | ||
E.nonSubscriptionFieldAsyncIterable()); | ||
} | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (_d && !_d.done && (_a = _c["return"])) _a.call(_c); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
}; | ||
// TODO: Is this still used? | ||
TypeContext.prototype.handleAbstractDefinitions = function (docs) { | ||
var e_2, _a; | ||
var newDocs = []; | ||
var errors = []; | ||
var interfaceGraphResult = (0, InterfaceGraph_1.computeInterfaceMap)(this, docs); | ||
if (interfaceGraphResult.kind === "ERROR") { | ||
return interfaceGraphResult; | ||
} | ||
var interfaceGraph = interfaceGraphResult.value; | ||
try { | ||
for (var docs_1 = __values(docs), docs_1_1 = docs_1.next(); !docs_1_1.done; docs_1_1 = docs_1.next()) { | ||
var doc = docs_1_1.value; | ||
if (doc.kind === "AbstractFieldDefinition") { | ||
var abstractDocResults = this.addAbstractFieldDefinition(doc, interfaceGraph); | ||
if (abstractDocResults.kind === "ERROR") { | ||
(0, helpers_1.extend)(errors, abstractDocResults.err); | ||
} | ||
else { | ||
(0, helpers_1.extend)(newDocs, abstractDocResults.value); | ||
} | ||
} | ||
else { | ||
newDocs.push(doc); | ||
} | ||
} | ||
} | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
finally { | ||
try { | ||
if (docs_1_1 && !docs_1_1.done && (_a = docs_1["return"])) _a.call(docs_1); | ||
} | ||
finally { if (e_2) throw e_2.error; } | ||
} | ||
if (errors.length > 0) { | ||
return (0, DiagnosticError_1.err)(errors); | ||
} | ||
return (0, DiagnosticError_1.ok)(newDocs); | ||
}; | ||
// A field definition may be on a concrete type, or on an interface. If it's on an interface, | ||
// we need to add it to each concrete type that implements the interface. | ||
TypeContext.prototype.addAbstractFieldDefinition = function (doc, interfaceGraph) { | ||
var e_3, _a; | ||
var _b; | ||
var newDocs = []; | ||
var typeNameResult = this.resolveNamedType(doc.onType); | ||
if (typeNameResult.kind === "ERROR") { | ||
return (0, DiagnosticError_1.err)([typeNameResult.err]); | ||
} | ||
var symbol = this._unresolvedTypes.get(doc.onType); | ||
if (symbol == null) { | ||
// This should have already been handled by resolveNamedType | ||
throw new Error("Expected to find unresolved type."); | ||
} | ||
var nameDefinition = this._symbolToName.get(symbol); | ||
if (nameDefinition == null) { | ||
// This should have already been handled by resolveNamedType | ||
throw new Error("Expected to find name definition."); | ||
} | ||
switch (nameDefinition.kind) { | ||
case "TYPE": | ||
// Extending a type, is just adding a field to it. | ||
newDocs.push({ | ||
kind: graphql_1.Kind.OBJECT_TYPE_EXTENSION, | ||
name: doc.onType, | ||
fields: [doc.field], | ||
loc: doc.loc | ||
}); | ||
break; | ||
case "INTERFACE": { | ||
// Extending an interface is a bit more complicated. We need to add the field | ||
// to the interface, and to each type that implements the interface. | ||
// The interface field definition is not executable, so we don't | ||
// need to annotate it with the details of the implementation. | ||
var directives = (_b = doc.field.directives) === null || _b === void 0 ? void 0 : _b.filter(function (directive) { | ||
return directive.name.value !== serverDirectives_1.EXPORTED_DIRECTIVE; | ||
}); | ||
newDocs.push({ | ||
kind: graphql_1.Kind.INTERFACE_TYPE_EXTENSION, | ||
name: doc.onType, | ||
fields: [__assign(__assign({}, doc.field), { directives: directives })] | ||
}); | ||
try { | ||
for (var _c = __values(interfaceGraph.get(nameDefinition.name.value)), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
var implementor = _d.value; | ||
var name = { | ||
kind: graphql_1.Kind.NAME, | ||
value: implementor.name, | ||
loc: doc.loc | ||
}; | ||
switch (implementor.kind) { | ||
case "TYPE": | ||
newDocs.push({ | ||
kind: graphql_1.Kind.OBJECT_TYPE_EXTENSION, | ||
name: name, | ||
fields: [doc.field], | ||
loc: doc.loc | ||
}); | ||
break; | ||
case "INTERFACE": | ||
newDocs.push({ | ||
kind: graphql_1.Kind.INTERFACE_TYPE_EXTENSION, | ||
name: name, | ||
fields: [__assign(__assign({}, doc.field), { directives: directives })], | ||
loc: doc.loc | ||
}); | ||
break; | ||
} | ||
} | ||
} | ||
catch (e_3_1) { e_3 = { error: e_3_1 }; } | ||
finally { | ||
try { | ||
if (_d && !_d.done && (_a = _c["return"])) _a.call(_c); | ||
} | ||
finally { if (e_3) throw e_3.error; } | ||
} | ||
break; | ||
} | ||
default: { | ||
// Extending any other type of definition is not supported. | ||
var loc = doc.onType.loc; | ||
if (loc == null) { | ||
throw new Error("Expected onType to have a location."); | ||
} | ||
var relatedLoc = nameDefinition.name.loc; | ||
if (relatedLoc == null) { | ||
throw new Error("Expected nameDefinition to have a location."); | ||
} | ||
return (0, DiagnosticError_1.err)([ | ||
this.err(loc, E.invalidTypePassedToFieldFunction(), [ | ||
this.relatedInformation(relatedLoc, "This is the type that was passed to `@".concat(Extractor_1.FIELD_TAG, "`.")), | ||
]), | ||
]); | ||
} | ||
} | ||
return (0, DiagnosticError_1.ok)(newDocs); | ||
}; | ||
TypeContext.prototype.resolveNamedType = function (unresolved) { | ||
@@ -103,21 +337,32 @@ var symbol = this._unresolvedTypes.get(unresolved); | ||
} | ||
var name = this._symbolToName.get(symbol); | ||
if (name == null) { | ||
var nameDefinition = this._symbolToName.get(symbol); | ||
if (nameDefinition == null) { | ||
if (unresolved.loc == null) { | ||
throw new Error("Expected namedType to have a location."); | ||
} | ||
return (0, DiagnosticError_1.err)({ | ||
messageText: "This type is not a valid GraphQL type. Did you mean to annotate it's definition with a `/** @gql */` tag such as `/** @gqlType */` or `/** @gqlInput **/`?", | ||
start: unresolved.loc.start, | ||
length: unresolved.loc.end - unresolved.loc.start, | ||
category: ts.DiagnosticCategory.Error, | ||
code: DiagnosticError_1.FAKE_ERROR_CODE, | ||
file: ts.createSourceFile(unresolved.loc.source.name, unresolved.loc.source.body, ts.ScriptTarget.Latest) | ||
}); | ||
return (0, DiagnosticError_1.err)(this.err(unresolved.loc, E.unresolvedTypeReference())); | ||
} | ||
return (0, DiagnosticError_1.ok)(__assign(__assign({}, unresolved), { value: name })); | ||
return (0, DiagnosticError_1.ok)(__assign(__assign({}, unresolved), { value: nameDefinition.name.value })); | ||
}; | ||
TypeContext.prototype.validateInterfaceImplementorsHaveTypenameField = function () { | ||
return (0, DiagnosticError_1.ok)(null); | ||
TypeContext.prototype.err = function (loc, message, relatedInformation) { | ||
return { | ||
messageText: message, | ||
start: loc.start, | ||
length: loc.end - loc.start, | ||
category: ts.DiagnosticCategory.Error, | ||
code: DiagnosticError_1.FAKE_ERROR_CODE, | ||
file: ts.createSourceFile(loc.source.name, loc.source.body, ts.ScriptTarget.Latest), | ||
relatedInformation: relatedInformation | ||
}; | ||
}; | ||
TypeContext.prototype.relatedInformation = function (loc, message) { | ||
return { | ||
category: ts.DiagnosticCategory.Message, | ||
code: DiagnosticError_1.FAKE_ERROR_CODE, | ||
messageText: message, | ||
file: ts.createSourceFile(loc.source.name, loc.source.body, ts.ScriptTarget.Latest), | ||
start: loc.start, | ||
length: loc.end - loc.start | ||
}; | ||
}; | ||
TypeContext.prototype.getDestFilePath = function (sourceFile) { | ||
@@ -124,0 +369,0 @@ return (0, gratsRoot_1.getRelativeOutputPath)(this._options, sourceFile); |
{ | ||
"name": "grats", | ||
"version": "0.0.0-main-0765b498", | ||
"version": "0.0.0-main-0c545134", | ||
"main": "dist/src/index.js", | ||
@@ -32,4 +32,8 @@ "bin": "dist/src/cli.js", | ||
"packageManager": "pnpm@8.1.1", | ||
"engines": { | ||
"node": ">=16 <=21", | ||
"pnpm": "^8" | ||
}, | ||
"scripts": { | ||
"test": "ts-node --esm src/tests/test.ts", | ||
"test": "ts-node src/tests/test.ts", | ||
"integration-tests": "node src/tests/integration.mjs", | ||
@@ -36,0 +40,0 @@ "build": "tsc --build", |
# -=[ ALPHA SOFTWARE ]=- | ||
**Grats is still experimental. Feel free to try it out and give feedback, but they api is still in flux** | ||
**Grats is still experimental. Feel free to try it out and give feedback, but the api is still in flux** | ||
@@ -11,3 +11,3 @@ # Grats: Implementation-First GraphQL for TypeScript | ||
When you write your GraphQL server in TypeScript, your fields and resovlers | ||
When you write your GraphQL server in TypeScript, your fields and resolvers | ||
are _already_ annotated with type information. _Grats leverages your existing | ||
@@ -18,3 +18,3 @@ type annotations to automatically extract an executable GraphQL schema from your | ||
By making your TypeScript implementation the source of truth, you never have to | ||
worry about valiating that your implementiaton matches your schema. Your | ||
worry about validating that your implementation matches your schema. Your | ||
implementation _is_ your schema! | ||
@@ -30,9 +30,9 @@ | ||
* [@mofeiZ](https://github.com/mofeiZ) and [@alunyov](https://github/alunyov) for their Relay hack-week project exploring a similar idea. | ||
* [@josephsavona](https://github.com/josephsavona) for input on the design of [Relay Resolvers](https://relay.dev/docs/guides/relay-resolvers/) which inspired this project. | ||
* [@bradzacher](https://github.com/bradzacher) for tips on how to handle TypeScript ASTs. | ||
* Everyone who worked on Meta's Hack GraphQL server, the developer experince of which inspired this project. | ||
* A number of other projects which seem to have explored similar ideas in the past: | ||
* [ts2gql](https://github.com/convoyinc/ts2gql) | ||
* [ts2graphql](https://github.com/cevek/ts2graphql) | ||
* [typegraphql-reflection-poc](https://github.com/MichalLytek/typegraphql-reflection-poc) | ||
- [@mofeiZ](https://github.com/mofeiZ) and [@alunyov](https://github/alunyov) for their Relay hack-week project exploring a similar idea. | ||
- [@josephsavona](https://github.com/josephsavona) for input on the design of [Relay Resolvers](https://relay.dev/docs/guides/relay-resolvers/) which inspired this project. | ||
- [@bradzacher](https://github.com/bradzacher) for tips on how to handle TypeScript ASTs. | ||
- Everyone who worked on Meta's Hack GraphQL server, the developer experince of which inspired this project. | ||
- A number of other projects which seem to have explored similar ideas in the past: | ||
- [ts2gql](https://github.com/convoyinc/ts2gql) | ||
- [ts2graphql](https://github.com/cevek/ts2graphql) | ||
- [typegraphql-reflection-poc](https://github.com/MichalLytek/typegraphql-reflection-poc) |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
220967
51
4540
7