/** * Timestamp normalisation for values coming out of the smartbotic-database. * * The daemon stamps documents with `_created_at` and `_updated_at` in * NANOSECONDS - around 1.78e18. Everything else in the platform works in * milliseconds: Date.now() in nodes, TimeUtils::nowMs() in C++, and the * startedAt / finishedAt the engine writes onto an execution. * * Two traps live here, and the workflow list fell into both at once: * * - The metadata keys are snake_case (`_created_at`). Reading `_createdAt` * returns undefined and every date renders as "unknown". * - A nanosecond value is far larger than the largest date JavaScript accepts * (8.64e15 ms). Passing one to new Date() or date-fns yields Invalid Date, * so fixing only the key name still renders "unknown" - which makes the * first fix look like it did nothing. */ // The largest instant the ECMAScript Date type can represent, in milliseconds. const MAX_JS_DATE_MS = 8640000000000000 // Well past any plausible millisecond timestamp (1e15 ms is the year 33658) and // well below any plausible nanosecond one, so it separates the two units // without having to be told which was meant. const NANOSECOND_THRESHOLD = 1e15 /** * Convert a database timestamp to milliseconds, tolerating either unit. * Returns null when the value is missing or cannot be a real instant, so * callers can render their own placeholder rather than an Invalid Date. */ export function toMillis(value: unknown): number | null { const raw = typeof value === 'string' ? Number(value) : value if (typeof raw !== 'number' || !isFinite(raw) || raw <= 0) { return null } const millis = raw > NANOSECOND_THRESHOLD ? Math.floor(raw / 1000000) : raw if (millis > MAX_JS_DATE_MS) { return null } return millis } /** * Read a document's timestamp regardless of which spelling it carries. * * The database writes snake_case, but parts of this codebase were written * against camelCase and some responses are reshaped before they arrive. Trying * both costs nothing and removes a whole class of silent "unknown". */ export function readTimestamp( data: Record | null | undefined, ...keys: string[] ): number | null { if (!data) { return null } for (const key of keys) { const millis = toMillis(data[key]) if (millis !== null) { return millis } } return null }