Files
WRNexusJS/packages/compiler/src/server-codegen.ts
T
2026-08-01 01:09:58 +05:30

69 lines
2.6 KiB
TypeScript

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.<name>(...)`.
* Server functions remain available to SSR/server modules without becoming
* remotely callable by default.
*/
export function remotelyReferencedServerFunctions(ast: PageAst): Set<string> {
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<string>();
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>" : "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`;
}