Quellcode durchsuchen

refactor(core): Make RequestContext serialize / deserialize type-safe

Michael Bromley vor 5 Jahren
Ursprung
Commit
b1a13814d2

+ 12 - 16
packages/core/src/api/common/request-context.spec.ts

@@ -7,14 +7,12 @@ import { Session } from '../../entity/session/session.entity';
 import { User } from '../../entity/user/user.entity';
 import { Zone } from '../../entity/zone/zone.entity';
 
-import { RequestContext } from './request-context';
+import { RequestContext, SerializedRequestContext } from './request-context';
 
 describe('RequestContext', () => {
-
     describe('fromObject()', () => {
-
         let original: RequestContext;
-        let ctxObject: object;
+        let ctxObject: SerializedRequestContext;
         let session: Session;
         let user: User;
         let channel: Channel;
@@ -60,54 +58,52 @@ describe('RequestContext', () => {
                 authorizedAsOwnerOnly: false,
             });
 
-            ctxObject = JSON.parse(JSON.stringify(original));
+            ctxObject = original.serialize();
         });
 
         it('apiType', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.apiType).toBe(original.apiType);
         });
 
         it('channelId', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.channelId).toBe(original.channelId);
         });
 
         it('languageCode', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.languageCode).toBe(original.languageCode);
         });
 
         it('activeUserId', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.activeUserId).toBe(original.activeUserId);
         });
 
         it('isAuthorized', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.isAuthorized).toBe(original.isAuthorized);
         });
 
         it('authorizedAsOwnerOnly', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.authorizedAsOwnerOnly).toBe(original.authorizedAsOwnerOnly);
         });
 
         it('channel', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.channel).toEqual(original.channel);
         });
 
         it('session', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.session).toEqual(original.session);
         });
 
         it('activeUser', () => {
-            const result = RequestContext.fromObject(ctxObject);
+            const result = RequestContext.deserialize(ctxObject);
             expect(result.activeUser).toEqual(original.activeUser);
         });
-
     });
-
 });

+ 19 - 2
packages/core/src/api/common/request-context.ts

@@ -1,5 +1,5 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
-import { ID } from '@vendure/common/lib/shared-types';
+import { ID, Type } from '@vendure/common/lib/shared-types';
 import i18next, { TFunction } from 'i18next';
 
 import { DEFAULT_LANGUAGE_CODE } from '../../common/constants';
@@ -11,6 +11,19 @@ import { User } from '../../entity/user/user.entity';
 
 import { ApiType } from './get-api-type';
 
+export type ObjectOf<T> = { [K in keyof T]: T[K] };
+
+export interface SerializedRequestContext {
+    _session?: ObjectOf<Session> & {
+        user?: ObjectOf<User>;
+    };
+    _apiType: ApiType;
+    _channel: ObjectOf<Channel>;
+    _languageCode: LanguageCode;
+    _isAuthorized: boolean;
+    _authorizedAsOwnerOnly: boolean;
+}
+
 /**
  * @description
  * The RequestContext holds information relevant to the current request, which may be
@@ -55,7 +68,7 @@ export class RequestContext {
      * Creates a new RequestContext object from a plain object which is the result of
      * a JSON serialization - deserialization operation.
      */
-    static fromObject(ctxObject: any): RequestContext {
+    static deserialize(ctxObject: SerializedRequestContext): RequestContext {
         let session: Session | undefined;
         if (ctxObject._session) {
             if (ctxObject._session.user) {
@@ -78,6 +91,10 @@ export class RequestContext {
         });
     }
 
+    serialize(): SerializedRequestContext {
+        return JSON.parse(JSON.stringify(this));
+    }
+
     get apiType(): ApiType {
         return this._apiType;
     }

+ 27 - 22
packages/core/src/plugin/default-search-plugin/indexer/indexer.controller.ts

@@ -57,8 +57,8 @@ export class IndexerController {
 
     @MessagePattern(ReindexMessage.pattern)
     reindex({ ctx: rawContext }: ReindexMessage['data']): Observable<ReindexMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
-        return asyncObservable(async observer => {
+        const ctx = RequestContext.deserialize(rawContext);
+        return asyncObservable(async (observer) => {
             const timeStart = Date.now();
             const qb = this.getSearchIndexQueryBuilder(ctx.channelId);
             const count = await qb.getCount();
@@ -100,9 +100,9 @@ export class IndexerController {
         ctx: rawContext,
         ids,
     }: UpdateVariantsByIdMessage['data']): Observable<UpdateVariantsByIdMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
 
-        return asyncObservable(async observer => {
+        return asyncObservable(async (observer) => {
             const timeStart = Date.now();
             if (ids.length) {
                 const batches = Math.ceil(ids.length / BATCH_SIZE);
@@ -137,7 +137,7 @@ export class IndexerController {
 
     @MessagePattern(UpdateProductMessage.pattern)
     updateProduct(data: UpdateProductMessage['data']): Observable<UpdateProductMessage['response']> {
-        const ctx = RequestContext.fromObject(data.ctx);
+        const ctx = RequestContext.deserialize(data.ctx);
         return asyncObservable(async () => {
             return this.updateProductInChannel(ctx, data.productId, ctx.channelId);
         });
@@ -145,7 +145,7 @@ export class IndexerController {
 
     @MessagePattern(UpdateVariantMessage.pattern)
     updateVariants(data: UpdateVariantMessage['data']): Observable<UpdateVariantMessage['response']> {
-        const ctx = RequestContext.fromObject(data.ctx);
+        const ctx = RequestContext.deserialize(data.ctx);
         return asyncObservable(async () => {
             return this.updateVariantsInChannel(ctx, data.variantIds, ctx.channelId);
         });
@@ -153,7 +153,7 @@ export class IndexerController {
 
     @MessagePattern(DeleteProductMessage.pattern)
     deleteProduct(data: DeleteProductMessage['data']): Observable<DeleteProductMessage['response']> {
-        const ctx = RequestContext.fromObject(data.ctx);
+        const ctx = RequestContext.deserialize(data.ctx);
         return asyncObservable(async () => {
             return this.deleteProductInChannel(ctx, data.productId, ctx.channelId);
         });
@@ -161,11 +161,15 @@ export class IndexerController {
 
     @MessagePattern(DeleteVariantMessage.pattern)
     deleteVariant(data: DeleteVariantMessage['data']): Observable<DeleteVariantMessage['response']> {
-        const ctx = RequestContext.fromObject(data.ctx);
+        const ctx = RequestContext.deserialize(data.ctx);
         return asyncObservable(async () => {
             const variants = await this.connection.getRepository(ProductVariant).findByIds(data.variantIds);
             if (variants.length) {
-                await this.removeSearchIndexItems(ctx.languageCode, ctx.channelId, variants.map(v => v.id));
+                await this.removeSearchIndexItems(
+                    ctx.languageCode,
+                    ctx.channelId,
+                    variants.map((v) => v.id),
+                );
             }
             return true;
         });
@@ -175,7 +179,7 @@ export class IndexerController {
     assignProductToChannel(
         data: AssignProductToChannelMessage['data'],
     ): Observable<AssignProductToChannelMessage['response']> {
-        const ctx = RequestContext.fromObject(data.ctx);
+        const ctx = RequestContext.deserialize(data.ctx);
         return asyncObservable(async () => {
             return this.updateProductInChannel(ctx, data.productId, data.channelId);
         });
@@ -185,7 +189,7 @@ export class IndexerController {
     removeProductFromChannel(
         data: RemoveProductFromChannelMessage['data'],
     ): Observable<RemoveProductFromChannelMessage['response']> {
-        const ctx = RequestContext.fromObject(data.ctx);
+        const ctx = RequestContext.deserialize(data.ctx);
         return asyncObservable(async () => {
             return this.deleteProductInChannel(ctx, data.productId, data.channelId);
         });
@@ -218,14 +222,15 @@ export class IndexerController {
             relations: ['variants'],
         });
         if (product) {
-            let updatedVariants = await this.connection
-                .getRepository(ProductVariant)
-                .findByIds(product.variants.map(v => v.id), {
+            let updatedVariants = await this.connection.getRepository(ProductVariant).findByIds(
+                product.variants.map((v) => v.id),
+                {
                     relations: variantRelations,
                     where: { deletedAt: null },
-                });
+                },
+            );
             if (product.enabled === false) {
-                updatedVariants.forEach(v => (v.enabled = false));
+                updatedVariants.forEach((v) => (v.enabled = false));
             }
             Logger.verbose(`Updating ${updatedVariants.length} variants`, workerLoggerCtx);
             updatedVariants = this.hydrateVariants(ctx, updatedVariants);
@@ -262,7 +267,7 @@ export class IndexerController {
             relations: ['variants'],
         });
         if (product) {
-            const removedVariantIds = product.variants.map(v => v.id);
+            const removedVariantIds = product.variants.map((v) => v.id);
             if (removedVariantIds.length) {
                 await this.removeSearchIndexItems(ctx.languageCode, channelId, removedVariantIds);
             }
@@ -289,8 +294,8 @@ export class IndexerController {
      */
     private hydrateVariants(ctx: RequestContext, variants: ProductVariant[]): ProductVariant[] {
         return variants
-            .map(v => this.productVariantService.applyChannelPriceAndTax(v, ctx))
-            .map(v => translateDeep(v, ctx.languageCode, ['product']));
+            .map((v) => this.productVariantService.applyChannelPriceAndTax(v, ctx))
+            .map((v) => translateDeep(v, ctx.languageCode, ['product']));
     }
 
     private async saveVariants(languageCode: LanguageCode, channelId: ID, variants: ProductVariant[]) {
@@ -317,10 +322,10 @@ export class IndexerController {
                     productVariantAssetId: v.featuredAsset ? v.featuredAsset.id : null,
                     productPreview: v.product.featuredAsset ? v.product.featuredAsset.preview : '',
                     productVariantPreview: v.featuredAsset ? v.featuredAsset.preview : '',
-                    channelIds: v.product.channels.map(c => c.id as string),
+                    channelIds: v.product.channels.map((c) => c.id as string),
                     facetIds: this.getFacetIds(v),
                     facetValueIds: this.getFacetValueIds(v),
-                    collectionIds: v.collections.map(c => c.id.toString()),
+                    collectionIds: v.collections.map((c) => c.id.toString()),
                 }),
         );
         await this.queue.push(() => this.connection.getRepository(SearchIndexItem).save(items));
@@ -344,7 +349,7 @@ export class IndexerController {
      * Remove items from the search index
      */
     private async removeSearchIndexItems(languageCode: LanguageCode, channelId: ID, variantIds: ID[]) {
-        const compositeKeys = variantIds.map(id => ({
+        const compositeKeys = variantIds.map((id) => ({
             productVariantId: id,
             channelId,
             languageCode,

+ 17 - 15
packages/core/src/plugin/default-search-plugin/indexer/search-index.service.ts

@@ -34,15 +34,17 @@ export class SearchIndexService {
         return this.jobService.createJob({
             name: 'reindex',
             singleInstance: true,
-            work: async reporter => {
+            work: async (reporter) => {
                 Logger.verbose(`sending ReindexMessage`);
-                this.workerService.send(new ReindexMessage({ ctx })).subscribe(this.createObserver(reporter));
+                this.workerService
+                    .send(new ReindexMessage({ ctx: ctx.serialize() }))
+                    .subscribe(this.createObserver(reporter));
             },
         });
     }
 
     updateProduct(ctx: RequestContext, product: Product) {
-        const data = { ctx, productId: product.id };
+        const data = { ctx: ctx.serialize(), productId: product.id };
         return this.createShortWorkerJob(new UpdateProductMessage(data), {
             entity: 'Product',
             id: product.id,
@@ -50,8 +52,8 @@ export class SearchIndexService {
     }
 
     updateVariants(ctx: RequestContext, variants: ProductVariant[]) {
-        const variantIds = variants.map(v => v.id);
-        const data = { ctx, variantIds };
+        const variantIds = variants.map((v) => v.id);
+        const data = { ctx: ctx.serialize(), variantIds };
         return this.createShortWorkerJob(new UpdateVariantMessage(data), {
             entity: 'ProductVariant',
             ids: variantIds,
@@ -59,7 +61,7 @@ export class SearchIndexService {
     }
 
     deleteProduct(ctx: RequestContext, product: Product) {
-        const data = { ctx, productId: product.id };
+        const data = { ctx: ctx.serialize(), productId: product.id };
         return this.createShortWorkerJob(new DeleteProductMessage(data), {
             entity: 'Product',
             id: product.id,
@@ -67,8 +69,8 @@ export class SearchIndexService {
     }
 
     deleteVariant(ctx: RequestContext, variants: ProductVariant[]) {
-        const variantIds = variants.map(v => v.id);
-        const data = { ctx, variantIds };
+        const variantIds = variants.map((v) => v.id);
+        const data = { ctx: ctx.serialize(), variantIds };
         return this.createShortWorkerJob(new DeleteVariantMessage(data), {
             entity: 'ProductVariant',
             id: variantIds,
@@ -81,24 +83,24 @@ export class SearchIndexService {
             metadata: {
                 variantIds: ids,
             },
-            work: reporter => {
+            work: (reporter) => {
                 Logger.verbose(`sending UpdateVariantsByIdMessage`);
                 this.workerService
-                    .send(new UpdateVariantsByIdMessage({ ctx, ids }))
+                    .send(new UpdateVariantsByIdMessage({ ctx: ctx.serialize(), ids }))
                     .subscribe(this.createObserver(reporter));
             },
         });
     }
 
     updateAsset(ctx: RequestContext, asset: Asset) {
-        return this.createShortWorkerJob(new UpdateAssetMessage({ ctx, asset }), {
+        return this.createShortWorkerJob(new UpdateAssetMessage({ ctx: ctx.serialize(), asset }), {
             entity: 'Asset',
             id: asset.id,
         });
     }
 
     assignProductToChannel(ctx: RequestContext, productId: ID, channelId: ID) {
-        const data = { ctx, productId, channelId };
+        const data = { ctx: ctx.serialize(), productId, channelId };
         return this.createShortWorkerJob(new AssignProductToChannelMessage(data), {
             entity: 'Product',
             id: productId,
@@ -106,7 +108,7 @@ export class SearchIndexService {
     }
 
     removeProductFromChannel(ctx: RequestContext, productId: ID, channelId: ID) {
-        const data = { ctx, productId, channelId };
+        const data = { ctx: ctx.serialize(), productId, channelId };
         return this.createShortWorkerJob(new RemoveProductFromChannelMessage(data), {
             entity: 'Product',
             id: productId,
@@ -120,10 +122,10 @@ export class SearchIndexService {
         return this.jobService.createJob({
             name: 'update-index',
             metadata,
-            work: reporter => {
+            work: (reporter) => {
                 this.workerService.send(message).subscribe({
                     complete: () => reporter.complete(true),
-                    error: err => {
+                    error: (err) => {
                         Logger.error(err);
                         reporter.complete(false);
                     },

+ 7 - 7
packages/core/src/plugin/default-search-plugin/types.ts

@@ -1,6 +1,6 @@
 import { ID } from '@vendure/common/lib/shared-types';
 
-import { RequestContext } from '../../api/common/request-context';
+import { SerializedRequestContext } from '../../api/common/request-context';
 import { Asset } from '../../entity/asset/asset.entity';
 import { WorkerMessage } from '../../worker/types';
 
@@ -11,31 +11,31 @@ export interface ReindexMessageResponse {
 }
 
 export type UpdateProductMessageData = {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     productId: ID;
 };
 
 export type UpdateVariantMessageData = {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     variantIds: ID[];
 };
 
 export interface UpdateVariantsByIdMessageData {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     ids: ID[];
 }
 export interface UpdateAssetMessageData {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     asset: Asset;
 }
 
 export interface ProductChannelMessageData {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     productId: ID;
     channelId: ID;
 }
 
-export class ReindexMessage extends WorkerMessage<{ ctx: RequestContext }, ReindexMessageResponse> {
+export class ReindexMessage extends WorkerMessage<{ ctx: SerializedRequestContext }, ReindexMessageResponse> {
     static readonly pattern = 'Reindex';
 }
 export class UpdateVariantMessage extends WorkerMessage<UpdateVariantMessageData, boolean> {

+ 15 - 15
packages/elasticsearch-plugin/src/elasticsearch-index.service.ts

@@ -34,17 +34,17 @@ export class ElasticsearchIndexService {
         return this.jobService.createJob({
             name: 'reindex',
             singleInstance: true,
-            work: async reporter => {
+            work: async (reporter) => {
                 Logger.verbose(`sending reindex message`);
                 this.workerService
-                    .send(new ReindexMessage({ ctx, dropIndices }))
+                    .send(new ReindexMessage({ ctx: ctx.serialize(), dropIndices }))
                     .subscribe(this.createObserver(reporter));
             },
         });
     }
 
     updateProduct(ctx: RequestContext, product: Product) {
-        const data = { ctx, productId: product.id };
+        const data = { ctx: ctx.serialize(), productId: product.id };
         return this.createShortWorkerJob(new UpdateProductMessage(data), {
             entity: 'Product',
             id: product.id,
@@ -52,8 +52,8 @@ export class ElasticsearchIndexService {
     }
 
     updateVariants(ctx: RequestContext, variants: ProductVariant[]) {
-        const variantIds = variants.map(v => v.id);
-        const data = { ctx, variantIds };
+        const variantIds = variants.map((v) => v.id);
+        const data = { ctx: ctx.serialize(), variantIds };
         return this.createShortWorkerJob(new UpdateVariantMessage(data), {
             entity: 'ProductVariant',
             ids: variantIds,
@@ -61,7 +61,7 @@ export class ElasticsearchIndexService {
     }
 
     deleteProduct(ctx: RequestContext, product: Product) {
-        const data = { ctx, productId: product.id };
+        const data = { ctx: ctx.serialize(), productId: product.id };
         return this.createShortWorkerJob(new DeleteProductMessage(data), {
             entity: 'Product',
             id: product.id,
@@ -69,8 +69,8 @@ export class ElasticsearchIndexService {
     }
 
     deleteVariant(ctx: RequestContext, variants: ProductVariant[]) {
-        const variantIds = variants.map(v => v.id);
-        const data = { ctx, variantIds };
+        const variantIds = variants.map((v) => v.id);
+        const data = { ctx: ctx.serialize(), variantIds };
         return this.createShortWorkerJob(new DeleteVariantMessage(data), {
             entity: 'ProductVariant',
             id: variantIds,
@@ -78,7 +78,7 @@ export class ElasticsearchIndexService {
     }
 
     assignProductToChannel(ctx: RequestContext, product: Product, channelId: ID) {
-        const data = { ctx, productId: product.id, channelId };
+        const data = { ctx: ctx.serialize(), productId: product.id, channelId };
         return this.createShortWorkerJob(new AssignProductToChannelMessage(data), {
             entity: 'Product',
             id: product.id,
@@ -86,7 +86,7 @@ export class ElasticsearchIndexService {
     }
 
     removeProductFromChannel(ctx: RequestContext, product: Product, channelId: ID) {
-        const data = { ctx, productId: product.id, channelId };
+        const data = { ctx: ctx.serialize(), productId: product.id, channelId };
         return this.createShortWorkerJob(new RemoveProductFromChannelMessage(data), {
             entity: 'Product',
             id: product.id,
@@ -99,17 +99,17 @@ export class ElasticsearchIndexService {
             metadata: {
                 variantIds: ids,
             },
-            work: reporter => {
+            work: (reporter) => {
                 Logger.verbose(`sending UpdateVariantsByIdMessage`);
                 this.workerService
-                    .send(new UpdateVariantsByIdMessage({ ctx, ids }))
+                    .send(new UpdateVariantsByIdMessage({ ctx: ctx.serialize(), ids }))
                     .subscribe(this.createObserver(reporter));
             },
         });
     }
 
     updateAsset(ctx: RequestContext, asset: Asset) {
-        const data = { ctx, asset };
+        const data = { ctx: ctx.serialize(), asset };
         return this.createShortWorkerJob(new UpdateAssetMessage(data), {
             entity: 'Asset',
             id: asset.id,
@@ -123,10 +123,10 @@ export class ElasticsearchIndexService {
         return this.jobService.createJob({
             name: 'update-index',
             metadata,
-            work: reporter => {
+            work: (reporter) => {
                 this.workerService.send(message).subscribe({
                     complete: () => reporter.complete(true),
-                    error: err => {
+                    error: (err) => {
                         Logger.error(err);
                         reporter.complete(false);
                     },

+ 54 - 43
packages/elasticsearch-plugin/src/indexer.controller.ts

@@ -98,7 +98,7 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         ctx: rawContext,
         productId,
     }: UpdateProductMessage['data']): Observable<UpdateProductMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
         return asyncObservable(async () => {
             await this.updateProductInternal(ctx, productId, ctx.channelId);
             return true;
@@ -113,11 +113,14 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         ctx: rawContext,
         productId,
     }: DeleteProductMessage['data']): Observable<DeleteProductMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
         return asyncObservable(async () => {
             await this.deleteProductInternal(productId, ctx.channelId);
             const variants = await this.productVariantService.getVariantsByProductId(ctx, productId);
-            await this.deleteVariantsInternal(variants.map(v => v.id), ctx.channelId);
+            await this.deleteVariantsInternal(
+                variants.map((v) => v.id),
+                ctx.channelId,
+            );
             return true;
         });
     }
@@ -131,11 +134,15 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         productId,
         channelId,
     }: AssignProductToChannelMessage['data']): Observable<AssignProductToChannelMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
         return asyncObservable(async () => {
             await this.updateProductInternal(ctx, productId, channelId);
             const variants = await this.productVariantService.getVariantsByProductId(ctx, productId);
-            await this.updateVariantsInternal(ctx, variants.map(v => v.id), channelId);
+            await this.updateVariantsInternal(
+                ctx,
+                variants.map((v) => v.id),
+                channelId,
+            );
             return true;
         });
     }
@@ -149,11 +156,14 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         productId,
         channelId,
     }: RemoveProductFromChannelMessage['data']): Observable<RemoveProductFromChannelMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
         return asyncObservable(async () => {
             await this.deleteProductInternal(productId, channelId);
             const variants = await this.productVariantService.getVariantsByProductId(ctx, productId);
-            await this.deleteVariantsInternal(variants.map(v => v.id), channelId);
+            await this.deleteVariantsInternal(
+                variants.map((v) => v.id),
+                channelId,
+            );
             return true;
         });
     }
@@ -166,7 +176,7 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         ctx: rawContext,
         variantIds,
     }: UpdateVariantMessage['data']): Observable<UpdateVariantMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
         return asyncObservable(async () => {
             return this.asyncQueue.push(async () => {
                 await this.updateVariantsInternal(ctx, variantIds, ctx.channelId);
@@ -180,12 +190,12 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         ctx: rawContext,
         variantIds,
     }: DeleteVariantMessage['data']): Observable<DeleteVariantMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
         return asyncObservable(async () => {
             const variants = await this.connection
                 .getRepository(ProductVariant)
                 .findByIds(variantIds, { relations: ['product'] });
-            const productIds = unique(variants.map(v => v.product.id));
+            const productIds = unique(variants.map((v) => v.product.id));
             for (const productId of productIds) {
                 await this.updateProductInternal(ctx, productId, ctx.channelId);
             }
@@ -199,10 +209,10 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         ctx: rawContext,
         ids,
     }: UpdateVariantsByIdMessage['data']): Observable<UpdateVariantsByIdMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
         const { batchSize } = this.options;
 
-        return asyncObservable(async observer => {
+        return asyncObservable(async (observer) => {
             return this.asyncQueue.push(async () => {
                 const timeStart = Date.now();
                 if (ids.length) {
@@ -254,10 +264,10 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         ctx: rawContext,
         dropIndices,
     }: ReindexMessage['data']): Observable<ReindexMessage['response']> {
-        const ctx = RequestContext.fromObject(rawContext);
+        const ctx = RequestContext.deserialize(rawContext);
         const { batchSize } = this.options;
 
-        return asyncObservable(async observer => {
+        return asyncObservable(async (observer) => {
             return this.asyncQueue.push(async () => {
                 const timeStart = Date.now();
 
@@ -422,20 +432,17 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
         if (updatedVariants.length) {
             // When ProductVariants change, we need to update the corresponding Product index
             // since e.g. price changes must be reflected on the Product level too.
-            const productIdsOfVariants = unique(updatedVariants.map(v => v.productId));
+            const productIdsOfVariants = unique(updatedVariants.map((v) => v.productId));
             for (const variantProductId of productIdsOfVariants) {
                 await this.updateProductInternal(ctx, variantProductId, channelId);
             }
-            const operations = updatedVariants.reduce(
-                (ops, variant) => {
-                    return [
-                        ...ops,
-                        { update: { _id: this.getId(variant.id, channelId) } },
-                        { doc: this.createVariantIndexItem(variant, channelId), doc_as_upsert: true },
-                    ];
-                },
-                [] as Array<BulkOperation | BulkOperationDoc<VariantIndexItem>>,
-            );
+            const operations = updatedVariants.reduce((ops, variant) => {
+                return [
+                    ...ops,
+                    { update: { _id: this.getId(variant.id, channelId) } },
+                    { doc: this.createVariantIndexItem(variant, channelId), doc_as_upsert: true },
+                ];
+            }, [] as Array<BulkOperation | BulkOperationDoc<VariantIndexItem>>);
             Logger.verbose(`Updating ${updatedVariants.length} ProductVariants`, loggerCtx);
             await this.executeBulkOperations(VARIANT_INDEX_NAME, VARIANT_INDEX_TYPE, operations);
         }
@@ -447,16 +454,17 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
             relations: ['variants'],
         });
         if (product) {
-            updatedProductVariants = await this.connection
-                .getRepository(ProductVariant)
-                .findByIds(product.variants.map(v => v.id), {
+            updatedProductVariants = await this.connection.getRepository(ProductVariant).findByIds(
+                product.variants.map((v) => v.id),
+                {
                     relations: variantRelations,
                     where: {
                         deletedAt: null,
                     },
-                });
+                },
+            );
             if (product.enabled === false) {
-                updatedProductVariants.forEach(v => (v.enabled = false));
+                updatedProductVariants.forEach((v) => (v.enabled = false));
             }
         }
         if (updatedProductVariants.length) {
@@ -479,7 +487,7 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
 
     private async deleteVariantsInternal(variantIds: ID[], channelId: ID) {
         Logger.verbose(`Deleting ${variantIds.length} ProductVariants`, loggerCtx);
-        const operations: BulkOperation[] = variantIds.map(id => ({
+        const operations: BulkOperation[] = variantIds.map((id) => ({
             delete: { _id: this.getId(id, channelId) },
         }));
         await this.executeBulkOperations(VARIANT_INDEX_NAME, VARIANT_INDEX_TYPE, operations);
@@ -504,7 +512,7 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
                     `Some errors occurred running bulk operations on ${indexType}! Set logger to "debug" to print all errors.`,
                     loggerCtx,
                 );
-                body.items.forEach(item => {
+                body.items.forEach((item) => {
                     if (item.index) {
                         Logger.debug(JSON.stringify(item.index.error, null, 2), loggerCtx);
                     }
@@ -578,8 +586,8 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
      */
     private hydrateVariants(ctx: RequestContext, variants: ProductVariant[]): ProductVariant[] {
         return variants
-            .map(v => this.productVariantService.applyChannelPriceAndTax(v, ctx))
-            .map(v => translateDeep(v, ctx.languageCode, ['product']));
+            .map((v) => this.productVariantService.applyChannelPriceAndTax(v, ctx))
+            .map((v) => translateDeep(v, ctx.languageCode, ['product']));
     }
 
     private createVariantIndexItem(v: ProductVariant, channelId: ID): VariantIndexItem {
@@ -604,9 +612,9 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
             currencyCode: v.currencyCode,
             description: v.product.description,
             facetIds: this.getFacetIds([v]),
-            channelIds: v.product.channels.map(c => c.id as string),
+            channelIds: v.product.channels.map((c) => c.id as string),
             facetValueIds: this.getFacetValueIds([v]),
-            collectionIds: v.collections.map(c => c.id.toString()),
+            collectionIds: v.collections.map((c) => c.id.toString()),
             enabled: v.enabled && v.product.enabled,
         };
         const customMappings = Object.entries(this.options.customProductVariantMappings);
@@ -618,11 +626,11 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
 
     private createProductIndexItem(variants: ProductVariant[], channelId: ID): ProductIndexItem {
         const first = variants[0];
-        const prices = variants.map(v => v.price);
-        const pricesWithTax = variants.map(v => v.priceWithTax);
+        const prices = variants.map((v) => v.price);
+        const pricesWithTax = variants.map((v) => v.priceWithTax);
         const productAsset = first.product.featuredAsset;
-        const variantAsset = variants.filter(v => v.featuredAsset).length
-            ? variants.filter(v => v.featuredAsset)[0].featuredAsset
+        const variantAsset = variants.filter((v) => v.featuredAsset).length
+            ? variants.filter((v) => v.featuredAsset)[0].featuredAsset
             : null;
         const item: ProductIndexItem = {
             channelId,
@@ -646,9 +654,12 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
             description: first.product.description,
             facetIds: this.getFacetIds(variants),
             facetValueIds: this.getFacetValueIds(variants),
-            collectionIds: variants.reduce((ids, v) => [...ids, ...v.collections.map(c => c.id)], [] as ID[]),
-            channelIds: first.product.channels.map(c => c.id as string),
-            enabled: variants.some(v => v.enabled),
+            collectionIds: variants.reduce(
+                (ids, v) => [...ids, ...v.collections.map((c) => c.id)],
+                [] as ID[],
+            ),
+            channelIds: first.product.channels.map((c) => c.id as string),
+            enabled: variants.some((v) => v.enabled),
         };
 
         const customMappings = Object.entries(this.options.customProductMappings);

+ 7 - 7
packages/elasticsearch-plugin/src/types.ts

@@ -7,7 +7,7 @@ import {
     SearchResult,
 } from '@vendure/common/lib/generated-types';
 import { ID } from '@vendure/common/lib/shared-types';
-import { Asset, RequestContext, WorkerMessage } from '@vendure/core';
+import { Asset, SerializedRequestContext, WorkerMessage } from '@vendure/core';
 
 export type ElasticSearchInput = SearchInput & {
     priceRange?: PriceRange;
@@ -149,32 +149,32 @@ export interface ReindexMessageResponse {
 }
 
 export type ReindexMessageData = {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     dropIndices: boolean;
 };
 
 export type UpdateProductMessageData = {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     productId: ID;
 };
 
 export type UpdateVariantMessageData = {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     variantIds: ID[];
 };
 
 export interface UpdateVariantsByIdMessageData {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     ids: ID[];
 }
 
 export interface ProductChannelMessageData {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     productId: ID;
     channelId: ID;
 }
 export interface UpdateAssetMessageData {
-    ctx: RequestContext;
+    ctx: SerializedRequestContext;
     asset: Asset;
 }
 

+ 33 - 33
packages/email-plugin/src/plugin.spec.ts

@@ -63,16 +63,16 @@ describe('EmailPlugin', () => {
     });
 
     it('setting from, recipient, subject', async () => {
-        const ctx = RequestContext.fromObject({
+        const ctx = RequestContext.deserialize({
             _channel: { code: DEFAULT_CHANNEL_CODE },
             _languageCode: LanguageCode.en,
-        });
+        } as any);
         const handler = new EmailEventListener('test')
             .on(MockEvent)
             .setFrom('"test from" <noreply@test.com>')
             .setRecipient(() => 'test@test.com')
             .setSubject('Hello')
-            .setTemplateVars(event => ({ subjectVar: 'foo' }));
+            .setTemplateVars((event) => ({ subjectVar: 'foo' }));
 
         await initPluginWithHandlers([handler]);
 
@@ -84,15 +84,15 @@ describe('EmailPlugin', () => {
     });
 
     describe('event filtering', () => {
-        const ctx = RequestContext.fromObject({
+        const ctx = RequestContext.deserialize({
             _channel: { code: DEFAULT_CHANNEL_CODE },
             _languageCode: LanguageCode.en,
-        });
+        } as any);
 
         it('single filter', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
-                .filter(event => event.shouldSend === true)
+                .filter((event) => event.shouldSend === true)
                 .setRecipient(() => 'test@test.com')
                 .setFrom('"test from" <noreply@test.com>')
                 .setSubject('test subject');
@@ -111,8 +111,8 @@ describe('EmailPlugin', () => {
         it('multiple filters', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
-                .filter(event => event.shouldSend === true)
-                .filter(event => !!event.ctx.activeUserId)
+                .filter((event) => event.shouldSend === true)
+                .filter((event) => !!event.ctx.activeUserId)
                 .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('test subject');
@@ -123,7 +123,7 @@ describe('EmailPlugin', () => {
             await pause();
             expect(onSend).not.toHaveBeenCalled();
 
-            const ctxWithUser = RequestContext.fromObject({ ...ctx, _session: { user: { id: 42 } } });
+            const ctxWithUser = RequestContext.deserialize({ ...ctx, _session: { user: { id: 42 } } } as any);
 
             eventBus.publish(new MockEvent(ctxWithUser, true));
             await pause();
@@ -133,8 +133,8 @@ describe('EmailPlugin', () => {
         it('with .loadData() after .filter()', async () => {
             const handler = new EmailEventListener('test')
                 .on(MockEvent)
-                .filter(event => event.shouldSend === true)
-                .loadData(context => Promise.resolve('loaded data'))
+                .filter((event) => event.shouldSend === true)
+                .loadData((context) => Promise.resolve('loaded data'))
                 .setRecipient(() => 'test@test.com')
                 .setFrom('"test from" <noreply@test.com>')
                 .setSubject('test subject');
@@ -152,10 +152,10 @@ describe('EmailPlugin', () => {
     });
 
     describe('templateVars', () => {
-        const ctx = RequestContext.fromObject({
+        const ctx = RequestContext.deserialize({
             _channel: { code: DEFAULT_CHANNEL_CODE },
             _languageCode: LanguageCode.en,
-        });
+        } as any);
 
         it('interpolates subject', async () => {
             const handler = new EmailEventListener('test')
@@ -163,7 +163,7 @@ describe('EmailPlugin', () => {
                 .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello {{ subjectVar }}')
-                .setTemplateVars(event => ({ subjectVar: 'foo' }));
+                .setTemplateVars((event) => ({ subjectVar: 'foo' }));
 
             await initPluginWithHandlers([handler]);
 
@@ -178,7 +178,7 @@ describe('EmailPlugin', () => {
                 .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello')
-                .setTemplateVars(event => ({ testVar: 'this is the test var' }));
+                .setTemplateVars((event) => ({ testVar: 'this is the test var' }));
 
             await initPluginWithHandlers([handler]);
 
@@ -198,7 +198,7 @@ describe('EmailPlugin', () => {
                 .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello')
-                .setTemplateVars(event => ({ order: new Order({ subTotal: 123 }) }));
+                .setTemplateVars((event) => ({ order: new Order({ subTotal: 123 }) }));
 
             await initPluginWithHandlers([handler]);
 
@@ -273,10 +273,10 @@ describe('EmailPlugin', () => {
     });
 
     describe('handlebars helpers', () => {
-        const ctx = RequestContext.fromObject({
+        const ctx = RequestContext.deserialize({
             _channel: { code: DEFAULT_CHANNEL_CODE },
             _languageCode: LanguageCode.en,
-        });
+        } as any);
 
         it('formateDate', async () => {
             const handler = new EmailEventListener('test-helpers')
@@ -284,7 +284,7 @@ describe('EmailPlugin', () => {
                 .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello')
-                .setTemplateVars(event => ({ myDate: new Date('2020-01-01T10:00:00.000Z'), myPrice: 0 }));
+                .setTemplateVars((event) => ({ myDate: new Date('2020-01-01T10:00:00.000Z'), myPrice: 0 }));
 
             await initPluginWithHandlers([handler]);
 
@@ -299,7 +299,7 @@ describe('EmailPlugin', () => {
                 .setFrom('"test from" <noreply@test.com>')
                 .setRecipient(() => 'test@test.com')
                 .setSubject('Hello')
-                .setTemplateVars(event => ({ myDate: new Date(), myPrice: 123 }));
+                .setTemplateVars((event) => ({ myDate: new Date(), myPrice: 123 }));
 
             await initPluginWithHandlers([handler]);
 
@@ -310,10 +310,10 @@ describe('EmailPlugin', () => {
     });
 
     describe('multiple configs', () => {
-        const ctx = RequestContext.fromObject({
+        const ctx = RequestContext.deserialize({
             _channel: { code: DEFAULT_CHANNEL_CODE },
             _languageCode: LanguageCode.en,
-        });
+        } as any);
 
         it('additional LanguageCode', async () => {
             const handler = new EmailEventListener('test')
@@ -331,13 +331,13 @@ describe('EmailPlugin', () => {
 
             await initPluginWithHandlers([handler]);
 
-            const ctxTa = RequestContext.fromObject({ ...ctx, _languageCode: LanguageCode.ta });
+            const ctxTa = RequestContext.deserialize({ ...ctx, _languageCode: LanguageCode.ta } as any);
             eventBus.publish(new MockEvent(ctxTa, true));
             await pause();
             expect(onSend.mock.calls[0][0].subject).toBe('Hello, Test!');
             expect(onSend.mock.calls[0][0].body).toContain('Default body.');
 
-            const ctxDe = RequestContext.fromObject({ ...ctx, _languageCode: LanguageCode.de });
+            const ctxDe = RequestContext.deserialize({ ...ctx, _languageCode: LanguageCode.de } as any);
             eventBus.publish(new MockEvent(ctxDe, true));
             await pause();
             expect(onSend.mock.calls[1][0].subject).toBe('Servus, Test!');
@@ -356,16 +356,16 @@ describe('EmailPlugin', () => {
                 .setFrom('"test from" <noreply@test.com>')
                 .setSubject('Hello, {{ testData }}!')
                 .setRecipient(() => 'test@test.com')
-                .setTemplateVars(event => ({ testData: event.data }));
+                .setTemplateVars((event) => ({ testData: event.data }));
 
             await initPluginWithHandlers([handler]);
 
             eventBus.publish(
                 new MockEvent(
-                    RequestContext.fromObject({
+                    RequestContext.deserialize({
                         _channel: { code: DEFAULT_CHANNEL_CODE },
                         _languageCode: LanguageCode.en,
-                    }),
+                    } as any),
                     true,
                 ),
             );
@@ -384,16 +384,16 @@ describe('EmailPlugin', () => {
                     const service = inject(MockService);
                     return service.someAsyncMethod();
                 })
-                .setTemplateVars(event => ({ testData: event.data }));
+                .setTemplateVars((event) => ({ testData: event.data }));
 
             await initPluginWithHandlers([handler]);
 
             eventBus.publish(
                 new MockEvent(
-                    RequestContext.fromObject({
+                    RequestContext.deserialize({
                         _channel: { code: DEFAULT_CHANNEL_CODE },
                         _languageCode: LanguageCode.en,
-                    }),
+                    } as any),
                     true,
                 ),
             );
@@ -412,10 +412,10 @@ describe('EmailPlugin', () => {
             });
         });
 
-        const ctx = RequestContext.fromObject({
+        const ctx = RequestContext.deserialize({
             _channel: { code: DEFAULT_CHANNEL_CODE },
             _languageCode: LanguageCode.en,
-        });
+        } as any);
 
         const order = ({
             code: 'ABCDE',
@@ -458,7 +458,7 @@ describe('EmailPlugin', () => {
     });
 });
 
-const pause = () => new Promise(resolve => setTimeout(resolve, 100));
+const pause = () => new Promise((resolve) => setTimeout(resolve, 100));
 
 class MockEvent extends VendureEvent {
     constructor(public ctx: RequestContext, public shouldSend: boolean) {