import * as Effect from "effect/Effect"; import { App } from "./app.ts"; import { InstanceId } from "./instance-id.ts"; export const createPhysicalName = Effect.fn(function* ({ id, // 16 base32 characters = 80 bits of entropy = 4 × 10⁻⁷ instanceId, suffixLength = 16, maxLength = 64, }: { id: string; /** * Hex-encoded instance ID (16 random bytes) * * @default - the InstanceID set by the engine in Context */ instanceId?: string; suffixLength?: number; /** * Maximum length of the physical name. * * If the name exceeds this length, the human-friendly portion of the name will be truncated to maxLength-suffixLength */ maxLength?: number; }) { const app = yield* App; const sanitizedId = id.replace(/[^a-zA-Z0-9-_]/g, "-"); const prefix = `${app.name}-${sanitizedId}-${app.stage}-`; const randomId = base32( Buffer.from(instanceId ?? (yield* InstanceId), "hex"), ); const suffix = randomId.slice(0, suffixLength); const name = `${prefix}${suffix}`; if (maxLength && name.length > maxLength) { return `${prefix.slice(0, maxLength - suffix.length)}${suffix}`; } return name; }); // Base32 is ideal for physical names because it's denser than hex (5 bits per char vs 4) // and compatible with DNS/S3 (as opposed to base64 which contains uppercase letters and symbols). // base32.ts // Concise, fast RFC 4648 Base32 encoder (no padding), lowercase output. // Charset: a-z2-7 (DNS/S3 friendly) const ALPH = "abcdefghijklmnopqrstuvwxyz234567"; /** * Encode bytes into RFC4648 Base32 (no padding), lowercase. * * Performance notes: * - O(n) single pass, no big-int * - Avoids per-byte string concatenation by using a char array */ export function base32(bytes: Uint8Array): string { const n = bytes.length; if (n === 0) return ""; // Base32 length without padding: ceil(n*8/5) const outLen = ((n * 8 + 4) / 5) | 0; const out = Array.from({ length: outLen }); let buffer = 0; let bits = 0; let o = 0; for (let i = 0; i < n; i++) { buffer = (buffer << 8) | bytes[i]; bits += 8; while (bits >= 5) { out[o++] = ALPH[(buffer >>> (bits - 5)) & 31]; bits -= 5; } } if (bits > 0) { out[o++] = ALPH[(buffer << (5 - bits)) & 31]; } // outLen computed as exact ceiling; o should match, but slice defensively. return o === outLen ? out.join("") : out.slice(0, o).join(""); }