Comparing version 0.0.6 to 0.0.7
{ | ||
"name": "grats", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"main": "dist/src/index.js", | ||
@@ -5,0 +5,0 @@ "bin": "dist/src/cli.js", |
@@ -89,1 +89,3 @@ /** | ||
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.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.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; | ||
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"); | ||
@@ -349,1 +349,9 @@ // TODO: Move these to short URLS that are easier to keep from breaking. | ||
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; |
@@ -94,3 +94,6 @@ import { FieldDefinitionNode, InputValueDefinitionNode, NamedTypeNode, NameNode, TypeNode, StringValueNode, ConstValueNode, ConstDirectiveNode, EnumValueDefinitionNode, ConstObjectFieldNode, ConstObjectValueNode, ConstListValueNode } from "graphql"; | ||
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; | ||
@@ -97,0 +100,0 @@ maybeUnwrapPromise(node: ts.TypeNode): ts.TypeNode | null; |
@@ -305,5 +305,6 @@ "use strict"; | ||
} | ||
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; | ||
@@ -327,2 +328,5 @@ var argsParam = node.parameters[1]; | ||
]; | ||
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); | ||
@@ -1108,3 +1112,6 @@ if (deprecated != null) { | ||
} | ||
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 | ||
@@ -1130,2 +1137,5 @@ if (type == null) | ||
} | ||
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); | ||
@@ -1137,7 +1147,25 @@ if (deprecated != null) { | ||
}; | ||
Extractor.prototype.collectMethodType = function (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 }; | ||
}; | ||
@@ -1157,3 +1185,3 @@ Extractor.prototype.collectPropertyType = function (node) { | ||
if (identifier.text === "Promise") { | ||
if (node.typeArguments == null) { | ||
if (node.typeArguments == null || node.typeArguments.length === 0) { | ||
return this.report(node, E.promiseMissingTypeArg()); | ||
@@ -1345,3 +1373,3 @@ } | ||
// 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. | ||
@@ -1348,0 +1376,0 @@ // https://graphql.org/learn/best-practices/#nullability |
@@ -163,2 +163,6 @@ "use strict"; | ||
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 | ||
@@ -165,0 +169,0 @@ // (`String`, `Int`, etc). However, if we pass a second param (extending an |
@@ -8,3 +8,4 @@ import { DocumentNode, GraphQLSchema } from "graphql"; | ||
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,3 +50,3 @@ "use strict"; | ||
exports.__esModule = true; | ||
exports.applyServerDirectives = exports.DIRECTIVES_AST = 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; | ||
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"); | ||
@@ -62,3 +62,4 @@ var graphql_1 = require("graphql"); | ||
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.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")); | ||
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) { | ||
@@ -71,3 +72,3 @@ // TODO: Throw if the schema is missing our directives! | ||
_a[utils_1.MapperKind.OBJECT_FIELD] = function (fieldConfig) { | ||
var _a, _b; | ||
var _a, _b, _c; | ||
var newFieldConfig = fieldConfig; | ||
@@ -82,2 +83,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; | ||
@@ -84,0 +89,0 @@ }, |
@@ -1,2 +0,2 @@ | ||
import { DefinitionNode, DocumentNode, FieldDefinitionNode, Location, NameNode } from "graphql"; | ||
import { DefinitionNode, DocumentNode, FieldDefinitionNode, InterfaceTypeDefinitionNode, InterfaceTypeExtensionNode, Location, NameNode, ObjectTypeDefinitionNode, ObjectTypeExtensionNode } from "graphql"; | ||
import * as ts from "typescript"; | ||
@@ -52,2 +52,4 @@ import { DiagnosticResult, DiagnosticsResult } from "./utils/DiagnosticError"; | ||
resolveTypes(doc: DocumentNode): DiagnosticsResult<DocumentNode>; | ||
validateAsyncIterableFields(doc: DocumentNode): DiagnosticsResult<void>; | ||
validateField(t: ObjectTypeDefinitionNode | ObjectTypeExtensionNode | InterfaceTypeDefinitionNode | InterfaceTypeExtensionNode): ts.Diagnostic | void; | ||
handleAbstractDefinitions(docs: GratsDefinitionNode[]): DiagnosticsResult<DefinitionNode[]>; | ||
@@ -58,3 +60,2 @@ addAbstractFieldDefinition(doc: AbstractFieldDefinitionNode, interfaceGraph: InterfaceMap): DiagnosticsResult<DefinitionNode[]>; | ||
relatedInformation(loc: Location, message: string): ts.DiagnosticRelatedInformation; | ||
validateInterfaceImplementorsHaveTypenameField(): DiagnosticResult<null>; | ||
getDestFilePath(sourceFile: ts.SourceFile): { | ||
@@ -61,0 +62,0 @@ jsModulePath: string; |
@@ -122,4 +122,65 @@ "use strict"; | ||
}; | ||
// 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_1, _a; | ||
var e_2, _a; | ||
var newDocs = []; | ||
@@ -149,3 +210,3 @@ var errors = []; | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
finally { | ||
@@ -155,3 +216,3 @@ try { | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
finally { if (e_2) throw e_2.error; } | ||
} | ||
@@ -166,3 +227,3 @@ if (errors.length > 0) { | ||
TypeContext.prototype.addAbstractFieldDefinition = function (doc, interfaceGraph) { | ||
var e_2, _a; | ||
var e_3, _a; | ||
var _b; | ||
@@ -235,3 +296,3 @@ var newDocs = []; | ||
} | ||
catch (e_2_1) { e_2 = { error: e_2_1 }; } | ||
catch (e_3_1) { e_3 = { error: e_3_1 }; } | ||
finally { | ||
@@ -241,3 +302,3 @@ try { | ||
} | ||
finally { if (e_2) throw e_2.error; } | ||
finally { if (e_3) throw e_3.error; } | ||
} | ||
@@ -304,5 +365,2 @@ break; | ||
}; | ||
TypeContext.prototype.validateInterfaceImplementorsHaveTypenameField = function () { | ||
return (0, DiagnosticError_1.ok)(null); | ||
}; | ||
TypeContext.prototype.getDestFilePath = function (sourceFile) { | ||
@@ -309,0 +367,0 @@ return (0, gratsRoot_1.getRelativeOutputPath)(this._options, sourceFile); |
{ | ||
"name": "grats", | ||
"version": "0.0.6", | ||
"version": "0.0.7", | ||
"main": "dist/src/index.js", | ||
@@ -5,0 +5,0 @@ "bin": "dist/src/cli.js", |
220953
4540