Repository Public
1import { createHash, createHmac, timingSafeEqual } from "node:crypto";23export const sha256 = (data: string | Buffer) => createHash("sha256").update(data).digest("hex");45/** Deterministic JSON (sorted keys) so equal inputs hash equally. */6export function canonical(v: unknown): string {7 if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "null";8 if (Array.isArray(v)) return `[${v.map(canonical).join(",")}]`;9 const o = v as Record<string, unknown>;10 return `{${Object.keys(o).filter((k) => o[k] !== undefined).sort().map((k) => `${JSON.stringify(k)}:${canonical(o[k])}`).join(",")}}`;11}12export const hashOf = (v: unknown) => sha256(canonical(v));1314export const hmac = (secret: string, data: string) => createHmac("sha256", secret).update(data).digest("hex");15export function safeEqual(a: string, b: string) {16 const x = Buffer.from(a), y = Buffer.from(b);17 return x.length === y.length && timingSafeEqual(x, y);18}19export function slugify(s: string) {20 return s.normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/['’]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 70) || "film";21}22