Various API cleanup, trying to simplify overall concepts

This commit is contained in:
Tim Griesser 2019-01-21 14:29:30 -05:00
parent 1119241b3c
commit b8d23c30f3
52 changed files with 1123 additions and 1663 deletions

2
.gitignore vendored
View File

@ -3,6 +3,4 @@ dist/*
website/build
examples/*/dist
website/static/playground-dist
website/api/dist
website/api/src/*.json
yarn-error.log

13
CHANGELOG.md Normal file
View File

@ -0,0 +1,13 @@
# Changelog
### Current:
- Adds [extendType]() for incrementally building types across a project.
- Object types defined by Nexus may be passed as the second argument for a field
- Major documentation overhaul
### 0.7.0-alpha.1
Changed the type-signatures around so fields which are not defined in the backing types associated with the
### 0.6.2

View File

@ -3,5 +3,3 @@ id: api-reference
title: API Reference
sidebar_label: API Reference
---
API reference has been [moved here](../api-reference)

24
docs/api/args.md Normal file
View File

@ -0,0 +1,24 @@
---
id: args
title: args
sidebar_label: "args: arg / *Arg"
hide_title: true
---
## Args: `arg()` / `*Arg()`
Defines an argument that can be used in any object or interface type
Takes the GraphQL type name and any options.
The value returned from this argument can be used multiple times in any valid `args` object value
@see https://graphql.github.io/learn/schema/#arguments
Alias for `arg("Float", options)`
Alias for `arg("ID", options)`
Alias for `arg("Int", options)`
Alias for `arg("String", options)`

53
docs/api/enumType.md Normal file
View File

@ -0,0 +1,53 @@
---
id: enumType
title: enumType
sidebar_label: enumType
hide_title: true
---
## enumType()
An 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 enumType:
As an array of enum values:
```ts
const Episode = enumType("Episode", ["NEWHOPE", "EMPIRE", "JEDI"]);
```
As an object, with a mapping of enum values to internal values:
```ts
const Episode = enumType("Episode", {
NEWHOPE: 4,
EMPIRE: 5,
JEDI: 6,
});
```
As a function, where other enums can be mixed in:
```ts
const Episode = enumType("Episode", (t) => {
t.mix("OneThroughThree");
t.mix("FourThroughSix");
t.mix("SevenThroughNine");
t.members(["OTHER"]);
t.description("All Movies in the Skywalker saga, or OTHER");
});
```
```graphql
"""
All Movies in the Skywalker saga, or OTHER
"""
enum Episode {
OTHER
}
```
@see https://graphql.github.io/learn/schema/#enumeration-types
###

9
docs/api/extendType.md Normal file
View File

@ -0,0 +1,9 @@
---
id: extendType
title: extendType
sidebar_label: extendType
hide_title: true
---
Defines a way to incrementally build types, by "extending" a type
from multiple locations in a project

View File

@ -0,0 +1,12 @@
---
id: inputObjectType
title: inputObjectType
sidebar_label: inputObjectType
hide_title: true
---
Defines a complex object which can be passed as an input value.
Unlike object types, input types do not have arguments, so they do not have resolvers or "backing types"
@see https://graphql.org/learn/schema/#input-types

26
docs/api/interfaceType.md Normal file
View File

@ -0,0 +1,26 @@
---
id: interfaceType
title: interfaceType
sidebar_label: interfaceType
hide_title: true
---
Like many type systems, GraphQL supports interfaces. An Interface is an
abstract type that includes a certain set of fields that a type must
include to implement the interface.
In GraphQL Nexus, you do not need to redefine the interface fields on the
implementing object types, instead you may use `.implements(interfaceName)`
and all of the interface fields will be added to the type.
```ts
const Node = interfaceType("Node", (t) => {
t.id("id", { description: "GUID for a resource" });
});
const User = objectType("User", (t) => {
t.implements("Node");
});
```
@see https://graphql.github.io/learn/schema/#interfaces

50
docs/api/objectType.md Normal file
View File

@ -0,0 +1,50 @@
---
id: objectType
title: objectType
sidebar_label: objectType
hide_title: true
---
# `objectType(typeName: string, fn: (t: ObjectTypeDef) => void)`
The most basic components of a GraphQL schema are object types, which just represent
a kind of object you can fetch from your service, and what fields it has.
```ts
const User = objectType("User", (t) => {
t.int("id", { description: "Id of the user" });
t.string("fullName", { description: "Full name of the user" });
t.field("status", "StatusEnum");
t.field("posts", "Post", {
list: true,
resolve(root, args, ctx) {
return ctx.getUser(root.id).posts();
},
});
});
const Post = objectType("Post", (t) => {
t.int("id");
t.string("title");
});
const StatusEnum = enumType("StatusEnum", {
ACTIVE: 1,
DISABLED: 2,
});
```
@see https://graphql.github.io/learn/schema/#object-types-and-fields
### ObjectDefinition
```ts
t.nullability(config: NullabilityConfig): void
```
Configures the nullability for the type, check the
documentation's "Getting Started" section to learn
more about GraphQL Nexus's assumptions and configuration
on nullability.
@param nullability

30
docs/api/scalarType.md Normal file
View File

@ -0,0 +1,30 @@
---
id: scalarType
title: scalarType
sidebar_label: scalarType
hide_title: true
---
A GraphQL object type has a name and fields, but at some point those fields have
to resolve to some concrete data. That's where the scalar types come in:
they represent the leaves of the query.
```js
const DateScalar = scalarType("Date", {
description: "Date custom scalar type",
parseValue(value) {
return new Date(value);
},
serialize(value) {
return value.getTime();
},
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return new Date(ast.value);
}
return null;
},
});
```
@see https://graphql.github.io/learn/schema/#scalar-types

21
docs/api/unionType.md Normal file
View File

@ -0,0 +1,21 @@
---
id: scalarType
title: scalarType
sidebar_label: scalarType
hide_title: true
---
Union types are very similar to interfaces, but they don't get to specify
any common fields between the types.
As a function, where other unions can be mixed in:
```ts
const CombinedResult = unionType("CombinedResult", (t) => {
t.mix("SearchResult");
t.members("AnotherType", "YetAnotherType");
t.resolveType((item) => item.name);
});
```
@see https://graphql.org/learn/schema/#union-types

View File

@ -0,0 +1,38 @@
---
id: testing
title: App Structure & Testing
sidebar_label: App Structure & Testing
---
GraphQL Nexus encourages separating your domain logic from the actual resolvers, so you can easily test and reuse your application logic independent of their use in GraphQL.
### Structuring Context
```ts
class MyAppContext {
constructor() {
this.userSource = new UserDataSource(this);
this.articleSource = new ArticleDataSource(this);
}
}
```
```ts
class UserDataSource {
constructor(protected context: AppContext) {}
byId(id: number) {
return this.byIdLoader.load(id);
}
createUser() {}
updateUser() {}
}
```
```ts
```
```ts
```

View File

@ -12,7 +12,7 @@ The development experience works best when you are using a tool like [Nodemon](h
## VSCode: Configuring a keyboard shortcut for "Go to Type Definition"
```
```json
{
"key": "cmd+.",
"command": "editor.action.goToTypeDefinition"
@ -35,23 +35,23 @@ code chunks. The most common approach is to break up types into files, either on
However you end up structuring your files, they ultimately all need to be imported and passed to the `makeSchema` function, and keeping a consistent approach to file naming makes it simpler
```
import * as userTypes from './schema/user'
import * as postTypes from './schema/post'
import * as commentTypes from './schema/comment'
```ts
import * as userTypes from "./schema/user";
import * as postTypes from "./schema/post";
import * as commentTypes from "./schema/comment";
```
You could also consolidate this in an `index.js` or similar export file:
```
export * from './user'
export * from './post'
export * from './comment'
```ts
export * from "./user";
export * from "./post";
export * from "./comment";
```
Using that file to build the schema:
```
```ts
import * as allTypes from './schema'
const schema = makeSchema({

View File

@ -4,10 +4,8 @@ title: Frequently Asked Questions
sidebar_label: FAQ
---
## Why not object
## Why isn't this a decorator / class-based API like [TypeGraphQL](https://github.com/19majkel94/type-graphql)
TODO
GraphQL Nexus aims to be both strongly type-safe, but also approachable from vanilla JavaScript. There are some issues with Nexus' approach to type generation and type resolution when utilizing decorators. Metadata reflection is
## Why isn't this a class-based API
TODO
We have ideas on how a decorator API could be added to the API, however we are waiting to see how the ecossytems

View File

@ -1,7 +0,0 @@
---
id: testing
title: Testing
sidebar_label: Testing
---
GraphQL Nexus encourages separating your domain logic from the actual resolvers, so you can easily test your application logic outside of the resolvers.

View File

@ -4,7 +4,7 @@ title: Type Generation Details
sidebar_label: Type Generation Details
---
This is relevant to JavaScript as well as TypeScript users, as tools like VSCode can utilize these types to aid in autocomplete. The goal is to have the best possible type coverage with the least possible manual type annotation.
This is relevant to JavaScript as well as TypeScript users, as tools like VSCode can utilize these types to aid in autocomplete. A core goal of Nexus is to have the best possible type coverage with the least possible manual type annotation.
## Overview

View File

@ -1,15 +0,0 @@
---
id: typescript-setup
title: TypeScript Configuration
sidebar_label: Use with TypeScript
---
GraphQL Nexus was designed with TypeScript in mind. The goal is to have the best possible type coverage with the least possible manual type annotation. In order to do this, we have created a dedicated code generation template for graphql-code-generator.
```ts
const schema = makeSchema({
types: [
/* All types here */
],
});
```

View File

@ -6,7 +6,7 @@ title: Why GraphQL Nexus
GraphQL Nexus was born out of my experiences building several production GraphQL APIs. I have been using and actively contributing back to various tooling since the project was initially released. I've had the fortune of being able to work in in different languages and frameworks on these projects; one with vanilla [graphql-js](https://github.com/graphql/graphql-js), another schema-first with [graph.ql](https://github.com/matthewmueller/graph.ql) and [graphql-tools](https://github.com/apollographql/graphql-tools). Following that with [graphene-python](https://docs.graphene-python.org/en/latest/) and most recently with a bit of [graphql-ruby](http://graphql-ruby.org/).
After working with the toolkits in other languages, it felt like the JavaScript implementations were lacking a bit. <!--truncate--> Schema-first development starts out great, by simply expressing your schema in the GraphQL Schema Definition Language (SDL) and providing resolvers matching to the types as needed you are up and running fast! No need for tons of requires or "overhead" to get a GraphQL server running.
After working with the toolkits in other languages, it felt like the JavaScript implementations were lacking a bit. Schema-first development starts out great, by simply expressing your schema in the GraphQL Schema Definition Language (SDL) and providing resolvers matching to the types as needed you are up and running fast! No need for tons of requires or "overhead" to get a GraphQL server running.
As your schema then grows to hundreds or thousands of types, manually curating these SDL fragments becomes tedious. Documentation changes can be tough. Modifying fields on interfaces can require manual changes to many implementing types, a process that can be quite error prone.
@ -15,7 +15,3 @@ _If only there were a way to combine the simplicity of schema-first development,
GraphQL Nexus aims to fill that void, making the process as simple as possible while also making good use of the runtime to introduce powerful ways of composing types, introducing type or schema wide changes, and much more.
The core idea of GraphQL Nexus draws from basing the schema off the SDL - it uses the type names as string literals rather than as imported to reference types! How can that be type safe, you might be wondering? By combining automatic type generation with some of the more powerful features of TypeScript - type merging, conditional types, and type inference, we can know exactly which type names we are able to use in which position. We can know both the parameters and the return type of resolvers without providing any type annotation.
## Non-Goals
The project stays intentionally unopinionated on resolvers or the shape of a context object. It should be a tool you can layer on top of with higher level abstractions or opinions. When you have more information about a database

View File

@ -8,7 +8,7 @@
import * as t from "./typeDefs"
declare global {
interface GraphQLNexusGen extends GraphQLNexusGenTypes {}
interface NexusGen extends NexusGenTypes {}
}
// Maybe Promise
@ -151,7 +151,7 @@ export type TripUpdateResponse_ReturnType = {
success: MaybeThunk<MaybePromise<boolean>>;
}
export interface GraphQLNexusGenArgTypes {
export interface NexusGenArgTypes {
Query: {
launch: QueryLaunchArgs;
launches: QueryLaunchesArgs;
@ -166,7 +166,7 @@ export interface GraphQLNexusGenArgTypes {
};
}
export interface GraphQLNexusGenRootTypes {
export interface NexusGenRootTypes {
Query: {};
Launch: LaunchRootType;
Mission: MissionRootType;
@ -177,7 +177,7 @@ export interface GraphQLNexusGenRootTypes {
TripUpdateResponse: TripUpdateResponseRootType;
}
export interface GraphQLNexusGenReturnTypes {
export interface NexusGenReturnTypes {
Query: {
launch: QueryLaunchReturnType;
launches: QueryLaunchesReturnType;
@ -221,10 +221,10 @@ export interface GraphQLNexusGenReturnTypes {
};
}
export interface GraphQLNexusGenTypes {
argTypes: GraphQLNexusGenArgTypes;
backingTypes: GraphQLNexusGenRootTypes;
returnTypes: GraphQLNexusGenReturnTypes;
export interface NexusGenTypes {
argTypes: NexusGenArgTypes;
backingTypes: NexusGenRootTypes;
returnTypes: NexusGenReturnTypes;
context: t.Context;
enums: {
PatchSize: PatchSize;
@ -249,15 +249,15 @@ export interface GraphQLNexusGenTypes {
};
inputObjects: {};
allInputTypes:
| Extract<keyof GraphQLNexusGenTypes['inputObjects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['inputObjects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
allOutputTypes:
| Extract<keyof GraphQLNexusGenTypes['objects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['unions'], string>
| Extract<keyof GraphQLNexusGenTypes['interfaces'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['objects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['unions'], string>
| Extract<keyof NexusGenTypes['interfaces'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
}
export type Gen = GraphQLNexusGenTypes;
export type Gen = NexusGenTypes;

View File

@ -8,7 +8,7 @@
declare global {
interface GraphQLNexusGen extends GraphQLNexusGenTypes {}
interface NexusGen extends NexusGenTypes {}
}
// Maybe Promise
@ -207,7 +207,7 @@ export type VoteType = "CANCEL" | "DOWN" | "UP";
export type CacheControlScope = "PRIVATE" | "PUBLIC";
export interface GraphQLNexusGenArgTypes {
export interface NexusGenArgTypes {
Query: {
entry: QueryEntryArgs;
feed: QueryFeedArgs;
@ -222,7 +222,7 @@ export interface GraphQLNexusGenArgTypes {
};
}
export interface GraphQLNexusGenRootTypes {
export interface NexusGenRootTypes {
Query: {};
User: UserRootType;
Entry: EntryRootType;
@ -232,7 +232,7 @@ export interface GraphQLNexusGenRootTypes {
Mutation: {};
}
export interface GraphQLNexusGenReturnTypes {
export interface NexusGenReturnTypes {
Query: {
currentUser: QueryCurrentUserReturnType;
entry: QueryEntryReturnType;
@ -280,10 +280,10 @@ export interface GraphQLNexusGenReturnTypes {
};
}
export interface GraphQLNexusGenTypes {
argTypes: GraphQLNexusGenArgTypes;
backingTypes: GraphQLNexusGenRootTypes;
returnTypes: GraphQLNexusGenReturnTypes;
export interface NexusGenTypes {
argTypes: NexusGenArgTypes;
backingTypes: NexusGenRootTypes;
returnTypes: NexusGenReturnTypes;
context: unknown;
enums: {
FeedType: FeedType;
@ -309,15 +309,15 @@ export interface GraphQLNexusGenTypes {
};
inputObjects: {};
allInputTypes:
| Extract<keyof GraphQLNexusGenTypes['inputObjects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['inputObjects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
allOutputTypes:
| Extract<keyof GraphQLNexusGenTypes['objects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['unions'], string>
| Extract<keyof GraphQLNexusGenTypes['interfaces'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['objects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['unions'], string>
| Extract<keyof NexusGenTypes['interfaces'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
}
export type Gen = GraphQLNexusGenTypes;
export type Gen = NexusGenTypes;

View File

@ -8,7 +8,7 @@
declare global {
interface GraphQLNexusGen extends GraphQLNexusGenTypes {}
interface NexusGen extends NexusGenTypes {}
}
// Maybe Promise
@ -54,17 +54,17 @@ export type BazRootType = FooRootType;
export type Baz_ReturnType = Foo_ReturnType;
export interface GraphQLNexusGenArgTypes {
export interface NexusGenArgTypes {
}
export interface GraphQLNexusGenRootTypes {
export interface NexusGenRootTypes {
Bar: BarRootType;
Baz: BazRootType;
Query: {};
Foo: FooRootType;
}
export interface GraphQLNexusGenReturnTypes {
export interface NexusGenReturnTypes {
Query: {
bar: QueryBarReturnType;
};
@ -79,10 +79,10 @@ export interface GraphQLNexusGenReturnTypes {
};
}
export interface GraphQLNexusGenTypes {
argTypes: GraphQLNexusGenArgTypes;
backingTypes: GraphQLNexusGenRootTypes;
returnTypes: GraphQLNexusGenReturnTypes;
export interface NexusGenTypes {
argTypes: NexusGenArgTypes;
backingTypes: NexusGenRootTypes;
returnTypes: NexusGenReturnTypes;
context: unknown;
enums: {};
objects: {
@ -103,15 +103,15 @@ export interface GraphQLNexusGenTypes {
InputType: InputType;
};
allInputTypes:
| Extract<keyof GraphQLNexusGenTypes['inputObjects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['inputObjects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
allOutputTypes:
| Extract<keyof GraphQLNexusGenTypes['objects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['unions'], string>
| Extract<keyof GraphQLNexusGenTypes['interfaces'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['objects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['unions'], string>
| Extract<keyof NexusGenTypes['interfaces'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
}
export type Gen = GraphQLNexusGenTypes;
export type Gen = NexusGenTypes;

View File

@ -3,8 +3,7 @@ import { objectType } from "nexus";
export const Droid = objectType("Droid", (t) => {
t.description("A mechanical creature in the Star Wars universe.");
t.implements("Character");
t.string("primaryFunction", {
description: "The primary function of the droid.",
default: "N/A",
});
t.string("primaryFunction", (o) => o.primary_function || "N/A").description(
"The primary function of the droid."
);
});

View File

@ -8,7 +8,7 @@
import * as swapi from "./types/backingTypes"
declare global {
interface GraphQLNexusGen extends GraphQLNexusGenTypes {}
interface NexusGen extends NexusGenTypes {}
}
// Maybe Promise
@ -95,7 +95,7 @@ export type CharacterRootType = swapi.Character;
export type Character_ReturnType = swapi.Character;
export interface GraphQLNexusGenArgTypes {
export interface NexusGenArgTypes {
Query: {
droid: QueryDroidArgs;
hero: QueryHeroArgs;
@ -112,14 +112,14 @@ export interface GraphQLNexusGenArgTypes {
};
}
export interface GraphQLNexusGenRootTypes {
export interface NexusGenRootTypes {
Character: CharacterRootType;
Query: {};
Droid: DroidRootType;
Human: HumanRootType;
}
export interface GraphQLNexusGenReturnTypes {
export interface NexusGenReturnTypes {
Query: {
droid: QueryDroidReturnType;
hero: QueryHeroReturnType;
@ -147,10 +147,10 @@ export interface GraphQLNexusGenReturnTypes {
};
}
export interface GraphQLNexusGenTypes {
argTypes: GraphQLNexusGenArgTypes;
backingTypes: GraphQLNexusGenRootTypes;
returnTypes: GraphQLNexusGenReturnTypes;
export interface NexusGenTypes {
argTypes: NexusGenArgTypes;
backingTypes: NexusGenRootTypes;
returnTypes: NexusGenReturnTypes;
context: swapi.ContextType;
enums: {
Episode: Episode;
@ -172,15 +172,15 @@ export interface GraphQLNexusGenTypes {
};
inputObjects: {};
allInputTypes:
| Extract<keyof GraphQLNexusGenTypes['inputObjects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['inputObjects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
allOutputTypes:
| Extract<keyof GraphQLNexusGenTypes['objects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['unions'], string>
| Extract<keyof GraphQLNexusGenTypes['interfaces'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['objects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['unions'], string>
| Extract<keyof NexusGenTypes['interfaces'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
}
export type Gen = GraphQLNexusGenTypes;
export type Gen = NexusGenTypes;

View File

@ -9,7 +9,7 @@ import * as t from "./types/index"
import ts from "typescript"
declare global {
interface GraphQLNexusGen extends GraphQLNexusGenTypes {}
interface NexusGen extends NexusGenTypes {}
}
// Maybe Promise
@ -2016,7 +2016,7 @@ export type JSDocTagRootType = ts.JSDocTag;
export type JSDocTag_ReturnType = ts.JSDocTag;
export interface GraphQLNexusGenArgTypes {
export interface NexusGenArgTypes {
Query: {
parseFile: QueryParseFileArgs;
};
@ -2248,7 +2248,7 @@ export interface GraphQLNexusGenArgTypes {
};
}
export interface GraphQLNexusGenRootTypes {
export interface NexusGenRootTypes {
Node: NodeRootType;
HasJSDoc: HasJSDocRootType;
JSDocTag: JSDocTagRootType;
@ -2322,7 +2322,7 @@ export interface GraphQLNexusGenRootTypes {
VariableDeclaration: VariableDeclarationRootType;
}
export interface GraphQLNexusGenReturnTypes {
export interface NexusGenReturnTypes {
Query: {
parseFile: QueryParseFileReturnType;
};
@ -3182,10 +3182,10 @@ export interface GraphQLNexusGenReturnTypes {
};
}
export interface GraphQLNexusGenTypes {
argTypes: GraphQLNexusGenArgTypes;
backingTypes: GraphQLNexusGenRootTypes;
returnTypes: GraphQLNexusGenReturnTypes;
export interface NexusGenTypes {
argTypes: NexusGenArgTypes;
backingTypes: NexusGenRootTypes;
returnTypes: NexusGenReturnTypes;
context: t.ContextType;
enums: {
NodeFlags: NodeFlags;
@ -3276,15 +3276,15 @@ export interface GraphQLNexusGenTypes {
};
inputObjects: {};
allInputTypes:
| Extract<keyof GraphQLNexusGenTypes['inputObjects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['inputObjects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
allOutputTypes:
| Extract<keyof GraphQLNexusGenTypes['objects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['unions'], string>
| Extract<keyof GraphQLNexusGenTypes['interfaces'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['objects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['unions'], string>
| Extract<keyof NexusGenTypes['interfaces'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
}
export type Gen = GraphQLNexusGenTypes;
export type Gen = NexusGenTypes;

View File

@ -20,7 +20,7 @@ export const JSDocTag = interfaceType("JSDocTag", (t) => {
});
});
const jsDocTag = (t: core.ObjectTypeDef<GraphQLNexusGen, any>) =>
const jsDocTag = (t: core.ObjectTypeDef<NexusGen, any>) =>
t.implements("JSDocTag");
export const JSDocUnknownTag = objectType("JSDocUnknownTag", jsDocTag);

View File

@ -1,5 +1,4 @@
import { core } from "nexus";
import typescript from "typescript";
import { Gen } from "../ts-ast-reader-typegen";
export function withTypeArguments(t: core.ObjectTypeDef<Gen, any>) {

View File

@ -1,4 +1,15 @@
/**
* @type {jest.InitialOptions}
*/
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
preset: "ts-jest",
testEnvironment: "node",
globals: {
"ts-jest": {
isolatedModules: !process.env.CI,
diagnostics: {
warnOnly: !process.env.CI,
},
},
},
};

View File

@ -39,6 +39,8 @@
"@types/jest": "^23.3.7",
"@types/node": "^10.12.2",
"@types/prettier": "^1.15.2",
"@types/faker": "^4.1.5",
"faker": "^4.1.0",
"graphql": "^14.0.2",
"husky": "^1.1.2",
"jest": "^23.6.0",

View File

@ -1,156 +0,0 @@
/// <reference types="jest" />
import { GraphQLEnumType, GraphQLObjectType } from "graphql";
import { buildTypes, objectType, enumType } from "../";
describe("nexus", () => {
describe("enumType", () => {
const PrimaryColors = enumType<any>("PrimaryColors", (t) => {
t.members(["RED", "YELLOW", "BLUE"]);
});
const RainbowColors = enumType<any>("RainbowColors", (t) => {
t.mix("PrimaryColors");
t.members(["ORANGE", "GREEN", "VIOLET"]);
});
const AdditivePrimaryColors = enumType<any>(
"AdditivePrimaryColors",
(t) => {
t.mix("PrimaryColors", { omit: ["YELLOW"] });
t.members(["GREEN"]);
}
);
const CircularRefTestA = enumType<any>("CircularA", (t) => {
t.mix("CircularB");
t.members(["A"]);
});
const CircularRefTestB = enumType<any>("CircularB", (t) => {
t.mix("CircularA");
t.members(["B"]);
});
it("builds an enum", () => {
const types = buildTypes<{ PrimaryColors: GraphQLEnumType }>([
PrimaryColors,
]);
expect(types.typeMap.PrimaryColors).toBeInstanceOf(GraphQLEnumType);
expect(
types.typeMap.PrimaryColors.getValues().map((v) => v.value)
).toEqual(["RED", "YELLOW", "BLUE"]);
});
it("can mix enums", () => {
const types = buildTypes<{ RainbowColors: GraphQLEnumType }>([
PrimaryColors,
RainbowColors,
]);
expect(types.typeMap.RainbowColors).toBeInstanceOf(GraphQLEnumType);
expect(
types.typeMap.RainbowColors.getValues().map((v) => v.value)
).toEqual(["RED", "YELLOW", "BLUE", "ORANGE", "GREEN", "VIOLET"]);
});
it("can omit with mix", () => {
const types = buildTypes<{
AdditivePrimaryColors: GraphQLEnumType;
}>([PrimaryColors, AdditivePrimaryColors]);
expect(types.typeMap.AdditivePrimaryColors).toBeInstanceOf(
GraphQLEnumType
);
expect(
types.typeMap.AdditivePrimaryColors.getValues().map((v) => v.value)
).toEqual(["RED", "BLUE", "GREEN"]);
});
it("can pick with mix", () => {
const FavoriteColors = enumType<any>("FavoriteColors", (t) => {
t.mix("RainbowColors", { pick: ["RED", "GREEN"] });
});
const types = buildTypes<{ FavoriteColors: GraphQLEnumType }>([
PrimaryColors,
RainbowColors,
FavoriteColors,
]);
expect(types.typeMap.FavoriteColors).toBeInstanceOf(GraphQLEnumType);
expect(
types.typeMap.FavoriteColors.getValues().map((v) => v.value)
).toEqual(["RED", "GREEN"]);
});
it("can map internal values", () => {
const Internal = enumType("Internal", (t) => {
t.members([
{ name: "A", value: "--A--" },
{ name: "B", value: "--B--" },
]);
});
const types = buildTypes<{ Internal: GraphQLEnumType }>([Internal]);
expect(types.typeMap.Internal.getValues().map((v) => v.name)).toEqual([
"A",
"B",
]);
expect(types.typeMap.Internal.getValues().map((v) => v.value)).toEqual([
"--A--",
"--B--",
]);
});
it("has shorthand syntax for enum mapping", () => {
const MappedArr = enumType("MappedArr", ["A", "B"]);
const MappedObj = enumType("MappedObj", {
a: 1,
b: 2,
});
const types = buildTypes<{
MappedObj: GraphQLEnumType;
MappedArr: GraphQLEnumType;
}>([MappedObj, MappedArr]);
expect(types.typeMap.MappedArr.getValues().map((v) => v.name)).toEqual([
"A",
"B",
]);
expect(types.typeMap.MappedObj.getValues().map((v) => v.name)).toEqual([
"a",
"b",
]);
expect(types.typeMap.MappedObj.getValues().map((v) => v.value)).toEqual([
1,
2,
]);
});
it("throws if the enum has no members", () => {
const NoMembers = enumType("NoMembers", () => {});
expect(() => {
const types = buildTypes<{ NoMembers: GraphQLEnumType }>([NoMembers]);
expect(types.typeMap.NoMembers.getValues()).toHaveLength(0);
}).toThrow("must have at least one member");
});
it("throws when building with a circular reference", () => {
expect(() => {
buildTypes([CircularRefTestA, CircularRefTestB]);
}).toThrowError(
"GraphQL Nexus: Circular dependency detected, while building types"
);
});
});
describe("objectType", () => {
const Account = objectType("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 = buildTypes<{ Account: GraphQLObjectType }>([Account]);
expect(Object.keys(type.typeMap.Account.getFields()).sort()).toEqual([
"email",
"id",
"name",
]);
});
});

View File

@ -73,8 +73,7 @@ export function typegenAutoConfig(options: Types.TypegenAutoConfigOptions) {
// e.g. in the Playground, it doesn't break things.
// Yeah, this doesn't exist in Node 6, but since this is a new
// lib and that's super close to EOL so if you really need it.
// open a PR :)
// lib and Node 6 is close to EOL so if you really need it, open a PR :)
const fs = require("fs") as typeof import("fs");
const util = require("util") as typeof import("util");
const readFile = util.promisify(fs.readFile);

View File

@ -34,6 +34,7 @@ import {
isUnionType,
GraphQLSchema,
specifiedDirectives,
Thunk,
} from "graphql";
import { Metadata } from "./metadata";
import {
@ -174,6 +175,14 @@ export class SchemaBuilder {
});
}
extendType(
config: Types.ExtendTypeConfig
): Thunk<GraphQLFieldConfigMap<any, any>> {
return () => {
return {};
};
}
objectType(config: Types.ObjectTypeConfig) {
this.metadata.addObjectType(config);
return new GraphQLObjectType({
@ -181,18 +190,18 @@ export class SchemaBuilder {
interfaces: () => config.interfaces.map((i) => this.getInterface(i)),
description: config.description,
fields: () => {
const interfaceFields: GraphQLFieldConfigMap<any, any> = {};
const interfaceFieldsMap: GraphQLFieldConfigMap<any, any> = {};
const allInterfaces = config.interfaces.map((i) =>
this.getInterface(i)
);
allInterfaces.forEach((i) => {
const iFields = i.getFields();
const interfaceFields = i.getFields();
// We need to take the interface fields and reconstruct them
// this actually simplifies things becuase if we've modified
// the field at all it needs to happen here.
Object.keys(iFields).forEach((iFieldName) => {
const { isDeprecated, args, ...rest } = iFields[iFieldName];
interfaceFields[iFieldName] = {
Object.keys(interfaceFields).forEach((iFieldName) => {
const { isDeprecated, args, ...rest } = interfaceFields[iFieldName];
interfaceFieldsMap[iFieldName] = {
...rest,
args: args.reduce(
(result: GraphQLFieldConfigArgumentMap, arg) => {
@ -206,7 +215,7 @@ export class SchemaBuilder {
});
});
return {
...interfaceFields,
...interfaceFieldsMap,
...this.buildObjectFields(config),
};
},
@ -255,34 +264,31 @@ export class SchemaBuilder {
protected buildEnumMembers(config: Types.EnumTypeConfig) {
let values: GraphQLEnumValueConfigMap = {};
config.members.forEach((member) => {
switch (member.item) {
case Types.NodeType.ENUM_MEMBER:
values[member.info.name] = {
value: member.info.value,
description: member.info.description,
};
break;
case Types.NodeType.MIX:
const {
mixOptions: { pick, omit },
typeName,
} = member;
const enumToMix = this.getEnum(typeName);
enumToMix.getValues().forEach((val) => {
if (pick && pick.indexOf(val.name) === -1) {
return;
}
if (omit && omit.indexOf(val.name) !== -1) {
return;
}
values[val.name] = {
description: val.description,
deprecationReason: val.deprecationReason,
value: val.value,
// astNode: val.astNode,
};
});
}
values[member.name] = {
value: member.value,
description: member.description,
};
});
config.mixed.forEach((mixed) => {
const {
options: { pick, omit },
typeName,
} = mixed;
const enumToMix = this.getEnum(typeName);
enumToMix.getValues().forEach((val) => {
if (pick && pick.indexOf(val.name) === -1) {
return;
}
if (omit && omit.indexOf(val.name) !== -1) {
return;
}
values[val.name] = {
description: val.description,
deprecationReason: val.deprecationReason,
value: val.value,
// astNode: val.astNode,
};
});
});
if (!Object.keys(values).length) {
throw new Error(
@ -295,27 +301,23 @@ export class SchemaBuilder {
protected buildUnionMembers(config: Types.UnionTypeConfig) {
const unionMembers: GraphQLObjectType[] = [];
config.members.forEach((member) => {
switch (member.item) {
case Types.NodeType.UNION_MEMBER:
unionMembers.push(this.getObjectType(member.typeName));
break;
case Types.NodeType.MIX:
const {
mixOptions: { pick, omit },
typeName,
} = member;
const unionToMix = this.getUnion(typeName);
unionToMix.getTypes().forEach((type) => {
if (pick && pick.indexOf(type.name) === -1) {
return;
}
if (omit && omit.indexOf(type.name) !== -1) {
return;
}
unionMembers.push(type);
});
break;
}
unionMembers.push(this.getObjectType(member));
});
config.mixed.forEach((mixed) => {
const {
options: { pick, omit },
typeName,
} = mixed;
const unionToMix = this.getUnion(typeName);
unionToMix.getTypes().forEach((type) => {
if (pick && pick.indexOf(type.name) === -1) {
return;
}
if (omit && omit.indexOf(type.name) !== -1) {
return;
}
unionMembers.push(type);
});
});
if (!Object.keys(unionMembers).length) {
throw new Error(
@ -330,17 +332,10 @@ export class SchemaBuilder {
): GraphQLFieldConfigMap<any, any> {
const fieldMap: GraphQLFieldConfigMap<any, any> = {};
typeConfig.fields.forEach((field) => {
switch (field.item) {
case Types.NodeType.MIX:
throw new Error("TODO");
break;
case Types.NodeType.FIELD:
fieldMap[field.config.name] = this.buildObjectField(
field.config,
typeConfig
);
break;
}
fieldMap[field.name] = this.buildObjectField(field, typeConfig);
});
typeConfig.mixed.forEach((mixed) => {
throw new Error("TODO");
});
return fieldMap;
}
@ -350,17 +345,10 @@ export class SchemaBuilder {
): GraphQLInputFieldConfigMap {
const fieldMap: GraphQLInputFieldConfigMap = {};
typeConfig.fields.forEach((field) => {
switch (field.item) {
case Types.NodeType.MIX:
throw new Error("TODO");
break;
case Types.NodeType.FIELD:
fieldMap[field.config.name] = this.buildInputObjectField(
field.config,
typeConfig
);
break;
}
fieldMap[field.name] = this.buildInputObjectField(field, typeConfig);
});
typeConfig.mixed.forEach((mixed) => {
throw new Error("TODO");
});
return fieldMap;
}
@ -438,7 +426,7 @@ export class SchemaBuilder {
protected decorateOutputType(
type: GraphQLOutputType,
fieldConfig: Types.FieldConfig,
fieldConfig: Types.FieldDef,
typeConfig: Types.ObjectTypeConfig | Types.InterfaceTypeConfig
) {
return this.decorateType(type, fieldConfig, typeConfig, false);
@ -467,19 +455,19 @@ export class SchemaBuilder {
*/
protected decorateType(
type: GraphQLOutputType,
fieldConfig: Types.Omit<Types.FieldConfig, "type" | "default">,
fieldConfig: Types.Omit<Types.FieldDef, "type" | "default">,
typeConfig: Types.ObjectTypeConfig | Types.InterfaceTypeConfig,
isInput: false
): GraphQLOutputType;
protected decorateType(
type: GraphQLInputType,
fieldConfig: Types.Omit<Types.FieldConfig, "type" | "default">,
fieldConfig: Types.Omit<Types.FieldDef, "type" | "default">,
typeConfig: Types.InputTypeConfig,
isInput: true
): GraphQLInputType;
protected decorateType(
type: any,
fieldConfig: Types.Omit<Types.FieldConfig, "type" | "default">,
fieldConfig: Types.Omit<Types.FieldDef, "type" | "default">,
typeConfig:
| Types.ObjectTypeConfig
| Types.InterfaceTypeConfig

View File

@ -1,7 +1,6 @@
/**
* Any common types / constants that would otherwise be circular-imported
*/
export const SCALAR_TYPES = {
Int: "number",
String: "string",

View File

@ -9,6 +9,8 @@ import {
DirectiveLocationEnum,
GraphQLObjectType,
GraphQLDirective,
GraphQLFieldConfigMap,
Thunk,
} from "graphql";
import { dedent } from "./utils";
import { SchemaBuilder, isNamedTypeDef } from "./builder";
@ -26,25 +28,162 @@ export { SchemaBuilder, isNamedTypeDef, Metadata };
export { typegenAutoConfig } from "./autoConfig";
declare global {
interface GraphQLNexusGen {}
interface NexusGen {}
}
/**
* Provided to the [objectType](#objectType) function, this
* Contains methods shared between `objectType`, `extendType`, and `interfaceType`
*/
export class ObjectTypeDef<
GenTypes = GraphQLNexusGen,
export class AbstractOutputObject<
GenTypes = NexusGen,
TypeName extends string = any
> {
protected typeConfig: Types.OutputObjectConfig;
constructor(readonly name: string) {
this.typeConfig = {
name,
fields: [],
};
}
/**
* Add an ID field type to the object schema.
*/
id<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
string
>
) {
this.field(name, "ID", ...opts);
}
/**
* Add an Int field type to the object schema.
*/
int<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
number
>
) {
this.field(name, "Int", ...opts);
}
/**
* Add a Float field type to the object schema.
*/
float<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
number
>
) {
this.field(name, "Float", ...opts);
}
/**
* Add a String field type to the object schema.
*/
string<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
string
>
) {
this.field(name, "String", ...opts);
}
/**
* Add a Boolean field type to the object schema.
*/
boolean<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
boolean
>
) {
this.field(name, "Boolean", ...opts);
}
/**
* Adds a new field to the object type
*/
field<FieldName extends string>(
name: FieldName,
type:
| Types.AllOutputTypes<GenTypes>
| Types.WrappedOutput
| Types.BaseScalars,
...opts: Types.ConditionalOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
any
>
) {
let options: Types.OutputFieldOpts<GenTypes, TypeName, any> = {};
if (typeof opts[0] === "function") {
options.resolve = opts[0];
} else {
options = { ...opts[0] };
}
this.typeConfig.fields.push({
name,
type,
...options,
});
}
protected addField(name: string, type: any, ...opts: any[]) {
let options: Types.OutputFieldOpts<GenTypes, TypeName, any> = {};
if (typeof opts[0] === "function") {
options.resolve = opts[0];
} else {
options = { ...opts[0] };
}
this.typeConfig.fields.push({
name,
type,
...options,
});
}
}
/**
* Container object for defining the `GraphQLObjectType`
*/
export class ObjectTypeDef<
GenTypes = NexusGen,
TypeName extends string = any
> extends AbstractOutputObject<GenTypes, TypeName> {
/**
* All metadata about the object type
*/
protected typeConfig: Types.ObjectTypeConfig;
constructor(readonly name: string) {
super(name);
this.typeConfig = {
name,
fields: [],
mixed: [],
interfaces: [],
directives: [],
fieldModifications: {},
@ -55,85 +194,10 @@ export class ObjectTypeDef<
* Mixes in an existing field definition or object type
* with the current type.
*/
mix(typeName: string, options?: Types.MixOpts<any>) {
this.typeConfig.fields.push({
item: Types.NodeType.MIX,
mix(typeName: string, options: Types.MixOpts<any> = {}) {
this.typeConfig.mixed.push({
typeName,
mixOptions: options || {},
});
}
/**
* Add an ID field type to the object schema.
*/
id<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "ID", ...opts);
}
/**
* Add an Int field type to the object schema.
*/
int<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "Int", ...opts);
}
/**
* Add a Float field type to the object schema.
*/
float<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "Float", ...opts);
}
/**
* Add a String field type to the object schema.
*/
string<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "String", ...opts);
}
/**
* Add a Boolean field type to the object schema.
*/
boolean<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "Boolean", ...opts);
}
/**
* Adds a new field to the object type
*/
field<FieldName extends string>(
name: FieldName,
type: Types.AllOutputTypes<GenTypes> | Types.BaseScalars,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
let options: Types.OutputFieldOpts<GenTypes, TypeName, any> = {};
if (typeof opts[0] === "function") {
options.resolve = opts[0];
} else {
options = { ...opts[0] };
}
this.typeConfig.fields.push({
item: Types.NodeType.FIELD,
config: {
name,
type,
...options,
},
options,
});
}
@ -170,7 +234,7 @@ export class ObjectTypeDef<
*/
modify<FieldName extends Types.ObjectTypeFields<GenTypes, TypeName>>(
field: FieldName,
options: Types.ModifyFieldOpts<GenTypes, TypeName, FieldName>
options: Types.ModifyFieldOpts<GenTypes, TypeName, FieldName, any>
): void {
this.typeConfig.fieldModifications[field as string] = options;
}
@ -204,10 +268,8 @@ export class ObjectTypeDef<
* documentation's "Getting Started" section to learn
* more about GraphQL Nexus's assumptions and configuration
* on nullability.
*
* @param nullability
*/
nullability(nullability: Types.NullabilityConfig): void {
nullability(config: Types.NullabilityConfig): void {
if (this.typeConfig.nullability) {
console.warn(
`nullability has already been set for type ${
@ -215,10 +277,13 @@ export class ObjectTypeDef<
}, the previous value will be replaced`
);
}
this.typeConfig.nullability = nullability;
this.typeConfig.nullability = config;
}
/**
* Used internally in the construction of the types,
* this method should not be called in normal schema building.
*
* @internal
*/
buildType(builder: SchemaBuilder): GraphQLObjectType {
@ -229,36 +294,33 @@ export class ObjectTypeDef<
/**
* Backing type for an enum member.
*/
export class EnumTypeDef<GenTypes = GraphQLNexusGen> {
export class EnumTypeDef<GenTypes = NexusGen> {
protected typeConfig: Types.EnumTypeConfig;
constructor(readonly name: string) {
this.typeConfig = {
name,
members: [],
mixed: [],
directives: [],
};
}
mix<EnumName extends Types.EnumNames<GenTypes>>(
typeName: EnumName,
options?: Types.MixOpts<Types.EnumMembers<GenTypes, EnumName>>
options: Types.MixOpts<Types.EnumMembers<GenTypes, EnumName>> = {}
) {
this.typeConfig.members.push({
item: Types.NodeType.MIX,
this.typeConfig.mixed.push({
typeName,
mixOptions: options || {},
options,
});
}
member(name: string, config?: Types.EnumMemberConfig) {
member(name: string, config: Types.EnumMemberConfig = {}) {
this.typeConfig.members.push({
item: Types.NodeType.ENUM_MEMBER,
info: {
name,
value: name,
...config,
},
name,
value: name,
...config,
});
}
@ -269,14 +331,11 @@ export class EnumTypeDef<GenTypes = GraphQLNexusGen> {
info.forEach((member) => {
if (typeof member === "string") {
return this.typeConfig.members.push({
item: Types.NodeType.ENUM_MEMBER,
info: { name: member, value: member },
name: member,
value: member,
});
}
this.typeConfig.members.push({
item: Types.NodeType.ENUM_MEMBER,
info: member,
});
this.typeConfig.members.push(member);
});
}
@ -301,6 +360,9 @@ export class EnumTypeDef<GenTypes = GraphQLNexusGen> {
}
/**
* Used internally in the construction of the types,
* this method should not be called in normal schema building.
*
* @internal
*/
buildType(builder: SchemaBuilder): GraphQLEnumType {
@ -311,15 +373,13 @@ export class EnumTypeDef<GenTypes = GraphQLNexusGen> {
/**
* Configure the `GraphQLUnionType` definition
*/
export class UnionTypeDef<
GenTypes = GraphQLNexusGen,
TypeName extends string = any
> {
export class UnionTypeDef<GenTypes = NexusGen, TypeName extends string = any> {
protected typeConfig: Types.UnionTypeConfig;
constructor(readonly name: string) {
this.typeConfig = {
name,
mixed: [],
members: [],
directives: [],
};
@ -333,13 +393,12 @@ export class UnionTypeDef<
* trigger an error at build-time.
*/
mix<UnionTypeName extends string>(
type: UnionTypeName,
options?: Types.MixOmitOpts<any>
typeName: UnionTypeName,
options: Types.MixOmitOpts<any> = {}
) {
this.typeConfig.members.push({
item: Types.NodeType.MIX,
typeName: type,
mixOptions: options || {},
this.typeConfig.mixed.push({
typeName,
options,
});
}
@ -349,10 +408,7 @@ export class UnionTypeDef<
*/
members(...types: Array<Types.ObjectNames<GenTypes>>) {
types.forEach((typeName) => {
this.typeConfig.members.push({
item: Types.NodeType.UNION_MEMBER,
typeName,
});
this.typeConfig.members.push(typeName);
});
}
@ -381,6 +437,9 @@ export class UnionTypeDef<
}
/**
* Used internally in the construction of the types,
* this method should not be called in normal schema building.
*
* @internal
*/
buildType(builder: SchemaBuilder): GraphQLUnionType {
@ -388,109 +447,28 @@ export class UnionTypeDef<
}
}
/**
* Container for the `GraphQLInterfaceType` definition
*/
export class InterfaceTypeDef<
GenTypes = GraphQLNexusGen,
GenTypes = NexusGen,
TypeName extends string = any
> {
> extends AbstractOutputObject<GenTypes, TypeName> {
/**
* Metadata about the object type
*/
protected typeConfig: Types.InterfaceTypeConfig;
constructor(readonly name: string) {
super(name);
this.typeConfig = {
name,
fields: [],
mixed: [],
directives: [],
};
}
/**
* Mixes in an existing field definition or object type
* with the current type.
*/
mix(typeName: string, options?: Types.MixOpts<any>) {
this.typeConfig.fields.push({
item: Types.NodeType.MIX,
typeName,
mixOptions: options || {},
});
}
/**
* Add an ID field type to the object schema.
*/
id<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "ID", ...opts);
}
/**
* Add an Int field type to the object schema.
*/
int<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "Int", ...opts);
}
/**
* Add a Float field type to the object schema.
*/
float<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "Float", ...opts);
}
/**
* Add a String field type to the object schema.
*/
string<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "String", ...opts);
}
/**
* Add a Boolean field type to the object schema.
*/
boolean<FieldName extends string>(
name: FieldName,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
this.field(name, "Boolean", ...opts);
}
/**
* Adds a new field to the object type
*/
field<FieldName extends string>(
name: FieldName,
type: Types.AllOutputTypes<GenTypes> | Types.BaseScalars,
...opts: Types.ConditionalOutputFieldOpts<GenTypes, TypeName, FieldName>
) {
let options: Types.OutputFieldOpts<GenTypes, TypeName, any> = {};
if (typeof opts[0] === "function") {
options.resolve = opts[0];
} else {
options = { ...opts[0] };
}
this.typeConfig.fields.push({
item: Types.NodeType.FIELD,
config: {
name,
type,
...options,
},
});
}
/**
* Adds a description to the `GraphQLInterfaceType`
*
@ -521,6 +499,9 @@ export class InterfaceTypeDef<
}
/**
* Used internally in the construction of the types,
* this method should not be called in normal schema building.
*
* @internal
*/
buildType(builder: SchemaBuilder): GraphQLInterfaceType {
@ -529,7 +510,7 @@ export class InterfaceTypeDef<
}
export class InputObjectTypeDef<
GenTypes = GraphQLNexusGen,
GenTypes = NexusGen,
TypeName extends string = any
> {
protected typeConfig: Types.InputTypeConfig;
@ -538,6 +519,7 @@ export class InputObjectTypeDef<
this.typeConfig = {
name,
fields: [],
mixed: [],
directives: [],
};
}
@ -580,18 +562,15 @@ export class InputObjectTypeDef<
/**
* Adds a new field to the input object type
*/
field<FieldName extends string>(
name: FieldName,
field(
name: string,
type: Types.AllInputTypes<GenTypes> | Types.BaseScalars,
options?: Types.InputFieldOpts<GenTypes, TypeName>
) {
this.typeConfig.fields.push({
item: Types.NodeType.FIELD,
config: {
name,
type,
...options,
},
name,
type,
...options,
});
}
@ -632,6 +611,9 @@ export class InputObjectTypeDef<
}
/**
* Used internally in the construction of the types,
* this method should not be called in normal schema building.
*
* @internal
*/
buildType(builder: SchemaBuilder): GraphQLInputObjectType {
@ -639,7 +621,7 @@ export class InputObjectTypeDef<
}
}
export class DirectiveTypeDef<GenTypes = GraphQLNexusGen> {
export class DirectiveTypeDef<GenTypes = NexusGen> {
protected typeConfig: Types.DirectiveTypeConfig;
constructor(readonly name: string) {
@ -709,6 +691,9 @@ export class DirectiveTypeDef<GenTypes = GraphQLNexusGen> {
}
/**
* Used internally in the construction of the types,
* this method should not be called in normal schema building.
*
* @internal
*/
buildType(builder: SchemaBuilder): GraphQLDirective {
@ -716,6 +701,47 @@ export class DirectiveTypeDef<GenTypes = GraphQLNexusGen> {
}
}
/**
* Provided to the `extendType` function, the ExtendTypeDef
* is a container for all metadata about the type we're extending.
*/
export class ExtendTypeDef<
GenTypes = NexusGen,
TypeName extends string = any
> extends AbstractOutputObject<GenTypes, TypeName> {
/**
* All metadata about the object type
*/
protected typeConfig: Types.ExtendTypeConfig;
constructor(readonly name: string) {
super(name);
this.typeConfig = {
name,
fields: [],
interfaces: [],
};
}
/**
* Declare that an object type implements a particular interface,
* by providing the name of the interface
*/
implements(...interfaceName: Types.AllInterfaces<GenTypes>[]) {
this.typeConfig.interfaces.push(...interfaceName);
}
/**
* Used internally in the construction of the types,
* this method should not be called in normal schema building.
*
* @internal
*/
buildType(builder: SchemaBuilder): Thunk<GraphQLFieldConfigMap<any, any>> {
return builder.extendType(this.typeConfig);
}
}
/**
* The `WrappedType` exists to signify that the value returned from
* the type construction APIs should not be used externally outside of the

View File

@ -7,80 +7,24 @@ import {
ObjectTypeDef,
UnionTypeDef,
WrappedType,
ExtendTypeDef,
} from "./core";
import * as Types from "./types";
import { enumShorthandMembers } from "./utils";
/**
* The most basic components of a GraphQL schema are object types, which just represent
* a kind of object you can fetch from your service, and what fields it has.
*
* ```
* const User = objectType('User', (t) => {
* t.int('id', { description: 'Id of the user' })
* t.string('fullName', { description: 'Full name of the user' })
* t.field('status', 'StatusEnum');
* t.field('posts', 'Post', {
* list: true,
* resolve(root, args, ctx) {
* return ctx.getUser(root.id).posts()
* }
* })
* });
*
* const Post = objectType('Post', (t) => {
* t.int('id')
* t.string('title')
* })
*
* const StatusEnum = enumType('StatusEnum', {
* ACTIVE: 1,
* DISABLED: 2
* });
* ```
*
* @see https://graphql.github.io/learn/schema/#object-types-and-fields
*/
export function objectType<
GenTypes = GraphQLNexusGen,
TypeName extends string = any
>(
export function objectType<GenTypes = NexusGen, TypeName extends string = any>(
name: TypeName,
fn: (t: ObjectTypeDef<GenTypes, TypeName>) => void
): WrappedType {
) {
const factory = new ObjectTypeDef<GenTypes, TypeName>(assertValidName(name));
fn(factory);
return new WrappedType(factory);
}
/**
* Like many type systems, GraphQL supports interfaces. An Interface is an
* abstract type that includes a certain set of fields that a type must
* include to implement the interface.
*
* In GraphQL Nexus, you do not need to redefine the interface fields on the
* implementing object types, instead you may use `.implements(interfaceName)`
* and all of the interface fields will be added to the type.
*
* ```
* const Node = interfaceType('Node', t => {
* t.id('id', { description: 'GUID for a resource' });
* });
*
* const User = objectType('User', t => {
* t.implements('Node');
* });
* ```
*
* @see https://graphql.github.io/learn/schema/#interfaces
*/
export function interfaceType<
GenTypes = GraphQLNexusGen,
GenTypes = NexusGen,
TypeName extends string = any
>(
name: TypeName,
fn: (t: InterfaceTypeDef<GenTypes, TypeName>) => void
): WrappedType {
>(name: TypeName, fn: (t: InterfaceTypeDef<GenTypes, TypeName>) => void) {
const factory = new InterfaceTypeDef<GenTypes, TypeName>(
assertValidName(name)
);
@ -88,79 +32,22 @@ export function interfaceType<
return new WrappedType(factory);
}
/**
* Union types are very similar to interfaces, but they don't get to specify
* any common fields between the types.
*
* As a function, where other unions can be mixed in:
*
* ```
* const CombinedResult = unionType('CombinedResult', t => {
* t.mix('SearchResult')
* t.members('AnotherType', 'YetAnotherType')
* t.resolveType(item => item.name)
* })
* ```
*
* @see https://graphql.org/learn/schema/#union-types
*/
export function unionType<
GenTypes = GraphQLNexusGen,
TypeName extends string = any
>(
export function unionType<GenTypes = NexusGen, TypeName extends string = any>(
name: TypeName,
fn: (t: UnionTypeDef<GenTypes, TypeName>) => void
): WrappedType {
) {
const factory = new UnionTypeDef<GenTypes>(assertValidName(name));
fn(factory);
return new WrappedType(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 enumType:
*
* As an array of enum values:
*
* ```
* const Episode = enumType('Episode', ['NEWHOPE', 'EMPIRE', 'JEDI'])
* ```
*
* As an object, with a mapping of enum values to internal values:
*
* ```
* const Episode = enumType('Episode', {
* NEWHOPE: 4,
* EMPIRE: 5,
* JEDI: 6
* });
* ```
*
* As a function, where other enums can be mixed in:
*
* ```
* const Episode = enumType('Episode', (t) => {
* t.mix('OneThroughThree')
* t.mix('FourThroughSix')
* t.mix('SevenThroughNine')
* t.members(['OTHER'])
* t.description('All Movies in the Skywalker saga, or OTHER')
* })
* ```
*
* @see https://graphql.github.io/learn/schema/#enumeration-types
*/
export function enumType<
GenTypes = GraphQLNexusGen,
TypeName extends string = any
>(
export function enumType<GenTypes = NexusGen, TypeName extends string = any>(
name: TypeName,
fn:
| ((arg: EnumTypeDef<GenTypes>) => void)
| string[]
| Record<string, string | number | object | boolean>
): WrappedType {
) {
const factory = new EnumTypeDef<GenTypes>(assertValidName(name));
if (typeof fn === "function") {
fn(factory);
@ -170,67 +57,22 @@ export function enumType<
return new WrappedType(factory);
}
/**
* Defines a complex object which can be passed as an input value.
*
* Unlike object types, input types do not have arguments, and they do not
* have resolvers, backing types, etc.
*
* @see https://graphql.org/learn/schema/#input-types
*/
export function inputObjectType<
GenTypes = GraphQLNexusGen,
GenTypes = NexusGen,
TypeName extends string = any
>(name: TypeName, fn: (t: InputObjectTypeDef<GenTypes>) => void): WrappedType {
>(name: TypeName, fn: (t: InputObjectTypeDef<GenTypes>) => void) {
const factory = new InputObjectTypeDef<GenTypes>(assertValidName(name));
fn(factory);
return new WrappedType(factory);
}
/**
* A GraphQL object type has a name and fields, but at some point those fields have
* to resolve to some concrete data. That's where the scalar types come in:
* they represent the leaves of the query.
*
* ```js
* const DateScalar = scalarType("Date", {
* description: "Date custom scalar type",
* parseValue(value) {
* return new Date(value);
* },
* serialize(value) {
* return value.getTime();
* },
* parseLiteral(ast) {
* if (ast.kind === Kind.INT) {
* return new Date(ast.value);
* }
* return null;
* }
* });
* ```
*
* @see https://graphql.github.io/learn/schema/#scalar-types
*/
export function scalarType(
name: string,
options: Types.ScalarOpts
): WrappedType {
export function scalarType(name: string, options: Types.ScalarOpts) {
return new WrappedType(
new GraphQLScalarType({ name: assertValidName(name), ...options })
);
}
/**
* Defines an argument that can be used in any object or interface type
*
* Takes the GraphQL type name and any options.
*
* The value returned from this argument can be used multiple times in any valid `args` object value
*
* @see https://graphql.github.io/learn/schema/#arguments
*/
export function arg<GenTypes = GraphQLNexusGen>(
export function arg<GenTypes = NexusGen>(
type: Types.AllInputTypes<GenTypes> | Types.BaseScalars,
options?: Types.ArgOpts
): Types.ArgDefinition {
@ -242,41 +84,41 @@ export function arg<GenTypes = GraphQLNexusGen>(
};
}
/**
* Alias for `arg("String", options)`
*/
export function stringArg(options?: Types.ArgOpts): Types.ArgDefinition {
return arg("String", options);
}
/**
* Alias for `arg("Int", options)`
*/
export function intArg(options?: Types.ArgOpts): Types.ArgDefinition {
return arg("Int", options);
}
/**
* Alias for `arg("Float", options)`
*/
export function floatArg(options?: Types.ArgOpts): Types.ArgDefinition {
return arg("Float", options);
}
/**
* Alias for `arg("ID", options)`
*/
export function idArg(options?: Types.ArgOpts): Types.ArgDefinition {
return arg("ID", options);
}
/**
* Alias for `arg("Boolean", options)`
*/
export function booleanArg(options?: Types.ArgOpts): Types.ArgDefinition {
return arg("Boolean", options);
}
/**
* Adds new fields to an existing type in the schema. Useful when splitting your
* schema across several domains.
*
* @see http://graphql-nexus.com/api/extendType
*/
export function extendType<GenTypes = NexusGen, TypeName extends string = any>(
name: TypeName,
fn: (t: ExtendTypeDef<GenTypes, TypeName>) => void
) {
const factory = new ExtendTypeDef<GenTypes, TypeName>(assertValidName(name));
fn(factory);
return new WrappedType(factory);
}
/**
* Defines a directive that can be used by the schema.
*
@ -284,14 +126,14 @@ export function booleanArg(options?: Types.ArgOpts): Types.ArgDefinition {
* > consumers of the schema.
*/
export function directiveType<
GenTypes = GraphQLNexusGen,
GenTypes = NexusGen,
DirectiveName extends string = any
>(
name: DirectiveName,
config:
| Types.DirectiveConfig<GenTypes, DirectiveName>
| ((arg: DirectiveTypeDef<GenTypes>) => void)
): WrappedType {
) {
const directive = new DirectiveTypeDef<GenTypes>(assertValidName(name));
if (typeof config === "function") {
config(directive);

View File

@ -1,5 +1,7 @@
export * from "./definitions";
import * as core from "./core";
import * as Types from "./types";
export { core };
export { makeSchema, buildTypes, makeSchemaWithMetadata } from "./builder";
export { convertSDL } from "./sdlConverter";
export { Types };

View File

@ -165,7 +165,7 @@ export class Metadata {
this.checkMutable();
}
addField(typeName: string, field: Types.FieldConfig) {
addField(typeName: string, field: Types.OutputFieldConfig) {
this.checkMutable();
this.objectFieldMeta[typeName] = this.objectFieldMeta[typeName] || {};
this.objectFieldMeta[typeName][field.name] = field;

View File

@ -13,7 +13,6 @@ import {
GraphQLUnionType,
isUnionType,
isEnumType,
GraphQLNamedType,
GraphQLField,
} from "graphql";
import { isInterfaceField, eachObj } from "./utils";

View File

@ -15,7 +15,6 @@ import {
isObjectType,
isScalarType,
isUnionType,
GraphQLNamedType,
GraphQLUnionType,
} from "graphql";
import { Metadata } from "./metadata";
@ -481,7 +480,7 @@ export async function buildTypeDefinitions(
${imports.join("\n")}
declare global {
interface GraphQLNexusGen extends GraphQLNexusGenTypes {}
interface NexusGen extends NexusGenTypes {}
}
// Maybe Promise
@ -498,21 +497,21 @@ export type ${MTA}<T, A> = T | ((args?: A) => T);
${allTypeStrings.join("\n\n")}
${stringifyTypeFieldMapping("GraphQLNexusGenArgTypes", argTypeFields)}
${stringifyTypeFieldMapping("NexusGenArgTypes", argTypeFields)}
export interface GraphQLNexusGenRootTypes {
export interface NexusGenRootTypes {
${map(
typeNames.interfaces.concat(typeNames.objects),
(name) => ` ${name}: ${typeRootTypeName(name)};`
)}
typeNames.interfaces.concat(typeNames.objects),
(name) => ` ${name}: ${typeRootTypeName(name)};`
)}
}
${stringifyTypeFieldMapping("GraphQLNexusGenReturnTypes", returnTypeFields)}
${stringifyTypeFieldMapping("NexusGenReturnTypes", returnTypeFields)}
export interface GraphQLNexusGenTypes {
argTypes: GraphQLNexusGenArgTypes;
backingTypes: GraphQLNexusGenRootTypes;
returnTypes: GraphQLNexusGenReturnTypes;
export interface NexusGenTypes {
argTypes: NexusGenArgTypes;
backingTypes: NexusGenRootTypes;
returnTypes: NexusGenReturnTypes;
context: ${contextType};
enums: ${enums()};
objects: ${objectNames()};
@ -521,18 +520,18 @@ export interface GraphQLNexusGenTypes {
scalars: ${scalars()};
inputObjects: ${inputObjects()};
allInputTypes:
| Extract<keyof GraphQLNexusGenTypes['inputObjects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['inputObjects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
allOutputTypes:
| Extract<keyof GraphQLNexusGenTypes['objects'], string>
| Extract<keyof GraphQLNexusGenTypes['enums'], string>
| Extract<keyof GraphQLNexusGenTypes['unions'], string>
| Extract<keyof GraphQLNexusGenTypes['interfaces'], string>
| Extract<keyof GraphQLNexusGenTypes['scalars'], string>;
| Extract<keyof NexusGenTypes['objects'], string>
| Extract<keyof NexusGenTypes['enums'], string>
| Extract<keyof NexusGenTypes['unions'], string>
| Extract<keyof NexusGenTypes['interfaces'], string>
| Extract<keyof NexusGenTypes['scalars'], string>;
}
export type Gen = GraphQLNexusGenTypes;
export type Gen = NexusGenTypes;
`;
}

View File

@ -16,18 +16,31 @@ import {
EnumTypeDef,
UnionTypeDef,
DirectiveTypeDef,
ExtendTypeDef,
WrappedType,
} from "./core";
import { Metadata } from "./metadata";
export type Wrappable = NamedTypeDef | DirectiveTypeDef | GraphQLScalarType;
export type WrappedOutput =
| WrappedType<NamedOutputTypeDef>
| WrappedType<GraphQLScalarType>;
export type NamedTypeDef<GenTypes = any> =
export type Wrappable =
| NamedTypeDef
| ExtendTypeDef<any, any>
| DirectiveTypeDef
| GraphQLScalarType;
export type NamedOutputTypeDef<GenTypes = any> =
| ObjectTypeDef<GenTypes, any>
| InputObjectTypeDef<GenTypes, any>
| InterfaceTypeDef<GenTypes, any>
| EnumTypeDef<GenTypes>
| UnionTypeDef<GenTypes>;
export type NamedTypeDef<GenTypes = any> =
| InputObjectTypeDef<GenTypes, any>
| NamedOutputTypeDef<GenTypes>;
export enum NodeType {
MIX = "MIX",
FIELD = "FIELD",
@ -46,17 +59,11 @@ export type MaybeThunk<T> = T | (() => T);
export type Maybe<T> = T | null;
export type MixDef = {
item: NodeType.MIX;
typeName: string;
mixOptions: MixOpts<any>;
options: MixOpts<any>;
};
export type FieldDef = {
item: NodeType.FIELD;
config: FieldConfig;
};
export type FieldConfig = InputFieldConfig | OutputFieldConfig;
export type FieldDef = InputFieldConfig | OutputFieldConfig;
export interface OutputFieldConfig extends OutputFieldOpts {
name: string;
@ -68,16 +75,6 @@ export interface InputFieldConfig extends InputFieldOpts {
type: any;
}
export type FieldDefType = MixDef | FieldDef;
export type EnumDefType =
| MixDef
| { item: NodeType.ENUM_MEMBER; info: EnumMemberInfo };
export type UnionDefType =
| MixDef
| { item: NodeType.UNION_MEMBER; typeName: string };
export interface EnumMemberInfo {
name: string;
value: any;
@ -213,17 +210,20 @@ export interface CommonOutputOpts extends FieldOpts {
export interface OutputFieldOpts<
GenTypes = any,
TypeName = any,
FieldName = any
FieldName = any,
ResolveFallback = any
> extends CommonOutputOpts {
/**
* Resolver for the output field
*/
resolve?: OutputFieldResolver<GenTypes, TypeName, FieldName>;
resolve?: OutputFieldResolver<GenTypes, TypeName, FieldName, ResolveFallback>;
/**
* Default value for the field, if none is returned.
*/
default?: MaybeThunk<ResultValue<GenTypes, TypeName, FieldName>>;
default?: MaybeThunk<
ResultValue<GenTypes, TypeName, FieldName, ResolveFallback>
>;
// /**
// * Subscription for the output field.
@ -231,17 +231,22 @@ export interface OutputFieldOpts<
// subscribe?: OutputFieldResolver<GenTypes, TypeName, FieldName>
}
export type OutputFieldResolver<GenTypes, TypeName, FieldName> = (
export type OutputFieldResolver<
GenTypes,
TypeName,
FieldName,
ResolveFallback
> = (
root: RootValue<GenTypes, TypeName>,
args: ArgsValue<GenTypes, TypeName, FieldName>,
context: ContextValue<GenTypes>,
info: GraphQLResolveInfo
) => MaybePromise<ResultValue<GenTypes, TypeName, FieldName>>;
) => MaybePromise<ResultValue<GenTypes, TypeName, FieldName, ResolveFallback>>;
/**
* All properties that can be changed about a field
*/
export type ModifyFieldOpts<GenTypes, TypeName, FieldName> = {
export type ModifyFieldOpts<GenTypes, TypeName, FieldName, ResolveFallback> = {
/**
* The description of the field, as defined in the GraphQL object definition.
*/
@ -249,11 +254,13 @@ export type ModifyFieldOpts<GenTypes, TypeName, FieldName> = {
/**
* Resolver for the output field
*/
resolve?: OutputFieldResolver<GenTypes, TypeName, FieldName>;
resolve?: OutputFieldResolver<GenTypes, TypeName, FieldName, ResolveFallback>;
/**
* Default value for the field, if none is returned.
*/
default?: MaybeThunk<ResultValue<GenTypes, TypeName, FieldName>>;
default?: MaybeThunk<
ResultValue<GenTypes, TypeName, FieldName, ResolveFallback>
>;
};
export interface InputFieldOpts<GenTypes = any, TypeName = any>
@ -287,8 +294,16 @@ export interface ScalarOpts
deprecation?: string | DeprecationInfo;
}
interface HasMixins {
mixed: MixDef[];
}
interface HasFields {
fields: FieldDefType[];
fields: FieldDef[];
}
interface HasInterfaces {
interfaces: string[];
}
interface HasDirectives {
@ -325,15 +340,20 @@ export interface Nullability {
nullability?: NullabilityConfig;
}
export interface EnumTypeConfig extends Named, HasDirectives, SharedTypeConfig {
members: EnumDefType[];
export interface EnumTypeConfig
extends Named,
HasMixins,
HasDirectives,
SharedTypeConfig {
members: EnumMemberInfo[];
}
export interface UnionTypeConfig
extends Named,
HasMixins,
HasDirectives,
SharedTypeConfig {
members: UnionDefType[];
members: string[];
/**
* Optionally provide a custom type resolver function. If one is not provided,
* the default implementation will call `isTypeOf` on each implementing
@ -342,9 +362,12 @@ export interface UnionTypeConfig
resolveType?: TypeResolver<any, any>;
}
export interface OutputObjectConfig extends Named, HasFields {}
export interface InputTypeConfig
extends Named,
HasFields,
HasMixins,
HasDirectives,
SharedTypeConfig,
Nullability {}
@ -352,19 +375,16 @@ export interface InputTypeConfig
export interface ObjectTypeConfig
extends Named,
HasFields,
HasMixins,
HasInterfaces,
HasDirectives,
SharedTypeConfig,
Nullability,
DefaultResolver {
/**
* All interfaces the object implements.
*/
interfaces: string[];
/**
* Any modifications to the field config
*/
fieldModifications: Record<string, ModifyFieldOpts<any, any, any>>;
fieldModifications: Record<string, ModifyFieldOpts<any, any, any, any>>;
/**
* An (optional) isTypeOf check for the object type
@ -372,9 +392,7 @@ export interface ObjectTypeConfig
isTypeOf?: GraphQLIsTypeOfFn<any, any>;
}
export interface AbstractTypeConfig {
fields: FieldConfig[];
}
export interface ExtendTypeConfig extends Named, HasFields, HasInterfaces {}
export interface DirectiveTypeConfig extends Named {
description?: string;
@ -385,6 +403,7 @@ export interface DirectiveTypeConfig extends Named {
export interface InterfaceTypeConfig
extends Named,
HasFields,
HasMixins,
HasDirectives,
SharedTypeConfig,
Nullability,
@ -771,14 +790,15 @@ export type ArgsValue<
export type ResultValue<
GenTypes,
TypeName,
FieldName
FieldName,
ResolveFallback
> = GenTypes extends GenTypesShape
? TypeName extends keyof GenTypes["returnTypes"]
? FieldName extends keyof GenTypes["returnTypes"][TypeName]
? GenTypes["returnTypes"][TypeName][FieldName]
: any
: any
: never;
: ResolveFallback
: ResolveFallback
: ResolveFallback;
export type InputValue<GenTypes, TypeName> = any;
// GenTypes extends GenTypesShape
@ -816,40 +836,83 @@ export type DirectiveConfig<GenTypes, DirectiveName> = {
// - Else if it's the wrong type, then we need a resolver / default for this field
// - Else field doesn't even exist in the root type, we need a resolver
export type ConditionalOutputFieldOpts<
GenTypes = any,
TypeName = any,
FieldName = any
> = FieldName extends keyof RootValue<GenTypes, TypeName>
? RootValueField<GenTypes, TypeName, FieldName> extends ResultValue<
GenTypes,
TypeName,
FieldName
>
? OptionalOutputOpts<GenTypes, TypeName, FieldName>
: Extract<RootValueField<GenTypes, TypeName, FieldName>, null> extends null
? OptionalOutputOpts<GenTypes, TypeName, FieldName>
: RequiredOutputOpts<GenTypes, TypeName, FieldName>
: RequiredOutputOpts<GenTypes, TypeName, FieldName>;
GenTypes,
TypeName,
FieldName,
ResolveFallback
> = GenTypes extends GenTypesShape // If we actually have generated definitions
? FieldName extends keyof RootValue<GenTypes, TypeName> // And the field name is in the root value
? RootValueField<GenTypes, TypeName, FieldName> extends ResultValue<
GenTypes,
TypeName,
FieldName,
ResolveFallback
> // And the root value matches up with a valid value for the result
? OptionalOutputOpts<GenTypes, TypeName, FieldName, ResolveFallback> // Then the result is optional
: Extract<
RootValueField<GenTypes, TypeName, FieldName>,
null
> extends null // If the root value can be null
? OptionalOutputOpts<GenTypes, TypeName, FieldName, ResolveFallback> // Then it's also optional
: RequiredOutputOpts<GenTypes, TypeName, FieldName, ResolveFallback> // Otherwise it's required
: RequiredOutputOpts<GenTypes, TypeName, FieldName, ResolveFallback> // If it's not in the root value, it's required
: OptionalOutputOpts<GenTypes, TypeName, FieldName, ResolveFallback>; // If we don't have generated defs, it's optional
export type OptionalOutputOpts<GenTypes, TypeName, FieldName> =
export type OptionalOutputOpts<
GenTypes,
TypeName,
FieldName,
ResolveFallback
> =
| []
| [OptionalResolverOutputFieldOpts<GenTypes, TypeName, FieldName>]
| [OutputFieldResolver<GenTypes, TypeName, FieldName>];
| [
OptionalResolverOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
ResolveFallback
>
]
| [OutputFieldResolver<GenTypes, TypeName, FieldName, ResolveFallback>];
export type RequiredOutputOpts<GenTypes, TypeName, FieldName> =
| [NeedsResolverOutputFieldOpts<GenTypes, TypeName, FieldName>]
| [OutputFieldResolver<GenTypes, TypeName, FieldName>];
export type RequiredOutputOpts<
GenTypes,
TypeName,
FieldName,
ResolveFallback
> =
| [
NeedsResolverOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
ResolveFallback
>
]
| [OutputFieldResolver<GenTypes, TypeName, FieldName, ResolveFallback>];
export interface OutputWithDefaultOpts<GenTypes, TypeName, FieldName>
extends CommonOutputOpts {
resolve?: OutputFieldResolver<GenTypes, TypeName, FieldName>;
default: MaybeThunk<ResultValue<GenTypes, TypeName, FieldName>>;
export interface OutputWithDefaultOpts<
GenTypes,
TypeName,
FieldName,
ResolveFallback
> extends CommonOutputOpts {
resolve?: OutputFieldResolver<GenTypes, TypeName, FieldName, ResolveFallback>;
default: MaybeThunk<
ResultValue<GenTypes, TypeName, FieldName, ResolveFallback>
>;
}
export interface OutputWithResolveOpts<GenTypes, TypeName, FieldName>
extends CommonOutputOpts {
resolve: OutputFieldResolver<GenTypes, TypeName, FieldName>;
default?: MaybeThunk<ResultValue<GenTypes, TypeName, FieldName>>;
export interface OutputWithResolveOpts<
GenTypes,
TypeName,
FieldName,
ResolveFallback
> extends CommonOutputOpts {
resolve: OutputFieldResolver<GenTypes, TypeName, FieldName, ResolveFallback>;
default?: MaybeThunk<
ResultValue<GenTypes, TypeName, FieldName, ResolveFallback>
>;
}
/**
@ -857,16 +920,27 @@ export interface OutputWithResolveOpts<GenTypes, TypeName, FieldName>
* be fulfilled by the "backing value" alone, and therefore needs either
* a valid resolver or a "root value".
*/
export type NeedsResolverOutputFieldOpts<GenTypes, TypeName, FieldName> =
| OutputWithDefaultOpts<GenTypes, TypeName, FieldName>
| OutputWithResolveOpts<GenTypes, TypeName, FieldName>;
export type NeedsResolverOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
ResolveFallback
> =
| OutputWithDefaultOpts<GenTypes, TypeName, FieldName, ResolveFallback>
| OutputWithResolveOpts<GenTypes, TypeName, FieldName, ResolveFallback>;
/**
* If we already have the correct value for the field from the root type,
* then we can provide a resolver or a default value, but we don't have to.
*/
export interface OptionalResolverOutputFieldOpts<GenTypes, TypeName, FieldName>
extends CommonOutputOpts {
resolve?: OutputFieldResolver<GenTypes, TypeName, FieldName>;
default?: MaybeThunk<ResultValue<GenTypes, TypeName, FieldName>>;
export interface OptionalResolverOutputFieldOpts<
GenTypes,
TypeName,
FieldName,
ResolveFallback
> extends CommonOutputOpts {
resolve?: OutputFieldResolver<GenTypes, TypeName, FieldName, ResolveFallback>;
default?: MaybeThunk<
ResultValue<GenTypes, TypeName, FieldName, ResolveFallback>
>;
}

21
tests/_helpers.ts Normal file
View File

@ -0,0 +1,21 @@
import faker from "faker";
import { objectType, inputObjectType } from "../src";
faker.seed(0);
/**
* Used in testing, creates a generic "User" object
*/
export const UserObject = objectType("User", (t) => {
t.id("id", () => `User:1`);
t.string("email", () => faker.internet.email());
t.string("name", () => `${faker.name.firstName()} ${faker.name.lastName()}`);
});
export const PostObject = objectType("Post", (t) => {
t.field("user", UserObject);
});
export const InputObject = inputObjectType("Something", (t) => {
t.int("id");
});

130
tests/enumType.spec.ts Normal file
View File

@ -0,0 +1,130 @@
/// <reference types="jest" />
import { GraphQLEnumType } from "graphql";
import { buildTypes, enumType } from "../src";
describe("enumType", () => {
const PrimaryColors = enumType<any>("PrimaryColors", (t) => {
t.members(["RED", "YELLOW", "BLUE"]);
});
const RainbowColors = enumType<any>("RainbowColors", (t) => {
t.mix("PrimaryColors");
t.members(["ORANGE", "GREEN", "VIOLET"]);
});
const AdditivePrimaryColors = enumType<any>("AdditivePrimaryColors", (t) => {
t.mix("PrimaryColors", { omit: ["YELLOW"] });
t.members(["GREEN"]);
});
const CircularRefTestA = enumType<any>("CircularA", (t) => {
t.mix("CircularB");
t.members(["A"]);
});
const CircularRefTestB = enumType<any>("CircularB", (t) => {
t.mix("CircularA");
t.members(["B"]);
});
it("builds an enum", () => {
const types = buildTypes<{ PrimaryColors: GraphQLEnumType }>([
PrimaryColors,
]);
expect(types.typeMap.PrimaryColors).toBeInstanceOf(GraphQLEnumType);
expect(types.typeMap.PrimaryColors.getValues().map((v) => v.value)).toEqual(
["RED", "YELLOW", "BLUE"]
);
});
it("can mix enums", () => {
const types = buildTypes<{ RainbowColors: GraphQLEnumType }>([
PrimaryColors,
RainbowColors,
]);
expect(types.typeMap.RainbowColors).toBeInstanceOf(GraphQLEnumType);
expect(types.typeMap.RainbowColors.getValues().map((v) => v.value)).toEqual(
["ORANGE", "GREEN", "VIOLET", "RED", "YELLOW", "BLUE"]
);
});
it("can omit with mix", () => {
const types = buildTypes<{
AdditivePrimaryColors: GraphQLEnumType;
}>([PrimaryColors, AdditivePrimaryColors]);
expect(types.typeMap.AdditivePrimaryColors).toBeInstanceOf(GraphQLEnumType);
expect(
types.typeMap.AdditivePrimaryColors.getValues().map((v) => v.value)
).toEqual(["GREEN", "RED", "BLUE"]);
});
it("can pick with mix", () => {
const FavoriteColors = enumType<any>("FavoriteColors", (t) => {
t.mix("RainbowColors", { pick: ["RED", "GREEN"] });
});
const types = buildTypes<{ FavoriteColors: GraphQLEnumType }>([
PrimaryColors,
RainbowColors,
FavoriteColors,
]);
expect(types.typeMap.FavoriteColors).toBeInstanceOf(GraphQLEnumType);
expect(
types.typeMap.FavoriteColors.getValues().map((v) => v.value)
).toEqual(["GREEN", "RED"]);
});
it("can map internal values", () => {
const Internal = enumType("Internal", (t) => {
t.members([{ name: "A", value: "--A--" }, { name: "B", value: "--B--" }]);
});
const types = buildTypes<{ Internal: GraphQLEnumType }>([Internal]);
expect(types.typeMap.Internal.getValues().map((v) => v.name)).toEqual([
"A",
"B",
]);
expect(types.typeMap.Internal.getValues().map((v) => v.value)).toEqual([
"--A--",
"--B--",
]);
});
it("has shorthand syntax for enum mapping", () => {
const MappedArr = enumType("MappedArr", ["A", "B"]);
const MappedObj = enumType("MappedObj", {
a: 1,
b: 2,
});
const types = buildTypes<{
MappedObj: GraphQLEnumType;
MappedArr: GraphQLEnumType;
}>([MappedObj, MappedArr]);
expect(types.typeMap.MappedArr.getValues().map((v) => v.name)).toEqual([
"A",
"B",
]);
expect(types.typeMap.MappedObj.getValues().map((v) => v.name)).toEqual([
"a",
"b",
]);
expect(types.typeMap.MappedObj.getValues().map((v) => v.value)).toEqual([
1,
2,
]);
});
it("throws if the enum has no members", () => {
const NoMembers = enumType("NoMembers", () => {});
expect(() => {
const types = buildTypes<{ NoMembers: GraphQLEnumType }>([NoMembers]);
expect(types.typeMap.NoMembers.getValues()).toHaveLength(0);
}).toThrow("must have at least one member");
});
it("throws when building with a circular reference", () => {
expect(() => {
buildTypes([CircularRefTestA, CircularRefTestB]);
}).toThrowError(
"GraphQL Nexus: Circular dependency detected, while building types"
);
});
});

22
tests/extendType.spec.ts Normal file
View File

@ -0,0 +1,22 @@
import { extendType, buildTypes, idArg } from "../src";
import { UserObject, InputObject } from "./_helpers";
describe("extendType", () => {
it("should allow adding types to the Query type", () => {
const GetUser = extendType("Query", (t) => {
t.field("user", UserObject, { args: { id: idArg() } });
});
const GetAccount = extendType("Query", (t) => {
t.field("account");
});
buildTypes([GetUser]);
});
it("should allow adding types to the Mutation type", () => {
const AddUser = extendType("Mutation", (t) => {});
});
it("should error if the type is extended but not defined", () => {
//
});
});

20
tests/objectType.spec.ts Normal file
View File

@ -0,0 +1,20 @@
import { objectType, buildTypes } from "../src";
import { GraphQLObjectType } from "graphql";
describe("objectType", () => {
it("should build an object type", () => {
const Account = objectType("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 = buildTypes<{ Account: GraphQLObjectType }>([Account]);
expect(Object.keys(type.typeMap.Account.getFields()).sort()).toEqual([
"email",
"id",
"name",
]);
});
});

View File

@ -18,7 +18,7 @@
"./examples",
"./scripts",
"./dist",
"./src/__tests__",
"./tests",
"./website",
"./gulpfile.ts"
]

View File

@ -1,288 +0,0 @@
import React from "react";
import marked from "marked";
import prism from "prismjs";
import data from "./allTypes-transit.json";
import {
transit,
ParsedFiles,
BaseRecords,
isFuncRecord,
TypeVal,
isInterfaceRecord,
isClassRecord,
ClassMethodRecord,
ParamRecord,
DocRecord,
} from "./typedefs";
import { List } from "immutable";
const { api, core, types } = transit.fromJSON(data) as ReturnType<
typeof ParsedFiles
>;
marked.setOptions({
xhtml: true,
});
const renderer = new marked.Renderer();
renderer.code = function(code) {
const rendered = prism.highlight(
code.replace(/^ \* /g, "").replace(/\n \* ?/g, "\n"),
prism.languages.javascript
);
return `<code class="codeBlock">${rendered}</code>`;
};
function extractDoc(item: {
doc: DocRecord | null;
}): { first: JSX.Element | null; rest: JSX.Element | null } {
if (item.doc) {
const [first, ...tail] = item.doc.comment.split("\n\n");
const discussion = tail.join("\n\n").trim();
const rest = discussion ? (
<p
dangerouslySetInnerHTML={{
__html: marked(discussion, { renderer }),
}}
/>
) : null;
return {
first: first ? (
<p
dangerouslySetInnerHTML={{
__html: marked(first, { renderer }),
}}
/>
) : null,
rest,
};
}
return { first: null, rest: null };
}
function extractTags(obj: { doc: DocRecord | null }) {
return null;
}
export function ApiDocs() {
const apiKeys = api.keySeq();
const coreKeys = core.keySeq();
const typeKeys = types.keySeq();
const allKeys = new Set(apiKeys.concat(coreKeys).concat(typeKeys));
const usedKeys = new Set();
const isKnownKey = (key: string): boolean => {
return api.has(key) || core.has(key) || types.has(key);
};
const makeClassMethod = (method: ClassMethodRecord) => {
return (
<>
{method.name}({makeSignature(method.params)}){returnType(method)}
</>
);
};
const returnType = (obj: { type: TypeVal | null }) => {
return obj.type ? <>: {makeType(obj.type)}</> : "";
};
const makeType = (
type: TypeVal,
asUnion = false
): JSX.Element | JSX.Element[] | string => {
if (typeof type === "string") {
if (type.indexOf("Types.") === 0) {
type = type.replace("Types.", "");
}
if (
type === "DirectiveName" ||
type === "TypeName" ||
type === "FieldName"
) {
type = "string";
}
usedKeys.add(type);
return isKnownKey(type) ? (
<a className="codeLink" href={`#${type}`}>
{type}
</a>
) : (
type
);
}
if (isFuncRecord(type)) {
const fn = (
<>
({makeSignature(type.params)}) => {makeType(type.type || "unknown")}
</>
);
return asUnion ? <>({fn})</> : fn;
}
if (List.isList(type)) {
return interleave(
type.toArray().map((item) => <>{makeType(item, true)}</>),
" | "
);
}
return "unknown";
};
const makeSignature = (params: List<ParamRecord>) => {
let args: JSX.Element[] = [];
args = params.toArray().map((param, i) => {
const separator = param.optional ? "?:" : ":";
return (
<>
{param.name}
{separator} {makeType(param.type)}
{i < params.size - 1 ? ", " : ""}
</>
);
});
return args;
};
const extractMethods = (def: BaseRecords) => {
let args: JSX.Element[] = [];
if (isClassRecord(def)) {
args = def.members.toArray().map((member) => {
const { first, rest } = extractDoc(member);
return (
<>
<h4>
<a className="anchor" id={`${def.name}-${member.name}`} />
t.
{member.name}
()
</h4>
{first}
<code key={member.name} className="codeBlock memberSignature">
{makeClassMethod(member)}
</code>
{rest}
</>
);
});
}
return <>{args}</>;
};
return (
<div>
<h1>API Reference</h1>
{api.toArray().map(([key, def]) => {
const { first, rest } = extractDoc(def);
return (
<div>
<a className="anchor" id={key} />
<h3 key={key}>
{key}
()
</h3>
{first}
<code className="codeBlock memberSignature">
{key}
{isFuncRecord(def) ? (
<>
({makeSignature(def.params)}){returnType(def)}
</>
) : null}
</code>
{rest}
{extractTags(def)}
</div>
);
})}
<h2 id="Definition-Objects">Definition Objects:</h2>
<p>
Each of these objects represent the value passed to the{" "}
<b>builder function</b>, the second argument of the main API:
</p>
<div
dangerouslySetInnerHTML={{
__html: marked(builderExample, { renderer }),
}}
/>
<hr />
{core.toArray().map(([key, def]) => {
const { first, rest } = extractDoc(def);
return (
<div style={{ paddingTop: 10 }}>
<a className="anchor" id={key} />
<h3 key={key}>{key}</h3>
{first}
{extractMethods(def)}
{rest}
</div>
);
})}
<h2>Type Annotations</h2>
{types
.toArray()
.filter(([key]) => usedKeys.has(key))
.map(([key, def]) => {
return (
<div>
<a className="anchor" id={key} />
<h4 key={key}>{key}</h4>
</div>
);
})}
</div>
);
}
const builderExample = `
\`\`\`
// "t" is the Definition Object in this code block
const User = objectType('User', (t: ObjectTypeDef) => {
t.int('id', { description: 'Primary key of the user' });
});
\`\`\`
`;
const interleave = (
elements: any[],
toInterleave: JSX.Element | string
): any[] => {
return elements.reduce((result: any[], elem, i) => {
result.push(elem);
if (i < elements.length - 1) {
result.push(toInterleave);
}
return result;
}, []);
};
export const ApiSidebar = () => {
const apiKeys = api.keySeq();
const coreKeys = core.keySeq();
return (
<nav className="onPageNav">
<ul className="toc-headings">
<li>
<a href="#">Core API</a>
<ul className="toc-headings">
{apiKeys
.map((key) => {
if (/(.*)Arg/.test(key)) {
return null;
}
return (
<li key={key}>
<a href={`#${key}`}>{key === "arg" ? "arg / *Arg" : key}</a>
</li>
);
})
.filter((f) => f)}
</ul>
</li>
<li>
<a href="#Definition-Objects">Definition Objects</a>
<ul className="toc-headings">
{coreKeys.map((key) => (
<li key={key}>
<a href={`#${key}`}>{key}</a>
</li>
))}
</ul>
</li>
</ul>
</nav>
);
};

View File

@ -1,9 +0,0 @@
declare module "transit-immutable-js" {
import { Record } from "immutable";
class TransitImmutable {
static withRecords(records: Record[]): TransitImmutable;
toJSON(data: any): any;
fromJSON<T = any>(data: any): T;
}
export = TransitImmutable;
}

View File

@ -1,206 +0,0 @@
/// <reference path="../src/modules.d.ts" />
import { OrderedMap, List, RecordOf, Record } from "immutable";
import TransitImmutable from "transit-immutable-js";
export type Maybe<T> = T | null;
export type TypeValPrimitive = string | FuncRecord | TypeWithArgsRecord;
export type TypeVal = TypeValPrimitive | List<TypeValPrimitive>;
export type TypeWithArgsProps = {
name: string;
args: List<TypeVal>;
};
export interface TypeWithArgsRecord extends RecordOf<TypeWithArgsProps> {}
export interface TagProps {
name: string;
comment: string;
}
export interface TagRecord extends RecordOf<TagProps> {}
export interface DocProps {
comment: string;
tags: List<RecordOf<TagProps>>;
}
export interface DocRecord extends RecordOf<DocProps> {}
export interface ParamProps {
name: string;
type: TypeVal;
optional: boolean;
doc: Maybe<DocRecord>;
}
export interface ParamRecord extends RecordOf<ParamProps> {}
export interface FuncProps {
node: "Func";
name: string | null;
params: List<RecordOf<ParamProps>>;
doc: Maybe<RecordOf<DocProps>>;
type: null | TypeVal;
}
export interface FuncRecord extends RecordOf<FuncProps> {}
export interface ClassMethodProps {
name: string;
params: List<ParamRecord>;
type: null | TypeVal;
doc: Maybe<DocRecord>;
}
export interface ClassMethodRecord extends RecordOf<ClassMethodProps> {}
export interface ClassProps {
node: "Class";
name: string | null;
members: List<ClassMethodRecord>;
doc: Maybe<DocRecord>;
}
export interface ClassRecord extends RecordOf<ClassProps> {}
export interface InterfaceProps {
node: "Interface";
name: string;
doc: Maybe<DocRecord>;
members: List<PropertyRecord>;
inherits: List<string>;
}
export interface InterfaceRecord extends RecordOf<InterfaceProps> {}
export interface PropertyProps {
name: string;
type: TypeVal | null;
doc: Maybe<DocRecord>;
}
export interface PropertyRecord extends RecordOf<PropertyProps> {}
export const TypeWithArgsRecord = Record<TypeWithArgsProps>(
{
name: "",
args: List(),
},
"TypeWithArgsRecord"
);
export const TagRecord = Record<TagProps>(
{
name: "",
comment: "",
},
"TagRecord"
);
export const DocRecord = Record<DocProps>(
{
comment: "",
tags: List(),
},
"DocRecord"
);
export const ParamRecord = Record<ParamProps>(
{
name: "",
type: "",
optional: false,
doc: null,
},
"ParamRecord"
);
export const FuncRecord = Record<FuncProps>(
{
node: "Func",
name: "",
params: List(),
doc: null,
type: null,
},
"FuncRecord"
);
export const ClassRecord = Record<ClassProps>(
{
node: "Class",
name: "",
members: List(),
doc: null,
},
"ClassRecord"
);
export const ClassMethodRecord = Record<ClassMethodProps>(
{
name: "",
params: List(),
type: null,
doc: null,
},
"ClassMethodRecord"
);
export const InterfaceRecord = Record<InterfaceProps>(
{
node: "Interface",
name: "",
doc: null,
members: List(),
inherits: List(),
},
"InterfaceRecord"
);
export const PropertyRecord = Record<PropertyProps>(
{
name: "",
type: null,
doc: null,
},
"PropertyRecord"
);
export type BaseRecords = FuncRecord | ClassRecord | InterfaceRecord;
export const ParsedFiles = Record(
{
api: OrderedMap<string, BaseRecords>(),
core: OrderedMap<string, BaseRecords>(),
types: OrderedMap<string, BaseRecords>(),
},
"ParsedFiles"
);
export const isFuncRecord = (val: any): val is FuncRecord => {
return val.node === "Func";
};
export const isClassRecord = (val: any): val is ClassRecord => {
return val.node === "Class";
};
export const isInterfaceRecord = (val: any): val is InterfaceRecord => {
return val.node === "Interface";
};
export const transit = TransitImmutable.withRecords([
TagRecord,
ClassMethodRecord,
ParamRecord,
ClassRecord,
FuncRecord,
DocRecord,
InterfaceRecord,
PropertyRecord,
TypeWithArgsRecord,
ParsedFiles,
]);

View File

@ -1,10 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"noEmit": false,
"resolveJsonModule": true
},
"exclude": ["./types", "./dist"]
}

View File

@ -1,224 +0,0 @@
import ts from "typescript";
import { List, OrderedMap } from "immutable";
import fs from "fs-extra";
import path from "path";
import {
TagRecord,
TypeVal,
ClassMethodRecord,
ClassRecord,
FuncRecord,
DocRecord,
ParamRecord,
InterfaceRecord,
PropertyRecord,
TypeWithArgsRecord,
transit,
ParsedFiles,
} from "../src/typedefs";
const TYPES_TO_EXPAND = new Set([
"Maybe",
"Record",
"Promise",
"PromiseLike",
"MaybePromise",
"MaybeThunk",
]);
function hasJsDoc(node: any): node is { jsDoc: ts.JSDoc[] } {
if ("jsDoc" in node && node.jsDoc.length > 0) {
return true;
}
return false;
}
function extractTags(tags: ReadonlyArray<ts.JSDocTag> = []) {
return tags.reduce((a, tag) => {
return a.push(
TagRecord({
comment: tag.comment,
name: tag.tagName.text,
})
);
}, List());
}
function parseSourceFile(source: ts.SourceFile) {
function setName(
map: OrderedMap<string, any>,
ident: ts.Identifier,
val: any
) {
const name = ident.getText(source);
if (map.has(name)) {
throw new Error(`Duplicate top level name ${name}`);
}
return map.set(ident.getText(source), val);
}
function typeWithArgs(text: string, arr: ts.NodeArray<ts.TypeNode>) {
return TypeWithArgsRecord({
name: text,
args: arr.reduce((a, b) => a.push(extractType(b)), List()),
});
}
function extractType(type: ts.TypeNode): TypeVal {
if (ts.isUnionTypeNode(type)) {
return type.types.reduce((a, b) => a.push(extractType(b)), List());
}
if (ts.isTypeReferenceNode(type)) {
const typeNameText = type.typeName.getText(source);
if (TYPES_TO_EXPAND.has(typeNameText) && type.typeArguments) {
return typeWithArgs(typeNameText, type.typeArguments);
}
return type.typeName.getText(source);
}
if (ts.isFunctionTypeNode(type)) {
return extractFn(type);
}
if (ts.isParenthesizedTypeNode(type)) {
return extractType(type.type);
}
return type.getText(source);
}
function extractMethods(
members: ts.NodeArray<ts.ClassElement | ts.TypeElement>
): List<ClassMethodRecord> {
return members.reduce((a, b) => {
if (ts.isMethodDeclaration(b)) {
return a.push(
ClassMethodRecord({
name: b.name.getText(source),
params: extractParams(b.parameters),
type: b.type ? extractType(b.type) : "unknown",
doc: extractDoc(b),
})
);
}
return a;
}, List<ClassMethodRecord>());
}
function extractProperties(
members: ts.NodeArray<ts.ClassElement | ts.TypeElement>
) {
return members.reduce((a, b) => {
if (ts.isPropertySignature(b)) {
return a.push(
PropertyRecord({
name: b.name.getText(source),
type: b.type ? extractType(b.type) : null,
doc: extractDoc(b),
})
);
}
return a;
}, List<PropertyRecord>());
}
function extractParams(params: ts.NodeArray<ts.ParameterDeclaration>) {
return params.reduce((a, param) => {
return a.push(
ParamRecord({
name: param.name.getText(source),
type: param.type ? extractType(param.type) : "unknown",
optional: Boolean(param.questionToken),
doc: extractDoc(param),
})
);
}, List<ParamRecord>());
}
function extractDoc(type: ts.Node) {
if (hasJsDoc(type)) {
if (type.jsDoc.length > 1) {
throw new Error("Each item must have only one associated jsDoc block");
}
const doc = type.jsDoc[0];
return DocRecord({
comment: doc.comment,
tags: extractTags(doc.tags),
});
}
return null;
}
function extractFn(node: ts.FunctionDeclaration | ts.FunctionTypeNode) {
return FuncRecord({
name: node.name ? node.name.getText(source) : null,
doc: extractDoc(node),
params: extractParams(node.parameters),
type: node.type ? extractType(node.type) : null,
});
}
function extractClass(node: ts.ClassDeclaration) {
return ClassRecord({
name: node.name ? node.name.getText(source) : null,
doc: extractDoc(node),
members: extractMethods(node.members),
});
}
function extractInterface(node: ts.InterfaceDeclaration) {
return InterfaceRecord({
name: node.name.getText(source),
doc: extractDoc(node),
members: extractProperties(node.members),
inherits: node.heritageClauses
? extractHeritageClauses(node.heritageClauses)
: List(),
});
}
function extractHeritageClauses(nodeArr: ts.NodeArray<ts.HeritageClause>) {
return nodeArr.reduce((a, node) => {
return node.types.reduce((b, c) => {
return b.concat(c.getText(source));
}, a);
}, List());
}
return source.statements.reduce((a, b) => {
if (ts.isFunctionDeclaration(b) && b.name) {
return setName(a, b.name, extractFn(b));
}
if (ts.isClassDeclaration(b) && b.name) {
return setName(a, b.name, extractClass(b));
}
if (ts.isInterfaceDeclaration(b)) {
return setName(a, b.name, extractInterface(b));
}
if (ts.isTypeAliasDeclaration(b)) {
}
return a;
}, OrderedMap<string, any>());
}
export async function run() {
const typesPath = path.join(__dirname, "../../../dist/types.d.ts");
const definitionPath = path.join(__dirname, "../../../dist/definitions.d.ts");
const corePath = path.join(__dirname, "../../../dist/core.d.ts");
const typesSource = ts.createSourceFile(
typesPath,
await fs.readFile(typesPath, "utf-8"),
ts.ScriptTarget.ES2017
);
const definitionSource = ts.createSourceFile(
definitionPath,
await fs.readFile(definitionPath, "utf-8"),
ts.ScriptTarget.ES2017
);
const coreSource = ts.createSourceFile(
definitionPath,
await fs.readFile(corePath, "utf-8"),
ts.ScriptTarget.ES2017
);
const parsed = ParsedFiles()
.set("api", parseSourceFile(definitionSource))
.set("types", parseSourceFile(typesSource))
.set("core", parseSourceFile(coreSource));
await fs.writeFile(
path.join(__dirname, "../src/allTypes.json"),
JSON.stringify(parsed, null, 2)
);
await fs.writeFile(
path.join(__dirname, "../src/allTypes-transit.json"),
JSON.stringify(transit.toJSON(parsed), null, 2)
);
}

View File

@ -8,11 +8,6 @@ import { unlinkExamples } from "./scripts/unlink-examples";
import { allExamples } from "./scripts/constants";
import { upgradeDeps } from "./scripts/upgrade-deps";
function requireFresh(pkg: string) {
delete require.cache[require.resolve(pkg)];
return require(pkg);
}
const serviceRegistry = new Map<string, ChildProcess>();
const runService = (
@ -32,21 +27,6 @@ const runService = (
}
};
gulp.task("api-types", [], async () => {
const { run } = requireFresh("./api/types/types-to-json.ts");
await run();
});
gulp.task("watch:api-types", ["core-tsc", "api-types"], () => {
gulp.watch(
[
path.join(__dirname, "../dist/*.d.ts"),
path.join(__dirname, "./api/types/*.ts"),
],
["api-types"]
);
});
gulp.task("docusaurus", () => {
runService("yarn", "docusaurus-start", { stdio: "ignore" }, true);
});
@ -65,14 +45,7 @@ gulp.task("core-tsc", () => {
gulp.task(
"start",
[
"docusaurus",
"link-examples",
"webpack",
"watch:api-types",
"api-tsc",
"core-tsc",
],
["docusaurus", "link-examples", "webpack", "core-tsc"],
() => {
console.log("Server starting, please wait...");
gulp.watch(path.join(__dirname, "siteConfig.js"), ["docusaurus"]);

View File

@ -32,8 +32,6 @@
"@types/graphql": "^14.0.3",
"@types/gulp": "3.8.36",
"@types/lodash.debounce": "^4.0.4",
"@types/marked": "^0.4.2",
"@types/prismjs": "^1.9.0",
"@types/react": "^16.7.6",
"@types/react-dom": "^16.0.9",
"@types/webpack": "^4.4.19",
@ -43,17 +41,13 @@
"fs-extra": "^7.0.1",
"get-port": "^4.0.0",
"gulp": "^3.9.1",
"immutable": "^4.0.0-rc.12",
"lodash.debounce": "^4.0.8",
"lodash.groupby": "^4.6.0",
"lz-string": "^1.4.4",
"marked": "^0.5.2",
"monaco-editor-webpack-plugin": "^1.6.0",
"prismjs": "^1.15.0",
"raw-loader": "^0.5.1",
"react-split-pane": "^0.1.84",
"style-loader": "^0.23.1",
"transit-immutable-js": "^0.7.0",
"transit-js": "^0.8.861",
"ts-node": "^7.0.1",
"typescript": "3.1.6",

View File

@ -25,6 +25,11 @@
dependencies:
any-observable "^0.3.0"
"@types/faker@^4.1.5":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/faker/-/faker-4.1.5.tgz#8f620f9c9a67150aa0a32b4e8a407da43fca61d4"
integrity sha512-YSDqoBEWYGdNk53xSkkb6REaUaVSlIjxIAGjj/nbLzlZOit7kUU+nA2zC2qQkIVO4MQ+3zl4Sz7aw+kbpHHHUQ==
"@types/graphql@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-14.0.3.tgz#389e2e5b83ecdb376d9f98fae2094297bc112c1c"
@ -40,7 +45,7 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47"
integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==
"@types/prettier@^1.13.2":
"@types/prettier@^1.15.2":
version "1.15.2"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.15.2.tgz#91594ea7cb6f3b1f7ea69f32621246654c7cc231"
integrity sha512-XIB0ZCaFZmWUHAa9dBqP5UKXXHwuukmVlP+XcyU94dui2k+l2lG+CHAbt2ffenHPUqoIs5Beh8Pdf2YEq/CZ7A==
@ -1105,6 +1110,11 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
faker@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f"
integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"