Skip to content

Commit

Permalink
Fixups/tests/docs for derived context
Browse files Browse the repository at this point in the history
Summary:

Test Plan:
  • Loading branch information
captbaritone committed Jan 15, 2025
1 parent a9438e4 commit 0e141b7
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 24 deletions.
31 changes: 7 additions & 24 deletions website/docs/04-docblock-tags/04-context.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import GratsCode from "@site/src/components/GratsCode";
import DerivedContext from "!!raw-loader!./snippets/04-derived-context.out";
import DerivedContextMemoized from "!!raw-loader!./snippets/04-derived-context-memoized.out";

# Context

In addition to the [arguments object](./03-arguments.mdx), each resolver method/function may also access a [context value](https://graphql.org/learn/execution/#root-fields--resolvers). Context is the standard way to implement dependency injection for GraphQL resolvers. Typically the context value will be an object including the current request, information about the requesting user, as well as a database connection and per-request caches, such as [DataLoaders](https://github.com/graphql/dataloader).
Expand Down Expand Up @@ -45,32 +49,11 @@ In some cases you may have a context value that is expensive to create, or is on

Once a derived resolver is defined, the type it returns becomes a new context type. **Any resolver argument with this type will receive the value produced by the derived resolver returning that type.**

:::tip
Derived context functions will be called individually by each resolve that wants to access the derived context value. If you want the result to be reused across multiple resolvers, you should memoize the result.
:::

```ts
/**
* A resolver which reads both the global context and a derived context value.
*
* @gqlQueryContext */
export function me(ctx: GQLCtx, db: Database): User {
return db.users.getById(ctx.userID);
}

// A derived context resolver cached using a WeakMap to ensure the value is
// computed at most once per request.
<GratsCode out={DerivedContext} mode="ts" />

const DB_CACHE = new WeakMap<GQLCtx, Database>();
Derived context functions will be called individually by each resolve that wants to access the derived context value. If you want the result to be reused across multiple resolvers, you should memoize the result:

/** @gqlContext */
export function createDb(ctx: GQLCtx): Database {
if (!DB_CACHE.has(ctx)) {
DB_CACHE.set(ctx, new Database());
}
return DB_CACHE.get(ctx);
}
```
<GratsCode out={DerivedContextMemoized} mode="ts" />

## Constructing your root context object

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// trim-start
class DB {
selectUser(): { name: string } {
return { name: "Alice" };
}
}

// trim-end
/** @gqlContext */
type Ctx = {};

// A derived context resolver cached using a WeakMap to ensure the value is
// computed at most once per request.

const DB_CACHE = new WeakMap<Ctx, DB>();

// highlight-start
/** @gqlContext */
export function getDb(ctx: Ctx): DB {
if (!DB_CACHE.has(ctx)) {
DB_CACHE.set(ctx, new DB());
}
return DB_CACHE.get(ctx)!;
}
// highlight-end

/**
* @gqlQueryField */
export function me(db: DB): string {
return db.selectUser().name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// trim-start
class DB {
selectUser(): { name: string } {
return { name: "Alice" };
}
}

// trim-end
/** @gqlContext */
type Ctx = {};

// A derived context resolver cached using a WeakMap to ensure the value is
// computed at most once per request.

const DB_CACHE = new WeakMap<Ctx, DB>();

// highlight-start
/** @gqlContext */
export function getDb(ctx: Ctx): DB {
if (!DB_CACHE.has(ctx)) {
DB_CACHE.set(ctx, new DB());
}
return DB_CACHE.get(ctx)!;
}
// highlight-end

/**
* @gqlQueryField */
export function me(db: DB): string {
return db.selectUser().name;
}

=== SNIP ===
type Query {
me: String
}
=== SNIP ===
import { me as queryMeResolver, getDb as getDb } from "./04-derived-context-memoized.grats";
import { GraphQLSchema, GraphQLObjectType, GraphQLString } from "graphql";
export function getSchema(): GraphQLSchema {
const QueryType: GraphQLObjectType = new GraphQLObjectType({
name: "Query",
fields() {
return {
me: {
name: "me",
type: GraphQLString,
resolve(_source, _args, context) {
return queryMeResolver(getDb(context));
}
}
};
}
});
return new GraphQLSchema({
query: QueryType,
types: [QueryType]
});
}
25 changes: 25 additions & 0 deletions website/docs/04-docblock-tags/snippets/04-derived-context.grats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// trim-start
type DB = {
selectUser(): { name: string };
/*...*/
};

// trim-end
/** @gqlContext */
type Ctx = { db: DB };

// highlight-start
/** @gqlContext */
export function getDb(ctx: Ctx): DB {
return ctx.db;
}
// highlight-end

/**
* A field which reads a derived context. Grats will invoke the above `getDb`
* function and pass it to this resolver function.
*
* @gqlQueryField */
export function me(db: DB): string {
return db.selectUser().name;
}
58 changes: 58 additions & 0 deletions website/docs/04-docblock-tags/snippets/04-derived-context.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// trim-start
type DB = {
selectUser(): { name: string };
/*...*/
};

// trim-end
/** @gqlContext */
type Ctx = { db: DB };

// highlight-start
/** @gqlContext */
export function getDb(ctx: Ctx): DB {
return ctx.db;
}
// highlight-end

/**
* A field which reads a derived context. Grats will invoke the above `getDb`
* function and pass it to this resolver function.
*
* @gqlQueryField */
export function me(db: DB): string {
return db.selectUser().name;
}

=== SNIP ===
type Query {
"""
A field which reads a derived context. Grats will invoke the above `getDb`
function and pass it to this resolver function.
"""
me: String
}
=== SNIP ===
import { me as queryMeResolver, getDb as getDb } from "./04-derived-context.grats";
import { GraphQLSchema, GraphQLObjectType, GraphQLString } from "graphql";
export function getSchema(): GraphQLSchema {
const QueryType: GraphQLObjectType = new GraphQLObjectType({
name: "Query",
fields() {
return {
me: {
description: "A field which reads a derived context. Grats will invoke the above `getDb`\nfunction and pass it to this resolver function.",
name: "me",
type: GraphQLString,
resolve(_source, _args, context) {
return queryMeResolver(getDb(context));
}
}
};
}
});
return new GraphQLSchema({
query: QueryType,
types: [QueryType]
});
}

0 comments on commit 0e141b7

Please sign in to comment.