Skip to content

Commit

Permalink
Small improvements (#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan authored Dec 5, 2024
1 parent c77884b commit 3f1a0fa
Show file tree
Hide file tree
Showing 14 changed files with 176 additions and 101 deletions.
5 changes: 5 additions & 0 deletions .changeset/old-horses-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/fusion-runtime': patch
---

Improve \`import\` and \`require\` interop
5 changes: 5 additions & 0 deletions .changeset/ten-cougars-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-tools/executor-http': patch
---

Improve multipart support
23 changes: 12 additions & 11 deletions bench/federation/federation.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ApolloGateway, LocalGraphQLDataSource } from '@apollo/gateway';
import { createDefaultExecutor } from '@graphql-tools/delegate';
import { normalizedExecutor } from '@graphql-tools/executor';
import { getStitchedSchemaFromSupergraphSdl } from '@graphql-tools/federation';
import { mapMaybePromise } from '@graphql-tools/utils';
import { fakePromise, mapMaybePromise } from '@graphql-tools/utils';
import {
accounts,
createExampleSetup,
Expand All @@ -13,7 +13,7 @@ import {
} from '@internal/e2e';
import { benchConfig } from '@internal/testing';
import { getOperationAST, GraphQLSchema, parse } from 'graphql';
import { bench, describe, expect } from 'vitest';
import { bench, describe, expect, vi } from 'vitest';
import monolith from './monolith';

function memoize1<T extends (...args: any) => any>(fn: T): T {
Expand Down Expand Up @@ -56,16 +56,17 @@ describe('Federation', async () => {

const supergraphPath = await supergraph();
const supergraphSdl = await fs.read(supergraphPath);
const dummyLogger = {
debug: () => {},
info: () => {},
warn: () => {},
error: () => {},
type ApolloGWExecutorOpts = Parameters<ApolloGateway['executor']>[0];
const dummyLogger: ApolloGWExecutorOpts['logger'] = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const dummyCache = {
get: async () => undefined,
set: async () => {},
delete: async () => true,
const dummyCache: ApolloGWExecutorOpts['cache'] = {
get: () => fakePromise(undefined),
set: () => fakePromise(undefined),
delete: () => fakePromise(true),
};
const parsedQuery = parse(query, { noLocation: true });
const operationAST = getOperationAST(parsedQuery, operationName);
Expand Down
28 changes: 17 additions & 11 deletions internal/e2e/src/tenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
type ServiceEndpointDefinition,
} from '@apollo/gateway';
import { createDeferred } from '@graphql-tools/delegate';
import { fakePromise } from '@graphql-tools/utils';
import {
boolEnv,
createOpt,
Expand Down Expand Up @@ -869,7 +870,7 @@ export function createTenv(cwd: string): Tenv {
return new RemoteGraphQLDataSource(opts);
},
update() {},
async healthCheck() {},
healthCheck: () => fakePromise(undefined),
});

const supergraphFile = await tenv.fs.tempfile('supergraph.graphql');
Expand Down Expand Up @@ -905,6 +906,7 @@ function spawn(

const exitDeferred = createDeferred<void>();
const waitForExit = exitDeferred.promise;
let exited = false;
let stdout = '';
let stderr = '';
let stdboth = '';
Expand Down Expand Up @@ -937,7 +939,7 @@ function spawn(
},
[DisposableSymbols.asyncDispose]: () => {
const childPid = child.pid;
if (childPid && child.exitCode == null) {
if (childPid && !exited) {
return terminate(childPid);
}
return waitForExit;
Expand Down Expand Up @@ -965,6 +967,7 @@ function spawn(
child.stderr.destroy();
});
child.once('close', (code) => {
exited = true;
// process ended _and_ the stdio streams have been closed
if (code) {
exitDeferred.reject(
Expand All @@ -977,6 +980,7 @@ function spawn(

return new Promise((resolve, reject) => {
child.once('error', (err) => {
exited = true;
exitDeferred.reject(err); // reject waitForExit promise
reject(err);
});
Expand All @@ -985,22 +989,24 @@ function spawn(
}

export function getAvailablePort(): Promise<number> {
const deferred = createDeferred<number>();
const server = createServer();
return new Promise((resolve, reject) => {
server.once('error', (err) => deferred.reject(err));
server.listen(0, () => {
try {
server.listen(0, () => {
try {
const addressInfo = server.address() as AddressInfo;
resolve(addressInfo.port);
server.close();
} catch (err) {
reject(err);
const addressInfo = server.address() as AddressInfo;
server.close((err) => {
if (err) {
return deferred.reject(err);
}

return deferred.resolve(addressInfo.port);
});
} catch (err) {
reject(err);
return deferred.reject(err);
}
});
return deferred.promise;
}

async function waitForPort(port: number, signal: AbortSignal) {
Expand Down
88 changes: 60 additions & 28 deletions packages/executors/http/src/createFormDataFromVariables.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
import { fakePromise, isAsyncIterable, isPromise } from '@graphql-tools/utils';
import {
isAsyncIterable,
isPromise,
mapMaybePromise,
MaybePromise,
} from '@graphql-tools/utils';
import {
File as DefaultFile,
FormData as DefaultFormData,
} from '@whatwg-node/fetch';
import { extractFiles, isExtractableFile } from 'extract-files';
import { isGraphQLUpload } from './isGraphQLUpload.js';

function collectAsyncIterableValues<T>(
asyncIterable: AsyncIterable<T>,
): MaybePromise<T[]> {
const values: T[] = [];
const iterator = asyncIterable[Symbol.asyncIterator]();
function iterate(): MaybePromise<T[]> {
return mapMaybePromise(iterator.next(), ({ value, done }) => {
if (value != null) {
values.push(value);
}
if (done) {
return values;
}
return iterate();
});
}
return iterate();
}

export function createFormDataFromVariables<TVariables>(
{
query,
Expand Down Expand Up @@ -71,34 +95,42 @@ export function createFormDataFromVariables<TVariables>(
function handleUpload(upload: any, i: number): void | PromiseLike<void> {
const indexStr = i.toString();
if (upload != null) {
const filename =
upload.filename || upload.name || upload.path || `blob-${indexStr}`;
if (isPromise(upload)) {
return upload.then((resolvedUpload: any) =>
handleUpload(resolvedUpload, i),
);
// If Blob
} else if (isBlob(upload)) {
form.append(indexStr, upload, filename);
} else if (isGraphQLUpload(upload)) {
const stream = upload.createReadStream();
const chunks: number[] = [];
return fakePromise<void>(undefined).then(async () => {
for await (const chunk of stream) {
if (chunk) {
chunks.push(...chunk);
}
return mapMaybePromise(
upload?.promise || upload,
(upload): MaybePromise<void> => {
const filename =
upload.filename || upload.name || upload.path || `blob-${indexStr}`;
if (isBlob(upload)) {
form.append(indexStr, upload, filename);
} else if (isAsyncIterable(upload)) {
return mapMaybePromise(
collectAsyncIterableValues<any>(upload),
(chunks) => {
const blobPart = new Uint8Array(chunks);
form.append(
indexStr,
new FileCtor([blobPart], filename),
filename,
);
},
);
} else if (isGraphQLUpload(upload)) {
return mapMaybePromise(
collectAsyncIterableValues(upload.createReadStream()),
(chunks) => {
const blobPart = new Uint8Array(chunks);
form.append(
indexStr,
new FileCtor([blobPart], filename, { type: upload.mimetype }),
filename,
);
},
);
} else {
form.append(indexStr, new FileCtor([upload], filename), filename);
}
const blobPart = new Uint8Array(chunks);
form.append(
indexStr,
new FileCtor([blobPart], filename, { type: upload.mimetype }),
filename,
);
});
} else {
form.append(indexStr, new FileCtor([upload], filename), filename);
}
},
);
}
}
const jobs: PromiseLike<void>[] = [];
Expand Down
16 changes: 6 additions & 10 deletions packages/executors/http/src/defaultPrintFn.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { memoize1 } from '@graphql-tools/utils';
import { DocumentNode, print, stripIgnoredCharacters } from 'graphql';

const printCache = new WeakMap<DocumentNode, string>();

export function defaultPrintFn(document: DocumentNode) {
let printed = printCache.get(document);
if (!printed) {
printed = stripIgnoredCharacters(print(document));
printCache.set(document, printed);
}
return printed;
}
export const defaultPrintFn = memoize1(function defaultPrintFn(
document: DocumentNode,
) {
return stripIgnoredCharacters(print(document));
});
5 changes: 3 additions & 2 deletions packages/executors/http/tests/buildHTTPExecutor.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { setTimeout } from 'timers/promises';
import {
createGraphQLError,
ExecutionResult,
Expand Down Expand Up @@ -105,7 +106,7 @@ describe('buildHTTPExecutor', () => {
controller.enqueue(
`data: ${JSON.stringify({ data: { hello: 'world' } })}\n\n`,
);
await new Promise((resolve) => setTimeout(resolve, 100));
await setTimeout(100);
controller.close();
},
}),
Expand Down Expand Up @@ -145,7 +146,7 @@ describe('buildHTTPExecutor', () => {
controller.enqueue(
`data: ${JSON.stringify({ data: { hello: 'world' } })}\n\n`,
);
await new Promise((resolve) => setTimeout(resolve, 100));
await setTimeout(100);
controller.close();
},
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { setTimeout } from 'timers/promises';
import { ReadableStream, Response, TextEncoder } from '@whatwg-node/fetch';
import { describe, expect, it } from 'vitest';
import { handleEventStreamResponse } from '../src/handleEventStreamResponse.js';
Expand Down Expand Up @@ -97,7 +98,7 @@ describe('handleEventStreamResponse', () => {
async pull(controller) {
const chunk = chunks[currChunk++];
if (chunk) {
await new Promise((resolve) => setTimeout(resolve, 0)); // stream chunk after one tick
await setTimeout(0); // stream chunk after one tick
controller.enqueue(encoder.encode(chunk));
} else {
controller.close();
Expand Down
16 changes: 11 additions & 5 deletions packages/federation/tests/defer-stream.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { setTimeout } from 'timers/promises';
import { inspect } from 'util';
import { IntrospectAndCompose, LocalGraphQLDataSource } from '@apollo/gateway';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { normalizedExecutor } from '@graphql-tools/executor';
import { buildHTTPExecutor } from '@graphql-tools/executor-http';
import { asArray, ExecutionResult, mergeDeep } from '@graphql-tools/utils';
import {
asArray,
ExecutionResult,
fakePromise,
mergeDeep,
} from '@graphql-tools/utils';
import { useDeferStream } from '@graphql-yoga/plugin-defer-stream';
import { assertAsyncIterable } from '@internal/testing';
import { GraphQLSchema, parse, print } from 'graphql';
Expand Down Expand Up @@ -73,7 +79,7 @@ describe('Defer/Stream', () => {
{ id: '2', title: 'My Story', authorId: '2' },
];
function resolveWithDelay<T>(value: () => T, delay: number): Promise<T> {
return new Promise((resolve) => setTimeout(() => resolve(value()), delay));
return setTimeout(delay).then(() => value());
}
const usersSubgraph = buildSubgraphSchema({
typeDefs: parse(/* GraphQL */ `
Expand All @@ -100,7 +106,7 @@ describe('Defer/Stream', () => {
usersStream: async function* usersStream() {
for (const user of users) {
yield user;
await new Promise((resolve) => setTimeout(resolve, 500));
await setTimeout(500);
}
},
},
Expand Down Expand Up @@ -154,7 +160,7 @@ describe('Defer/Stream', () => {
postsStream: async function* postsStream() {
for (const post of posts) {
yield post;
await new Promise((resolve) => setTimeout(resolve, 500));
await setTimeout(500);
}
},
},
Expand All @@ -174,7 +180,7 @@ describe('Defer/Stream', () => {
],
}).initialize({
update() {},
async healthCheck() {},
healthCheck: () => fakePromise(undefined),
getDataSource({ name }) {
if (name === 'users') return new LocalGraphQLDataSource(usersSubgraph);
if (name === 'posts') return new LocalGraphQLDataSource(postsSubgraph);
Expand Down
4 changes: 2 additions & 2 deletions packages/federation/tests/error-handling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IntrospectAndCompose, LocalGraphQLDataSource } from '@apollo/gateway';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { createDefaultExecutor } from '@graphql-tools/delegate';
import { normalizedExecutor } from '@graphql-tools/executor';
import { isAsyncIterable } from '@graphql-tools/utils';
import { fakePromise, isAsyncIterable } from '@graphql-tools/utils';
import { GraphQLSchema, parse } from 'graphql';
import { beforeAll, describe, expect, it } from 'vitest';
import { getStitchedSchemaFromSupergraphSdl } from '../src/supergraph';
Expand Down Expand Up @@ -87,7 +87,7 @@ describe('Error handling', () => {
}
throw new Error(`Unknown subgraph: ${name}`);
},
async healthCheck() {},
healthCheck: () => fakePromise(undefined),
update() {},
});
supergraph = getStitchedSchemaFromSupergraphSdl({
Expand Down
4 changes: 2 additions & 2 deletions packages/federation/tests/fixtures/gateway/supergraph.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IntrospectAndCompose, LocalGraphQLDataSource } from '@apollo/gateway';
import { buildSubgraphSchema as apolloBuildSubgraphSchema } from '@apollo/subgraph';
import { IResolvers } from '@graphql-tools/utils';
import { fakePromise, IResolvers } from '@graphql-tools/utils';
import { accounts, inventory, products, reviews } from '@internal/e2e';
import { DocumentNode, GraphQLSchema } from 'graphql';

Expand Down Expand Up @@ -52,7 +52,7 @@ export async function getSupergraph(
})),
}).initialize({
update() {},
async healthCheck() {},
healthCheck: () => fakePromise(undefined),
getDataSource({ name }) {
const serviceInput = serviceInputs.find((input) => input.name === name);
if (!serviceInput) {
Expand Down
Loading

0 comments on commit 3f1a0fa

Please sign in to comment.