This commit is contained in:
Tim Griesser 2018-11-03 23:44:05 -04:00
parent 86e7e5c26a
commit b4b1ed58f3
22 changed files with 660 additions and 343 deletions

3
CHANGELOG.md Normal file
View File

@ -0,0 +1,3 @@
# 1.0.0
Initial release

0
CONTRIBUTING.md Normal file
View File

View File

@ -1,28 +1,34 @@
## GQLit
## GQLiteral
Literate, strongly typed GraphQL schema construction for JavaScript.
Simple, strongly typed GraphQL schema construction for TypeScript/JavaScript
Combines the best practices in building out a GraphQL server layer without the boilerplate.
Combines the best practices from building real-world GraphQL servers without the boilerplate or excessive imports. Compose types with [abstract types](#GQLiteralAbstractType) and [type mixing](#Type-combination).
Inspired by real-world use of graphql-js, apollo, graphene, and graphql-ruby.
Inspired by use of [graphql-tools](https://github.com/apollographql/graphql-tools), [graphene](https://docs.graphene-python.org/en/latest/), and [graphql-ruby](https://github.com/rmosolgo/graphql-ruby).
Provides full control of your schema, with added benefits like dynamic schemas based on user permissions. Check out the `/examples` for some sample uses.
### Installation
```
yarn install gqliteral
```
### Features:
**_ Type combination _**
##### Type combination
Ever have a situation where your input types look eerily simliar to your output types, yet you need to define them both by hand in the schema? Or maybe you have an interface shared by several types, and each time you add a field to the interface you need to remember to add it to the types.
**_ No circular reference issues _**
##### No circular reference issues
One of the problems with GraphQL construction is the self-referential types and the
**_ Awesome intellisense _**
##### Awesome intellisense
Leverages type generation internally so you don't have to.
**_ Great error messages _**
##### Great error messages
We want you to know where things went wrong, not spend time trying to figure out cryptic error messages.
@ -36,8 +42,20 @@ Ever notice that your input types and output types are usually fairly similar? M
### API:
#### GQLitType
#### GQLiteralObject
#### GQLitInputType
#### GQLiteralInputObject
####
#### GQLiteralInterface
#### GQLiteralObject
#### GQLiteralUnion
#### GQLiteralSchema
#### GQLiteralEnum
#### GQLiteralAbstractType
### License

View File

@ -1,5 +1,5 @@
{
"name": "gqlit-simple-app",
"name": "gqliteral-simple-app",
"dependencies": {
"express": "^4.16.4",
"apollo-server-express": "^2.1.0"

View File

@ -1,6 +1,6 @@
import { GQLitObject } from "../../../../src";
import { GQLiteralObject } from "../../../../src";
export const Blog = GQLitObject("Blog", t => {
export const Blog = GQLiteralObject("Blog", t => {
t.field("id", "ID");
t.field("title", "String", { description: "The title of the blog" });
t.list("posts", "Post");

View File

@ -1,9 +1,12 @@
import { GQLitObject, GQLitInputObject } from "../../../../src";
import { GQLiteralObject, GQLiteralInputObject } from "../../../../src";
export const Comment = GQLitObject("Comment", t => {
export const Comment = GQLiteralObject("Comment", t => {
t.field("id", "ID");
});
export const CreateCommentInput = GQLitInputObject("CreateCommentInput", t => {
t.mix("Comment");
});
export const CreateCommentInput = GQLiteralInputObject(
"CreateCommentInput",
t => {
t.mix("Comment");
}
);

View File

@ -1,5 +1,5 @@
import { GQLitInterface } from "../../../../src";
import { GQLiteralInterface } from "../../../../src";
export const Node = GQLitInterface("Node", t => {
export const Node = GQLiteralInterface("Node", t => {
t.resolveType(() => {});
});

View File

@ -1,5 +1,5 @@
import { GQLitObject } from '../../../../src';
import { GQLiteralObject } from "../../../../src";
export const Post = GQLitObject('Post', t => {
t.implements('Node');
export const Post = GQLiteralObject("Post", t => {
t.implements("Node");
});

View File

@ -1,5 +1,5 @@
import { GQLitObject } from "../../../../src";
import { GQLiteralObject } from "../../../../src";
export const User = GQLitObject("User", t => {
export const User = GQLiteralObject("User", t => {
t.mix("Node");
});

View File

@ -1,5 +1,5 @@
{
"name": "gqlit-swapi-example",
"name": "gqliteral-swapi-example",
"version": "0.0.0",
"dependencies": {
"swapi-graphql": "^0.0.6"

View File

@ -1,22 +1,30 @@
import { GQLitObject } from '../../../../../src';
import { GQLiteralObject } from "../../../../../src";
export const Film = GQLitObject('Film', t => {
t.description('A single film');
t.string('title', { description: 'The title of the film' });
t.int('episodeID', { description: 'The episode number of this film.', property: 'episode_id' });
t.string('openingCrawl', { description: 'The opening paragraphs at the beginning of this film.' });
t.string('director', { description: 'The name of the director of this film.' });
t.list('producers', 'String', {
description: 'The name(s) of the producer(s) of this film.',
export const Film = GQLiteralObject("Film", t => {
t.description("A single film");
t.string("title", { description: "The title of the film" });
t.int("episodeID", {
description: "The episode number of this film.",
property: "episode_id",
});
t.string("openingCrawl", {
description: "The opening paragraphs at the beginning of this film.",
});
t.string("director", {
description: "The name of the director of this film.",
});
t.list("producers", "String", {
description: "The name(s) of the producer(s) of this film.",
resolve: film => {
return film.producer.split(',').map((s: string) => s.trim());
return film.producer.split(",").map((s: string) => s.trim());
},
});
t.string('releaseDate', {
property: 'release_date',
description: 'The ISO 8601 date format of film release at original creator country.',
t.string("releaseDate", {
property: "release_date",
description:
"The ISO 8601 date format of film release at original creator country.",
});
t.field('speciesConnection', 'FilmSpeciesConnection');
t.field('starshipConnection', 'StarshipConnection');
t.field('starshipConnection', 'StarshipConnection');
t.field("speciesConnection", "FilmSpeciesConnection");
t.field("starshipConnection", "StarshipConnection");
t.field("starshipConnection", "StarshipConnection");
});

View File

@ -1,43 +1,44 @@
import { GQLitObject } from '../../../../../src';
import { GQLiteralObject } from "../../../../../src";
export const Person = GQLitObject('Person', t => {
t.string('name', { description: 'The name of this person' });
t.string('birthYear', {
property: 'birth_year',
export const Person = GQLiteralObject("Person", t => {
t.string("name", { description: "The name of this person" });
t.string("birthYear", {
property: "birth_year",
description: `The birth year of the person, using the in-universe standard of BBY or ABY -
Before the Battle of Yavin or After the Battle of Yavin. The Battle of Yavin is
a battle that occurs at the end of Star Wars episode IV: A New Hope.`,
});
t.string('eyeColor', {
property: 'eye_color',
t.string("eyeColor", {
property: "eye_color",
description: `The eye color of this person. Will be "unknown" if not known or "n/a" if the
person does not have an eye.`,
});
t.string('gender', {
t.string("gender", {
description: `The gender of this person. Either "Male", "Female" or "unknown",
"n/a" if the person does not have a gender.`,
defaultValue: 'n/a',
defaultValue: "n/a",
});
t.string('hairColor', {
property: 'hair_color',
t.string("hairColor", {
property: "hair_color",
description: `The hair color of this person. Will be "unknown" if not known or "n/a" if the
person does not have hair.`,
});
t.int('height', {
t.int("height", {
resolve: person => convertToNumber(person.height),
description: 'The height of the person in centimeters.',
description: "The height of the person in centimeters.",
});
t.float('mass', {
t.float("mass", {
resolve: person => convertToNumber(person.mass),
description: 'The mass of the person in kilograms.',
description: "The mass of the person in kilograms.",
});
t.string('skinColor', {
property: 'skin_color',
description: 'The skin color of this person.',
t.string("skinColor", {
property: "skin_color",
description: "The skin color of this person.",
});
t.field('homeworld', 'Planet', {
resolve: person => (person.homeworld ? getObjectFromUrl(person.homeworld) : null),
description: 'A planet that this person was born on or inhabits.',
t.field("homeworld", "Planet", {
resolve: person =>
person.homeworld ? getObjectFromUrl(person.homeworld) : null,
description: "A planet that this person was born on or inhabits.",
});
});
@ -45,11 +46,11 @@ person does not have an eye.`,
* Given a string, convert it to a number
*/
function convertToNumber(value: string): number | null {
if (['unknown', 'n/a'].indexOf(value) !== -1) {
if (["unknown", "n/a"].indexOf(value) !== -1) {
return null;
}
// remove digit grouping
const numberString = value.replace(/,/, '');
const numberString = value.replace(/,/, "");
return Number(numberString);
}

View File

@ -1,8 +1,8 @@
import { GQLitObject } from '../../../../../src';
import { GQLiteralObject } from "../../../../../src";
export const Planet = GQLitObject('Planet', t => {
export const Planet = GQLiteralObject("Planet", t => {
t.description(`A large mass, planet or planetoid in the Star Wars Universe, at the time of
0 ABY.`);
t.string('name', { description: 'The name of this planet.' });
t.int('diameter');
t.string("name", { description: "The name of this planet." });
t.int("diameter");
});

View File

@ -1,21 +1,24 @@
import { GQLitObject } from '../../../../../src';
import { GQLiteralObject } from "../../../../../src";
export const Vehicle = GQLitObject('Vehicle', t => {
t.description('A single transport craft that does not have hyperdrive capability');
t.string('name', {
export const Vehicle = GQLiteralObject("Vehicle", t => {
t.description(
"A single transport craft that does not have hyperdrive capability"
);
t.string("name", {
description: `The name of this vehicle. The common name, such as "Sand Crawler" or "Speeder
bike".`,
});
t.string('model', {
t.string("model", {
description: `The model or official name of this vehicle. Such as "All-Terrain Attack
Transport".`,
});
t.string('vehicleClass', {
property: 'vehicle_class',
description: 'The class of this vehicle, such as "Wheeled" or "Repulsorcraft".',
t.string("vehicleClass", {
property: "vehicle_class",
description:
'The class of this vehicle, such as "Wheeled" or "Repulsorcraft".',
});
t.list('manufacturers', 'String', {
t.list("manufacturers", "String", {
// resolve: () => {},
description: 'The manufacturers of this vehicle.',
description: "The manufacturers of this vehicle.",
});
});

View File

@ -1,5 +1,5 @@
{
"name": "gqlit",
"name": "gqliteral",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
@ -15,6 +15,7 @@
"husky": "^1.1.2",
"lint-staged": "^7.3.0",
"typescript": "^3.1.3",
"tslint": "^5.11.0",
"jest": "^23.6.0",
"ts-jest": "^23.10.4"
},

View File

@ -1,68 +1,82 @@
/// <reference types="jest" />
import { gqlitBuildTypes, GQLitEnum, GQLitObject } from '../definitions';
import { GraphQLEnumType, GraphQLObjectType } from 'graphql';
import {
gqlitBuildTypes,
GQLiteralEnum,
GQLiteralObject,
} from "../definitions";
import { GraphQLEnumType, GraphQLObjectType } from "graphql";
describe('gqlit', () => {
describe('GQLitEnum', () => {
const PrimaryColors = GQLitEnum('PrimaryColors', t => {
t.member({ value: 'RED' });
t.member({ value: 'YELLOW' });
t.member({ value: 'BLUE' });
describe("gqlit", () => {
describe("GQLiteralEnum", () => {
const PrimaryColors = GQLiteralEnum("PrimaryColors", t => {
t.member({ value: "RED" });
t.member({ value: "YELLOW" });
t.member({ value: "BLUE" });
});
const RainbowColors = GQLitEnum('RainbowColors', t => {
t.mix('PrimaryColors');
t.member({ value: 'ORANGE' });
t.member({ value: 'GREEN' });
t.member({ value: 'VIOLET' });
const RainbowColors = GQLiteralEnum("RainbowColors", t => {
t.mix("PrimaryColors");
t.member({ value: "ORANGE" });
t.member({ value: "GREEN" });
t.member({ value: "VIOLET" });
});
const AdditivePrimaryColors = GQLitEnum('AdditivePrimaryColors', t => {
t.mix('PrimaryColors', { omit: ['YELLOW'] });
t.member({ value: 'GREEN' });
const AdditivePrimaryColors = GQLiteralEnum("AdditivePrimaryColors", t => {
t.mix("PrimaryColors", { omit: ["YELLOW"] });
t.member({ value: "GREEN" });
});
const CircularRefTestA = GQLitEnum('CircularA', t => {
t.mix('CircularB');
t.member({ value: 'A' });
const CircularRefTestA = GQLiteralEnum("CircularA", t => {
t.mix("CircularB");
t.member({ value: "A" });
});
const CircularRefTestB = GQLitEnum('CircularA', t => {
t.mix('CircularA');
t.member({ value: 'B' });
const CircularRefTestB = GQLiteralEnum("CircularA", t => {
t.mix("CircularA");
t.member({ value: "B" });
});
it('builds an enum', () => {
const types: { PrimaryColors: GraphQLEnumType } = gqlitBuildTypes([PrimaryColors]) as any;
it("builds an enum", () => {
const types: { PrimaryColors: GraphQLEnumType } = gqlitBuildTypes([
PrimaryColors,
]) as any;
expect(types.PrimaryColors).toBeInstanceOf(GraphQLEnumType);
expect(types.PrimaryColors.getValues().map(v => v.value)).toEqual(['RED', 'YELLOW', 'BLUE']);
});
it('can mix enums', () => {
const types: { RainbowColors: GraphQLEnumType } = gqlitBuildTypes([PrimaryColors, RainbowColors]) as any;
expect(types.RainbowColors).toBeInstanceOf(GraphQLEnumType);
expect(types.RainbowColors.getValues().map(v => v.value)).toEqual([
'RED',
'YELLOW',
'BLUE',
'ORANGE',
'GREEN',
'VIOLET',
expect(types.PrimaryColors.getValues().map(v => v.value)).toEqual([
"RED",
"YELLOW",
"BLUE",
]);
});
it('can omit with mix', () => {
const types: { AdditivePrimaryColors: GraphQLEnumType } = gqlitBuildTypes([
it("can mix enums", () => {
const types: { RainbowColors: GraphQLEnumType } = gqlitBuildTypes([
PrimaryColors,
AdditivePrimaryColors,
RainbowColors,
]) as any;
expect(types.AdditivePrimaryColors).toBeInstanceOf(GraphQLEnumType);
expect(types.AdditivePrimaryColors.getValues().map(v => v.value)).toEqual(['RED', 'BLUE', 'GREEN']);
expect(types.RainbowColors).toBeInstanceOf(GraphQLEnumType);
expect(types.RainbowColors.getValues().map(v => v.value)).toEqual([
"RED",
"YELLOW",
"BLUE",
"ORANGE",
"GREEN",
"VIOLET",
]);
});
it('can pick with mix', () => {
const FavoriteColors = GQLitEnum('FavoriteColors', t => {
t.mix('RainbowColors', { pick: ['RED', 'GREEN'] });
it("can omit with mix", () => {
const types: { AdditivePrimaryColors: GraphQLEnumType } = gqlitBuildTypes(
[PrimaryColors, AdditivePrimaryColors]
) as any;
expect(types.AdditivePrimaryColors).toBeInstanceOf(GraphQLEnumType);
expect(types.AdditivePrimaryColors.getValues().map(v => v.value)).toEqual(
["RED", "BLUE", "GREEN"]
);
});
it("can pick with mix", () => {
const FavoriteColors = GQLiteralEnum("FavoriteColors", t => {
t.mix("RainbowColors", { pick: ["RED", "GREEN"] });
});
const types: { FavoriteColors: GraphQLEnumType } = gqlitBuildTypes([
PrimaryColors,
@ -70,56 +84,76 @@ describe('gqlit', () => {
FavoriteColors,
]) as any;
expect(types.FavoriteColors).toBeInstanceOf(GraphQLEnumType);
expect(types.FavoriteColors.getValues().map(v => v.value)).toEqual(['RED', 'GREEN']);
expect(types.FavoriteColors.getValues().map(v => v.value)).toEqual([
"RED",
"GREEN",
]);
});
it('can map internal values', () => {
const Internal = GQLitEnum('Internal', t => {
t.member({ value: 'A', internalValue: '--A--' });
t.member({ value: 'B', internalValue: '--B--' });
it("can map internal values", () => {
const Internal = GQLiteralEnum("Internal", t => {
t.member({ value: "A", internalValue: "--A--" });
t.member({ value: "B", internalValue: "--B--" });
});
const types: { Internal: GraphQLEnumType } = gqlitBuildTypes([Internal]) as any;
expect(types.Internal.getValues().map(v => v.name)).toEqual(['A', 'B']);
expect(types.Internal.getValues().map(v => v.value)).toEqual(['--A--', '--B--']);
const types: { Internal: GraphQLEnumType } = gqlitBuildTypes([
Internal,
]) as any;
expect(types.Internal.getValues().map(v => v.name)).toEqual(["A", "B"]);
expect(types.Internal.getValues().map(v => v.value)).toEqual([
"--A--",
"--B--",
]);
});
it('has shorthand syntax for enum mapping', () => {
const MappedArr = GQLitEnum('MappedArr', ['A', 'B']);
const MappedObj = GQLitEnum('MappedObj', {
it("has shorthand syntax for enum mapping", () => {
const MappedArr = GQLiteralEnum("MappedArr", ["A", "B"]);
const MappedObj = GQLiteralEnum("MappedObj", {
a: 1,
b: 2,
});
const types: { MappedObj: GraphQLEnumType; MappedArr: GraphQLEnumType } = gqlitBuildTypes([
MappedObj,
MappedArr,
]) as any;
expect(types.MappedArr.getValues().map(v => v.name)).toEqual(['A', 'B']);
expect(types.MappedObj.getValues().map(v => v.name)).toEqual(['a', 'b']);
const types: {
MappedObj: GraphQLEnumType;
MappedArr: GraphQLEnumType;
} = gqlitBuildTypes([MappedObj, MappedArr]) as any;
expect(types.MappedArr.getValues().map(v => v.name)).toEqual(["A", "B"]);
expect(types.MappedObj.getValues().map(v => v.name)).toEqual(["a", "b"]);
expect(types.MappedObj.getValues().map(v => v.value)).toEqual([1, 2]);
});
it('throws if the enum has no members', () => {
const NoMembers = GQLitEnum('NoMembers', () => {});
it("throws if the enum has no members", () => {
const NoMembers = GQLiteralEnum("NoMembers", () => {});
expect(() => {
const types: { NoMembers: GraphQLEnumType } = gqlitBuildTypes([NoMembers]) as any;
const types: { NoMembers: GraphQLEnumType } = gqlitBuildTypes([
NoMembers,
]) as any;
expect(types.NoMembers.getValues()).toHaveLength(0);
}).toThrow('must have at least one member');
}).toThrow("must have at least one member");
});
it('throws when building with a circular reference', () => {
it("throws when building with a circular reference", () => {
expect(() => {
gqlitBuildTypes([CircularRefTestA, CircularRefTestB]);
}).toThrowError('Circular dependency mixin detected when building GQLit Enum');
}).toThrowError(
"Circular dependency mixin detected when building GQLit Enum"
);
});
});
describe('GQLitObject', () => {
const Account = GQLitObject('Account', t => {
t.id('id', { description: 'The ID of the account' });
t.string('name', { description: 'Holder of the account' });
t.string('email', { description: 'The email of the person whos account this is' });
describe("GQLiteralObject", () => {
const Account = GQLiteralObject("Account", t => {
t.id("id", { description: "The ID of the account" });
t.string("name", { description: "Holder of the account" });
t.string("email", {
description: "The email of the person whos account this is",
});
});
const type: { Account: GraphQLObjectType } = gqlitBuildTypes([Account]) as any;
expect(Object.keys(type.Account.getFields()).sort()).toEqual(['id', 'email', 'name']);
const type: { Account: GraphQLObjectType } = gqlitBuildTypes([
Account,
]) as any;
expect(Object.keys(type.Account.getFields()).sort()).toEqual([
"id",
"email",
"name",
]);
});
});

View File

@ -1,19 +1,19 @@
import {
GraphQLSchema,
isNamedType,
GraphQLNamedType,
isNamedType,
isObjectType,
} from "graphql";
import * as Types from "./types";
import * as Factories from "./factories";
/**
* Wraps a GQLitType object, since all GQLit types have a
* Wraps a GQLiteralType object, since all GQLiteral types have a
* name, but that name isn't relevant to the type object until it's
* constructed so we don't want it as a public member, purely for
* intellisense/cosmetic purposes :)
*/
export class GQLitTypeWrapper<T extends Factories.GQLitType> {
export class GQLiteralTypeWrapper<T extends Factories.GQLiteralType> {
constructor(readonly name: string, readonly type: T) {}
}
@ -23,8 +23,14 @@ export class GQLitTypeWrapper<T extends Factories.GQLitType> {
* @param {string} name
* @param {object} options
*/
export function GQLitScalar(name: string, options: Types.GQLitScalarOptions) {
return new GQLitTypeWrapper(name, new Factories.GQLitScalarType(options));
export function GQLiteralScalar(
name: string,
options: Types.GQLiteralScalarOptions
) {
return new GQLiteralTypeWrapper(
name,
new Factories.GQLiteralScalarType(name, options)
);
}
/**
@ -32,65 +38,65 @@ export function GQLitScalar(name: string, options: Types.GQLitScalarOptions) {
*
* @param {string}
*/
export function GQLitObject<Root = any, Schema = any>(
export function GQLiteralObject<Root = any, Schema = any>(
name: string,
fn: (arg: Factories.GQLitObjectType<Root, Schema>) => void
fn: (arg: Factories.GQLiteralObjectType<Root, Schema>) => void
) {
const factory = new Factories.GQLitObjectType<Root, Schema>();
const factory = new Factories.GQLiteralObjectType<Root, Schema>(name);
fn(factory);
return new GQLitTypeWrapper(name, factory);
return new GQLiteralTypeWrapper(name, factory);
}
/**
* Define a GraphQL interface type
*/
export function GQLitInterface<Root = any, Schema = any>(
export function GQLiteralInterface<Root = any, Schema = any>(
name: string,
fn: (arg: Factories.GQLitInterfaceType<Root, Schema>) => void
fn: (arg: Factories.GQLiteralInterfaceType<Root, Schema>) => void
) {
const factory = new Factories.GQLitInterfaceType();
const factory = new Factories.GQLiteralInterfaceType(name);
fn(factory);
return new GQLitTypeWrapper(name, factory);
return new GQLiteralTypeWrapper(name, factory);
}
/**
* Union types are very similar to interfaces, but they don't get to specify
* any common fields between the types.
*
* There are two ways to create a GraphQLUnionType with GQLitUnion:
* There are two ways to create a GraphQLUnionType with GQLiteralUnion:
*
* As an array of types to satisfy the union:
*
* const SearchResult = GQLitUnion('SearchResult', ['Human', 'Droid', 'Starship'])
* const SearchResult = GQLiteralUnion('SearchResult', ['Human', 'Droid', 'Starship'])
*
* As a function, where other unions can be mixed in:
*
* const CombinedResult = GQLitUnion('CombinedResult', t => {
* const CombinedResult = GQLiteralUnion('CombinedResult', t => {
* t.mix('SearchResult')
* t.types()
* t.members('OtherType', 'AnotherType')
* })
*/
export function GQLitUnion(
export function GQLiteralUnion(
name: string,
fn: (arg: Factories.GQLitUnionType) => void
fn: (arg: Factories.GQLiteralUnionType) => void
) {
const factory = new Factories.GQLitUnionType();
const factory = new Factories.GQLiteralUnionType(name);
fn(factory);
return new GQLitTypeWrapper(name, factory);
return new GQLiteralTypeWrapper(name, factory);
}
/**
* A Enum is a special GraphQL type that represents a set of symbolic names (members)
* bound to unique, constant values. There are three ways to create a GraphQLEnumType
* with GQLitEnum:
* with GQLiteralEnum:
*
* As an array of enum values:
*
* const Episode = GQLitEnum('Episode', ['NEWHOPE', 'EMPIRE', 'JEDI'])
* const Episode = GQLiteralEnum('Episode', ['NEWHOPE', 'EMPIRE', 'JEDI'])
*
* As an object, with a mapping of enum values to internal values:
*
* const Episode = GQLitEnum('Episode', {
* const Episode = GQLiteralEnum('Episode', {
* NEWHOPE: 4,
* EMPIRE: 5,
* JEDI: 6
@ -98,7 +104,7 @@ export function GQLitUnion(
*
* As a function, where other enums can be mixed in:
*
* const Episode = GQLitEnum('Episode', (t) => {
* const Episode = GQLiteralEnum('Episode', (t) => {
* t.mix('OneThroughThree')
* t.mix('FourThroughSix')
* t.mix('SevenThroughNine')
@ -106,21 +112,24 @@ export function GQLitUnion(
* t.description('All Movies in the Skywalker saga, or OTHER')
* })
*/
export function GQLitEnum(
export function GQLiteralEnum(
name: string,
fn: ((arg: Factories.GQLitEnumType) => void) | string[] | Record<string, any>
fn:
| ((arg: Factories.GQLiteralEnumType) => void)
| string[]
| Record<string, any>
) {
const toCall = typeof fn === "function" ? fn : addEnumValue(fn);
const factory = new Factories.GQLitEnumType();
const factory = new Factories.GQLiteralEnumType(name);
toCall(factory);
return new GQLitTypeWrapper(name, factory);
return new GQLiteralTypeWrapper(name, factory);
}
/**
* Handles the shorhand syntax for creating the GraphQL schema
*/
const addEnumValue = (arg: string[] | Record<string, any>) => (
f: Factories.GQLitEnumType
f: Factories.GQLiteralEnumType
) => {
if (Array.isArray(arg)) {
arg.forEach(value => {
@ -136,27 +145,29 @@ const addEnumValue = (arg: string[] | Record<string, any>) => (
/**
*
*/
export function GQLitInputObject(
export function GQLiteralInputObject(
name: string,
fn: (arg: Factories.GQLitInputObjectType) => void
fn: (arg: Factories.GQLiteralInputObjectType) => void
) {
const factory = new Factories.GQLitInputObjectType();
const factory = new Factories.GQLiteralInputObjectType(name);
fn(factory);
return new GQLitTypeWrapper(name, factory);
return new GQLiteralTypeWrapper(name, factory);
}
/**
* A `GQLitAbstract` object contains fields that can be shared among
* `GQLitObject`, `GQLitInterface`, `GQLitInputObject` or other `GQLitAbstract` types.
* A `GQLiteralAbstractType` object contains fields that can be shared among
* `GQLiteralObject`, `GQLiteralInterface`, `GQLiteralInputObject` or other `GQLiteralAbstractType` types.
*
* Unlike concrete GraphQL types (types that show up in the generated schema),
* GQLitAbstract types must be mixed in using the actual JS object returned by this
* GQLiteralAbstractType types must be mixed in using the actual JS object returned by this
* function rather than a string "name" representing the type.
*
* @return GQLitAbstractType
* @return GQLiteralAbstractType
*/
export function GQLitAbstract(fn: (arg: Factories.GQLitAbstractType) => void) {
const factory = new Factories.GQLitAbstractType();
export function GQLiteralAbstractType(
fn: (arg: Factories.GQLiteralAbstractType) => void
) {
const factory = new Factories.GQLiteralAbstractType();
fn(factory);
// This is not wrapped in a type, since it's not actually a concrete type.
@ -165,16 +176,16 @@ export function GQLitAbstract(fn: (arg: Factories.GQLitAbstractType) => void) {
/**
* Defines the GraphQL schema, by combining the GraphQL types defined
* by the GQLit layer or any manually defined GraphQLType objects.
* by the GQLiteral layer or any manually defined GraphQLType objects.
*
* Requires at least one type be named "Query", which will be used as the
* root query type.
*/
export function GQLitSchema(options: Types.GQLitSchemaConfig) {
const typeMap = gqlitBuildTypes(options.types);
export function GQLiteralSchema(options: Types.GQLiteralSchemaConfig) {
const typeMap = gqliteralBuildTypes(options.types);
if (!isObjectType(typeMap["Query"])) {
throw new Error("Missing a Query type.");
throw new Error("Missing a Query type");
}
const schema = new GraphQLSchema({
@ -197,7 +208,7 @@ export function GQLitSchema(options: Types.GQLitSchemaConfig) {
* Builds all of the types, properly accounts for any enums using "mix".
* Since the enum types are resolved synchronously, these need to guard for circular references.
*/
export function gqlitBuildTypes(
export function gqliteralBuildTypes(
types: any[]
): Record<string, GraphQLNamedType> {
const finalTypeMap: Record<string, GraphQLNamedType> = {};
@ -207,9 +218,9 @@ export function gqlitBuildTypes(
> = {};
types.forEach(typeDef => {
if (typeDef instanceof GQLitTypeWrapper) {
if (typeDef instanceof GQLiteralTypeWrapper) {
pendingTypeMap[typeDef.name] = currentlyBuilding => {
finalTypeMap[typeDef.name] = Factories.buildGQLitType(
finalTypeMap[typeDef.name] = Factories.buildGQLiteralType(
typeDef.name,
typeDef.type,
{

View File

@ -1,6 +1,8 @@
export enum NodeType {
MIX = "MIX",
MIX_ABSTRACT = "MIX_ABSTRACT",
LIST = "LIST",
FIELD = "FIELD",
ENUM_MEMBER = "ENUM_MEMBER",
UNION_MEMBER = "UNION_MEMBER",
}

View File

@ -16,28 +16,36 @@ import {
GraphQLObjectTypeConfig,
GraphQLUnionTypeConfig,
GraphQLEnumValueConfigMap,
GraphQLFieldConfig,
GraphQLList,
GraphQLNonNull,
GraphQLField,
isInterfaceType,
isObjectType,
GraphQLFieldConfigMap,
GraphQLOutputType,
} from "graphql";
export type GQLitType =
| GQLitScalarType
| GQLitEnumType
| GQLitObjectType<any>
| GQLitInterfaceType<any>
| GQLitUnionType
| GQLitInputObjectType;
export type GQLiteralType =
| GQLiteralScalarType
| GQLiteralEnumType
| GQLiteralObjectType<any>
| GQLiteralInterfaceType<any>
| GQLiteralUnionType
| GQLiteralInputObjectType;
export class GQLitScalarType {
constructor(readonly options: T.GQLitScalarOptions) {}
export class GQLiteralScalarType {
constructor(
protected readonly name: string,
readonly options: T.GQLiteralScalarOptions
) {}
/**
* Internal use only. Creates the configuration to create
* the GraphQL named type.
*/
toConfig(name: string): GraphQLScalarTypeConfig<any, any> {
toConfig(): GraphQLScalarTypeConfig<any, any> {
ensureBuilding();
return {
name,
name: this.name,
...this.options,
};
}
@ -46,11 +54,13 @@ export class GQLitScalarType {
/**
* Backing type for an enum member.
*/
export class GQLitEnumType {
protected meta: T.GQLitTypeMetadata = {};
export class GQLiteralEnumType {
protected meta: T.GQLiteralTypeMetadata = {};
protected members: T.EnumDefType[] = [];
mix(typeName: string, mixOptions?: T.GQLitMixOptions) {
constructor(protected readonly name: string) {}
mix(typeName: string, mixOptions?: T.GQLiteralMixOptions) {
this.members.push({
type: E.NodeType.MIX,
typeName,
@ -83,10 +93,11 @@ export class GQLitEnumType {
* Internal use only. Creates the configuration to create
* the GraphQL named type.
*
* The GQLitEnumTpye requires the typeData arg because it
* needs to synchronously return and therefore must
* The GQLiteralEnumType requires the typeData arg because it
* needs to synchronously return and therefore must check for / break
* circular references when mixing.
*/
toConfig(name: string, typeData: TypeDataArg): GraphQLEnumTypeConfig {
toConfig(typeData: TypeDataArg): GraphQLEnumTypeConfig {
ensureBuilding();
let values: GraphQLEnumValueConfigMap = {},
description;
@ -101,19 +112,20 @@ export class GQLitEnumType {
case E.NodeType.MIX:
if (typeData.currentlyBuilding.has(member.typeName)) {
throw new Error(
`Circular dependency mixin detected when building GQLit Enum: ${name}`
`Circular dependency mixin detected when building GQLit Enum: ${
this.name
}`
);
}
const toBuildFn = typeData.pendingTypeMap[member.typeName];
let enumToMix;
if (typeof toBuildFn === "function") {
enumToMix = toBuildFn(typeData.currentlyBuilding.add(name));
enumToMix = toBuildFn(typeData.currentlyBuilding.add(this.name));
} else if (typeData.finalTypeMap[member.typeName]) {
enumToMix = typeData.finalTypeMap[member.typeName];
} else {
throw new Error(`Missing mixin enum type: ${member.typeName}`);
}
if (!isEnumType(enumToMix)) {
throw new Error(
`Cannot mix non-enum type ${enumToMix.name} with enum values`
@ -138,45 +150,63 @@ export class GQLitEnumType {
}
});
if (Object.keys(values).length === 0) {
throw new Error(`GQLitEnum ${name} must have at least one member`);
throw new Error(
`GQLiteralEnum ${this.name} must have at least one member`
);
}
return {
name,
name: this.name,
values,
description,
};
}
}
export class GQLitUnionType {
protected meta: T.GQLitTypeMetadata = {};
protected members: T.UnionTypeDef[] = [];
export class GQLiteralUnionType {
protected meta: T.GQLiteralTypeMetadata = {};
protected unionMembers: T.UnionTypeDef[] = [];
constructor(protected readonly name: string) {}
mix(type: string) {
this.members.push({ type: E.NodeType.MIX, typeName: type, mixOptions: {} });
this.unionMembers.push({
type: E.NodeType.MIX,
typeName: type,
mixOptions: {},
});
}
members(...types: string[]) {
types.forEach(typeName => {
this.unionMembers.push({ type: E.NodeType.UNION_MEMBER, typeName });
});
}
/**
* Internal use only. Creates the configuration to create
* the GraphQL named type.
*/
toConfig(name: string): GraphQLUnionTypeConfig<any, any> {
toConfig(getType: T.GetTypeFn): GraphQLUnionTypeConfig<any, any> {
ensureBuilding();
return {
name,
types: () => [],
name: this.name,
types: () => {
return this.unionMembers.reduce((result: GraphQLObjectType[], item) => {
return result;
}, []);
},
};
}
}
abstract class GQLitWithFields<Root, Schema, Opts, ListOpts> {
protected fields: T.FieldDefType<Opts, ListOpts>[] = [];
protected fields: T.FieldDefType<Root>[] = [];
/**
* Mixes in an existing field definition or object type
* with the current type.
*/
mix(typeName: string, mixOptions?: T.GQLitMixOptions) {
mix(typeName: string, mixOptions?: T.GQLiteralMixOptions) {
this.fields.push({
type: E.NodeType.MIX,
typeName,
@ -244,21 +274,25 @@ abstract class GQLitWithFields<Root, Schema, Opts, ListOpts> {
}
}
export class GQLitObjectType<Root, Schema = any> extends GQLitWithFields<
export class GQLiteralObjectType<Root, Schema = any> extends GQLitWithFields<
Root,
Schema,
T.GQLitFieldOptions<Root>,
T.GQLitListOptions<Root>
T.GQLiteralFieldOptions<Root>,
T.GQLiteralListOptions<Root>
> {
/**
* Metadata about the object type
*/
protected meta: T.GQLitTypeMetadata = {};
protected meta: T.GQLiteralTypeMetadata = {};
/**
* All interfaces the object implements.
*/
protected interfaces: string[] = [];
constructor(protected readonly name: string) {
super();
}
/**
* Declare that an object type implements a particular interface,
* by providing the name of the interface
@ -285,66 +319,131 @@ export class GQLitObjectType<Root, Schema = any> extends GQLitWithFields<
* Internal use only. Creates the configuration to create
* the GraphQL named type.
*/
toConfig(
name: string,
getType: T.GetTypeFn
): GraphQLObjectTypeConfig<any, any> {
toConfig(getType: T.GetTypeFn): GraphQLObjectTypeConfig<any, any> {
ensureBuilding();
const additional: Partial<GraphQLObjectTypeConfig<any, any>> = {};
if (this.meta.description) {
additional.description = withDeprecationComment(this.meta.description);
}
return {
name,
name: this.name,
interfaces: () => {
return this.interfaces.map(i => getType<GraphQLInterfaceType>(i));
return this.interfaces.map(i => {
const iface = getType(i);
if (!isInterfaceType(iface)) {
throw new Error(
`Expected ${this.name} - ${i} to be an interface, saw ${iface}`
);
}
return iface;
});
},
fields: () => {
return Object.keys(this.fields).reduce(
(fields, field) => {
fields[field] = {
type: getType<GraphQLOutputType>(field),
// args?: GraphQLFieldConfigArgumentMap;
// resolve?: GraphQLFieldResolver<TSource, TContext, TArgs>;
// subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs>;
// deprecationReason?: Maybe<string>;
// description?: Maybe<string>;
// astNode?: Maybe<FieldDefinitionNode>;
};
return fields;
},
{} as GraphQLFieldConfigMap<any, any>
);
const interfaceFields: Record<
string,
GraphQLFieldConfig<any, any>
> = {};
const typeFields: Record<string, GraphQLFieldConfig<any, any>> = {};
this.fields.forEach(field => {
switch (field.type) {
case E.NodeType.FIELD: {
typeFields[field.fieldName] = buildGraphQLField(
this.name,
field,
getType
);
break;
}
case E.NodeType.LIST: {
typeFields[field.fieldName] = buildGraphQLList(
this.name,
field,
getType
);
break;
}
case E.NodeType.MIX: {
//
break;
}
}
// typeFields[field] =
});
return {
...interfaceFields,
...typeFields,
};
},
};
}
}
export class GQLitInterfaceType<Root, Schema = any> extends GQLitWithFields<
export class GQLiteralInterfaceType<Root, Schema = any> extends GQLitWithFields<
Root,
Schema,
T.GQLitFieldOptions<Root>,
T.GQLitListOptions<Root>
T.GQLiteralFieldOptions<Root>,
T.GQLiteralListOptions<Root>
> {
resolveType(typeResolver: any) {}
/**
* Metadata about the object type
*/
protected meta: T.GQLiteralInterfaceMetadata = {};
constructor(protected readonly name: string) {
super();
}
/**
* Adds a description to the metadata for the interface type.
*/
description(description: string) {
this.meta.description = description;
}
/**
* Optionally provide a custom type resolver function. If one is not provided,
* the default implementation will call `isTypeOf` on each implementing
* Object type.
*/
resolveType(typeResolver: any) {
this.meta.resolveType = typeResolver;
}
/**
* Internal use only. Creates the configuration to create
* the GraphQL named type.
*/
toConfig(name: string): GraphQLInterfaceTypeConfig<any, any> {
toConfig(getType: T.GetTypeFn): GraphQLInterfaceTypeConfig<any, any> {
ensureBuilding();
let description;
return {
name,
name: this.name,
fields: () => {
return {};
const interfaceFields: GraphQLFieldConfigMap<any, any> = {};
this.fields.forEach(field =>
buildObjectField(interfaceFields, this.name, field)
);
return interfaceFields;
},
resolveType: this.meta.resolveType,
description,
// astNode?: Maybe<InterfaceTypeDefinitionNode>;
// extensionASTNodes?: Maybe<ReadonlyArray<InterfaceTypeExtensionNode>>;
};
}
}
export class GQLitInputObjectType extends GQLitWithFields<any, any, any, any> {
protected meta: T.GQLitTypeMetadata = {};
export class GQLiteralInputObjectType extends GQLitWithFields<
any,
any,
any,
any
> {
protected meta: T.GQLiteralTypeMetadata = {};
constructor(protected readonly name: string) {
super();
}
description(description: string) {
this.meta.description = description;
@ -354,10 +453,10 @@ export class GQLitInputObjectType extends GQLitWithFields<any, any, any, any> {
* Internal use only. Creates the configuration to create
* the GraphQL named type.
*/
toConfig(name: string): GraphQLInputObjectTypeConfig {
toConfig(getType: T.GetTypeFn): GraphQLInputObjectTypeConfig {
ensureBuilding();
return {
name,
name: this.name,
fields: () => {
return {};
},
@ -366,17 +465,22 @@ export class GQLitInputObjectType extends GQLitWithFields<any, any, any, any> {
}
/**
* A `GQLitAbstractType` contains fields that can be shared among `GQLitObjectType`,
* `GQLitInterface`, `GQLitInputObjectType` or other `GQLitAbstractType`s
* A `GQLiteralAbstractType` contains fields that can be shared among `GQLiteralObjectType`,
* `GQLiteralInterface`, `GQLiteralInputObjectType` or other `GQLiteralAbstractType`s
*
* Use the `.mix` to mixin the abstract type fields.
*/
export class GQLitAbstractType extends GQLitWithFields<any, any, any, any> {}
export class GQLiteralAbstractType extends GQLitWithFields<
any,
any,
any,
any
> {}
// Ignoring these, since they're only provided for a better developer experience,
// we don't want these to actually be picked up by intellisense.
// @ts-ignore
GQLitAbstractType.prototype.implements = function() {
GQLiteralAbstractType.prototype.implements = function() {
throw new Error(`
Oops, looks like you are trying to call "implements" on an abstract type definition.
Abstract types cannot implement interfaces, as they are not concrete types.
@ -384,6 +488,66 @@ GQLitAbstractType.prototype.implements = function() {
`);
};
function buildObjectField(
targetObject: GraphQLFieldConfigMap<any, any>,
typeName: string,
field: T.FieldDefType
) {
switch (field.type) {
case E.NodeType.MIX: {
}
case E.NodeType.MIX_ABSTRACT: {
}
}
}
function buildGraphQLField(
name: string,
field: T.FieldDef<any>,
getType: T.GetTypeFn
): GraphQLFieldConfig<any, any> {
const nullItem = Boolean(field.fieldOptions.nullable);
const fieldType = getType(field.fieldType);
if (!isObjectType(fieldType)) {
throw new Error(
`Expected ${name} - ${
field.fieldName
} to be an object type, saw ${fieldType}`
);
}
let args;
return {
type: nullItem ? fieldType : GraphQLNonNull(fieldType),
// args?: GraphQLFieldConfigArgumentMap;
// resolve?: GraphQLFieldResolver<TSource, TContext, TArgs>;
// subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs>;
// deprecationReason?: Maybe<string>;
// description?: Maybe<string>;
// astNode?: Maybe<FieldDefinitionNode>;
};
}
function buildGraphQLList(
name: string,
list: T.ListDef<any>,
getType: T.GetTypeFn
): GraphQLFieldConfig<any, any> {
const nullList = Boolean(list.fieldOptions.nullable);
const nullItem = Boolean(list.fieldOptions.itemNull);
const fieldType = getType(list.fieldType);
let args;
const type = GraphQLList(nullItem ? fieldType : GraphQLNonNull(fieldType));
return {
type: nullList ? type : GraphQLNonNull(type),
// args?: GraphQLFieldConfigArgumentMap;
// resolve?: GraphQLFieldResolver<TSource, TContext, TArgs>;
// subscribe?: GraphQLFieldResolver<TSource, TContext, TArgs>;
// deprecationReason?: Maybe<string>;
// description?: Maybe<string>;
// astNode?: Maybe<FieldDefinitionNode>;
};
}
interface TypeDataArg {
finalTypeMap: Record<string, GraphQLNamedType>;
pendingTypeMap: Record<
@ -394,34 +558,44 @@ interface TypeDataArg {
}
/**
* buildGQLitType
* buildGQLiteralType
*
* For internal use, builds concrete representations of the GQLit types for the Schema.
*
* @param name name of the type being built
* @param type GQLitType representation of the GraphQL type being built
* @param type GQLiteralType representation of the GraphQL type being built
* @param typeMap the map of currently resolved types, used to
* @param currentlyBuilding a Set of types currenty being "mixed", used to break circular dependencies
*/
export function buildGQLitType(
export function buildGQLiteralType(
name: string,
type: GQLitType,
typeData: TypeDataArg
type: GQLiteralType,
typeData: TypeDataArg,
getType?: T.GetTypeFn
): GraphQLNamedType {
isBuilding += 1;
const getTypeFn: T.GetTypeFn =
getType ||
((typeName: string) => {
const t = typeData.finalTypeMap[typeName];
if (!t) {
throw new Error(`Missing type ${typeName}`);
}
return t;
});
let returnType: GraphQLNamedType;
if (type instanceof GQLitObjectType) {
returnType = new GraphQLObjectType(type.toConfig(name);
} else if (type instanceof GQLitInputObjectType) {
returnType = new GraphQLInputObjectType(type.toConfig(name));
} else if (type instanceof GQLitEnumType) {
returnType = new GraphQLEnumType(type.toConfig(name, typeData));
} else if (type instanceof GQLitScalarType) {
returnType = new GraphQLScalarType(type.toConfig(name));
} else if (type instanceof GQLitUnionType) {
returnType = new GraphQLUnionType(type.toConfig(name));
} else if (type instanceof GQLitInterfaceType) {
returnType = new GraphQLInterfaceType(type.toConfig(name));
if (type instanceof GQLiteralObjectType) {
returnType = new GraphQLObjectType(type.toConfig(getTypeFn));
} else if (type instanceof GQLiteralInputObjectType) {
returnType = new GraphQLInputObjectType(type.toConfig(getTypeFn));
} else if (type instanceof GQLiteralInterfaceType) {
returnType = new GraphQLInterfaceType(type.toConfig(getTypeFn));
} else if (type instanceof GQLiteralUnionType) {
returnType = new GraphQLUnionType(type.toConfig(getTypeFn));
} else if (type instanceof GQLiteralEnumType) {
returnType = new GraphQLEnumType(type.toConfig(typeData));
} else if (type instanceof GQLiteralScalarType) {
returnType = new GraphQLScalarType(type.toConfig());
} else {
throw new Error(`Invalid value, expected GQLit type, saw ${type}`);
}

View File

@ -1,10 +1,10 @@
export {
GQLitAbstract,
GQLitScalar,
GQLitEnum,
GQLitInputObject,
GQLitInterface,
GQLitObject,
GQLitUnion,
GQLitSchema,
GQLiteralAbstractType,
GQLiteralScalar,
GQLiteralEnum,
GQLiteralInputObject,
GQLiteralInterface,
GQLiteralObject,
GQLiteralUnion,
GQLiteralSchema,
} from "./definitions";

View File

@ -3,10 +3,11 @@ import {
GraphQLFieldResolver,
GraphQLScalarTypeConfig,
GraphQLNamedType,
GraphQLTypeResolver,
} from "graphql";
declare global {
namespace GQLit { export interface Context {} }
namespace GQLiteral { export interface Context {} }
}
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
@ -31,30 +32,46 @@ export type FieldName<Root, Opts> = Opts extends {
export type GQLTypes = "ID" | "String" | "Int" | "Float" | string;
export type FieldDefType<Opts, ListOpts> =
| { type: NodeType.MIX; typeName: string; mixOptions: GQLitMixOptions }
| {
type: NodeType.FIELD;
fieldName: string;
fieldType: GQLTypes;
fieldOptions: Opts;
}
| {
type: NodeType.LIST;
fieldName: string;
fieldType: GQLTypes;
fieldOptions: ListOpts;
};
export type MixDef = {
type: NodeType.MIX;
typeName: string;
mixOptions: GQLiteralMixOptions;
};
export type MixAbstractDef = {
type: NodeType.MIX_ABSTRACT;
typeName: string;
mixOptions: GQLiteralMixOptions;
};
export type ListDef<Root> = {
type: NodeType.LIST;
fieldName: string;
fieldType: GQLTypes;
fieldOptions: GQLiteralListOptions<Root>;
};
export type FieldDef<Root> = {
type: NodeType.FIELD;
fieldName: string;
fieldType: GQLTypes;
fieldOptions: GQLiteralFieldOptions<Root>;
};
export type FieldDefType<Root = any> =
| MixDef
| MixAbstractDef
| FieldDef<Root>
| ListDef<Root>;
export type EnumDefType =
| { type: NodeType.MIX; typeName: string; mixOptions: GQLitMixOptions }
| { type: NodeType.ENUM_MEMBER; info: GQLitEnumMemberInfo };
| MixDef
| { type: NodeType.ENUM_MEMBER; info: GQLiteralEnumMemberInfo };
export type UnionTypeDef =
| { type: NodeType.MIX; typeName: string; mixOptions: GQLitMixOptions }
| { type: NodeType.ENUM_MEMBER; typeName: string };
| MixDef
| { type: NodeType.UNION_MEMBER; typeName: string };
export interface GQLitEnumMemberInfo {
export interface GQLiteralEnumMemberInfo {
value: string;
internalValue: string;
description?: string;
@ -64,17 +81,17 @@ export interface GQLitEnumMemberInfo {
* When you're mixing types/partials, you can pick or omit
* fields from the types you're mixing in.
*/
export interface GQLitMixOptions {
export interface GQLiteralMixOptions {
pick?: string[];
omit?: string[];
}
export interface GQLitDeprecationInfo {
export interface GQLiteralDeprecationInfo {
/**
* Date | YYYY-MM-DD formatted date of when this field
* became deprecated.
*/
startDate: string | Date;
startDate?: string | Date;
/**
* Reason for the deprecation.
*/
@ -82,10 +99,10 @@ export interface GQLitDeprecationInfo {
/**
* Field or usage that replaces the deprecated field.
*/
supersededBy: string;
supersededBy?: string;
}
export interface GQLitCommonOptions {
export interface GQLiteralCommonOptions {
/**
* Whether the field can be returned or input as null
* @default false
@ -105,10 +122,10 @@ export interface GQLitCommonOptions {
* Info about a field deprecation. Formatted as a string and provided with the
* deprecated directive on field/enum types and as a comment on input fields.
*/
deprecation?: GQLitDeprecationInfo;
deprecation?: GQLiteralDeprecationInfo;
}
export interface GQLitArgument {
export interface GQLiteralArgument {
/**
* The name of the argument
*/
@ -124,11 +141,11 @@ export interface GQLitArgument {
defaultValue?: string;
}
export interface GQLitFieldOptions<Root> extends GQLitCommonOptions {
export interface GQLiteralFieldOptions<Root> extends GQLiteralCommonOptions {
/**
* Any arguments defined
*/
args?: GQLitArgument[];
args?: GQLiteralArgument[];
/**
* Property to use to resolve the field
*/
@ -136,21 +153,21 @@ export interface GQLitFieldOptions<Root> extends GQLitCommonOptions {
/**
* Resolver used to resolve the field.
*/
resolve?: GraphQLFieldResolver<Root, GQLit.Context>;
resolve?: GraphQLFieldResolver<Root, GQLiteral.Context>;
/**
* Default value for the field, if none is returned.
*/
defaultValue?: any;
}
export interface GQLitInputFieldOptions extends GQLitCommonOptions {
export interface GQLiteralInputFieldOptions extends GQLiteralCommonOptions {
/**
* Default value for the input field, if one is not provided.
*/
defaultValue?: any;
}
export interface GQLitListOptions<Root> extends GQLitCommonOptions {
export interface GQLiteralListOptions<Root> extends GQLiteralCommonOptions {
/**
* Whether the list item can be a null value
* @default false
@ -159,10 +176,10 @@ export interface GQLitListOptions<Root> extends GQLitCommonOptions {
/**
* Resolves the list
*/
resolve?: GraphQLFieldResolver<Root, GQLit.Context>;
resolve?: GraphQLFieldResolver<Root, GQLiteral.Context>;
}
export interface GQLitScalarOptions
export interface GQLiteralScalarOptions
extends Omit<
GraphQLScalarTypeConfig<any, any>,
"name" | "astNode" | "extensionASTNodes"
@ -170,10 +187,10 @@ export interface GQLitScalarOptions
/**
* Any deprecation info for this scalar type
*/
deprecation?: GQLitDeprecationInfo;
deprecation?: GQLiteralDeprecationInfo;
}
export interface GQLitTypeMetadata {
export interface GQLiteralTypeMetadata {
/**
* Description for a GraphQL type
*/
@ -184,14 +201,27 @@ export interface GQLitTypeMetadata {
isTypeOf?: ((value: any) => boolean);
}
export interface GQLitSchemaConfig {
export interface GQLiteralInterfaceMetadata {
/**
* Description for a GraphQL type
*/
description?: string;
/**
* Optionally provide a custom type resolver function. If one is not provided,
* the default implementation will call `isTypeOf` on each implementing
* Object type.
*/
resolveType?: GraphQLTypeResolver<any, any>;
}
export interface GQLiteralSchemaConfig {
/**
* All of the GraphQL types
*/
types: any[];
}
export type GetTypeFn = <T>(t: string) => T;
export type GetTypeFn = (t: string) => GraphQLNamedType;
export type FieldTypeNames<S> = S extends any ? string : string;

View File

@ -263,7 +263,7 @@ aws4@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
babel-code-frame@^6.26.0:
babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
dependencies:
@ -498,7 +498,7 @@ buffer-from@1.x, buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
builtin-modules@^1.0.0:
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@ -551,7 +551,7 @@ chalk@^1.0.0, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.1:
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.3.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
dependencies:
@ -688,7 +688,7 @@ commander@2.18.0:
version "2.18.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970"
commander@^2.14.1, commander@^2.9.0:
commander@^2.12.1, commander@^2.14.1, commander@^2.9.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
@ -3242,6 +3242,12 @@ resolve@1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
resolve@^1.3.2:
version "1.8.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
dependencies:
path-parse "^1.0.5"
restore-cursor@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@ -3730,10 +3736,33 @@ ts-log@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.1.3.tgz#9e30aca1baffe7693a2e4142b8f07ecb01cb8340"
tslib@^1.9.0:
tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
tslint@^5.11.0:
version "5.11.0"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed"
dependencies:
babel-code-frame "^6.22.0"
builtin-modules "^1.1.1"
chalk "^2.3.0"
commander "^2.12.1"
diff "^3.2.0"
glob "^7.1.1"
js-yaml "^3.7.0"
minimatch "^3.0.4"
resolve "^1.3.2"
semver "^5.3.0"
tslib "^1.8.0"
tsutils "^2.27.2"
tsutils@^2.27.2:
version "2.29.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
dependencies:
tslib "^1.8.1"
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"