import { createModel } from '@rematch/core' import { EventRecord, EventPhase, EventStage, EventPhaseLog, EventError, TraceData } from '../../types/core' import { RootModel } from '.' const isPhaseOutOfOrder = (loggedPhases: EventPhaseLog, newPhase: EventPhase) => { const correctOrder: EventStage[] = ['triggered', 'enriched', 'delivered'] if (!correctOrder.includes(newPhase.stage)) { throw new Error(`Unknown phase: ${newPhase.stage}`) } const latterStages = correctOrder.slice(correctOrder.indexOf(newPhase.stage)) return latterStages.some((stage) => typeof loggedPhases?.[stage] === 'object') } const isErrorTrace = (trace: TraceData): trace is EventError => 'type' in trace && trace.type === 'error' const defaultState = () => ({} as Record) export const events = createModel()({ state: defaultState(), reducers: { traceEvent(state, trace: TraceData) { const { id } = trace // other event phases cannot be safely traced if we don't have lift off details if (!state[id] && (!('stage' in trace) || trace.stage !== 'triggered')) { // just fail silently return state } state[id] ??= { id, event: null, timeline: [], errors: [], phases: {} } if ('stage' in trace) { if (isPhaseOutOfOrder(state[id]?.phases, trace)) { throw new Error(`${trace.stage} out of order`) } Object.assign(state[id].phases, { [trace.stage]: trace }) } state[id].event = trace.event state[id].timeline.unshift(trace) if (isErrorTrace(trace)) { state[id].errors.push(trace) } return state } }, effects: {} })