1/**2 * Small dependency-free helpers shared across the worker. Everything here is3 * pure or WebCrypto-based so it unit-tests without wrangler.4 */56/** nanoid's default url-safe alphabet (64 chars, so a 6-bit mask is uniform). */7const NANOID_ALPHABET =8 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';910/** Collision-resistant url-safe id via crypto.getRandomValues (no dependency). */11export function nanoid(size = 21): string {12 const bytes = crypto.getRandomValues(new Uint8Array(size));13 let id = '';14 for (let i = 0; i < size; i++) id += NANOID_ALPHABET[bytes[i]! & 63]!;15 return id;16}1718/** Trash retention: soft-deleted scripts are purged 14 days after deletion. */19export const TRASH_SECONDS = 14 * 24 * 3600;2021/** Unix epoch seconds (all DB timestamps use this). */22export function nowSec(): number {23 return Math.floor(Date.now() / 1000);24}2526export function isoOf(sec: number): string {27 return new Date(sec * 1000).toISOString();28}2930export async function sha256Hex(text: string): Promise<string> {31 const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));32 return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');33}3435export function bytesToBase64Url(bytes: Uint8Array): string {36 let bin = '';37 for (const b of bytes) bin += String.fromCharCode(b);38 return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');39}4041/** Throws on malformed input — callers doing verification must try/catch. */42export function base64UrlToBytes(s: string): Uint8Array {43 const b64 = s.replace(/-/g, '+').replace(/_/g, '/');44 const pad = b64.length % 4 === 0 ? '' : '='.repeat(4 - (b64.length % 4));45 const bin = atob(b64 + pad);46 const out = new Uint8Array(bin.length);47 for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);48 return out;49}5051/** Random decimal-string seed matching CreateSimInput's /^\d{1,15}$/ . */52export function randomDecimalSeed(): string {53 const words = crypto.getRandomValues(new Uint32Array(2));54 const n = ((BigInt(words[0]!) << 32n) | BigInt(words[1]!)) % 1_000_000_000_000_000n;55 return n.toString();56}