| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- /**
- * 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<string, any> | 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
- }
|