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.

169 lines (150 loc) 4.61 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'; import { createLogger } from '@unchainedshop/logger'; const logger = createLogger('unchained:core-events'); export interface EventReport { emitCount: number; type: string; } export interface EventQuery { types?: string[]; queryString?: string; created?: { end?: Date; start?: 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 = created?.end ? { $gte: created.start || new Date(0), $lte: created.end } : { $gte: created.start || new Date(0) }; } return selector; }; export const configureEventsModule = async ({ db }: ModuleInput<Record<string, never>>) => { const Events = await EventsCollection(db); const create = async ( doc: Omit<Event, '_id' | 'created'> & Pick<Partial<Event>, '_id' | 'created'>, ) => { 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, ) => { 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[] } = {}): Promise<EventReport[]> => { const match: any = {}; if (dateRange?.start || dateRange?.end) { const conditions: any[] = []; if (dateRange.start) { const fromDate = new Date(dateRange.start); conditions.push({ created: { $gte: fromDate } }); } if (dateRange.end) { const toDate = new Date(dateRange.end); conditions.push({ created: { $lte: toDate } }); } if (conditions.length) { match.$or = conditions; } } if (types?.length) { match.type = { $in: types }; } const pipeline = [ Object.keys(match).length ? { $match: match } : null, { $group: { _id: { type: '$type', day: { $dateToString: { format: '%Y-%m-%d', date: '$created' }, }, }, count: { $sum: 1 }, }, }, { $group: { _id: '$_id.type', detail: { $push: { date: '$_id.day', count: '$count', }, }, emitCount: { $sum: '$count' }, }, }, { $project: { _id: 0, type: '$_id', emitCount: 1, detail: 1, }, }, { $sort: { type: 1 } }, ].filter(Boolean) as mongodb.BSON.Document[]; const report = await Events.aggregate<EventReport>(pipeline).toArray(); logger.info(report); return report ?? []; }, }; }; export type EventsModule = Awaited<ReturnType<typeof configureEventsModule>>;