UNPKG

@getanthill/datastore

Version:

Event-Sourced Datastore

238 lines (189 loc) 5.96 kB
import type { ModelInstance, Services } from '../../typings'; import type { Request } from 'express'; import omit from 'lodash/omit'; import { getDate } from '../../utils'; const LAST_FRAGMENT_REGEXP = /\/[^/]+$/; export async function publish(services: Services, topic: string, event: any) { if (services.config.features.mqtt.isEnabled === true) { await services.mqtt.publish(topic, event); // Required to listen on deterministic event names: services.mqtt.emit(topic.replace(LAST_FRAGMENT_REGEXP, ''), event); } if (services.config.features.amqp.isEnabled === true) { await services.amqp.publish(topic, event); // Required to listen on deterministic event names: services.amqp.emit(topic.replace(LAST_FRAGMENT_REGEXP, ''), event); } } export async function publishEntityUpdates( services: Services, modelName: string, eventName: string, entity: ModelInstance, ) { const topic = `${modelName}/${eventName}/success/${entity.correlationId}`.toLowerCase(); await Promise.all([ publish(services, topic, entity.state), ...entity.latestHandledEvents.map((e) => publish( services, `${modelName}/${e.type}/events/${entity.correlationId}`.toLowerCase(), e, ), ), ]); } export async function created( services: Services, req: Partial<Request>, event: any, ) { const entity = services.models.factory(req.params!.model); const body = event; if (req.headers?.['created-at']) { body.created_at = getDate(req.headers!['created-at'] as string); } await entity.create(body); services.telemetry.logger.debug('[events#created] Entity created', { entity: entity.state, }); await publishEntityUpdates(services, req.params!.model, 'created', entity); return entity; } export async function updated( services: Services, req: Partial<Request>, event: any, ) { const entity = services.models.factory( req.params!.model, req.params!.correlation_id, ); const upsert: boolean = req.headers?.['upsert'] === 'true'; const imperativeVersion: number | undefined = req.headers?.['version'] !== undefined ? parseInt(req.headers?.['version'] as string, 10) : undefined; const body = event; if (req.headers?.['created-at']) { body.created_at = getDate(req.headers!['created-at'] as string); } if (upsert === true) { await entity.upsert(body, { imperativeVersion }); } else { await entity.update(body, { imperativeVersion }); } services.telemetry.logger.debug('[api/models#update] Entity updated', { model: req.params!.model, entity: entity.state, }); await publishEntityUpdates(services, req.params!.model, 'updated', entity); return entity; } export async function patched( services: Services, req: Partial<Request>, event: any, ) { const entity = services.models.factory( req.params!.model, req.params!.correlation_id, ); const imperativeVersion: number | undefined = req.headers?.['version'] ? parseInt(req.headers?.['version'] as string, 10) : undefined; const body = event; if (req.headers?.['created-at']) { body.created_at = getDate(req.headers['created-at'] as string); } await entity.patch(body, { imperativeVersion }); services.telemetry.logger.debug('[api/models#update] Entity patched', { model: req.params!.model, }); await publishEntityUpdates(services, req.params!.model, 'patched', entity); return entity; } export async function applied( services: Services, req: Partial<Request>, event: any, ) { const entity = services.models.factory( req.params!.model, req.params!.correlation_id, ); const imperativeVersion: number | undefined = req.headers!['version'] !== undefined ? parseInt(req.headers!['version'] as string, 10) : undefined; const isReplay: boolean = req.headers!['replay'] === 'true'; const retryDuration: number | undefined = req.headers!['retry-duration'] !== undefined ? parseInt(req.headers!['retry-duration'] as string, 10) : undefined; let body = event; if (req.headers!['created-at']) { body.created_at = getDate(req.headers!['created-at'] as string); } if (isReplay === true) { const Model = services.models.getModel(req.params!.model); const modelConfig = Model.getModelConfig(); const correlationField: string = modelConfig.correlation_field; const foundEvent = await Model.getEventsCollection( Model.db(services.mongodb), ).findOne({ [correlationField]: req.params!.correlation_id, type: body.type, created_at: { $gte: body.created_at, $lte: body.created_at, }, }); if (!!foundEvent === true) { entity.state = await entity.getStateAtVersion(foundEvent.version); return entity; } const originalPayload = omit(body, 'type', 'v', '_id', 'version'); body = await Model.decrypt(originalPayload); } await entity.apply( req.params!.event_type.toUpperCase(), body, { imperativeVersion, retryDuration, }, (req.headers!['event-version'] as string) || req.params!.event_version, ); if (isReplay === false) { await publishEntityUpdates( services, req.params!.model, req.params!.event_type, entity, ); } return entity; } export async function restored( services: Services, req: Request & { locals: any }, event: any, ) { const entity = services.models.factory( req.params.model, req.params.correlation_id, ); req.locals = req.locals || {}; req.locals.model = entity; const version = parseInt(req.params.version, 10); await entity.getState(); if (entity.state === null) { throw new Error('Not Found'); } req.locals.currentVersion = entity.state.version; await entity.restore(version); await publishEntityUpdates(services, req.params.model, 'restored', entity); return entity; }