import { stripRuntimeFunctionModifiers, type PageAst } from "@wrnexus/syntax"; export interface RpcManifestEntry { id: string; component: string; function: string; parameters: Array<{ name: string; type: string; optional: boolean }>; returnType: string; } function stableId(value: string): string { let hash = 0x811c9dc5; for (let index = 0; index < value.length; index++) { hash ^= value.charCodeAt(index); hash = Math.imul(hash, 0x01000193); } return `wrn_${(hash >>> 0).toString(36)}`; } /** * Remote exposure is reference based in v0.6. A server function is included in * the RPC manifest only when browser-capable code calls `server.(...)`. * Server functions remain available to SSR/server modules without becoming * remotely callable by default. */ export function remotelyReferencedServerFunctions(ast: PageAst): Set { const browserSources = ast.runtimeFunctions .filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime)) .map((fn) => fn.body); for (const [hook, body] of Object.entries(ast.storeLifecycle)) { if (hook !== "serverInit" && body) browserSources.push(body); } const names = new Set(); const call = /\bserver\.([A-Za-z_$][\w$]*)\s*\(/g; for (const source of browserSources) { for (const match of source.matchAll(call)) names.add(match[1]!); } return names; } export function rpcManifest(ast: PageAst): RpcManifestEntry[] { const exposed = remotelyReferencedServerFunctions(ast); return ast.runtimeFunctions .filter((fn) => fn.runtime === "server" && exposed.has(fn.name)) .map((fn) => ({ id: stableId(`${ast.name}:${fn.name}`), component: ast.name, function: fn.name, parameters: fn.parameters.map((param) => ({ name: param.name, type: param.valueType ?? "unknown", optional: param.optional, })), returnType: fn.returnType ?? (fn.async ? "Promise" : "unknown"), })); } export function generateServerFunctionsModule(ast: PageAst): string { const source = ast.functions .map((body) => stripRuntimeFunctionModifiers(body, ["legacy", "server", "shared"])) .filter(Boolean) .join("\n\n"); const names = ast.runtimeFunctions .filter((fn) => ["legacy", "server", "shared"].includes(fn.runtime)) .map((fn) => fn.name); const manifest = rpcManifest(ast); return `// generated WRNexusJS server module for ${ast.name}\n${source}\n\nexport const __wrnexusServerFunctions = { ${[...new Set(names)].join(", ")} };\nexport const __wrnexusRpcManifest = ${JSON.stringify(manifest, null, 2)};\n`; }