module-stage.ts (4481B)
1 // Decode the pinned native compiler's header API. 2 import type { Graph, NodeId } from './graph.js'; 3 import { readList, readText, text } from './codecs.js'; 4 import { compilerDiagnostic, SOURCE_LIMIT } from './compiler.js'; 5 import { fetchBytes, decodeBundle, type Bundle } from './bundle.js'; 6 import { packageIndex } from './packages.js'; 7 import { localModuleInputs, validateLocalModule, type LocalModuleInput } from './local-modules.js'; 8 import { fetchModule } from './module-resolver.js'; 9 export interface ModuleStage { 10 body: string; 11 imports: [string, string][]; // immutable reference, qualifier (or *) 12 exports: [string, string][]; // local name/*, or from + immutable reference 13 native: boolean; 14 } 15 export function stageModule(graph: Graph, library: Map<string, NodeId>, source: string): ModuleStage { 16 const call = (name: string, argument: NodeId): NodeId => { 17 const f = library.get('source_module.' + name); 18 if (f === undefined) throw new Error('Missing native module compiler helper'); 19 return graph.apply(f, [argument]); 20 }; 21 const value = (result: NodeId, diagnostic = false): NodeId => { 22 const [tag, payload] = graph.pair(result), flag = graph.observe(tag).node; 23 if (flag.kind === 'leaf') throw new Error(diagnostic ? compilerDiagnostic(graph, payload) : readText(graph, graph.pair(payload)[0], 512)); 24 if (flag.kind !== 'stem' || graph.observe(flag.child).node.kind !== 'leaf') throw new Error('Invalid native module result'); 25 return graph.pair(payload)[0]; 26 }; 27 const [edges, rest] = graph.pair(value(call('sourceModuleHeaderStage', text(graph, source, SOURCE_LIMIT)), true)); 28 if (readList(graph, edges, SOURCE_LIMIT).length) throw new Error('Browser modules require manifest hashes; workspace and build-only sources are unavailable'); 29 const [uses, tail] = graph.pair(rest), [exports, body] = graph.pair(tail); 30 const shapes = (rows: NodeId, helper: string): [string, string][] => readList(graph, rows, SOURCE_LIMIT).map(row => { 31 const [a, b] = graph.pair(value(call(helper, row))); 32 return [readText(graph, a, 128), readText(graph, b, 128)]; 33 }); 34 const imports = shapes(uses, 'sourceModuleUseShape'), selected = shapes(exports, 'sourceModuleExportShape'); 35 return { body: readText(graph, body, SOURCE_LIMIT), imports, exports: selected, native: !!(imports.length || selected.length) }; 36 } 37 export async function moduleDependencies(base: URL, stage: ModuleStage, local: LocalModuleInput[] = []): Promise<Map<string, Bundle>> { 38 const refs = [...new Set([...stage.imports.map(i => i[0]), ...stage.exports.filter(e => e[0] === 'from').map(e => e[1])])]; 39 const result = new Map<string, Bundle>(); 40 if (!refs.length) return result; 41 const available = new Map(localModuleInputs(local).map(entry => [entry.manifest, entry])); 42 let index: ReturnType<typeof packageIndex> | undefined; 43 for (const ref of refs) { 44 const entry = available.get(ref); 45 if (entry) { 46 const checked = await validateLocalModule(entry.bytes); 47 if (checked.manifest !== ref) throw new Error('Local module manifest mismatch'); 48 result.set(ref, decodeBundle(checked.bytes)); 49 } else { 50 index ??= packageIndex(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(await fetchBytes(new URL('packages.json', base), 2_097_152))) as unknown); 51 result.set(ref, await fetchModule(base, ref, index)); 52 } 53 } 54 return result; 55 } 56 // Projection of already native-parsed export requests onto backend-owned trees. 57 // Keep executable trees as backend-owned handles during interface selection. 58 export function selectModuleExports<T>(stage: ModuleStage, locals: Map<string, T>, modules: Map<string, Map<string, T>>): Map<string, T> { 59 const selected = new Map<string, T>(); 60 const add = (name: string, tree: T): void => { 61 if (selected.has(name)) throw new Error('duplicate export'); 62 if (selected.size >= 4096) throw new Error('Module export limit exceeded'); 63 selected.set(name, tree); 64 }; 65 for (const [name, ref] of stage.exports) { 66 if (name === 'from') { 67 const values = modules.get(ref); if (!values) throw new Error('Missing re-export dependency'); 68 for (const [key, value] of values) add(key, value); 69 } else if (name === '*') { for (const [key, value] of locals) add(key, value); } 70 else { if (!locals.has(name)) throw new Error('undeclared local export'); add(name, locals.get(name)!); } 71 } 72 if (!selected.size) throw new Error('module requires an export'); 73 return selected; 74 }