feat(medusa): Migrate SearchService to TS + add SearchInterface (#1922)

This commit is contained in:
Adrien de Peretti
2022-07-27 20:58:44 +02:00
committed by GitHub
parent 6014872a72
commit 204dd23a39
7 changed files with 234 additions and 93 deletions

View File

@@ -8,3 +8,4 @@ export * from "./notification-service"
export * from "./price-selection-strategy"
export * from "./models/base-entity"
export * from "./models/soft-deletable-entity"
export * from "./search-service"

View File

@@ -0,0 +1,127 @@
import { TransactionBaseService } from "./transaction-base-service"
import { SearchService } from "medusa-interfaces"
export interface ISearchService<T extends TransactionBaseService<never>> {
options: Record<string, unknown>
/**
* Used to create an index
* @param indexName the index name
* @param options the options
* @return returns response from search engine provider
*/
createIndex(indexName: string, options: unknown): unknown
/**
* Used to get an index
* @param indexName - the index name.
* @return returns response from search engine provider
*/
getIndex(indexName: string): unknown
/**
* Used to index documents by the search engine provider
* @param indexName the index name
* @param documents documents array to be indexed
* @param type of documents to be added (e.g: products, regions, orders, etc)
* @return returns response from search engine provider
*/
addDocuments(indexName: string, documents: unknown, type: string): unknown
/**
* Used to replace documents
* @param indexName the index name.
* @param documents array of document objects that will replace existing documents
* @param type type of documents to be replaced (e.g: products, regions, orders, etc)
* @return returns response from search engine provider
*/
replaceDocuments(indexName: string, documents: unknown, type: string): unknown
/**
* Used to delete document
* @param indexName the index name
* @param document_id the id of the document
* @return returns response from search engine provider
*/
deleteDocument(indexName: string, document_id: string | number): unknown
/**
* Used to delete all documents
* @param indexName the index name
* @return returns response from search engine provider
*/
deleteAllDocuments(indexName: string): unknown
/**
* Used to search for a document in an index
* @param indexName the index name
* @param query the search query
* @param options
* - any options passed to the request object other than the query and indexName
* - additionalOptions contain any provider specific options
* @return returns response from search engine provider
*/
search(indexName: string, query: string | null, options: unknown): unknown
/**
* Used to update the settings of an index
* @param indexName the index name
* @param settings settings object
* @return returns response from search engine provider
*/
updateSettings(indexName: string, settings: unknown): unknown
}
export abstract class AbstractSearchService<
T extends TransactionBaseService<never>
>
extends TransactionBaseService<T>
implements ISearchService<T>
{
abstract readonly isDefault
protected readonly options_: Record<string, unknown>
get options(): Record<string, unknown> {
return this.options_
}
protected constructor(container, options) {
super(container, options)
this.options_ = options
}
abstract createIndex(indexName: string, options: unknown): unknown
abstract getIndex(indexName: string): unknown
abstract addDocuments(
indexName: string,
documents: unknown,
type: string
): unknown
abstract replaceDocuments(
indexName: string,
documents: unknown,
type: string
): unknown
abstract deleteDocument(
indexName: string,
document_id: string | number
): unknown
abstract deleteAllDocuments(indexName: string): unknown
abstract search(
indexName: string,
query: string | null,
options: unknown
): unknown
abstract updateSettings(indexName: string, settings: unknown): unknown
}
export function isSearchService(obj: unknown): boolean {
return obj instanceof AbstractSearchService || obj instanceof SearchService
}

View File

@@ -1,37 +1,37 @@
import glob from "glob"
import { aliasTo, asClass, asFunction, asValue } from "awilix"
import { Express } from "express"
import { EntitySchema } from "typeorm"
import fs from "fs"
import { sync as existsSync } from "fs-exists-cached"
import glob from "glob"
import _ from "lodash"
import { createRequireFromPath } from "medusa-core-utils"
import {
BaseService as LegacyBaseService,
PaymentService,
FulfillmentService,
FileService,
FulfillmentService,
OauthService,
SearchService,
PaymentService,
} from "medusa-interfaces"
import { createRequireFromPath } from "medusa-core-utils"
import _ from "lodash"
import path from "path"
import fs from "fs"
import { asValue, asClass, asFunction, aliasTo } from "awilix"
import { sync as existsSync } from "fs-exists-cached"
import { EntitySchema } from "typeorm"
import {
AbstractTaxService,
isBatchJobStrategy,
isFileService,
isNotificationService,
isPriceSelectionStrategy,
isSearchService,
isTaxCalculationStrategy,
TransactionBaseService as BaseService,
isNotificationService,
isBatchJobStrategy,
isPriceSelectionStrategy,
} from "../interfaces"
import formatRegistrationName from "../utils/format-registration-name"
import { MiddlewareService } from "../services"
import {
ClassConstructor,
ConfigModule,
Logger,
MedusaContainer,
} from "../types/global"
import { MiddlewareService } from "../services"
import formatRegistrationName from "../utils/format-registration-name"
import logger from "./logger"
type Options = {
@@ -418,7 +418,7 @@ export async function registerServices(
),
[`fileService`]: aliasTo(name),
})
} else if (loaded.prototype instanceof SearchService) {
} else if (isSearchService(loaded.prototype)) {
// Add the service directly to the container in order to make simple
// resolution if we already know which search provider we need to use
container.register({

View File

@@ -1,7 +1,7 @@
import { MedusaContainer } from "../types/global"
import DefaultSearchService from "../services/search"
import { Logger } from "../types/global"
import { EventBusService } from "../services"
import { AbstractSearchService } from "../interfaces"
export const SEARCH_INDEX_EVENT = "SEARCH_INDEX_EVENT"
@@ -21,7 +21,8 @@ export default async ({
}: {
container: MedusaContainer
}): Promise<void> => {
const searchService = container.resolve<DefaultSearchService>("searchService")
const searchService =
container.resolve<AbstractSearchService<never>>("searchService")
const logger = container.resolve<Logger>("logger")
if (searchService.isDefault) {
logger.warn(

View File

@@ -1,71 +0,0 @@
import { SearchService } from "medusa-interfaces"
/**
* Default class that implements SearchService but provides stuv implementation for all methods
* @extends SearchService
*/
class DefaultSearchService extends SearchService {
constructor(container, options) {
super()
this.isDefault = true
this.logger_ = container.logger
this.options_ = options
}
get options() {
return this.options_
}
createIndex(indexName, options) {
this.logger_.warn(
"This is an empty method: createIndex must be overridden by a child class"
)
}
getIndex(indexName) {
this.logger_.warn(
"This is an empty method: getIndex must be overridden by a child class"
)
}
addDocuments(indexName, documents, type) {
this.logger_.warn(
"This is an empty method: addDocuments must be overridden by a child class"
)
}
replaceDocuments(indexName, documents, type) {
this.logger_.warn(
"This is an empty method: replaceDocuments must be overridden by a child class"
)
}
deleteDocument(indexName, document_id) {
this.logger_.warn(
"This is an empty method: deleteDocument must be overridden by a child class"
)
}
deleteAllDocuments(indexName) {
this.logger_.warn(
"This is an empty method: deleteAllDocuments must be overridden by a child class"
)
}
search(indexName, query, options) {
this.logger_.warn(
"This is an empty method: search must be overridden a the child class"
)
return { hits: [] }
}
updateSettings(indexName, settings) {
this.logger_.warn(
"This is an empty method: updateSettings must be overridden by a child class"
)
}
}
export default DefaultSearchService

View File

@@ -0,0 +1,83 @@
import { EntityManager } from "typeorm"
import { AbstractSearchService } from "../interfaces/search-service"
import { Logger } from "../types/global"
type InjectedDependencies = {
logger: Logger
manager: EntityManager
}
export default class DefaultSearchService extends AbstractSearchService<DefaultSearchService> {
isDefault = true
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly logger_: Logger
protected readonly options_: Record<string, unknown>
constructor({ logger, manager }: InjectedDependencies, options) {
super(
{
logger,
},
options
)
this.options_ = options
this.logger_ = logger
this.manager_ = manager
}
createIndex(indexName: string, options: unknown): void {
this.logger_.warn(
"This is an empty method: createIndex must be overridden by a child class"
)
}
getIndex(indexName: string): void {
this.logger_.warn(
"This is an empty method: getIndex must be overridden by a child class"
)
}
addDocuments(indexName: string, documents: unknown, type: string): void {
this.logger_.warn(
"This is an empty method: addDocuments must be overridden by a child class"
)
}
replaceDocuments(indexName: string, documents: unknown, type: string): void {
this.logger_.warn(
"This is an empty method: replaceDocuments must be overridden by a child class"
)
}
deleteDocument(indexName: string, document_id: string | number): void {
this.logger_.warn(
"This is an empty method: deleteDocument must be overridden by a child class"
)
}
deleteAllDocuments(indexName: string): void {
this.logger_.warn(
"This is an empty method: deleteAllDocuments must be overridden by a child class"
)
}
search(
indexName: string,
query: unknown,
options: unknown
): { hits: unknown[] } {
this.logger_.warn(
"This is an empty method: search must be overridden a the child class"
)
return { hits: [] }
}
updateSettings(indexName: string, settings: unknown): void {
this.logger_.warn(
"This is an empty method: updateSettings must be overridden by a child class"
)
}
}

View File

@@ -3,17 +3,17 @@ import { SEARCH_INDEX_EVENT } from "../loaders/search-index"
import ProductService from "../services/product"
import { indexTypes } from "medusa-core-utils"
import { Product } from "../models"
import { SearchService } from "../services"
import { ISearchService } from "../interfaces"
type InjectedDependencies = {
eventBusService: EventBusService
searchService: SearchService
searchService: ISearchService<never>
productService: ProductService
}
class SearchIndexingSubscriber {
private readonly eventBusService_: EventBusService
private readonly searchService_: SearchService
private readonly searchService_: ISearchService<never>
private readonly productService_: ProductService
constructor({
@@ -29,7 +29,7 @@ class SearchIndexingSubscriber {
}
indexDocuments = async (): Promise<void> => {
const TAKE = this.searchService_?.options?.batch_size ?? 1000
const TAKE = (this.searchService_?.options?.batch_size as number) ?? 1000
let hasMore = true
let lastSeenId = ""