Skip to main content

Lifecycle Hooks

Every Elasticsearch service exposes a set of protected _before* / _after* methods that are called around each database operation. Override them in your subclass to inject business logic without wrapping every call site.

The base implementations do nothing — there is no need to call super.


Available hooks

HookWhen it runs
_beforeCreate(command)Before indexing a document
_afterCreate(command, result)After index operation
_beforeUpdate(command)Before a single-document update
_afterUpdate(command, result)After a single-document update
_beforeUpdateMany(command)Before a multi-document update
_afterUpdateMany(command, affected)After a multi-document update
_beforeDelete(command)Before deleting a single document
_afterDelete(command, affected)After deleting a single document
_beforeDeleteMany(command)Before deleting multiple documents
_afterDeleteMany(command, affected)After deleting multiple documents

Accessing the command object

command.crud // 'create' | 'read' | 'update' | 'delete'
command.method // 'create' | 'update' | 'updateMany' | 'delete' | ...
command.documentId // _id of the target document (where applicable)
command.input // the patch/input data — can be mutated in _before* hooks
command.options // the operation options — can be mutated in _before* hooks

Real-world examples

Stamping timestamps and ownership

export class ProductsService extends ElasticCollectionService<Product> {
constructor(client: Client) {
super(Product, {
client,
documentFilter: (_, _this) => `tenantId = "${_this.context.tenantId}"`,
});
}

protected override async _beforeCreate(command: ElasticEntityService.CreateCommand) {
command.input.tenantId = this.context.tenantId;
command.input.createdBy = this.context.userId;
command.input.createdAt = new Date().toISOString();
command.input.updatedAt = new Date().toISOString();
}

protected override async _beforeUpdate(command: ElasticEntityService.UpdateCommand<Product>) {
if (command.input) command.input.updatedAt = new Date().toISOString();
}
}

Input validation

protected override async _beforeCreate(command: ElasticEntityService.CreateCommand) {
const exists = await this.existsOne({ filter: `sku = "${command.input.sku}"` });
if (exists) throw new ConflictError(`SKU ${command.input.sku} already exists`);
}

Audit logging

protected override async _afterCreate(
command: ElasticEntityService.CreateCommand,
result: PartialDTO<Product>,
) {
await this.auditLog.for(this).create({
action: 'create',
resourceId: result._id,
userId: this.context?.userId,
at: new Date().toISOString(),
});
}

protected override async _afterDelete(
command: ElasticEntityService.DeleteCommand,
affected: number,
) {
if (affected) {
await this.auditLog.for(this).create({
action: 'delete',
resourceId: command.documentId,
userId: this.context?.userId,
at: new Date().toISOString(),
});
}
}

Cache invalidation

protected override async _afterUpdate(
command: ElasticEntityService.UpdateCommand<Product>,
result: PartialDTO<Product> | undefined,
) {
await this.cache.del(`product:${command.documentId}`);
}

Full API reference

ElasticEntityService — lifecycle hooks