export const WASM_MODULE_MANIFEST_SCHEMA = 1 as const; export type WasmModuleLifecycle = "complete" | "debug"; export type WasmModuleVariant = "single" | "pthread"; export interface WasmModuleResource { fileName: string; url: string; sha256: string; } export interface WasmModuleEntry { id: string; lifecycle: WasmModuleLifecycle; preload: boolean; resources: Record; } export interface WasmModuleManifest { schemaVersion: typeof WASM_MODULE_MANIFEST_SCHEMA; releaseId: string; modules: WasmModuleEntry[]; } const SHA256 = /^[a-f0-9]{64}$/; const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/; function record(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function resource(value: unknown, path: string): WasmModuleResource { if (!record(value) || typeof value.fileName !== "string" || !NAME.test(value.fileName) || !value.fileName.endsWith(".wasm") || typeof value.url !== "string" || !value.url.startsWith("/") || /[?#\\]/.test(value.url) || typeof value.sha256 !== "string" || !SHA256.test(value.sha256)) { throw new Error(`WASM_MODULE_MANIFEST_INVALID: ${path}`); } const segments = value.url.slice(1).split("/"); if (segments.some((segment) => !NAME.test(segment)) || segments.at(-1) !== value.fileName) { throw new Error(`WASM_MODULE_MANIFEST_INVALID: ${path}.url`); } return { fileName: value.fileName, url: value.url, sha256: value.sha256 }; } export function validateWasmModuleManifest(value: unknown): WasmModuleManifest { if (!record(value) || value.schemaVersion !== WASM_MODULE_MANIFEST_SCHEMA || typeof value.releaseId !== "string" || !NAME.test(value.releaseId) || !Array.isArray(value.modules)) { throw new Error("WASM_MODULE_MANIFEST_INVALID"); } const ids = new Set(); const modules = value.modules.map((entry, index): WasmModuleEntry => { if (!record(entry) || typeof entry.id !== "string" || !NAME.test(entry.id) || ids.has(entry.id) || (entry.lifecycle !== "complete" && entry.lifecycle !== "debug") || typeof entry.preload !== "boolean" || !record(entry.resources) || !entry.resources.single || !entry.resources.pthread) { throw new Error(`WASM_MODULE_MANIFEST_INVALID: modules[${index}]`); } ids.add(entry.id); if (entry.lifecycle === "debug" && entry.preload) throw new Error(`WASM_MODULE_MANIFEST_INVALID: modules[${index}].preload`); return { id: entry.id, lifecycle: entry.lifecycle, preload: entry.preload, resources: { single: resource(entry.resources.single, `modules[${index}].resources.single`), pthread: resource(entry.resources.pthread, `modules[${index}].resources.pthread`), }, }; }); return { schemaVersion: WASM_MODULE_MANIFEST_SCHEMA, releaseId: value.releaseId, modules }; }