Browse Source

Merge branch 'master' into minor

Michael Bromley 4 years ago
parent
commit
94ddb75476

+ 1 - 1
docs/content/getting-started.md

@@ -7,7 +7,7 @@ weight: 0
 
 ## Requirements
  
-* [Node.js](https://nodejs.org/en/) **v10.13.0** or above
+* [Node.js](https://nodejs.org/en/) **v12** or above, with support for **even-numbered Node.js versions**.
 * If you want to use MySQL, MariaDB, or Postgres as your data store, then you'll need an instance available locally. However, if you are just testing out Vendure, we recommend using SQLite, which has no external requirements.
 * For Windows users: make sure you have **[windows build tools](https://www.npmjs.com/package/windows-build-tools) installed**
   * `npm install --global --production windows-build-tools`

+ 1 - 0
packages/admin-ui/src/lib/settings/src/components/channel-detail/channel-detail.component.ts

@@ -137,6 +137,7 @@ export class ChannelDetailComponent
                     const input = {
                         id: channel.id,
                         code: formValue.code,
+                        token: formValue.token,
                         pricesIncludeTax: formValue.pricesIncludeTax,
                         currencyCode: formValue.currencyCode,
                         defaultShippingZoneId: formValue.defaultShippingZoneId,

+ 68 - 0
packages/core/e2e/default-search-plugin.e2e-spec.ts

@@ -2,6 +2,7 @@
 import { pick } from '@vendure/common/lib/pick';
 import {
     DefaultJobQueuePlugin,
+    DefaultLogger,
     DefaultSearchPlugin,
     facetValueCollectionFilter,
     mergeConfig,
@@ -72,6 +73,7 @@ jest.setTimeout(10000);
 describe('Default search plugin', () => {
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig, {
+            logger: new DefaultLogger(),
             plugins: [DefaultSearchPlugin, DefaultJobQueuePlugin],
         }),
     );
@@ -1282,6 +1284,72 @@ describe('Default search plugin', () => {
                 });
                 expect(searchGrouped.items.map(i => i.productName)).toEqual(['xyz']);
             });
+
+            // https://github.com/vendure-ecommerce/vendure/issues/896
+            it('removing from channel with multiple languages', async () => {
+                adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
+
+                await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
+                    input: {
+                        id: 'T_4',
+                        translations: [
+                            {
+                                languageCode: LanguageCode.en,
+                                name: 'product en',
+                                slug: 'product-en',
+                                description: 'en',
+                            },
+                            {
+                                languageCode: LanguageCode.de,
+                                name: 'product de',
+                                slug: 'product-de',
+                                description: 'de',
+                            },
+                        ],
+                    },
+                });
+
+                await adminClient.query<AssignProductsToChannel.Mutation, AssignProductsToChannel.Variables>(
+                    ASSIGN_PRODUCT_TO_CHANNEL,
+                    {
+                        input: { channelId: secondChannel.id, productIds: ['T_4'] },
+                    },
+                );
+                await awaitRunningJobs(adminClient);
+
+                async function searchSecondChannelForDEProduct() {
+                    adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
+                    const { search } = await adminClient.query<
+                        SearchProductsShop.Query,
+                        SearchProductsShop.Variables
+                    >(
+                        SEARCH_PRODUCTS,
+                        {
+                            input: { term: 'product', groupByProduct: true },
+                        },
+                        { languageCode: LanguageCode.de },
+                    );
+                    return search;
+                }
+
+                const search1 = await searchSecondChannelForDEProduct();
+                expect(search1.items.map(i => i.productName)).toEqual(['product de']);
+
+                adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
+                const { removeProductsFromChannel } = await adminClient.query<
+                    RemoveProductsFromChannel.Mutation,
+                    RemoveProductsFromChannel.Variables
+                >(REMOVE_PRODUCT_FROM_CHANNEL, {
+                    input: {
+                        productIds: ['T_4'],
+                        channelId: secondChannel.id,
+                    },
+                });
+                await awaitRunningJobs(adminClient);
+
+                const search2 = await searchSecondChannelForDEProduct();
+                expect(search2.items.map(i => i.productName)).toEqual([]);
+            });
         });
 
         describe('multiple language handling', () => {

+ 0 - 2
packages/core/e2e/graphql/shop-definitions.ts

@@ -1,7 +1,5 @@
 import gql from 'graphql-tag';
 
-import { ERROR_RESULT_FRAGMENT } from '../../../admin-ui/src/lib/core/src/data/definitions/shared-definitions';
-
 export const TEST_ORDER_FRAGMENT = gql`
     fragment TestOrderFragment on Order {
         id

+ 1 - 4
packages/core/package.json

@@ -58,9 +58,8 @@
     "express": "^4.17.1",
     "fs-extra": "^9.0.1",
     "graphql": "15.5.0",
-    "graphql-iso-date": "^3.6.1",
+    "graphql-scalars": "^1.10.0",
     "graphql-tag": "^2.12.4",
-    "graphql-type-json": "^0.3.2",
     "graphql-upload": "^12.0.0",
     "http-proxy-middleware": "^1.0.5",
     "i18next": "^19.8.1",
@@ -82,8 +81,6 @@
     "@types/csv-parse": "^1.2.2",
     "@types/express": "^4.17.8",
     "@types/faker": "^4.1.7",
-    "@types/graphql-iso-date": "^3.4.0",
-    "@types/graphql-type-json": "^0.3.2",
     "@types/graphql-upload": "^8.0.4",
     "@types/gulp": "^4.0.7",
     "@types/mime-types": "^2.1.0",

+ 1 - 3
packages/core/src/api/config/generate-resolvers.ts

@@ -1,8 +1,7 @@
 import { StockMovementType } from '@vendure/common/lib/generated-types';
 import { IFieldResolver, IResolvers } from 'apollo-server-express';
 import { GraphQLSchema } from 'graphql';
-import { GraphQLDateTime } from 'graphql-iso-date';
-import GraphQLJSON from 'graphql-type-json';
+import { GraphQLDateTime, GraphQLJSON } from 'graphql-scalars';
 import { GraphQLUpload } from 'graphql-upload';
 
 import { REQUEST_CONTEXT_KEY } from '../../common/constants';
@@ -14,7 +13,6 @@ import { shopErrorOperationTypeResolvers } from '../../common/error/generated-gr
 import { Translatable } from '../../common/types/locale-types';
 import { ConfigService } from '../../config/config.service';
 import { CustomFieldConfig, RelationCustomFieldConfig } from '../../config/custom-field/custom-field-types';
-import { CustomFieldRelationService } from '../../service/helpers/custom-field-relation/custom-field-relation.service';
 import { CustomFieldRelationResolverService } from '../common/custom-field-relation-resolver.service';
 import { ApiType } from '../common/get-api-type';
 import { RequestContext } from '../common/request-context';

+ 3 - 1
packages/core/src/bootstrap.ts

@@ -91,7 +91,9 @@ export async function bootstrapWorker(
 ): Promise<{ app: INestApplicationContext; startJobQueue: () => Promise<void> }> {
     const vendureConfig = await preBootstrapConfig(userConfig);
     const config = disableSynchronize(vendureConfig);
-    (config.logger as any).setDefaultContext('Vendure Worker');
+    if (config.logger instanceof DefaultLogger) {
+        config.logger.setDefaultContext('Vendure Worker');
+    }
     Logger.useLogger(config.logger);
     Logger.info(`Bootstrapping Vendure Worker (pid: ${process.pid})...`);
 

+ 2 - 0
packages/core/src/common/ttl-cache.ts

@@ -1,6 +1,8 @@
 /**
  * An in-memory cache with a configurable TTL (time to live) which means cache items
  * expire after they have been in the cache longer than that time.
+ *
+ * The `ttl` config option is in milliseconds. Defaults to 30 seconds TTL and a cache size of 1000.
  */
 export class TtlCache<K, V> {
     private cache = new Map<K, { value: V; expires: number }>();

+ 32 - 10
packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts

@@ -12,6 +12,7 @@ import { asyncObservable, idsAreEqual } from '../../../common/utils';
 import { ConfigService } from '../../../config/config.service';
 import { Logger } from '../../../config/logger/vendure-logger';
 import { FacetValue } from '../../../entity/facet-value/facet-value.entity';
+import { ProductVariantTranslation } from '../../../entity/product-variant/product-variant-translation.entity';
 import { ProductVariant } from '../../../entity/product-variant/product-variant.entity';
 import { Product } from '../../../entity/product/product.entity';
 import { ProductVariantService } from '../../../service/services/product-variant.service';
@@ -151,10 +152,15 @@ export class IndexerController {
         const ctx = RequestContext.deserialize(data.ctx);
         const variants = await this.connection.getRepository(ProductVariant).findByIds(data.variantIds);
         if (variants.length) {
+            const languageVariants = unique([
+                ...variants
+                    .reduce((vt, v) => [...vt, ...v.translations], [] as Array<Translation<ProductVariant>>)
+                    .map(t => t.languageCode),
+            ]);
             await this.removeSearchIndexItems(
-                ctx.languageCode,
                 ctx.channelId,
                 variants.map(v => v.id),
+                languageVariants,
             );
         }
         return true;
@@ -177,15 +183,19 @@ export class IndexerController {
 
     async removeVariantFromChannel(data: VariantChannelMessageData): Promise<boolean> {
         const ctx = RequestContext.deserialize(data.ctx);
-        await this.removeSearchIndexItems(ctx.languageCode, data.channelId, [data.productVariantId]);
+        const variant = await this.connection.getRepository(ProductVariant).findOne(data.productVariantId);
+        const languageVariants = variant?.translations.map(t => t.languageCode) ?? [];
+        await this.removeSearchIndexItems(data.channelId, [data.productVariantId], languageVariants);
         return true;
     }
 
     async updateAsset(data: UpdateAssetMessageData): Promise<boolean> {
         const id = data.asset.id;
+
         function getFocalPoint(point?: { x: number; y: number }) {
             return point && point.x && point.y ? point : null;
         }
+
         const focalPoint = getFocalPoint(data.asset.focalPoint);
         await this.connection
             .getRepository(SearchIndexItem)
@@ -266,9 +276,16 @@ export class IndexerController {
             relations: ['variants'],
         });
         if (product) {
+            const languageVariants = unique([
+                ...product.translations.map(t => t.languageCode),
+                ...product.variants
+                    .reduce((vt, v) => [...vt, ...v.translations], [] as Array<Translation<ProductVariant>>)
+                    .map(t => t.languageCode),
+            ]);
+
             const removedVariantIds = product.variants.map(v => v.id);
             if (removedVariantIds.length) {
-                await this.removeSearchIndexItems(ctx.languageCode, channelId, removedVariantIds);
+                await this.removeSearchIndexItems(channelId, removedVariantIds, languageVariants);
             }
         }
         return true;
@@ -436,13 +453,18 @@ export class IndexerController {
     /**
      * Remove items from the search index
      */
-    private async removeSearchIndexItems(languageCode: LanguageCode, channelId: ID, variantIds: ID[]) {
-        const compositeKeys = variantIds.map(id => ({
-            productVariantId: id,
-            channelId,
-            languageCode,
-        })) as any[];
-        await this.queue.push(() => this.connection.getRepository(SearchIndexItem).delete(compositeKeys));
+    private async removeSearchIndexItems(channelId: ID, variantIds: ID[], languageCodes: LanguageCode[]) {
+        const keys: Array<Partial<SearchIndexItem>> = [];
+        for (const productVariantId of variantIds) {
+            for (const languageCode of languageCodes) {
+                keys.push({
+                    productVariantId,
+                    channelId,
+                    languageCode,
+                });
+            }
+        }
+        await this.queue.push(() => this.connection.getRepository(SearchIndexItem).delete(keys as any));
     }
 
     /**

+ 5 - 2
packages/core/src/service/services/channel.service.ts

@@ -88,10 +88,13 @@ export class ChannelService {
         entityType: Type<T>,
         entityId: ID,
         channelIds: ID[],
-    ): Promise<T> {
-        const entity = await this.connection.getEntityOrThrow(ctx, entityType, entityId, {
+    ): Promise<T | undefined> {
+        const entity = await this.connection.getRepository(ctx, entityType).findOne(entityId, {
             relations: ['channels'],
         });
+        if (!entity) {
+            return;
+        }
         for (const id of channelIds) {
             entity.channels = entity.channels.filter(c => !idsAreEqual(c.id, id));
         }

+ 4 - 4
packages/core/src/service/services/collection.service.ts

@@ -87,9 +87,7 @@ export class CollectionService implements OnModuleInit {
                 Logger.verbose(`Processing ${job.data.collectionIds.length} Collections`);
                 let completed = 0;
                 for (const collectionId of job.data.collectionIds) {
-                    const collection = await this.connection.getRepository(Collection).findOne(collectionId, {
-                        relations: ['productVariants'],
-                    });
+                    const collection = await this.connection.getRepository(Collection).findOne(collectionId);
                     if (!collection) {
                         Logger.warn(`Could not find Collection with id ${collectionId}, skipping`);
                         continue;
@@ -482,8 +480,10 @@ export class CollectionService implements OnModuleInit {
             const productVariants = await this.connection
                 .getRepository(ctx, ProductVariant)
                 .createQueryBuilder('variant')
+                .select('variant.id', 'id')
                 .innerJoin('variant.collections', 'collection', 'collection.id = :id', { id: collection.id })
-                .getMany();
+                .getRawMany();
+
             return productVariants.map(v => v.id);
         }
     }

+ 0 - 8
packages/create/src/create-vendure-app.ts

@@ -106,14 +106,6 @@ async function createApp(
             'migration:run': usingTs ? 'ts-node migration run' : 'node migration run',
             'migration:revert': usingTs ? 'ts-node migration revert' : 'node migration revert',
         },
-        /**
-         * A work-around for the breaking update of tslib as described here:
-         * https://github.com/typeorm/typeorm/issues/6054
-         * TODO: Remove this once the TypeScript team come up with a solution
-         */
-        resolutions: {
-            tslib: '1.11.2',
-        },
     };
 
     console.log();

+ 3 - 65
packages/dev-server/dev-config.ts

@@ -2,63 +2,11 @@
 import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
 import { AssetServerPlugin } from '@vendure/asset-server-plugin';
 import { ADMIN_API_PATH, API_PORT, SHOP_API_PATH } from '@vendure/common/lib/shared-constants';
-import {
-    Asset,
-    DefaultJobQueuePlugin,
-    DefaultLogger,
-    defaultPromotionActions,
-    DefaultSearchPlugin,
-    dummyPaymentHandler,
-    examplePaymentHandler,
-    FulfillmentHandler,
-    LanguageCode,
-    LogLevel,
-    manualFulfillmentHandler,
-    PaymentMethodEligibilityChecker,
-    PromotionItemAction,
-    VendureConfig,
-} from '@vendure/core';
-import { ElasticsearchPlugin } from '@vendure/elasticsearch-plugin';
+import { DefaultJobQueuePlugin, DefaultLogger, DefaultSearchPlugin, dummyPaymentHandler, LogLevel, VendureConfig, } from '@vendure/core';
 import { defaultEmailHandlers, EmailPlugin } from '@vendure/email-plugin';
 import path from 'path';
 import { ConnectionOptions } from 'typeorm';
 
-const testPaymentChecker = new PaymentMethodEligibilityChecker({
-    code: 'test-checker',
-    description: [{ languageCode: LanguageCode.en, value: 'test checker' }],
-    args: {},
-    check: (ctx, order) => true,
-});
-
-const testPromoAction = new PromotionItemAction({
-    code: 'discount-price-action',
-    description: [{ languageCode: LanguageCode.en, value: 'Apply discount price' }],
-    args: {},
-    execute: (ctx, orderItem, orderLine) => {
-        if ((orderLine.productVariant.customFields as any).discountPrice) {
-            return -(
-                orderLine.unitPriceWithTax - (orderLine.productVariant.customFields as any).discountPrice
-            );
-        }
-        return 0;
-    },
-});
-
-const myHandler = new FulfillmentHandler({
-    code: 'test-handler',
-    args: {},
-    description: [{ languageCode: LanguageCode.en, value: 'test fulfillment handler' }],
-    createFulfillment: ctx => {
-        return {
-            method: 'test-handler',
-            trackingCode: '123123123123',
-            customFields: {
-                logoId: 1,
-            },
-        };
-    },
-});
-
 /**
  * Config settings used during development
  */
@@ -93,23 +41,13 @@ export const devConfig: VendureConfig = {
         ...getDbConfig(),
     },
     paymentOptions: {
-        paymentMethodEligibilityCheckers: [testPaymentChecker],
         paymentMethodHandlers: [dummyPaymentHandler],
     },
-    promotionOptions: {
-        promotionActions: [...defaultPromotionActions, testPromoAction],
-    },
-    customFields: {
-        /*Asset: [{ name: 'description', type: 'string' }],*/
-        ProductVariant: [{ name: 'discountPrice', type: 'int' }],
-    },
-    logger: new DefaultLogger({ level: LogLevel.Info }),
+    customFields: {},
+    logger: new DefaultLogger({level: LogLevel.Info}),
     importExportOptions: {
         importAssetsDir: path.join(__dirname, 'import-assets'),
     },
-    shippingOptions: {
-        fulfillmentHandlers: [manualFulfillmentHandler, myHandler],
-    },
     plugins: [
         AssetServerPlugin.init({
             route: 'assets',

+ 66 - 0
packages/elasticsearch-plugin/e2e/elasticsearch-plugin.e2e-spec.ts

@@ -1013,6 +1013,72 @@ describe('Elasticsearch plugin', () => {
                 });
                 expect(searchGrouped.items.map(i => i.productName)).toEqual(['xyz']);
             });
+
+            // https://github.com/vendure-ecommerce/vendure/issues/896
+            it('removing from channel with multiple languages', async () => {
+                adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
+
+                await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
+                    input: {
+                        id: 'T_4',
+                        translations: [
+                            {
+                                languageCode: LanguageCode.en,
+                                name: 'product en',
+                                slug: 'product-en',
+                                description: 'en',
+                            },
+                            {
+                                languageCode: LanguageCode.de,
+                                name: 'product de',
+                                slug: 'product-de',
+                                description: 'de',
+                            },
+                        ],
+                    },
+                });
+
+                await adminClient.query<AssignProductsToChannel.Mutation, AssignProductsToChannel.Variables>(
+                    ASSIGN_PRODUCT_TO_CHANNEL,
+                    {
+                        input: { channelId: secondChannel.id, productIds: ['T_4'] },
+                    },
+                );
+                await awaitRunningJobs(adminClient);
+
+                async function searchSecondChannelForDEProduct() {
+                    adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
+                    const { search } = await adminClient.query<
+                        SearchProductsShop.Query,
+                        SearchProductsShop.Variables
+                    >(
+                        SEARCH_PRODUCTS,
+                        {
+                            input: { term: 'product', groupByProduct: true },
+                        },
+                        { languageCode: LanguageCode.de },
+                    );
+                    return search;
+                }
+
+                const search1 = await searchSecondChannelForDEProduct();
+                expect(search1.items.map(i => i.productName)).toEqual(['product de']);
+
+                adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
+                const { removeProductsFromChannel } = await adminClient.query<
+                    RemoveProductsFromChannel.Mutation,
+                    RemoveProductsFromChannel.Variables
+                >(REMOVE_PRODUCT_FROM_CHANNEL, {
+                    input: {
+                        productIds: ['T_4'],
+                        channelId: secondChannel.id,
+                    },
+                });
+                await awaitRunningJobs(adminClient);
+
+                const search2 = await searchSecondChannelForDEProduct();
+                expect(search2.items.map(i => i.productName)).toEqual([]);
+            });
         });
 
         describe('multiple language handling', () => {

+ 1 - 0
packages/email-plugin/src/plugin.ts

@@ -118,6 +118,7 @@ import {
  * ```ts
  * EmailPlugin.init({
  *   devMode: true,
+ *   route: 'mailbox',
  *   handlers: defaultEmailHandlers,
  *   templatePath: path.join(__dirname, 'vendure/email/templates'),
  *   outputPath: path.join(__dirname, 'test-emails'),

+ 9 - 33
yarn.lock

@@ -3797,20 +3797,6 @@
   dependencies:
     "@types/node" "*"
 
-"@types/graphql-iso-date@^3.4.0":
-  version "3.4.0"
-  resolved "https://registry.npmjs.org/@types/graphql-iso-date/-/graphql-iso-date-3.4.0.tgz#b6710b21e3b0bfdb1a0529b285148d98eac18b1f"
-  integrity sha512-V3jITHTsoI2E8TGt9+/HPDz6LWt3z9/HYnPJYWI6WwiLRexsngg7KzaQlCgQkA4jkEbGPROUD0hJFc9F02W9WA==
-  dependencies:
-    graphql "^15.1.0"
-
-"@types/graphql-type-json@^0.3.2":
-  version "0.3.2"
-  resolved "https://registry.npmjs.org/@types/graphql-type-json/-/graphql-type-json-0.3.2.tgz#1a7105e6546fc1630a5db4834bfbc0eb554986e4"
-  integrity sha512-c1cq4o8EhY0Z39ua8UXwG8uBs23xBYA/Uw0tXFl6SuTUpkVv/IJqf6pHQbfdC7nwFRhX2ifTOV/UIg0Q/IJsbg==
-  dependencies:
-    graphql "^14.5.3"
-
 "@types/graphql-upload@^8.0.4":
   version "8.0.4"
   resolved "https://registry.npmjs.org/@types/graphql-upload/-/graphql-upload-8.0.4.tgz#23a8ffb3d2fe6e0ee07e6f16ee9d9d5e995a2f4f"
@@ -9675,11 +9661,6 @@ graphql-extensions@^0.14.0:
     apollo-server-env "^3.1.0"
     apollo-server-types "^0.8.0"
 
-graphql-iso-date@^3.6.1:
-  version "3.6.1"
-  resolved "https://registry.npmjs.org/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz#bd2d0dc886e0f954cbbbc496bbf1d480b57ffa96"
-  integrity sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q==
-
 graphql-request@^3.3.0:
   version "3.4.0"
   resolved "https://registry.npmjs.org/graphql-request/-/graphql-request-3.4.0.tgz#3a400cd5511eb3c064b1873afb059196bbea9c2b"
@@ -9689,6 +9670,13 @@ graphql-request@^3.3.0:
     extract-files "^9.0.0"
     form-data "^3.0.0"
 
+graphql-scalars@^1.10.0:
+  version "1.10.0"
+  resolved "https://registry.npmjs.org/graphql-scalars/-/graphql-scalars-1.10.0.tgz#9daf9252b16e6fae553a06976163a23f41b65dfd"
+  integrity sha512-LONlj8FfhA2iGpkZJWf5e4PVAHXxnZEHSOEvowLYvNXl/TNnhIck8VmE+lren/aa6GKrG+lZufo5lgnyjxcF6g==
+  dependencies:
+    tslib "~2.2.0"
+
 graphql-subscriptions@^1.0.0:
   version "1.2.0"
   resolved "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.2.0.tgz#d82ff76e7504ac91acbbea15f36cd3904043937b"
@@ -9726,11 +9714,6 @@ graphql-tools@^4.0.8:
     iterall "^1.1.3"
     uuid "^3.1.0"
 
-graphql-type-json@^0.3.2:
-  version "0.3.2"
-  resolved "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz#f53a851dbfe07bd1c8157d24150064baab41e115"
-  integrity sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==
-
 graphql-upload@^11.0.0:
   version "11.0.0"
   resolved "https://registry.npmjs.org/graphql-upload/-/graphql-upload-11.0.0.tgz#24b245ff18f353bab6715e8a055db9fd73035e10"
@@ -9758,18 +9741,11 @@ graphql-ws@4.1.5:
   resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.1.5.tgz#03526b29acb54a424a9fbe300a4bd69ff65a50b3"
   integrity sha512-yUQ1AjegD1Y9jDS699kyw7Mw+9H+rILm2HoS8N5a5B5YTH93xy3yifFhAJpKGc2wb/8yGdlVy8gTcud0TPqi6Q==
 
-graphql@*, graphql@15.5.0, graphql@^15.1.0, graphql@^15.3.0:
+graphql@*, graphql@15.5.0, graphql@^15.3.0:
   version "15.5.0"
   resolved "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5"
   integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA==
 
-graphql@^14.5.3:
-  version "14.7.0"
-  resolved "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72"
-  integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA==
-  dependencies:
-    iterall "^1.2.2"
-
 growly@^1.3.0:
   version "1.3.0"
   resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@@ -11191,7 +11167,7 @@ istanbul-reports@^3.0.2:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
 
-iterall@1.3.0, iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2, iterall@^1.3.0:
+iterall@1.3.0, iterall@^1.1.3, iterall@^1.2.1, iterall@^1.3.0:
   version "1.3.0"
   resolved "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
   integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==