release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+112 -11
View File
@@ -1,3 +1,5 @@
import { WRN_RUNTIME_TARGETS } from "./spec.ts";
/**
* Recursive-descent parser for `.wrn`, producing a small AST.
*
@@ -63,12 +65,16 @@ export interface EffectBlock {
export interface LoadBlock {
mode: "server" | "client";
name?: string;
dependsOn?: string[];
deferred?: boolean;
body: string;
}
export interface ActionBlock {
name: string;
args: string[];
schema?: string;
body: string;
}
@@ -205,9 +211,13 @@ export interface PageAst {
layout?: string;
layoutIsSymbol?: boolean;
/** Execution boundary metadata. Defaults to universal. */
runtime?: "server" | "client" | "universal";
runtime?: "server" | "client" | "universal" | "edge" | "worker" | "service-worker";
/** Explicit rendering policy; `hybrid` is the default SSR + optional hydration behavior. */
renderMode?: "static" | "server" | "hybrid" | "client" | "partial-static";
/** Client hydration strategy. Defaults to load when interactivity is present. */
hydrate?: string;
/** Declarative framework cache policy. */
cache?: Record<string, string>;
/** Declared component props (empty for pages). */
props: PropDecl[];
/** Legacy public events exposed by a reusable component. */
@@ -222,6 +232,7 @@ export interface PageAst {
loads: LoadBlock[];
actions: ActionBlock[];
security: Record<string, string>;
navigation: Record<string, string>;
seo: SeoBlock;
view: ViewNode[];
styles: string[];
@@ -343,6 +354,7 @@ export function parse(source: string): PageAst {
let layout: string | undefined;
let layoutIsSymbol = false;
let runtime: PageAst["runtime"];
let renderMode: PageAst["renderMode"];
let hydrate: string | undefined;
const props: PropDecl[] = [];
const events: EventDecl[] = [];
@@ -354,6 +366,8 @@ export function parse(source: string): PageAst {
const loads: LoadBlock[] = [];
const actions: ActionBlock[] = [];
const security: Record<string, string> = {};
const cache: Record<string, string> = {};
const navigation: Record<string, string> = {};
const seo: SeoBlock = {};
const view: ViewNode[] = [];
const styles: string[] = [];
@@ -393,19 +407,32 @@ export function parse(source: string): PageAst {
lx.next();
expect("eq");
const value = expect("string").value;
if (value !== "server" && value !== "client" && value !== "universal") {
if (!WRN_RUNTIME_TARGETS.includes(value as (typeof WRN_RUNTIME_TARGETS)[number])) {
throw new ParseError(
`Unknown runtime target '${value}' at offset ${kw.pos}`,
"WRN-RUNTIME-TARGET",
);
}
runtime = value;
runtime = value as PageAst["runtime"];
break;
}
case "render": {
lx.next();
expect("eq");
const value = expect("string").value;
if (!["static", "server", "hybrid", "client", "partial-static"].includes(value))
throw new ParseError(
`Unknown render mode '${value}' at offset ${kw.pos}`,
"WRN-RENDER-MODE",
);
renderMode = value as PageAst["renderMode"];
break;
}
case "hydrate": {
lx.next();
expect("eq");
hydrate = expect("string").value;
const value = expect("string").value;
hydrate = value === "never" ? "none" : value;
break;
}
case "props": {
@@ -549,15 +576,49 @@ export function parse(source: string): PageAst {
Object.assign(security, parseSeoBlock(lx.readBalancedBraces()));
break;
}
case "navigation": {
lx.next();
Object.assign(navigation, parseSeoBlock(lx.readBalancedBraces()));
break;
}
case "cache": {
lx.next();
Object.assign(cache, parseSeoBlock(lx.readBalancedBraces()));
break;
}
case "load": {
lx.next();
const modeToken = expect("ident");
if (modeToken.value !== "server" && modeToken.value !== "client") {
throw new ParseError(
`Expected 'server' or 'client' after load at offset ${modeToken.pos}`,
);
const first = expect("ident");
const mode = first.value === "client" ? "client" : "server";
const name =
first.value === "server" || first.value === "client"
? lx.peek().type === "ident"
? expect("ident").value
: undefined
: first.value;
const dependsOn: string[] = [];
let deferred = false;
while (lx.peek().type === "ident") {
if (lx.peek().value === "defer") {
lx.next();
deferred = true;
continue;
}
if (lx.peek().value !== "after") break;
lx.next();
dependsOn.push(expect("ident").value);
while (lx.peek().type === "comma") {
lx.next();
dependsOn.push(expect("ident").value);
}
}
loads.push({ mode: modeToken.value, body: lx.readBalancedBraces() });
loads.push({
mode,
name,
...(dependsOn.length ? { dependsOn } : {}),
...(deferred ? { deferred: true } : {}),
body: lx.readBalancedBraces(),
});
break;
}
case "action": {
@@ -572,7 +633,12 @@ export function parse(source: string): PageAst {
}
expect("rparen");
}
actions.push({ name: actionName, args, body: lx.readBalancedBraces() });
let schema: string | undefined;
if (lx.peek().type === "ident" && lx.peek().value === "using") {
lx.next();
schema = expect("ident").value;
}
actions.push({ name: actionName, args, schema, body: lx.readBalancedBraces() });
break;
}
case "api": {
@@ -806,6 +872,38 @@ export function parse(source: string): PageAst {
);
functionKeys.add(key);
}
const namedLoads = new Map(loads.filter((load) => load.name).map((load) => [load.name!, load]));
for (const load of namedLoads.values()) {
for (const dependency of load.dependsOn ?? []) {
const dependencyLoad = namedLoads.get(dependency);
if (!dependencyLoad)
throw new ParseError(
`Load '${load.name}' depends on unknown load '${dependency}'`,
"WRN-LOAD-DEPENDENCY",
);
if (
load.mode === "server" &&
!load.deferred &&
(dependencyLoad.mode !== "server" || dependencyLoad.deferred)
)
throw new ParseError(
`Server load '${load.name}' cannot depend on deferred/client load '${dependency}'`,
"WRN-LOAD-PHASE",
);
}
}
const visiting = new Set<string>();
const visited = new Set<string>();
const visitLoad = (name: string): void => {
if (visiting.has(name))
throw new ParseError(`Load dependency cycle includes '${name}'`, "WRN-LOAD-CYCLE");
if (visited.has(name)) return;
visiting.add(name);
for (const dependency of namedLoads.get(name)?.dependsOn ?? []) visitLoad(dependency);
visiting.delete(name);
visited.add(name);
};
for (const name of namedLoads.keys()) visitLoad(name);
return {
type: "page",
imports,
@@ -816,7 +914,9 @@ export function parse(source: string): PageAst {
layout,
layoutIsSymbol,
runtime,
renderMode,
hydrate,
cache,
props,
events,
outputs,
@@ -827,6 +927,7 @@ export function parse(source: string): PageAst {
loads,
actions,
security,
navigation,
seo,
view,
styles,