Skip to content

Commit

Permalink
GET request deduplication, pass 'context' as 3rd param in JSON Schema…
Browse files Browse the repository at this point in the history
… handler and expose HTTP details via extensions (#4356)

* GET request deduplication and pass 'context' as 3rd param in JSON Schema handler

* Improvements

* Go

* Go

* chore(dependencies): updated changesets for modified dependencies

* go go go

* Cleanup

* chore(dependencies): updated changesets for modified dependencies

* Add more tests

* chore(dependencies): updated changesets for modified dependencies

* Go

* chore(dependencies): updated changesets for modified dependencies

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
ardatan and github-actions[bot] authored Aug 24, 2022
1 parent 0b96c33 commit b5c59ff
Show file tree
Hide file tree
Showing 35 changed files with 590 additions and 101 deletions.
8 changes: 8 additions & 0 deletions .changeset/@graphql-mesh_config-4356-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@graphql-mesh/config": patch
---

dependencies updates:

- Removed dependency [`@whatwg-node/fetch@^0.3.0` ↗︎](https://www.npmjs.com/package/@whatwg-node/fetch/v/null) (from `dependencies`)
- Removed dependency [`[email protected]` ↗︎](https://www.npmjs.com/package/fetchache/v/0.1.2) (from `dependencies`)
7 changes: 7 additions & 0 deletions .changeset/@graphql-mesh_runtime-4356-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@graphql-mesh/runtime": patch
---

dependencies updates:

- Added dependency [`@graphql-mesh/[email protected]` ↗︎](https://www.npmjs.com/package/@graphql-mesh/cross-helpers/v/0.2.2) (to `dependencies`)
8 changes: 8 additions & 0 deletions .changeset/@graphql-mesh_utils-4356-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@graphql-mesh/utils": patch
---

dependencies updates:

- Added dependency [`[email protected]` ↗︎](https://www.npmjs.com/package/fetchache/v/0.1.2) (to `dependencies`)
- Added dependency [`@whatwg-node/[email protected]` ↗︎](https://www.npmjs.com/package/@whatwg-node/fetch/v/0.3.2) (to `dependencies`)
24 changes: 24 additions & 0 deletions .changeset/odd-cheetahs-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"@graphql-mesh/config": minor
"@graphql-mesh/graphql": minor
"@graphql-mesh/json-schema": minor
"@graphql-mesh/neo4j": minor
"@graphql-mesh/odata": minor
"@graphql-mesh/openapi": minor
"@graphql-mesh/raml": minor
"@graphql-mesh/soap": minor
"@graphql-mesh/thrift": minor
"@omnigraph/json-schema": minor
"@graphql-mesh/utils": minor
---

## Improvements on outgoing HTTP calls

- Now Mesh's default fetch implementation deduplicates the same GET JSON requests in the same execution context
- You should pass `Accept: application/json` to make this work.
- JSON Schema, new OpenAPI and RAML handlers now take GraphQL context as 3rd parameter. If you use `customFetch`, you can use that value to access Mesh internals such as the incoming `Request` object.

## HTTP Details in extensions for tracking HTTP calls

You can add `includeHttpDetailsInExtensions: true` to your configuration file to get more information about HTTP calls done by Mesh during the execution in `extensions` field of the response.

2 changes: 0 additions & 2 deletions packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@
"@graphql-tools/load": "7.7.4",
"@graphql-tools/code-file-loader": "7.3.3",
"@graphql-tools/graphql-file-loader": "7.5.2",
"@whatwg-node/fetch": "^0.3.0",
"fetchache": "0.1.2",
"param-case": "3.0.4",
"@envelop/core": "2.5.0",
"tslib": "^2.4.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/config/src/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export type ProcessedConfig = {
store: MeshStore;
code: string;
additionalEnvelopPlugins: EnvelopPlugins;
includeHttpDetailsInExtensions: boolean;
};

function getDefaultMeshStore(dir: string, importFn: ImportFn, artifactsDir: string) {
Expand Down Expand Up @@ -608,6 +609,7 @@ export async function processConfig(
logger,
store: rootStore,
additionalEnvelopPlugins,
includeHttpDetailsInExtensions: config.includeHttpDetailsInExtensions,
code: [...new Set([...importCodes, ...codes])].join('\n'),
};
}
20 changes: 6 additions & 14 deletions packages/config/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import { printSchemaWithDirectives, Source } from '@graphql-tools/utils';
import { paramCase } from 'param-case';
import { loadDocuments, loadTypedefs } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { PubSub, DefaultLogger, parseWithCache } from '@graphql-mesh/utils';
import { PubSub, DefaultLogger, parseWithCache, createDefaultMeshFetch, MeshFetch } from '@graphql-mesh/utils';
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
import { MeshStore } from '@graphql-mesh/store';
import { fetch, Request, Response } from '@whatwg-node/fetch';
import { fetchFactory } from 'fetchache';

type ResolvedPackage<T> = {
moduleName: string;
Expand Down Expand Up @@ -98,26 +96,20 @@ export async function resolveCustomFetch({
additionalPackagePrefixes: string[];
cache: KeyValueCache;
}): Promise<{
fetchFn: ReturnType<typeof fetchFactory>;
fetchFn: MeshFetch;
importCode: string;
code: string;
}> {
let importCode = '';
if (!fetchConfig) {
importCode += `import { fetchFactory } from 'fetchache';\n`;
importCode += `import { fetch, Request, Response } from '@whatwg-node/fetch';\n`;
importCode += `import { createDefaultMeshFetch } from '@graphql-mesh/utils';\n`;
return {
fetchFn: fetchFactory({
cache,
fetch,
Request,
Response,
}),
fetchFn: createDefaultMeshFetch(cache),
importCode,
code: `const fetchFn = fetchFactory({ cache, fetch, Request, Response });`,
code: `const fetchFn = createDefaultMeshFetch(cache);`,
};
}
const { moduleName, resolved: fetchFn } = await getPackage<ReturnType<typeof fetchFactory>>({
const { moduleName, resolved: fetchFn } = await getPackage<MeshFetch>({
name: fetchConfig,
type: 'fetch',
importFn,
Expand Down
4 changes: 4 additions & 0 deletions packages/config/yaml-config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ type Query {
"""
customFetch: Any
"""
Include HTTP details to the extensions
"""
includeHttpDetailsInExtensions: Boolean
"""
Allow connections to an SSL endpoint without certificates
"""
skipSSLValidation: Boolean
Expand Down
22 changes: 18 additions & 4 deletions packages/handlers/graphql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ import {
buildClientSchema,
ExecutionResult,
SelectionNode,
GraphQLResolveInfo,
} from 'graphql';
import { introspectSchema } from '@graphql-tools/wrap';
import { loadFromModuleExportExpression, readFileOrUrl } from '@graphql-mesh/utils';
import { loadFromModuleExportExpression, MeshFetch, readFileOrUrl } from '@graphql-mesh/utils';
import {
ExecutionRequest,
isDocumentNode,
memoize1,
getOperationASTFromRequest,
parseSelectionSet,
isAsyncIterable,
Executor,
} from '@graphql-tools/utils';
import { PredefinedProxyOptions, StoreProxy } from '@graphql-mesh/store';
import lodashGet from 'lodash.get';
Expand All @@ -40,22 +42,25 @@ const getResolverData = memoize1(function getResolverData(params: ExecutionReque
});

export default class GraphQLHandler implements MeshHandler {
private name: string;
private config: YamlConfig.Handler['graphql'];
private baseDir: string;
private nonExecutableSchema: StoreProxy<GraphQLSchema>;
private importFn: ImportFn;
private fetchFn: typeof fetch;
private fetchFn: MeshFetch;
private logger: Logger;
private urlLoader = new UrlLoader();

constructor({
name,
config,
baseDir,
fetchFn,
store,
importFn,
logger,
}: GetMeshSourceOptions<YamlConfig.Handler['graphql']>) {
this.name = name;
this.config = config;
this.baseDir = baseDir;
this.fetchFn = fetchFn;
Expand All @@ -70,6 +75,15 @@ export default class GraphQLHandler implements MeshHandler {
return parseInterpolationStrings(this.interpolationStringSet);
}

private wrapExecutorToPassSourceName(executor: Executor) {
const sourceName = this.name;
return function executorWithSourceName(executionRequest: ExecutionRequest) {
executionRequest.info = executionRequest.info || ({} as GraphQLResolveInfo);
(executionRequest.info as any).sourceName = sourceName;
return executor(executionRequest);
};
}

async getExecutorForHTTPSourceConfig(
httpSourceConfig: YamlConfig.GraphQLHandlerHTTPConfiguration
): Promise<MeshSource['executor']> {
Expand Down Expand Up @@ -309,7 +323,7 @@ export default class GraphQLHandler implements MeshHandler {

return {
schema,
executor: highestValueExecutor,
executor: this.wrapExecutorToPassSourceName(highestValueExecutor),
// Batching doesn't make sense with fallback strategy
batch: false,
contextVariables,
Expand Down Expand Up @@ -364,7 +378,7 @@ export default class GraphQLHandler implements MeshHandler {

return {
schema: schemaResult.value,
executor: executorResult.value,
executor: this.wrapExecutorToPassSourceName(executorResult.value),
batch: this.config.batch != null ? this.config.batch : true,
contextVariables,
};
Expand Down
4 changes: 2 additions & 2 deletions packages/handlers/json-schema/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PredefinedProxyOptions, StoreProxy } from '@graphql-mesh/store';
import { GetMeshSourceOptions, ImportFn, Logger, MeshHandler, MeshPubSub, YamlConfig } from '@graphql-mesh/types';
import { JSONSchemaLoaderBundle, createBundle, getGraphQLSchemaFromBundle } from '@omnigraph/json-schema';
import { loadFromModuleExportExpression, readFileOrUrl } from '@graphql-mesh/utils';
import { loadFromModuleExportExpression, MeshFetch, readFileOrUrl } from '@graphql-mesh/utils';
import { getInterpolatedHeadersFactory } from '@graphql-mesh/string-interpolation';
import { process } from '@graphql-mesh/cross-helpers';

Expand All @@ -11,7 +11,7 @@ export default class JsonSchemaHandler implements MeshHandler {
private bundleStoreProxy: StoreProxy<JSONSchemaLoaderBundle>;
private baseDir: string;
private logger: Logger;
private fetchFn: typeof fetch;
private fetchFn: MeshFetch;
private importFn: ImportFn;
private pubsub: MeshPubSub;

Expand Down
4 changes: 2 additions & 2 deletions packages/handlers/neo4j/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Neo4jGraphQL } from '@neo4j/graphql';
import neo4j, { Driver } from 'neo4j-driver';
import { YamlConfig, MeshHandler, GetMeshSourceOptions, MeshPubSub, Logger, ImportFn } from '@graphql-mesh/types';
import { PredefinedProxyOptions, StoreProxy } from '@graphql-mesh/store';
import { readFileOrUrl } from '@graphql-mesh/utils';
import { MeshFetch, readFileOrUrl } from '@graphql-mesh/utils';
import { process } from '@graphql-mesh/cross-helpers';

function getEventEmitterFromPubSub(pubsub: MeshPubSub): any {
Expand Down Expand Up @@ -36,7 +36,7 @@ export default class Neo4JHandler implements MeshHandler {
private pubsub: MeshPubSub;
private typeDefs: StoreProxy<string>;
private logger: Logger;
fetchFn: typeof fetch;
fetchFn: MeshFetch;
importFn: ImportFn;

constructor({
Expand Down
16 changes: 13 additions & 3 deletions packages/handlers/odata/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { YamlConfig, MeshHandler, GetMeshSourceOptions, MeshSource, Logger, ImportFn } from '@graphql-mesh/types';
import { readFileOrUrl } from '@graphql-mesh/utils';
import { MeshFetch, readFileOrUrl } from '@graphql-mesh/utils';
import {
getInterpolatedHeadersFactory,
parseInterpolationStrings,
Expand Down Expand Up @@ -115,7 +115,7 @@ const queryOptionsFields = {
export default class ODataHandler implements MeshHandler {
private name: string;
private config: YamlConfig.ODataHandler;
private fetchFn: typeof fetch;
private fetchFn: MeshFetch;
private logger: Logger;
private importFn: ImportFn;
private baseDir: string;
Expand Down Expand Up @@ -555,8 +555,18 @@ export default class ODataHandler implements MeshHandler {
return handleBatchJsonResults(batchResponseJson, requests);
}),
none: () =>
// We should refactor here
new DataLoader(
(requests: Request[]): Promise<Response[]> => Promise.all(requests.map(request => this.fetchFn(request)))
(requests: Request[]): Promise<Response[]> =>
Promise.all(
requests.map(async request =>
this.fetchFn(request.url, {
method: request.method,
body: request.body && (await request.text()),
headers: request.headers,
})
)
)
),
};

Expand Down
12 changes: 8 additions & 4 deletions packages/handlers/odata/test/custom-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ export const MockRequest = function (url: string, config: RequestInit) {
return {
url,
...config,
text: async () => config.body,
text: async () => config.body.toString('utf-8'),
json: async () => JSON.parse(config.body.toString('utf-8')),
clone() {
return this;
},
} as Request;
} as any as typeof Request;
export const MockResponse = function (body: string) {
export const MockResponse = function (body: any) {
return {
text: async () => body,
json: async () => JSON.parse(body),
text: async () => body.toString('utf-8'),
json: async () => JSON.parse(body.toString('utf-8')),
} as Response;
} as any as typeof Response;

Expand Down
Loading

1 comment on commit b5c59ff

@vercel
Copy link

@vercel vercel bot commented on b5c59ff Aug 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.