-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): Implement caching for FacetValueChecker
Relates to #3043 BREAKING CHANGE: If you are using the `FacetValueChecker` utility class, you should update your code to get it via the `Injector` rather than directly instantiating it. Existing code _will_ still work without changes, but by updating you will see improved performance due to new caching techniques. ```diff - facetValueChecker = new FacetValueChecker(injector.get(TransactionalConnection)); + facetValueChecker = injector.get(FacetValueChecker); ```
- Loading branch information
1 parent
489c9c0
commit 3603b11
Showing
8 changed files
with
158 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 0 additions & 79 deletions
79
packages/core/src/config/promotion/utils/facet-value-checker.ts
This file was deleted.
Oops, something went wrong.
149 changes: 149 additions & 0 deletions
149
packages/core/src/service/helpers/facet-value-checker/facet-value-checker.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { Injectable, OnModuleInit } from '@nestjs/common'; | ||
import { UpdateProductInput, UpdateProductVariantInput } from '@vendure/common/lib/generated-types'; | ||
import { ID } from '@vendure/common/lib/shared-types'; | ||
import { unique } from '@vendure/common/lib/unique'; | ||
import ms from 'ms'; | ||
import { filter } from 'rxjs/operators'; | ||
|
||
import { RequestContext } from '../../../api/index'; | ||
import { CacheService } from '../../../cache/cache.service'; | ||
import { idsAreEqual } from '../../../common/utils'; | ||
import { TransactionalConnection } from '../../../connection/transactional-connection'; | ||
import { OrderLine } from '../../../entity/order-line/order-line.entity'; | ||
import { ProductVariant } from '../../../entity/product-variant/product-variant.entity'; | ||
import { EventBus, ProductEvent, ProductVariantEvent } from '../../../event-bus/index'; | ||
|
||
/** | ||
* @description | ||
* The FacetValueChecker is a helper class used to determine whether a given OrderLine consists | ||
* of ProductVariants containing the given FacetValues. | ||
* | ||
* @example | ||
* ```ts | ||
* import { FacetValueChecker, LanguageCode, PromotionCondition, TransactionalConnection } from '\@vendure/core'; | ||
* | ||
* let facetValueChecker: FacetValueChecker; | ||
* | ||
* export const hasFacetValues = new PromotionCondition({ | ||
* code: 'at_least_n_with_facets', | ||
* description: [ | ||
* { languageCode: LanguageCode.en, value: 'Buy at least { minimum } products with the given facets' }, | ||
* ], | ||
* args: { | ||
* minimum: { type: 'int' }, | ||
* facets: { type: 'ID', list: true, ui: { component: 'facet-value-form-input' } }, | ||
* }, | ||
* init(injector) { | ||
* facetValueChecker = injector.get(FacetValueChecker); | ||
* }, | ||
* async check(ctx, order, args) { | ||
* let matches = 0; | ||
* for (const line of order.lines) { | ||
* if (await facetValueChecker.hasFacetValues(line, args.facets)) { | ||
* matches += line.quantity; | ||
* } | ||
* } | ||
* return args.minimum <= matches; | ||
* }, | ||
* }); | ||
* ``` | ||
* | ||
* @docsCategory Promotions | ||
*/ | ||
@Injectable() | ||
export class FacetValueChecker implements OnModuleInit { | ||
/** | ||
* @deprecated | ||
* Do not directly instantiate. Use the injector to get an instance: | ||
* | ||
* ```ts | ||
* facetValueChecker = injector.get(FacetValueChecker); | ||
* ``` | ||
* @param connection | ||
*/ | ||
constructor( | ||
private connection: TransactionalConnection, | ||
private cacheService?: CacheService, | ||
private eventBus?: EventBus, | ||
) {} | ||
|
||
onModuleInit(): any { | ||
this.eventBus | ||
?.ofType(ProductEvent) | ||
.pipe(filter(event => event.type === 'updated')) | ||
.subscribe(async event => { | ||
if ((event.input as UpdateProductInput).facetValueIds) { | ||
const variantIds = await this.connection.rawConnection | ||
.getRepository(ProductVariant) | ||
.createQueryBuilder('variant') | ||
.select('variant.id', 'id') | ||
.where('variant.productId = :prodId', { prodId: event.product.id }) | ||
.getRawMany() | ||
.then(result => result.map(r => r.id)); | ||
|
||
if (variantIds.length) { | ||
await this.deleteVariantIdsFromCache(variantIds); | ||
} | ||
} | ||
}); | ||
|
||
this.eventBus | ||
?.ofType(ProductVariantEvent) | ||
.pipe(filter(event => event.type === 'updated')) | ||
.subscribe(async event => { | ||
const updatedVariantIds: ID[] = []; | ||
if (Array.isArray(event.input)) { | ||
for (const input of event.input) { | ||
if ((input as UpdateProductVariantInput).facetValueIds) { | ||
updatedVariantIds.push((input as UpdateProductVariantInput).id); | ||
} | ||
} | ||
} | ||
if (updatedVariantIds.length > 0) { | ||
await this.deleteVariantIdsFromCache(updatedVariantIds); | ||
} | ||
}); | ||
} | ||
|
||
private deleteVariantIdsFromCache(variantIds: ID[]) { | ||
return Promise.all(variantIds.map(id => this.cacheService?.delete(this.getCacheKey(id)))); | ||
} | ||
|
||
/** | ||
* @description | ||
* Checks a given {@link OrderLine} against the facetValueIds and returns | ||
* `true` if the associated {@link ProductVariant} & {@link Product} together | ||
* have *all* the specified {@link FacetValue}s. | ||
*/ | ||
async hasFacetValues(orderLine: OrderLine, facetValueIds: ID[], ctx?: RequestContext): Promise<boolean> { | ||
const variantId = orderLine.productVariant.id; | ||
const cacheKey = this.getCacheKey(variantId); | ||
let variantFacetValueIds = await this.cacheService?.get<ID[]>(cacheKey); | ||
if (!variantFacetValueIds) { | ||
const variant = await this.connection | ||
.getRepository(ctx, ProductVariant) | ||
.findOne({ | ||
where: { id: orderLine.productVariant.id }, | ||
relations: ['product', 'product.facetValues', 'facetValues'], | ||
loadEagerRelations: false, | ||
}) | ||
.then(result => result ?? undefined); | ||
if (!variant) { | ||
variantFacetValueIds = []; | ||
} else { | ||
variantFacetValueIds = unique( | ||
[...variant.facetValues, ...variant.product.facetValues].map(fv => fv.id), | ||
); | ||
} | ||
await this.cacheService?.set(cacheKey, variantFacetValueIds, { ttl: ms('1w') }); | ||
} | ||
return facetValueIds.reduce( | ||
(result, id) => result && !!(variantFacetValueIds ?? []).find(_id => idsAreEqual(_id, id)), | ||
true as boolean, | ||
); | ||
} | ||
|
||
private getCacheKey(variantId: ID) { | ||
return `FacetValueChecker.${variantId}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters