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

grats

Package Overview
Dependencies
Maintainers
0
Versions
253
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

to
0.0.0-main-7501b34a

dist/src/CodeActions.d.ts

46

dist/package.json
{
"name": "grats",
"version": "0.0.2",
"version": "0.0.32",
"main": "dist/src/index.js",

@@ -9,18 +9,21 @@ "bin": "dist/src/cli.js",

"files": [
"dist"
"dist",
"!dist/src/tests"
],
"scripts": {
"test": "ts-node --esm src/tests/test.ts",
"test": "ts-node src/tests/test.ts",
"integration-tests": "node src/tests/integration.mjs",
"build": "tsc --build",
"lint": "eslint src/**/*.ts"
"build": "rm -rf dist/ && tsc --build",
"format": "prettier . --write",
"lint": "eslint . && prettier . --check"
},
"dependencies": {
"@graphql-tools/utils": "^9.2.1",
"commander": "^10.0.0",
"graphql": "^16.6.0",
"typescript": "^4.9.5"
"graphql": "^16.9.0",
"typescript": "5.5.4",
"semver": "^7.5.4"
},
"devDependencies": {
"@types/node": "^18.14.6",
"@types/semver": "^7.5.6",
"@typescript-eslint/eslint-plugin": "^5.55.0",

@@ -32,2 +35,3 @@ "@typescript-eslint/parser": "^5.55.0",

"path-browserify": "^1.0.1",
"prettier": "^2.8.7",
"process": "^0.11.10",

@@ -38,3 +42,27 @@ "ts-node": "^10.9.1"

"trailingComma": "all"
}
},
"packageManager": "pnpm@8.12.0",
"engines": {
"node": ">=16 <=21"
},
"bugs": {
"url": "https://github.com/captbaritone/grats/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/captbaritone/grats.git"
},
"author": {
"name": "Jordan Eldredge",
"email": "jordan@jordaneldredge.com",
"url": "https://jordaneldredge.com"
},
"keywords": [
"graphql",
"typescript",
"resolvers",
"schema",
"code-first",
"implementation-first"
]
}

3

dist/src/cli.d.ts
#!/usr/bin/env node
export {};
import { Location } from "graphql";
export declare function formatLoc(loc: Location): string;

@@ -39,7 +39,8 @@ #!/usr/bin/env node

};
exports.__esModule = true;
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatLoc = formatLoc;
var E = require("./Errors");
var graphql_1 = require("graphql");
var _1 = require("./");
var lib_1 = require("./lib");
var utils_1 = require("@graphql-tools/utils");
var commander_1 = require("commander");

@@ -49,2 +50,7 @@ var fs_1 = require("fs");

var package_json_1 = require("../package.json");
var Locate_1 = require("./Locate");
var printSchema_1 = require("./printSchema");
var ts = require("typescript");
var DiagnosticError_1 = require("./utils/DiagnosticError");
var Result_1 = require("./utils/Result");
var program = new commander_1.Command();

@@ -55,37 +61,133 @@ program

.version(package_json_1.version)
.option("-o, --output <SCHEMA_FILE>", "Where to write the schema file. Defaults to stdout")
.option("--tsconfig <TSCONFIG>", "Path to tsconfig.json. Defaults to auto-detecting based on the current working directory")
.action(function (_a) {
var output = _a.output, tsconfig = _a.tsconfig;
return __awaiter(void 0, void 0, void 0, function () {
return __generator(this, function (_b) {
build(output, tsconfig);
return [2 /*return*/];
});
.option("--watch", "Watch for changes and rebuild schema files as needed")
.action(function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
var tsconfig = _b.tsconfig, watch = _b.watch;
return __generator(this, function (_c) {
if (watch) {
startWatchMode(tsconfig);
}
else {
runBuild(tsconfig);
}
return [2 /*return*/];
});
}); });
program
.command("locate")
.argument("<ENTITY>", "GraphQL entity to locate. E.g. `User` or `User.id`")
.option("--tsconfig <TSCONFIG>", "Path to tsconfig.json. Defaults to auto-detecting based on the current working directory")
.action(function (entity, _a) {
var tsconfig = _a.tsconfig;
var config = handleDiagnostics(getTsConfig(tsconfig)).config;
var schema = handleDiagnostics(buildSchemaAndDocResultForCli(config)).schema;
var loc = (0, Locate_1.locate)(schema, entity);
if (loc.kind === "ERROR") {
console.error(loc.err);
process.exit(1);
}
console.log(formatLoc(loc.value));
});
program.parse();
function build(output, tsconfig) {
if (tsconfig && !(0, fs_1.existsSync)(tsconfig)) {
console.error("Grats: Could not find tsconfig.json at `".concat(tsconfig, "`."));
process.exit(1);
/**
* Run the compiler in watch mode.
*/
function startWatchMode(tsconfig) {
var _a = handleDiagnostics(getTsConfig(tsconfig)), config = _a.config, configPath = _a.configPath;
var watchHost = ts.createWatchCompilerHost(configPath, {}, ts.sys, ts.createSemanticDiagnosticsBuilderProgram, function (diagnostic) { return reportDiagnostics([diagnostic]); }, function (diagnostic) { return reportDiagnostics([diagnostic]); });
watchHost.afterProgramCreate = function (program) {
// For now we just rebuild the schema on every change.
var schemaResult = (0, lib_1.extractSchemaAndDoc)(config, program.getProgram());
if (schemaResult.kind === "ERROR") {
reportDiagnostics(schemaResult.err);
return;
}
writeSchemaFilesAndReport(schemaResult.value, config, configPath);
};
ts.createWatchProgram(watchHost);
}
function isUserDefinedType(type) {
return type instanceof graphql_1.GraphQLObjectType && !type.name.startsWith("__");
}
/**
* Like `buildSchemaAndDocResult` but applies a few additional validations that
* are considered helpful for CLI usage, like warning if you have no types defined..
*/
function buildSchemaAndDocResultForCli(config) {
var result = (0, lib_1.buildSchemaAndDocResult)(config);
if (result.kind === "ERROR") {
return result;
}
var parsed = (0, _1.getParsedTsConfig)(tsconfig);
// FIXME: Validate config!
// https://github.com/tsconfig/bases
var schemaResult = (0, lib_1.buildSchemaResult)(parsed);
if (schemaResult.kind === "ERROR") {
console.error(schemaResult.err.formatDiagnosticsWithColorAndContext());
var types = Object.values(result.value.schema.getTypeMap());
if (!types.some(function (t) { return isUserDefinedType(t); })) {
return (0, Result_1.err)(DiagnosticError_1.ReportableDiagnostics.fromDiagnostics([
(0, DiagnosticError_1.locationlessErr)(E.noTypesDefined()),
]));
}
return result;
}
/**
* Run the compiler performing a single build.
*/
function runBuild(tsconfig) {
var _a = handleDiagnostics(getTsConfig(tsconfig)), config = _a.config, configPath = _a.configPath;
var schemaAndDoc = handleDiagnostics(buildSchemaAndDocResultForCli(config));
writeSchemaFilesAndReport(schemaAndDoc, config, configPath);
}
/**
* Serializes the SDL and TypeScript schema to disk and reports to the console.
*/
function writeSchemaFilesAndReport(schemaAndDoc, config, configPath) {
var schema = schemaAndDoc.schema, doc = schemaAndDoc.doc, resolvers = schemaAndDoc.resolvers;
var gratsConfig = config.raw.grats;
var dest = (0, path_1.resolve)((0, path_1.dirname)(configPath), gratsConfig.tsSchema);
var code = (0, printSchema_1.printExecutableSchema)(schema, resolvers, gratsConfig, dest);
(0, fs_1.writeFileSync)(dest, code);
console.error("Grats: Wrote TypeScript schema to `".concat(dest, "`."));
var schemaStr = (0, printSchema_1.printGratsSDL)(doc, gratsConfig);
var absOutput = (0, path_1.resolve)((0, path_1.dirname)(configPath), gratsConfig.graphqlSchema);
(0, fs_1.writeFileSync)(absOutput, schemaStr);
console.error("Grats: Wrote schema to `".concat(absOutput, "`."));
if (config.raw.grats.EXPERIMENTAL__emitMetadata) {
var absOutput_1 = (0, path_1.resolve)((0, path_1.dirname)(configPath), gratsConfig.graphqlSchema.replace(/\.graphql$/, ".json"));
(0, fs_1.writeFileSync)(absOutput_1, JSON.stringify(resolvers, null, 2));
console.error("Grats: Wrote resolver signatures to `".concat(absOutput_1, "`."));
}
}
/**
* Utility function to report diagnostics to the console.
*/
function reportDiagnostics(diagnostics) {
var reportable = DiagnosticError_1.ReportableDiagnostics.fromDiagnostics(diagnostics);
console.error(reportable.formatDiagnosticsWithColorAndContext());
}
/**
* Utility function to report diagnostics to the console.
*/
function handleDiagnostics(result) {
if (result.kind === "ERROR") {
console.error(result.err.formatDiagnosticsWithColorAndContext());
process.exit(1);
}
var schema = (0, graphql_1.lexicographicSortSchema)(schemaResult.value);
var schemaStr = (0, utils_1.printSchemaWithDirectives)(schema, { assumeValid: true });
if (output) {
var absOutput = (0, path_1.resolve)(process.cwd(), output);
(0, fs_1.writeFileSync)(absOutput, schemaStr);
console.error("Grats: Wrote schema to `".concat(absOutput, "`."));
return result.value;
}
// Locate and read the tsconfig.json file
function getTsConfig(tsconfig) {
var cwd = process.cwd();
var configPath = tsconfig || ts.findConfigFile(cwd, ts.sys.fileExists);
if (configPath == null) {
return (0, Result_1.err)(DiagnosticError_1.ReportableDiagnostics.fromDiagnostics([
(0, DiagnosticError_1.locationlessErr)(E.tsConfigNotFound(cwd)),
]));
}
else {
console.log(schemaStr);
var optionsResult = (0, _1.getParsedTsConfig)(configPath);
if (optionsResult.kind === "ERROR") {
return (0, Result_1.err)(optionsResult.err);
}
return (0, Result_1.ok)({ configPath: configPath, config: optionsResult.value });
}
// Format a location for printing to the console. Tools like VS Code and iTerm
// will automatically turn this into a clickable link.
function formatLoc(loc) {
return "".concat(loc.source.name, ":").concat(loc.startToken.line + 1, ":").concat(loc.startToken.column + 1);
}

@@ -1,7 +0,34 @@

import { DefinitionNode, FieldDefinitionNode, InputValueDefinitionNode, ListTypeNode, NamedTypeNode, Location as GraphQLLocation, NameNode, Token, TypeNode, NonNullTypeNode, StringValueNode, ConstValueNode, ConstDirectiveNode, ConstArgumentNode, EnumValueDefinitionNode, ConstObjectFieldNode, ConstObjectValueNode, ConstListValueNode } from "graphql";
import { NameNode, DefinitionNode } from "graphql";
import { DiagnosticsResult } from "./utils/DiagnosticError";
import * as ts from "typescript";
import { TypeContext } from "./TypeContext";
import { ConfigOptions } from "./lib";
type ArgDefaults = Map<string, ts.Expression>;
import { DeclarationDefinition, NameDefinition } from "./TypeContext";
export declare const LIBRARY_IMPORT_NAME = "grats";
export declare const LIBRARY_NAME = "Grats";
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 DIRECTIVE_TAG = "gqlDirective";
export declare const ANNOTATE_TAG = "gqlAnnotate";
export declare const QUERY_FIELD_TAG = "gqlQueryField";
export declare const MUTATION_FIELD_TAG = "gqlMutationField";
export declare const SUBSCRIPTION_FIELD_TAG = "gqlSubscriptionField";
export declare const CONTEXT_TAG = "gqlContext";
export declare const INFO_TAG = "gqlInfo";
export declare const IMPLEMENTS_TAG_DEPRECATED = "gqlImplements";
export declare const KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException";
export declare const ALL_TAGS: string[];
export declare const ONE_OF_TAG = "oneOf";
export declare const OPERATION_TYPES: Set<string>;
export type ExtractionSnapshot = {
readonly definitions: DefinitionNode[];
readonly unresolvedNames: Map<ts.EntityName, NameNode>;
readonly nameDefinitions: Map<ts.DeclarationStatement, NameDefinition>;
readonly implicitNameDefinitions: Map<DeclarationDefinition, ts.TypeReferenceNode>;
readonly typesWithTypename: Set<string>;
readonly interfaceDeclarations: Array<ts.InterfaceDeclaration>;
};
/**

@@ -17,83 +44,2 @@ * Extracts GraphQL definitions from TypeScript source code.

*/
export declare class Extractor {
definitions: DefinitionNode[];
sourceFile: ts.SourceFile;
ctx: TypeContext;
configOptions: ConfigOptions;
errors: ts.Diagnostic[];
constructor(sourceFile: ts.SourceFile, ctx: TypeContext, buildOptions: ConfigOptions);
extract(): DiagnosticsResult<DefinitionNode[]>;
extractType(node: ts.Node, tag: ts.JSDocTag): void;
extractScalar(node: ts.Node, tag: ts.JSDocTag): void;
extractInterface(node: ts.Node, tag: ts.JSDocTag): void;
extractEnum(node: ts.Node, tag: ts.JSDocTag): void;
extractInput(node: ts.Node, tag: ts.JSDocTag): void;
extractUnion(node: ts.Node, tag: ts.JSDocTag): void;
/** Error handling and location juggling */
report(node: ts.Node, message: string): null;
reportUnhandled(node: ts.Node, message: string): null;
diagnosticAnnotatedLocation(node: ts.Node): {
start: number;
length: number;
filepath: ts.SourceFile;
};
loc(node: ts.Node): GraphQLLocation;
gqlDummyToken(pos: number): Token;
/** TypeScript traversals */
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;
inputTypeAliasDeclaration(node: ts.TypeAliasDeclaration, tag: ts.JSDocTag): null | undefined;
collectInputFields(node: ts.TypeAliasDeclaration): Array<InputValueDefinitionNode> | null;
collectInputField(node: ts.PropertySignature): InputValueDefinitionNode | null;
typeClassDeclaration(node: ts.ClassDeclaration, tag: ts.JSDocTag): null | undefined;
typeInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): null | undefined;
typeTypeAliasDeclaration(node: ts.TypeAliasDeclaration, tag: ts.JSDocTag): null | undefined;
checkForTypenameProperty(node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeLiteralNode, expectedName: string): void;
isValidTypeNameProperty(member: ts.ClassElement | ts.TypeElement, expectedName: string): boolean;
isValidTypenamePropertyDeclaration(node: ts.PropertyDeclaration, expectedName: string): boolean;
isValidTypenamePropertySignature(node: ts.PropertySignature, expectedName: string): boolean;
isValidTypenamePropertyType(node: ts.TypeNode, expectedName: string): boolean;
collectInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<NamedTypeNode> | null;
interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): void;
collectFields(node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeLiteralNode): Array<FieldDefinitionNode>;
collectArgs(argsParam: ts.ParameterDeclaration): ReadonlyArray<InputValueDefinitionNode> | null;
collectArgDefaults(node: ts.ObjectBindingPattern): ArgDefaults;
collectConstValue(node: ts.Expression): ConstValueNode | null;
collectArrayLiteral(node: ts.ArrayLiteralExpression): ConstListValueNode | null;
cellectObjectLiteral(node: ts.ObjectLiteralExpression): ConstObjectValueNode | null;
collectObjectField(node: ts.ObjectLiteralElementLike): ConstObjectFieldNode | null;
collectArg(node: ts.TypeElement, defaults?: Map<string, ts.Expression> | null): InputValueDefinitionNode | null;
enumEnumDeclaration(node: ts.EnumDeclaration, tag: ts.JSDocTag): void;
enumTypeAliasDeclaration(node: ts.TypeAliasDeclaration, tag: ts.JSDocTag): void;
enumTypeAliasVariants(node: ts.TypeAliasDeclaration): EnumValueDefinitionNode[] | null;
collectEnumValues(node: ts.EnumDeclaration): ReadonlyArray<EnumValueDefinitionNode>;
entityName(node: ts.ClassDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.PropertyDeclaration | ts.InterfaceDeclaration | ts.PropertySignature | ts.EnumDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration, tag: ts.JSDocTag): NameNode | null;
methodDeclaration(node: ts.MethodDeclaration | ts.MethodSignature): FieldDefinitionNode | null;
collectMethodType(node: ts.TypeNode): TypeNode | null;
collectPropertyType(node: ts.TypeNode): TypeNode | null;
maybeUnwrapePromise(node: ts.TypeNode): ts.TypeNode | null;
collectDescription(node: ts.Node): StringValueNode | null;
collectDeprecated(node: ts.Node): ConstDirectiveNode | null;
property(node: ts.PropertyDeclaration | ts.PropertySignature): FieldDefinitionNode | null;
collectType(node: ts.TypeNode): TypeNode | null;
typeReference(node: ts.TypeReferenceNode): TypeNode | null;
isNullish(node: ts.Node): boolean;
expectIdentifier(node: ts.Node): ts.Identifier | null;
findTag(node: ts.Node, tagName: string): ts.JSDocTag | null;
handleErrorBubbling(parentNode: ts.Node, type: TypeNode): TypeNode;
methodNameDirective(nameNode: ts.Node, name: string): ConstDirectiveNode;
exportDirective(nameNode: ts.Node, filename: string, functionName: string): ConstDirectiveNode;
/** GraphQL AST node helper methods */
gqlName(node: ts.Node, value: string): NameNode;
gqlNamedType(node: ts.Node, value: string): NamedTypeNode;
gqlNonNullType(node: ts.Node, type: TypeNode): NonNullTypeNode;
gqlNullableType(type: TypeNode): NamedTypeNode | ListTypeNode;
gqlListType(node: ts.Node, type: TypeNode): ListTypeNode;
gqlConstArgument(node: ts.Node, name: NameNode, value: ConstValueNode): ConstArgumentNode;
gqlConstDirective(node: ts.Node, name: NameNode, args: ReadonlyArray<ConstArgumentNode>): ConstDirectiveNode;
gqlString(node: ts.Node, value: string): StringValueNode;
}
export {};
export declare function extract(sourceFile: ts.SourceFile): DiagnosticsResult<ExtractionSnapshot>;

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

import * as ts from "typescript";
export declare function getRelativeOutputPath(options: ts.ParsedCommandLine, sourceFile: ts.SourceFile): string;
export declare function relativePath(absolute: string): string;
export declare function resolveRelativePath(relativePath: string): string;
"use strict";
exports.__esModule = true;
exports.resolveRelativePath = exports.getRelativeOutputPath = void 0;
Object.defineProperty(exports, "__esModule", { value: true });
exports.relativePath = relativePath;
exports.resolveRelativePath = resolveRelativePath;
var path_1 = require("path");
var ts = require("typescript");
// Grats parses TypeScript files and finds resolvers. If the field resolver is a

@@ -13,19 +13,8 @@ // named export, Grats needs to be able to import that file during execution.

// step and the runtime can agree on. This path is that thing.
var gratsRoot = __dirname;
function getRelativeOutputPath(options, sourceFile) {
var fileNames = ts.getOutputFileNames(options, sourceFile.fileName, true);
// ts.getOutputFileNames returns a list of files that includes both the .d.ts
// and .js files.
var jsFileNames = fileNames.filter(function (fileName) { return fileName.endsWith(".js"); });
if (jsFileNames.length !== 1) {
throw new Error("Grats: Expected ts.getOutputFileNames to return exactly one `.js` file. " +
"Found ".concat(jsFileNames.length, "}. This is a bug in Grats. I'd appreciate it if ") +
"you could open an issue.");
}
return (0, path_1.relative)(gratsRoot, fileNames[0]);
var gratsRoot = (0, path_1.join)(__dirname, "../..");
function relativePath(absolute) {
return (0, path_1.relative)(gratsRoot, absolute);
}
exports.getRelativeOutputPath = getRelativeOutputPath;
function resolveRelativePath(relativePath) {
return (0, path_1.resolve)(gratsRoot, relativePath);
}
exports.resolveRelativePath = resolveRelativePath;

@@ -1,10 +0,9 @@

import { GraphQLSchema } from "graphql";
import * as ts from "typescript";
import { ParsedCommandLineGrats } from "./gratsConfig";
import { ReportableDiagnostics } from "./utils/DiagnosticError";
import { Result } from "./utils/Result";
export { printSDLWithoutMetadata } from "./printSchema";
export * from "./Types";
export * from "./lib";
type RuntimeOptions = {
emitSchemaFile?: string;
};
export declare function extractGratsSchemaAtRuntime(runtimeOptions: RuntimeOptions): GraphQLSchema;
export declare function buildSchemaFromSDL(sdl: string): GraphQLSchema;
export declare function getParsedTsConfig(configPath?: string): ts.ParsedCommandLine;
export { extract } from "./Extractor";
export { codegen } from "./codegen/schemaCodegen";
export declare function getParsedTsConfig(configFile: string): Result<ParsedCommandLineGrats, ReportableDiagnostics>;

@@ -16,51 +16,30 @@ "use strict";

};
exports.__esModule = true;
exports.getParsedTsConfig = exports.buildSchemaFromSDL = exports.extractGratsSchemaAtRuntime = void 0;
var graphql_1 = require("graphql");
var utils_1 = require("@graphql-tools/utils");
var fs = require("fs");
Object.defineProperty(exports, "__esModule", { value: true });
exports.codegen = exports.extract = exports.printSDLWithoutMetadata = void 0;
exports.getParsedTsConfig = getParsedTsConfig;
var ts = require("typescript");
var lib_1 = require("./lib");
var gratsConfig_1 = require("./gratsConfig");
var DiagnosticError_1 = require("./utils/DiagnosticError");
var Result_1 = require("./utils/Result");
var printSchema_1 = require("./printSchema");
Object.defineProperty(exports, "printSDLWithoutMetadata", { enumerable: true, get: function () { return printSchema_1.printSDLWithoutMetadata; } });
__exportStar(require("./Types"), exports);
__exportStar(require("./lib"), exports);
// Build an executable schema from a set of files. Note that if extraction
// fails, this function will exit the process and print a helpful error
// message.
function extractGratsSchemaAtRuntime(runtimeOptions) {
var _a;
var parsedTsConfig = getParsedTsConfig();
var schemaResult = (0, lib_1.buildSchemaResult)(parsedTsConfig);
if (schemaResult.kind === "ERROR") {
console.error(schemaResult.err.formatDiagnosticsWithColorAndContext());
process.exit(1);
}
var runtimeSchema = schemaResult.value;
if (runtimeOptions.emitSchemaFile) {
runtimeSchema = (0, graphql_1.lexicographicSortSchema)(runtimeSchema);
var sdl = (0, utils_1.printSchemaWithDirectives)(runtimeSchema, { assumeValid: true });
var filePath = (_a = runtimeOptions.emitSchemaFile) !== null && _a !== void 0 ? _a : "./schema.graphql";
fs.writeFileSync(filePath, sdl);
}
return runtimeSchema;
}
exports.extractGratsSchemaAtRuntime = extractGratsSchemaAtRuntime;
function buildSchemaFromSDL(sdl) {
var schema = (0, graphql_1.buildSchema)(sdl);
return (0, lib_1.applyServerDirectives)(schema);
}
exports.buildSchemaFromSDL = buildSchemaFromSDL;
// Used by the experimental TypeScript plugin
var Extractor_1 = require("./Extractor");
Object.defineProperty(exports, "extract", { enumerable: true, get: function () { return Extractor_1.extract; } });
var schemaCodegen_1 = require("./codegen/schemaCodegen");
Object.defineProperty(exports, "codegen", { enumerable: true, get: function () { return schemaCodegen_1.codegen; } });
// #FIXME: Report diagnostics instead of throwing!
function getParsedTsConfig(configPath) {
var configFile = configPath || ts.findConfigFile(process.cwd(), ts.sys.fileExists);
if (!configFile) {
throw new Error("Grats: Could not find tsconfig.json");
}
function getParsedTsConfig(configFile) {
// 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");
if (!parsed) {
throw new Error("Grats: Could not locate tsconfig.json");
}
return parsed;
if (parsed.errors.length > 0) {
return (0, Result_1.err)(DiagnosticError_1.ReportableDiagnostics.fromDiagnostics(parsed.errors));
}
return (0, Result_1.ok)((0, gratsConfig_1.validateGratsOptions)(parsed));
}
exports.getParsedTsConfig = getParsedTsConfig;

@@ -1,10 +0,19 @@

import { GraphQLSchema } from "graphql";
import { Result, ReportableDiagnostics } from "./utils/DiagnosticError";
import { DocumentNode, GraphQLSchema } from "graphql";
import { DiagnosticsWithoutLocationResult, ReportableDiagnostics } from "./utils/DiagnosticError";
import { Result } from "./utils/Result";
import * as ts from "typescript";
export { applyServerDirectives } from "./serverDirectives";
export type ConfigOptions = {
nullableByDefault?: boolean;
reportTypeScriptTypeErrors?: boolean;
import { ParsedCommandLineGrats } from "./gratsConfig";
import { Metadata } from "./metadata";
export { initTsPlugin } from "./tsPlugin/initTsPlugin";
export { GratsConfig } from "./gratsConfig";
export type SchemaAndDoc = {
schema: GraphQLSchema;
doc: DocumentNode;
resolvers: Metadata;
};
export declare function buildSchemaResult(options: ts.ParsedCommandLine): Result<GraphQLSchema, ReportableDiagnostics>;
export declare function buildSchemaResultWithHost(options: ts.ParsedCommandLine, compilerHost: ts.CompilerHost): Result<GraphQLSchema, ReportableDiagnostics>;
export declare function buildSchemaAndDocResult(options: ParsedCommandLineGrats): Result<SchemaAndDoc, ReportableDiagnostics>;
export declare function buildSchemaAndDocResultWithHost(options: ParsedCommandLineGrats, compilerHost: ts.CompilerHost): Result<SchemaAndDoc, ReportableDiagnostics>;
/**
* The core transformation pipeline of Grats.
*/
export declare function extractSchemaAndDoc(options: ParsedCommandLineGrats, program: ts.Program): DiagnosticsWithoutLocationResult<SchemaAndDoc>;
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __values = (this && this.__values) || function(o) {

@@ -35,16 +13,54 @@ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;

};
exports.__esModule = true;
exports.buildSchemaResultWithHost = exports.buildSchemaResult = exports.applyServerDirectives = void 0;
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.initTsPlugin = void 0;
exports.buildSchemaAndDocResult = buildSchemaAndDocResult;
exports.buildSchemaAndDocResultWithHost = buildSchemaAndDocResultWithHost;
exports.extractSchemaAndDoc = extractSchemaAndDoc;
var graphql_1 = require("graphql");
var DiagnosticError_1 = require("./utils/DiagnosticError");
var Result_1 = require("./utils/Result");
var Result_2 = require("./utils/Result");
var ts = require("typescript");
var Extractor_1 = require("./Extractor");
var TypeContext_1 = require("./TypeContext");
var validate_1 = require("graphql/validation/validate");
var serverDirectives_1 = require("./serverDirectives");
var serverDirectives_2 = require("./serverDirectives");
__createBinding(exports, serverDirectives_2, "applyServerDirectives");
var validateTypenames_1 = require("./validations/validateTypenames");
var snapshotsFromProgram_1 = require("./transforms/snapshotsFromProgram");
var validateMergedInterfaces_1 = require("./validations/validateMergedInterfaces");
var addInterfaceFields_1 = require("./transforms/addInterfaceFields");
var filterNonGqlInterfaces_1 = require("./transforms/filterNonGqlInterfaces");
var validateAsyncIterable_1 = require("./validations/validateAsyncIterable");
var applyDefaultNullability_1 = require("./transforms/applyDefaultNullability");
var mergeExtensions_1 = require("./transforms/mergeExtensions");
var sortSchemaAst_1 = require("./transforms/sortSchemaAst");
var validateDuplicateContextOrInfo_1 = require("./validations/validateDuplicateContextOrInfo");
var validateSemanticNullability_1 = require("./validations/validateSemanticNullability");
var resolveTypes_1 = require("./transforms/resolveTypes");
var resolveResolverParams_1 = require("./transforms/resolveResolverParams");
var customSpecValidations_1 = require("./validations/customSpecValidations");
var makeResolverSignature_1 = require("./transforms/makeResolverSignature");
var addImplicitRootTypes_1 = require("./transforms/addImplicitRootTypes");
var validateDirectiveArguments_1 = require("./validations/validateDirectiveArguments");
// Export the TypeScript plugin implementation used by
// grats-ts-plugin
var initTsPlugin_1 = require("./tsPlugin/initTsPlugin");
Object.defineProperty(exports, "initTsPlugin", { enumerable: true, get: function () { return initTsPlugin_1.initTsPlugin; } });
// Construct a schema, using GraphQL schema language
// Exported for tests that want to intercept diagnostic errors.
function buildSchemaResult(options) {
function buildSchemaAndDocResult(options) {
// https://stackoverflow.com/a/66604532/1263117

@@ -54,136 +70,147 @@ var compilerHost = ts.createCompilerHost(options.options,

true);
return buildSchemaResultWithHost(options, compilerHost);
return buildSchemaAndDocResultWithHost(options, compilerHost);
}
exports.buildSchemaResult = buildSchemaResult;
function buildSchemaResultWithHost(options, compilerHost) {
var gratsOptions = parseGratsOptions(options);
var schemaResult = extractSchema(options, gratsOptions, compilerHost);
if (schemaResult.kind === "ERROR") {
return (0, DiagnosticError_1.err)(new DiagnosticError_1.ReportableDiagnostics(compilerHost, schemaResult.err));
}
return (0, DiagnosticError_1.ok)((0, serverDirectives_1.applyServerDirectives)(schemaResult.value));
function buildSchemaAndDocResultWithHost(options, compilerHost) {
var program = ts.createProgram(options.fileNames, options.options, compilerHost);
return new Result_1.ResultPipe(extractSchemaAndDoc(options, program))
.mapErr(function (e) { return new DiagnosticError_1.ReportableDiagnostics(compilerHost, e); })
.result();
}
exports.buildSchemaResultWithHost = buildSchemaResultWithHost;
// TODO: Make this return diagnostics
function parseGratsOptions(options) {
var _a, _b;
var gratsOptions = __assign({}, ((_b = (_a = options.raw) === null || _a === void 0 ? void 0 : _a.grats) !== null && _b !== void 0 ? _b : {}));
if (gratsOptions.nullableByDefault === undefined) {
gratsOptions.nullableByDefault = true;
/**
* The core transformation pipeline of Grats.
*/
function extractSchemaAndDoc(options, program) {
return new Result_1.ResultPipe((0, snapshotsFromProgram_1.extractSnapshotsFromProgram)(program, options))
.map(function (snapshots) { return combineSnapshots(snapshots); })
.andThen(function (snapshot) {
var typesWithTypename = snapshot.typesWithTypename;
var config = options.raw.grats;
var checker = program.getTypeChecker();
var ctxResult = TypeContext_1.TypeContext.fromSnapshot(checker, snapshot);
if (ctxResult.kind === "ERROR") {
return ctxResult;
}
var ctx = ctxResult.value;
// Collect validation errors
var validationResult = (0, Result_1.concatResults)((0, validateMergedInterfaces_1.validateMergedInterfaces)(checker, snapshot.interfaceDeclarations), (0, validateDuplicateContextOrInfo_1.validateDuplicateContextOrInfo)(ctx));
var docResult = new Result_1.ResultPipe(validationResult)
// Filter out any `implements` clauses that are not GraphQL interfaces.
.map(function () { return (0, filterNonGqlInterfaces_1.filterNonGqlInterfaces)(ctx, snapshot.definitions); })
.andThen(function (definitions) { return (0, resolveResolverParams_1.resolveResolverParams)(ctx, definitions); })
.andThen(function (definitions) { return (0, resolveTypes_1.resolveTypes)(ctx, definitions); })
// If you define a field on an interface using the functional style, we need to add
// that field to each concrete type as well. This must be done after all types are created,
// but before we validate the schema.
.andThen(function (definitions) { return (0, addInterfaceFields_1.addInterfaceFields)(ctx, definitions); })
// Convert the definitions into a DocumentNode
.map(function (definitions) { return ({ kind: graphql_1.Kind.DOCUMENT, definitions: definitions }); })
// Ensure all subscription fields return an AsyncIterable.
.andThen(function (doc) { return (0, validateAsyncIterable_1.validateAsyncIterable)(doc); })
// Apply default nullability to fields and arguments, and detect any misuse of
// `@killsParentOnException`.
.andThen(function (doc) { return (0, applyDefaultNullability_1.applyDefaultNullability)(doc, config); })
// Ensure we have Query/Mutation/Subscription types if they've been extended with
// `@gqlQueryField` and friends.
.map(function (doc) { return (0, addImplicitRootTypes_1.addImplicitRootTypes)(doc); })
// Merge any `extend` definitions into their base definitions.
.map(function (doc) { return (0, mergeExtensions_1.mergeExtensions)(doc); })
// Perform custom validations that reimplement spec validation rules
// with more tailored error messages.
.andThen(function (doc) { return (0, customSpecValidations_1.customSpecValidations)(doc); })
// Sort the definitions in the document to ensure a stable output.
.map(function (doc) { return (0, sortSchemaAst_1.sortSchemaAst)(doc); })
.andThen(function (doc) { return specValidateSDL(doc); })
.result();
if (docResult.kind === "ERROR") {
return docResult;
}
var doc = docResult.value;
var resolvers = (0, makeResolverSignature_1.makeResolverSignature)(doc);
// Build and validate the schema with regards to the GraphQL spec.
return (new Result_1.ResultPipe(buildSchema(doc))
// Apply the "Type Validation" sub-sections of the specification's
// "Type System" section.
.andThen(function (schema) { return specSchemaValidation(schema); })
// The above spec validation fails to catch type errors in directive
// arguments, so Grats checks these manually.
.andThen(function (schema) { return (0, validateDirectiveArguments_1.validateDirectiveArguments)(schema, doc); })
// Ensure that every type which implements an interface or is a member of a
// union has a __typename field.
.andThen(function (schema) { return (0, validateTypenames_1.validateTypenames)(schema, typesWithTypename); })
// Validate that semantic nullability directives are not in conflict
// with type nullability.
.andThen(function (schema) { return (0, validateSemanticNullability_1.validateSemanticNullability)(schema, config); })
// Combine the schema and document into a single result.
.map(function (schema) { return ({ schema: schema, doc: doc, resolvers: resolvers }); })
.result());
})
.result();
}
function buildSchema(doc) {
return (0, Result_2.ok)((0, graphql_1.buildASTSchema)(doc, { assumeValidSDL: true }));
}
function specValidateSDL(doc) {
// TODO: Currently this does not detect definitions that shadow builtins
// (`String`, `Int`, etc). However, if we pass a second param (extending an
// existing schema) we do! So, we should find a way to validate that we don't
// shadow builtins.
return asDiagnostics(doc, validate_1.validateSDL);
}
function specSchemaValidation(schema) {
return asDiagnostics(schema, graphql_1.validateSchema);
}
// Utility to map GraphQL validation errors to a Result of
function asDiagnostics(value, validate) {
var validationErrors = validate(value).filter(
// FIXME: Handle case where query is not defined (no location)
function (e) { return e.source && e.locations && e.positions; });
if (validationErrors.length > 0) {
return (0, Result_2.err)(validationErrors.map(DiagnosticError_1.graphQlErrorToDiagnostic));
}
else if (typeof gratsOptions.nullableByDefault !== "boolean") {
throw new Error("Grats: The Grats config option `nullableByDefault` must be a boolean if provided.");
}
if (gratsOptions.reportTypeScriptTypeErrors === undefined) {
gratsOptions.reportTypeScriptTypeErrors = false;
}
else if (typeof gratsOptions.reportTypeScriptTypeErrors !== "boolean") {
throw new Error("Grats: The Grats config option `reportTypeScriptTypeErrors` must be a boolean if provided");
}
// FIXME: Check for unknown options
return gratsOptions;
return (0, Result_2.ok)(value);
}
function extractSchema(options, gratsOptions, host) {
var e_1, _a, e_2, _b;
var program = ts.createProgram(options.fileNames, options.options, host);
var checker = program.getTypeChecker();
var ctx = new TypeContext_1.TypeContext(options, checker, host);
var definitions = Array.from(serverDirectives_1.DIRECTIVES_AST.definitions);
// Given a list of snapshots, merge them into a single snapshot.
function combineSnapshots(snapshots) {
var e_1, _a, e_2, _b, e_3, _c, e_4, _d, e_5, _e, e_6, _f, e_7, _g;
var result = {
definitions: [],
nameDefinitions: new Map(),
implicitNameDefinitions: new Map(),
unresolvedNames: new Map(),
typesWithTypename: new Set(),
interfaceDeclarations: [],
};
try {
for (var _c = __values(program.getSourceFiles()), _d = _c.next(); !_d.done; _d = _c.next()) {
var sourceFile = _d.value;
// If the file doesn't contain any GraphQL definitions, skip it.
if (!/@gql/i.test(sourceFile.text)) {
continue;
}
if (gratsOptions.reportTypeScriptTypeErrors) {
// If the user asked for us to report TypeScript errors, then we'll report them.
var typeErrors = ts.getPreEmitDiagnostics(program, sourceFile);
if (typeErrors.length > 0) {
return (0, DiagnosticError_1.err)(typeErrors);
for (var snapshots_1 = __values(snapshots), snapshots_1_1 = snapshots_1.next(); !snapshots_1_1.done; snapshots_1_1 = snapshots_1.next()) {
var snapshot = snapshots_1_1.value;
try {
for (var _h = (e_2 = void 0, __values(snapshot.definitions)), _j = _h.next(); !_j.done; _j = _h.next()) {
var definition = _j.value;
result.definitions.push(definition);
}
}
else {
// Otherwise, we will only report syntax errors, since they will prevent us from
// extracting any GraphQL definitions.
var syntaxErrors = program.getSyntacticDiagnostics(sourceFile);
if (syntaxErrors.length > 0) {
// It's not very helpful to report multiple syntax errors, so just report
// the first one.
return (0, DiagnosticError_1.err)([syntaxErrors[0]]);
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_j && !_j.done && (_b = _h.return)) _b.call(_h);
}
finally { if (e_2) throw e_2.error; }
}
var extractor = new Extractor_1.Extractor(sourceFile, ctx, gratsOptions);
var extractedResult = extractor.extract();
if (extractedResult.kind === "ERROR")
return extractedResult;
try {
for (var _e = (e_2 = void 0, __values(extractedResult.value)), _f = _e.next(); !_f.done; _f = _e.next()) {
var definition = _f.value;
definitions.push(definition);
for (var _k = (e_3 = void 0, __values(snapshot.nameDefinitions)), _l = _k.next(); !_l.done; _l = _k.next()) {
var _m = __read(_l.value, 2), node = _m[0], definition = _m[1];
result.nameDefinitions.set(node, definition);
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_f && !_f.done && (_b = _e["return"])) _b.call(_e);
if (_l && !_l.done && (_c = _k.return)) _c.call(_k);
}
finally { if (e_2) throw e_2.error; }
finally { if (e_3) throw e_3.error; }
}
}
}
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; }
}
var docResult = ctx.resolveTypes({ kind: graphql_1.Kind.DOCUMENT, definitions: definitions });
if (docResult.kind === "ERROR")
return docResult;
var doc = docResult.value;
// TODO: Currently this does not detect definitions that shadow builtins
// (`String`, `Int`, etc). However, if we pass a second param (extending an
// existing schema) we do! So, we should find a way to validate that we don't
// shadow builtins.
var validationErrors = (0, validate_1.validateSDL)(doc).map(function (e) {
return (0, DiagnosticError_1.graphQlErrorToDiagnostic)(e);
});
if (validationErrors.length > 0) {
return (0, DiagnosticError_1.err)(validationErrors);
}
var schema = (0, graphql_1.buildASTSchema)(doc, { assumeValidSDL: true });
var diagnostics = (0, graphql_1.validateSchema)(schema)
// FIXME: Handle case where query is not defined (no location)
.filter(function (e) { return e.source && e.locations && e.positions; })
.map(function (e) { return (0, DiagnosticError_1.graphQlErrorToDiagnostic)(e); });
if (diagnostics.length > 0) {
return (0, DiagnosticError_1.err)(diagnostics);
}
var typenameDiagnostics = validateTypename(schema, ctx);
if (typenameDiagnostics.length > 0)
return (0, DiagnosticError_1.err)(typenameDiagnostics);
return (0, DiagnosticError_1.ok)(schema);
}
function validateTypename(schema, ctx) {
var e_3, _a, e_4, _b;
var _c, _d;
var typenameDiagnostics = [];
var abstractTypes = Object.values(schema.getTypeMap()).filter(graphql_1.isAbstractType);
try {
for (var abstractTypes_1 = __values(abstractTypes), abstractTypes_1_1 = abstractTypes_1.next(); !abstractTypes_1_1.done; abstractTypes_1_1 = abstractTypes_1.next()) {
var type = abstractTypes_1_1.value;
// TODO: If we already implement resolveType, we don't need to check implementors
var typeImplementors = schema.getPossibleTypes(type).filter(graphql_1.isType);
try {
for (var typeImplementors_1 = (e_4 = void 0, __values(typeImplementors)), typeImplementors_1_1 = typeImplementors_1.next(); !typeImplementors_1_1.done; typeImplementors_1_1 = typeImplementors_1.next()) {
var implementor = typeImplementors_1_1.value;
if (!ctx.hasTypename.has(implementor.name)) {
var loc = (_d = (_c = implementor.astNode) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.loc;
if (loc == null) {
throw new Error("Grats expected the parsed type `".concat(implementor.name, "` to have location information. This is a bug in Grats. Please report it."));
}
typenameDiagnostics.push((0, DiagnosticError_1.diagnosticAtGraphQLLocation)("Missing __typename on `".concat(implementor.name, "`. The type `").concat(type.name, "` is used in a union or interface, so it must have a `__typename` field."), loc));
}
for (var _o = (e_4 = void 0, __values(snapshot.unresolvedNames)), _p = _o.next(); !_p.done; _p = _o.next()) {
var _q = __read(_p.value, 2), node = _q[0], typeName = _q[1];
result.unresolvedNames.set(node, typeName);
}

@@ -194,16 +221,55 @@ }

try {
if (typeImplementors_1_1 && !typeImplementors_1_1.done && (_b = typeImplementors_1["return"])) _b.call(typeImplementors_1);
if (_p && !_p.done && (_d = _o.return)) _d.call(_o);
}
finally { if (e_4) throw e_4.error; }
}
try {
for (var _r = (e_5 = void 0, __values(snapshot.implicitNameDefinitions)), _s = _r.next(); !_s.done; _s = _r.next()) {
var _t = __read(_s.value, 2), node = _t[0], definition = _t[1];
result.implicitNameDefinitions.set(node, definition);
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (_s && !_s.done && (_e = _r.return)) _e.call(_r);
}
finally { if (e_5) throw e_5.error; }
}
try {
for (var _u = (e_6 = void 0, __values(snapshot.typesWithTypename)), _v = _u.next(); !_v.done; _v = _u.next()) {
var typeName = _v.value;
result.typesWithTypename.add(typeName);
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (_v && !_v.done && (_f = _u.return)) _f.call(_u);
}
finally { if (e_6) throw e_6.error; }
}
try {
for (var _w = (e_7 = void 0, __values(snapshot.interfaceDeclarations)), _x = _w.next(); !_x.done; _x = _w.next()) {
var interfaceDeclaration = _x.value;
result.interfaceDeclarations.push(interfaceDeclaration);
}
}
catch (e_7_1) { e_7 = { error: e_7_1 }; }
finally {
try {
if (_x && !_x.done && (_g = _w.return)) _g.call(_w);
}
finally { if (e_7) throw e_7.error; }
}
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (abstractTypes_1_1 && !abstractTypes_1_1.done && (_a = abstractTypes_1["return"])) _a.call(abstractTypes_1);
if (snapshots_1_1 && !snapshots_1_1.done && (_a = snapshots_1.return)) _a.call(snapshots_1);
}
finally { if (e_3) throw e_3.error; }
finally { if (e_1) throw e_1.error; }
}
return typenameDiagnostics;
return result;
}

@@ -1,5 +0,20 @@

import { DocumentNode, NameNode } from "graphql";
import { InputObjectTypeDefinitionNode, InterfaceTypeDefinitionNode, NameNode, ObjectTypeDefinitionNode, UnionTypeDefinitionNode } from "graphql";
import * as ts from "typescript";
import { DiagnosticResult, DiagnosticsResult } from "./utils/DiagnosticError";
import { ExtractionSnapshot } from "./Extractor";
import { ResolverArgument } from "./resolverSignature";
export declare const UNRESOLVED_REFERENCE_NAME = "__UNRESOLVED_REFERENCE__";
export type DerivedResolverDefinition = {
name: NameNode;
path: string;
exportName: string | null;
args: ResolverArgument[];
kind: "DERIVED_CONTEXT";
};
export type NameDefinition = {
name: NameNode;
kind: "TYPE" | "INTERFACE" | "UNION" | "SCALAR" | "INPUT_OBJECT" | "ENUM" | "CONTEXT" | "INFO";
};
export type DeclarationDefinition = NameDefinition | DerivedResolverDefinition;
type TsIdentifier = number;
/**

@@ -19,15 +34,21 @@ * Used to track TypeScript references.

checker: ts.TypeChecker;
host: ts.CompilerHost;
_options: ts.ParsedCommandLine;
_symbolToName: Map<ts.Symbol, string>;
_unresolvedTypes: Map<NameNode, ts.Symbol>;
hasTypename: Set<string>;
constructor(options: ts.ParsedCommandLine, checker: ts.TypeChecker, host: ts.CompilerHost);
recordTypeName(node: ts.Node, name: string): void;
recordHasTypenameField(name: string): void;
markUnresolvedType(node: ts.Node, name: NameNode): void;
resolveTypes(doc: DocumentNode): DiagnosticsResult<DocumentNode>;
resolveNamedType(unresolved: NameNode): DiagnosticResult<NameNode>;
validateInterfaceImplementorsHaveTypenameField(): DiagnosticResult<null>;
getDestFilePath(sourceFile: ts.SourceFile): string;
_declarationToDefinition: Map<ts.Declaration, DeclarationDefinition>;
_unresolvedNodes: Map<TsIdentifier, ts.EntityName>;
_idToDeclaration: Map<TsIdentifier, ts.Declaration>;
static fromSnapshot(checker: ts.TypeChecker, snapshot: ExtractionSnapshot): DiagnosticsResult<TypeContext>;
constructor(checker: ts.TypeChecker);
private _recordDeclaration;
private _markUnresolvedType;
allDefinitions(): Iterable<DeclarationDefinition>;
findSymbolDeclaration(startSymbol: ts.Symbol): ts.Declaration | null;
private resolveSymbol;
resolveUnresolvedNamedType(unresolved: NameNode): DiagnosticResult<NameNode>;
unresolvedNameIsGraphQL(unresolved: NameNode): boolean;
gqlNameDefinitionForGqlName(nameNode: NameNode): DiagnosticResult<DeclarationDefinition>;
gqlNameForTsName(node: ts.EntityName): DiagnosticResult<string>;
private maybeTsDeclarationForTsName;
tsDeclarationForTsName(node: ts.EntityName): DiagnosticResult<ts.Declaration>;
tsDeclarationForGqlDefinition(definition: ObjectTypeDefinitionNode | UnionTypeDefinitionNode | InputObjectTypeDefinitionNode | InterfaceTypeDefinitionNode): ts.Declaration;
getEntityName(name: NameNode): ts.EntityName | null;
}
export {};

@@ -13,8 +13,35 @@ "use strict";

};
exports.__esModule = true;
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.");
};
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeContext = exports.UNRESOLVED_REFERENCE_NAME = void 0;
var graphql_1 = require("graphql");
var ts = require("typescript");
var DiagnosticError_1 = require("./utils/DiagnosticError");
var gratsRoot_1 = require("./gratsRoot");
var Result_1 = require("./utils/Result");
var E = require("./Errors");
exports.UNRESOLVED_REFERENCE_NAME = "__UNRESOLVED_REFERENCE__";

@@ -34,88 +61,205 @@ /**

var TypeContext = /** @class */ (function () {
function TypeContext(options, checker, host) {
this._symbolToName = new Map();
this._unresolvedTypes = new Map();
this.hasTypename = new Set();
this._options = options;
function TypeContext(checker) {
this._declarationToDefinition = new Map();
this._unresolvedNodes = new Map();
this._idToDeclaration = new Map();
this.checker = checker;
this.host = host;
}
TypeContext.prototype.recordTypeName = function (node, name) {
var symbol = this.checker.getSymbolAtLocation(node);
if (symbol == null) {
// FIXME: Make this a diagnostic
throw new Error("Could not resolve type reference. You probably have a TypeScript error.");
TypeContext.fromSnapshot = function (checker, snapshot) {
var e_1, _a, e_2, _b, e_3, _c;
var errors = [];
var self = new TypeContext(checker);
try {
for (var _d = __values(snapshot.unresolvedNames), _e = _d.next(); !_e.done; _e = _d.next()) {
var _f = __read(_e.value, 2), node = _f[0], typeName = _f[1];
self._markUnresolvedType(node, typeName);
}
}
if (this._symbolToName.has(symbol)) {
// Ensure we never try to record the same name twice.
throw new Error("Unexpected double recording of typename.");
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
}
finally { if (e_1) throw e_1.error; }
}
this._symbolToName.set(symbol, name);
};
TypeContext.prototype.recordHasTypenameField = function (name) {
this.hasTypename.add(name);
};
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.");
try {
for (var _g = __values(snapshot.nameDefinitions), _h = _g.next(); !_h.done; _h = _g.next()) {
var _j = __read(_h.value, 2), node = _j[0], definition = _j[1];
self._recordDeclaration(node, definition);
}
}
if (symbol.flags & ts.SymbolFlags.Alias) {
// Follow any aliases to get the real type declaration.
symbol = this.checker.getAliasedSymbol(symbol);
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_h && !_h.done && (_b = _g.return)) _b.call(_g);
}
finally { if (e_2) throw e_2.error; }
}
this._unresolvedTypes.set(name, symbol);
};
TypeContext.prototype.resolveTypes = function (doc) {
var _this = this;
var errors = [];
var newDoc = (0, graphql_1.visit)(doc, {
Name: function (t) {
var namedTypeResult = _this.resolveNamedType(t);
if (namedTypeResult.kind === "ERROR") {
errors.push(namedTypeResult.err);
return t;
try {
for (var _k = __values(snapshot.implicitNameDefinitions), _l = _k.next(); !_l.done; _l = _k.next()) {
var _m = __read(_l.value, 2), definition = _m[0], reference = _m[1];
var declaration = self.maybeTsDeclarationForTsName(reference.typeName);
if (declaration == null) {
errors.push((0, DiagnosticError_1.tsErr)(reference.typeName, E.unresolvedTypeReference()));
continue;
}
return namedTypeResult.value;
var existing = self._declarationToDefinition.get(declaration);
if (existing != null) {
errors.push((0, DiagnosticError_1.tsErr)(declaration, "Multiple derived contexts defined for given type", [
(0, DiagnosticError_1.gqlRelated)(definition.name, "One was defined here"),
(0, DiagnosticError_1.gqlRelated)(existing.name, "Another here"),
]));
continue;
}
self._recordDeclaration(declaration, definition);
}
});
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (_l && !_l.done && (_c = _k.return)) _c.call(_k);
}
finally { if (e_3) throw e_3.error; }
}
if (errors.length > 0) {
return (0, DiagnosticError_1.err)(errors);
return (0, Result_1.err)(errors);
}
return (0, DiagnosticError_1.ok)(newDoc);
return (0, Result_1.ok)(self);
};
TypeContext.prototype.resolveNamedType = function (unresolved) {
var symbol = this._unresolvedTypes.get(unresolved);
if (symbol == null) {
if (unresolved.value === exports.UNRESOLVED_REFERENCE_NAME) {
// This is a logic error on our side.
throw new Error("Unexpected unresolved reference name.");
// Record that a GraphQL construct of type `kind` with the name `name` is
// declared at `node`.
TypeContext.prototype._recordDeclaration = function (node, definition) {
this._idToDeclaration.set(definition.name.tsIdentifier, node);
this._declarationToDefinition.set(node, definition);
};
// Record that a type references `node`
TypeContext.prototype._markUnresolvedType = function (node, name) {
this._unresolvedNodes.set(name.tsIdentifier, node);
};
TypeContext.prototype.allDefinitions = function () {
return this._declarationToDefinition.values();
};
TypeContext.prototype.findSymbolDeclaration = function (startSymbol) {
var _a;
var symbol = this.resolveSymbol(startSymbol);
var declaration = (_a = symbol.declarations) === null || _a === void 0 ? void 0 : _a[0];
return declaration !== null && declaration !== void 0 ? declaration : null;
};
// Follow symbol aliases until we find the original symbol. Accounts for
// cyclical aliases.
TypeContext.prototype.resolveSymbol = function (startSymbol) {
var symbol = startSymbol;
var visitedSymbols = new Set();
while (ts.SymbolFlags.Alias & symbol.flags) {
if (visitedSymbols.has(symbol)) {
throw new Error("Cyclical alias detected. Breaking resolution.");
}
return (0, DiagnosticError_1.ok)(unresolved);
visitedSymbols.add(symbol);
symbol = this.checker.getAliasedSymbol(symbol);
}
var name = this._symbolToName.get(symbol);
if (name == null) {
if (unresolved.loc == null) {
throw new Error("Expected namedType to have a location.");
}
return (0, DiagnosticError_1.err)({
messageText: "This type is not a valid GraphQL type. Did you mean to annotate it's definition with a `/** @gql */` tag such as `/** @gqlType */` or `/** @gqlInput **/`?",
start: unresolved.loc.start,
length: unresolved.loc.end - unresolved.loc.start,
category: ts.DiagnosticCategory.Error,
code: DiagnosticError_1.FAKE_ERROR_CODE,
file: ts.createSourceFile(unresolved.loc.source.name, unresolved.loc.source.body, ts.ScriptTarget.Latest)
});
return symbol;
};
TypeContext.prototype.resolveUnresolvedNamedType = function (unresolved) {
if (unresolved.value !== exports.UNRESOLVED_REFERENCE_NAME) {
return (0, Result_1.ok)(unresolved);
}
return (0, DiagnosticError_1.ok)(__assign(__assign({}, unresolved), { value: name }));
var typeReference = this.getEntityName(unresolved);
if (typeReference == null) {
throw new Error("Unexpected unresolved reference name.");
}
var declarationResult = this.tsDeclarationForTsName(typeReference);
if (declarationResult.kind === "ERROR") {
return (0, Result_1.err)(declarationResult.err);
}
if (ts.isTypeParameterDeclaration(declarationResult.value)) {
return (0, Result_1.err)((0, DiagnosticError_1.gqlErr)(unresolved, "Type parameters are not supported in this context."));
}
var nameDefinition = this._declarationToDefinition.get(declarationResult.value);
if (nameDefinition == null) {
return (0, Result_1.err)((0, DiagnosticError_1.gqlErr)(unresolved, E.unresolvedTypeReference()));
}
if (nameDefinition.kind === "CONTEXT" || nameDefinition.kind === "INFO") {
return (0, Result_1.err)((0, DiagnosticError_1.gqlErr)(unresolved, E.contextOrInfoUsedInGraphQLPosition(nameDefinition.kind), [(0, DiagnosticError_1.gqlRelated)(nameDefinition.name, "Defined here")]));
}
return (0, Result_1.ok)(__assign(__assign({}, unresolved), { value: nameDefinition.name.value }));
};
TypeContext.prototype.validateInterfaceImplementorsHaveTypenameField = function () {
return (0, DiagnosticError_1.ok)(null);
TypeContext.prototype.unresolvedNameIsGraphQL = function (unresolved) {
var referenceNode = this.getEntityName(unresolved);
if (referenceNode == null)
return false;
var declaration = this.maybeTsDeclarationForTsName(referenceNode);
if (declaration == null)
return false;
return this._declarationToDefinition.has(declaration);
};
TypeContext.prototype.getDestFilePath = function (sourceFile) {
return (0, gratsRoot_1.getRelativeOutputPath)(this._options, sourceFile);
TypeContext.prototype.gqlNameDefinitionForGqlName = function (nameNode) {
var referenceNode = this.getEntityName(nameNode);
if (referenceNode == null) {
throw new Error("Expected to find reference node for name node.");
}
var declaration = this.maybeTsDeclarationForTsName(referenceNode);
if (declaration == null) {
return (0, Result_1.err)((0, DiagnosticError_1.gqlErr)(nameNode, E.unresolvedTypeReference()));
}
var definition = this._declarationToDefinition.get(declaration);
if (definition == null) {
return (0, Result_1.err)((0, DiagnosticError_1.gqlErr)(nameNode, E.unresolvedTypeReference()));
}
return (0, Result_1.ok)(definition);
};
// Note! This assumes you have already handled any type parameters.
TypeContext.prototype.gqlNameForTsName = function (node) {
var declarationResult = this.tsDeclarationForTsName(node);
if (declarationResult.kind === "ERROR") {
return (0, Result_1.err)(declarationResult.err);
}
if (ts.isTypeParameterDeclaration(declarationResult.value)) {
return (0, Result_1.err)((0, DiagnosticError_1.tsErr)(node, "Type parameter not valid", [
(0, DiagnosticError_1.tsErr)(declarationResult.value, "Defined here"),
]));
}
var nameDefinition = this._declarationToDefinition.get(declarationResult.value);
if (nameDefinition == null) {
return (0, Result_1.err)((0, DiagnosticError_1.tsErr)(node, E.unresolvedTypeReference()));
}
if (nameDefinition.kind === "CONTEXT" || nameDefinition.kind === "INFO") {
return (0, Result_1.err)((0, DiagnosticError_1.tsErr)(node, E.contextOrInfoUsedInGraphQLPosition(nameDefinition.kind), [
(0, DiagnosticError_1.gqlRelated)(nameDefinition.name, "Defined here"),
]));
}
return (0, Result_1.ok)(nameDefinition.name.value);
};
TypeContext.prototype.maybeTsDeclarationForTsName = function (node) {
var symbol = this.checker.getSymbolAtLocation(node);
if (symbol == null) {
return null;
}
return this.findSymbolDeclaration(symbol);
};
TypeContext.prototype.tsDeclarationForTsName = function (node) {
var declaration = this.maybeTsDeclarationForTsName(node);
if (!declaration) {
return (0, Result_1.err)((0, DiagnosticError_1.tsErr)(node, E.unresolvedTypeReference()));
}
return (0, Result_1.ok)(declaration);
};
TypeContext.prototype.tsDeclarationForGqlDefinition = function (definition) {
var name = definition.name;
var declaration = this._idToDeclaration.get(name.tsIdentifier);
if (!declaration) {
console.log(definition);
throw new Error("Could not find declaration for ".concat(name.value));
}
return declaration;
};
TypeContext.prototype.getEntityName = function (name) {
var _a;
var entityName = (_a = this._unresolvedNodes.get(name.tsIdentifier)) !== null && _a !== void 0 ? _a : null;
if (entityName == null && name.value === exports.UNRESOLVED_REFERENCE_NAME) {
throw new Error("Expected unresolved reference to have a node.");
}
return entityName;
};
return TypeContext;
}());
exports.TypeContext = TypeContext;

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

import type { GraphQLResolveInfo } from "graphql";
/** @gqlScalar */

@@ -7,1 +8,3 @@ export type Float = number;

export type ID = string;
/** @gqlInfo */
export type GqlInfo = GraphQLResolveInfo;
"use strict";
exports.__esModule = true;
Object.defineProperty(exports, "__esModule", { value: true });
import { GraphQLError, Location, Source } from "graphql";
import * as ts from "typescript";
type Ok<T> = {
kind: "OK";
value: T;
import { Result } from "./Result";
type FixableDiagnostic = ts.Diagnostic & {
fix?: ts.CodeFixAction;
};
type Err<E> = {
kind: "ERROR";
err: E;
export type FixableDiagnosticWithLocation = ts.DiagnosticWithLocation & {
fix?: ts.CodeFixAction;
};
export type Result<T, E> = Ok<T> | Err<E>;
export type DiagnosticResult<T> = Result<T, ts.Diagnostic>;
export type DiagnosticsResult<T> = Result<T, ts.Diagnostic[]>;
export declare function ok<T>(value: T): Ok<T>;
export declare function err<E>(err: E): Err<E>;
export type DiagnosticResult<T> = Result<T, FixableDiagnosticWithLocation>;
export type DiagnosticsResult<T> = Result<T, FixableDiagnosticWithLocation[]>;
export type DiagnosticsWithoutLocationResult<T> = Result<T, ts.Diagnostic[]>;
export declare class ReportableDiagnostics {
_host: ts.CompilerHost;
_diagnostics: ts.Diagnostic[];
constructor(host: ts.CompilerHost, diagnostics: ts.Diagnostic[]);
_host: ts.FormatDiagnosticsHost;
_diagnostics: FixableDiagnostic[];
constructor(host: ts.FormatDiagnosticsHost, diagnostics: FixableDiagnostic[]);
static fromDiagnostics(diagnostics: ts.Diagnostic[]): ReportableDiagnostics;
formatDiagnosticsWithColorAndContext(): string;
formatDiagnosticsWithContext(): string;
}
export declare const FAKE_ERROR_CODE = 349389149282;
export declare const FAKE_ERROR_CODE = 1038;
export declare function graphQlErrorToDiagnostic(error: GraphQLError): ts.Diagnostic;
export declare function diagnosticAtGraphQLLocation(message: string, loc: Location): ts.Diagnostic;
export declare function locationlessErr(message: string): ts.Diagnostic;
export declare function gqlErr(item: {
loc?: Location;
}, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): ts.DiagnosticWithLocation;
export declare function gqlRelated(item: {
loc?: Location;
}, message: string): ts.DiagnosticRelatedInformation;
export declare function rangeErr(file: ts.SourceFile, commentRange: ts.CommentRange, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[], fix?: ts.CodeFixAction): FixableDiagnosticWithLocation;
/**
* A generic version of the methods on ts.Node that we need
* to create diagnostics.
*
* This interface allows us to create diagnostics from our
* own classes.
*/
export interface TsLocatableNode {
getStart(): number;
getEnd(): number;
getSourceFile(): ts.SourceFile;
}
export declare function tsErr(node: TsLocatableNode, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[], fix?: ts.CodeFixAction): FixableDiagnosticWithLocation;
export declare function tsRelated(node: ts.Node, message: string): ts.DiagnosticRelatedInformation;
export declare function graphqlSourceToSourceFile(source: Source): ts.SourceFile;
export {};
"use strict";
exports.__esModule = true;
exports.graphqlSourceToSourceFile = exports.diagnosticAtGraphQLLocation = exports.graphQlErrorToDiagnostic = exports.FAKE_ERROR_CODE = exports.ReportableDiagnostics = exports.err = exports.ok = void 0;
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.");
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FAKE_ERROR_CODE = exports.ReportableDiagnostics = void 0;
exports.graphQlErrorToDiagnostic = graphQlErrorToDiagnostic;
exports.locationlessErr = locationlessErr;
exports.gqlErr = gqlErr;
exports.gqlRelated = gqlRelated;
exports.rangeErr = rangeErr;
exports.tsErr = tsErr;
exports.tsRelated = tsRelated;
exports.graphqlSourceToSourceFile = graphqlSourceToSourceFile;
var ts = require("typescript");
function ok(value) {
return { kind: "OK", value: value };
}
exports.ok = ok;
function err(err) {
return { kind: "ERROR", err: err };
}
exports.err = err;
var ReportableDiagnostics = /** @class */ (function () {

@@ -18,2 +45,12 @@ function ReportableDiagnostics(host, diagnostics) {

}
// If you don't have a host, for example if you error while parsing the
// tsconfig, you can use this method and one will be created for you.
ReportableDiagnostics.fromDiagnostics = function (diagnostics) {
var formatHost = {
getCanonicalFileName: function (path) { return path; },
getCurrentDirectory: ts.sys.getCurrentDirectory,
getNewLine: function () { return ts.sys.newLine; },
};
return new ReportableDiagnostics(formatHost, diagnostics);
};
ReportableDiagnostics.prototype.formatDiagnosticsWithColorAndContext = function () {

@@ -32,5 +69,5 @@ var formatted = ts.formatDiagnosticsWithColorAndContext(this._diagnostics, this._host);

exports.ReportableDiagnostics = ReportableDiagnostics;
// A madeup error code that we use to fake a TypeScript error code.
// A made-up error code that we use to fake a TypeScript error code.
// We pick a very random number to avoid collisions with real error messages.
exports.FAKE_ERROR_CODE = 349389149282;
exports.FAKE_ERROR_CODE = 1038;
function stripColor(str) {

@@ -44,2 +81,3 @@ // eslint-disable-next-line no-control-regex

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

@@ -49,2 +87,34 @@ if (position == null) {

}
// Start with baseline location information
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(gqlRelated(relatedNode, "Related location"));
}
}
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;

@@ -59,11 +129,26 @@ if (error.source != null) {

category: ts.DiagnosticCategory.Error,
start: position,
// FIXME: Improve ranges
length: 1
start: start,
length: length,
relatedInformation: relatedInformation,
source: "Grats",
};
}
exports.graphQlErrorToDiagnostic = graphQlErrorToDiagnostic;
function diagnosticAtGraphQLLocation(message, loc) {
function locationlessErr(message) {
return {
messageText: message,
file: undefined,
code: exports.FAKE_ERROR_CODE,
category: ts.DiagnosticCategory.Error,
start: undefined,
length: undefined,
source: "Grats",
};
}
function gqlErr(item, message, relatedInformation) {
if (item.loc == null) {
throw new Error("Expected item to have loc");
}
var loc = item.loc;
return {
messageText: message,
file: graphqlSourceToSourceFile(loc.source),

@@ -73,9 +158,64 @@ code: exports.FAKE_ERROR_CODE,

start: loc.start,
length: loc.end - loc.start
length: loc.end - loc.start,
relatedInformation: relatedInformation,
source: "Grats",
};
}
exports.diagnosticAtGraphQLLocation = diagnosticAtGraphQLLocation;
function gqlRelated(item, message) {
if (item.loc == null) {
throw new Error("Expected item to have loc");
}
var loc = item.loc;
return {
category: ts.DiagnosticCategory.Message,
code: exports.FAKE_ERROR_CODE,
messageText: message,
file: graphqlSourceToSourceFile(loc.source),
start: loc.start,
length: loc.end - loc.start,
};
}
function rangeErr(file, commentRange, message, relatedInformation, fix) {
var start = commentRange.pos;
var length = commentRange.end - commentRange.pos;
return {
messageText: message,
file: file,
code: exports.FAKE_ERROR_CODE,
category: ts.DiagnosticCategory.Error,
start: start,
length: length,
relatedInformation: relatedInformation,
source: "Grats",
fix: fix,
};
}
function tsErr(node, message, relatedInformation, fix) {
var start = node.getStart();
var length = node.getEnd() - start;
var sourceFile = node.getSourceFile();
return {
messageText: message,
file: sourceFile,
code: exports.FAKE_ERROR_CODE,
category: ts.DiagnosticCategory.Error,
start: start,
length: length,
relatedInformation: relatedInformation,
source: "Grats",
fix: fix,
};
}
function tsRelated(node, message) {
return {
category: ts.DiagnosticCategory.Message,
code: 0,
file: node.getSourceFile(),
start: node.getStart(),
length: node.getWidth(),
messageText: message,
};
}
function graphqlSourceToSourceFile(source) {
return ts.createSourceFile(source.name, source.body, ts.ScriptTarget.Latest);
}
exports.graphqlSourceToSourceFile = graphqlSourceToSourceFile;
{
"name": "grats",
"version": "0.0.0-main-7408c383",
"version": "0.0.0-main-7501b34a",
"main": "dist/src/index.js",

@@ -9,12 +9,14 @@ "bin": "dist/src/cli.js",

"files": [
"dist"
"dist",
"!dist/src/tests"
],
"dependencies": {
"@graphql-tools/utils": "^9.2.1",
"commander": "^10.0.0",
"graphql": "^16.6.0",
"typescript": "^4.9.5"
"graphql": "^16.9.0",
"typescript": "5.5.4",
"semver": "^7.5.4"
},
"devDependencies": {
"@types/node": "^18.14.6",
"@types/semver": "^7.5.6",
"@typescript-eslint/eslint-plugin": "^5.55.0",

@@ -26,2 +28,3 @@ "@typescript-eslint/parser": "^5.55.0",

"path-browserify": "^1.0.1",
"prettier": "^2.8.7",
"process": "^0.11.10",

@@ -33,8 +36,32 @@ "ts-node": "^10.9.1"

},
"engines": {
"node": ">=16 <=21"
},
"bugs": {
"url": "https://github.com/captbaritone/grats/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/captbaritone/grats.git"
},
"author": {
"name": "Jordan Eldredge",
"email": "jordan@jordaneldredge.com",
"url": "https://jordaneldredge.com"
},
"keywords": [
"graphql",
"typescript",
"resolvers",
"schema",
"code-first",
"implementation-first"
],
"scripts": {
"test": "ts-node --esm src/tests/test.ts",
"test": "ts-node src/tests/test.ts",
"integration-tests": "node src/tests/integration.mjs",
"build": "tsc --build",
"lint": "eslint src/**/*.ts"
"build": "rm -rf dist/ && tsc --build",
"format": "prettier . --write",
"lint": "eslint . && prettier . --check"
}
}

@@ -1,56 +0,29 @@

# -=[ EXPERIMENTAL PRE ALPHA ]=-
**This is currently a proof of concept. It won't yet work on any real projects.**
# Grats: Implementation-First GraphQL for TypeScript
[![Join our Discord!](https://img.shields.io/discord/1089650710796320868?logo=discord)](https://capt.dev/grats-chat)
**The simplest way to build a GraphQL server in TypeScript**
Grats is a tool for statically infering GraphQL schema from your vanilla
TypeScript code.
When you write your GraphQL server in TypeScript, your fields and resolvers
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._
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 never have to
worry about validating that your implementation matches your schema. Your
implementation _is_ your schema!
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!
Read the [blog post](https://jordaneldredge.com/blog/grats).
## Examples
## Example
Grats is flexible enough to work with both class-based and functional
approaches to authoring GraphQL types and resolvers.
Here's what it looks like to define a User type with a greeting field using Grats:
### 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';
name: string;
/** @gqlField */
greeting(args: { salutation: string }): string {
return `${args.salutation}, ${this.name}`;
greet(args: { greeting: string }): string {
return `${args.greeting}, ${this.name}`;
}

@@ -60,467 +33,15 @@ }

### Functional
After running `npx grats`, you'll find a `schema.ts` module that exports an executable schema, and a `schema.graphql` file contains your GraphQL schema definition:
```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
greet(greeting: String!): String
}
```
**Give it a try in the [online playground](https://capt.dev/grats-sandbox)!**
That's just the beginning! To learn more, **Read the docs: https://grats.capt.dev/**
## Quick Start
[![Join our Discord!](https://img.shields.io/discord/1089650710796320868?logo=discord)](https://capt.dev/grats-chat)
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,49 +51,15 @@

# FAQ
# Acknowledgements
## Why would I _not_ want to use Grats
- [@mofeiZ](https://github.com/mofeiZ) and [@alunyov](https://github/alunyov) for their Relay hack-week project exploring a similar idea.
- [@josephsavona](https://github.com/josephsavona) for input on the design of [Relay Resolvers](https://relay.dev/docs/guides/relay-resolvers/) which inspired this project.
- [@bradzacher](https://github.com/bradzacher) for tips on how to handle TypeScript ASTs.
- Everyone who worked on Meta's Hack GraphQL server, the developer experience of which inspired this project.
- A number of other projects which seem to have explored similar ideas in the past:
- [ts2gql](https://github.com/convoyinc/ts2gql)
- [ts2graphql](https://github.com/cevek/ts2graphql)
- [typegraphql-reflection-poc](https://github.com/MichalLytek/typegraphql-reflection-poc)
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.
## License
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
* [@mofeiZ](https://github.com/mofeiZ) and [@alunyov](https://github/alunyov) for their Relay hack-week project exploring a similar idea.
* [@josephsavona](https://github.com/josephsavona) for input on the design of [Relay Resolvers](https://relay.dev/docs/guides/relay-resolvers/) which inspired this project.
* [@bradzacher](https://github.com/bradzacher) for tips on how to handle TypeScript ASTs.
* Everyone who worked on Meta's Hack GraphQL server, the developer experince of which inspired this project.
* A number of other projects which seem to have explored similar ideas in the past:
* [ts2gql](https://github.com/convoyinc/ts2gql)
* [ts2graphql](https://github.com/cevek/ts2graphql)
* [typegraphql-reflection-poc](https://github.com/MichalLytek/typegraphql-reflection-poc)
Grats is [MIT licensed](./LICENSE).

Sorry, the diff of this file is too big to display