@vendure/core
Version:
A modern, headless ecommerce framework
232 lines • 11.9 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var DefaultSearchPlugin_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultSearchPlugin = void 0;
const core_1 = require("@nestjs/core");
const operators_1 = require("rxjs/operators");
const typeorm_1 = require("typeorm");
const common_1 = require("../../common");
const utils_1 = require("../../common/utils");
const event_bus_1 = require("../../event-bus/event-bus");
const asset_event_1 = require("../../event-bus/events/asset-event");
const collection_modification_event_1 = require("../../event-bus/events/collection-modification-event");
const product_channel_event_1 = require("../../event-bus/events/product-channel-event");
const product_event_1 = require("../../event-bus/events/product-event");
const product_variant_channel_event_1 = require("../../event-bus/events/product-variant-channel-event");
const product_variant_event_1 = require("../../event-bus/events/product-variant-event");
const tax_rate_modification_event_1 = require("../../event-bus/events/tax-rate-modification-event");
const job_queue_service_1 = require("../../job-queue/job-queue.service");
const plugin_common_module_1 = require("../plugin-common.module");
const vendure_plugin_1 = require("../vendure-plugin");
const stock_movement_event_1 = require("../../event-bus/events/stock-movement-event");
const api_extensions_1 = require("./api/api-extensions");
const fulltext_search_resolver_1 = require("./api/fulltext-search.resolver");
const constants_1 = require("./constants");
const search_index_item_entity_1 = require("./entities/search-index-item.entity");
const fulltext_search_service_1 = require("./fulltext-search.service");
const indexer_controller_1 = require("./indexer/indexer.controller");
const search_index_service_1 = require("./indexer/search-index.service");
const search_job_buffer_service_1 = require("./search-job-buffer/search-job-buffer.service");
/**
* @description
* The DefaultSearchPlugin provides a full-text Product search based on the full-text searching capabilities of the
* underlying database.
*
* The DefaultSearchPlugin is bundled with the `\@vendure/core` package. If you are not using an alternative search
* plugin, then make sure this one is used, otherwise you will not be able to search products via the
* [`search` query](/reference/graphql-api/shop/queries#search).
*
* :::caution
* Note that the quality of the fulltext search capabilities varies depending on the underlying database being used. For example,
* the MySQL & Postgres implementations will typically yield better results than the SQLite implementation.
* :::
*
*
* @example
* ```ts
* import { DefaultSearchPlugin, VendureConfig } from '\@vendure/core';
*
* export const config: VendureConfig = {
* // Add an instance of the plugin to the plugins array
* plugins: [
* DefaultSearchPlugin.init({
* indexStockStatus: true,
* bufferUpdates: true,
* }),
* ],
* };
* ```
*
* @docsCategory DefaultSearchPlugin
*/
let DefaultSearchPlugin = DefaultSearchPlugin_1 = class DefaultSearchPlugin {
/** @internal */
constructor(eventBus, searchIndexService, jobQueueService, moduleRef) {
this.eventBus = eventBus;
this.searchIndexService = searchIndexService;
this.jobQueueService = jobQueueService;
this.moduleRef = moduleRef;
}
static init(options) {
this.options = options;
if (options.indexStockStatus === true) {
this.addStockColumnsToEntity();
}
if (options.indexCurrencyCode) {
this.addCurrencyCodeToEntity();
}
return DefaultSearchPlugin_1;
}
/** @internal */
async onApplicationBootstrap() {
this.eventBus.ofType(product_event_1.ProductEvent).subscribe(event => {
if (event.type === 'deleted') {
return this.searchIndexService.deleteProduct(event.ctx, event.product);
}
else {
return this.searchIndexService.updateProduct(event.ctx, event.product);
}
});
this.eventBus.ofType(product_variant_event_1.ProductVariantEvent).subscribe(event => {
if (event.type === 'deleted') {
return this.searchIndexService.deleteVariant(event.ctx, event.variants);
}
else {
return this.searchIndexService.updateVariants(event.ctx, event.variants);
}
});
this.eventBus.ofType(asset_event_1.AssetEvent).subscribe(event => {
if (event.type === 'updated') {
return this.searchIndexService.updateAsset(event.ctx, event.asset);
}
if (event.type === 'deleted') {
return this.searchIndexService.deleteAsset(event.ctx, event.asset);
}
});
this.eventBus.ofType(product_channel_event_1.ProductChannelEvent).subscribe(event => {
if (event.type === 'assigned') {
return this.searchIndexService.assignProductToChannel(event.ctx, event.product.id, event.channelId);
}
else {
return this.searchIndexService.removeProductFromChannel(event.ctx, event.product.id, event.channelId);
}
});
this.eventBus.ofType(product_variant_channel_event_1.ProductVariantChannelEvent).subscribe(event => {
if (event.type === 'assigned') {
return this.searchIndexService.assignVariantToChannel(event.ctx, event.productVariant.id, event.channelId);
}
else {
return this.searchIndexService.removeVariantFromChannel(event.ctx, event.productVariant.id, event.channelId);
}
});
this.eventBus.ofType(stock_movement_event_1.StockMovementEvent).subscribe(event => {
return this.searchIndexService.updateVariants(event.ctx, event.stockMovements.map(m => m.productVariant));
});
// TODO: Remove this buffering logic because because we have dedicated buffering based on #1137
const collectionModification$ = this.eventBus.ofType(collection_modification_event_1.CollectionModificationEvent);
const closingNotifier$ = collectionModification$.pipe((0, operators_1.debounceTime)(50));
collectionModification$
.pipe((0, operators_1.buffer)(closingNotifier$), (0, operators_1.filter)(events => 0 < events.length), (0, operators_1.map)(events => ({
ctx: events[0].ctx,
ids: events.reduce((ids, e) => [...ids, ...e.productVariantIds], []),
})), (0, operators_1.filter)(e => 0 < e.ids.length))
.subscribe(events => {
return this.searchIndexService.updateVariantsById(events.ctx, events.ids);
});
this.eventBus
.ofType(tax_rate_modification_event_1.TaxRateModificationEvent)
// The delay prevents a "TransactionNotStartedError" (in SQLite/sqljs) by allowing any existing
// transactions to complete before a new job is added to the queue (assuming the SQL-based
// JobQueueStrategy).
// TODO: should be able to remove owing to f0fd6625
.pipe((0, operators_1.delay)(1))
.subscribe(event => {
const defaultTaxZone = event.ctx.channel.defaultTaxZone;
if (defaultTaxZone && (0, utils_1.idsAreEqual)(defaultTaxZone.id, event.taxRate.zone.id)) {
return this.searchIndexService.reindex(event.ctx);
}
});
await this.initSearchStrategy();
}
/** @internal */
async onApplicationShutdown(signal) {
await this.destroySearchStrategy();
}
async initSearchStrategy() {
const injector = new common_1.Injector(this.moduleRef);
const searchService = injector.get(fulltext_search_service_1.FulltextSearchService);
if (typeof searchService.searchStrategy.init === 'function') {
await searchService.searchStrategy.init(injector);
}
}
async destroySearchStrategy() {
const injector = new common_1.Injector(this.moduleRef);
const searchService = injector.get(fulltext_search_service_1.FulltextSearchService);
if (typeof searchService.searchStrategy.destroy === 'function') {
await searchService.searchStrategy.destroy();
}
}
/**
* If the `indexStockStatus` option is set to `true`, we dynamically add a couple of
* columns to the SearchIndexItem entity. This is done in this way to allow us to add
* support for indexing the stock status, while preventing a backwards-incompatible
* schema change.
*/
static addStockColumnsToEntity() {
const instance = new search_index_item_entity_1.SearchIndexItem();
(0, typeorm_1.Column)({ type: 'boolean', default: true })(instance, 'inStock');
(0, typeorm_1.Column)({ type: 'boolean', default: true })(instance, 'productInStock');
}
/**
* If the `indexCurrencyCode` option is set to `true`, we dynamically add
* a column to the SearchIndexItem entity. This is done in this way to allow us to add
* support for indexing on the currency code, while preventing a backwards-incompatible
* schema change.
*/
static addCurrencyCodeToEntity() {
const instance = new search_index_item_entity_1.SearchIndexItem();
(0, typeorm_1.PrimaryColumn)({ type: 'varchar' })(instance, 'currencyCode');
}
};
exports.DefaultSearchPlugin = DefaultSearchPlugin;
DefaultSearchPlugin.options = {};
exports.DefaultSearchPlugin = DefaultSearchPlugin = DefaultSearchPlugin_1 = __decorate([
(0, vendure_plugin_1.VendurePlugin)({
imports: [plugin_common_module_1.PluginCommonModule],
providers: [
fulltext_search_service_1.FulltextSearchService,
search_index_service_1.SearchIndexService,
indexer_controller_1.IndexerController,
search_job_buffer_service_1.SearchJobBufferService,
{ provide: constants_1.PLUGIN_INIT_OPTIONS, useFactory: () => DefaultSearchPlugin.options },
{
provide: constants_1.BUFFER_SEARCH_INDEX_UPDATES,
useFactory: () => DefaultSearchPlugin.options.bufferUpdates === true,
},
],
adminApiExtensions: {
schema: () => DefaultSearchPlugin.options.indexStockStatus === true ? api_extensions_1.stockStatusExtension : undefined,
resolvers: [fulltext_search_resolver_1.AdminFulltextSearchResolver],
},
shopApiExtensions: {
schema: () => DefaultSearchPlugin.options.indexStockStatus === true ? api_extensions_1.stockStatusExtension : undefined,
resolvers: [fulltext_search_resolver_1.ShopFulltextSearchResolver],
},
entities: [search_index_item_entity_1.SearchIndexItem],
compatibility: '>0.0.0',
}),
__metadata("design:paramtypes", [event_bus_1.EventBus,
search_index_service_1.SearchIndexService,
job_queue_service_1.JobQueueService,
core_1.ModuleRef])
], DefaultSearchPlugin);
//# sourceMappingURL=default-search-plugin.js.map