Skip to content

Commit

Permalink
enhance(@graphql-hive/yoga): improvements with Yoga's features
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Jan 2, 2025
1 parent b1be810 commit 73596cc
Show file tree
Hide file tree
Showing 14 changed files with 418 additions and 1,205 deletions.
17 changes: 17 additions & 0 deletions .changeset/spotty-toes-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@graphql-hive/yoga': patch
---

Align with GraphQL Yoga >=5.10.4 which has the relevant features mentioned below;

- Remove \`tiny-lru\` dependency, and use `createLruCache` from `graphql-yoga`
- - Yoga already provides a LRU cache implementation, so we can use that instead of having a separate dependency
- Use Explicit Resource Management of GraphQL Yoga plugins for disposal which already respect Node.js process termination
- - The feature has been implemented in `@whatwg-node/server` which is used by GraphQL Yoga. [Learn more about this feature](https://github.com/ardatan/whatwg-node/pull/1830)
- Use \`context.waitUntil\` which is handled by the environment automatically, if not `@whatwg-node/server` already takes care of it with above.
- - The feature has been implemented in `@whatwg-node/server` which is used by GraphQL Yoga. [Learn more about this feature](
https://github.com/ardatan/whatwg-node/pull/1830)
- Respect the given `fetchAPI` by GraphQL Yoga(might be Hive Gateway) for the `fetch` function
- - Hive Gateway uses `fetchAPI` given to GraphQL Yoga for the entire Fetch API implementation, so if Hive Client respects that, we have a control over HTTP calls done internally by the gateway
- Respect Yoga's \`logger\` for logging
- - Similar to above, we want to respect the logger provided by Yoga to have a better control over logging
2 changes: 1 addition & 1 deletion packages/libraries/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"dependencies": {
"@graphql-tools/utils": "^10.0.0",
"@whatwg-node/fetch": "0.9.22",
"@whatwg-node/fetch": "0.10.1",
"async-retry": "1.3.3",
"lodash.sortby": "4.7.0",
"tiny-lru": "8.0.2"
Expand Down
4 changes: 2 additions & 2 deletions packages/libraries/core/src/client/persisted-documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function createPersistedDocuments(
logger: Logger;
},
): null | {
resolve(documentId: string): Promise<string | null>;
resolve(documentId: string): PromiseOrValue<string | null>;
allowArbitraryDocuments(context: { headers?: HeadersObject }): PromiseOrValue<boolean>;
} {
const persistedDocumentsCache = LRU<string>(config.cache ?? 10_000);
Expand All @@ -32,7 +32,7 @@ export function createPersistedDocuments(
const fetchCache = new Map<string, Promise<string | null>>();

/** Batch load a persisted documents */
async function loadPersistedDocument(documentId: string) {
function loadPersistedDocument(documentId: string) {
const document = persistedDocumentsCache.get(documentId);
if (document) {
return document;
Expand Down
2 changes: 1 addition & 1 deletion packages/libraries/core/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface HiveClient {
createInstrumentedSubscribe(executeImpl: any): any;
dispose(): Promise<void>;
experimental__persistedDocuments: null | {
resolve(documentId: string): Promise<string | null>;
resolve(documentId: string): PromiseOrValue<string | null>;
allowArbitraryDocuments(context: { headers?: HeadersObject }): PromiseOrValue<boolean>;
};
}
Expand Down
9 changes: 4 additions & 5 deletions packages/libraries/yoga/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,21 @@
},
"peerDependencies": {
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
"graphql-yoga": "^5.9.0"
"graphql-yoga": "^5.10.8"
},
"dependencies": {
"@graphql-hive/core": "workspace:*",
"@graphql-yoga/plugin-persisted-operations": "^3.9.0",
"tiny-lru": "8.0.2"
"@graphql-yoga/plugin-persisted-operations": "^3.9.0"
},
"devDependencies": {
"@graphql-tools/schema": "10.0.8",
"@graphql-yoga/plugin-defer-stream": "3.7.0",
"@graphql-yoga/plugin-disable-introspection": "2.7.0",
"@graphql-yoga/plugin-graphql-sse": "3.7.0",
"@graphql-yoga/plugin-response-cache": "3.9.0",
"@whatwg-node/fetch": "0.9.22",
"@whatwg-node/fetch": "0.10.1",
"graphql-ws": "5.16.0",
"graphql-yoga": "5.9.0",
"graphql-yoga": "5.10.8",
"nock": "14.0.0-beta.19",
"vitest": "2.0.5",
"ws": "8.18.0"
Expand Down
126 changes: 68 additions & 58 deletions packages/libraries/yoga/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { DocumentNode, ExecutionArgs, GraphQLError, GraphQLSchema, Kind, parse } from 'graphql';
import type { GraphQLParams, Plugin } from 'graphql-yoga';
import LRU from 'tiny-lru';
import { _createLRUCache, YogaServer, type GraphQLParams, type Plugin } from 'graphql-yoga';
import {
autoDisposeSymbol,
CollectUsageCallback,
Expand Down Expand Up @@ -42,31 +41,16 @@ export function createHive(clientOrOptions: HivePluginOptions) {
export function useHive(clientOrOptions: HiveClient): Plugin;
export function useHive(clientOrOptions: HivePluginOptions): Plugin;
export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin {
const hive = isHiveClient(clientOrOptions) ? clientOrOptions : createHive(clientOrOptions);

void hive.info();

if (hive[autoDisposeSymbol]) {
if (global.process) {
const signals = Array.isArray(hive[autoDisposeSymbol])
? hive[autoDisposeSymbol]
: ['SIGINT', 'SIGTERM'];
for (const signal of signals) {
process.once(signal, () => hive.dispose());
}
} else {
console.error(
'It seems that GraphQL Hive is not being executed in Node.js. ' +
'Please attempt manual client disposal and use autoDispose: false option.',
);
}
}

const parsedDocumentCache = LRU<DocumentNode>(10_000);
const parsedDocumentCache = _createLRUCache<DocumentNode>();
let latestSchema: GraphQLSchema | null = null;
const contextualCache = new WeakMap<object, CacheRecord>();

let hive: HiveClient;
let yoga: YogaServer<any, any>;
return {
onYogaInit(payload) {
yoga = payload.yoga;
},
onSchemaChange({ schema }) {
hive.reportSchema({ schema });
latestSchema = schema;
Expand Down Expand Up @@ -103,13 +87,15 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
record.executionArgs = args;

if (!isAsyncIterable(result)) {
void record.callback(
{
...record.executionArgs,
document: record.parsedDocument ?? record.executionArgs.document,
},
result,
record.experimental__documentId,
args.contextValue.waitUntil(
record.callback(
{
...record.executionArgs,
document: record.parsedDocument ?? record.executionArgs.document,
},
result,
record.experimental__documentId,
),
);
return;
}
Expand All @@ -124,10 +110,12 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
errors.push(...ctx.result.errors);
},
onEnd() {
void record.callback(
args,
errors.length ? { errors } : {},
record.experimental__documentId,
args.contextValue.waitUntil(
record.callback(
args,
errors.length ? { errors } : {},
record.experimental__documentId,
),
);
},
};
Expand All @@ -147,46 +135,66 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
},
};
},
onResultProcess(context) {
const record = contextualCache.get(context.serverContext);
onResultProcess({ serverContext, result }) {
const record = contextualCache.get(serverContext);

if (
!record ||
Array.isArray(context.result) ||
isAsyncIterable(context.result) ||
record.executionArgs
) {
if (!record || Array.isArray(result) || isAsyncIterable(result) || record.executionArgs) {
return;
}

// Report if execution was skipped due to response cache ( Symbol.for('servedFromResponseCache') in context.result)
if (
record.paramsArgs.query &&
latestSchema &&
Symbol.for('servedFromResponseCache') in context.result
Symbol.for('servedFromResponseCache') in result
) {
try {
let document = parsedDocumentCache.get(record.paramsArgs.query);
if (document === undefined) {
document = parse(record.paramsArgs.query);
parsedDocumentCache.set(record.paramsArgs.query, document);
}
void record.callback(
{
document,
schema: latestSchema,
variableValues: record.paramsArgs.variables,
operationName: record.paramsArgs.operationName,
},
context.result,
record.experimental__documentId,
serverContext.waitUntil(
record.callback(
{
document,
schema: latestSchema,
variableValues: record.paramsArgs.variables,
operationName: record.paramsArgs.operationName,
},
result,
record.experimental__documentId,
),
);
} catch (err) {
console.error(err);
yoga.logger.error(err);
}
}
},
onPluginInit({ addPlugin }) {
hive = isHiveClient(clientOrOptions)
? clientOrOptions
: createHive({
...clientOrOptions,
agent: clientOrOptions.agent
? {
logger: {
// Hive Plugin should respect the given Yoga logger
error: (...args) => yoga.logger.error(...args),
info: (...args) => yoga.logger.info(...args),
},
...clientOrOptions.agent,
__testing: {
// Hive Plugin should respect the given FetchAPI, note that this is not `yoga.fetch`
fetch(...args) {
return yoga.fetchAPI.fetch(...args);
},
...clientOrOptions.agent.__testing,
},
}
: undefined,
});
void hive.info();
const { experimental__persistedDocuments } = hive;
if (!experimental__persistedDocuments) {
return;
Expand All @@ -206,7 +214,7 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin

return null;
},
async getPersistedOperation(key, request, context) {
async getPersistedOperation(key, _request, context) {
const document = await experimental__persistedDocuments.resolve(key);
// after we resolve the document we need to update the cache record to contain the resolved document
if (document) {
Expand All @@ -219,13 +227,10 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
};
}
}

return document;
},
allowArbitraryOperations(request) {
return experimental__persistedDocuments.allowArbitraryDocuments({
headers: request.headers,
});
return experimental__persistedDocuments.allowArbitraryDocuments(request);
},
customErrors: {
keyNotFound() {
Expand All @@ -247,5 +252,10 @@ export function useHive(clientOrOptions: HiveClient | HivePluginOptions): Plugin
}),
);
},
onDispose() {
if (hive[autoDisposeSymbol]) {
return hive.dispose();
}
},
};
}
2 changes: 1 addition & 1 deletion packages/migrations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@types/bcryptjs": "2.4.6",
"@types/node": "22.10.2",
"@types/pg": "8.11.10",
"@whatwg-node/fetch": "0.9.22",
"@whatwg-node/fetch": "0.10.1",
"bcryptjs": "2.4.3",
"copyfiles": "2.4.1",
"dotenv": "16.4.7",
Expand Down
2 changes: 1 addition & 1 deletion packages/services/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"graphql-modules": "2.4.0",
"graphql-parse-resolve-info": "4.13.0",
"graphql-scalars": "1.24.0",
"graphql-yoga": "5.9.0",
"graphql-yoga": "5.10.8",
"ioredis": "5.4.2",
"ioredis-mock": "8.9.0",
"lodash": "4.17.21",
Expand Down
2 changes: 1 addition & 1 deletion packages/services/broker-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"devDependencies": {
"@cloudflare/workers-types": "4.20241224.0",
"@types/service-worker-mock": "2.0.4",
"@whatwg-node/server": "0.9.50",
"@whatwg-node/server": "0.9.65",
"esbuild": "0.24.2",
"itty-router": "4.2.2",
"toucan-js": "3.4.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/services/cdn-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"devDependencies": {
"@cloudflare/workers-types": "4.20241224.0",
"@types/service-worker-mock": "2.0.4",
"@whatwg-node/server": "0.9.50",
"@whatwg-node/server": "0.9.65",
"bcryptjs": "2.4.3",
"dotenv": "16.4.7",
"esbuild": "0.24.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/services/demo/federation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"dependencies": {
"@apollo/subgraph": "2.9.3",
"graphql": "16.9.0",
"graphql-yoga": "5.9.0"
"graphql-yoga": "5.10.8"
},
"devDependencies": {
"wrangler": "3.99.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@apollo/composition": "2.9.3",
"@apollo/federation-internals": "2.9.3",
"@graphql-hive/external-composition": "workspace:*",
"@whatwg-node/server": "0.9.50",
"@whatwg-node/server": "0.9.65",
"dotenv": "16.4.7",
"graphql": "16.9.0",
"lru-cache": "^7.17.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/services/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@graphql-hive/yoga": "workspace:*",
"@graphql-yoga/plugin-persisted-operations": "3.9.0",
"@graphql-yoga/plugin-response-cache": "3.9.0",
"@graphql-yoga/redis-event-target": "3.0.1",
"@graphql-yoga/redis-event-target": "3.0.2",
"@hive/api": "workspace:*",
"@hive/cdn-script": "workspace:*",
"@hive/service-common": "workspace:*",
Expand All @@ -34,12 +34,12 @@
"@swc/core": "1.10.1",
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"@whatwg-node/server": "0.9.50",
"@whatwg-node/server": "0.9.65",
"dotenv": "16.4.7",
"fastify": "4.29.0",
"got": "14.4.5",
"graphql": "16.9.0",
"graphql-yoga": "5.9.0",
"graphql-yoga": "5.10.8",
"hyperid": "3.3.0",
"ioredis": "5.4.2",
"pino-pretty": "11.3.0",
Expand Down
Loading

0 comments on commit 73596cc

Please sign in to comment.