SPB Git forge
38commits 1branches 0releases
338.7 MBsize
maindefault branch
4 h agolast push
HTML 53.9% TypeScript 44.5% JavaScript 0.6% SQL 0.5%
3.1 KB · 91 lines typescript
Raw Blame History
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