Skip to content

Commit

Permalink
docs: sync docs changes to deno
Browse files Browse the repository at this point in the history
  • Loading branch information
hayes committed May 9, 2021
1 parent 07c727b commit ce2ffc5
Show file tree
Hide file tree
Showing 10 changed files with 2,038 additions and 28 deletions.
48 changes: 38 additions & 10 deletions packages/deno/packages/core/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
# GiraphQL SchemaBuilder
## GiraphQL - A plugin based GraphQL schema builder for typescript

GiraphQL is library for creating GraphQL schemas in typescript using a strongly typed code first
approach. The GiraphQL schema builder makes writing schemas easy by providing a simple clean API
with helpful auto-completes, and removing the need for compile steps or defining the same types in
multiple files.
GiraphQL makes writing graphql schemas in typescript easy, fast and enjoyable. The core of GiraphQL
adds 0 overhead at runtime, and has `graphql` as its only dependency.

By leaning heavily on typescripts ability to infer types, GiraphQL is the most type-safe way of
writing GraphQL schemas in typescript/node while requiring very few manual type definitions and no
code generation.

GiraphQL has a unique and powerful plugin system that makes every plugin feel like its features are
built into the core library. Plugins can extend almost any part of the API by adding new options or
methods that can take full advantage of GiraphQLs type system.

## Hello, World

```typescript
import SchemaBuilder from '@giraphql/core';
import { ApolloServer } from 'apollo-server';
import SchemaBuilder from '@giraphql/core';

const builder = new SchemaBuilder({});

Expand All @@ -22,11 +30,31 @@ builder.queryType({
}),
});

const server = new ApolloServer({
new ApolloServer({
schema: builder.toSchema({}),
});

server.listen(3000);
}).listen(3000);
```

## Plugins that make GiraphQL even better

- ## [Scope Auth](plugins/scope-auth.md)
Add global, type level, or field level authorization checks to your schema
- ## [Validation](plugins/validation.md)
Validating your inputs and arguments
- ## [Dataloader](plugins/dataloader.md)
Quickly define data-loaders for your types and fields to avoid n+1 queries.
- ## [Relay](plugins/relay.md)
- Easy to use builder methods for defining relay style nodes and connections, and helpful utilities
for cursor based pagination.
- ## [Simple Objects](plugins/simple-objects.md)
Define simple object types without resolvers or manual type definitions.
- ## [Mocks](plugins/mocks.md)
Add mock resolver for easier testing
- ## [Sub-Graph](plugins/sub-graph.md)
Build multiple subsets of your graph to easily share code between internal and external APIs.
- ## [Directives](plugins/directives.md)
Integrate with existing schema graphql directives in a type-safe way.
- ## [Smart Subscriptions](plugins/smart-subscriptions.md)
Make any part of your graph subscribable to get live updates as your data changes.

## Full docs available at https://giraphql.com
239 changes: 238 additions & 1 deletion packages/deno/packages/plugin-dataloader/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,240 @@
# Dataloader Plugin for GiraphQL

## Full docs available at https://giraphql.com/plugins/dataloader
This plugin makes it easy to add fields and types that are loaded through a dataloader.

## Usage

### Install

To use the dataloader plugin you will need to install both the `dataloader` package and the
validation plugin:

```bash
yarn add dataloader @giraphql/plugin-dataloader
```

### Setup

```typescript
import DataloaderPlugin from '@giraphql/plugin-dataloader';
const builder = new SchemaBuilder({
plugins: [DataloaderPlugin],
});
```

### loadable objects

To create an object type that can be loaded with a dataloader use the new `builder.loadableObject`
method:

```ts
const User = builder.loadableObject('User', {
// load will be called with ids of users that need to be loaded
// Note that the types for keys (and context if present) are required
load: (ids: string[], context: ContextType) => context.loadUsersById(ids),
fields: (t) => ({
id: t.exposeID('id', {}),
username: t.string({
// the shape of parent will be inferred from `loadUsersById()` above
resolve: (parent) => parent.username,
}),
}),
});
```

When defining fields that return `User`s, you will now be able to return either a `string` (based in
ids param of `load`), or a User object (type based on the return type of `loadUsersById`).

```ts
builder.queryType({
fields: (t) => ({
user: t.field({
type: User,
args: {
id: t.arg.string({ required: true }),
},
// Here we can just return the ID directly rather than loading the user ourselves
resolve: (root, args) => args.id,
}),
currentUser: t.field({
type: User,
// If we already have the user, we use it, and the dataloader will not be called
resolve: (root, args, context) => context.currentUser,
}),
users: t.field({
type: [User],
args: {
ids: t.arg.stringList({ required: true }),
},
// Mixing ids and user objects also works
resolve: (_root, args, context) => [...args.ids, context.CurrentUser],
}),
}),
});
```

GiraphQL will detect when a resolver returns `string`, `number`, or `bigint` (typescript will
constrain the allowed types to whatever is expected by the load function). If a resolver returns an
object instead, GiraphQL knows it can skip the dataloader for that object.

### loadable fields

In some cases you may need more granular dataloaders. To handle these cases there is a new
`t.loadable` method for defining fields with their own dataloaders.

```ts
// Normal object that the fields below will load
const Post = builder.objectRef<{ id: string; title: string; content: string }>('Post').implement({
fields: (t) => ({
id: t.exposeID('id', {}),
title: t.exposeString('title', {}),
content: t.exposeString('title', {}),
}),
});

// Loading a single
builder.objectField(User, 'latestPost', (t) =>
t.loadable({
type: Post,
// will be called with ids of latest posts for all users in query
load: (ids: number[], context) => context.loadPosts(ids),
resolve: (user, args) => user.lastPostID,
}),
);
// Loading a multiple
builder.objectField(User, 'posts', (t) =>
t.loadable({
type: [Post],
// will be called with ids of posts loaded for all users in query
load: (ids: number[], context) => context.loadPosts(ids),
resolve: (user, args) => user.postIDs,
}),
);
```

### Manually using dataloader

Dataloaders for "loadable" objects can be accessed via their ref by passing in the context object
for the current request. dataloaders are not shared across requests, so we need the context to get
the correct dataloader for the current request:

```ts
// create loadable object
const User = builder.loadableObject('User', {
load: (ids: string[], context: ContextType) => context.loadUsersById(ids),
fields: (t) => ({
id: t.exposeID('id', {}),
}),
});

builder.queryField('user', (t) =>
t.field({
type: User,
resolve: (parent, args, context) => {
// get data loader for User type
const loader = User.getDataloader(context);

// manually load a user
return loader.load('123');
},
}),
);
```

### Errors

Calling dataloader.loadMany will resolve to a value like (Type | Error)[]. Your `load` function may
also return results in that format if your loader can have parital failures. GraphQL does not have
special handling for Error objects. Instead GiraphQL will map these results to something like
`(Type | Promise<Type>)[]` where Errors are replaced with promises that will be rejected. This
allows the normal graphql resolver flow to correctly handle these errors.

If you are using the `loadMany` method from a dataloader manually, you can apply the same mapping
using the `rejectErrors` helper:

```ts
import { rejectErrors } from '@giraphql/plugin-dataloader';

builder.queryField('user', (t) =>
t.field({
type: [User],
resolve: (parent, args, context) => {
const loader = User.getDataloader(context);

return rejectErrors(loader.loadMany(['123', '456']));
},
}),
);
```

### (Optional) Adding loaders to context

If you want to make dataloaders accessible via the context object directly, there is some additional
setup required. Below are a few options for different ways you can load data from the context
object. You can determine which of these options works best for you or add you own helpers.

First you'll need to update the types for your context type:

```ts
import { LoadableRef } from '@giraphql/plugin-dataloader';

export interface ContextType {
userLoader: DataLoader<string, { id: number }>; // expose a specific loader
getLoader: <K, V>(ref: LoadableRef<K, V, ContextType>) => DataLoader<K, V>; // helper to get a loader from a ref
load: <K, V>(ref: LoadableRef<K, V, ContextType>, id: K) => Promise<V>; // helper for loading a single resource
loadMany: <K, V>(ref: LoadableRef<K, V, ContextType>, ids: K[]) => Promise<(Error | V)[]>; // helper for loading many
// other context fields
}
```

next you'll need to update your context factory function. The exact format of this depends on what
graphql server implementation you are using.

```ts
import { initContextCache } from '@giraphql/core';
import { LoadableRef, rejectErrors } from '@giraphql/plugin-dataloader';

export const createContext = (req, res): ContextType => ({
// Adding this will prevent any issues if you server implementation
// copies or extends the context object before passing it to your resolvers
...initContextCache(),

// using getters allows us to access the context object using `this`
get userLoader() {
return User.getDataloader(this);
},
get getLoader() {
return <K, V>(ref: LoadableRef<K, V, ContextType>) => ref.getDataloader(this);
},
get load() {
return <K, V>(ref: LoadableRef<K, V, ContextType>, id: K) => ref.getDataloader(this).load(id);
},
get loadMany() {
return <K, V>(ref: LoadableRef<K, V, ContextType>, ids: K[]) =>
rejectErrors(ref.getDataloader(this).loadMany(ids));
},
});
```

Now you can use these helpers from your context object:

```ts
builder.queryFields((t) => ({
fromContext1: t.field({
type: User,
resolve: (root, args, { userLoader }) => userLoader.load('123'),
}),
fromContext2: t.field({
type: User,
resolve: (root, args, { getLoader }) => getLoader(User).load('456'),
}),
fromContext3: t.field({
type: User,
resolve: (root, args, { load }) => load(User, '789'),
}),
fromContext4: t.field({
type: [User],
resolve: (root, args, { loadMany }) => loadMany(User, ['123', '456']),
}),
}));
```
Loading

0 comments on commit ce2ffc5

Please sign in to comment.