@opra/elastic
Version:
Opra Elastic Search adapter package
418 lines (417 loc) • 15.2 kB
JavaScript
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];
}
}