UNPKG

@webda/core

Version:

Expose API with Lambda

217 lines 6.13 kB
import { Service, ServiceParameters } from "./service.js"; /** * AsyncEvent representation */ export class AsyncEvent { constructor(service, type, payload = {}) { if (service instanceof Service) { this.service = `service:${service.getName()}`; } else if (typeof service === "string") { this.service = service; } else { this.service = `model:${service.name}`; } this.type = type; this.payload = payload; this.time = new Date(); } /** * Allow payload to contain Service but do not serialize them * replacing them by a #Webda:Service:${service.getName()} so it * can be revived * * @returns */ toJSON() { return { ...this, payload: JSON.stringify(this.payload, (_key, value) => { if (value instanceof Service) { return `${AsyncEvent.ServiceTag}${value.getName()}`; } return value; }) }; } /** * Deserialize from the queue, reviving any detected service * * @param data * @param service * @returns */ static fromQueue(data, service) { let evt = new AsyncEvent(data.service, data.type, JSON.parse(data.payload, (_key, value) => { if (typeof value === "string" && value.startsWith(AsyncEvent.ServiceTag)) { return service.getService(value.substring(AsyncEvent.ServiceTag.length)); } return value; })); evt.time = data.time; return evt; } /** * Mapper name * @returns */ getMapper() { return this.service + "_" + this.type; } } /** * Used when serializing a service */ AsyncEvent.ServiceTag = "#Webda:Service:"; /** * @inheritdoc */ export class EventServiceParameters extends ServiceParameters { /** * @inheritdoc */ constructor(params) { super(params); this.queues ?? (this.queues = {}); this.sync ?? (this.sync = false); } } /** * @category CoreServices * @WebdaModda AsyncEvents */ class EventService extends Service { constructor() { super(...arguments); this._callbacks = {}; this._queues = {}; this._defaultQueue = ""; } /** * Load parameters * * @param params * @ignore */ loadParameters(params) { return new EventServiceParameters(params); } /** * @ignore * Setup the default routes */ async computeParameters() { Object.keys(this.parameters.queues).forEach(key => { // Define default as first queue if (!this._defaultQueue) { this._defaultQueue = key; } this._queues[key] = this.getService(this.parameters.queues[key]); }); this._async = !this.parameters.sync; // Check we have at least one queue to handle asynchronous if (this._async && Object.keys(this._queues).length < 1) { this._webda.log("ERROR", "Need at least one queue for async to be ready", this.parameters); throw Error("Need at least one queue for async to be ready"); } } /** * Bind a asynchronous event * * ```mermaid * sequenceDiagram * participant S as Service * participant As as AsyncEventService * participant Q as Queue * participant Aw as AsyncEventService Worker * * As->>S: Bind event to a sendQueue listener * activate As * S->>As: Emit event * As->>Q: Push the event to the queue * deactivate As * Aw->>Q: Consume queue * Aw->>Aw: Call the original listener * ``` * * @param service * @param event * @param callback * @param queue */ bindAsyncListener(service, event, callback, queue) { if (!this._async) { throw Error("EventService is not configured for asynchronous"); } if (!queue) { queue = this._defaultQueue; } let mapper = new AsyncEvent(service, event).getMapper(); if (!this._callbacks[mapper]) { if (service instanceof Service) { service.on(event, data => this.pushEvent(service, event, queue, data)); } else { service.on(event, data => this.pushEvent(service, event, queue, data)); } this._callbacks[mapper] = []; } this._callbacks[mapper].push(callback); } /** * Synchronous Listener to proxy to async * * @param service * @param type * @param queue * @param payload * @returns */ async pushEvent(service, type, queue, payload) { let event = new AsyncEvent(service, type, payload); if (this._async) { return this._queues[queue].sendMessage(event); } else { return this.handleEvent(event); } } /** * Process one event * * @param event * @returns */ async handleEvent(event) { if (!this._callbacks[event.getMapper()]) { return Promise.reject("Callbacks should not be empty, possible application version mismatch between emitter and worker"); } let promises = []; this._callbacks[event.getMapper()].map(executor => { promises.push(executor(event.payload, event)); }); // Need to handle the failure await Promise.all(promises); } /** * * @param eventBody serialized event * @returns */ async handleRawEvent(event) { return this.handleEvent(AsyncEvent.fromQueue(event, this)); } /** * Process asynchronous event on queue * @param queue * @returns */ worker(queue = this._defaultQueue) { // Avoid loops this._async = false; return this._queues[queue].consume(this.handleRawEvent.bind(this)); } } export { EventService }; //# sourceMappingURL=asyncevents.js.map