release: WRNexusJS 0.6.0
This commit is contained in:
@@ -32,7 +32,15 @@ import {
|
||||
import { connectFromConfig } from "@wrnexus/db/connect";
|
||||
import { configureStorage, type StorageConfig } from "@wrnexus/uploader";
|
||||
import { realtimeBusFromConfig } from "./realtime-bus.ts";
|
||||
import { invalidateModule, loadModule, setCompileCacheDir } from "./pipeline.ts";
|
||||
import {
|
||||
invalidateModule,
|
||||
loadModule,
|
||||
loadWrnServerModule,
|
||||
setCompileCacheDir,
|
||||
setCompileImportOptions,
|
||||
wrnBrowserArtifactUrl,
|
||||
} from "./pipeline.ts";
|
||||
import { createRpcHandler } from "@wrnexus/ssr/rpc";
|
||||
import { createHandlers, type WsData } from "./runtime.ts";
|
||||
import { createDevAssetServer } from "./assets.ts";
|
||||
import { pluginAssetsFromContributions } from "./plugin-assets.ts";
|
||||
@@ -173,6 +181,12 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
const appDir = resolve(opts.appDir);
|
||||
const appRoot = dirname(appDir);
|
||||
const mode: Mode = opts.mode ?? "development";
|
||||
const importConfig = (opts.appConfig?.imports ?? {}) as {
|
||||
mode?: "legacy" | "compatible" | "explicit";
|
||||
autoImport?: boolean;
|
||||
aliases?: Record<string, string>;
|
||||
};
|
||||
setCompileImportOptions(appRoot, importConfig);
|
||||
const configuredPlugins = opts.plugins ?? (opts.appConfig?.plugins as PluginInput | undefined);
|
||||
|
||||
const discoveredPlugins = await discoverPlugins(appRoot, configuredPlugins, {
|
||||
@@ -384,8 +398,33 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
config: devToolbarConfig,
|
||||
collector: devToolbarCollector,
|
||||
root: appRoot,
|
||||
panels: pluginToolbarPanels,
|
||||
panels: [
|
||||
{
|
||||
id: "runtime",
|
||||
title: "Runtime",
|
||||
icon: "cpu",
|
||||
description:
|
||||
"Client, server, and shared functions, hydration modules, and runtime-boundary diagnostics.",
|
||||
order: 10,
|
||||
},
|
||||
{
|
||||
id: "stores",
|
||||
title: "Stores",
|
||||
icon: "database",
|
||||
description:
|
||||
"Global/page stores, safe client state, computed values, actions, persistence, and hydration.",
|
||||
order: 20,
|
||||
},
|
||||
...pluginToolbarPanels,
|
||||
],
|
||||
platform: {
|
||||
wrnexus060: {
|
||||
clientFunctions: true,
|
||||
serverFunctions: true,
|
||||
sharedFunctions: true,
|
||||
typedOutputs: true,
|
||||
requestScopedStores: true,
|
||||
},
|
||||
plugins: pluginRunner.plugins.map((plugin) => ({
|
||||
name: plugin.name,
|
||||
version: plugin.version,
|
||||
@@ -413,13 +452,59 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
: undefined,
|
||||
};
|
||||
const handlers = createHandlers(runtimeDeps);
|
||||
const rpcHandler = createRpcHandler({
|
||||
async resolve(rawComponent) {
|
||||
const componentName = rawComponent.split(":", 1)[0] ?? rawComponent;
|
||||
const preferred = router.components.find(
|
||||
(entry) => entry.name.toLowerCase() === componentName.toLowerCase(),
|
||||
);
|
||||
const candidates = [
|
||||
...(preferred ? [preferred.file] : []),
|
||||
...router.pages.filter((route) => route.file.endsWith(".wrn")).map((route) => route.file),
|
||||
...router.layouts.map((entry) => entry.file),
|
||||
...router.stores.map((entry) => entry.file),
|
||||
...router.components.filter((entry) => entry !== preferred).map((entry) => entry.file),
|
||||
];
|
||||
for (const file of new Set(candidates)) {
|
||||
const module = await loadWrnServerModule(file);
|
||||
const functions = module.__wrnexusServerFunctions;
|
||||
const manifest = module.__wrnexusRpcManifest;
|
||||
if (!functions || typeof functions !== "object" || !Array.isArray(manifest)) continue;
|
||||
const ownsComponent = manifest.some(
|
||||
(entry: any) =>
|
||||
String(entry?.component ?? "").toLowerCase() === componentName.toLowerCase(),
|
||||
);
|
||||
if (!ownsComponent) continue;
|
||||
return {
|
||||
functions: functions as Record<string, (...args: any[]) => any>,
|
||||
manifest: manifest as any,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
validateCsrf(request) {
|
||||
const url = new URL(request.url);
|
||||
const origin = request.headers.get("origin");
|
||||
if (origin && origin !== url.origin) return false;
|
||||
const cookieToken = /(?:^|;\s*)wrnexus_csrf=([^;]+)/.exec(
|
||||
request.headers.get("cookie") ?? "",
|
||||
)?.[1];
|
||||
return (
|
||||
!cookieToken ||
|
||||
decodeURIComponent(cookieToken) === (request.headers.get("x-wrnexus-csrf") ?? "")
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const server = Bun.serve<WsData>({
|
||||
port,
|
||||
hostname,
|
||||
development: mode === "development",
|
||||
maxRequestBodySize: 10 * 1024 * 1024,
|
||||
fetch: handlers.fetch,
|
||||
fetch(request, server) {
|
||||
if (new URL(request.url).pathname === "/__wrnexus/rpc") return rpcHandler(request);
|
||||
return handlers.fetch(request, server);
|
||||
},
|
||||
websocket: handlers.websocket,
|
||||
});
|
||||
|
||||
@@ -473,7 +558,40 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
||||
}
|
||||
|
||||
console.log(`[wrnexus] hot update — ${files.join(", ")}`);
|
||||
hub.reload();
|
||||
const storeUpdates: Array<{ name: string; url: string; kind: string }> = [];
|
||||
for (const changed of files) {
|
||||
const absolute = isAbsolute(changed) ? changed : resolve(appDir, changed);
|
||||
if (!absolute.endsWith(".wrn")) continue;
|
||||
try {
|
||||
const source = readFileSync(absolute, "utf8");
|
||||
const declaration = /\b(global|page)\s+store\s+([A-Za-z_$][\w$]*)\s*\{/.exec(source);
|
||||
if (!declaration) continue;
|
||||
storeUpdates.push({
|
||||
name: declaration[2]!,
|
||||
kind: declaration[1]!,
|
||||
url: wrnBrowserArtifactUrl(absolute),
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(`[wrnexus] failed to prepare store HMR for ${absolute}`, error);
|
||||
}
|
||||
}
|
||||
if (storeUpdates.length) {
|
||||
hub.broadcastJson({ type: "store-update", version: Date.now(), stores: storeUpdates });
|
||||
}
|
||||
const onlyStores =
|
||||
storeUpdates.length > 0 &&
|
||||
files.every((changed) => {
|
||||
const absolute = isAbsolute(changed) ? changed : resolve(appDir, changed);
|
||||
if (!absolute.endsWith(".wrn")) return false;
|
||||
try {
|
||||
return /\b(?:global|page)\s+store\s+[A-Za-z_$][\w$]*\s*\{/.test(
|
||||
readFileSync(absolute, "utf8"),
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (!onlyStores) hub.reload();
|
||||
hub.broadcastJson({
|
||||
channel: "toolbar",
|
||||
type: "toolbar:scan",
|
||||
|
||||
Reference in New Issue
Block a user