spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1/**2 * Coercion helpers for raw SQL rows. postgres.js (as configured by drizzle) returns bigint counts as3 * strings, timestamps as strings ("2026-09-11 02:41:00.123456+00") and jsonb already parsed.4 */5export type Row = Record<string, unknown>;67export function str(v: unknown): string | null {8 if (v == null) return null;9 return typeof v === "string" ? v : String(v);10}1112export function reqStr(v: unknown, fallback = ""): string {13 return str(v) ?? fallback;14}1516export function num(v: unknown): number | null {17 if (v == null || v === "") return null;18 const n = typeof v === "number" ? v : Number(v);19 return Number.isFinite(n) ? n : null;20}2122export function int(v: unknown, fallback = 0): number {23 const n = num(v);24 return n == null ? fallback : Math.trunc(n);25}2627export function float(v: unknown, fallback = 0): number {28 return num(v) ?? fallback;29}3031export function bool(v: unknown): boolean {32 return v === true || v === "t" || v === "true" || v === 1 || v === "1";33}3435/** Timestamp (string from the driver, Date from other paths) → ISO 8601. */36export function iso(v: unknown): string | null {37 if (v == null) return null;38 if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : v.toISOString();39 const s = String(v).trim();40 if (!s) return null;41 if (/^\d{4}-\d{2}-\d{2}$/.test(s)) return s; // date column: keep the day42 const d = new Date(s.replace(" ", "T").replace(/([+-]\d{2})$/, "$1:00"));43 if (Number.isNaN(d.getTime())) {44 const d2 = new Date(s);45 return Number.isNaN(d2.getTime()) ? s : d2.toISOString();46 }47 return d.toISOString();48}4950export function reqIso(v: unknown): string {51 return iso(v) ?? new Date(0).toISOString();52}5354export function strArray(v: unknown): string[] {55 if (Array.isArray(v)) return v.filter((x) => x != null).map(String);56 if (typeof v === "string") {57 // text[] literal "{a,b}" when not parsed by the driver58 if (v.startsWith("{") && v.endsWith("}")) return v.slice(1, -1).split(",").map((s) => s.replace(/^"|"$/g, "")).filter(Boolean);59 return [v];60 }61 return [];62}6364export function json<T = unknown>(v: unknown, fallback: T): T {65 if (v == null) return fallback;66 if (typeof v === "string") {67 try { return JSON.parse(v) as T; } catch { return fallback; }68 }69 return v as T;70}7172export function record(v: unknown): Record<string, string | number> {73 const j = json<Record<string, unknown>>(v, {});74 const out: Record<string, string | number> = {};75 if (j && typeof j === "object" && !Array.isArray(j)) for (const [k, val] of Object.entries(j)) if (typeof val === "string" || typeof val === "number") out[k] = val;76 return out;77}7879/** {id, slug, name} reference from prefixed columns (e.g. "op_id", "op_slug", "op_name"). */80export function ref(r: Row, prefix: string): { id: string; slug: string; name: string } | null {81 const id = str(r[`${prefix}_id`]);82 if (!id) return null;83 return { id, slug: reqStr(r[`${prefix}_slug`]), name: reqStr(r[`${prefix}_name`]) };84}8586export function round(v: number | null, digits = 2): number | null {87 if (v == null) return null;88 const f = 10 ** digits;89 return Math.round(v * f) / f;90}91