Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368import { monitorRegionSchema } from "@openstatus/db/src/schema/constants";import type { Region } from "@openstatus/db/src/schema/constants";import { continentDict, getRegionInfo, regionDict } from "@openstatus/regions";import { Redis } from "@upstash/redis";import { z } from "zod";
// ============================================================================// Constants// ============================================================================
const FLY_CHECKER_URL = "https://checker.openstatus.dev/ping";const KOYEB_CHECKER_URL = "https://openstatus-checker.koyeb.app/ping";const RAILWAY_CHECKER_URL = "https://railway-proxy-production-9cb1.up.railway.app/ping";
// ============================================================================// Formatters// ============================================================================
export function latencyFormatter(value: number) { return `${new Intl.NumberFormat("us").format(value).toString()}ms`;}
export function timestampFormatter(timestamp: number) { return new Date(timestamp).toUTCString(); // GMT format}
export function continentFormatter(region: Region) { const continent = regionDict[region].continent; return continentDict[continent].code;}
export function regionFormatter( region: string, type: "short" | "long" = "short",) { const { code, flag, location } = getRegionInfo(region); if (type === "short") return `${code} ${flag}`; return `${location} ${flag}`;}
// ============================================================================// Timing Utilities// ============================================================================
export function getTotalLatency(timing: Timing) { const { dns, connection, tls, ttfb, transfer } = getTimingPhases(timing); return dns + connection + tls + ttfb + transfer;}
export function getTimingPhases(timing: Timing) { const dns = timing.dnsDone - timing.dnsStart; const connection = timing.connectDone - timing.connectStart; const tls = timing.tlsHandshakeDone - timing.tlsHandshakeStart; const ttfb = timing.firstByteDone - timing.firstByteStart; const transfer = timing.transferDone - timing.transferStart;
return { dns, connection, tls, ttfb, transfer, };}
export function getTimingPhasesWidth(timing: Timing) { const total = getTotalLatency(timing); const phases = getTimingPhases(timing);
const dns = { preWidth: 0, width: (phases.dns / total) * 100 };
const connection = { preWidth: dns.preWidth + dns.width, width: (phases.connection / total) * 100, };
const tls = { preWidth: connection.preWidth + connection.width, width: (phases.tls / total) * 100, };
const ttfb = { preWidth: tls.preWidth + tls.width, width: (phases.ttfb / total) * 100, };
const transfer = { preWidth: ttfb.preWidth + ttfb.width, width: (phases.transfer / total) * 100, };
return { dns, connection, tls, ttfb, transfer, };}
// ============================================================================// Schemas & Types// ============================================================================
export const timingSchema = z.object({ dnsStart: z.number(), dnsDone: z.number(), connectStart: z.number(), connectDone: z.number(), tlsHandshakeStart: z.number(), tlsHandshakeDone: z.number(), firstByteStart: z.number(), firstByteDone: z.number(), transferStart: z.number(), transferDone: z.number(),});
export const checkerSchema = z.object({ type: z.literal("http").prefault("http"), state: z.literal("success").prefault("success"), status: z.number(), latency: z.number(), headers: z.record(z.string(), z.string()), timestamp: z.number(), timing: timingSchema, body: z.string().optional().nullable(),});
export const cachedCheckerSchema = z.object({ url: z.string(), timestamp: z.number(), method: z.string(), // Simplified - validation happens at runtime headers: z.array(z.object({ key: z.string(), value: z.string() })).optional(), body: z.string().optional(), checks: checkerSchema.extend({ region: monitorRegionSchema }).array(),});
const errorRequest = z.object({ message: z.string(), state: z.literal("error").prefault("error"),});
export const regionCheckerSchema = checkerSchema.extend({ region: monitorRegionSchema, state: z.literal("success").prefault("success"),});
export const regionCheckerSchemaResponse = regionCheckerSchema.or( errorRequest.extend({ region: monitorRegionSchema, }),);
export type Timing = z.infer<typeof timingSchema>;export type Checker = z.infer<typeof checkerSchema>;export type RegionChecker = z.infer<typeof regionCheckerSchema>;export type RegionCheckerResponse = z.infer<typeof regionCheckerSchemaResponse>;export type Method = | "GET" | "HEAD" | "OPTIONS" | "POST" | "PUT" | "DELETE" | "PATCH" | "CONNECT" | "TRACE";export type CachedRegionChecker = z.infer<typeof cachedCheckerSchema>;export type ErrorRequest = z.infer<typeof errorRequest>;
type CheckRegionRequest = { url: string; region: Region; method?: Method; headers?: { value: string; key: string }[]; body?: string; signal?: AbortSignal;};
// ============================================================================// API Functions// ============================================================================
// A timeout / unreachable target is an expected outcome, not a bug. Callers// throw this so a route catch can return 400 while keeping it out of Sentry.export class TargetUnreachableError extends Error {}
// Bound the upstream checker request below the client's 10s abort so a stalled// checker can't keep the route running to the platform limit.export const CHECKER_REQUEST_TIMEOUT_MS = 9_000;
export function isTimeoutError(e: unknown): boolean { return e instanceof Error && e.name === "TimeoutError";}
export async function checkRegion( props: CheckRegionRequest,): Promise<RegionCheckerResponse> { const { url, region, method, headers, body, signal } = props; const regionInfo = regionDict[region];
let endpoint = ""; let regionHeader = {}; switch (regionInfo.provider) { case "fly": endpoint = `${FLY_CHECKER_URL}/${region}`; regionHeader = { "fly-prefer-region": region }; break; case "koyeb": endpoint = `${KOYEB_CHECKER_URL}/${region}`; regionHeader = { "X-KOYEB-REGION-OVERRIDE": region.replace("koyeb_", ""), }; break; case "railway": endpoint = `${RAILWAY_CHECKER_URL}/${region}`; regionHeader = { "railway-region": region.replace("railway_", "") }; break; default: break; }
// A caller-supplied signal is the caller's to interpret (it inspects the raw // TimeoutError); only our own default bound maps to TargetUnreachableError. const usingDefaultTimeout = !signal;
let res: Response; try { res = await fetch(endpoint, { headers: { Authorization: `Basic ${process.env.CRON_SECRET}`, "Content-Type": "application/json", ...regionHeader, }, method: "POST", body: JSON.stringify({ url, method: method || "GET", headers: headers?.reduce( (acc, { key, value }) => { if (!key) return acc; // key === "" is an invalid header return { ...acc, [key]: value }; }, {} as Record<string, string>, ), body: body ? body : undefined, }), next: { revalidate: 0 }, signal: signal ?? AbortSignal.timeout(CHECKER_REQUEST_TIMEOUT_MS), }); } catch (e) { if (usingDefaultTimeout && isTimeoutError(e)) { throw new TargetUnreachableError("checker request timed out"); } throw e; }
const json = await res.json();
const data = checkerSchema.or(errorRequest).safeParse(json);
if (!data.success) { // Neither the success nor the error shape matched โ likely checker schema // drift rather than an unreachable target. Warn (not error) so it stays // observable in production without paging Sentry. console.warn("Unexpected checker response shape:", json); throw new TargetUnreachableError(data.error.message); }
return { region, ...data.data, };}
// ============================================================================// Redis Caching// ============================================================================
export async function storeBaseCheckerData({ url, method, id,}: { url: string; method: Method; id: string;}) { const redis = Redis.fromEnv(); const timestamp = new Date().getTime(); const cache = { url, method, timestamp };
const parsed = cachedCheckerSchema .pick({ url: true, method: true, timestamp: true }) .safeParse(cache);
if (!parsed.success) { throw new Error(parsed.error.message); }
await redis.hset(`check:base:${id}`, parsed.data); const expire = 60 * 60 * 24 * 7; // 7days await redis.expire(`check:base:${id}`, expire);}
export async function storeCheckerData({ check, id,}: { check: RegionChecker; id: string;}) { const parsed = cachedCheckerSchema .pick({ checks: true }) .safeParse({ checks: [check] });
if (!parsed.success) { throw new Error(parsed.error.message); }
const first = parsed.data.checks?.[0];
const redis = Redis.fromEnv(); if (first) await redis.sadd(`check:data:${id}`, first);
return id;}
export async function getCheckerDataById(id: string) { const redis = Redis.fromEnv(); const pipe = redis.pipeline(); pipe.hgetall(`check:base:${id}`); pipe.smembers(`check:data:${id}`);
const res = await pipe.exec< [{ url: string; method: Method; time: number }, RegionChecker] >();
if (!res) { return null; }
const parsed = cachedCheckerSchema.safeParse({ ...res[0], checks: res[1] });
if (!parsed.success) { // throw new Error(parsed.error.message); return null; }
return parsed.data;}
// ============================================================================// Validation Utilities// ============================================================================
/** * Simple function to validate crypto.randomUUID() format like "aec4e0ec3c4f4557b8ce46e55078fc95" * @param uuid * @returns */export function is32CharHex(uuid: string) { const hexRegex = /^[0-9a-fA-F]{32}$/; return hexRegex.test(uuid);}