{"version":3,"sources":["../../../src/audit/events.ts"],"sourcesContent":["import { type Pipe, type PipeInput, type PipeOutput, v } from 'valleyed'\n\nimport { Conditions, Db, type Table, wrapQueryParams } from '../dbs'\nimport { EquippedError } from '../errors'\nimport { Instance } from '../instance'\n\nexport type EventDefinition<P extends Pipe<any, any>, R> = {\n\tpipe: P\n\thandle: (payload: PipeOutput<P>, context: EventContext) => R | Promise<R>\n\tsync?: (result: R, payload: PipeOutput<P>, context: EventContext) => void\n\tasync?: (result: R, payload: PipeOutput<P>, context: EventContext) => void\n}\n\nexport type EventContext = {\n\tkey: string\n\tby: string | undefined\n\tat: Date\n\tfirstRun: boolean\n}\n\nexport type EventDoc = {\n\tkey: string\n\tname: string\n\tts: number\n\tbody: unknown\n\tsteps: { status: 'start' | 'sync' | 'async'; ts: number }[]\n\tby?: string\n}\ntype Context = Partial<Pick<EventContext, 'by' | 'at'>>\n\nfunction createStep(step: EventDoc['steps'][number]) {\n\treturn step\n}\n\nexport class EventAudit {\n\tprivate table: Table<any, EventDoc, EventDoc & { toJSON: () => Record<string, unknown> }, any>\n\tprivate definitions: Record<string, EventDefinition<any, any>> = {}\n\tprivate asyncQueue: (() => Promise<void>)[] = []\n\n\tconstructor(\n\t\tprivate db: Db<any>,\n\t\tdbName: string,\n\t) {\n\t\tthis.table = db.use({\n\t\t\tdb: dbName,\n\t\t\tcol: '__audits',\n\t\t\tmapper: (model) => ({ ...model, toJSON: () => model as Record<string, unknown> }),\n\t\t\toptions: { skipAudit: true },\n\t\t})\n\n\t\tInstance.on(\n\t\t\t'start',\n\t\t\t() => {\n\t\t\t\tsetInterval(async () => {\n\t\t\t\t\tconst queue = [...this.asyncQueue]\n\t\t\t\t\tthis.asyncQueue = []\n\t\t\t\t\tawait Promise.all(queue.map((job) => job()))\n\t\t\t\t}, 200)\n\t\t\t},\n\t\t\t4,\n\t\t)\n\t}\n\n\tasync #createEvent(name: string, payload: unknown, context: Context) {\n\t\tconst def = this.definitions[name]\n\t\tif (!def) throw new EquippedError('audit definition not found', { name, payload })\n\n\t\tconst validBody = v.assert(def.pipe, payload)\n\t\tconst ts = context.at ?? new Date()\n\t\tconst key = Instance.createId({ time: ts })\n\n\t\treturn await this.table.insertOne(\n\t\t\t{\n\t\t\t\tkey,\n\t\t\t\tname,\n\t\t\t\tts: ts.getTime(),\n\t\t\t\tbody: validBody,\n\t\t\t\tby: context.by,\n\t\t\t\tsteps: [],\n\t\t\t},\n\t\t\t{ getTime: () => ts, makeId: () => key },\n\t\t)\n\t}\n\n\tasync #processEvent<R>(event: EventDoc, firstRun: boolean) {\n\t\treturn this.db.session(async () => {\n\t\t\tconst def = this.definitions[event.name]\n\t\t\tif (!def) throw new EquippedError('audit definition not found', { event })\n\t\t\tawait this.table.updateOne({ key: event.key }, { $set: { steps: [createStep({ status: 'start', ts: Date.now() })] } })\n\t\t\tconst context: EventContext = {\n\t\t\t\tkey: event.key,\n\t\t\t\tby: event.by,\n\t\t\t\tat: new Date(event.ts),\n\t\t\t\tfirstRun,\n\t\t\t}\n\t\t\tconst result = await def.handle(event.body, context)\n\t\t\tawait def.sync?.(result, event.body, context)\n\t\t\tawait this.table.updateOne({ key: event.key }, { $push: { steps: createStep({ status: 'sync', ts: Date.now() }) } })\n\n\t\t\tconst asyncHandle = async () => {\n\t\t\t\tawait def.async?.(result, event.body, context)\n\t\t\t\tawait this.table.updateOne({ key: event.key }, { $push: { steps: createStep({ status: 'async', ts: Date.now() }) } })\n\t\t\t}\n\t\t\tif (!context.firstRun) await asyncHandle()\n\t\t\telse this.asyncQueue.push(asyncHandle)\n\t\t\treturn result as R\n\t\t})\n\t}\n\n\tasync replay(from?: Date) {\n\t\tconst { results: events } = await this.table.query(\n\t\t\twrapQueryParams({\n\t\t\t\twhere: [...(from ? [{ field: 'ts', value: from.getTime(), condition: Conditions.gte }] : [])],\n\t\t\t\tsort: [{ field: 'ts', desc: false }],\n\t\t\t\tall: true,\n\t\t\t}),\n\t\t)\n\t\tfor (const event of events) await this.#processEvent(event, false)\n\t}\n\n\tasync rerun(key: string) {\n\t\tconst event = await this.table.findOne({ key })\n\t\tif (!event) throw new EquippedError('audit event not found', { key })\n\t\tawait this.#processEvent(event, false)\n\t}\n\n\tregister<P extends Pipe<any, any>, R>(name: string, def: EventDefinition<P, R>) {\n\t\tif (this.definitions[name]) throw new EquippedError(`${name} already has a registered handler`, {})\n\t\tthis.definitions[name] = def\n\t\tv.compile(def.pipe)\n\t\treturn async (payload: PipeInput<P>, context: Context) =>\n\t\t\tthis.db.session(async () => {\n\t\t\t\tconst event = await this.#createEvent(name, payload, context)\n\t\t\t\treturn this.#processEvent<R>(event, true)\n\t\t\t})\n\t}\n}\n"],"mappings":"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","names":[]}