Repository: graphql/graphql-js
Pull request #3697 (2022-08-15 11:43:09)

resources: Move `mkdirSync` into `writeGeneratedFile`
Context: less repetition and better code reuse

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *compassionate-pike-271cb3* processing.


|  Name | Link |
|---------------------------------|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | ecc3db7ef2e5e3d48b6b31b03f5c3b1d9d3506a3 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/compassionate-pike-271cb3/deploys/62fa314f5ecbaf0008a8245e |
github-actions[bot]: Hi @IvanGoncharov, I'm @github-actions bot happy to help you with this PR 👋

<details>
<summary> Supported commands </summary>

Please post this commands in separate comments and only one per comment:
* `@github-actions run-benchmark` - Run benchmark comparing base and merge commits for this PR
* `@github-actions publish-pr-on-npm` - Build package from this PR and publish it on NPM
</details>


Patch:
diff --git a/resources/build-deno.ts b/resources/build-deno.ts
index c0b3e895..0d7ba254 100644
--- a/resources/build-deno.ts
+++ b/resources/build-deno.ts
@@ -33,7 +33,6 @@ for (const filepath of srcFiles) {
     transformed.dispose();
 
     const destPath = path.join('./denoDist', filepath);
-    fs.mkdirSync(path.dirname(destPath), { recursive: true });
     writeGeneratedFile(destPath, newContent);
   }
 }
diff --git a/resources/build-npm.ts b/resources/build-npm.ts
index badbecc9..91874c29 100644
--- a/resources/build-npm.ts
+++ b/resources/build-npm.ts
@@ -48,10 +48,7 @@ assert(
 );
 
 const tsHost = ts.createCompilerHost(tsOptions);
-tsHost.writeFile = (filepath, body) => {
-  fs.mkdirSync(path.dirname(filepath), { recursive: true });
-  writeGeneratedFile(filepath, body);
-};
+tsHost.writeFile = writeGeneratedFile;
 
 const tsProgram = ts.createProgram(['src/index.ts'], tsOptions, tsHost);
 const tsResult = tsProgram.emit(undefined, undefined, undefined, undefined, {
diff --git a/resources/diff-npm-package.ts b/resources/diff-npm-package.ts
index 47ec08a8..a2b14413 100644
--- a/resources/diff-npm-package.ts
+++ b/resources/diff-npm-package.ts
@@ -3,7 +3,7 @@ import * as fs from 'node:fs';
 import * as os from 'node:os';
 import * as path from 'node:path';
 
-import { exec, execOutput, localRepoPath } from './utils';
+import { exec, execOutput, localRepoPath, writeGeneratedFile } from './utils';
 
 const LOCAL = 'local';
 const tmpDir = path.join(os.tmpdir(), 'graphql-js-npm-diff');
@@ -32,12 +32,8 @@ const diff = execOutput(`npm diff --diff=${fromPackage} --diff=${toPackage}`);
 if (diff === '') {
   console.log('No changes found!');
 } else {
-  const reportsDir = localRepoPath('reports');
-  if (!fs.existsSync(reportsDir)) {
-    fs.mkdirSync(reportsDir);
-  }
-  const reportPath = path.join(reportsDir, 'npm-dist-diff.html');
-  fs.writeFileSync(reportPath, generateReport(diff));
+  const reportPath = localRepoPath('reports', 'npm-dist-diff.html');
+  writeGeneratedFile(reportPath, generateReport(diff));
   console.log('Report saved to: ', reportPath);
 }
 
diff --git a/resources/utils.ts b/resources/utils.ts
index c8d3bb71..ca7918c4 100644
--- a/resources/utils.ts
+++ b/resources/utils.ts
@@ -106,6 +106,7 @@ const prettierConfig = JSON.parse(
 
 export function writeGeneratedFile(filepath: string, body: string): void {
   const formatted = prettier.format(body, { filepath, ...prettierConfig });
+  fs.mkdirSync(path.dirname(filepath), { recursive: true });
   fs.writeFileSync(filepath, formatted);
 }
 
diff --git a/src/execution/execute.ts b/src/execution/execute.ts
index e7b7e4e1..c288e5e5 100644
--- a/src/execution/execute.ts
+++ b/src/execution/execute.ts
@@ -51,7 +51,7 @@ import {
   collectFields,
   collectSubfields as _collectSubfields,
 } from './collectFields';
-import { mapAsyncIterator } from './mapAsyncIterator';
+import { mapAsyncIterable } from './mapAsyncIterable';
 import { getArgumentValues, getVariableValues } from './values';
 
 /* eslint-disable max-params */
@@ -1143,7 +1143,7 @@ function mapSourceToResponse(
   // the GraphQL specification. The `execute` function provides the
   // "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
   // "ExecuteQuery" algorithm, for which `execute` is also used.
-  return mapAsyncIterator(resultOrStream, (payload: unknown) =>
+  return mapAsyncIterable(resultOrStream, (payload: unknown) =>
     executeImpl(buildPerEventExecutionContext(exeContext, payload)),
   );
 }
diff --git a/src/execution/mapAsyncIterator.ts b/src/execution/mapAsyncIterable.ts
similarity index 96%
rename from src/execution/mapAsyncIterator.ts
rename to src/execution/mapAsyncIterable.ts
index 82e863c6..f61855c8 100644
--- a/src/execution/mapAsyncIterator.ts
+++ b/src/execution/mapAsyncIterable.ts
@@ -4,7 +4,7 @@ import type { PromiseOrValue } from '../jsutils/PromiseOrValue';
  * Given an AsyncIterable and a callback function, return an AsyncIterator
  * which produces values mapped via calling the callback function.
  */
-export function mapAsyncIterator<T, U, R = undefined>(
+export function mapAsyncIterable<T, U, R = undefined>(
   iterable: AsyncGenerator<T, R, void> | AsyncIterable<T>,
   callback: (value: T) => PromiseOrValue<U>,
 ): AsyncGenerator<U, R, void> {


Test patch:
diff --git a/src/execution/__tests__/mapAsyncIterator-test.ts b/src/execution/__tests__/mapAsyncIterable-test.ts
similarity index 82%
rename from src/execution/__tests__/mapAsyncIterator-test.ts
rename to src/execution/__tests__/mapAsyncIterable-test.ts
index 81070757..d70714fa 100644
--- a/src/execution/__tests__/mapAsyncIterator-test.ts
+++ b/src/execution/__tests__/mapAsyncIterable-test.ts
@@ -1,10 +1,12 @@
 import { expect } from 'chai';
 import { describe, it } from 'mocha';
 
-import { mapAsyncIterator } from '../mapAsyncIterator';
+import { expectPromise } from '../../__testUtils__/expectPromise';
+
+import { mapAsyncIterable } from '../mapAsyncIterable';
 
 /* eslint-disable @typescript-eslint/require-await */
-describe('mapAsyncIterator', () => {
+describe('mapAsyncIterable', () => {
   it('maps over async generator', async () => {
     async function* source() {
       yield 1;
@@ -12,7 +14,7 @@ describe('mapAsyncIterator', () => {
       yield 3;
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -42,7 +44,7 @@ describe('mapAsyncIterator', () => {
       },
     };
 
-    const doubles = mapAsyncIterator(iterable, (x) => x + x);
+    const doubles = mapAsyncIterable(iterable, (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -60,7 +62,7 @@ describe('mapAsyncIterator', () => {
       yield 3;
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     const result = [];
     for await (const x of doubles) {
@@ -76,7 +78,7 @@ describe('mapAsyncIterator', () => {
       yield 3;
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => Promise.resolve(x + x));
+    const doubles = mapAsyncIterable(source(), (x) => Promise.resolve(x + x));
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -100,7 +102,7 @@ describe('mapAsyncIterator', () => {
       }
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -139,7 +141,7 @@ describe('mapAsyncIterator', () => {
       },
     };
 
-    const doubles = mapAsyncIterator(iterable, (x) => x + x);
+    const doubles = mapAsyncIterable(iterable, (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -164,7 +166,7 @@ describe('mapAsyncIterator', () => {
       }
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 'aa', done: false });
     expect(await doubles.next()).to.deep.equal({ value: 'bb', done: false });
@@ -203,20 +205,15 @@ describe('mapAsyncIterator', () => {
       },
     };
 
-    const doubles = mapAsyncIterator(iterable, (x) => x + x);
+    const doubles = mapAsyncIterable(iterable, (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
 
     // Throw error
-    let caughtError;
-    try {
-      /* c8 ignore next 2 */
-      await doubles.throw('ouch');
-    } catch (e) {
-      caughtError = e;
-    }
-    expect(caughtError).to.equal('ouch');
+    const message = 'allows throwing errors when mapping async iterable';
+    const thrown = doubles.throw(new Error(message));
+    await expectPromise(thrown).toRejectWith(message);
   });
 
   it('passes through caught errors through async generators', async () => {
@@ -231,7 +228,7 @@ describe('mapAsyncIterator', () => {
       }
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({ value: 2, done: false });
     expect(await doubles.next()).to.deep.equal({ value: 4, done: false });
@@ -258,24 +255,14 @@ describe('mapAsyncIterator', () => {
       throw new Error('Goodbye');
     }
 
-    const doubles = mapAsyncIterator(source(), (x) => x + x);
+    const doubles = mapAsyncIterable(source(), (x) => x + x);
 
     expect(await doubles.next()).to.deep.equal({
       value: 'HelloHello',
       done: false,
     });
 
-    let caughtError;
-    try {
-      /* c8 ignore next 2 */
-      await doubles.next();
-    } catch (e) {
-      caughtError = e;
-    }
-
-    expect(caughtError)
-      .to.be.an.instanceOf(Error)
-      .with.property('message', 'Goodbye');
+    await expectPromise(doubles.next()).toRejectWith('Goodbye');
   });
 
   async function testClosesSourceWithMapper<T>(mapper: (value: number) => T) {
@@ -293,21 +280,11 @@ describe('mapAsyncIterator', () => {
       }
     }
 
-    const throwOver1 = mapAsyncIterator(source(), mapper);
+    const throwOver1 = mapAsyncIterable(source(), mapper);
 
     expect(await throwOver1.next()).to.deep.equal({ value: 1, done: false });
 
-    let expectedError;
-    try {
-      /* c8 ignore next 2 */
-      await throwOver1.next();
-    } catch (error) {
-      expectedError = error;
-    }
-
-    expect(expectedError)
-      .to.be.an.instanceOf(Error)
-      .with.property('message', 'Cannot count to 2');
+    await expectPromise(throwOver1.next()).toRejectWith('Cannot count to 2');
 
     expect(await throwOver1.next()).to.deep.equal({
       value: undefined,