New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More

grats

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 to 0.0.1

@@ -41,32 +41,28 @@ #!/usr/bin/env node

var graphql_1 = require("graphql");
var _1 = require("./");
var lib_1 = require("./lib");
var glob_1 = require("glob");
/**
* Build a schema from a glob pattern.
* Extract schema from TypeScript files and print to stdout.
*
* Usage: node dist/cli.js "./**.ts"
* Will search for a tsconfig.json file relative to the current working
* directory.
*
* There are not yet any command line options.
*
* Usage: grats
*/
function main() {
return __awaiter(this, void 0, void 0, function () {
var pattern, files, schemaResult;
var parsed, schemaResult;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
pattern = process.argv[2];
if (!pattern) {
throw new Error("Expected glob as first argument");
}
return [4 /*yield*/, (0, glob_1.glob)(pattern)];
case 1:
files = _a.sent();
schemaResult = (0, lib_1.buildSchemaResult)({ files: files });
if (schemaResult.kind === "ERROR") {
console.error(schemaResult.err.formatDiagnosticsWithColorAndContext());
process.exit(1);
}
else {
console.log((0, graphql_1.printSchema)(schemaResult.value));
}
return [2 /*return*/];
parsed = (0, _1.getParsedTsConfig)();
schemaResult = (0, lib_1.buildSchemaResult)((0, _1.gratsOptionsFromTsConfig)(parsed));
if (schemaResult.kind === "ERROR") {
console.error(schemaResult.err.formatDiagnosticsWithColorAndContext());
process.exit(1);
}
else {
console.log((0, graphql_1.printSchema)(schemaResult.value));
}
return [2 /*return*/];
});

@@ -73,0 +69,0 @@ });

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

import { TypeContext } from "./TypeContext";
import { BuildOptions } from "./lib";
import { ConfigOptions } from "./lib";
type ArgDefaults = Map<string, ts.Expression>;

@@ -22,5 +22,5 @@ /**

ctx: TypeContext;
buildOptions: BuildOptions;
configOptions: ConfigOptions;
errors: ts.Diagnostic[];
constructor(sourceFile: ts.SourceFile, ctx: TypeContext, buildOptions: BuildOptions);
constructor(sourceFile: ts.SourceFile, ctx: TypeContext, buildOptions: ConfigOptions);
extract(): DiagnosticsResult<DefinitionNode[]>;

@@ -33,5 +33,6 @@ extractType(node: ts.Node, tag: ts.JSDocTag): void;

extractUnion(node: ts.Node, tag: ts.JSDocTag): void;
extractExtendType(node: ts.Node, tag: ts.JSDocTag): void;
/** Error handling and location juggling */
report(node: ts.Node, message: string): void;
reportUnhandled(node: ts.Node, message: string): void;
report(node: ts.Node, message: string): null;
reportUnhandled(node: ts.Node, message: string): null;
diagnosticAnnotatedLocation(node: ts.Node): {

@@ -46,2 +47,5 @@ start: number;

unionTypeAliasDeclaration(node: ts.TypeAliasDeclaration, tag: ts.JSDocTag): null | undefined;
functionDeclarationExtendType(node: ts.FunctionDeclaration, tag: ts.JSDocTag): null | undefined;
typeReferenceFromParam(typeParam: ts.ParameterDeclaration): NameNode | null;
namedFunctionExportName(node: ts.FunctionDeclaration): ts.Identifier | null;
scalarTypeAliasDeclaration(node: ts.TypeAliasDeclaration, tag: ts.JSDocTag): null | undefined;

@@ -52,6 +56,7 @@ inputTypeAliasDeclaration(node: ts.TypeAliasDeclaration, tag: ts.JSDocTag): null | undefined;

typeClassDeclaration(node: ts.ClassDeclaration, tag: ts.JSDocTag): null | undefined;
collectInterfaces(node: ts.ClassDeclaration): Array<NamedTypeNode> | null;
typeInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): null | undefined;
collectInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<NamedTypeNode> | null;
interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): void;
collectFields(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<FieldDefinitionNode>;
collectArgs(node: ts.MethodDeclaration): ReadonlyArray<InputValueDefinitionNode> | null;
collectArgs(argsParam: ts.ParameterDeclaration): ReadonlyArray<InputValueDefinitionNode> | null;
collectArgDefaults(node: ts.ObjectBindingPattern): ArgDefaults;

@@ -63,3 +68,3 @@ collectConstValue(node: ts.Expression): ConstValueNode | null;

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

@@ -76,2 +81,3 @@ collectDescription(node: ts.Node): StringValueNode | null;

methodNameDirective(nameNode: ts.Node, name: string): ConstDirectiveNode;
exportDirective(nameNode: ts.Node, filename: string, functionName: string): ConstDirectiveNode;
/** GraphQL AST node helper methods */

@@ -78,0 +84,0 @@ gqlName(node: ts.Node, value: string): NameNode;

@@ -20,5 +20,6 @@ "use strict";

var serverDirectives_1 = require("./serverDirectives");
var LIBRARY_IMPORT_NAME = "<library import name>";
var LIBRARY_NAME = "<library name>";
var ISSUE_URL = "<issue URL>";
var path_1 = require("path");
var LIBRARY_IMPORT_NAME = "grats";
var LIBRARY_NAME = "Grats";
var ISSUE_URL = "https://github.com/captbaritone/grats/issues";
var TYPE_TAG = "GQLType";

@@ -31,2 +32,3 @@ var FIELD_TAG = "GQLField";

var INPUT_TAG = "GQLInput";
var EXTEND_TYPE = "GQLExtendType";
var DEPRECATED_TAG = "deprecated";

@@ -49,3 +51,3 @@ /**

this.ctx = ctx;
this.buildOptions = buildOptions;
this.configOptions = buildOptions;
}

@@ -88,2 +90,5 @@ // Traverse all nodes, checking each one for its JSDoc tags.

break;
case EXTEND_TYPE:
_this.extractExtendType(node, tag);
break;
}

@@ -109,4 +114,7 @@ }

}
else if (ts.isInterfaceDeclaration(node)) {
this.typeInterfaceDeclaration(node, tag);
}
else {
this.report(tag, "`@".concat(TYPE_TAG, "` can only be used on class declarations."));
this.report(tag, "`@".concat(TYPE_TAG, "` can only be used on class or interface declarations."));
}

@@ -157,2 +165,11 @@ };

};
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 */

@@ -170,2 +187,3 @@ Extractor.prototype.report = function (node, message) {

});
return null;
};

@@ -178,2 +196,3 @@ // Report an error that we don't know how to infer a type, but it's possible that we should.

this.report(node, completedMessage);
return null;
};

@@ -205,4 +224,3 @@ Extractor.prototype.diagnosticAnnotatedLocation = function (node) {

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

@@ -215,7 +233,6 @@ var description = this.collectDescription(node.name);

if (!ts.isTypeReferenceNode(member)) {
this.reportUnhandled(member, "Expected `@".concat(UNION_TAG, "` union members to be type references."));
return null;
return this.reportUnhandled(member, "Expected `@".concat(UNION_TAG, "` union members to be type references."));
}
var namedType = this.gqlNamedType(member.typeName, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
this.ctx.markUnresolvedType(member.typeName, namedType);
this.ctx.markUnresolvedType(member.typeName, namedType.name);
types.push(namedType);

@@ -240,2 +257,90 @@ }

};
Extractor.prototype.functionDeclarationExtendType = function (node, tag) {
var funcName = this.namedFunctionExportName(node);
if (funcName == null)
return null;
var typeParam = node.parameters[0];
if (typeParam == null) {
return this.report(funcName, "Expected type extension function to have a first argument representing the type to extend.");
}
var typeName = this.typeReferenceFromParam(typeParam);
if (typeName == null)
return null;
var name = this.entityName(node, tag);
if (name == null)
return null;
if (node.type == null) {
return this.report(funcName, "Expected GraphQL field to have an explicit return type.");
}
var type = this.collectType(node.type);
if (type == null)
return null;
var args = null;
var argsParam = node.parameters[1];
if (argsParam != null) {
args = this.collectArgs(argsParam);
}
var description = this.collectDescription(funcName);
if (!ts.isSourceFile(node.parent)) {
return this.report(node, "Expected type extension function to be a top-level declaration.");
}
// TODO: Does this work in the browser?
var filename = (0, path_1.relative)(this.ctx.host.getCurrentDirectory(), node.parent.fileName);
var directives = [this.exportDirective(funcName, filename, funcName.text)];
if (funcName.text !== name.value) {
directives = [this.methodNameDirective(funcName, funcName.text)];
}
var deprecated = this.collectDeprecated(node);
if (deprecated != null) {
directives.push(deprecated);
}
this.definitions.push({
kind: graphql_1.Kind.OBJECT_TYPE_EXTENSION,
loc: this.loc(node),
name: typeName,
fields: [
{
kind: graphql_1.Kind.FIELD_DEFINITION,
loc: this.loc(node),
description: description || undefined,
name: name,
arguments: args || undefined,
type: this.handleErrorBubbling(type),
directives: directives.length === 0 ? undefined : directives
},
]
});
};
Extractor.prototype.typeReferenceFromParam = function (typeParam) {
if (typeParam.type == null) {
return this.report(typeParam, "Expected first argument of a type extension 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.");
}
var nameNode = typeParam.type.typeName;
var typeName = this.gqlName(nameNode, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
this.ctx.markUnresolvedType(nameNode, typeName);
return typeName;
};
Extractor.prototype.namedFunctionExportName = function (node) {
var _a, _b;
if (node.name == null) {
return this.report(node, "Expected type extension function to be a named function.");
}
var exportKeyword = (_a = node.modifiers) === null || _a === void 0 ? void 0 : _a.some(function (modifier) {
return modifier.kind === ts.SyntaxKind.ExportKeyword;
});
var defaultKeyword = (_b = node.modifiers) === null || _b === void 0 ? void 0 : _b.find(function (modifier) {
return modifier.kind === ts.SyntaxKind.DefaultKeyword;
});
if (defaultKeyword != null) {
// TODO: We could support this
return this.report(defaultKeyword, "Expected type extension 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 node.name;
};
Extractor.prototype.scalarTypeAliasDeclaration = function (node, tag) {

@@ -273,4 +378,3 @@ var name = this.entityName(node, tag);

if (!ts.isTypeLiteralNode(node.type)) {
this.reportUnhandled(node, "`@".concat(INPUT_TAG, "` can only be used on type literals."));
return null;
return this.reportUnhandled(node, "`@".concat(INPUT_TAG, "` can only be used on type literals."));
}

@@ -303,4 +407,3 @@ try {

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

@@ -324,4 +427,3 @@ var inner = this.collectType(node.type);

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

@@ -345,2 +447,20 @@ var name = this.entityName(node, tag);

};
Extractor.prototype.typeInterfaceDeclaration = function (node, tag) {
var name = this.entityName(node, tag);
if (name == null)
return null;
var description = this.collectDescription(node.name);
var fields = this.collectFields(node);
var interfaces = this.collectInterfaces(node);
this.ctx.recordTypeName(node.name, name.value);
this.definitions.push({
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,
loc: this.loc(node),
description: description !== null && description !== void 0 ? description : undefined,
directives: undefined,
name: name,
fields: fields,
interfaces: interfaces !== null && interfaces !== void 0 ? interfaces : undefined
});
};
Extractor.prototype.collectInterfaces = function (node) {

@@ -359,3 +479,3 @@ var _this = this;

var namedType = _this.gqlNamedType(type.expression, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
_this.ctx.markUnresolvedType(type.expression, namedType);
_this.ctx.markUnresolvedType(type.expression, namedType.name);
return namedType;

@@ -410,13 +530,8 @@ });

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

@@ -427,4 +542,3 @@ if (argsType.kind === ts.SyntaxKind.NeverKeyword) {

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

@@ -493,13 +607,10 @@ var defaults = null;

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

@@ -629,4 +740,3 @@ var type = this.collectType(node.type);

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

@@ -646,4 +756,3 @@ var id = this.expectIdentifier(node.name);

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

@@ -654,3 +763,7 @@ var type = this.collectType(node.type);

return null;
var args = this.collectArgs(node);
var args = null;
var argsParam = node.parameters[0];
if (argsParam != null) {
args = this.collectArgs(argsParam);
}
var description = this.collectDescription(node.name);

@@ -681,4 +794,3 @@ var id = this.expectIdentifier(node.name);

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

@@ -785,8 +897,6 @@ var doc = symbol.getDocumentationComment(this.ctx.checker);

else if (node.kind === ts.SyntaxKind.NumberKeyword) {
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."));
return null;
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."));
}
else if (ts.isTypeLiteralNode(node)) {
this.report(node, "Unexpected type literal. You may want to define a named GraphQL type elsewhere and reference it here.");
return null;
return this.report(node, "Unexpected type literal. You may want to define a named GraphQL type elsewhere and reference it here.");
}

@@ -805,4 +915,3 @@ // TODO: Better error message. This is okay if it's a type reference, but everything else is not.

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

@@ -818,4 +927,3 @@ var type = this.collectType(node.typeArguments[0]);

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

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

var namedType = this.gqlNamedType(node, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
this.ctx.markUnresolvedType(node.typeName, namedType);
this.ctx.markUnresolvedType(node.typeName, namedType.name);
return this.gqlNonNullType(node, namedType);

@@ -855,4 +963,3 @@ }

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

@@ -870,3 +977,3 @@ Extractor.prototype.findTag = function (node, tagName) {

Extractor.prototype.handleErrorBubbling = function (type) {
if (this.buildOptions.nullableByDefault) {
if (this.configOptions.nullableByDefault) {
return this.gqlNullableType(type);

@@ -881,2 +988,8 @@ }

};
Extractor.prototype.exportDirective = function (nameNode, filename, functionName) {
return this.gqlConstDirective(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_DIRECTIVE), [
this.gqlConstArgument(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_FILENAME_ARG), this.gqlString(nameNode, filename)),
this.gqlConstArgument(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_FUNCTION_NAME_ARG), this.gqlString(nameNode, functionName)),
]);
};
/** GraphQL AST node helper methods */

@@ -883,0 +996,0 @@ Extractor.prototype.gqlName = function (node, value) {

import { GraphQLSchema } from "graphql";
import { BuildOptions } from "./lib";
import * as ts from "typescript";
import { GratsOptions } from "./lib";
export * from "./Types";
export * from "./lib";
export declare function buildSchema(options: BuildOptions): GraphQLSchema;
type RuntimeOptions = {
emitSchemaFile?: string;
};
export declare function extractGratsSchemaAtRuntime(runtimeOptions: RuntimeOptions): GraphQLSchema;
export declare function buildSchemaFromSDL(sdlFilePath: string): GraphQLSchema;
export declare function gratsOptionsFromTsConfig(tsConfig: ts.ParsedCommandLine): GratsOptions;
export declare function getParsedTsConfig(): ts.ParsedCommandLine;

@@ -17,6 +17,7 @@ "use strict";

exports.__esModule = true;
exports.buildSchemaFromSDL = exports.buildSchema = void 0;
exports.getParsedTsConfig = exports.gratsOptionsFromTsConfig = exports.buildSchemaFromSDL = exports.extractGratsSchemaAtRuntime = void 0;
var graphql_1 = require("graphql");
var utils_1 = require("@graphql-tools/utils");
var fs = require("fs");
var ts = require("typescript");
var lib_1 = require("./lib");

@@ -28,5 +29,6 @@ __exportStar(require("./Types"), exports);

// message.
function buildSchema(options) {
function extractGratsSchemaAtRuntime(runtimeOptions) {
var _a;
var schemaResult = (0, lib_1.buildSchemaResult)(options);
var parsedTsConfig = getParsedTsConfig();
var schemaResult = (0, lib_1.buildSchemaResult)(gratsOptionsFromTsConfig(parsedTsConfig));
if (schemaResult.kind === "ERROR") {

@@ -37,6 +39,6 @@ console.error(schemaResult.err.formatDiagnosticsWithColorAndContext());

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

@@ -46,3 +48,3 @@ }

}
exports.buildSchema = buildSchema;
exports.extractGratsSchemaAtRuntime = extractGratsSchemaAtRuntime;
function buildSchemaFromSDL(sdlFilePath) {

@@ -54,1 +56,26 @@ var sdl = fs.readFileSync(sdlFilePath, "utf8");

exports.buildSchemaFromSDL = buildSchemaFromSDL;
function gratsOptionsFromTsConfig(tsConfig) {
var _a;
var gratsConfig = (_a = tsConfig.raw.grats) !== null && _a !== void 0 ? _a : {};
return {
configOptions: gratsConfig,
files: tsConfig.fileNames,
tsCompilerOptions: tsConfig.options
};
}
exports.gratsOptionsFromTsConfig = gratsOptionsFromTsConfig;
// #FIXME: Report diagnostics instead of throwing!
function getParsedTsConfig() {
var configFile = ts.findConfigFile(process.cwd(), ts.sys.fileExists);
if (!configFile) {
throw new Error("Grats: Could not find tsconfig.json");
}
// https://github.com/microsoft/TypeScript/blob/46d70d79cd0dd00d19e4c617d6ebb25e9f3fc7de/src/compiler/watch.ts#L216
var configFileHost = ts.sys;
var parsed = ts.getParsedCommandLineOfConfigFile(configFile, undefined, configFileHost);
if (!parsed || parsed.errors.length > 0) {
throw new Error("Grats: Could not parse tsconfig.json");
}
return parsed;
}
exports.getParsedTsConfig = getParsedTsConfig;

@@ -5,9 +5,12 @@ import { DocumentNode, GraphQLSchema } from "graphql";

export { applyServerDirectives } from "./serverDirectives";
export type BuildOptions = {
files: string[];
emitSchemaFile?: string;
export type ConfigOptions = {
nullableByDefault?: boolean;
};
export declare function buildSchemaResult(options: BuildOptions): Result<GraphQLSchema, ReportableDiagnostics>;
export declare function buildSchemaResultWithHost(options: BuildOptions, compilerOptions: ts.CompilerOptions, compilerHost: ts.CompilerHost): Result<GraphQLSchema, ReportableDiagnostics>;
export declare function buildSchemaAst(options: BuildOptions, host: ts.CompilerHost, compilerOptions: ts.CompilerOptions): DiagnosticsResult<DocumentNode>;
export type GratsOptions = {
configOptions: ConfigOptions;
tsCompilerOptions: ts.CompilerOptions;
files: string[];
};
export declare function buildSchemaResult(options: GratsOptions): Result<GraphQLSchema, ReportableDiagnostics>;
export declare function buildSchemaResultWithHost(options: GratsOptions, compilerHost: ts.CompilerHost): Result<GraphQLSchema, ReportableDiagnostics>;
export declare function buildSchemaAst(options: GratsOptions, host: ts.CompilerHost): DiagnosticsResult<DocumentNode>;

@@ -39,11 +39,10 @@ "use strict";

// https://stackoverflow.com/a/66604532/1263117
var compilerOptions = { allowJs: true };
var compilerHost = ts.createCompilerHost(options,
var compilerHost = ts.createCompilerHost(options.tsCompilerOptions,
/* setParentNodes this is needed for finding jsDocs */
true);
return buildSchemaResultWithHost(options, compilerOptions, compilerHost);
return buildSchemaResultWithHost(options, compilerHost);
}
exports.buildSchemaResult = buildSchemaResult;
function buildSchemaResultWithHost(options, compilerOptions, compilerHost) {
var docResult = buildSchemaAst(options, compilerHost, compilerOptions);
function buildSchemaResultWithHost(options, compilerHost) {
var docResult = buildSchemaAst(options, compilerHost);
if (docResult.kind === "ERROR") {

@@ -63,4 +62,4 @@ return (0, DiagnosticError_1.err)(new DiagnosticError_1.ReportableDiagnostics(compilerHost, docResult.err));

exports.buildSchemaResultWithHost = buildSchemaResultWithHost;
function buildSchemaAst(options, host, compilerOptions) {
var docResult = definitionsFromFile(options, host, compilerOptions);
function buildSchemaAst(options, host) {
var docResult = definitionsFromFile(options, host);
if (docResult.kind === "ERROR")

@@ -82,5 +81,5 @@ return docResult;

exports.buildSchemaAst = buildSchemaAst;
function definitionsFromFile(options, host, compilerOptions) {
function definitionsFromFile(options, host) {
var e_1, _a, e_2, _b;
var program = ts.createProgram(options.files, compilerOptions, host);
var program = ts.createProgram(options.files, options.tsCompilerOptions, host);
var checker = program.getTypeChecker();

@@ -96,3 +95,3 @@ var ctx = new TypeContext_1.TypeContext(checker, host);

}
var extractor = new Extractor_1.Extractor(sourceFile, ctx, options);
var extractor = new Extractor_1.Extractor(sourceFile, ctx, options.configOptions);
var extractedResult = extractor.extract();

@@ -99,0 +98,0 @@ if (extractedResult.kind === "ERROR")

import { GraphQLSchema } from "graphql";
export declare const METHOD_NAME_DIRECTIVE = "methodName";
export declare const METHOD_NAME_ARG = "name";
export declare const EXPORTED_DIRECTIVE = "exported";
export declare const EXPORTED_FILENAME_ARG = "filename";
export declare const EXPORTED_FUNCTION_NAME_ARG = "functionName";
export declare const DIRECTIVES_AST: import("graphql").DocumentNode;
/**
* Field renaming directive:
*
* By default, when resolving a field, the server will take the schema field
* name, and look for a resolver/property by that name on the parent object.
* Since we support exposing a method/property under a different name, we need
* to modify that field's resolver to look for the implementation name rather
* than the schema name.
*/
export declare function applyServerDirectives(schema: GraphQLSchema): GraphQLSchema;

@@ -13,18 +13,49 @@ "use strict";

};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
exports.applyServerDirectives = exports.DIRECTIVES_AST = exports.METHOD_NAME_ARG = exports.METHOD_NAME_DIRECTIVE = void 0;
exports.applyServerDirectives = exports.DIRECTIVES_AST = exports.EXPORTED_FUNCTION_NAME_ARG = exports.EXPORTED_FILENAME_ARG = exports.EXPORTED_DIRECTIVE = exports.METHOD_NAME_ARG = exports.METHOD_NAME_DIRECTIVE = void 0;
var utils_1 = require("@graphql-tools/utils");
var graphql_1 = require("graphql");
var path_1 = require("path");
exports.METHOD_NAME_DIRECTIVE = "methodName";
exports.METHOD_NAME_ARG = "name";
exports.DIRECTIVES_AST = (0, graphql_1.parse)("\n directive @".concat(exports.METHOD_NAME_DIRECTIVE, "(").concat(exports.METHOD_NAME_ARG, ": String!) on FIELD_DEFINITION\n"));
/**
* Field renaming directive:
*
* By default, when resolving a field, the server will take the schema field
* name, and look for a resolver/property by that name on the parent object.
* Since we support exposing a method/property under a different name, we need
* to modify that field's resolver to look for the implementation name rather
* than the schema name.
*/
exports.EXPORTED_DIRECTIVE = "exported";
exports.EXPORTED_FILENAME_ARG = "filename";
exports.EXPORTED_FUNCTION_NAME_ARG = "functionName";
exports.DIRECTIVES_AST = (0, graphql_1.parse)("\n directive @".concat(exports.METHOD_NAME_DIRECTIVE, "(").concat(exports.METHOD_NAME_ARG, ": String!) on FIELD_DEFINITION\n directive @").concat(exports.EXPORTED_DIRECTIVE, "(\n ").concat(exports.EXPORTED_FILENAME_ARG, ": String!,\n ").concat(exports.EXPORTED_FUNCTION_NAME_ARG, ": String!\n ) on FIELD_DEFINITION\n"));
function applyServerDirectives(schema) {

@@ -36,11 +67,13 @@ var _a;

_a[utils_1.MapperKind.OBJECT_FIELD] = function (fieldConfig) {
var _a;
var _a, _b;
var newFieldConfig = fieldConfig;
var methodNameDirective = (_a = (0, utils_1.getDirective)(schema, fieldConfig, exports.METHOD_NAME_DIRECTIVE)) === null || _a === void 0 ? void 0 : _a[0];
if (!methodNameDirective)
return;
var _b = fieldConfig.resolve, resolve = _b === void 0 ? graphql_1.defaultFieldResolver : _b;
return __assign(__assign({}, fieldConfig), { resolve: function (source, args, context, info) {
var newInfo = __assign(__assign({}, info), { fieldName: methodNameDirective[exports.METHOD_NAME_ARG] });
return resolve(source, args, context, newInfo);
} });
if (methodNameDirective != null) {
newFieldConfig = applyMethodNameDirective(newFieldConfig, methodNameDirective);
}
var exportedDirective = (_b = (0, utils_1.getDirective)(schema, fieldConfig, exports.EXPORTED_DIRECTIVE)) === null || _b === void 0 ? void 0 : _b[0];
if (exportedDirective != null) {
newFieldConfig = applyExportDirective(newFieldConfig, exportedDirective);
}
return newFieldConfig;
},

@@ -50,1 +83,59 @@ _a));

exports.applyServerDirectives = applyServerDirectives;
/**
* Field renaming directive:
*
* By default, when resolving a field, the server will take the schema field
* name, and look for a resolver/property by that name on the parent object.
* Since we support exposing a method/property under a different name, we need
* to modify that field's resolver to look for the implementation name rather
* than the schema name.
*/
function applyMethodNameDirective(fieldConfig, methodNameDirective) {
var _a = fieldConfig.resolve, resolve = _a === void 0 ? graphql_1.defaultFieldResolver : _a;
return __assign(__assign({}, fieldConfig), { resolve: function (source, args, context, info) {
var newInfo = __assign(__assign({}, info), { fieldName: methodNameDirective[exports.METHOD_NAME_ARG] });
return resolve(source, args, context, newInfo);
} });
}
/**
* Export directive:
*
* By default, when resolving a field, the server will look for a resolver
* function on the parent object. This directive allows you to specify a
* module and function name to import and use as the resolver.
*/
function applyExportDirective(fieldConfig, methodNameDirective) {
// FIXME: This relies on the server being run with the same cwd as the build script.
// TODO: Does this work in the browser?
var filename = (0, path_1.resolve)(process.cwd(), methodNameDirective[exports.EXPORTED_FILENAME_ARG]);
var functionName = methodNameDirective[exports.EXPORTED_FUNCTION_NAME_ARG];
return __assign(__assign({}, fieldConfig), { resolve: function (source, args, context, info) {
return __awaiter(this, void 0, void 0, function () {
var mod, e_1, resolve;
return __generator(this, function (_a) {
var _b;
switch (_a.label) {
case 0:
mod = {};
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, (_b = filename, Promise.resolve().then(function () { return require(_b); }))];
case 2:
mod = _a.sent();
return [3 /*break*/, 4];
case 3:
e_1 = _a.sent();
console.error("Grats Error: Failed to import module `".concat(filename, "`. You may need to rerun Grats."));
throw e_1;
case 4:
resolve = mod[functionName];
if (typeof resolve !== "function") {
throw new Error("Grats Error: Expected `".concat(filename, "` to have a named export `").concat(functionName, "` that is a function, but it was `").concat(typeof resolve, "`. You may need to rerun Grats."));
}
return [2 /*return*/, resolve(source, args, context, info)];
}
});
});
} });
}

@@ -127,3 +127,8 @@ "use strict";

var files = ["".concat(fixturesDir, "/").concat(fileName), "src/Types.ts"];
var schemaResult = (0, lib_1.buildSchemaResult)(__assign({ files: files }, options));
var parsedOptions = {
tsCompilerOptions: {},
files: files,
configOptions: options
};
var schemaResult = (0, lib_1.buildSchemaResult)(parsedOptions);
if (schemaResult.kind === "ERROR") {

@@ -130,0 +135,0 @@ return schemaResult.err.formatDiagnosticsWithContext();

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

import { DocumentNode, NamedTypeNode } from "graphql";
import { DocumentNode, NameNode } from "graphql";
import * as ts from "typescript";

@@ -21,8 +21,8 @@ import { DiagnosticResult, DiagnosticsResult } from "./utils/DiagnosticError";

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

@@ -50,5 +50,6 @@ "use strict";

};
TypeContext.prototype.markUnresolvedType = function (node, namedType) {
TypeContext.prototype.markUnresolvedType = function (node, name) {
var symbol = this.checker.getSymbolAtLocation(node);
if (symbol == null) {
//
throw new Error("Could not resolve type reference. You probably have a TypeScript error.");

@@ -60,3 +61,3 @@ }

}
this._unresolvedTypes.set(namedType, symbol);
this._unresolvedTypes.set(name, symbol);
};

@@ -67,3 +68,3 @@ TypeContext.prototype.resolveTypes = function (doc) {

var newDoc = (0, graphql_1.visit)(doc, {
NamedType: function (t) {
Name: function (t) {
var namedTypeResult = _this.resolveNamedType(t);

@@ -82,14 +83,14 @@ if (namedTypeResult.kind === "ERROR") {

};
TypeContext.prototype.resolveNamedType = function (namedType) {
var symbol = this._unresolvedTypes.get(namedType);
TypeContext.prototype.resolveNamedType = function (unresolved) {
var symbol = this._unresolvedTypes.get(unresolved);
if (symbol == null) {
if (namedType.name.value === exports.UNRESOLVED_REFERENCE_NAME) {
if (unresolved.value === exports.UNRESOLVED_REFERENCE_NAME) {
// This is a logic error on our side.
throw new Error("Unexpected unresolved reference name.");
}
return (0, DiagnosticError_1.ok)(namedType);
return (0, DiagnosticError_1.ok)(unresolved);
}
var name = this._symbolToName.get(symbol);
if (name == null) {
if (namedType.loc == null) {
if (unresolved.loc == null) {
throw new Error("Expected namedType to have a location.");

@@ -99,10 +100,10 @@ }

messageText: "This type is not a valid GraphQL type. Did you mean to annotate it's definition with `/** @GQLType */` or `/** @GQLScalar */`?",
start: namedType.loc.start,
length: namedType.loc.end - namedType.loc.start,
start: unresolved.loc.start,
length: unresolved.loc.end - unresolved.loc.start,
category: ts.DiagnosticCategory.Error,
code: DiagnosticError_1.FAKE_ERROR_CODE,
file: ts.createSourceFile(namedType.loc.source.name, namedType.loc.source.body, ts.ScriptTarget.Latest)
file: ts.createSourceFile(unresolved.loc.source.name, unresolved.loc.source.body, ts.ScriptTarget.Latest)
});
}
return (0, DiagnosticError_1.ok)(__assign(__assign({}, namedType), { name: __assign(__assign({}, namedType.name), { value: name }) }));
return (0, DiagnosticError_1.ok)(__assign(__assign({}, unresolved), { value: name }));
};

@@ -109,0 +110,0 @@ return TypeContext;

{
"name": "grats",
"version": "0.0.0",
"version": "0.0.1",
"main": "dist/src/index.js",

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

"@graphql-tools/utils": "^9.2.1",
"glob": "^9.2.1",
"graphql": "^16.6.0",

@@ -16,0 +15,0 @@ "typescript": "^4.9.5"

@@ -1,16 +0,19 @@

# ------- EXPERIMENTAL PRE ALPHA ------
# -=[ EXPERIMENTAL PRE ALPHA ]=-
**This is just a proof of concept right now. Everything is very thrown together and inefficient. If it encounters any AST nodes I haven't considered yet, it will simply error out.**
**This is currently a proof of concept. It won't yet work on any real projects.**
# TypeScript First GraphQL (Name to be determined)
# Grats: True code-first GraphQL for TypeScript
TypeScript First GraphQL is a library for deriving a GraphQL schema from
your lightly-annotated TypeScript code. The goal is to make your TypeScript
code, and its types, the source of truth for your GraphQL schema without
requiring decorators or other special syntax/APIs.
Grats is a tool for statically infering GraphQL schema from your vanilla
TypeScript code.
Opt classes and methods on your existing models into your GraphQL API by
annotating them with a simple comment. TypeScript First GraphQL will derive the
GraphQL schema for you via static analysis.
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.
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!
## Example

@@ -64,21 +67,70 @@

## CLI Usage
**Give it a try in the [online playground](https://capt.dev/grats-example)!**
Still very rough, but you can try it out with:
## Quick Start
*Note:* Globs will be evaluated relative to the current working directory.
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
git clone ...
pnpm install
pnpm cli <glob of files to analyze>
```sh
npm install express express-graphql grats
```
# Example: `pnpm cli "example-server/**/*.ts"`
**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");
```
## Configuration
Grats has a few configuration options. They can be set in your project's
`tsconfig.json` file:
```json
{
"grats": {
// Should all fields be typed as nullable in accordance with GraphQL best
// practices?
// https://graphql.org/learn/best-practices/#nullability
"nullableByDefault": true, // Default: true
},
"compilerOptions": {
// ... TypeScript config...
}
}
```
## API Usage
In order for TypeScript First GraphQL to be able to extract GraphQL schema from
your code, simply mark which classes and methods should be included in the schema by
marking them with special JSDoc tags such as `/** @GQLType */` or `/** @GQLField */`.
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 */`.

@@ -90,2 +142,14 @@ Any comment text preceding the JSDoc `@` tag will be used as that element's description.

The following JSDoc tags are supported:
* [`@GQLType`](#GQLtype)
* [`@GQLInterface`](#GQLinterface)
* [`@GQLField`](#GQLfield)
* [`@GQLUnion`](#GQLunion)
* [`@GQLScalar`](#GQLscalar)
* [`@GQLEnum`](#GQLenum)
* [`@GQLInput`](#GQLinput)
* [`@GQLExtendType`](#GQLExtendType)
### @GQLType

@@ -96,2 +160,3 @@

* Class declaration
* Interface declaration

@@ -109,2 +174,13 @@ ```ts

```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 interface name>
*/
interface MyInterface {
/** @GQLField */
someField: string;
}
```
### @GQLInterface

@@ -154,3 +230,4 @@

**Note**: TypeScript First GraphQL makes all fields nullable by default in keeping with [GraphQL best practices](https://graphql.org/learn/best-practices/#nullability). In the future, we should make this configurable.
**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`.

@@ -227,2 +304,16 @@ If you wish to define arguments for a field, define your argument types inline:

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 } from "grats";
/** @GQLType */
class Query {
/** @GQLField */
round(args: {float: Float}): Int {
return Math.round(args.float);
}
}
```
### @GQLEnum

@@ -263,3 +354,3 @@

We also support defining enums using a union of string literals, howerver there
We also support defining enums using a union of string literals, however there
are some limitations to this approach:

@@ -298,2 +389,45 @@

### @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

@@ -312,10 +446,15 @@

## Why would I _not_ want to use TypeScript First GraphQL?
## Why would I _not_ want to use Grats
Because TypeScript First GraphQL 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
TypeScript First GraphQL needs to be able to infer the type. For example, field
arguments and return values.
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?

@@ -340,3 +479,3 @@

the impression that they might have some runtime behavior. This is not the
case for TypeScript First GraphQL, which is purely a static analysis tool.
case for Grats, which is purely a static analysis tool.

@@ -354,2 +493,2 @@ Given these tradeoffs, we've decided to use comments instead of decorators.

* [ts2graphql](https://github.com/cevek/ts2graphql)
* [typegraphql-reflection-poc](https://github.com/MichalLytek/typegraphql-reflection-poc)
* [typegraphql-reflection-poc](https://github.com/MichalLytek/typegraphql-reflection-poc)