Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

grats

Package Overview
Dependencies
Maintainers
1
Versions
238
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

grats - npm Package Compare versions

Comparing version 0.0.1 to 0.0.2

10

dist/src/Extractor.d.ts

@@ -31,3 +31,2 @@ import { DefinitionNode, FieldDefinitionNode, InputValueDefinitionNode, ListTypeNode, NamedTypeNode, Location as GraphQLLocation, NameNode, Token, TypeNode, NonNullTypeNode, StringValueNode, ConstValueNode, ConstDirectiveNode, ConstArgumentNode, EnumValueDefinitionNode } from "graphql";

extractUnion(node: ts.Node, tag: ts.JSDocTag): void;
extractExtendType(node: ts.Node, tag: ts.JSDocTag): void;
/** Error handling and location juggling */

@@ -54,2 +53,7 @@ report(node: ts.Node, message: string): null;

typeInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): null | undefined;
checkForTypenameProperty(node: ts.ClassDeclaration | ts.InterfaceDeclaration, expectedName: string): void;
isValidTypeNameProperty(member: ts.ClassElement | ts.TypeElement, expectedName: string): boolean;
isValidTypenamePropertyDeclaration(node: ts.PropertyDeclaration, expectedName: string): boolean;
isValidTypenamePropertySignature(node: ts.PropertySignature, expectedName: string): boolean;
isValidTypenamePropertyType(node: ts.TypeNode, expectedName: string): boolean;
collectInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<NamedTypeNode> | null;

@@ -65,4 +69,4 @@ interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): void;

collectEnumValues(node: ts.EnumDeclaration): ReadonlyArray<EnumValueDefinitionNode>;
entityName(node: ts.ClassDeclaration | ts.MethodDeclaration | ts.PropertyDeclaration | ts.InterfaceDeclaration | ts.PropertySignature | ts.EnumDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration, tag: ts.JSDocTag): NameNode | null;
methodDeclaration(node: ts.MethodDeclaration): FieldDefinitionNode | null;
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;
methodDeclaration(node: ts.MethodDeclaration | ts.MethodSignature): FieldDefinitionNode | null;
collectDescription(node: ts.Node): StringValueNode | null;

@@ -69,0 +73,0 @@ collectDeprecated(node: ts.Node): ConstDirectiveNode | null;

@@ -24,10 +24,18 @@ "use strict";

var ISSUE_URL = "https://github.com/captbaritone/grats/issues";
var TYPE_TAG = "GQLType";
var FIELD_TAG = "GQLField";
var SCALAR_TAG = "GQLScalar";
var INTERFACE_TAG = "GQLInterface";
var ENUM_TAG = "GQLEnum";
var UNION_TAG = "GQLUnion";
var INPUT_TAG = "GQLInput";
var EXTEND_TYPE = "GQLExtendType";
var TYPE_TAG = "gqlType";
var FIELD_TAG = "gqlField";
var SCALAR_TAG = "gqlScalar";
var INTERFACE_TAG = "gqlInterface";
var ENUM_TAG = "gqlEnum";
var UNION_TAG = "gqlUnion";
var INPUT_TAG = "gqlInput";
var ALL_TAGS = [
TYPE_TAG,
FIELD_TAG,
SCALAR_TAG,
INTERFACE_TAG,
ENUM_TAG,
UNION_TAG,
INPUT_TAG,
];
var DEPRECATED_TAG = "deprecated";

@@ -59,6 +67,6 @@ /**

ts.forEachChild(this.sourceFile, function (node) {
var e_1, _a;
var e_1, _a, e_2, _b;
try {
for (var _b = __values(ts.getJSDocTags(node)), _c = _b.next(); !_c.done; _c = _b.next()) {
var tag = _c.value;
for (var _c = __values(ts.getJSDocTags(node)), _d = _c.next(); !_d.done; _d = _c.next()) {
var tag = _d.value;
switch (tag.tagName.text) {

@@ -71,8 +79,2 @@ case TYPE_TAG:

break;
case FIELD_TAG:
// Right now this happens via deep traversal
// TODO: Handle GQLField as part of this top level traversal
// by keeping track of the current type we're in and appending fields
// as we go.
break;
case INTERFACE_TAG:

@@ -90,5 +92,37 @@ _this.extractInterface(node, tag);

break;
case EXTEND_TYPE:
_this.extractExtendType(node, tag);
case FIELD_TAG:
if (ts.isFunctionDeclaration(node)) {
_this.functionDeclarationExtendType(node, tag);
}
else if (!(ts.isMethodDeclaration(node) ||
ts.isPropertyDeclaration(node) ||
ts.isMethodSignature(node) ||
ts.isPropertySignature(node))) {
// Right now this happens via deep traversal
// Note: Keep this in sync with `collectFields`
_this.reportUnhandled(node, "`@".concat(FIELD_TAG, "` can only be used on method/property declarations or signatures."));
}
break;
default:
var lowerCaseTag = tag.tagName.text.toLowerCase();
if (lowerCaseTag.startsWith("gql")) {
try {
for (var ALL_TAGS_1 = (e_2 = void 0, __values(ALL_TAGS)), ALL_TAGS_1_1 = ALL_TAGS_1.next(); !ALL_TAGS_1_1.done; ALL_TAGS_1_1 = ALL_TAGS_1.next()) {
var t = ALL_TAGS_1_1.value;
if (t.toLowerCase() === lowerCaseTag) {
_this.report(tag.tagName, "Incorrect casing for Grats tag `@".concat(tag.tagName.text, "`. Use `@").concat(t, "` instead."));
break;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (ALL_TAGS_1_1 && !ALL_TAGS_1_1.done && (_b = ALL_TAGS_1["return"])) _b.call(ALL_TAGS_1);
}
finally { if (e_2) throw e_2.error; }
}
_this.report(tag.tagName, "`@".concat(tag.tagName.text, "` is not a valid Grats tag. Valid tags are: ").concat(ALL_TAGS.map(function (t) { return "`@".concat(t, "`"); }).join(", "), "."));
}
break;
}

@@ -100,3 +134,3 @@ }

try {
if (_c && !_c.done && (_a = _b["return"])) _a.call(_b);
if (_d && !_d.done && (_a = _c["return"])) _a.call(_c);
}

@@ -165,11 +199,2 @@ finally { if (e_1) throw e_1.error; }

};
Extractor.prototype.extractExtendType = function (node, tag) {
if (ts.isFunctionDeclaration(node)) {
// FIXME: Validate that the function is a named export
this.functionDeclarationExtendType(node, tag);
}
else {
this.report(tag, "`@".concat(EXTEND_TYPE, "` can only be used on function declarations."));
}
};
/** Error handling and location juggling */

@@ -217,3 +242,3 @@ Extractor.prototype.report = function (node, message) {

Extractor.prototype.unionTypeAliasDeclaration = function (node, tag) {
var e_2, _a;
var e_3, _a;
var name = this.entityName(node, tag);

@@ -238,3 +263,3 @@ if (name == null)

}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {

@@ -244,3 +269,3 @@ try {

}
finally { if (e_2) throw e_2.error; }
finally { if (e_3) throw e_3.error; }
}

@@ -262,3 +287,3 @@ this.ctx.recordTypeName(node.name, name.value);

if (typeParam == null) {
return this.report(funcName, "Expected type extension function to have a first argument representing the type to extend.");
return this.report(funcName, "Expected `@".concat(FIELD_TAG, "` function to have a first argument representing the type to extend."));
}

@@ -284,3 +309,3 @@ var typeName = this.typeReferenceFromParam(typeParam);

if (!ts.isSourceFile(node.parent)) {
return this.report(node, "Expected type extension function to be a top-level declaration.");
return this.report(node, "Expected `@".concat(FIELD_TAG, "` function to be a top-level declaration."));
}

@@ -316,6 +341,6 @@ // TODO: Does this work in the browser?

if (typeParam.type == null) {
return this.report(typeParam, "Expected first argument of a type extension function to have an explicit type annotation.");
return this.report(typeParam, "Expected first argument of a `@".concat(FIELD_TAG, "` function to have an explicit type annotation."));
}
if (!ts.isTypeReferenceNode(typeParam.type)) {
return this.report(typeParam.type, "Expected first argument of a type extension function to be typed as a `@GQLType` type.");
return this.report(typeParam.type, "Expected first argument of a `@".concat(FIELD_TAG, "` function to be typed as a `@gqlType` type."));
}

@@ -330,3 +355,3 @@ var nameNode = typeParam.type.typeName;

if (node.name == null) {
return this.report(node, "Expected type extension function to be a named function.");
return this.report(node, "Expected a `@".concat(FIELD_TAG, "` function to be a named function."));
}

@@ -341,6 +366,6 @@ var exportKeyword = (_a = node.modifiers) === null || _a === void 0 ? void 0 : _a.some(function (modifier) {

// TODO: We could support this
return this.report(defaultKeyword, "Expected type extension function to be a named export, not a default export.");
return this.report(defaultKeyword, "Expected a `@".concat(FIELD_TAG, "` function to be a named export, not a default export."));
}
if (exportKeyword == null) {
return this.report(node.name, "Expected type extension function to be a named export.");
return this.report(node.name, "Expected a `@".concat(FIELD_TAG, "` function to be a named export."));
}

@@ -378,3 +403,3 @@ return node.name;

Extractor.prototype.collectInputFields = function (node) {
var e_3, _a;
var e_4, _a;
var fields = [];

@@ -396,3 +421,3 @@ if (!ts.isTypeLiteralNode(node.type)) {

}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {

@@ -402,3 +427,3 @@ try {

}
finally { if (e_3) throw e_3.error; }
finally { if (e_4) throw e_4.error; }
}

@@ -440,2 +465,3 @@ return fields.length === 0 ? null : fields;

this.ctx.recordTypeName(node.name, name.value);
this.checkForTypenameProperty(node, name.value);
this.definitions.push({

@@ -459,2 +485,3 @@ kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,

this.ctx.recordTypeName(node.name, name.value);
this.checkForTypenameProperty(node, name.value);
this.definitions.push({

@@ -470,2 +497,67 @@ kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,

};
Extractor.prototype.checkForTypenameProperty = function (node, expectedName) {
var _this = this;
var hasTypename = node.members.some(function (member) {
return _this.isValidTypeNameProperty(member, expectedName);
});
if (hasTypename) {
this.ctx.recordHasTypenameField(expectedName);
}
};
Extractor.prototype.isValidTypeNameProperty = function (member, expectedName) {
if (member.name == null ||
!ts.isIdentifier(member.name) ||
member.name.text !== "__typename") {
return false;
}
if (ts.isPropertyDeclaration(member)) {
return this.isValidTypenamePropertyDeclaration(member, expectedName);
}
if (ts.isPropertySignature(member)) {
return this.isValidTypenamePropertySignature(member, expectedName);
}
this.report(member.name,
// TODO: Could show what kind we found, but TS AST does not have node names.
"Expected `__typename` to be a property declaration.");
return false;
};
Extractor.prototype.isValidTypenamePropertyDeclaration = function (node, expectedName) {
// If we have a type annotation, we ask that it be a string literal.
// That means, that if we have one, _and_ it's valid, we're done.
// Otherwise we fall through to the initializer check.
if (node.type != null) {
return this.isValidTypenamePropertyType(node.type, expectedName);
}
if (node.initializer == null) {
this.report(node.name, "Expected `__typename` property to have an initializer or a string literal type. For example: `__typename = \"MyType\"` or `__typename: \"MyType\";`.");
return false;
}
if (!ts.isStringLiteral(node.initializer)) {
this.report(node.initializer, "Expected `__typename` property initializer to be a string literal. For example: `__typename = \"MyType\"` or `__typename: \"MyType\";`.");
return false;
}
if (node.initializer.text !== expectedName) {
this.report(node.initializer, "Expected `__typename` property initializer to be `\"".concat(expectedName, "\"`, found `\"").concat(node.initializer.text, "\"`."));
return false;
}
return true;
};
Extractor.prototype.isValidTypenamePropertySignature = function (node, expectedName) {
if (node.type == null) {
this.report(node, "Expected `__typename` property signature to specify the typename as a string literal string type. For example `__typename: \"".concat(expectedName, "\";`"));
return false;
}
return this.isValidTypenamePropertyType(node.type, expectedName);
};
Extractor.prototype.isValidTypenamePropertyType = function (node, expectedName) {
if (!ts.isLiteralTypeNode(node) || !ts.isStringLiteral(node.literal)) {
this.report(node, "Expected `__typename` property signature to specify the typename as a string literal string type. For example `__typename: \"".concat(expectedName, "\";`"));
return false;
}
if (node.literal.text !== expectedName) {
this.report(node, "Expected `__typename` property to be `\"".concat(expectedName, "\"`"));
return false;
}
return true;
};
Extractor.prototype.collectInterfaces = function (node) {

@@ -518,3 +610,3 @@ var _this = this;

ts.forEachChild(node, function (node) {
if (ts.isMethodDeclaration(node)) {
if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) {
var field = _this.methodDeclaration(node);

@@ -536,3 +628,3 @@ if (field) {

Extractor.prototype.collectArgs = function (argsParam) {
var e_4, _a;
var e_5, _a;
var args = [];

@@ -562,3 +654,3 @@ var argsType = argsParam.type;

}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {

@@ -568,3 +660,3 @@ try {

}
finally { if (e_4) throw e_4.error; }
finally { if (e_5) throw e_5.error; }
}

@@ -574,3 +666,3 @@ return args;

Extractor.prototype.collectArgDefaults = function (node) {
var e_5, _a;
var e_6, _a;
var defaults = new Map();

@@ -587,3 +679,3 @@ try {

}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {

@@ -593,3 +685,3 @@ try {

}
finally { if (e_5) throw e_5.error; }
finally { if (e_6) throw e_6.error; }
}

@@ -663,3 +755,3 @@ return defaults;

Extractor.prototype.enumTypeAliasDeclaration = function (node, tag) {
var e_6, _a;
var e_7, _a;
var name = this.entityName(node, tag);

@@ -693,3 +785,3 @@ if (name == null || name.value == null) {

}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
catch (e_7_1) { e_7 = { error: e_7_1 }; }
finally {

@@ -699,3 +791,3 @@ try {

}
finally { if (e_6) throw e_6.error; }
finally { if (e_7) throw e_7.error; }
}

@@ -712,3 +804,3 @@ this.ctx.recordTypeName(node.name, name.value);

Extractor.prototype.collectEnumValues = function (node) {
var e_7, _a;
var e_8, _a;
var values = [];

@@ -734,3 +826,3 @@ try {

}
catch (e_7_1) { e_7 = { error: e_7_1 }; }
catch (e_8_1) { e_8 = { error: e_8_1 }; }
finally {

@@ -740,3 +832,3 @@ try {

}
finally { if (e_7) throw e_7.error; }
finally { if (e_8) throw e_8.error; }
}

@@ -743,0 +835,0 @@ return values;

@@ -1,3 +0,3 @@

import { DocumentNode, GraphQLSchema } from "graphql";
import { DiagnosticsResult, Result, ReportableDiagnostics } from "./utils/DiagnosticError";
import { GraphQLSchema } from "graphql";
import { Result, ReportableDiagnostics } from "./utils/DiagnosticError";
import * as ts from "typescript";

@@ -15,2 +15,1 @@ export { applyServerDirectives } from "./serverDirectives";

export declare function buildSchemaResultWithHost(options: GratsOptions, compilerHost: ts.CompilerHost): Result<GraphQLSchema, ReportableDiagnostics>;
export declare function buildSchemaAst(options: GratsOptions, host: ts.CompilerHost): DiagnosticsResult<DocumentNode>;

@@ -25,3 +25,3 @@ "use strict";

exports.__esModule = true;
exports.buildSchemaAst = exports.buildSchemaResultWithHost = exports.buildSchemaResult = exports.applyServerDirectives = void 0;
exports.buildSchemaResultWithHost = exports.buildSchemaResult = exports.applyServerDirectives = void 0;
var graphql_1 = require("graphql");

@@ -47,36 +47,10 @@ var DiagnosticError_1 = require("./utils/DiagnosticError");

function buildSchemaResultWithHost(options, compilerHost) {
var docResult = buildSchemaAst(options, compilerHost);
if (docResult.kind === "ERROR") {
return (0, DiagnosticError_1.err)(new DiagnosticError_1.ReportableDiagnostics(compilerHost, docResult.err));
var schemaResult = extractSchema(options, compilerHost);
if (schemaResult.kind === "ERROR") {
return (0, DiagnosticError_1.err)(new DiagnosticError_1.ReportableDiagnostics(compilerHost, schemaResult.err));
}
var schema = (0, graphql_1.buildASTSchema)(docResult.value, { assumeValidSDL: true });
var diagnostics = (0, graphql_1.validateSchema)(schema)
// FIXME: Handle case where query is not defined (no location)
.filter(function (e) { return e.source && e.locations && e.positions; })
.map(function (e) { return (0, DiagnosticError_1.graphQlErrorToDiagnostic)(e); });
if (diagnostics.length > 0) {
return (0, DiagnosticError_1.err)(new DiagnosticError_1.ReportableDiagnostics(compilerHost, diagnostics));
}
return (0, DiagnosticError_1.ok)((0, serverDirectives_1.applyServerDirectives)(schema));
return (0, DiagnosticError_1.ok)((0, serverDirectives_1.applyServerDirectives)(schemaResult.value));
}
exports.buildSchemaResultWithHost = buildSchemaResultWithHost;
function buildSchemaAst(options, host) {
var docResult = definitionsFromFile(options, host);
if (docResult.kind === "ERROR")
return docResult;
var doc = docResult.value;
// TODO: Currently this does not detect definitions that shadow builtins
// (`String`, `Int`, etc). However, if we pass a second param (extending an
// existing schema) we do! So, we should find a way to validate that we don't
// shadow builtins.
var validationErrors = (0, validate_1.validateSDL)(doc).map(function (e) {
return (0, DiagnosticError_1.graphQlErrorToDiagnostic)(e);
});
if (validationErrors.length > 0) {
return (0, DiagnosticError_1.err)(validationErrors);
}
return (0, DiagnosticError_1.ok)(doc);
}
exports.buildSchemaAst = buildSchemaAst;
function definitionsFromFile(options, host) {
function extractSchema(options, host) {
var e_1, _a, e_2, _b;

@@ -91,3 +65,3 @@ var program = ts.createProgram(options.files, options.tsCompilerOptions, host);

// If the file doesn't contain any GraphQL definitions, skip it.
if (!/@GQL/.test(sourceFile.text)) {
if (!/@gql/i.test(sourceFile.text)) {
continue;

@@ -121,3 +95,68 @@ }

}
return ctx.resolveTypes({ kind: graphql_1.Kind.DOCUMENT, definitions: definitions });
var docResult = ctx.resolveTypes({ kind: graphql_1.Kind.DOCUMENT, definitions: definitions });
if (docResult.kind === "ERROR")
return docResult;
var doc = docResult.value;
// TODO: Currently this does not detect definitions that shadow builtins
// (`String`, `Int`, etc). However, if we pass a second param (extending an
// existing schema) we do! So, we should find a way to validate that we don't
// shadow builtins.
var validationErrors = (0, validate_1.validateSDL)(doc).map(function (e) {
return (0, DiagnosticError_1.graphQlErrorToDiagnostic)(e);
});
if (validationErrors.length > 0) {
return (0, DiagnosticError_1.err)(validationErrors);
}
var schema = (0, graphql_1.buildASTSchema)(doc, { assumeValidSDL: true });
var diagnostics = (0, graphql_1.validateSchema)(schema)
// FIXME: Handle case where query is not defined (no location)
.filter(function (e) { return e.source && e.locations && e.positions; })
.map(function (e) { return (0, DiagnosticError_1.graphQlErrorToDiagnostic)(e); });
if (diagnostics.length > 0) {
return (0, DiagnosticError_1.err)(diagnostics);
}
var typenameDiagnostics = validateTypename(schema, ctx);
if (typenameDiagnostics.length > 0)
return (0, DiagnosticError_1.err)(typenameDiagnostics);
return (0, DiagnosticError_1.ok)(schema);
}
function validateTypename(schema, ctx) {
var e_3, _a, e_4, _b;
var _c, _d;
var typenameDiagnostics = [];
var abstractTypes = Object.values(schema.getTypeMap()).filter(graphql_1.isAbstractType);
try {
for (var abstractTypes_1 = __values(abstractTypes), abstractTypes_1_1 = abstractTypes_1.next(); !abstractTypes_1_1.done; abstractTypes_1_1 = abstractTypes_1.next()) {
var type = abstractTypes_1_1.value;
// TODO: If we already implement resolveType, we don't need to check implementors
var typeImplementors = schema.getPossibleTypes(type).filter(graphql_1.isType);
try {
for (var typeImplementors_1 = (e_4 = void 0, __values(typeImplementors)), typeImplementors_1_1 = typeImplementors_1.next(); !typeImplementors_1_1.done; typeImplementors_1_1 = typeImplementors_1.next()) {
var implementor = typeImplementors_1_1.value;
if (!ctx.hasTypename.has(implementor.name)) {
var loc = (_d = (_c = implementor.astNode) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.loc;
if (loc == null) {
throw new Error("Grats expected the parsed type `".concat(implementor.name, "` to have location information. This is a bug in Grats. Please report it."));
}
typenameDiagnostics.push((0, DiagnosticError_1.diagnosticAtGraphQLLocation)("Missing __typename on `".concat(implementor.name, "`. The type `").concat(type.name, "` is used in a union or interface, so it must have a `__typename` field."), loc));
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (typeImplementors_1_1 && !typeImplementors_1_1.done && (_b = typeImplementors_1["return"])) _b.call(typeImplementors_1);
}
finally { if (e_4) throw e_4.error; }
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (abstractTypes_1_1 && !abstractTypes_1_1.done && (_a = abstractTypes_1["return"])) _a.call(abstractTypes_1);
}
finally { if (e_3) throw e_3.error; }
}
return typenameDiagnostics;
}

@@ -1,2 +0,2 @@

import { GraphQLSchema } from "graphql";
import { DocumentNode, GraphQLSchema } from "graphql";
export declare const METHOD_NAME_DIRECTIVE = "methodName";

@@ -7,3 +7,3 @@ export declare const METHOD_NAME_ARG = "name";

export declare const EXPORTED_FUNCTION_NAME_ARG = "functionName";
export declare const DIRECTIVES_AST: import("graphql").DocumentNode;
export declare const DIRECTIVES_AST: DocumentNode;
export declare function applyServerDirectives(schema: GraphQLSchema): GraphQLSchema;

@@ -22,7 +22,10 @@ import { DocumentNode, NameNode } from "graphql";

_unresolvedTypes: Map<NameNode, ts.Symbol>;
hasTypename: Set<string>;
constructor(checker: ts.TypeChecker, host: ts.CompilerHost);
recordTypeName(node: ts.Node, name: string): void;
recordHasTypenameField(name: string): void;
markUnresolvedType(node: ts.Node, name: NameNode): void;
resolveTypes(doc: DocumentNode): DiagnosticsResult<DocumentNode>;
resolveNamedType(unresolved: NameNode): DiagnosticResult<NameNode>;
validateInterfaceImplementorsHaveTypenameField(): DiagnosticResult<null>;
}

@@ -35,2 +35,3 @@ "use strict";

this._unresolvedTypes = new Map();
this.hasTypename = new Set();
this.checker = checker;

@@ -51,2 +52,5 @@ this.host = host;

};
TypeContext.prototype.recordHasTypenameField = function (name) {
this.hasTypename.add(name);
};
TypeContext.prototype.markUnresolvedType = function (node, name) {

@@ -97,3 +101,3 @@ var symbol = this.checker.getSymbolAtLocation(node);

return (0, DiagnosticError_1.err)({
messageText: "This type is not a valid GraphQL type. Did you mean to annotate it's definition with `/** @GQLType */` or `/** @GQLScalar */`?",
messageText: "This type is not a valid GraphQL type. Did you mean to annotate it's definition with `/** @gqlType */` or `/** @gqlScalar */`?",
start: unresolved.loc.start,

@@ -108,4 +112,7 @@ length: unresolved.loc.end - unresolved.loc.start,

};
TypeContext.prototype.validateInterfaceImplementorsHaveTypenameField = function () {
return (0, DiagnosticError_1.ok)(null);
};
return TypeContext;
}());
exports.TypeContext = TypeContext;

@@ -1,4 +0,4 @@

/** @GQLScalar */
/** @gqlScalar */
export type Float = number;
/** @GQLScalar */
/** @gqlScalar */
export type Int = number;

@@ -1,2 +0,2 @@

import { GraphQLError } from "graphql";
import { GraphQLError, Location, Source } from "graphql";
import * as ts from "typescript";

@@ -25,2 +25,4 @@ type Ok<T> = {

export declare function graphQlErrorToDiagnostic(error: GraphQLError): ts.Diagnostic;
export declare function diagnosticAtGraphQLLocation(message: string, loc: Location): ts.Diagnostic;
export declare function graphqlSourceToSourceFile(source: Source): ts.SourceFile;
export {};
"use strict";
exports.__esModule = true;
exports.graphQlErrorToDiagnostic = exports.FAKE_ERROR_CODE = exports.ReportableDiagnostics = exports.err = exports.ok = void 0;
exports.graphqlSourceToSourceFile = exports.diagnosticAtGraphQLLocation = exports.graphQlErrorToDiagnostic = exports.FAKE_ERROR_CODE = exports.ReportableDiagnostics = exports.err = exports.ok = void 0;
var ts = require("typescript");

@@ -23,3 +23,3 @@ function ok(value) {

// lets us leverage all of TypeScript's error reporting logic.
return formatted.replace(" TS".concat(exports.FAKE_ERROR_CODE, ": "), ": ");
return formatted.replace(new RegExp(" TS".concat(exports.FAKE_ERROR_CODE, ": "), "g"), ": ");
};

@@ -49,3 +49,3 @@ ReportableDiagnostics.prototype.formatDiagnosticsWithContext = function () {

if (error.source != null) {
sourceFile = ts.createSourceFile(error.source.name, error.source.body, ts.ScriptTarget.Latest);
sourceFile = graphqlSourceToSourceFile(error.source);
}

@@ -63,1 +63,16 @@ return {

exports.graphQlErrorToDiagnostic = graphQlErrorToDiagnostic;
function diagnosticAtGraphQLLocation(message, loc) {
return {
messageText: message,
file: graphqlSourceToSourceFile(loc.source),
code: exports.FAKE_ERROR_CODE,
category: ts.DiagnosticCategory.Error,
start: loc.start,
length: loc.end - loc.start
};
}
exports.diagnosticAtGraphQLLocation = diagnosticAtGraphQLLocation;
function graphqlSourceToSourceFile(source) {
return ts.createSourceFile(source.name, source.body, ts.ScriptTarget.Latest);
}
exports.graphqlSourceToSourceFile = graphqlSourceToSourceFile;
{
"name": "grats",
"version": "0.0.1",
"version": "0.0.2",
"main": "dist/src/index.js",

@@ -5,0 +5,0 @@ "bin": "dist/src/cli.js",

@@ -7,2 +7,4 @@ # -=[ EXPERIMENTAL PRE ALPHA ]=-

[![Join our Discord!](https://img.shields.io/discord/1089650710796320868?logo=discord)](https://capt.dev/grats-chat)
Grats is a tool for statically infering GraphQL schema from your vanilla

@@ -18,3 +20,4 @@ TypeScript code.

By making your TypeScript implementation the source of truth, you entirely
remove the question of mismatches between your implementation and your GraphQL schema definition. Your implementation _is_ the schema definition!
remove the question of mismatches between your implementation and your GraphQL
schema definition. Your implementation _is_ the schema definition!

@@ -24,5 +27,5 @@ ## Example

```ts
/** @GQLType */
/** @gqlType */
export default class Query {
/** @GQLField */
/** @gqlField */
me(): UserResolver {

@@ -32,3 +35,3 @@ return new UserResolver();

/**
* @GQLField
* @gqlField
* @deprecated Please use `me` instead.

@@ -43,9 +46,9 @@ */

* A user in our kick-ass system!
* @GQLType User
* @gqlType User
*/
class UserResolver {
/** @GQLField */
/** @gqlField */
name: string = 'Alice';
/** @GQLField */
/** @gqlField */
greeting(args: { salutation: string }): string {

@@ -72,3 +75,3 @@ return `${args.salutation}, ${this.name}`;

**Give it a try in the [online playground](https://capt.dev/grats-example)!**
**Give it a try in the [online playground](https://capt.dev/grats-sandbox)!**

@@ -93,5 +96,5 @@ ## Quick Start

/** @GQLType */
/** @gqlType */
class Query {
/** @GQLField */
/** @gqlField */
hello(): string {

@@ -118,2 +121,4 @@ return "Hello world!";

Try it out on [CodeSandbox](https://capt.dev/grats-sandbox)!
## Configuration

@@ -124,3 +129,3 @@

```json
```json5
{

@@ -143,3 +148,3 @@ "grats": {

TypeScript structures should be included in the schema by marking them with
special JSDoc tags such as `/** @GQLType */` or `/** @GQLField */`.
special JSDoc tags such as `/** @gqlType */` or `/** @gqlField */`.

@@ -153,16 +158,14 @@ Any comment text preceding the JSDoc `@` tag will be used as that element's description.

* [`@GQLType`](#GQLtype)
* [`@GQLInterface`](#GQLinterface)
* [`@GQLField`](#GQLfield)
* [`@GQLUnion`](#GQLunion)
* [`@GQLScalar`](#GQLscalar)
* [`@GQLEnum`](#GQLenum)
* [`@GQLInput`](#GQLinput)
* [`@GQLExtendType`](#GQLExtendType)
* [`@gqlType`](#GQLtype)
* [`@gqlInterface`](#GQLinterface)
* [`@gqlField`](#GQLfield)
* [`@gqlUnion`](#GQLunion)
* [`@gqlScalar`](#GQLscalar)
* [`@gqlEnum`](#GQLenum)
* [`@gqlInput`](#GQLinput)
### @gqlType
### @GQLType
GraphQL types can be defined by placing a `@gqlType` docblock directly before a:
GraphQL types can be defined by placing a `@GQLType` docblock directly before a:
* Class declaration

@@ -174,6 +177,6 @@ * Interface declaration

* Here I can write a description of my type that will be included in the schema.
* @GQLType <optional name of the type, if different from class name>
* @gqlType <optional name of the type, if different from class name>
*/
class MyClass {
/** @GQLField */
/** @gqlField */
someField: string;

@@ -186,6 +189,6 @@ }

* Here I can write a description of my type that will be included in the schema.
* @GQLType <optional name of the type, if different from interface name>
* @gqlType <optional name of the type, if different from interface name>
*/
interface MyInterface {
/** @GQLField */
/** @gqlField */
someField: string;

@@ -195,6 +198,10 @@ }

### @GQLInterface
Note: If your type implements a GraphQL interface or is a member of a GraphQL
union, Grats will remind you to add a `__typename: "MyType"` property to your
class or interface.
GraphQL interfaces can be defined by placing a `@GQLInterface` docblock directly before an:
### @gqlInterface
GraphQL interfaces can be defined by placing a `@gqlInterface` docblock directly before an:
* Interface declaration

@@ -205,6 +212,6 @@

* A description of my interface.
* @GQLInterface <optional name of the type, if different from class name>
* @gqlInterface <optional name of the type, if different from class name>
*/
interface MyClass {
/** @GQLField */
/** @gqlField */
someField: string;

@@ -214,12 +221,14 @@ }

All `@GQLType` types which implement the interface in TypeScript will
All `@gqlType` types which implement the interface in TypeScript will
automatically implement it in GraphQL as well.
### @GQLField
### @gqlField
Within a `@GQLType` class, you can define GraphQL fields by placing a `@GQLField` directly before a:
You can define GraphQL fields by placing a `@gqlField` directly before a:
* Method declaration
* Method signature
* Property declaration
* Property signature
* Function declaration (with named export)

@@ -229,3 +238,3 @@ ```ts

* A description of some field.
* @GQLField <optional name of the field, if different from property name>
* @gqlField <optional name of the field, if different from property name>
*/

@@ -236,3 +245,3 @@ someField: string;

* A description of my field.
* @GQLField <optional name of the field, if different from method name>
* @gqlField <optional name of the field, if different from method name>
*/

@@ -245,3 +254,5 @@ myField(): string {

**Note**: By default, Grats makes all fields nullable in keeping with [GraphQL
*best practices](https://graphql.org/learn/best-practices/#nullability). This behavior can be changed by setting config option `nullableByDefault` to `false`.
*best practices](https://graphql.org/learn/best-practices/#nullability). This
*behavior can be changed by setting the config option `nullableByDefault` to
`false`.

@@ -251,3 +262,3 @@ If you wish to define arguments for a field, define your argument types inline:

```ts
/** @GQLField */
/** @gqlField */
myField(args: { greeting: string }): string {

@@ -261,3 +272,3 @@ return `${args.greeting} World`;

```ts
/** @GQLField */
/** @gqlField */
myField({ greeting = "Hello" }: { greeting: string }): string {

@@ -271,3 +282,3 @@ return `${greeting} World`;

```ts
/** @GQLField */
/** @gqlField */
myField(args: {

@@ -285,3 +296,3 @@ /** A description of the greeting argument */

/**
* @GQLField
* @gqlField
* @deprecated Please use myNewField instead.

@@ -294,6 +305,42 @@ */

### @GQLUnion
Sometimes you want to add a computed field to a non-class type, or extend base
types like `Query` or `Mutation` from another file. Both of these usecases are
enabled by placing a `@gqlField` before an exported function declaration.
GraphQL unions can be defined by placing a `@GQLUnion` docblock directly before a:
In this case, the function should expect an instance of the base type as the
first argument, and an object representing the GraphQL field arguments as the
second argument. The function should return the value of the field.
Extending Query:
```ts
/**
* Description of my field
* @gqlField <optional name of the field, if different from function name>
*/
export function userById(_: Query, args: {id: string}): User {
return DB.getUserById(args.id);
}
```
Extending Mutation:
```ts
/**
* Delete a user. GOODBYE!
* @gqlField <optional name of the mutation, if different from function name>
*/
export function deleteUser(_: Mutation, args: {id: string}): boolean {
return DB.deleteUser(args.id);
}
```
Note that Grats will use the type of the first argument to determine which type
is being extended. So, as seen in the previous examples, even if you don't need
access to the instance you should still define a typed first argument.
### @gqlUnion
GraphQL unions can be defined by placing a `@gqlUnion` docblock directly before a:
* Type alias of a union of object types

@@ -304,3 +351,3 @@

* A description of my union.
* @GQLUnion <optional name of the union, if different from type name>
* @gqlUnion <optional name of the union, if different from type name>
*/

@@ -310,5 +357,5 @@ type MyUnion = User | Post;

### @GQLScalar
### @gqlScalar
GraphQL custom sclars can be defined by placing a `@GQLScalar` docblock directly before a:
GraphQL custom sclars can be defined by placing a `@gqlScalar` docblock directly before a:

@@ -320,3 +367,3 @@ * Type alias declaration

* A description of my custom scalar.
* @GQLScalar <optional name of the scalar, if different from type name>
* @gqlScalar <optional name of the scalar, if different from type name>
*/

@@ -331,5 +378,5 @@ type MyCustomString = string;

/** @GQLType */
/** @gqlType */
class Query {
/** @GQLField */
/** @gqlField */
round(args: {float: Float}): Int {

@@ -341,5 +388,5 @@ return Math.round(args.float);

### @GQLEnum
### @gqlEnum
GraphQL enums can be defined by placing a `@GQLEnum` docblock directly before a:
GraphQL enums can be defined by placing a `@gqlEnum` docblock directly before a:

@@ -352,7 +399,7 @@ * TypeScript enum declaration

* A description of my enum.
* @GQLEnum <optional name of the enum, if different from type name>
* @gqlEnum <optional name of the enum, if different from type name>
*/
enum MyEnum {
/** A description of my variant */
OK = "OK"
OK = "OK",
/** A description of my other variant */

@@ -369,3 +416,3 @@ ERROR = "ERROR"

```ts
/** @GQLEnum */
/** @gqlEnum */
enum MyEnum {

@@ -391,3 +438,3 @@ OK = "OK"

* A description of my enum.
* @GQLEnum <optional name of the enum, if different from type name>
* @gqlEnum <optional name of the enum, if different from type name>
*/

@@ -397,5 +444,5 @@ type MyEnum = "OK" | "ERROR";

### @GQLInput
### @gqlInput
GraphQL input types can be defined by placing a `@GQLInput` docblock directly before a:
GraphQL input types can be defined by placing a `@gqlInput` docblock directly before a:

@@ -407,3 +454,3 @@ * Type alias declaration

* Description of my input type
* @GQLInput <optional name of the input, if different from type name>
* @gqlInput <optional name of the input, if different from type name>
*/

@@ -416,45 +463,2 @@ type MyInput = {

### @GQLExtendType
Sometimes you want to add a computed field to a non-class type, or extend base
type like `Query` or `Mutation` from another file. Both of these usecases are
enabled by placing a `@GQLExtendType` before a:
* Exported function declaration
In this case, the function should expect an instance of the base type as the
first argument, and an object representing the GraphQL field arguments as the
second argument. The function should return the value of the field.
Extending Query:
```ts
/**
* Description of my field
* @GQLExtendType <optional name of the field, if different from function name>
*/
export function userById(_: Query, args: {id: string}): User {
return DB.getUserById(args.id);
}
```
Extending Mutation:
```ts
/**
* Delete a user. GOODBYE!
* @GQLExtendType <optional name of the mutation, if different from function name>
*/
export function deleteUser(_: Mutation, args: {id: string}): boolean {
return DB.deleteUser(args.id);
}
```
Note that Grats will use the type of the first argument to determine which type
is being extended. So, as seen in the previous examples, even if you don't need
access to the instance you should still define a typed first argument.
You can think of `@GQLExtendType` as equivalent to the `extend type` syntax in
GraphQL's schema definition language.
## Example

@@ -511,5 +515,5 @@

* @mofeiZ and @alunyov for their Relay hack-week project exploring a similar idea.
* @josephsavona for input on the design of [Relay Resolvers](https://relay.dev/docs/guides/relay-resolvers/) which inspired this project.
* @bradzacher for tips on how to handle TypeScript ASTs.
* [@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.

@@ -516,0 +520,0 @@ * A number of other projects which seem to have explored similar ideas in the past:

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc