69 lines
2.6 KiB
TypeScript
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`;
|
|
}
|