Skip to content

Commit

Permalink
feat(extension/transport-memory): config ctx (#1312)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt authored Jan 13, 2025
1 parent d52e7ab commit 23ced13
Show file tree
Hide file tree
Showing 23 changed files with 674 additions and 216 deletions.
2 changes: 1 addition & 1 deletion .github/actions/setup/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ runs:
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: '9.15.1'
version: '9.15.3'
run_install: false
# Cannot use corepack because it doesn't permit global package installs.
# @see https://github.com/pnpm/action-setup/issues/105#issuecomment-2468689290
Expand Down
121 changes: 113 additions & 8 deletions examples/40_other/transport-memory.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,138 @@
/**
* This example shows how you can send requests against an in-memory GraphQL schema instead of one hosted over HTTP.
*
* Imagine this example as server code that receives HTTP requests and internally fulfills them with requests to a GraphQL schema.
*
* Included here is how to work with Graffle's lightweight client-forking model to create a request-scoped Graffle client.
* By having a Graffle instance per request, the context value that the GraphQL resolvers get during execution can reflect
* the user making the request.
*/

import { Graffle } from 'graffle'
import { TransportMemory } from 'graffle/extensions/transport-memory'
import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql'
import { showJson } from '../$/helpers.js'

interface Context {
database: DatabaseClient
user: RequestingUser
}

interface RequestingUser {
id: string
}

interface DatabaseClient {
account: {
findUnique: (parameters: { where: { ownerId: string } }) => DatabaseModelAccount | undefined
}
user: {
findUnique: (parameters: { where: { id: string } }) => DatabaseModelUser | undefined
}
}

interface DatabaseModelUser {
id: string
name: string
}

interface DatabaseModelAccount {
id: string
ownerId: string
}

const databaseData = {
accounts: [{
id: `account_abc123`,
ownerId: `user_abc123`,
}],
users: [{
id: `user_abc123`,
name: `Kenya Hara`,
}],
}

const DatabaseClient = {
create: (): DatabaseClient => {
return {
account: {
findUnique: (parameters: { where: { ownerId: string } }) =>
databaseData.accounts.find((account) => account.ownerId === parameters.where.ownerId),
},
user: {
findUnique: (parameters: { where: { id: string } }) =>
databaseData.users.find((user) => user.id === parameters.where.id),
},
}
},
}

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: `Query`,
fields: {
foo: {
account: {
type: GraphQLString,
resolve: () => `bar`,
resolve: (_, __, context: Context) => {
const account = context.database.account.findUnique({ where: { ownerId: context.user.id } })
if (!account) throw new Error(`Account not found.`)
return account.id
},
},
},
}),
})

const graffle = Graffle
interface Token {
userId: string
}

const getAndValidateToken = (request: Request): Token => {
const tokenEncoded = request.headers.get(`authorization`)?.match(/^Bearer\s+(.+)$/)?.[1]
if (!tokenEncoded) throw new Error(`No token provided.`)
// ... decode token securely ...
return {
userId: tokenEncoded,
}
}

const database = DatabaseClient.create()

const baseGraffle = Graffle
.create()
.use(TransportMemory({ schema }))
.transport(`memory`)

const data = await graffle.gql`
{
foo
const handleRequest = async (request: Request) => {
const user = {
id: getAndValidateToken(request).userId,
}

const resolverContextValue = {
database,
user,
}
`.send()

showJson(data)
// Create a copy of Graffle with transport configured
// with context data particular to this request.
const requestScopedGraffle = baseGraffle.transport({
resolverValues: {
context: resolverContextValue,
},
})

const data = await requestScopedGraffle.gql`
{
account
}
`.send()

showJson(data)
}

// Server receives a request...
await handleRequest(
new Request(`https://foo.com`, {
headers: new Headers({ authorization: `Bearer user_abc123` }),
}),
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
headers: Headers {
accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8',
'content-type': 'application/json',
'x-sent-at-time': '1735576747087'
'x-sent-at-time': '1736741812397'
},
method: 'post',
url: 'http://localhost:3000/graphql',
Expand Down
2 changes: 1 addition & 1 deletion examples/__outputs__/20_output/output_envelope.output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
headers: Headers {
'content-type': 'application/graphql-response+json; charset=utf-8',
'content-length': '142',
date: 'Mon, 30 Dec 2024 16:39:07 GMT',
date: 'Mon, 13 Jan 2025 04:16:53 GMT',
connection: 'keep-alive',
'keep-alive': 'timeout=5'
},
Expand Down
2 changes: 1 addition & 1 deletion examples/__outputs__/40_other/transport-memory.output.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---------------------------------------- SHOW ----------------------------------------
{
"foo": "bar"
"account": "account_abc123"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.29.0'
'telemetry.sdk.version': '1.30.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'd0802dece691ebe8c01131ec4d27d6d7',
parentId: 'e4eb6d12c211c727',
traceId: '305fecdf1d521d48b39e0f3dde5e32e1',
parentId: 'd6c3800b8979fcd8',
traceState: undefined,
name: 'encode',
id: 'ee78c4c6fb1240b0',
id: '4efad046792865d3',
kind: 0,
timestamp: 1735576748202000,
duration: 992.75,
timestamp: 1736741813348000,
duration: 875.167,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -29,18 +29,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.29.0'
'telemetry.sdk.version': '1.30.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'd0802dece691ebe8c01131ec4d27d6d7',
parentId: 'e4eb6d12c211c727',
traceId: '305fecdf1d521d48b39e0f3dde5e32e1',
parentId: 'd6c3800b8979fcd8',
traceState: undefined,
name: 'pack',
id: 'f05b5fcacbd69b81',
id: '29d86e3c651e066b',
kind: 0,
timestamp: 1735576748204000,
duration: 12422.125,
timestamp: 1736741813349000,
duration: 7728.833,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -53,18 +53,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.29.0'
'telemetry.sdk.version': '1.30.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'd0802dece691ebe8c01131ec4d27d6d7',
parentId: 'e4eb6d12c211c727',
traceId: '305fecdf1d521d48b39e0f3dde5e32e1',
parentId: 'd6c3800b8979fcd8',
traceState: undefined,
name: 'exchange',
id: '9eda354ab343b5cc',
id: '5ded2022385d9c01',
kind: 0,
timestamp: 1735576748217000,
duration: 33329,
timestamp: 1736741813358000,
duration: 20876.084,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -77,18 +77,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.29.0'
'telemetry.sdk.version': '1.30.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'd0802dece691ebe8c01131ec4d27d6d7',
parentId: 'e4eb6d12c211c727',
traceId: '305fecdf1d521d48b39e0f3dde5e32e1',
parentId: 'd6c3800b8979fcd8',
traceState: undefined,
name: 'unpack',
id: '2e81e406aefc2866',
id: '609e080ad2ff4514',
kind: 0,
timestamp: 1735576748251000,
duration: 1168.708,
timestamp: 1736741813379000,
duration: 744.917,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -101,18 +101,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.29.0'
'telemetry.sdk.version': '1.30.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'd0802dece691ebe8c01131ec4d27d6d7',
parentId: 'e4eb6d12c211c727',
traceId: '305fecdf1d521d48b39e0f3dde5e32e1',
parentId: 'd6c3800b8979fcd8',
traceState: undefined,
name: 'decode',
id: '09041112bc15a037',
id: '60c901a55f2139da',
kind: 0,
timestamp: 1735576748252000,
duration: 474.417,
timestamp: 1736741813379000,
duration: 376.5,
attributes: {},
status: { code: 0 },
events: [],
Expand All @@ -125,18 +125,18 @@
'service.name': 'unknown_service:/Users/jasonkuhrt/Library/pnpm/nodejs/22.11.0/bin/node',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': 'opentelemetry',
'telemetry.sdk.version': '1.29.0'
'telemetry.sdk.version': '1.30.0'
}
},
instrumentationScope: { name: 'graffle', version: undefined, schemaUrl: undefined },
traceId: 'd0802dece691ebe8c01131ec4d27d6d7',
traceId: '305fecdf1d521d48b39e0f3dde5e32e1',
parentId: undefined,
traceState: undefined,
name: 'request',
id: 'e4eb6d12c211c727',
id: 'd6c3800b8979fcd8',
kind: 0,
timestamp: 1735576748201000,
duration: 51608.708,
timestamp: 1736741813347000,
duration: 32641.166,
attributes: {},
status: { code: 0 },
events: [],
Expand Down
16 changes: 8 additions & 8 deletions examples/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 23ced13

Please sign in to comment.