SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
1.9 KB · 35 lines typescript
Raw Blame History
1import type { FastifyInstance } from 'fastify';2import { z } from 'zod';3import { TtlCache } from './cache.js';4import { envelope, envelopeSchema, type Envelope, type Pagination } from './envelope.js';5import { latestDataAsOf, loadSources } from './sources.js';67const asOfCache = new TtlCache<Date | 'none'>(60_000);89/** Build the envelope: resolves source references and the data-release month (cached 60 s). */10export async function respond<T>(app: FastifyInstance, data: T, sourceRefs: Iterable<string | null | undefined>, pagination?: Pagination): Promise<Envelope<T>> {11  const [sources, asOf] = await Promise.all([loadSources(app.db, sourceRefs), asOfCache.getOrLoad('asOf', async () => (await latestDataAsOf(app.db)) ?? 'none')]);12  return envelope(data, sources, pagination, asOf === 'none' ? undefined : asOf);13}1415/** Loose schemas for OpenAPI: payload shapes are documented in docs/API.md and DATA-MODEL.md. */16export const AnyRecord = z.record(z.string(), z.unknown());17export const AnyList = z.array(AnyRecord);18export const ok = (data: z.ZodTypeAny, paginated = false) => ({ 200: envelopeSchema(data, paginated) });1920export const ErrorResponse = z.object({ error: z.object({ code: z.string(), message: z.string(), details: z.unknown().optional() }), requestId: z.string() });2122/** snake_case → camelCase for raw SQL rows (shallow; jsonb values are left untouched). */23export function camel<T = Record<string, unknown>>(row: Record<string, unknown>): T {24  const out: Record<string, unknown> = {};25  for (const [k, v] of Object.entries(row)) out[k.replace(/_([a-z0-9])/g, (_, c: string) => c.toUpperCase())] = v;26  return out as T;27}28export function camelRows<T = Record<string, unknown>>(rows: Iterable<Record<string, unknown>>): T[] {29  return [...rows].map((r) => camel<T>(r));30}3132export function num(v: unknown): number {33  return v === null || v === undefined ? 0 : Number(v);34}35