UNPKG

@unchainedshop/core-events

Version:

This package defines the event module which ensures the emitted events are written to the database as an Event History and offers a core module to search for events.

157 lines (140 loc) 4.46 kB
import { mongodb, generateDbFilterById, buildSortOptions, generateDbObjectId, ModuleInput, assertDocumentDBCompatMode, } from '@unchainedshop/mongodb'; import { getRegisteredEvents } from '@unchainedshop/events'; import { SortDirection, SortOption, DateFilterInput } from '@unchainedshop/utils'; import { EventsCollection, Event } from '../db/EventsCollection.js'; import { configureEventHistoryAdapter } from './configureEventHistoryAdapter.js'; export interface EventReport { emitCount: number; type: string; } export interface EventQuery { types?: string[]; queryString?: string; created?: Date; } export const buildFindSelector = ({ types, queryString, created }: EventQuery) => { const selector: { type?: any; $text?: any; created?: any } = {}; if (types && Array.isArray(types)) selector.type = { $in: types }; if (queryString) { assertDocumentDBCompatMode(); selector.$text = { $search: queryString }; } if (created) selector.created = { $gte: created }; return selector; }; export const configureEventsModule = async ({ db }: ModuleInput<Record<string, never>>) => { const Events = await EventsCollection(db); const create = async (doc: Event) => { const result = await Events.insertOne({ _id: generateDbObjectId(), created: new Date(), ...doc, }); return result.insertedId; }; await configureEventHistoryAdapter(create); return { create, findEvent: async ( { eventId, ...rest }: mongodb.Filter<Event> & { eventId: string }, options?: mongodb.FindOptions, ): Promise<Event> => { const selector = eventId ? generateDbFilterById<Event>(eventId) : rest; return Events.findOne(selector, options); }, findEvents: async ({ limit, offset, sort, ...query }: EventQuery & { limit?: number; offset?: number; sort?: SortOption[]; }): Promise<Event[]> => { const defaultSort = [{ key: 'created', value: SortDirection.DESC }] as SortOption[]; return Events.find(buildFindSelector(query), { skip: offset, limit, sort: buildSortOptions(sort || defaultSort), }).toArray(); }, type: (event: Event) => { if (getRegisteredEvents().includes(event.type)) { return event.type; } return 'UNKNOWN'; }, count: async (query: EventQuery) => { const count = await Events.countDocuments(buildFindSelector(query)); return count; }, getReport: async ( { dateRange, types }: { dateRange?: DateFilterInput; types?: string[] } = { dateRange: {}, types: null, }, ): Promise<EventReport[]> => { const pipeline = []; const matchConditions = []; // build date filter based on provided values it can be a range if both to and from is supplied // a upper or lowe limit if either from or to is provided // or all if none is provided if (dateRange?.start || dateRange?.end) { const dateConditions = []; if (dateRange?.start) { const fromDate = new Date(dateRange?.start); dateConditions.push({ $or: [{ created: { $gte: fromDate } }, { updated: { $gte: fromDate } }], }); } if (dateRange?.end) { const toDate = new Date(dateRange?.end); dateConditions.push({ $or: [{ created: { $lte: toDate } }, { updated: { $lte: toDate } }], }); } if (dateConditions.length > 0) { matchConditions.push({ $and: dateConditions }); } } // build types filter if type is provided or ignore types if it is not provided if (types && Array.isArray(types) && types.length) { matchConditions.push({ type: { $in: types } }); } if (matchConditions.length > 0) { pipeline.push({ $match: { $and: matchConditions, }, }); } pipeline.push( ...[ { $group: { _id: '$type', emitCount: { $sum: 1 }, }, }, { $project: { _id: 0, type: '$_id', emitCount: 1, }, }, ], ); return Events.aggregate(pipeline).toArray() as Promise<EventReport[]>; }, }; }; export type EventsModule = Awaited<ReturnType<typeof configureEventsModule>>;