UNPKG

@opra/elastic

Version:

Opra Elastic Search adapter package

418 lines (417 loc) 15.2 kB
import { ResourceNotAvailableError } from '@opra/common'; import { ElasticAdapter } from './elastic-adapter.js'; import { ElasticEntityService } from './elastic-entity-service.js'; /** * @class ElasticCollectionService * @template T - The type of the documents in the collection. */ export class ElasticCollectionService extends ElasticEntityService { /** * Represents a common filter function for a ElasticService. * * @type {FilterInput | Function} */ documentFilter; /** * Represents the default limit value for a certain operation. * * @type {number} */ defaultLimit; /** * Constructs a new instance * * @param {Type | string} dataType - The data type of the array elements. * @param {ElasticCollectionService.Options} [options] - The options for the array service. * @constructor */ constructor(dataType, options) { super(dataType, options); this.documentFilter = options?.documentFilter; this.defaultLimit = options?.defaultLimit || 10; } for(context, overwriteProperties, overwriteContext) { if (overwriteProperties?.documentFilter && this.documentFilter) { overwriteProperties.documentFilter = [ ...(Array.isArray(this.documentFilter) ? this.documentFilter : [this.documentFilter]), ...(Array.isArray(overwriteProperties?.documentFilter) ? overwriteProperties.documentFilter : [overwriteProperties.documentFilter]), ]; } return super.for(context, overwriteProperties, overwriteContext); } /** * Asserts the existence of a resource with the given ID. * Throws a ResourceNotFoundError if the resource does not exist. * * @param {string} id - The ID of the resource to assert. * @param {ElasticEntityService.FindOneOptions} [options] - Optional options for checking the existence. * @returns {Promise<void>} - A Promise that resolves when the resource exists. * @throws {ResourceNotAvailableError} - If the resource does not exist. */ async assert(id, options) { if (!(await this.exists(id, options))) throw new ResourceNotAvailableError(this.getResourceName(), id); } /** * Adds a document to the specified index * * @param {PartialDTO<T>} input - The input data for creating the document. * @param {ElasticEntityService.CreateOptions} [options] - The options for creating the document. * @returns {Promise<PartialDTO<T>>} A promise that resolves to the created document. * @throws {Error} if an unknown error occurs while creating the document. */ async create(input, options) { const command = { crud: 'create', method: 'createOnly', byId: false, input, options, }; command.input._id = command.input._id == null || command.input._id === '' ? this._generateId(command) : command.input._id; return this._executeCommand(command, () => this._create(command)); } /** * Returns the count of documents in the collection based on the provided options. * * @param {ElasticEntityService.CountOptions<T>} options - The options for the count operation. * @return {Promise<number>} - A promise that resolves to the count of documents in the collection. */ async count(options) { const command = { crud: 'read', method: 'count', byId: false, options, }; return this._executeCommand(command, async () => { const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); command.options = { ...command.options, filter }; const r = await this._count(command); return r.count; }); } /** * Deletes a document from the collection. * * @param {string} id - The ID of the document to delete. * @param {ElasticEntityService.DeleteOptions} [options] - Optional delete options. * @return {Promise<number>} - A Promise that resolves to the number of documents deleted. */ async delete(id, options) { const command = { crud: 'delete', method: 'delete', byId: true, documentId: id, options, }; return this._executeCommand(command, async () => { const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); command.options = { ...command.options, filter }; return this._delete(command); }); } /** * Deletes multiple documents from the collection that meet the specified filter criteria. * * @param {ElasticEntityService.DeleteManyOptions} options - The options for the delete operation. * @return {Promise<number>} - A promise that resolves to the number of documents deleted. */ async deleteMany(options) { const command = { crud: 'delete', method: 'deleteMany', byId: false, options, }; return this._executeCommand(command, async () => { const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); command.options = { ...command.options, filter }; return this._deleteMany(command); }); } /** * Checks if an object with the given id exists. * * @param {string} id - The id of the object to check. * @param {ElasticEntityService.FindOneOptions} [options] - The options for the query (optional). * @return {Promise<boolean>} - A Promise that resolves to a boolean indicating whether the object exists or not. */ async exists(id, options) { const command = { crud: 'read', method: 'exists', byId: true, documentId: id, options, }; return this._executeCommand(command, async () => { const request = { ...command.options?.request, terminate_after: 1, size: 0, }; const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); command.options = { ...options, request, filter }; const r = await this._findMany(command); return !!(typeof r.hits.total === 'number' ? r.hits.total : r.hits.total?.value); }); } /** * Checks if an object with the given arguments exists. * * @param {ElasticEntityService.FindOneOptions} [options] - The options for the query (optional). * @return {Promise<boolean>} - A Promise that resolves to a boolean indicating whether the object exists or not. */ async existsOne(options) { const command = { crud: 'read', method: 'exists', byId: false, options, }; return this._executeCommand(command, async () => { const request = { ...command.options?.request, terminate_after: 1, size: 0, }; command.options = { ...options, request }; const r = await this._findMany(command); return !!(typeof r.hits.total === 'number' ? r.hits.total : r.hits.total?.value); }); } /** * */ async findById(id, options) { const command = { crud: 'read', method: 'findById', byId: true, documentId: id, options, }; return this._executeCommand(command, async () => { const documentFilter = await this._getDocumentFilter(command); const filter = ElasticAdapter.prepareFilter([ documentFilter, command.options?.filter, ]); const newCommand = { ...command, limit: 1, options: { ...command.options, filter }, }; const r = await this._findMany(newCommand); if (r.hits.hits?.length) { const outputCodec = this.getOutputCodec('find'); return { _id: r.hits.hits[0]._id, ...outputCodec(r.hits.hits[0]._source), }; } }); } /** * */ async findOne(options) { const command = { crud: 'read', method: 'findOne', byId: false, options, }; return this._executeCommand(command, async () => { const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); const newCommand = { ...command, limit: 1, options: { ...command.options, filter }, }; const r = await this._findMany(newCommand); if (r.hits.hits?.length) { const outputCodec = this.getOutputCodec('find'); return { _id: r.hits.hits[0]._id, ...outputCodec(r.hits.hits[0]._source), }; } }); } /** * */ async findMany(options) { const command = { crud: 'read', method: 'findMany', byId: false, options, }; return this._executeCommand(command, async () => { const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); const limit = command.options?.limit || this.defaultLimit; command.options = { ...command.options, filter, limit }; const r = await this._findMany(command); return r.hits?.hits.map((x) => x._source) || []; }); } async searchRaw(request, options) { const command = { crud: 'read', method: 'searchRaw', byId: false, request, options, }; return this._executeCommand(command, async () => this._searchRaw(command)); } /** * */ async findManyWithCount(options) { const command = { crud: 'read', method: 'findManyWithCount', byId: false, options, }; return this._executeCommand(command, async () => { const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); const limit = command.options?.limit || this.defaultLimit; command.options = { ...command.options, filter, limit, request: { ...command.options?.request, track_total_hits: true }, }; const r = await this._findMany(command); const out = {}; if (r.hits.hits?.length) { const outputCodec = this.getOutputCodec('find'); out.items = r.hits.hits.map((x) => ({ _id: x._id, ...outputCodec(x._source), })); } else out.items = []; if (typeof r.hits.total === 'object') { out.count = r.hits.total.value; out.relation = r.hits.total.relation; } else out.count = r.hits.total || 0; return out; }); } /** * */ async get(id, options) { const out = await this.findById(id, options); if (!out) throw new ResourceNotAvailableError(this.getResourceName(), id); return out; } /** * Updates a document in the collection with the specified ID. * * @param {string} id - The ID of the document to update. * @param {PatchDTO<T>} input - The partial input data to update the document with. * @param {ElasticEntityService.UpdateOneOptions} [options] - The options for updating the document. * @returns {Promise<estypes.UpdateResponse>} - A promise that resolves to the number of documents modified. */ async update(id, input, options) { const command = { crud: 'update', method: 'update', documentId: id, byId: true, input, options, }; return this._executeCommand(command, async () => { const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); command.options = { ...command.options, filter }; return await this._updateMany(command); }); } /** * Updates multiple documents in the collection based on the specified input and options. * * @param {PatchDTO<T>} input - The partial input to update the documents with. * @param {ElasticEntityService.UpdateManyOptions} options - The options for updating the documents. * @return {Promise<number>} - A promise that resolves to the number of documents matched and modified. */ async updateMany(input, options) { const command = { crud: 'update', method: 'updateMany', byId: false, input, options, }; return this._executeCommand(command, async () => { const filter = ElasticAdapter.prepareFilter([ await this._getDocumentFilter(command), command.options?.filter, ]); command.options = { ...command.options, filter }; return this._updateMany(command); }); } /** * Retrieves the common filter used for querying documents. * This method is mostly used for security issues like securing multi-tenant applications. * * @protected * @returns {FilterInput | Promise<FilterInput> | undefined} The common filter or a Promise * that resolves to the common filter, or undefined if not available. */ _getDocumentFilter(command) { const commonFilter = Array.isArray(this.documentFilter) ? this.documentFilter : [this.documentFilter]; const mapped = commonFilter.map(f => typeof f === 'function' ? f(command, this) : f); return mapped.length > 1 ? ElasticAdapter.prepareFilter(mapped) : mapped[0]; } }