{"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":["v","Conditions","wrapQueryParams","EquippedError","Instance","createStep","step","EventAudit","db","dbName","model","queue","job","#createEvent","name","payload","context","def","validBody","ts","key","#processEvent","event","firstRun","result","asyncHandle","from","events"]}