From 23ced13054b48a508b2decb38366f0a0ffdf3832 Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Sun, 12 Jan 2025 23:21:35 -0500 Subject: [PATCH] feat(extension/transport-memory): config ctx (#1312) --- .github/actions/setup/action.yaml | 2 +- examples/40_other/transport-memory.ts | 121 +++++++++++++++-- ...tension_headers__dynamicHeaders.output.txt | 2 +- .../20_output/output_envelope.output.txt | 2 +- .../40_other/transport-memory.output.txt | 2 +- ...on_opentelemetry__opentelemetry.output.txt | 70 +++++----- examples/pnpm-lock.yaml | 16 +-- package.json | 4 +- src/client/properties/transport.ts | 13 ++ .../TransportMemory/TransportMemory.ts | 31 ++++- src/lib/grafaid/execute.ts | 44 ++++-- .../extension/opentelemetry.detail.md | 70 +++++----- .../examples/extension/opentelemetry.md | 70 +++++----- .../examples/other/transport-memory.detail.md | 117 ++++++++++++++-- .../examples/other/transport-memory.md | 117 ++++++++++++++-- .../examples/output/envelope.detail.md | 2 +- .../_snippets/examples/output/envelope.md | 2 +- .../transport-http/dynamic-headers.detail.md | 2 +- .../transport-http/dynamic-headers.md | 2 +- .../10_transport-http/dynamic-headers.md | 2 +- .../content/examples/20_output/envelope.md | 2 +- .../examples/40_other/transport-memory.md | 127 ++++++++++++++++-- .../examples/60_extension/opentelemetry.md | 70 +++++----- 23 files changed, 674 insertions(+), 216 deletions(-) diff --git a/.github/actions/setup/action.yaml b/.github/actions/setup/action.yaml index 4c48d6379..69db04ac8 100644 --- a/.github/actions/setup/action.yaml +++ b/.github/actions/setup/action.yaml @@ -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 diff --git a/examples/40_other/transport-memory.ts b/examples/40_other/transport-memory.ts index a8c099fb1..9e6c0878c 100644 --- a/examples/40_other/transport-memory.ts +++ b/examples/40_other/transport-memory.ts @@ -1,5 +1,11 @@ /** * 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' @@ -7,27 +13,126 @@ 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` }), + }), +) diff --git a/examples/__outputs__/10_transport-http/transport-http_extension_headers__dynamicHeaders.output.txt b/examples/__outputs__/10_transport-http/transport-http_extension_headers__dynamicHeaders.output.txt index 132c64a17..520efdcb0 100644 --- a/examples/__outputs__/10_transport-http/transport-http_extension_headers__dynamicHeaders.output.txt +++ b/examples/__outputs__/10_transport-http/transport-http_extension_headers__dynamicHeaders.output.txt @@ -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', diff --git a/examples/__outputs__/20_output/output_envelope.output.txt b/examples/__outputs__/20_output/output_envelope.output.txt index b7ed8a4af..f923e1109 100644 --- a/examples/__outputs__/20_output/output_envelope.output.txt +++ b/examples/__outputs__/20_output/output_envelope.output.txt @@ -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' }, diff --git a/examples/__outputs__/40_other/transport-memory.output.txt b/examples/__outputs__/40_other/transport-memory.output.txt index 6a9c0f055..6a73ba738 100644 --- a/examples/__outputs__/40_other/transport-memory.output.txt +++ b/examples/__outputs__/40_other/transport-memory.output.txt @@ -1,4 +1,4 @@ ---------------------------------------- SHOW ---------------------------------------- { - "foo": "bar" + "account": "account_abc123" } \ No newline at end of file diff --git a/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt b/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt index a5f0a238d..ece856ffd 100644 --- a/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt +++ b/examples/__outputs__/60_extension/extension_opentelemetry__opentelemetry.output.txt @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], @@ -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: [], diff --git a/examples/pnpm-lock.yaml b/examples/pnpm-lock.yaml index 96216405a..0853e43fb 100644 --- a/examples/pnpm-lock.yaml +++ b/examples/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: packages: - '@0no-co/graphql.web@1.0.12': - resolution: {integrity: sha512-BTDjjsV/zSPy5fqItwm+KWUfh9CSe9tTtR6rCB72ddtkAxdcHbi4Ir4r/L1Et4lyxmL+i7Rb3m9sjLLi9tYrzA==} + '@0no-co/graphql.web@1.0.13': + resolution: {integrity: sha512-jqYxOevheVTU1S36ZdzAkJIdvRp2m3OYIG5SEoKDw5NI8eVwkoI0D/Q3DYNGmXCxkA6CQuoa7zvMiDPTLqUNuw==} peerDependencies: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 peerDependenciesMeta: @@ -181,8 +181,8 @@ packages: ts-toolbelt@9.6.0: resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} - type-fest@4.31.0: - resolution: {integrity: sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==} + type-fest@4.32.0: + resolution: {integrity: sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw==} engines: {node: '>=16'} typescript@5.7.2: @@ -195,7 +195,7 @@ packages: snapshots: - '@0no-co/graphql.web@1.0.12(graphql@16.10.0)': + '@0no-co/graphql.web@1.0.13(graphql@16.10.0)': optionalDependencies: graphql: 16.10.0 @@ -214,7 +214,7 @@ snapshots: string-length: 6.0.0 strip-ansi: 7.1.0 ts-toolbelt: 9.6.0 - type-fest: 4.31.0 + type-fest: 4.32.0 zod: 3.24.1 '@molt/types@0.2.0': @@ -286,7 +286,7 @@ snapshots: graffle@file:..(@opentelemetry/api@1.9.0)(graphql@16.10.0): dependencies: - '@0no-co/graphql.web': 1.0.12(graphql@16.10.0) + '@0no-co/graphql.web': 1.0.13(graphql@16.10.0) '@graphql-typed-document-node/core': 3.2.0(graphql@16.10.0) '@molt/command': 0.9.0 es-toolkit: 1.31.0 @@ -321,7 +321,7 @@ snapshots: ts-toolbelt@9.6.0: {} - type-fest@4.31.0: {} + type-fest@4.32.0: {} typescript@5.7.2: {} diff --git a/package.json b/package.json index 9e3b42c40..e9c0f6854 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "graffle", "description": "Simple GraphQL Client for JavaScript. Minimal. Extensible. Type Safe. Runs everywhere.", "version": "0.0.0-dripip", - "packageManager": "pnpm@9.15.1", + "packageManager": "pnpm@9.15.3", "type": "module", "bin": { "graffle": "./execute.sh" @@ -105,7 +105,7 @@ "website:gen:graffle": "cd website && pnpm i && pnpm gen:graffle", "gen:examples": "tsx scripts/generate-examples-derivatives/generate.ts && pnpm format", "gen:examples:outputs": "tsx scripts/generate-examples-derivatives/generate.ts --outputs", - "dev": "tsc --project tsconfig.build.json --watch", + "build:watch": "tsc --project tsconfig.build.json --watch", "format": "pnpm build:docs && dprint fmt", "lint": "eslint . --fix --ignore-pattern 'website' --ignore-pattern 'build'", "check": "pnpm check:types && pnpm check:format && pnpm check:lint && pnpm check:publint && pnpm examples:check:types", diff --git a/src/client/properties/transport.ts b/src/client/properties/transport.ts index 34516facd..34f6fb798 100644 --- a/src/client/properties/transport.ts +++ b/src/client/properties/transport.ts @@ -122,6 +122,19 @@ const reducerTransportConfig = ( } } + if (transportName === `memory`) { + // @ts-expect-error + if (config.resolverValues?.context) { + // @ts-expect-error + newConfiguration.resolverValues.context = config.resolverValues.context + } + // @ts-expect-error + if (config.resolverValues?.root) { + // @ts-expect-error + newConfiguration.resolverValues.root = config.resolverValues.root + } + } + return { ...state, transports: { diff --git a/src/extensions/TransportMemory/TransportMemory.ts b/src/extensions/TransportMemory/TransportMemory.ts index 0cf25e952..de771a691 100644 --- a/src/extensions/TransportMemory/TransportMemory.ts +++ b/src/extensions/TransportMemory/TransportMemory.ts @@ -9,20 +9,37 @@ import type { PartialOrUndefined } from '../../lib/prelude.js' import type { RequestPipeline } from '../../requestPipeline/RequestPipeline.js' export interface TransportMemoryConstructor { - <$ConfigInit extends ConfigInit = ConfigInitEmpty>( - configInit?: $ConfigInit, - ): TransportMemory<$ConfigInit> + <$ConfigurationInit extends ConfigurationInit = ConfigurationInitEmpty>( + configurationInit?: $ConfigurationInit, + ): TransportMemory<$ConfigurationInit> } export interface Configuration { + /** + * The schema to execute documents against. + */ schema: Grafaid.Schema.Schema + resolverValues?: { + /** + * The value to use for parent (aka. source) on _root_ resolvers. + * + * If a function is provided, it will be called before each request to get the root value. + */ + root?: unknown + /** + * The value to use for resolver context during execution. + * + * If a function is provided, it will be called before each request to get the context value. + */ + context?: object | (() => object) + } } -export type ConfigInit = PartialOrUndefined +export type ConfigurationInit = PartialOrUndefined -export interface ConfigInitEmpty {} +export interface ConfigurationInitEmpty {} -export interface TransportMemory<$ConfigInit extends ConfigInit = ConfigInitEmpty> extends Extension { +export interface TransportMemory<$ConfigInit extends ConfigurationInit = ConfigurationInitEmpty> extends Extension { name: `TransportMemory` config: Configuration configInit: $ConfigInit @@ -84,7 +101,7 @@ export const TransportMemory: TransportMemoryConstructor = create({ return { transport(create) { return create(`memory`) - .config<{ schema: Grafaid.Schema.Schema }>() + .config() .configInit<{}>() .defaults(config) .step(`pack`, { diff --git a/src/lib/grafaid/execute.ts b/src/lib/grafaid/execute.ts index 8a17abe00..0f7b23c69 100644 --- a/src/lib/grafaid/execute.ts +++ b/src/lib/grafaid/execute.ts @@ -3,28 +3,44 @@ import { execute as graphqlExecute, graphql } from 'graphql' import type { RequestInput } from './graphql.js' import { TypedDocument } from './typed-document/__.js' -export type ExecuteInput = { +export type ExecuteParameters = { request: RequestInput schema: GraphQLSchema + resolverValues?: { + context?: object | (() => object) + root?: unknown + } } -export const execute = async (input: ExecuteInput): Promise => { - const { schema, request: { query, operationName, variables: variableValues } } = input - if (TypedDocument.isString(query)) { +export const execute = async (parameters: ExecuteParameters): Promise => { + const schema = parameters.schema + const document = TypedDocument.unType(parameters.request.query) + const operationName = parameters.request.operationName + const variableValues = parameters.request.variables + const contextValue = typeof parameters.resolverValues?.context === `function` + ? parameters.resolverValues.context() + : parameters.resolverValues?.context + const rootValue = typeof parameters.resolverValues?.root === `function` + ? parameters.resolverValues.root() + : parameters.resolverValues?.root + + if (typeof document === `string`) { return await graphql({ schema, - source: query as string, + source: document, variableValues, operationName, - // contextValue: createContextValue(), // todo - }) - } else { - return await graphqlExecute({ - schema, - document: query, - variableValues, - operationName, - // contextValue: createContextValue(), // todo + contextValue, + rootValue, }) } + + return await graphqlExecute({ + schema, + document, + variableValues, + operationName, + contextValue, + rootValue, + }) } diff --git a/website/content/_snippets/examples/extension/opentelemetry.detail.md b/website/content/_snippets/examples/extension/opentelemetry.detail.md index 089653ef6..3446379d0 100644 --- a/website/content/_snippets/examples/extension/opentelemetry.detail.md +++ b/website/content/_snippets/examples/extension/opentelemetry.detail.md @@ -39,18 +39,18 @@ console.log(data) '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: [], @@ -66,18 +66,18 @@ console.log(data) '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: [], @@ -93,18 +93,18 @@ console.log(data) '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: [], @@ -120,18 +120,18 @@ console.log(data) '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: [], @@ -147,18 +147,18 @@ console.log(data) '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: [], @@ -174,18 +174,18 @@ console.log(data) '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: [], diff --git a/website/content/_snippets/examples/extension/opentelemetry.md b/website/content/_snippets/examples/extension/opentelemetry.md index 76922786e..d88a44a6f 100644 --- a/website/content/_snippets/examples/extension/opentelemetry.md +++ b/website/content/_snippets/examples/extension/opentelemetry.md @@ -37,18 +37,18 @@ console.log(data) '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: [], @@ -64,18 +64,18 @@ console.log(data) '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: [], @@ -91,18 +91,18 @@ console.log(data) '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: [], @@ -118,18 +118,18 @@ console.log(data) '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: [], @@ -145,18 +145,18 @@ console.log(data) '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: [], @@ -172,18 +172,18 @@ console.log(data) '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: [], diff --git a/website/content/_snippets/examples/other/transport-memory.detail.md b/website/content/_snippets/examples/other/transport-memory.detail.md index fabf00206..bd6048e22 100644 --- a/website/content/_snippets/examples/other/transport-memory.detail.md +++ b/website/content/_snippets/examples/other/transport-memory.detail.md @@ -15,37 +15,136 @@ import { Graffle } from 'graffle' import { TransportMemory } from 'graffle/extensions/transport-memory' import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql' +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, } -`.send() -console.log(data) + const resolverContextValue = { + database, + user, + } + + // 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() + + console.log(data) +} + +// Server receives a request... +await handleRequest( + new Request(`https://foo.com`, { + headers: new Headers({ authorization: `Bearer user_abc123` }), + }), +) ``` ```json { - "foo": "bar" + "account": "account_abc123" } ``` diff --git a/website/content/_snippets/examples/other/transport-memory.md b/website/content/_snippets/examples/other/transport-memory.md index fa620bbcc..937eda67f 100644 --- a/website/content/_snippets/examples/other/transport-memory.md +++ b/website/content/_snippets/examples/other/transport-memory.md @@ -13,37 +13,136 @@ import { Graffle } from 'graffle' import { TransportMemory } from 'graffle/extensions/transport-memory' import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql' +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, } -`.send() -console.log(data) + const resolverContextValue = { + database, + user, + } + + // 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() + + console.log(data) +} + +// Server receives a request... +await handleRequest( + new Request(`https://foo.com`, { + headers: new Headers({ authorization: `Bearer user_abc123` }), + }), +) ``` ```json { - "foo": "bar" + "account": "account_abc123" } ``` diff --git a/website/content/_snippets/examples/output/envelope.detail.md b/website/content/_snippets/examples/output/envelope.detail.md index 0c0c5abff..b0bd2d3b0 100644 --- a/website/content/_snippets/examples/output/envelope.detail.md +++ b/website/content/_snippets/examples/output/envelope.detail.md @@ -44,7 +44,7 @@ console.log(result) 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' }, diff --git a/website/content/_snippets/examples/output/envelope.md b/website/content/_snippets/examples/output/envelope.md index e02001963..87e25d7c0 100644 --- a/website/content/_snippets/examples/output/envelope.md +++ b/website/content/_snippets/examples/output/envelope.md @@ -42,7 +42,7 @@ console.log(result) 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' }, diff --git a/website/content/_snippets/examples/transport-http/dynamic-headers.detail.md b/website/content/_snippets/examples/transport-http/dynamic-headers.detail.md index 66fddbe87..43cad9f92 100644 --- a/website/content/_snippets/examples/transport-http/dynamic-headers.detail.md +++ b/website/content/_snippets/examples/transport-http/dynamic-headers.detail.md @@ -46,7 +46,7 @@ await graffle.gql`{ pokemons { name } }`.send() 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', diff --git a/website/content/_snippets/examples/transport-http/dynamic-headers.md b/website/content/_snippets/examples/transport-http/dynamic-headers.md index ec08765e5..b5b5e4190 100644 --- a/website/content/_snippets/examples/transport-http/dynamic-headers.md +++ b/website/content/_snippets/examples/transport-http/dynamic-headers.md @@ -44,7 +44,7 @@ await graffle.gql`{ pokemons { name } }`.send() 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', diff --git a/website/content/examples/10_transport-http/dynamic-headers.md b/website/content/examples/10_transport-http/dynamic-headers.md index abd5b565d..0d302c756 100644 --- a/website/content/examples/10_transport-http/dynamic-headers.md +++ b/website/content/examples/10_transport-http/dynamic-headers.md @@ -51,7 +51,7 @@ await graffle.gql`{ pokemons { name } }`.send() 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', diff --git a/website/content/examples/20_output/envelope.md b/website/content/examples/20_output/envelope.md index 6acdc9e6f..bd90f759b 100644 --- a/website/content/examples/20_output/envelope.md +++ b/website/content/examples/20_output/envelope.md @@ -49,7 +49,7 @@ console.log(result) 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' }, diff --git a/website/content/examples/40_other/transport-memory.md b/website/content/examples/40_other/transport-memory.md index ccd7e4377..34c6fbab5 100644 --- a/website/content/examples/40_other/transport-memory.md +++ b/website/content/examples/40_other/transport-memory.md @@ -6,6 +6,16 @@ aside: false 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. + ```ts twoslash // Our website uses Vitepress+Twoslash. Twoslash does not discover the generated Graffle modules. @@ -18,30 +28,129 @@ import { Graffle } from 'graffle' import { TransportMemory } from 'graffle/extensions/transport-memory' import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql' +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() -console.log(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() + + console.log(data) +} + +// Server receives a request... +await handleRequest( + new Request(`https://foo.com`, { + headers: new Headers({ authorization: `Bearer user_abc123` }), + }), +) ``` @@ -50,7 +159,7 @@ console.log(data) ```json { - "foo": "bar" + "account": "account_abc123" } ``` diff --git a/website/content/examples/60_extension/opentelemetry.md b/website/content/examples/60_extension/opentelemetry.md index f4aa1de35..bf921ec4f 100644 --- a/website/content/examples/60_extension/opentelemetry.md +++ b/website/content/examples/60_extension/opentelemetry.md @@ -42,18 +42,18 @@ console.log(data) '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: [], @@ -69,18 +69,18 @@ console.log(data) '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: [], @@ -96,18 +96,18 @@ console.log(data) '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: [], @@ -123,18 +123,18 @@ console.log(data) '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: [], @@ -150,18 +150,18 @@ console.log(data) '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: [], @@ -177,18 +177,18 @@ console.log(data) '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: [],