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

grats

Package Overview
Dependencies
Maintainers
1
Versions
240
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.0-main-7408c383 to 0.0.0-main-76d60d78

dist/src/Errors.d.ts

3

dist/package.json

@@ -36,3 +36,4 @@ {

"trailingComma": "all"
}
},
"packageManager": "pnpm@8.1.1"
}

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

import { ConfigOptions } from "./lib";
export declare const LIBRARY_IMPORT_NAME = "grats";
export declare const LIBRARY_NAME = "Grats";
export declare const ISSUE_URL = "https://github.com/captbaritone/grats/issues";
export declare const TYPE_TAG = "gqlType";
export declare const FIELD_TAG = "gqlField";
export declare const SCALAR_TAG = "gqlScalar";
export declare const INTERFACE_TAG = "gqlInterface";
export declare const ENUM_TAG = "gqlEnum";
export declare const UNION_TAG = "gqlUnion";
export declare const INPUT_TAG = "gqlInput";
export declare const KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException";
export declare const ALL_TAGS: string[];
type ArgDefaults = Map<string, ts.Expression>;

@@ -33,4 +45,5 @@ /**

/** Error handling and location juggling */
report(node: ts.Node, message: string): null;
reportUnhandled(node: ts.Node, message: string): null;
report(node: ts.Node, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): null;
reportUnhandled(node: ts.Node, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): null;
related(node: ts.Node, message: string): ts.DiagnosticRelatedInformation;
diagnosticAnnotatedLocation(node: ts.Node): {

@@ -37,0 +50,0 @@ start: number;

@@ -13,4 +13,20 @@ "use strict";

};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
exports.__esModule = true;
exports.Extractor = void 0;
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;
var graphql_1 = require("graphql");

@@ -21,21 +37,23 @@ var DiagnosticError_1 = require("./utils/DiagnosticError");

var serverDirectives_1 = require("./serverDirectives");
var LIBRARY_IMPORT_NAME = "grats";
var LIBRARY_NAME = "Grats";
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 KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException";
var ALL_TAGS = [
TYPE_TAG,
FIELD_TAG,
SCALAR_TAG,
INTERFACE_TAG,
ENUM_TAG,
UNION_TAG,
INPUT_TAG,
var E = require("./Errors");
var JSDoc_1 = require("./utils/JSDoc");
exports.LIBRARY_IMPORT_NAME = "grats";
exports.LIBRARY_NAME = "Grats";
exports.ISSUE_URL = "https://github.com/captbaritone/grats/issues";
exports.TYPE_TAG = "gqlType";
exports.FIELD_TAG = "gqlField";
exports.SCALAR_TAG = "gqlScalar";
exports.INTERFACE_TAG = "gqlInterface";
exports.ENUM_TAG = "gqlEnum";
exports.UNION_TAG = "gqlUnion";
exports.INPUT_TAG = "gqlInput";
exports.KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException";
exports.ALL_TAGS = [
exports.TYPE_TAG,
exports.FIELD_TAG,
exports.SCALAR_TAG,
exports.INTERFACE_TAG,
exports.ENUM_TAG,
exports.UNION_TAG,
exports.INPUT_TAG,
];

@@ -67,79 +85,70 @@ var DEPRECATED_TAG = "deprecated";

var _this = this;
ts.forEachChild(this.sourceFile, function (node) {
var e_1, _a, e_2, _b;
try {
for (var _c = __values(ts.getJSDocTags(node)), _d = _c.next(); !_d.done; _d = _c.next()) {
var tag = _d.value;
switch (tag.tagName.text) {
case TYPE_TAG:
_this.extractType(node, tag);
break;
case SCALAR_TAG:
_this.extractScalar(node, tag);
break;
case INTERFACE_TAG:
_this.extractInterface(node, tag);
break;
case ENUM_TAG:
_this.extractEnum(node, tag);
break;
case INPUT_TAG:
_this.extractInput(node, tag);
break;
case UNION_TAG:
_this.extractUnion(node, tag);
break;
case FIELD_TAG:
if (ts.isFunctionDeclaration(node)) {
_this.functionDeclarationExtendType(node, tag);
(0, JSDoc_1.traverseJSDocTags)(this.sourceFile, function (node, tag) {
var e_1, _a;
switch (tag.tagName.text) {
case exports.TYPE_TAG:
_this.extractType(node, tag);
break;
case exports.SCALAR_TAG:
_this.extractScalar(node, tag);
break;
case exports.INTERFACE_TAG:
_this.extractInterface(node, tag);
break;
case exports.ENUM_TAG:
_this.extractEnum(node, tag);
break;
case exports.INPUT_TAG:
_this.extractInput(node, tag);
break;
case exports.UNION_TAG:
_this.extractUnion(node, tag);
break;
case exports.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, E.fieldTagOnWrongNode());
}
break;
case exports.KILLS_PARENT_ON_EXCEPTION_TAG: {
var hasFieldTag = ts.getJSDocTags(node).some(function (t) {
return t.tagName.text === exports.FIELD_TAG;
});
if (!hasFieldTag) {
_this.report(tag.tagName, E.killsParentOnExceptionOnWrongNode());
}
break;
}
default:
{
var lowerCaseTag = tag.tagName.text.toLowerCase();
if (lowerCaseTag.startsWith("gql")) {
try {
for (var ALL_TAGS_1 = __values(exports.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, E.wrongCasingForGratsTag(tag.tagName.text, t));
break;
}
}
}
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;
case KILLS_PARENT_ON_EXCEPTION_TAG:
var hasFieldTag = ts.getJSDocTags(node).some(function (t) {
return t.tagName.text === FIELD_TAG;
});
if (!hasFieldTag) {
_this.report(tag.tagName, "Unexpected `@".concat(KILLS_PARENT_ON_EXCEPTION_TAG, "`. `@").concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` can only be used in field annotation docblocks. Perhaps you are missing a `@").concat(FIELD_TAG, "` tag?"));
}
break;
default:
var lowerCaseTag = tag.tagName.text.toLowerCase();
if (lowerCaseTag.startsWith("gql")) {
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
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;
}
}
if (ALL_TAGS_1_1 && !ALL_TAGS_1_1.done && (_a = ALL_TAGS_1["return"])) _a.call(ALL_TAGS_1);
}
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(", "), "."));
finally { if (e_1) throw e_1.error; }
}
break;
_this.report(tag.tagName, E.invalidGratsTag(tag.tagName.text));
}
}
}
break;
}
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; }
}
});

@@ -162,3 +171,3 @@ if (this.errors.length > 0) {

else {
this.report(tag, "`@".concat(TYPE_TAG, "` can only be used on class or interface declarations."));
this.report(tag, E.invalidTypeTagUsage());
}

@@ -171,3 +180,3 @@ };

else {
this.report(tag, "`@".concat(SCALAR_TAG, "` can only be used on type alias declarations."));
this.report(tag, E.invalidScalarTagUsage());
}

@@ -180,3 +189,3 @@ };

else {
this.report(tag, "`@".concat(INTERFACE_TAG, "` can only be used on interface declarations."));
this.report(tag, E.invalidInterfaceTagUsage());
}

@@ -192,3 +201,3 @@ };

else {
this.report(tag, "`@".concat(ENUM_TAG, "` can only be used on enum declarations or TypeScript unions."));
this.report(tag, E.invalidEnumTagUsage());
}

@@ -201,3 +210,3 @@ };

else {
this.report(tag, "`@".concat(INPUT_TAG, "` can only be used on type alias declarations."));
this.report(tag, E.invalidInputTagUsage());
}

@@ -210,7 +219,7 @@ };

else {
this.report(tag, "`@".concat(UNION_TAG, "` can only be used on type alias declarations."));
this.report(tag, E.invalidUnionTagUsage());
}
};
/** Error handling and location juggling */
Extractor.prototype.report = function (node, message) {
Extractor.prototype.report = function (node, message, relatedInformation) {
var start = node.getStart();

@@ -224,3 +233,4 @@ var length = node.getEnd() - start;

start: start,
length: length
length: length,
relatedInformation: relatedInformation
});

@@ -231,8 +241,17 @@ return null;

// Gives the user a path forward if they think we should be able to infer this type.
Extractor.prototype.reportUnhandled = function (node, message) {
var suggestion = "If you think ".concat(LIBRARY_NAME, " should be able to infer this type, please report an issue at ").concat(ISSUE_URL, ".");
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, ".");
var completedMessage = "".concat(message, "\n\n").concat(suggestion);
this.report(node, completedMessage);
return null;
return this.report(node, completedMessage, relatedInformation);
};
Extractor.prototype.related = function (node, message) {
return {
category: ts.DiagnosticCategory.Message,
code: 0,
file: node.getSourceFile(),
start: node.getStart(),
length: node.getWidth(),
messageText: message
};
};
Extractor.prototype.diagnosticAnnotatedLocation = function (node) {

@@ -258,3 +277,3 @@ var start = node.getStart();

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

@@ -264,3 +283,3 @@ if (name == null)

if (!ts.isUnionTypeNode(node.type)) {
return this.report(node, "Expected a TypeScript union. `@".concat(UNION_TAG, "` can only be used on TypeScript unions."));
return this.report(node, E.expectedUnionTypeNode());
}

@@ -273,3 +292,3 @@ var description = this.collectDescription(node.name);

if (!ts.isTypeReferenceNode(member)) {
return this.reportUnhandled(member, "Expected `@".concat(UNION_TAG, "` union members to be type references."));
return this.reportUnhandled(member, E.expectedUnionTypeReference());
}

@@ -281,3 +300,3 @@ var namedType = this.gqlNamedType(member.typeName, TypeContext_1.UNRESOLVED_REFERENCE_NAME);

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

@@ -287,3 +306,3 @@ try {

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

@@ -305,3 +324,3 @@ this.ctx.recordTypeName(node.name, name.value);

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

@@ -315,3 +334,3 @@ var typeName = this.typeReferenceFromParam(typeParam);

if (node.type == null) {
return this.report(funcName, "Expected GraphQL field to have an explicit return type.");
return this.report(funcName, E.invalidReturnTypeForFunctionField());
}

@@ -328,7 +347,9 @@ var type = this.collectMethodType(node.type);

if (!ts.isSourceFile(node.parent)) {
return this.report(node, "Expected `@".concat(FIELD_TAG, "` function to be a top-level declaration."));
return this.report(node, E.functionFieldNotTopLevel());
}
// TODO: Does this work in the browser?
var filename = this.ctx.getDestFilePath(node.parent);
var directives = [this.exportDirective(funcName, filename, funcName.text)];
var directives = [
this.exportDirective(funcName, filename, funcName.text),
];
if (funcName.text !== name.value) {

@@ -360,6 +381,6 @@ directives.push(this.methodNameDirective(funcName, funcName.text));

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

@@ -374,3 +395,3 @@ var nameNode = typeParam.type.typeName;

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

@@ -385,6 +406,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 a `@".concat(FIELD_TAG, "` function to be a named export, not a default export."));
return this.report(defaultKeyword, E.functionFieldDefaultExport());
}
if (exportKeyword == null) {
return this.report(node.name, "Expected a `@".concat(FIELD_TAG, "` function to be a named export."));
return this.report(node.name, E.functionFieldNotNamedExport());
}

@@ -413,2 +434,3 @@ return node.name;

var fields = this.collectInputFields(node);
var deprecatedDirective = this.collectDeprecated(node);
this.definitions.push({

@@ -419,10 +441,11 @@ kind: graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION,

name: name,
fields: fields !== null && fields !== void 0 ? fields : undefined
fields: fields !== null && fields !== void 0 ? fields : undefined,
directives: deprecatedDirective == null ? undefined : [deprecatedDirective]
});
};
Extractor.prototype.collectInputFields = function (node) {
var e_4, _a;
var e_3, _a;
var fields = [];
if (!ts.isTypeLiteralNode(node.type)) {
return this.reportUnhandled(node, "`@".concat(INPUT_TAG, "` can only be used on type literals."));
return this.reportUnhandled(node, E.inputTypeNotLiteral());
}

@@ -433,3 +456,3 @@ try {

if (!ts.isPropertySignature(member)) {
this.reportUnhandled(member, "`@".concat(INPUT_TAG, "` types only support property signature members."));
this.reportUnhandled(member, E.inputTypeFieldNotProperty());
continue;

@@ -442,3 +465,3 @@ }

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

@@ -448,3 +471,3 @@ try {

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

@@ -458,3 +481,3 @@ return fields.length === 0 ? null : fields;

if (node.type == null) {
return this.report(node, "Input field must have a type annotation.");
return this.report(node, E.inputFieldUntyped());
}

@@ -466,2 +489,3 @@ var inner = this.collectType(node.type);

var description = this.collectDescription(node.name);
var deprecatedDirective = this.collectDeprecated(node);
return {

@@ -474,3 +498,3 @@ kind: graphql_1.Kind.INPUT_VALUE_DEFINITION,

defaultValue: undefined,
directives: undefined
directives: deprecatedDirective == null ? undefined : [deprecatedDirective]
};

@@ -480,3 +504,3 @@ };

if (node.name == null) {
return this.report(node, "Unexpected `@".concat(TYPE_TAG, "` annotation on unnamed class declaration."));
return this.report(node, E.typeTagOnUnamedClass());
}

@@ -525,3 +549,3 @@ var name = this.entityName(node, tag);

if (!ts.isTypeLiteralNode(node.type)) {
this.reportUnhandled(node.type, "Expected `@".concat(TYPE_TAG, "` type to be a type literal. For example: `type Foo = { bar: string }`"));
this.reportUnhandled(node.type, E.typeTagOnAliasOfNonObject());
return;

@@ -566,5 +590,4 @@ }

}
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.");
this.report(member.name, E.typeNameNotDeclaration());
return false;

@@ -580,11 +603,11 @@ };

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\";`.");
this.report(node.name, E.typeNameMissingInitializer());
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\";`.");
this.report(node.initializer, E.typeNameInitializeNotString());
return false;
}
if (node.initializer.text !== expectedName) {
this.report(node.initializer, "Expected `__typename` property initializer to be `\"".concat(expectedName, "\"`, found `\"").concat(node.initializer.text, "\"`."));
this.report(node.initializer, E.typeNameInitializerWrong(expectedName, node.initializer.text));
return false;

@@ -596,3 +619,3 @@ }

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, "\";`"));
this.report(node, E.typeNameMissingTypeAnnotation(expectedName));
return false;

@@ -604,7 +627,7 @@ }

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, "\";`"));
this.report(node, E.typeNameTypeNotStringLiteral(expectedName));
return false;
}
if (node.literal.text !== expectedName) {
this.report(node, "Expected `__typename` property to be `\"".concat(expectedName, "\"`"));
this.report(node, E.typeNameDoesNotMatchExpected(expectedName));
return false;

@@ -678,7 +701,7 @@ }

Extractor.prototype.collectArgs = function (argsParam) {
var e_5, _a;
var e_4, _a;
var args = [];
var argsType = argsParam.type;
if (argsType == null) {
return this.report(argsParam, "Expected GraphQL field arguments to have a TypeScript type. If there are no arguments, you can use `args: never`.");
return this.report(argsParam, E.argumentParamIsMissingType());
}

@@ -689,3 +712,3 @@ if (argsType.kind === ts.SyntaxKind.NeverKeyword) {

if (!ts.isTypeLiteralNode(argsType)) {
return this.report(argsType, "Expected GraphQL field arguments to be typed using a literal object: `{someField: string}`.");
return this.report(argsType, E.argumentParamIsNotObject());
}

@@ -705,3 +728,3 @@ var defaults = null;

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

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

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

@@ -717,3 +740,3 @@ return args;

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

@@ -730,3 +753,3 @@ try {

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

@@ -736,3 +759,3 @@ try {

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

@@ -764,7 +787,6 @@ return defaults;

}
this.reportUnhandled(node, "Expected GraphQL field argument default values to be a literal.");
return null;
return this.reportUnhandled(node, E.defaultValueIsNotLiteral());
};
Extractor.prototype.collectArrayLiteral = function (node) {
var e_7, _a;
var e_6, _a;
var values = [];

@@ -784,3 +806,3 @@ var errors = false;

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

@@ -790,3 +812,3 @@ try {

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

@@ -803,3 +825,3 @@ if (errors) {

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

@@ -819,3 +841,3 @@ var errors = false;

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

@@ -825,3 +847,3 @@ try {

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

@@ -839,6 +861,6 @@ if (errors) {

if (!ts.isPropertyAssignment(node)) {
return this.reportUnhandled(node, "Expected object literal property to be a property assignment.");
return this.reportUnhandled(node, E.defaultArgElementIsNotAssignment());
}
if (node.name == null) {
return this.reportUnhandled(node, "Expected object literal property to have a name.");
return this.reportUnhandled(node, E.defaultArgPropertyMissingName());
}

@@ -850,3 +872,3 @@ var name = this.expectIdentifier(node.name);

if (initialize == null) {
return this.report(node, "Expected object literal property to have an initializer. For example: `{ offset = 10}`.");
return this.report(node, E.defaultArgPropertyMissingInitializer());
}

@@ -866,10 +888,10 @@ var value = this.collectConstValue(initialize);

// TODO: How can I create this error?
return this.report(node, "Expected GraphQL field argument type to be a property signature.");
return this.report(node, E.argIsNotProperty());
}
if (!ts.isIdentifier(node.name)) {
// TODO: How can I create this error?
return this.report(node.name, "Expected GraphQL field argument names to be a literal.");
return this.report(node.name, E.argNameNotLiteral());
}
if (node.type == null) {
return this.report(node.name, "Expected GraphQL field argument to have a type.");
return this.report(node.name, E.argNotTyped());
}

@@ -880,2 +902,8 @@ var type = this.collectType(node.type);

if (node.questionToken) {
/*
// TODO: Don't allow args that are optional but don't accept null
if (type.kind === Kind.NON_NULL_TYPE) {
return this.report(node.questionToken, E.nonNullTypeCannotBeOptional());
}
*/
type = this.gqlNullableType(type);

@@ -891,2 +919,3 @@ }

}
var deprecatedDirective = this.collectDeprecated(node);
return {

@@ -899,3 +928,3 @@ kind: graphql_1.Kind.INPUT_VALUE_DEFINITION,

defaultValue: defaultValue || undefined,
directives: []
directives: deprecatedDirective != null ? [deprecatedDirective] : undefined
};

@@ -938,3 +967,3 @@ };

Extractor.prototype.enumTypeAliasVariants = function (node) {
var e_9, _a;
var e_8, _a;
var _b;

@@ -958,3 +987,3 @@ // Semantically we only support deriving enums from type aliases that

if (!ts.isUnionTypeNode(node.type)) {
this.reportUnhandled(node.type, "Expected `@".concat(ENUM_TAG, "` to be a union type, or a string literal in the edge case of a single value enum."));
this.reportUnhandled(node.type, E.enumTagOnInvalidNode());
return null;

@@ -971,2 +1000,3 @@ }

if (((_b = symbol === null || symbol === void 0 ? void 0 : symbol.declarations) === null || _b === void 0 ? void 0 : _b.length) === 1) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var declaration = symbol.declarations[0];

@@ -993,3 +1023,3 @@ if (ts.isTypeAliasDeclaration(declaration)) {

!ts.isStringLiteral(member.literal)) {
this.reportUnhandled(member, "Expected `@".concat(ENUM_TAG, "` enum members to be string literal types. For example: `'foo'`."));
this.reportUnhandled(member, E.enumVariantNotStringLiteral());
continue;

@@ -1007,3 +1037,3 @@ }

}
catch (e_9_1) { e_9 = { error: e_9_1 }; }
catch (e_8_1) { e_8 = { error: e_8_1 }; }
finally {

@@ -1013,3 +1043,3 @@ try {

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

@@ -1019,3 +1049,3 @@ return values;

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

@@ -1027,3 +1057,3 @@ try {

!ts.isStringLiteral(member.initializer)) {
this.reportUnhandled(member, "Expected `@".concat(ENUM_TAG, "` enum members to have string literal initializers. For example: `FOO = 'foo'`."));
this.reportUnhandled(member, E.enumVariantMissingInitializer());
continue;

@@ -1042,3 +1072,3 @@ }

}
catch (e_10_1) { e_10 = { error: e_10_1 }; }
catch (e_9_1) { e_9 = { error: e_9_1 }; }
finally {

@@ -1048,3 +1078,3 @@ try {

}
finally { if (e_10) throw e_10.error; }
finally { if (e_9) throw e_9.error; }
}

@@ -1062,3 +1092,3 @@ return values;

if (node.name == null) {
return this.report(node, "Expected GraphQL entity to have a name.");
return this.report(node, E.gqlEntityMissingName());
}

@@ -1071,3 +1101,3 @@ var id = this.expectIdentifier(node.name);

Extractor.prototype.methodDeclaration = function (node) {
var tag = this.findTag(node, FIELD_TAG);
var tag = this.findTag(node, exports.FIELD_TAG);
if (tag == null)

@@ -1079,3 +1109,3 @@ return null;

if (node.type == null) {
return this.report(node.name, "Expected GraphQL field to have a type.");
return this.report(node.name, E.methodMissingType());
}

@@ -1133,3 +1163,3 @@ var type = this.collectMethodType(node.type);

if (node.typeArguments == null) {
return this.report(node, "Expected type reference to have type arguments.");
return this.report(node, E.promiseMissingTypeArg());
}

@@ -1144,3 +1174,3 @@ return node.typeArguments[0];

if (symbol == null) {
return this.report(node, "Expected TypeScript to be able to resolve this GraphQL entity to a symbol.");
return this.report(node, E.cannotResolveSymbolForDescription());
}

@@ -1174,4 +1204,4 @@ var doc = symbol.getDocumentationComment(this.ctx.checker);

kind: graphql_1.Kind.DIRECTIVE,
loc: this.loc(tag),
name: this.gqlName(tag, DEPRECATED_TAG),
loc: this.loc(tag.tagName),
name: this.gqlName(tag.tagName, DEPRECATED_TAG),
arguments: args

@@ -1181,3 +1211,3 @@ };

Extractor.prototype.property = function (node) {
var tag = this.findTag(node, FIELD_TAG);
var tag = this.findTag(node, exports.FIELD_TAG);
if (tag == null)

@@ -1189,3 +1219,3 @@ return null;

if (node.type == null) {
this.report(node.name, "Expected GraphQL field to have a type.");
this.report(node.name, E.propertyFieldMissingType());
return null;

@@ -1220,2 +1250,4 @@ }

};
// TODO: Support separate modes for input and output types
// For input nodes and field may only be optional if `null` is a valid value.
Extractor.prototype.collectType = function (node) {

@@ -1237,5 +1269,4 @@ var _this = this;

var types = node.types.filter(function (type) { return !_this.isNullish(type); });
if (types.length !== 1) {
this.report(node, "Expected exactly one non-nullish type.");
return null;
if (types.length === 0) {
return this.report(node, E.expectedOneNonNullishType());
}

@@ -1245,2 +1276,11 @@ var type = this.collectType(types[0]);

return null;
if (types.length > 1) {
var _a = __read(types), first = _a[0], rest = _a.slice(1);
// FIXME: If each of `rest` matches `first` this should be okay.
var incompatibleVariants = rest.map(function (tsType) {
return _this.related(tsType, "Other non-nullish type");
});
this.report(first, E.expectedOneNonNullishType(), incompatibleVariants);
return null;
}
if (node.types.length > 1) {

@@ -1261,9 +1301,9 @@ return this.gqlNullableType(type);

else if (node.kind === ts.SyntaxKind.NumberKeyword) {
return this.report(node, "Unexpected number type. GraphQL supports both Int and Float, making `number` ambiguous. Instead, import the `Int` or `Float` type from `".concat(LIBRARY_IMPORT_NAME, "` and use that. e.g. `import { Int, Float } from \"").concat(LIBRARY_IMPORT_NAME, "\";`."));
return this.report(node, E.ambiguousNumberType());
}
else if (ts.isTypeLiteralNode(node)) {
return this.report(node, "Unexpected type literal. You may want to define a named GraphQL type elsewhere and reference it here.");
return this.report(node, E.unsupportedTypeLiteral());
}
// TODO: Better error message. This is okay if it's a type reference, but everything else is not.
this.reportUnhandled(node, "Unknown GraphQL type.");
this.reportUnhandled(node, E.unknownGraphQLType());
return null;

@@ -1281,3 +1321,3 @@ };

if (node.typeArguments == null) {
return this.report(node, "Expected type reference to have type arguments.");
return this.report(node, E.pluralTypeMissingParameter());
}

@@ -1316,3 +1356,3 @@ var element = this.collectType(node.typeArguments[0]);

}
return this.report(node, "Expected an identifier.");
return this.report(node, E.expectedIdentifer());
};

@@ -1331,9 +1371,9 @@ Extractor.prototype.findTag = function (node, tagName) {

var tags = ts.getJSDocTags(parentNode);
var killsParentOnExceptions = tags.find(function (tag) { return tag.tagName.text === KILLS_PARENT_ON_EXCEPTION_TAG; });
var killsParentOnExceptions = tags.find(function (tag) { return tag.tagName.text === exports.KILLS_PARENT_ON_EXCEPTION_TAG; });
if (killsParentOnExceptions) {
if (!this.configOptions.nullableByDefault) {
this.report(killsParentOnExceptions.tagName, "Unexpected `@".concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` tag. `@").concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` is only supported when the Grats config `nullableByDefault` is enabled."));
this.report(killsParentOnExceptions.tagName, E.killsParentOnExceptionWithWrongConfig());
}
if (type.kind !== graphql_1.Kind.NON_NULL_TYPE) {
this.report(killsParentOnExceptions.tagName, "Unexpected `@".concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` tag. `@").concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` is unnessesary on fields that are already nullable."));
this.report(killsParentOnExceptions.tagName, E.killsParentOnExceptionOnNullable());
}

@@ -1340,0 +1380,0 @@ return type;

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

function extractGratsSchemaAtRuntime(runtimeOptions) {
var _a;
var parsedTsConfig = getParsedTsConfig();

@@ -41,3 +40,3 @@ var schemaResult = (0, lib_1.buildSchemaResult)(parsedTsConfig);

var sdl = (0, utils_1.printSchemaWithDirectives)(runtimeSchema, { assumeValid: true });
var filePath = (_a = runtimeOptions.emitSchemaFile) !== null && _a !== void 0 ? _a : "./schema.graphql";
var filePath = runtimeOptions.emitSchemaFile;
fs.writeFileSync(filePath, sdl);

@@ -44,0 +43,0 @@ }

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

var utils_1 = require("@graphql-tools/utils");
var graphql_1 = require("graphql");
function main() {

@@ -114,2 +115,3 @@ return __awaiter(this, void 0, void 0, function () {

var fixturesDir = path.join(__dirname, "fixtures");
var integrationFixturesDir = path.join(__dirname, "integrationFixtures");
var testDirs = [

@@ -146,3 +148,50 @@ {

},
{
fixturesDir: integrationFixturesDir,
transformer: function (code, fileName) { return __awaiter(void 0, void 0, void 0, function () {
var filePath, server, options, files, parsedOptions, schemaResult, schema, data;
return __generator(this, function (_a) {
var _b;
switch (_a.label) {
case 0:
filePath = "".concat(integrationFixturesDir, "/").concat(fileName);
return [4 /*yield*/, (_b = filePath, Promise.resolve().then(function () { return require(_b); }))];
case 1:
server = _a.sent();
if (server.query == null || typeof server.query !== "string") {
throw new Error("Expected `".concat(filePath, "` to export a query text as `query`"));
}
if (server.Query == null || typeof server.Query !== "function") {
throw new Error("Expected `".concat(filePath, "` to export a Query type as `Query`"));
}
options = {
nullableByDefault: true
};
files = [filePath, "src/Types.ts"];
parsedOptions = {
options: {},
raw: {
grats: options
},
errors: [],
fileNames: files
};
schemaResult = (0, lib_1.buildSchemaResult)(parsedOptions);
if (schemaResult.kind === "ERROR") {
throw new Error(schemaResult.err.formatDiagnosticsWithContext());
}
schema = schemaResult.value;
return [4 /*yield*/, (0, graphql_1.graphql)({
schema: schema,
source: server.query,
rootValue: new server.Query()
})];
case 2:
data = _a.sent();
return [2 /*return*/, JSON.stringify(data, null, 2)];
}
});
}); }
},
];
main();

@@ -74,3 +74,4 @@ "use strict";

this._testFixtures.push(fileName);
if (filterRegex != null && !fileName.match(filterRegex)) {
var filePath = path.join(fixturesDir, fileName);
if (filterRegex != null && !filePath.match(filterRegex)) {
this._skip.add(fileName);

@@ -77,0 +78,0 @@ }

"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
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;

@@ -42,2 +69,3 @@ exports.graphqlSourceToSourceFile = exports.diagnosticAtGraphQLLocation = exports.graphQlErrorToDiagnostic = exports.FAKE_ERROR_CODE = exports.ReportableDiagnostics = exports.err = exports.ok = void 0;

function graphQlErrorToDiagnostic(error) {
var e_1, _a;
var position = error.positions[0];

@@ -47,2 +75,41 @@ if (position == null) {

}
// Start with baseline location infromation
var start = position;
var length = 1;
var relatedInformation;
// Nodes have actual ranges (not just a single position), so we we have one
// (or more!) use that instead.
if (error.nodes != null && error.nodes.length > 0) {
var _b = __read(error.nodes), node = _b[0], rest = _b.slice(1);
if (node.loc != null) {
start = node.loc.start;
length = node.loc.end - node.loc.start;
if (rest.length > 0) {
relatedInformation = [];
try {
for (var rest_1 = __values(rest), rest_1_1 = rest_1.next(); !rest_1_1.done; rest_1_1 = rest_1.next()) {
var relatedNode = rest_1_1.value;
if (relatedNode.loc == null) {
continue;
}
relatedInformation.push({
category: ts.DiagnosticCategory.Message,
code: exports.FAKE_ERROR_CODE,
messageText: "Related location",
file: graphqlSourceToSourceFile(relatedNode.loc.source),
start: relatedNode.loc.start,
length: relatedNode.loc.end - relatedNode.loc.start
});
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (rest_1_1 && !rest_1_1.done && (_a = rest_1["return"])) _a.call(rest_1);
}
finally { if (e_1) throw e_1.error; }
}
}
}
}
var sourceFile;

@@ -57,5 +124,5 @@ if (error.source != null) {

category: ts.DiagnosticCategory.Error,
start: position,
// FIXME: Improve ranges
length: 1
start: start,
length: length,
relatedInformation: relatedInformation
};

@@ -62,0 +129,0 @@ }

{
"name": "grats",
"version": "0.0.0-main-7408c383",
"version": "0.0.0-main-76d60d78",
"main": "dist/src/index.js",

@@ -31,2 +31,3 @@ "bin": "dist/src/cli.js",

},
"packageManager": "pnpm@8.1.1",
"scripts": {

@@ -33,0 +34,0 @@ "test": "ts-node --esm src/tests/test.ts",

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

# -=[ EXPERIMENTAL PRE ALPHA ]=-
# -=[ ALPHA SOFTWARE ]=-
**This is currently a proof of concept. It won't yet work on any real projects.**
**Grats is still experimental. Feel free to try it out and give feedback, but they api is still in flux**

@@ -9,518 +9,15 @@ # Grats: Implementation-First GraphQL for TypeScript

Grats is a tool for statically infering GraphQL schema from your vanilla
TypeScript code.
**What if building a GraphQL server were as simple as just writing functions?**
Just write your types and resolvers as regular TypeScript and annotate your
types and fields with simple JSDoc tags. From there, Grats can extract your
GraphQL schema automatically by statically analyzing your code and its types. No
convoluted directive APIs to remember. No need to define your Schema at
runtime with verbose builder APIs.
When you write your GraphQL server in TypeScript, your fields and resovlers
are _already_ annotated with type information. _Grats leverages your existing
type annotations to automatically extract an executable GraphQL schema from your
generic TypeScript resolver 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!
By making your TypeScript implementation the source of truth, you never have to
worry about valiating that your implementiaton matches your schema. Your
implementation _is_ your schema!
## Examples
## Read the docs: https://grats.capt.dev/
Grats is flexible enough to work with both class-based and functional
approaches to authoring GraphQL types and resolvers.
### Class-Based
```ts
/** @gqlType */
export default class Query {
/** @gqlField */
me(): User {
return new User();
}
/**
* @gqlField
* @deprecated Please use `me` instead.
*/
viewer(): User {
return new User();
}
}
/**
* A user in our kick-ass system!
* @gqlType
*/
class User {
/** @gqlField */
name: string = 'Alice';
/** @gqlField */
greeting(args: { salutation: string }): string {
return `${args.salutation}, ${this.name}`;
}
}
```
### Functional
```ts
/** @gqlType */
export type Query {};
/** @gqlField */
export function me(_: Query): User {
return { name: "Alice" };
}
/**
* @gqlField
* @deprecated Please use `me` instead.
*/
export function viewer(_: Query): User {
return { name: "Alice" };
}
/**
* A user in our kick-ass system!
* @gqlType
*/
type User = {
/** @gqlField */
name: string;
}
/** @gqlField */
export function greeting(user: User, args: { salutation: string }): string {
return `${args.salutation}, ${user.name}`;
}
```
Both of the above examples extract the following GraphQL schema:
```graphql
type Query {
me: User
viewer: User @deprecated(reason: "Please use `me` instead.")
}
"""A user in our kick-ass system!"""
type User {
name: String
greeting(salutation: String!): String
}
```
**Give it a try in the [online playground](https://capt.dev/grats-sandbox)!**
## Quick Start
For dev mode or small projects, Grats offers a runtime extraction mode. This is
the easiest way to get started with Grats, although you may find that it causes
a slow startup time. For larger projects, you probably want to use the build
mode (documentation to come).
```bash
npm install express express-graphql grats
```
**Ensure your project has a `tsconfig.json` file.**
```ts
import * as express from "express";
import { graphqlHTTP } from "express-graphql";
import { extractGratsSchemaAtRuntime } from "grats";
/** @gqlType */
class Query {
/** @gqlField */
hello(): string {
return "Hello world!";
}
}
const app = express();
// At runtime Grats will parse your TypeScript project (including this file!) and
// extract the GraphQL schema.
const schema = extractGratsSchemaAtRuntime({
emitSchemaFile: "./schema.graphql",
});
app.use(
"/graphql",
graphqlHTTP({ schema, rootValue: new Query(), graphiql: true }),
);
app.listen(4000);
console.log("Running a GraphQL API server at http://localhost:4000/graphql");
```
Try it out on [CodeSandbox](https://capt.dev/grats-sandbox)!
## Configuration
Grats has a few configuration options. They can be set in your project's
`tsconfig.json` file:
```json5
{
"grats": {
// Should all fields be typed as nullable in accordance with GraphQL best
// practices?
// https://graphql.org/learn/best-practices/#nullability
//
// Individual fileds can declare themselves as nonnullable by adding the
// docblock tag `@killsParentOnException`.
"nullableByDefault": true, // Default: true
// Should Grats error if it encounters a TypeScript type error?
// Note that Grats will always error if it encounters a TypeScript syntax
// error.
"reportTypeScriptTypeErrors": false, // Default: false
},
"compilerOptions": {
// ... TypeScript config...
}
}
```
## API Usage
In order for Grats to extract GraphQL schema from your code, simply mark which
TypeScript structures should be included in the schema by marking them with
special JSDoc tags such as `/** @gqlType */` or `/** @gqlField */`.
Any comment text preceding the JSDoc `@` tag will be used as that element's description.
**Note that JSDocs must being with `/**` (two asterix).** However, they may be consolidated into a single line.
The following JSDoc tags are supported:
* [`@gqlType`](#gqltype)
* [`@gqlInterface`](#gqlinterface)
* [`@gqlField`](#gqlfield)
* [`@gqlUnion`](#gqlunion)
* [`@gqlScalar`](#gqlscalar)
* [`@gqlEnum`](#gqlenum)
* [`@gqlInput`](#gqlinput)
Each tag maps directly to a concept in the GraphQL [Schema Definition
Language](https://graphql.org/learn/schema/) (SDL). The documentation below aims
to be complete, but our hope is that you feel empowered to just slap one of
these docblock tags on the relevent TypeScript class/type/method/etc in your
code, and let Grats' helpful error messages guide you.
### @gqlType
GraphQL types can be defined by placing a `@gqlType` docblock directly before a:
* Class declaration
* Interface declaration
* Type alias of a literal type
```ts
/**
* 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>
*/
class MyClass {
/** @gqlField */
someField: string;
}
```
```ts
/** @gqlType */
interface MyType {
/** @gqlField */
someField: string;
}
```
```ts
/** @gqlType */
type MyType = {
/** @gqlField */
someField: string;
}
```
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.
### @gqlInterface
GraphQL interfaces can be defined by placing a `@gqlInterface` docblock directly before an:
* Interface declaration
```ts
/**
* A description of my interface.
* @gqlInterface <optional name of the type, if different from class name>
*/
interface MyClass {
/** @gqlField */
someField: string;
}
```
All `@gqlType` types which implement the interface in TypeScript will
automatically implement it in GraphQL as well.
**Note**: Types declared using type literals `type MyType = { ... }` cannot yet
implement interfaces. For now, you must use a class declarations for types which
implement interfaces.
### @gqlField
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)
```ts
/**
* A description of some field.
* @gqlField <optional name of the field, if different from property name>
*/
someField: string;
/** @gqlField */
myField(): string {
return "Hello World";
}
```
#### Field nullability
**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 the config option `nullableByDefault` to
`false`.
With `nullableByDefault` _enabled_, you may declare an individual field as
nonnullable adding the docblock tag `@killsParentOnException`. This will cause
the field to be typed as non-nullable, but _it comes at a price_. Should the
resolver throw, the error will bubble up to the first nullable parent. If
`@killsParentOnException` is used too liberally, small errors can take down huge
portions of your query.
Dissabling `nullableByDefault` is equivilent to marking all nonnullable fields
with `@killsParentOnException`.
```ts
/**
* @gqlField
* @killsParentOnException
*/
myField(): string {
return this.someOtherMethod();
}
```
#### Field arguments
If you wish to define arguments for a field, define your argument types inline:
```ts
/** @gqlField */
myField(args: { greeting: string }): string {
return `${args.greeting} World`;
}
```
Default values for arguments can be defined by using the `=` operator with destructuring:
```ts
/** @gqlField */
myField({ greeting = "Hello" }: { greeting: string }): string {
return `${greeting} World`;
}
```
```ts
/** @gqlField */
myField({ greeting = { salutation: "Sup" } }: { greeting: GreetingConfig }): string {
return `${greeting.salutation} World`;
}
```
#### Field descriptions
Arguments can be given descriptions by using the `/**` syntax:
```ts
/** @gqlField */
myField(args: {
/** A description of the greeting argument */
greeting: string
}): string {
return `${args.greeting} World`;
}
```
#### Deprecated fields
To mark a field as deprecated, use the `@deprecated` JSDoc tag:
```ts
/**
* @gqlField
* @deprecated Please use myNewField instead.
*/
myOldField(): string {
return "Hello World";
}
```
#### Functional style fields
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.
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
/** @gqlField */
export function userById(_: Query, args: {id: string}): User {
return DB.getUserById(args.id);
}
```
Extending Mutation:
```ts
/** @gqlField */
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
```ts
/**
* A description of my union.
* @gqlUnion <optional name of the union, if different from type name>
*/
type MyUnion = User | Post;
```
### @gqlScalar
GraphQL custom sclars can be defined by placing a `@gqlScalar` docblock directly before a:
* Type alias declaration
```ts
/**
* A description of my custom scalar.
* @gqlScalar <optional name of the scalar, if different from type name>
*/
type MyCustomString = string;
```
Note: For the built-in GraphQL scalars that don't have a corresponding TypeScript type, Grats ships with type aliases you can import. You may be promted to use one of these by Grat if you try to use `number` in a positon from which Grat needs to infer a GraphQL type.
```ts
import { Float, Int, ID } from "grats";
/** @gqlType */
class Math {
id: ID;
/** @gqlField */
round(args: {float: Float}): Int {
return Math.round(args.float);
}
}
```
### @gqlEnum
GraphQL enums can be defined by placing a `@gqlEnum` docblock directly before a:
* TypeScript enum declaration
* Type alias of a union of string literals
```ts
/**
* A description of my enum.
* @gqlEnum <optional name of the enum, if different from type name>
*/
enum MyEnum {
/** A description of my value */
OK = "OK",
/** A description of my other value */
ERROR = "ERROR"
}
```
Note that the values of the enum are used as the GraphQL enum values, and must
be string literals.
To mark a variants as deprecated, use the `@deprecated` JSDoc tag directly before it:
```ts
/** @gqlEnum */
enum MyEnum {
OK = "OK"
/** @deprecated Please use OK instead. */
OKAY = "OKAY"
ERROR = "ERROR"
}
```
We also support defining enums using a union of string literals, however there
are some limitations to this approach:
* You cannot add descriptions to enum values
* You cannot mark enum values as deprecated
This is due to the fact that TypeScript does not see JSDoc comments as
"attaching" to string literal types.
```ts
/** @gqlEnum */
type MyEnum = "OK" | "ERROR";
```
### @gqlInput
GraphQL input types can be defined by placing a `@gqlInput` docblock directly before a:
* Type alias declaration
```ts
/**
* Description of my input type
* @gqlInput <optional name of the input, if different from type name>
*/
type MyInput = {
name: string;
age: number;
};
```
## Example
See `examples/express-graphql/` in the repo root for a working example. Here we
run the static analysis at startup time. Nice for development, but not ideal for
production where you would want to cache the schema and write it to disk for
other tools to see.
## Contributing

@@ -530,40 +27,2 @@

# FAQ
## Why would I _not_ want to use Grats
Because Grats relies on static analysis to infer types, it requires that your
GraphQL fields use types that can be statically analyzed. This means that you
can't use complex derived types in positions where Grats needs to be able to
infer the type. For example, field arguments and return values.
Currently, Grats does not have a great way to handle the case where you want to
expose structures that are not owned by your codebase. For example, if you want
to expose a field that returns a type from a third-party library, or a type that
is generated by some other codegen tool. Today, your best option is to define a
wrapper resolver class.
## Why use comments and not decorators?
Using decorators to signal that a class/method/etc should be included in the
schema would have some advantages:
* The syntax is well defined, so it:
* Can be checked/documented by TypeScript types
* Formatted with tools like Prettier
* Would not require custom parsing/validaiton rules
However, it also has some disadvantages:
* The feature is technically "experimental" in TypeScript and may change in the future.
* Decorators cannot be applied to types, so it would precude the ability to
define GraphQL constructs using types (e.g. interfaces, unions, etc).
* Decorators cannot be applied to parameters, so it would preclude the ability
to define GraphQL constructs using parameters (e.g. field arguments).
* Decorators are a runtime construct, which means they must be imported and give
the impression that they might have some runtime behavior. This is not the
case for Grats, which is purely a static analysis tool.
Given these tradeoffs, we've decided to use comments instead of decorators.
# Acknowledgements

@@ -570,0 +29,0 @@

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