Files
WRNexusJS/editors/vscode/src/compiler.cjs
T
ClintchizandClaude Opus 5 442a3106ed test(islands): guard zero-JS routes and single-React bundling
Two guards protect the core promise: a route with no islands emits no
assets at all, and a page with several islands keeps React in one shared
chunk.

buildIslands now writes a generated entry per island instead of passing
component sources directly. Two islands sharing a source deduped to a
single entrypoint, and output order is not guaranteed to match input
order, so island names could bind to the wrong bundle.

Island modules are excluded from the editor compiler bundle: it globs
packages/compiler/src, and island-bundle.ts calls Bun.build while
island-codegen.ts imports @wrnexus/core — neither belongs in a Node-only
VS Code artifact.

Integration assertions share one build. bun test interferes with
Bun.build's module reads after several build calls in one process, while
the same calls succeed repeatedly outside the runner; production is
unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 15:50:26 +05:30

7593 lines
306 KiB
JavaScript

"use strict";
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
// WRN editor compiler source hash: 182fd799ca860d927879d4259c182ea61cbd89d913758cc9f690e1ae4a35d90f
// WRN editor compiler generator hash: 2690208ba65bb00d9fea3e08cb3ab324cfda77792021cd46785814fadf41c1bc
// Generated with TypeScript: 6.0.3
const __nodeRequire = require;
const __path = __nodeRequire("node:path");
const __modules = {
"packages/compiler/src/analysis.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.optimizeAst = optimizeAst;
exports.analyzeOptimizations = analyzeOptimizations;
exports.routeNeedsIslands = routeNeedsIslands;
exports.analyzeRuntimeRequirements = analyzeRuntimeRequirements;
function identifiers(value) {
return new Set(value.match(/[A-Za-z_$][\w$]*/g) ?? []);
}
function literalBoolean(expression) {
if (expression === null)
return true;
const value = expression.trim();
if (value === "true")
return true;
if (value === "false" ||
value === "null" ||
value === "undefined" ||
value === "0" ||
value === "''" ||
value === '""')
return false;
if (/^-?(?:[1-9]\d*|0?\.\d+)$/.test(value) || /^(['"]).+\1$/.test(value))
return true;
return undefined;
}
function optimizeNodes(nodes, report) {
const output = [];
for (const node of nodes) {
if (node.type === "element")
output.push({
...node,
attrs: node.attrs.map((attribute) => ({ ...attribute })),
children: optimizeNodes(node.children, report),
});
else if (node.type === "each")
output.push({
...node,
body: optimizeNodes(node.body, report),
empty: optimizeNodes(node.empty, report),
});
else if (node.type === "if") {
let selected;
let dynamic = false;
for (const branch of node.branches) {
const value = literalBoolean(branch.cond);
if (value === undefined) {
dynamic = true;
break;
}
report.eliminated++;
if (value) {
selected = branch.body;
break;
}
}
if (dynamic)
output.push({
...node,
branches: node.branches.map((branch) => ({
...branch,
body: optimizeNodes(branch.body, report),
})),
});
else if (selected)
output.push(...optimizeNodes(selected, report));
}
else
output.push({ ...node });
}
return output;
}
/** Safe compile-time folding for literal conditional branches. */
function optimizeAst(ast) {
const report = { eliminated: 0 };
return {
ast: { ...ast, view: optimizeNodes(ast.view, report) },
eliminatedBranches: report.eliminated,
};
}
function analyzeOptimizations(ast) {
const used = new Set();
let staticNodes = 0;
let reactiveRegions = 0;
const componentNames = new Set();
const staticClasses = new Set();
const visit = (nodes) => {
for (const node of nodes) {
if (node.type === "text") {
const refs = identifiers(node.value);
refs.forEach((name) => used.add(name));
if (node.value.includes("{"))
reactiveRegions++;
else
staticNodes++;
}
else if (node.type === "element") {
if (/^[A-Z]/.test(node.tag))
componentNames.add(node.tag);
let reactive = false;
for (const attribute of node.attrs) {
identifiers(attribute.value).forEach((name) => used.add(name));
reactive ||= attribute.event || attribute.value.includes("{");
if (attribute.name === "class" && !attribute.value.includes("{"))
for (const name of attribute.value.split(/\s+/))
if (name)
staticClasses.add(name);
}
if (reactive)
reactiveRegions++;
else
staticNodes++;
visit(node.children);
}
else if (node.type === "each") {
identifiers(`${node.list} ${node.key ?? ""}`).forEach((name) => used.add(name));
reactiveRegions++;
visit(node.body);
visit(node.empty);
}
else {
for (const branch of node.branches) {
identifiers(branch.cond ?? "").forEach((name) => used.add(name));
visit(branch.body);
}
reactiveRegions++;
}
}
};
visit(ast.view);
const handlerReferences = new Set(used);
const executable = [
...ast.runtimeFunctions.map((fn) => fn.body),
...ast.functions,
...ast.effects.map((effect) => effect.body),
...ast.watches.map((watch) => watch.body),
...ast.actions.map((action) => action.body),
].join("\n");
identifiers(executable).forEach((name) => used.add(name));
const localCss = new Set(ast.styles.flatMap((style) => [...style.matchAll(/\.([_a-zA-Z][\w-]*)/g)].map((match) => match[1])));
const optimized = optimizeAst(ast);
const assignmentCounts = ast.runtimeFunctions.map((fn) => ast.states.filter((state) => new RegExp(`\\b${state.name}\\s*(?:[+*/-]?=|\\+\\+|--)`).test(fn.body)).length);
return {
staticNodes,
reactiveRegions,
eliminatedBranches: optimized.eliminatedBranches,
unusedState: ast.states.filter((state) => !used.has(state.name)).map((state) => state.name),
unusedHandlers: ast.runtimeFunctions
.filter((fn) => fn.runtime !== "server" && !handlerReferences.has(fn.name))
.map((fn) => fn.name),
constantProps: ast.props
.filter((prop) => /^(?:-?\d+(?:\.\d+)?|true|false|null|(['"]).*\1)$/.test(prop.default.trim()))
.map((prop) => prop.name),
unusedLocalCssClasses: [...localCss].filter((name) => !staticClasses.has(name)).sort(),
batchableStateUpdates: assignmentCounts
.filter((count) => count > 1)
.reduce((sum, count) => sum + count - 1, 0),
memoizableComponents: [...componentNames].sort(),
preloadDependencies: ast.structuredImports
.filter((entry) => !entry.typeOnly && !entry.source.startsWith("node:"))
.map((entry) => entry.source),
serverOnlyModules: ast.structuredImports
.filter((entry) => entry.source.startsWith("node:") || ast.runtime === "server")
.map((entry) => entry.source),
};
}
function hasEvent(nodes) {
for (const node of nodes) {
if (node.type === "element") {
if (node.attrs.some((attribute) => attribute.event))
return true;
if (hasEvent(node.children))
return true;
}
else if (node.type === "each") {
if (hasEvent(node.body) || hasEvent(node.empty))
return true;
}
else if (node.type === "if") {
if (node.branches.some((branch) => hasEvent(branch.body)))
return true;
}
}
return false;
}
/**
* A route containing a React island ships JavaScript and can no longer be
* classified as zero-JS static, so island presence must reach the classifier.
*/
function routeNeedsIslands(imports) {
return imports.some((entry) => entry.kind === "island");
}
function analyzeRuntimeRequirements(ast, options = {}) {
const hasIslands = options.hasIslands ?? false;
const reasons = [];
if (hasIslands)
reasons.push("react island");
const clientFunctions = ast.runtimeFunctions.some((fn) => fn.runtime !== "server");
const clientState = ast.states.some((state) => state.runtime !== "server");
const interactive = clientFunctions ||
clientState ||
ast.effects.length > 0 ||
ast.watches.length > 0 ||
hasEvent(ast.view);
if (interactive)
reasons.push("client interactivity");
const requestData = ast.loads.length > 0 ||
ast.actions.length > 0 ||
ast.dataApis.length > 0 ||
ast.apis.length > 0 ||
ast.realtimes.length > 0 ||
ast.runtimeFunctions.some((fn) => fn.runtime === "server") ||
ast.states.some((state) => state.runtime === "server");
if (requestData)
reasons.push("server/request data");
const authenticated = /^(?:required|true)$/i.test(ast.security.auth ?? "");
if (authenticated)
reasons.push("authentication required");
const streaming = /^(?:true|required)$/i.test(ast.security.streaming ?? "");
if (streaming)
reasons.push("streaming enabled");
let kind;
if (streaming)
kind = "streaming-ssr";
else if (authenticated)
kind = "authenticated-ssr";
else if (requestData && interactive)
kind = "dynamic";
else if (requestData)
kind = "request-ssr";
else if (interactive)
kind = "static-interactive";
else
kind = "static";
if (ast.renderMode === "static") {
kind = "static";
reasons.push("explicit static rendering");
}
else if (ast.renderMode === "server") {
kind = requestData ? "request-ssr" : "static";
reasons.push("explicit server rendering");
}
else if (ast.renderMode === "client") {
kind = "static-interactive";
reasons.push("explicit client rendering");
}
else if (ast.renderMode === "partial-static") {
kind = "streaming-ssr";
reasons.push("partial-static shell with streamed dynamic regions");
}
// An island ships JavaScript, so a would-be zero-JS static route must be
// reported as static-interactive. Explicit render modes still win above.
if (hasIslands && kind === "static")
kind = "static-interactive";
const clientDisabled = ast.renderMode === "static" || ast.renderMode === "server";
const serverDisabled = ast.renderMode === "client";
return {
kind,
canPrerender: kind === "static" || kind === "static-interactive",
needsIslandRuntime: hasIslands,
needsClientRuntime: !clientDisabled &&
(interactive || ast.renderMode === "client") &&
ast.hydrate !== "none" &&
ast.runtime !== "server",
needsServerRuntime: !serverDisabled &&
(requestData ||
authenticated ||
streaming ||
ast.renderMode === "server" ||
["server", "edge", "worker", "service-worker"].includes(ast.runtime ?? "")),
hydrationStrategy: clientDisabled ? null : interactive ? (ast.hydrate ?? "load") : null,
reasons,
optimization: analyzeOptimizations(ast),
cachePolicy: { ...(ast.cache ?? {}) },
requiredPermission: ast.security.permission ?? null,
};
}
},
"packages/compiler/src/cache.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DependencyGraph = void 0;
exports.compilationKey = compilationKey;
exports.createCompilationCache = createCompilationCache;
const node_crypto_1 = require("node:crypto");
const syntax_1 = require("@wrnexus/syntax");
const codegen_ts_1 = require("./codegen.js");
function compileSource(source, filePath) {
const richDiagnostics = (0, syntax_1.diagnose)(source, { file: filePath, accessibility: true });
const errors = richDiagnostics.filter((diagnostic) => diagnostic.severity === "error");
if (errors.length > 0) {
throw new syntax_1.ParseError(errors.map((diagnostic) => diagnostic.message).join("\n"), errors[0].code);
}
const ast = (0, syntax_1.parse)(source);
return {
code: `// compiled from .wrn\n${(0, codegen_ts_1.generate)(ast)}`,
ast,
diagnostics: richDiagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`),
richDiagnostics,
};
}
function compilationKey(source, file = "<inline .wrn>", salt = "") {
return (0, node_crypto_1.createHash)("sha256")
.update(file)
.update("\0")
.update(salt)
.update("\0")
.update(source)
.digest("hex");
}
function createCompilationCache(options = {}) {
const maxEntries = options.maxEntries ?? 500;
if (!Number.isInteger(maxEntries) || maxEntries < 1)
throw new RangeError("maxEntries must be positive");
const now = options.now ?? Date.now;
const entries = new Map();
let hits = 0;
let misses = 0;
function touch(key, value) {
entries.delete(key);
entries.set(key, value);
while (entries.size > maxEntries)
entries.delete(entries.keys().next().value);
}
return {
compile(source, file = "<inline .wrn>", salt = "") {
const key = compilationKey(source, file, salt);
const existing = entries.get(key);
if (existing) {
hits++;
touch(key, existing);
return existing;
}
misses++;
const result = compileSource(source, file);
const entry = {
...result,
key,
file,
sourceHash: (0, node_crypto_1.createHash)("sha256").update(source).digest("hex"),
createdAt: now(),
};
touch(key, entry);
return entry;
},
get(key) {
const entry = entries.get(key);
if (entry)
touch(key, entry);
return entry;
},
invalidate(file) {
let removed = 0;
for (const [key, entry] of entries) {
if (!file || entry.file === file) {
entries.delete(key);
removed++;
}
}
return removed;
},
clear() {
entries.clear();
},
size: () => entries.size,
stats: () => ({ hits, misses, entries: entries.size }),
};
}
class DependencyGraph {
#dependencies = new Map();
#dependents = new Map();
set(file, dependencies) {
this.remove(file);
const values = new Set(dependencies);
this.#dependencies.set(file, values);
for (const dependency of values) {
const set = this.#dependents.get(dependency) ?? new Set();
set.add(file);
this.#dependents.set(dependency, set);
}
}
remove(file) {
for (const dependency of this.#dependencies.get(file) ?? []) {
const set = this.#dependents.get(dependency);
set?.delete(file);
if (set?.size === 0)
this.#dependents.delete(dependency);
}
this.#dependencies.delete(file);
}
dependencies(file) {
return [...(this.#dependencies.get(file) ?? [])].sort();
}
dependents(file) {
return [...(this.#dependents.get(file) ?? [])].sort();
}
affected(file) {
const found = new Set();
const queue = [file];
while (queue.length) {
const current = queue.shift();
for (const dependent of this.#dependents.get(current) ?? []) {
if (found.has(dependent))
continue;
found.add(dependent);
queue.push(dependent);
}
}
return [...found].sort();
}
}
exports.DependencyGraph = DependencyGraph;
},
"packages/compiler/src/client-codegen.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.browserModuleRequired = browserModuleRequired;
exports.generateBrowserModule = generateBrowserModule;
const syntax_1 = require("@wrnexus/syntax");
const RESERVED_BINDINGS = new Set([
"await",
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"implements",
"import",
"in",
"instanceof",
"interface",
"let",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"static",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
"yield",
]);
const RUNTIME_BINDINGS = new Set([
"context",
"state",
"output",
"server",
"props",
"refs",
"event",
"payload",
]);
function safeIdentifier(name) {
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name);
}
function identifierReferenced(source, name) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return new RegExp(`(?:^|[^A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`).test(source);
}
function viewReferenceSource(nodes) {
return nodes.flatMap((node) => {
if (node.type === "text")
return [node.value];
if (node.type === "each") {
return [
node.list,
node.key ?? "",
...viewReferenceSource(node.body),
...viewReferenceSource(node.empty),
];
}
if (node.type === "if") {
return node.branches.flatMap((branch) => [
branch.cond ?? "",
...viewReferenceSource(branch.body),
]);
}
return [...node.attrs.map((attr) => attr.value), ...viewReferenceSource(node.children)];
});
}
function browserReferenceSource(ast, functions) {
return [
...functions.flatMap((fn) => [fn.source, fn.body]),
...ast.computed.map((entry) => entry.expr),
...ast.effects,
ast.lifecycle.mount ?? "",
ast.lifecycle.update ?? "",
ast.lifecycle.unmount ?? "",
...viewReferenceSource(ast.view),
].join("\n");
}
function renderSelectedImport(entry, source) {
if (entry.typeOnly)
return null;
const isStore = entry.source.endsWith(".wrn") && /(?:^|\/)stores?\//.test(entry.source);
if (entry.source.endsWith(".wrn") && !isStore)
return null;
const defaultImport = entry.defaultImport && (isStore || identifierReferenced(source, entry.defaultImport))
? entry.defaultImport
: undefined;
const namespaceImport = entry.namespaceImport && (isStore || identifierReferenced(source, entry.namespaceImport))
? entry.namespaceImport
: undefined;
const namedImports = entry.namedImports.filter((item) => !item.typeOnly && (isStore || identifierReferenced(source, item.local)));
const bindings = [
...(defaultImport ? [defaultImport] : []),
...(namespaceImport ? [namespaceImport] : []),
...namedImports.map((item) => item.local),
].filter(safeIdentifier);
const hasBindings = Boolean(defaultImport || namespaceImport || namedImports.length);
const sideEffectOnly = !entry.defaultImport && !entry.namespaceImport && entry.namedImports.length === 0;
if (!hasBindings && !sideEffectOnly)
return null;
if (sideEffectOnly)
return { code: entry.raw, bindings: [] };
const clauses = [];
if (defaultImport)
clauses.push(defaultImport);
if (namespaceImport)
clauses.push(`* as ${namespaceImport}`);
if (namedImports.length) {
clauses.push(`{ ${namedImports
.map((item) => item.imported === item.local ? item.imported : `${item.imported} as ${item.local}`)
.join(", ")} }`);
}
return {
code: `import ${clauses.join(", ")} from ${JSON.stringify(entry.source)};`,
bindings,
};
}
function selectedBrowserImports(ast, functions) {
const referenceSource = browserReferenceSource(ast, functions);
return ast.structuredImports
.map((entry) => renderSelectedImport(entry, referenceSource))
.filter((entry) => entry !== null);
}
function browserModuleRequired(ast) {
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
return functions.length > 0 || selectedBrowserImports(ast, functions).length > 0;
}
function _functionEntry(ast, fn, availableFunctions) {
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
/*
* Names the function body declares for itself.
*
* Props and state are destructured into the SAME scope as the body, so a
* body that declares `var size` when `size` is also a prop produced
* "Identifier 'size' has already been declared" and the entire module
* failed to parse -- taking every function in the component down with it,
* with nothing to point at the one line responsible. Skipping the alias for
* a shadowed name is also what plain JavaScript does: inside that function
* the local wins.
*/
const declaredLocals = new Set();
for (const match of fn.body.matchAll(/\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)|\bfunction\s+([A-Za-z_$][\w$]*)/g)) {
const name = match[1] ?? match[2];
if (name)
declaredLocals.add(name);
}
const stateNames = ast.states
.filter((state) => state.runtime !== "server" &&
safeIdentifier(state.name) &&
!RUNTIME_BINDINGS.has(state.name) &&
!parameterNames.has(state.name) &&
!declaredLocals.has(state.name))
.map((state) => state.name);
const stateSet = new Set(stateNames);
const propNames = ast.props
.filter((prop) => safeIdentifier(prop.name) &&
!RUNTIME_BINDINGS.has(prop.name) &&
!parameterNames.has(prop.name) &&
!stateSet.has(prop.name) &&
!declaredLocals.has(prop.name))
.map((prop) => prop.name);
const functionAliases = availableFunctions.filter((name) => safeIdentifier(name) &&
!RUNTIME_BINDINGS.has(name) &&
!parameterNames.has(name) &&
!stateSet.has(name) &&
!propNames.includes(name) &&
!declaredLocals.has(name));
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
const initialStateSnapshot = stateNames.length
? `const __wrnexusInitialState = { ${stateNames.map((name) => `${JSON.stringify(name)}: context.state.${name}`).join(", ")} };`
: "";
const stateAliases = stateNames.length ? `let { ${stateNames.join(", ")} } = context.state;` : "";
const propAliases = propNames.length ? `const { ${propNames.join(", ")} } = context.props;` : "";
const syncStateToContext = stateNames.map((name) => `context.state.${name} = ${name};`).join(" ");
const syncStateFromContext = stateNames
.map((name) => `${name} = context.state.${name};`)
.join(" ");
const peerAliases = !stateNames.length
? functionAliases
.map((name) => `const ${name} = (...__wrnexusPeerArgs) => context.functions[${JSON.stringify(name)}](...__wrnexusPeerArgs);`)
.join("\n")
: `const __wrnexusFlush = () => { ${syncStateToContext} };
const __wrnexusRestore = () => { ${syncStateFromContext} };
const __wrnexusPeer = (name, args) => {
__wrnexusFlush();
let result;
try {
result = context.functions[name](...args);
} catch (error) {
__wrnexusRestore();
throw error;
}
if (result && typeof result.then === "function") {
return Promise.resolve(result).finally(__wrnexusRestore);
}
__wrnexusRestore();
return result;
};
${functionAliases.map((name) => `const ${name} = (...__wrnexusPeerArgs) => __wrnexusPeer(${JSON.stringify(name)}, __wrnexusPeerArgs);`).join("\n")}`;
const copyBack = stateNames
.map((name) => `if (!Object.is(${name}, __wrnexusInitialState[${JSON.stringify(name)}])) context.state.${name} = ${name};`)
.join("\n");
const commitBinding = stateNames.length
? `const __wrnexusCommit = () => { ${copyBack} };
${!parameterNames.has("commit") && !declaredLocals.has("commit") && !functionAliases.includes("commit") ? "const commit = __wrnexusCommit;" : ""}
${!parameterNames.has("setTimeout") &&
!declaredLocals.has("setTimeout") &&
!functionAliases.includes("setTimeout")
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);`
: ""}`
: "";
const body = (0, syntax_1.eraseFunctionTypes)(fn.body);
const runtimeBindings = [
!parameterNames.has("output") ? "const output = context.output;" : "",
!parameterNames.has("server") ? "const server = context.server;" : "",
!parameterNames.has("props") ? "const props = context.props;" : "",
!parameterNames.has("refs") ? "const refs = context.refs;" : "",
]
.filter(Boolean)
.join("\n ");
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(context${parameters ? `, ${parameters}` : ""}) {
${initialStateSnapshot}
${stateAliases}
${propAliases}
${commitBinding}
${peerAliases}
${runtimeBindings}
try {
${body}
} finally {
${stateNames.length ? "__wrnexusCommit();" : ""}
}
}`;
}
function generateBrowserModule(ast) {
const functions = ast.runtimeFunctions.filter((fn) => ["legacy", "client", "shared"].includes(fn.runtime));
const functionNames = functions.map((fn) => fn.name);
const state = ast.states.filter((entry) => entry.runtime !== "server").map((entry) => entry.name);
const selectedImports = selectedBrowserImports(ast, functions);
const imports = selectedImports.map((entry) => entry.code).join("\n");
const importedBindings = [...new Set(selectedImports.flatMap((entry) => entry.bindings))];
const sharedState = state.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name));
const sharedProps = ast.props
.map((entry) => entry.name)
.filter((name) => safeIdentifier(name) && !RUNTIME_BINDINGS.has(name) && !sharedState.includes(name));
const callableAliases = functionNames.filter((name) => safeIdentifier(name) &&
!RUNTIME_BINDINGS.has(name) &&
!sharedState.includes(name) &&
!sharedProps.includes(name));
const sharedCommit = sharedState.map((name) => `context.state.${name} = ${name};`).join(" ");
const sharedRestore = [
...sharedState.map((name) => `${name} = context.state.${name};`),
...sharedProps.map((name) => `${name} = context.props.${name};`),
].join(" ");
const hasAuthoredCommit = callableAliases.includes("commit");
const hasAuthoredSetTimeout = callableAliases.includes("setTimeout");
const implementations = functions
.map((fn) => {
const parameters = fn.parameters.map((parameter) => parameter.name).join(", ");
return `${JSON.stringify(fn.name)}: ${fn.async ? "async " : ""}function(${parameters}) {
try { ${(0, syntax_1.eraseFunctionTypes)(fn.body)} } finally { __wrnexusCommit(); }
}`;
})
.join(",\n");
return `// generated WRNexusJS browser module for ${ast.name}
${imports}
export const __wrnexusClientFunctions = {
${functions
.map((fn) => ` ${JSON.stringify(fn.name)}: (context, ...args) => __wrnexusBindings(context)[${JSON.stringify(fn.name)}](...args)`)
.join(",\n")}
};
export const __wrnexusClientState = ${JSON.stringify(state)};
export const __wrnexusOutputs = ${JSON.stringify(ast.outputs)};
export const __wrnexusImportedBindings = { ${importedBindings.join(", ")} };
function __wrnexusCreateClientFunctions(context) {
${sharedState.length ? `let { ${sharedState.join(", ")} } = context.state;` : ""}
${sharedProps.length ? `let { ${sharedProps.join(", ")} } = context.props;` : ""}
const output = context.output;
const server = context.server;
const props = context.props;
const refs = context.refs;
const __wrnexusCommit = () => { ${sharedCommit} };
const __wrnexusRestore = () => { ${sharedRestore} };
${!hasAuthoredCommit ? "const commit = __wrnexusCommit;" : ""}
${!hasAuthoredSetTimeout
? `const setTimeout = (callback, delay, ...args) => globalThis.setTimeout(() => {
try { return callback(...args); } finally { __wrnexusCommit(); }
}, delay);`
: ""}
const implementations = {
${implementations}
};
${callableAliases.map((name) => `const ${name} = (...args) => implementations[${JSON.stringify(name)}](...args);`).join("\n ")}
const functions = {};
for (const name of Object.keys(implementations)) {
functions[name] = (...args) => {
__wrnexusRestore();
return implementations[name](...args);
};
}
return functions;
}
function __wrnexusBindings(context) {
return context.__wrnexusBoundFunctions ||
(context.__wrnexusBoundFunctions = __wrnexusCreateClientFunctions(context));
}
export function bindClientScope(context) {
return __wrnexusBindings(context);
}
`;
}
},
"packages/compiler/src/codegen.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
/**
* Code generation: lower a `.wrn` AST to TypeScript that targets the framework's
* existing primitives.
*
* state -> a `data-scope` declaration consumed by the runtime
* view -> an HTML string returned by a page component
* @event="..." -> data-on-<event>="..."
* "...{expr}..." -> text kept verbatim ({expr} is mustache for runtime)
* api="<name>" -> SSR/client data binding declared in a mode block
* ssrGet/ssrText -> legacy server-side API fetch + render
* csrGet/csrText -> legacy browser-side API fetch + render
* style -> tagged local stylesheet metadata promoted by SSR
* functions -> server-only helpers for API/realtime code
* api M /p {b} -> export const M = async (ctx) => { b }
* realtime {..} -> export const websocket = { evt(ws, ...args) { b } }
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.generate = generate;
exports.parseForExpr = parseForExpr;
const node_buffer_1 = require("node:buffer");
const parser_ts_1 = require("./parser.js");
const types_ts_1 = require("./types.js");
const syntax_1 = require("@wrnexus/syntax");
const store_codegen_ts_1 = require("./store-codegen.js");
const analysis_ts_1 = require("./analysis.js");
const client_codegen_ts_1 = require("./client-codegen.js");
function isComponentTag(tag) {
return /^[A-Z][A-Za-z0-9_$]*$/.test(tag);
}
const HTML_BOOLEAN_ATTRIBUTES = new Set([
"allowfullscreen",
"async",
"autofocus",
"autoplay",
"checked",
"controls",
"default",
"defer",
"disabled",
"formnovalidate",
"hidden",
"inert",
"ismap",
"itemscope",
"loop",
"multiple",
"muted",
"nomodule",
"novalidate",
"open",
"playsinline",
"readonly",
"required",
"reversed",
"selected",
]);
function isHtmlBooleanAttribute(name) {
return HTML_BOOLEAN_ATTRIBUTES.has(name.toLowerCase());
}
const URL_ATTRIBUTES = new Set([
"href",
"src",
"action",
"formaction",
"poster",
"cite",
"background",
"xlink:href",
]);
function stripAsciiControlAndSpace(value) {
let result = "";
for (const character of value) {
if (character.charCodeAt(0) > 0x20)
result += character;
}
return result;
}
function sanitizeUrlAttribute(value) {
const compact = stripAsciiControlAndSpace(value.trim());
const lower = compact.toLowerCase();
if (/^(?:javascript|vbscript|file):/.test(lower))
return "about:blank";
if (/^data:(?!image\/(?:png|gif|jpeg|webp|avif);)/.test(lower))
return "about:blank";
return value;
}
function safeAttributeValue(name, value) {
if (!URL_ATTRIBUTES.has(name.toLowerCase()) || value.includes("{"))
return value;
return sanitizeUrlAttribute(value);
}
/** Escape a value placed inside a double-quoted HTML attribute. */
function attrEscape(value) {
return value
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
/** Make HTML safe to embed inside a JS template literal. */
function templateEscape(html) {
return html.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
}
function styleEscape(css) {
return css.replace(/<\/style/gi, "<\\/style");
}
function attrValue(attrs, name) {
return attrs.find((attr) => !attr.event && attr.name === name)?.value;
}
function renderAttr(attr) {
if (attr.event)
return ` ${eventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
switch (attr.name) {
case "api":
case "ssrGet":
case "ssrText":
case "csrGet":
case "csrText":
return "";
default:
return attr.boolean
? ` ${attr.name}`
: ` ${attr.name}="${attrEscape(safeAttributeValue(attr.name, attr.value))}"`;
}
}
function eventAttribute(name) {
if (name.startsWith("window:")) {
return `data-on-window-${name.slice("window:".length)}`;
}
if (name.startsWith("document:")) {
return `data-on-document-${name.slice("document:".length)}`;
}
if (name.startsWith("browser-")) {
return `data-on-wrnexus-browser-${name.slice(8)}`;
}
if (name.startsWith("mobile-")) {
return `data-on-wrnexus-mobile-${name.slice(7)}`;
}
return `data-on-${name}`;
}
/**
* Event attribute for a handler written on a *component tag*
* (`<Modal @confirm="save()">`).
*
* These need their own attribute name. The mount's attributes are forwarded
* into the component and land on its view root, i.e. inside the component's
* own `data-scope` -- but the statement (`save()`) belongs to the parent that
* wrote the tag. Emitting `data-on-confirm` makes the child's runtime bind it
* against the child's scope, where the parent's functions and state do not
* exist, so the handler silently does nothing. `data-wrn-out-*` is ignored by
* the child and claimed by the mounting scope instead.
*
* `window:`/`document:` (and the browser/mobile bridges) keep the plain
* `data-on-*` form: those bind to a global target rather than to the element,
* and the runtime has no component-output path for them.
*/
function componentEventAttribute(name) {
if (name.startsWith("window:") ||
name.startsWith("document:") ||
name.startsWith("browser-") ||
name.startsWith("mobile-")) {
return eventAttribute(name);
}
return `data-wrn-out-${name}`;
}
function reactiveAttrValue(raw, reactive) {
let found = false;
const value = raw.replace(/\{([^{}]+)\}/g, (whole, inner) => {
const expr = inner.trim();
if (!exprRefsState(expr, reactive.stateNames))
return whole;
found = true;
try {
const result = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
return result == null ? "" : String(result);
}
catch {
return whole;
}
});
return found ? value : null;
}
function renderAttrs(attrs, csrId, reactive = null, dynamicExpressions) {
let bindIndex = 0;
const rendered = attrs
.map((attr) => {
const base = renderAttr(attr);
if (!reactive || attr.event || attr.boolean || !base || !attr.value.includes("{"))
return base;
const expression = wholeAttributeExpression(attr.value);
if (expression &&
exprRefsState(expression, reactive.runtimeStateNames) &&
dynamicExpressions) {
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
const marker = JSON.stringify([attr.name, attr.value]);
return ` ${attr.name}="${sentinel}" data-wrn-bind-${bindIndex++}="${attrEscape(marker)}"`;
}
const initial = reactiveAttrValue(attr.value, reactive);
if (initial === null)
return base;
const marker = JSON.stringify([attr.name, attr.value]);
return ` ${attr.name}="${attrEscape(URL_ATTRIBUTES.has(attr.name.toLowerCase()) ? sanitizeUrlAttribute(initial) : initial)}" data-wrn-bind-${bindIndex++}="${attrEscape(marker)}"`;
})
.join("");
return csrId ? `${rendered} data-wrnexus-csr="${attrEscape(csrId)}"` : rendered;
}
/**
* Replace i18n text sugar `{t:key}` with a `<span data-t="key">` marker the
* runtime resolves server-side. Other `{expr}` mustaches are left untouched.
*/
function substituteTMarkers(text) {
return text.replace(/\{t:([^{}]+)\}/g, (_m, key) => `<span data-t="${attrEscape(key.trim())}"></span>`);
}
/** Escape a value for safe embedding in HTML text. */
function htmlTextEscape(value) {
return value.replace(/[&<>]/g, (c) => (c === "&" ? "&amp;" : c === "<" ? "&lt;" : "&gt;"));
}
/**
* Evaluate a page's `state` seed expressions at compile time to obtain the
* initial SSR values used to bake `data-text` spans. Seeds may reference
* earlier ones; anything that can't be evaluated becomes `undefined`.
*/
function evalStateSeeds(states) {
const scope = {};
for (const s of states) {
try {
scope[s.name] = new Function("with(this){return (" + s.expr + ");}").call(scope);
}
catch {
scope[s.name] = undefined;
}
}
return scope;
}
/**
* Page text compilation: resolve `{t:key}` i18n markers, then bake state
* interpolations (`{count}`, `{count * 2}`) into `data-text` spans carrying the
* evaluated initial value — so no-JS clients see real content and the reactive
* runtime keeps it live. Non-state `{expr}` and un-evaluable expressions are
* left as literal client mustaches.
*/
function substituteReactiveText(raw, reactive, dynamicExpressions) {
const text = substituteTMarkers(raw);
if (!reactive || reactive.stateNames.size === 0)
return text;
return text.replace(/\{([^{}]+)\}/g, (whole, inner) => {
const expr = inner.trim();
if (expr.startsWith("t:") || !exprRefsState(expr, reactive.stateNames))
return whole;
if (exprRefsState(expr, reactive.runtimeStateNames) && dynamicExpressions) {
dynamicExpressions.push(`\${__wrnexusEscapeHtml(${expr})}`);
const sentinel = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
return `<span data-text="${attrEscape(expr)}">${sentinel}</span>`;
}
let value;
try {
value = new Function("with(this){return (" + expr + ");}").call(reactive.scope);
}
catch {
return whole; // can't evaluate → keep as a client-only mustache
}
const baked = htmlTextEscape(value == null ? "" : String(value));
return `<span data-text="${attrEscape(expr)}">${baked}</span>`;
});
}
/**
* Bake a loop-body text run into template-literal source: static text is escaped
* for the literal, `{expr}` becomes `${__wrnexusEscapeHtml(expr)}` (server-rendered,
* escaped), and `{t:key}` becomes a `data-t` marker resolved later by translateHtml.
*/
function bakeLoopText(raw) {
let out = "";
let last = 0;
let m;
const re = /\{([^{}]+)\}/g;
while ((m = re.exec(raw))) {
out += escLit(raw.slice(last, m.index));
const expr = m[1].trim();
if (expr.startsWith("t:")) {
out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`);
}
else {
out += "${__wrnexusEscapeHtml(" + expr + ")}";
}
last = m.index + m[0].length;
}
return out + escLit(raw.slice(last));
}
/** Bake a loop-body attribute value (same rules as text; escapeHtml is attribute-safe). */
function bakeLoopAttr(raw, typed = false) {
const wholeExpression = wholeAttributeExpression(raw);
if (typed && wholeExpression)
return "${__wrnexusPropAttr(" + wholeExpression + ")}";
if (!raw.includes("{"))
return escLit(attrEscape(raw));
let out = "";
let last = 0;
let m;
const re = /\{([^{}]+)\}/g;
while ((m = re.exec(raw))) {
out += escLit(attrEscape(raw.slice(last, m.index)));
out += "${__wrnexusEscapeHtml(" + m[1].trim() + ")}";
last = m.index + m[0].length;
}
return out + escLit(attrEscape(raw.slice(last)));
}
/** Render one loop-body node to template-literal source (nested loops inline). */
function renderLoopBody(node) {
if (node.type === "text") {
return bakeLoopText(node.value);
}
if (node.type === "each") {
return compileEachExpr(node);
}
if (node.type === "if") {
return compileIfExpr(node);
}
const componentTag = isComponentTag(node.tag);
const attrs = node.attrs
.filter((attr) => attr.name !== "data-component")
.map((attr) => {
const name = attr.event
? componentTag
? componentEventAttribute(attr.name)
: eventAttribute(attr.name)
: attr.name;
if (attr.boolean) {
return escLit(` ${name}`);
}
return escLit(` ${name}="`) + bakeLoopAttr(attr.value, componentTag) + escLit(`"`);
})
.join("");
const inner = node.children.map(renderLoopBody).join("");
if (node.tag === "Static")
return inner;
if (node.tag === "Dynamic")
return (escLit('<wrn-dynamic-region data-wrn-dynamic="true">') +
inner +
escLit("</wrn-dynamic-region>"));
if (node.tag === "KeepAlive") {
const key = node.attrs.find((attribute) => attribute.name === "key")?.value ?? "default";
return (escLit('<div data-wrn-keepalive="') +
bakeLoopAttr(key) +
escLit(`">`) +
inner +
escLit("</div>"));
}
if (node.tag === "Portal") {
const target = node.attrs.find((attribute) => attribute.name === "to")?.value ?? "body";
return (escLit('<div data-wrn-portal="') +
bakeLoopAttr(target) +
escLit('">') +
inner +
escLit("</div>"));
}
if (node.tag === "Transition") {
const name = node.attrs.find((attribute) => attribute.name === "name")?.value ?? "wrn-transition";
return (escLit('<div data-wrn-transition="') +
bakeLoopAttr(name) +
escLit('">') +
inner +
escLit("</div>"));
}
if (node.tag === "Component") {
const selected = node.attrs.find((attribute) => attribute.name === "is")?.value ?? "";
return (escLit('<div data-wrn-dynamic-component="') +
bakeLoopAttr(selected) +
escLit('">') +
inner +
escLit("</div>"));
}
if (componentTag) {
return (escLit(`<div data-component="${attrEscape(node.tag)}"`) +
attrs +
escLit(">") +
inner +
escLit("</div>"));
}
if (parser_ts_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return escLit(`<${node.tag}`) + attrs + escLit(">");
}
return escLit(`<${node.tag}`) + attrs + escLit(">") + inner + escLit(`</${node.tag}>`);
}
/**
* Compile a `{#each list as item}` block to a `${…}` template-literal interpolation
* that iterates the (server-evaluated) list and joins the per-item body. `list` is a
* JS expression evaluated where `ssr` data bindings are in scope as raw named values.
*/
function compileEachExpr(node) {
const item = node.item;
const index = node.index ?? "__wi";
const body = node.body.map(renderLoopBody).join("");
const empty = node.empty.map(renderLoopBody).join("");
return ("${(() => { const __wl = Array.isArray(" +
node.list +
") ? (" +
node.list +
") : []; return __wl.length ? __wl.map((" +
item +
", " +
index +
") => `" +
body +
'`).join("") : `' +
empty +
"`; })()}");
}
/**
* Compile a `{#if}` block to a `${…}` template-literal interpolation: a nested ternary
* that renders the first truthy branch's body (or the `{:else}` body, or "" when neither).
* Conditions are JS expressions evaluated in the surrounding server scope.
*/
function compileIfExpr(node) {
let expr = "``"; // no matching branch → empty string
for (let k = node.branches.length - 1; k >= 0; k--) {
const b = node.branches[k];
const bodySrc = "`" + b.body.map(renderLoopBody).join("") + "`";
expr = b.cond === null ? bodySrc : "(" + b.cond + ") ? " + bodySrc + " : " + expr;
}
return "${" + expr + "}";
}
/**
* Collect every server-control expression in a view (recursively): `{#each}` list
* expressions and `{#if}` conditions. Used to wrn up raw SSR data consts.
*/
function collectControlExprs(nodes, out = []) {
for (const node of nodes) {
if (node.type === "text")
continue;
if (node.type === "each") {
out.push(node.list);
collectControlExprs(node.body, out);
collectControlExprs(node.empty, out);
}
else if (node.type === "if") {
for (const b of node.branches) {
if (b.cond)
out.push(b.cond);
collectControlExprs(b.body, out);
}
}
else if (node.type === "element") {
collectControlExprs(node.children, out);
}
}
return out;
}
function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive = null) {
if (node.type === "text")
return substituteReactiveText(node.value, reactive, loops); // {t:key} + state baking
// Server control block (loop / conditional) → a sentinel that survives
// templateEscape, swapped for its real `${…}` code after escaping.
if (node.type === "each" || node.type === "if") {
loops.push(node.type === "each" ? compileEachExpr(node) : compileIfExpr(node));
return `\x00WRNEACH${loops.length - 1}\x00`;
}
if (node.tag === "Static" || node.tag === "Dynamic") {
const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
return node.tag === "Static"
? inner
: `<wrn-dynamic-region data-wrn-dynamic="true">${inner}</wrn-dynamic-region>`;
}
if (node.tag === "Portal" || node.tag === "Transition" || node.tag === "Component") {
const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
const attribute = node.tag === "Portal"
? "data-wrn-portal"
: node.tag === "Transition"
? "data-wrn-transition"
: "data-wrn-dynamic-component";
const source = node.tag === "Portal" ? "to" : node.tag === "Transition" ? "name" : "is";
const fallback = node.tag === "Portal" ? "body" : node.tag === "Transition" ? "wrn-transition" : "";
const original = node.attrs.find((item) => item.name === source);
const rendered = original
? renderAttrs([{ ...original, name: attribute }], undefined, reactive, loops)
: ` ${attribute}="${attrEscape(fallback)}"`;
return `<div${rendered}>${inner}</div>`;
}
if (node.tag === "Async") {
const source = attrValue(node.attrs, "source") ?? "data";
const retries = attrValue(node.attrs, "retries") ?? "2";
const tags = attrValue(node.attrs, "tags") ?? source;
const serverResolved = attrValue(node.attrs, "data-wrn-async-server") === "true";
const branchElement = (name) => node.children.find((child) => child.type === "element" && child.tag === name);
const branch = (name) => {
const element = branchElement(name);
return (element?.children ?? [])
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
};
const loading = branch("Loading");
const success = branch("Success");
const error = branch("Error");
const successElement = branchElement("Success");
const errorElement = branchElement("Error");
const identifier = (value, fallback) => value && isSafeGeneratedIdentifier(value) ? value : fallback;
const successAlias = identifier(successElement ? attrValue(successElement.attrs, "data") : undefined, identifier(source, "data"));
const errorAlias = identifier(errorElement ? attrValue(errorElement.attrs, "error") : undefined, "error");
const nested = (value) => value.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
const scoped = (value, alias, expression) => {
const index = loops.push(`\${(() => { const ${alias} = ${expression}; return \`${nested(value)}\`; })()}`) - 1;
return `\x00WRNEACH${index}\x00`;
};
const successTemplate = scoped(success, successAlias, `(ctx[${JSON.stringify(source)}] ?? {})`);
const errorTemplate = scoped(error, errorAlias, `{ message: "" }`);
let initial = loading;
if (serverResolved) {
const sourcePattern = successAlias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const serverSuccess = success.replace(new RegExp(`\\{\\s*(${sourcePattern}(?:\\.[A-Za-z_$][\\w$]*)*)\\s*\\}`, "g"), (_whole, expression) => `\${__wrnexusEscapeHtml(${expression})}`);
const index = loops.push(`\${ctx[${JSON.stringify(source)}] !== undefined ? (() => { const ${successAlias} = ctx[${JSON.stringify(source)}]; return \`${nested(serverSuccess)}\`; })() : \`${nested(loading)}\`}`) - 1;
initial = `\x00WRNEACH${index}\x00`;
}
return `<section data-wrn-async="${attrEscape(source)}" data-wrn-async-tags="${attrEscape(tags)}" data-wrn-async-retries="${attrEscape(retries)}"${serverResolved ? ' data-wrn-async-resolved="true"' : ""} aria-busy="${serverResolved ? "false" : "true"}"><div data-wrn-async-content>${initial}</div><template data-wrn-async-loading>${loading}</template><template data-wrn-async-success data-wrn-async-alias="${attrEscape(successAlias)}">${successTemplate}</template><template data-wrn-async-error data-wrn-async-alias="${attrEscape(errorAlias)}">${errorTemplate}</template></section>`;
}
if (node.tag === "KeepAlive") {
const key = attrValue(node.attrs, "key") ?? "default";
const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
return `<div data-wrn-keepalive="${attrEscape(key)}">${inner}</div>`;
}
if (isComponentTag(node.tag)) {
return renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive);
}
const apiName = attrValue(node.attrs, "api");
const apiBinding = apiName ? apiBindings.get(apiName) : undefined;
if (apiName && !apiBinding) {
throw new Error(`Unknown .wrn api binding "${apiName}"`);
}
const ssrGet = attrValue(node.attrs, "ssrGet");
const ssrText = attrValue(node.attrs, "ssrText");
const csrGet = attrValue(node.attrs, "csrGet");
const csrText = attrValue(node.attrs, "csrText");
const csrId = apiBinding?.mode === "client"
? csrMarker(csrBindings, renderBinding(apiBinding))
: csrGet && csrText
? csrMarker(csrBindings, {
method: "GET",
path: apiRoutePath(csrGet),
body: expressionBody(csrText),
helpers: "",
})
: undefined;
// Void elements (<br>, <img>, …) have no closing tag and no children.
if (parser_ts_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive, loops)}>`;
}
const inner = apiBinding?.mode === "ssr"
? ssrMarker(ssrBindings, renderBinding(apiBinding))
: ssrGet && ssrText
? ssrMarker(ssrBindings, {
method: "GET",
path: apiRoutePath(ssrGet),
body: expressionBody(ssrText),
helpers: "",
})
: node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive, loops)}>${inner}</${node.tag}>`;
}
function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive) {
const attrs = node.attrs
.filter((attr) => attr.name !== "data-component")
.map((attr) => renderPageComponentAttr(attr, loops))
.join("");
const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
return `<div data-component="${attrEscape(node.tag)}"` + `${attrs}>${inner}</div>`;
}
function renderNestedComponentInvocation(node, ctx) {
let bindIndex = 0;
const attrs = node.attrs
.filter((attr) => attr.name !== "data-component")
.map((attr) => {
const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(attr.name);
if (spread) {
return `\${__wrnSpreadAttrs(${ctx.resolveExpr(spread[1])})}`;
}
if (attr.event) {
return (escLit(` ${componentEventAttribute(attr.name)}="`) +
escLit(attrEscape(attr.value)) +
escLit(`"`));
}
if (attr.boolean) {
return ` ${attr.name}`;
}
const wholeExpression = wholeAttributeExpression(attr.value);
const compiledValue = wholeExpression
? `\${__wrnProp(${ctx.resolveExpr(wholeExpression)})}`
: compileAttrValue(attr.value, ctx);
const rendered = ` ${attr.name}="${compiledValue}"`;
if (!attr.value.includes("{") ||
(!exprRefsState(attr.value, ctx.stateNames) && !exprRefsState(attr.value, ctx.propNames))) {
return rendered;
}
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
return rendered + ` data-wrn-prop-bind-${bindIndex++}="${escLit(marker)}"`;
})
.join("");
const loops = loopVarsOf(node);
const childCtx = loops.length > 0
? {
...ctx,
forwardRestAttrs: false,
loopVars: new Set([...(ctx.loopVars ?? []), ...loops]),
}
: { ...ctx, forwardRestAttrs: false };
const inner = node.children.map((child) => renderComponentNode(child, childCtx)).join("");
return (`<div data-component="${attrEscape(node.tag)}"` +
`${ctx.forwardRestAttrs ? "${__wrnSpreadAttrs(__attrs)}" : ""}` +
`${attrs}>${inner}</div>`);
}
function ssrMarker(bindings, binding) {
const marker = `<!--wrnexus-ssr:${bindings.length}-->`;
bindings.push({ marker, ...binding });
return marker;
}
function csrMarker(bindings, binding) {
const id = String(bindings.length);
bindings.push({ id, ...binding });
return id;
}
function renderBinding(binding) {
return {
method: binding.method,
path: binding.path,
body: binding.body,
helpers: binding.helpers,
};
}
function hasClientBehavior(nodes) {
return nodes.some((node) => {
// `{t:key}` is i18n sugar resolved server-side — not client reactivity.
if (node.type === "text")
return /\{(?!t:)[^{}]+\}/.test(node.value);
// Server control blocks render on the server; they don't add client reactivity.
if (node.type === "each") {
return hasClientBehavior(node.body) || hasClientBehavior(node.empty);
}
if (node.type === "if") {
return node.branches.some((branch) => hasClientBehavior(branch.body));
}
return (node.attrs.some((attr) => attr.event || attr.name === "csrGet" || attr.name === "csrText") ||
hasClientBehavior(node.children));
});
}
function apiRoutePath(path) {
const trimmed = path.trim();
if (!trimmed.startsWith("/")) {
throw new Error(`.wrn API paths must start with "/": ${path}`);
}
if (trimmed.includes("\0") || trimmed.includes("\\") || /(^|\/)\.\.(\/|$)/.test(trimmed)) {
throw new Error(`Unsafe .wrn API path: ${path}`);
}
if (trimmed === "/api" || trimmed.startsWith("/api/"))
return trimmed;
return `/api${trimmed}`;
}
function expressionBody(expr) {
return `return (${expr});`;
}
function dataBody(source) {
const trimmed = source.trim();
if (!trimmed)
return "return undefined;";
return /\breturn\b/.test(trimmed) ? trimmed : expressionBody(trimmed);
}
function modeHelpers(ast, mode, sharedHelpers) {
return [
sharedHelpers,
...ast.modeFunctions
.filter((block) => block.mode === mode)
.map((block) => block.body.trim())
.filter(Boolean),
]
.filter(Boolean)
.join("\n\n");
}
function apiBindingMap(ast, sharedHelpers) {
const bindings = new Map();
for (const block of ast.dataApis) {
if (bindings.has(block.name)) {
throw new Error(`Duplicate .wrn api binding "${block.name}"`);
}
bindings.set(block.name, {
mode: block.mode,
method: block.method,
path: apiRoutePath(block.path),
body: dataBody(block.body),
helpers: modeHelpers(ast, block.mode, sharedHelpers),
});
}
return bindings;
}
function ssrRuntimeSource() {
return `const __wrnexusHtmlEscapes = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\\"": "&quot;", "'": "&#39;" };
function __wrnexusEscapeHtml(value: unknown): string {
return String(value).replace(/[&<>"']/g, (ch) => __wrnexusHtmlEscapes[ch] ?? ch);
}
type __WrnexusContext = import("@wrnexus/core").Context & {
__wrnexusCallApi?: (path: string, method: string) => Promise<unknown>;
localStorage?: unknown;
};
function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown {
const adapters = {
cookies: ctx.cookies,
session: ctx.session,
localStorage: ctx.localStorage,
};
return new Function("$data", "$adapters", "const cookies = $adapters.cookies;\\nconst session = $adapters.session;\\nconst localStorage = $adapters.localStorage;\\nwith ($data ?? {}) {\\n" + helpers + "\\n" + body + "\\n}")(data, adapters);
}
function __wrnexusPropAttr(
value: unknown,
): string {
const serialized =
value !== null &&
typeof value === "object"
? JSON.stringify(value)
: String(value == null ? "" : value);
return serialized.replace(
/[&<>"]/g,
(character) =>
character === "&"
? "&amp;"
: character === "<"
? "&lt;"
: character === ">"
? "&gt;"
: "&quot;",
);
}
async function __wrnexusCallApi(path: string, method: string, ctx: __WrnexusContext): Promise<unknown> {
if (typeof ctx.__wrnexusCallApi === "function") {
return await ctx.__wrnexusCallApi(path, method);
}
const url = new URL(path, ctx.req.url);
const res = await fetch(new Request(url, { method, headers: ctx.req.headers }));
if (!res.ok) {
throw new Error(".wrn data API request failed with status " + res.status);
}
const type = res.headers.get("content-type") || "";
return type.includes("application/json") ? await res.json() : await res.text();
}
async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise<string> {
for (const binding of __wrnexusSsrBindings) {
const data = await __wrnexusCallApi(binding.path, binding.method, ctx);
const value = __wrnexusEvalData(data, binding.body, binding.helpers, ctx);
html = html.replace(binding.marker, __wrnexusEscapeHtml(value));
}
return html;
}`;
}
function stableHash(value) {
let hash = 0x811c9dc5;
for (let index = 0; index < value.length; index++) {
hash ^= value.charCodeAt(index);
hash = Math.imul(hash, 0x01000193);
}
return (hash >>> 0).toString(36);
}
function hydrationId(ast) {
const shape = JSON.stringify({
kind: ast.kind,
name: ast.name,
props: ast.props.map((entry) => entry.name),
events: ast.events.map((entry) => entry.name),
states: ast.states.map((entry) => entry.name),
computed: ast.computed.map((entry) => entry.name),
view: ast.view,
});
return `${ast.name}:${stableHash(shape)}`;
}
function localStyleId(ast) {
return `wrn-${ast.kind}-${stableHash(`${ast.kind}:${ast.name}`)}`;
}
function localStyleTag(ast, styles) {
if (!styles.length)
return "";
const id = localStyleId(ast);
const css = styles.map(styleEscape).join("\n");
return `<style data-wrnexus-style="${attrEscape(ast.name)}" data-wrnexus-style-id="${attrEscape(id)}" data-wrnexus-style-owner="${attrEscape(ast.name)}" data-wrnexus-style-kind="${attrEscape(ast.kind)}">\n${css}\n</style>`;
}
function localStyleExport(ast, styles) {
if (!styles.length)
return null;
return `export const __wrnexusStyles = ${JSON.stringify([
{
id: localStyleId(ast),
owner: ast.name,
kind: ast.kind,
css: styles.join("\n"),
},
], null, 2)};`;
}
function isStoreImportSource(source) {
return /(?:^|\/)stores?\//.test(source) || source.startsWith("@wrnexus/store");
}
function importedStoreBindings(ast) {
return ast.structuredImports
.filter((entry) => entry.defaultImport && entry.source.endsWith(".wrn") && isStoreImportSource(entry.source))
.map((entry) => ({
local: entry.defaultImport,
internal: `__wrnexusStoreDefinition_${entry.defaultImport}`,
}));
}
function generatedImports(ast) {
const stores = new Map(importedStoreBindings(ast).map((entry) => [entry.local, entry.internal]));
return ast.structuredImports.map((entry) => {
if (!entry.defaultImport)
return entry.raw;
const internal = stores.get(entry.defaultImport);
return internal
? entry.raw.replace(new RegExp(`^(\\s*import\\s+(?:type\\s+)?)(?:${entry.defaultImport.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")})(\\s+from\\s+)`), `$1${internal}$2`)
: entry.raw;
});
}
function isSafeGeneratedIdentifier(name) {
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
}
function generateSsrStateAliases(stateNames) {
const names = [...new Set(stateNames)].filter(isSafeGeneratedIdentifier);
if (!names.length) {
return "";
}
return `const { ${names.join(", ")} } = __state;\n`;
}
function orderPageStates(entries) {
if (entries.length < 2)
return entries;
const byName = new Map(entries.map((entry) => [entry.name, entry]));
const visiting = new Set();
const visited = new Set();
const ordered = [];
const visit = (name) => {
if (visited.has(name))
return;
if (visiting.has(name)) {
throw new Error(`WRN-STATE-CYCLE: state value '${name}' has a dependency cycle.`);
}
const entry = byName.get(name);
if (!entry)
return;
visiting.add(name);
for (const dependency of byName.keys()) {
if (dependency !== name &&
new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr)) {
visit(dependency);
}
}
visiting.delete(name);
visited.add(name);
ordered.push(entry);
};
for (const entry of entries)
visit(entry.name);
return ordered;
}
function generateSsrStateInitializer(entries) {
const ordered = orderPageStates(entries);
const declarations = ordered
.filter((entry) => isSafeGeneratedIdentifier(entry.name))
.map((entry) => `const ${entry.name} = (() => { try { return (${entry.expr}); } catch { return undefined; } })();`)
.join(" ");
const values = entries
.map((entry) => {
if (isSafeGeneratedIdentifier(entry.name)) {
return `${JSON.stringify(entry.name)}: ${entry.name}`;
}
return `${JSON.stringify(entry.name)}: (() => { try { return (${entry.expr}); } catch { return undefined; } })()`;
})
.join(", ");
return `(() => { ${declarations} return { ${values} }; })()`;
}
function orderPageComputed(entries) {
if (entries.length < 2)
return entries;
const byName = new Map(entries.map((entry) => [entry.name, entry]));
const visiting = new Set();
const visited = new Set();
const ordered = [];
const visit = (name) => {
if (visited.has(name))
return;
if (visiting.has(name)) {
throw new Error(`WRN-COMPUTED-CYCLE: computed value '${name}' has a dependency cycle.`);
}
const entry = byName.get(name);
if (!entry)
return;
visiting.add(name);
for (const dependency of byName.keys()) {
if (dependency !== name &&
new RegExp(`\\b${dependency.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr)) {
visit(dependency);
}
}
visiting.delete(name);
visited.add(name);
ordered.push(entry);
};
for (const entry of entries)
visit(entry.name);
return ordered;
}
function generateSsrComputedAliases(entries) {
return orderPageComputed(entries)
.filter((entry) => isSafeGeneratedIdentifier(entry.name))
.map((entry) => `const ${entry.name} = (() => { try { return (${entry.expr}); } catch { return undefined; } })();`)
.join("\n");
}
function runtimeComputedNames(states, computed, runtimeRoots = []) {
const entries = [...states, ...computed];
const runtime = new Set([
...runtimeRoots,
...entries.filter((entry) => /\bctx\b/.test(entry.expr)).map((entry) => entry.name),
]);
let changed = true;
while (changed) {
changed = false;
for (const entry of entries) {
if (runtime.has(entry.name))
continue;
if ([...runtime].some((name) => new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(entry.expr))) {
runtime.add(entry.name);
changed = true;
}
}
}
return runtime;
}
function hydrationAttribute(ast) {
const strategy = ["static", "server"].includes(ast.renderMode ?? "")
? "none"
: (ast.hydrate ?? "load");
const hasBrowserModule = (0, client_codegen_ts_1.browserModuleRequired)(ast);
const moduleAttribute = hasBrowserModule
? ' data-wrn-client-module="__WRNEXUS_CLIENT_MODULE__"'
: "";
return ` data-wrn-hydration="${attrEscape(hydrationId(ast))}" data-wrn-hydrate="${attrEscape(strategy)}" data-wrn-runtime="${attrEscape(ast.runtime ?? "universal")}"${moduleAttribute}`;
}
function targetFunctions(ast, target) {
const runtimes = target === "browser"
? ["legacy", "client", "shared"]
: ["legacy", "server", "shared"];
return ast.functions
.map((body) => (0, syntax_1.stripRuntimeFunctionModifiers)(body, [...runtimes]))
.map((body) => body.trim())
.filter(Boolean)
.join("\n\n");
}
function publicOutputNames(ast) {
return [
...new Set([
...ast.outputs.map((output) => output.name),
...ast.events.map((event) => event.name),
]),
];
}
function prepareActionForms(nodes, actions) {
for (const node of nodes) {
if (node.type === "text")
continue;
if (node.type === "each") {
prepareActionForms(node.body, actions);
prepareActionForms(node.empty, actions);
continue;
}
if (node.type === "if") {
node.branches.forEach((branch) => prepareActionForms(branch.body, actions));
continue;
}
prepareActionForms(node.children, actions);
if (node.tag.toLowerCase() !== "form")
continue;
const submit = node.attrs.find((attr) => attr.event && attr.name === "submit");
if (!submit || !actions.has(submit.value.trim()))
continue;
const name = submit.value.trim();
node.attrs = node.attrs.filter((attr) => attr !== submit);
if (!node.attrs.some((attr) => !attr.event && attr.name === "method")) {
node.attrs.push({ name: "method", value: "post", event: false });
}
node.attrs.push({ name: "data-wrn-action", value: name, event: false });
node.children.unshift({
type: "element",
tag: "input",
attrs: [
{ name: "type", value: "hidden", event: false },
{ name: "name", value: "_wrnexus_action", event: false },
{ name: "value", value: name, event: false },
],
children: [],
});
}
}
function markServerAsyncBoundaries(nodes, serverLoads) {
for (const node of nodes) {
if (node.type === "text")
continue;
if (node.type === "each") {
markServerAsyncBoundaries(node.body, serverLoads);
markServerAsyncBoundaries(node.empty, serverLoads);
continue;
}
if (node.type === "if") {
node.branches.forEach((branch) => markServerAsyncBoundaries(branch.body, serverLoads));
continue;
}
if (node.tag === "Async") {
const source = attrValue(node.attrs, "source") ?? "data";
if (serverLoads.has(source) &&
!node.attrs.some((attribute) => attribute.name === "data-wrn-async-server")) {
node.attrs.push({ name: "data-wrn-async-server", value: "true", event: false });
}
}
markServerAsyncBoundaries(node.children, serverLoads);
}
}
function generate(ast) {
ast = (0, analysis_ts_1.optimizeAst)(ast).ast;
if (ast.kind === "global-store" || ast.kind === "page-store")
return (0, store_codegen_ts_1.generateStoreModule)(ast);
if (ast.kind === "component" || ast.kind === "layout") {
return generateComponent(ast);
}
const out = [];
prepareActionForms(ast.view, new Set(ast.actions.map((action) => action.name)));
markServerAsyncBoundaries(ast.view, new Set(ast.loads
.filter((load) => load.mode === "server" && !load.deferred && load.name)
.map((load) => load.name)));
if (ast.actions.length > 0) {
out.push(`import { createActionClient } from "@wrnexus/csr";\nimport type { InferSchema } from "@wrnexus/validation";`);
}
if (ast.imports.length > 0)
out.push(generatedImports(ast).join("\n"));
const ssrBindings = [];
const csrBindings = [];
const helpers = targetFunctions(ast, "server");
const apiBindings = apiBindingMap(ast, helpers);
const typeSource = ast.types
.map((body) => body.trim())
.filter(Boolean)
.join("\n\n");
if (typeSource)
out.push(typeSource);
if (helpers) {
out.push(`// --- .wrn functions ---\n${helpers}`);
}
// --- Page metadata / SEO ---
out.push(`export const meta = ${JSON.stringify({ title: ast.name, ...ast.seo }, null, 2)};`);
if (ast.layout)
out.push(`export const layout = ${ast.layoutIsSymbol ? ast.layout : JSON.stringify(ast.layout)};`);
out.push(`export const __wrnexusRuntime = ${JSON.stringify(ast.runtime ?? "universal")};`);
out.push(`export const __wrnexusRender = ${JSON.stringify(ast.renderMode ?? "hybrid")};`);
out.push(`export const __wrnexusHydrate = ${JSON.stringify(["static", "server"].includes(ast.renderMode ?? "") ? "none" : (ast.hydrate ?? "load"))};`);
out.push(`export const __wrnexusHydrationId = ${JSON.stringify(hydrationId(ast))};`);
if (Object.keys(ast.cache ?? {}).length > 0)
out.push(`export const __wrnexusCache = ${JSON.stringify(ast.cache, null, 2)};`);
if (Object.keys(ast.security).length > 0) {
out.push(`export const __wrnexusSecurity = ${JSON.stringify(ast.security, null, 2)};`);
}
if (Object.keys(ast.navigation).length > 0) {
out.push(`export const __wrnexusNavigation = ${JSON.stringify(ast.navigation, null, 2)};`);
}
// --- View -> default page component ---
const orderedStates = orderPageStates(ast.states);
const browserStates = ast.states.filter((state) => state.runtime !== "server");
const seedScope = evalStateSeeds(orderedStates);
const orderedComputed = orderPageComputed(ast.computed);
for (const entry of orderedComputed) {
try {
seedScope[entry.name] = new Function("with(this){return (" + entry.expr + ");}").call(seedScope);
}
catch {
seedScope[entry.name] = undefined;
}
}
const reactiveNames = [
...browserStates.map((entry) => entry.name),
...ast.computed.map((entry) => entry.name),
];
const storeBindings = importedStoreBindings(ast);
const runtimeRoots = [
...storeBindings.map((entry) => entry.local),
...ast.loads
.filter((load) => load.mode === "server" && !load.deferred && load.name)
.map((load) => load.name),
];
const runtimeStateNames = runtimeComputedNames(orderedStates, orderedComputed, runtimeRoots);
const reactive = reactiveNames.length > 0
? { stateNames: new Set(reactiveNames), runtimeStateNames, scope: seedScope }
: null;
const loops = [];
let html = ast.view
.map((node) => renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive))
.join("");
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const pageBehavior = ast.runtime === "server" ? null : componentBehavior(ast);
const needsClientRuntime = ast.runtime !== "server" &&
(browserStates.length > 0 ||
ast.computed.length > 0 ||
hasClientBehavior(ast.view) ||
pageBehavior !== null);
if (needsClientRuntime) {
const scopePlaceholder = "__WRNEXUS_DYNAMIC_SCOPE__";
html = `<div data-scope="${scopePlaceholder}"${behaviorAttribute(pageBehavior)}${hydrationAttribute(ast)}>${html}</div>`;
}
const pageStyleTag = localStyleTag(ast, styles);
if (pageStyleTag) {
html = `${pageStyleTag}${html}`;
}
if (ast.renderMode === "client") {
const clientRoot = hydrationId(ast);
html = `<div data-wrn-client-root="${clientRoot}" aria-busy="true"></div><template data-wrn-client-template="${clientRoot}">${html}</template>`;
}
const pageStyleExport = localStyleExport(ast, styles);
if (pageStyleExport)
out.push(pageStyleExport);
if (csrBindings.length > 0) {
out.push(`export const __wrnexusCsr = ${JSON.stringify(csrBindings, null, 2)};`);
}
if (pageBehavior) {
out.push(`export const __wrnexusBehavior = ${JSON.stringify(pageBehavior, null, 2)};`);
}
// Escape the static HTML for the template literal, then swap loop sentinels for
// their real `${…}` code (which must NOT be escaped).
let body = templateEscape(html);
let staticShellBody;
if (ast.renderMode === "partial-static") {
const shellHtml = html.replace(/<wrn-dynamic-region\b[^>]*>[\s\S]*?<\/wrn-dynamic-region>/gi, '<wrn-dynamic-region data-wrn-dynamic="true"></wrn-dynamic-region>');
staticShellBody = templateEscape(shellHtml);
}
const dynamicStateInitializer = generateSsrStateInitializer(orderedStates);
const stateType = ast.states.length > 0
? `{ ${ast.states
.map((state) => `${JSON.stringify(state.name)}: ${state.valueType ?? "unknown"}`)
.join("; ")} }`
: "Record<string, never>";
const hydrationStateNames = JSON.stringify(browserStates.map((state) => state.name));
const ssrStateAliases = generateSsrStateAliases(ast.states.map((state) => state.name));
const ssrComputedAliases = generateSsrComputedAliases(orderedComputed);
const storeDeclarations = storeBindings
.map((entry) => ` const ${entry.local} = await ctx.__wrnexusUseStore(${entry.internal});`)
.join("\n");
const serverLoadAliases = ast.loads
.filter((load) => load.mode === "server" && !load.deferred && load.name)
.map((load) => ` const ${load.name} = ctx[${JSON.stringify(load.name)}];`)
.join("\n");
// Inner dynamic expressions are registered before the wrapper that scopes
// them (for example an Async branch alias). Resolve from the outside in so a
// wrapper sentinel is expanded before its nested sentinels are visited.
for (let idx = loops.length - 1; idx >= 0; idx--) {
const code = loops[idx];
body = body.replaceAll(`\x00WRNEACH${idx}\x00`, code);
if (staticShellBody?.includes(`\x00WRNEACH${idx}\x00`)) {
staticShellBody = staticShellBody.replaceAll(`\x00WRNEACH${idx}\x00`, code);
}
}
// Server loops iterate raw SSR data. Declare a named const for every `ssr` data
// binding a loop references, so `{#each <name> as …}` can iterate the real value.
const loopConsts = [];
if (loops.length > 0) {
const lists = collectControlExprs(ast.view);
for (const [name, binding] of apiBindings) {
if (binding.mode !== "ssr")
continue;
if (!lists.some((expr) => new RegExp(`\\b${name}\\b`).test(expr)))
continue;
loopConsts.push(` const ${name} = __wrnexusEvalData(await __wrnexusCallApi(${JSON.stringify(binding.path)}, ${JSON.stringify(binding.method)}, ctx), ${JSON.stringify(binding.body)}, ${JSON.stringify(binding.helpers)}, ctx);`);
}
}
const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || runtimeStateNames.size > 0;
if (needsSsrRuntime) {
out.push(ssrRuntimeSource());
out.push(`const __wrnexusSsrBindings = ${JSON.stringify(ssrBindings, null, 2)};`);
const decls = loopConsts.length > 0 ? loopConsts.join("\n") + "\n" : "";
out.push(`export default async function ${ast.name}(ctx: __WrnexusContext) {
${storeDeclarations}
${serverLoadAliases}
${decls}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
try {
encoded = value === undefined ? "undefined" : JSON.stringify(value);
} catch {
encoded = JSON.stringify(String(value));
}
return key + ": " + encoded;
})
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const html = \`${body}\`.replace(
"__WRNEXUS_DYNAMIC_SCOPE__",
__scopeValue,
);
return await __wrnexusRenderSsrBindings(html, ctx);
}`);
}
else {
out.push(`export default ${storeBindings.length > 0 ? "async " : ""}function ${ast.name}(ctx: import("@wrnexus/core").Context) {
${storeDeclarations}
${serverLoadAliases}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
try {
encoded = value === undefined ? "undefined" : JSON.stringify(value);
} catch {
encoded = JSON.stringify(String(value));
}
return key + ": " + encoded;
})
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return \`${body}\`.replace(
"__WRNEXUS_DYNAMIC_SCOPE__",
__scopeValue,
);
}`);
}
if (staticShellBody !== undefined) {
out.push(`export async function __wrnexusBuildStaticShell(ctx: import("@wrnexus/core").Context = {} as import("@wrnexus/core").Context) {
${storeDeclarations}
${serverLoadAliases}
${loopConsts.length > 0 ? loopConsts.join("\n") : ""}
const __state: ${stateType} = ${dynamicStateInitializer};
${ssrStateAliases}
${ssrComputedAliases}
const __hydrationState = Object.fromEntries(${hydrationStateNames}.map((key) => [key, Reflect.get(__state, key)]));
const __scopeValue = Object.entries(__hydrationState)
.map(([key, value]) => {
let encoded: string;
try {
encoded = value === undefined ? "undefined" : JSON.stringify(value);
} catch {
encoded = JSON.stringify(String(value));
}
return key + ": " + encoded;
})
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return \`${staticShellBody}\`.replace("__WRNEXUS_DYNAMIC_SCOPE__", __scopeValue);
}`);
}
if (ast.loads.length > 0) {
const serverLoads = ast.loads.filter((entry) => entry.mode === "server" && !entry.deferred);
const publicClientLoads = ast.loads.filter((entry) => entry.mode === "client" || entry.deferred);
const namedByName = new Map(ast.loads.filter((entry) => entry.name).map((entry) => [entry.name, entry]));
const clientNames = new Set(publicClientLoads.flatMap((entry) => (entry.name ? [entry.name] : [])));
const includeDependencies = (name) => {
for (const dependency of namedByName.get(name)?.dependsOn ?? []) {
if (clientNames.has(dependency))
continue;
clientNames.add(dependency);
includeDependencies(dependency);
}
};
for (const name of [...clientNames])
includeDependencies(name);
const clientLoads = ast.loads.filter((entry) => !entry.name || clientNames.has(entry.name));
const renderLoads = (exportName, execution, exposed) => {
const declarations = execution
.filter((entry) => entry.name)
.map((entry) => {
const dependencies = (entry.dependsOn ?? [])
.map((dependency) => `const ${dependency} = await __load_${dependency}();`)
.join("\n");
return ` let __promise_${entry.name}: Promise<unknown> | undefined;
const __load_${entry.name} = () => (__promise_${entry.name} ??= (async () => {
${dependencies}
${entry.body}
})());`;
})
.join("\n");
const visible = exposed.filter((entry) => entry.name);
return `export async function ${exportName}(ctx: import("@wrnexus/core").Context) {
${exposed
.filter((entry) => !entry.name)
.map((entry) => entry.body)
.join("\n")}
${declarations}
${visible.length
? ` const __values = await Promise.all([${visible.map((entry) => `__load_${entry.name}()`).join(", ")}]);
return { ${visible.map((entry, index) => `${JSON.stringify(entry.name)}: __values[${index}]`).join(", ")} };`
: ""}
}`;
};
if (serverLoads.length > 0)
out.push(renderLoads("__wrnexusLoad", serverLoads, serverLoads));
if (publicClientLoads.length > 0)
out.push(renderLoads("__wrnexusClientLoad", clientLoads, publicClientLoads));
}
if (ast.actions.length > 0) {
for (const action of ast.actions) {
if (!action.schema) {
out.push(`export async function ${action.name}(${action.args.join(", ")}) {${action.body}}`);
continue;
}
out.push(`export async function ${action.name}(input: InferSchema<typeof ${action.schema}>, ctx: import("@wrnexus/core").Context) {
const invalidate = (...tags: string[]) => {
const bucket = (ctx.locals.__wrnexusInvalidatedTags ??= []);
bucket.push(...tags.flat());
};
${action.body}
}`);
}
out.push(`export const __wrnexusActions = { ${ast.actions
.map((action) => `${action.name}: { run: ${action.name}, schema: ${action.schema ?? "undefined"} }`)
.join(", ")} };`);
out.push(`export const __wrnexusActionClients = {
${ast.actions
.map((action) => ` ${action.name}: createActionClient<${action.schema ? `InferSchema<typeof ${action.schema}>` : "Record<string, unknown>"}, Awaited<ReturnType<typeof ${action.name}>>>("", ${JSON.stringify(action.name)}),`)
.join("\n")}
};`);
}
// --- API blocks -> method handlers ---
if (ast.apis.length > 0) {
ast.apis.forEach((api, index) => {
const name = `__wrnexusApi_${api.method}_${index}`;
out.push(`// ${api.method} ${apiRoutePath(api.path)}
const ${name} = async (ctx: import("@wrnexus/core").Context) => {${api.body}};`);
});
const entries = ast.apis.map((api, index) => ` ${JSON.stringify(`${api.method} ${apiRoutePath(api.path)}`)}: __wrnexusApi_${api.method}_${index},`);
out.push(`export const __wrnexusApi = {\n${entries.join("\n")}\n};`);
const exported = new Set();
ast.apis.forEach((api, index) => {
if (exported.has(api.method))
return;
exported.add(api.method);
out.push(`export const ${api.method} = __wrnexusApi_${api.method}_${index};`);
});
}
// --- Realtime blocks -> a websocket export ---
if (ast.realtimes.length > 0) {
const handlers = ast.realtimes.flatMap((rt) => rt.handlers.map((h) => {
const params = ["ws", ...h.args].join(", ");
return ` ${h.event}(${params}: Event) {${h.body}},`;
}));
out.push(`export const websocket = {\n${handlers.join("\n")}\n};`);
}
return out.join("\n\n") + "\n";
}
/** Parse a `data-for="item in list [key expr]"` / `"item, i in list [key expr]"` directive. */
function parseForExpr(value) {
const m = /^\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\s+in\s+([\s\S]+?)(?:\s+key\s+([\s\S]+?))?\s*$/.exec(value);
if (!m)
return null;
return { item: m[1], index: m[2], list: m[3].trim(), key: m[4]?.trim() };
}
/** The loop variables a node introduces via `data-for`, if any. */
function loopVarsOf(node) {
if (node.type !== "element")
return [];
const attr = node.attrs.find((a) => !a.event && a.name === "data-for");
if (!attr)
return [];
const parsed = parseForExpr(attr.value);
return parsed ? [parsed.item, ...(parsed.index ? [parsed.index] : [])] : [];
}
/** JS reserved words that cannot be used as a plain `const` name. */
const JS_RESERVED = new Set([
"class",
"for",
"default",
"function",
"return",
"if",
"else",
"new",
"delete",
"typeof",
"in",
"instanceof",
"void",
"do",
"while",
"switch",
"case",
"break",
"continue",
"this",
"super",
"import",
"export",
"extends",
"var",
"let",
"const",
"null",
"true",
"false",
"try",
"catch",
"finally",
"throw",
"yield",
"await",
"enum",
"with",
"debugger",
"implements",
"interface",
"package",
"private",
"protected",
"public",
"static",
]);
/** A JS reference for a prop/state name (reserved words get a `__p_` prefix). */
function safeRef(name) {
return JS_RESERVED.has(name) ? `__p_${name}` : name;
}
/** Escape a literal segment so it is safe inside a JS template literal. */
function escLit(s) {
return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
}
function componentBehavior(ast) {
const functions = (0, types_ts_1.eraseFunctionTypes)(targetFunctions(ast, "browser"));
const computed = ast.computed.map((entry) => ({ name: entry.name, expr: entry.expr.trim() }));
const effects = ast.effects.map((entry) => entry.body.trim()).filter(Boolean);
const lifecycle = {
...(ast.lifecycle.mount?.trim() ? { mount: ast.lifecycle.mount.trim() } : {}),
...(ast.lifecycle.update?.trim() ? { update: ast.lifecycle.update.trim() } : {}),
...(ast.lifecycle.unmount?.trim() ? { unmount: ast.lifecycle.unmount.trim() } : {}),
};
const watches = ast.watches.map((watch) => ({
state: watch.state,
body: watch.body.trim(),
}));
if (!functions &&
ast.outputs.length === 0 &&
computed.length === 0 &&
effects.length === 0 &&
Object.keys(lifecycle).length === 0 &&
watches.length === 0) {
return null;
}
return {
functions,
outputs: ast.outputs,
computed,
effects,
lifecycle,
watches,
};
}
function behaviorAttribute(behavior) {
if (!behavior) {
return "";
}
const encoded = node_buffer_1.Buffer.from(JSON.stringify(behavior), "utf8").toString("base64");
return ` data-wrn-behavior="${encoded}"`;
}
const INTERP_RE = /\{([^{}]+)\}/g;
function exprRefsState(expr, stateNames) {
for (const name of stateNames) {
if (new RegExp(`\\b${name}\\b`).test(expr))
return true;
}
return false;
}
function exprRefsComponentReactiveValue(expr, ctx) {
return (exprRefsState(expr, ctx.stateNames) ||
exprRefsState(expr, ctx.propNames) ||
exprRefsState(expr, ctx.functionNames));
}
function viewHasEvents(nodes) {
return nodes.some((node) => {
if (node.type === "text")
return false;
if (node.type === "each") {
return viewHasEvents(node.body) || viewHasEvents(node.empty);
}
if (node.type === "if") {
return node.branches.some((branch) => viewHasEvents(branch.body));
}
return node.attrs.some((attr) => attr.event) || viewHasEvents(node.children);
});
}
function viewHasServerEach(nodes) {
return nodes.some((node) => {
if (node.type === "text")
return false;
if (node.type === "each")
return true;
if (node.type === "if") {
return node.branches.some((branch) => viewHasServerEach(branch.body));
}
return viewHasServerEach(node.children);
});
}
function viewHasRestAttributeSpread(nodes) {
return nodes.some((node) => {
if (node.type === "text")
return false;
if (node.type === "each") {
return viewHasRestAttributeSpread(node.body) || viewHasRestAttributeSpread(node.empty);
}
if (node.type === "if") {
return node.branches.some((branch) => viewHasRestAttributeSpread(branch.body));
}
return (node.attrs.some((attr) => /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.test(attr.name)) ||
viewHasRestAttributeSpread(node.children));
});
}
/**
* Compile a text node. Interpolations that reference state stay as client
* mustaches (`{expr}`, hydrated by the reactive runtime); interpolations of
* props/constants are baked server-side (`${__wrnHtml(expr)}`), so static
* components render correct HTML with zero JavaScript.
*/
function compileText(raw, ctx) {
let out = "";
let last = 0;
let m;
INTERP_RE.lastIndex = 0;
while ((m = INTERP_RE.exec(raw))) {
out += escLit(raw.slice(last, m.index));
const expr = m[1].trim();
if (expr.startsWith("t:")) {
// i18n sugar: {t:key} → a marker resolved server-side by translateHtml.
out += escLit(`<span data-t="${attrEscape(expr.slice(2).trim())}"></span>`);
}
else if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
// Loop variable (from data-for): leave a literal client mustache — the
// list renderer fills it per item; it has no server-side value.
out += escLit(`{${expr}}`);
}
else if (expr === "content") {
out += `\${__wrnRaw(${ctx.resolveExpr(expr)})}`;
}
else if (exprRefsComponentReactiveValue(expr, ctx)) {
// State interpolation: bake the initial value AND keep it reactive via a
// data-text span, so no-JS clients see the real value and hydration
// updates it in place. `count` → `<span data-text="count">0</span>`.
out +=
escLit(`<span data-text="${attrEscape(expr)}">`) +
`\${__wrnHtml(${ctx.resolveExpr(expr)})}` +
escLit(`</span>`);
}
else {
out += `\${__wrnHtml(${ctx.resolveExpr(expr)})}`;
}
last = m.index + m[0].length;
}
return out + escLit(raw.slice(last));
}
/** Compile an attribute value; `{expr}` is baked server-side (loop vars stay literal). */
function compileAttrValue(raw, ctx) {
if (!raw.includes("{"))
return escLit(attrEscape(raw));
let out = "";
let last = 0;
let m;
INTERP_RE.lastIndex = 0;
while ((m = INTERP_RE.exec(raw))) {
out += escLit(attrEscape(raw.slice(last, m.index)));
const expr = m[1].trim();
if (ctx.loopVars && exprRefsState(expr, ctx.loopVars)) {
out += escLit(`{${expr}}`); // hydrated per-item by the list renderer
}
else {
out += `\${__wrnAttr(${ctx.resolveExpr(expr)})}`;
}
last = m.index + m[0].length;
}
return out + escLit(attrEscape(raw.slice(last)));
}
function renderComponentIfNode(node, ctx) {
let expression = "``";
for (let index = node.branches.length - 1; index >= 0; index--) {
const branch = node.branches[index];
const body = branch.body.map((child) => renderComponentNode(child, ctx)).join("");
const bodyExpression = "`" + body + "`";
expression =
branch.cond === null
? bodyExpression
: `(${ctx.resolveExpr(branch.cond)}) ? ${bodyExpression} : ${expression}`;
}
return "${" + expression + "}";
}
function renderComponentEachNode(node, ctx) {
const item = node.item;
const index = node.index ?? "__wi";
const list = ctx.resolveExpr(node.list);
const childCtx = {
...ctx,
serverLocals: new Set([...(ctx.serverLocals ?? []), item, index]),
};
const body = node.body.map((child) => renderComponentNode(child, childCtx)).join("");
const empty = node.empty.map((child) => renderComponentNode(child, ctx)).join("");
return ("${(() => { const __wl = Array.isArray(" +
list +
") ? (" +
list +
") : []; return __wl.length ? __wl.map((" +
item +
", " +
index +
") => `" +
body +
'`).join("") : `' +
empty +
"`; })()}");
}
function serverLoopLocalsAttribute(ctx) {
const locals = [...(ctx.serverLocals ?? [])];
if (locals.length === 0) {
return "";
}
const entries = locals.map((name) => `${JSON.stringify(name)}: ${name}`).join(", ");
return ` data-wrn-loop-locals="\${__wrnexusEncodeLoopLocals({ ${entries} })}"`;
}
function unwrapDirectiveExpression(raw) {
const value = raw.trim();
if (!value.startsWith("{") || !value.endsWith("}")) {
return value;
}
let depth = 0;
let quote = null;
let escaped = false;
for (let index = 0; index < value.length; index++) {
const char = value[index];
if (escaped) {
escaped = false;
continue;
}
if (quote) {
if (char === "\\") {
escaped = true;
}
else if (char === quote) {
quote = null;
}
continue;
}
if (char === '"' || char === "'" || char === "`") {
quote = char;
continue;
}
if (char === "{")
depth++;
if (char === "}")
depth--;
if (depth === 0 && index < value.length - 1) {
return value;
}
}
return depth === 0 ? value.slice(1, -1).trim() : value;
}
/** Render a component view node into template-literal-ready source. */
function renderComponentNode(node, ctx) {
if (node.type === "text")
return compileText(node.value, ctx);
if (node.type === "each") {
return renderComponentEachNode(node, ctx);
}
if (node.type === "if") {
return renderComponentIfNode(node, ctx);
}
if (node.tag === "Static" || node.tag === "Dynamic") {
const inner = node.children.map((child) => renderComponentNode(child, ctx)).join("");
return node.tag === "Static"
? inner
: `<wrn-dynamic-region data-wrn-dynamic="true">${inner}</wrn-dynamic-region>`;
}
if (node.tag === "KeepAlive") {
const key = node.attrs.find((attribute) => attribute.name === "key")?.value ?? "default";
const inner = node.children.map((child) => renderComponentNode(child, ctx)).join("");
return `<div data-wrn-keepalive="${compileAttrValue(key, ctx)}">${inner}</div>`;
}
if (node.tag === "Portal" || node.tag === "Transition" || node.tag === "Component") {
const inner = node.children.map((child) => renderComponentNode(child, ctx)).join("");
const attribute = node.tag === "Portal"
? "data-wrn-portal"
: node.tag === "Transition"
? "data-wrn-transition"
: "data-wrn-dynamic-component";
const source = node.tag === "Portal" ? "to" : node.tag === "Transition" ? "name" : "is";
const fallback = node.tag === "Portal" ? "body" : node.tag === "Transition" ? "wrn-transition" : "";
const raw = node.attrs.find((item) => item.name === source)?.value ?? fallback;
return `<div ${attribute}="${compileAttrValue(raw, ctx)}">${inner}</div>`;
}
if (isComponentTag(node.tag)) {
return renderNestedComponentInvocation(node, ctx);
}
const loopVariables = loopVarsOf(node);
const elementContext = {
...ctx,
forwardRestAttrs: false,
...(loopVariables.length > 0
? { loopVars: new Set([...(ctx.loopVars ?? []), ...loopVariables]) }
: {}),
};
let bindIndex = 0;
const staticClasses = [];
const conditionalClasses = [];
for (const attr of node.attrs) {
if (!attr.event && attr.name === "class") {
staticClasses.push(attr.value);
}
if (!attr.event && attr.name.startsWith("class:")) {
conditionalClasses.push({
className: attr.name.slice("class:".length),
expression: unwrapDirectiveExpression(attr.value),
});
}
}
const isExplicitComponentMount = node.attrs.some((attribute) => attribute.name === "data-component");
const attrs = node.attrs
.filter((a) => a.name !== "class" && !a.name.startsWith("class:"))
.map((a) => {
const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(a.name);
if (spread) {
return `\${__wrnSpreadAttrs(${elementContext.resolveExpr(spread[1])})}`;
}
if (a.event) {
return ` ${eventAttribute(a.name)}="${escLit(attrEscape(a.value))}"`;
}
if (a.boolean) {
return ` ${a.name}`;
}
if (isHtmlBooleanAttribute(a.name)) {
const expression = wholeAttributeExpression(a.value);
if (expression) {
const referencesState = exprRefsComponentReactiveValue(a.value, ctx);
const referencesLoopVariable = elementContext.loopVars
? exprRefsState(a.value, elementContext.loopVars)
: false;
const referencesServerLocal = ctx.serverLocals
? exprRefsState(a.value, ctx.serverLocals)
: false;
const marker = referencesState || referencesLoopVariable || referencesServerLocal
? ` data-wrn-bind-${bindIndex++}="${escLit(attrEscape(JSON.stringify([a.name, a.value])))}"`
: "";
/*
* A boolean attribute whose expression names a loop variable cannot
* be resolved on the server: __wrnBooleanAttr runs at render time,
* where `row` or `item` simply does not exist, and the emitted
* module blew up. Leave the attribute off the server output and let
* the client bind set it -- the runtime toggles boolean attributes
* rather than stringifying them, so `checked={isSelected(row)}`
* behaves correctly once hydrated.
*/
if (referencesLoopVariable) {
return ` data-wrn-bind-${bindIndex++}="${escLit(attrEscape(JSON.stringify([a.name, a.value])))}"`;
}
return `\${__wrnBooleanAttr(${JSON.stringify(a.name)}, ${elementContext.resolveExpr(expression)})}${marker}`;
}
if (a.value === "false")
return "";
if (a.value === "true" || a.value === "")
return ` ${a.name}`;
}
const wholeExpression = wholeAttributeExpression(a.value);
/*
* data-show carries an EXPRESSION, not a value. The client re-evaluates
* whatever string it finds in the attribute on every state change, so
* interpolating `{open || visible}` down to the literal "false" at
* render time froze the directive: the element could never be shown
* again, no matter what the state did. A data-wrn-bind marker did not
* save it either -- the bind rewrites the same attribute the directive
* reads, and the directive had already captured "false" as its
* expression. Emitting the expression verbatim (the form Modal uses,
* data-show="isOpen()") makes both authoring styles behave the same.
*/
if (a.name === "data-show" && wholeExpression) {
return ` data-show="${escLit(attrEscape(wholeExpression))}"`;
}
const compiledValue = isExplicitComponentMount && wholeExpression
? `\${__wrnProp(${elementContext.resolveExpr(wholeExpression)})}`
: compileAttrValue(a.value, elementContext);
const rendered = ` ${a.name}="${compiledValue}"`;
const referencesState = exprRefsComponentReactiveValue(a.value, ctx);
const referencesLoopVariable = elementContext.loopVars
? exprRefsState(a.value, elementContext.loopVars)
: false;
const referencesServerLocal = ctx.serverLocals
? exprRefsState(a.value, ctx.serverLocals)
: false;
if (!a.value.includes("{") ||
(!referencesState && !referencesLoopVariable && !referencesServerLocal)) {
return rendered;
}
const marker = attrEscape(JSON.stringify([a.name, a.value]));
return `${rendered} data-wrn-bind-${bindIndex++}="${escLit(marker)}"`;
})
.join("");
const initialConditionalClasses = conditionalClasses
.map(({ className, expression }) => {
const referencesLoopVariable = elementContext.loopVars
? exprRefsState(expression, elementContext.loopVars)
: false;
// data-for variables do not exist during
// initial server rendering.
if (referencesLoopVariable) {
return "";
}
return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`;
})
.join("");
const staticClassValue = staticClasses.join(" ");
const classReferencesState = exprRefsComponentReactiveValue(staticClassValue, ctx);
const classReferencesLoopVariable = elementContext.loopVars
? exprRefsState(staticClassValue, elementContext.loopVars)
: false;
const classReferencesServerLocal = ctx.serverLocals
? exprRefsState(staticClassValue, ctx.serverLocals)
: false;
const classHasReactiveExpression = staticClassValue.includes("{") &&
(classReferencesState || classReferencesLoopVariable || classReferencesServerLocal);
const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0
? ` class="${compileAttrValue(staticClassValue, elementContext)}${initialConditionalClasses}"`
: "";
const classReactiveBinding = classHasReactiveExpression
? ` data-wrn-bind-class="${escLit(attrEscape(JSON.stringify(["class", staticClassValue])))}"`
: "";
const classBindings = conditionalClasses
.map(({ className, expression }, index) => {
const marker = attrEscape(JSON.stringify([className, expression]));
return ` data-wrn-class-${index}="${escLit(marker)}"`;
})
.join("");
const loopLocalsAttribute = serverLoopLocalsAttribute(ctx);
const allAttrs = `${loopLocalsAttribute}` +
`${ctx.forwardRestAttrs ? "${__wrnSpreadAttrs(__attrs)}" : ""}` +
`${ctx.eventNames?.length ? ` data-wrn-events="${attrEscape(ctx.eventNames.join(","))}"` : ""}` +
`${classAttribute}` +
`${classReactiveBinding}` +
`${classBindings}` +
`${attrs}`;
if (parser_ts_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) {
return `<${node.tag}${allAttrs}>`;
}
const inner = node.children.map((child) => renderComponentNode(child, elementContext)).join("");
return `<${node.tag}${allAttrs}>${inner}</${node.tag}>`;
}
function generateComponent(ast) {
const out = [];
if (ast.imports.length > 0)
out.push(generatedImports(ast).join("\n"));
const hasServerEach = viewHasServerEach(ast.view);
const effectiveProps = ast.kind === "layout" && !ast.props.some((prop) => prop.name === "content")
? [
{
name: "content",
default: '""',
valueType: "string",
required: false,
},
...ast.props,
]
: ast.props;
const browserStates = ast.states.filter((state) => state.runtime !== "server");
const stateNames = new Set([
...browserStates.map((entry) => entry.name),
...ast.computed.map((entry) => entry.name),
]);
const nameRefs = new Map();
for (const p of effectiveProps) {
nameRefs.set(p.name, safeRef(p.name));
}
if (!nameRefs.has("attrs")) {
nameRefs.set("attrs", "__attrs");
}
for (const s of ast.states)
nameRefs.set(s.name, safeRef(s.name));
for (const entry of ast.computed)
nameRefs.set(entry.name, safeRef(entry.name));
const resolveExpr = (expr) => {
let result = expr;
for (const [name, ref] of nameRefs) {
if (name !== ref)
result = result.replace(new RegExp(`\\b${name}\\b`, "g"), ref);
}
return result;
};
const ctx = {
stateNames,
propNames: new Set(effectiveProps.map((entry) => entry.name)),
functionNames: new Set(ast.runtimeFunctions.filter((fn) => fn.runtime !== "server").map((fn) => fn.name)),
resolveExpr,
eventNames: publicOutputNames(ast),
};
const serverFunctions = targetFunctions(ast, "server");
const hasExplicitRestSpread = viewHasRestAttributeSpread(ast.view);
const rootElementIndex = ast.view.findIndex((node) => node.type === "element");
const automaticallyForwardRootAttrs = !hasExplicitRestSpread &&
!effectiveProps.some((prop) => prop.name === "attrs") &&
rootElementIndex >= 0;
const viewCode = ast.view
.map((node, index) => renderComponentNode(node, automaticallyForwardRootAttrs && index === rootElementIndex
? { ...ctx, forwardRestAttrs: true }
: ctx))
.join("");
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
const styleTag = escLit(localStyleTag(ast, styles));
// Props remain server-rendered and also become signals so a parent can drive
// a mounted child after hydration.
const behavior = componentBehavior(ast);
const needsScope = ast.runtime !== "server" &&
(effectiveProps.length > 0 ||
browserStates.length > 0 ||
ast.computed.length > 0 ||
viewHasEvents(ast.view) ||
behavior !== null);
if (hasServerEach || needsScope) {
out.push(`import { Buffer as __WrnexusBuffer } from "node:buffer";`);
}
const scopeKeys = [
...effectiveProps.map((prop) => prop.name),
...browserStates.map((state) => state.name),
];
const behaviorAttr = behaviorAttribute(behavior);
const decls = [];
for (const prop of effectiveProps) {
if (prop.required) {
decls.push(` if (__p[${JSON.stringify(prop.name)}] === undefined) throw new TypeError(${JSON.stringify(`${ast.name} requires prop '${prop.name}' (${prop.valueType ?? "unknown"})`)});`);
}
decls.push(` const ${nameRefs.get(prop.name)}: ${prop.valueType ?? "unknown"} = __coerce(__p[${JSON.stringify(prop.name)}], (${resolveExpr(prop.default)}), ${JSON.stringify((0, types_ts_1.runtimeTypeOf)(prop.valueType))}, ${JSON.stringify(prop.name)}) as ${prop.valueType ?? "unknown"};`);
}
if (!effectiveProps.some((prop) => prop.name === "attrs")) {
decls.push(` const __attrs = __restProps(__p, new Set(${JSON.stringify(effectiveProps.map((prop) => prop.name))}));`);
}
for (const state of ast.states) {
decls.push(` let ${nameRefs.get(state.name)}${state.valueType ? `: ${state.valueType}` : ""} = (${resolveExpr(state.expr)});`);
}
for (const entry of ast.computed) {
decls.push(` const ${nameRefs.get(entry.name)} = (${resolveExpr(entry.expr)});`);
}
const returnExpr = needsScope
? "`" +
styleTag +
`<div data-scope="\${__scope}" data-wrn-scope="\${__scopePayload}"${behaviorAttr}${hydrationAttribute(ast)}>` +
viewCode +
"</div>`"
: "`" + styleTag + viewCode + "`";
const scopeLine = needsScope && scopeKeys.length > 0
? ` const __scopeState = { ${scopeKeys
.map((key) => `${JSON.stringify(key)}: ${nameRefs.get(key)}`)
.join(", ")} };\n const __scope = __wrnexusScopeDecl(__scopeState);\n const __scopePayload = __WrnexusBuffer.from(JSON.stringify(__scopeState), "utf8").toString("base64");\n`
: needsScope
? ` const __scopeState = {};\n const __scope = "";\n const __scopePayload = __WrnexusBuffer.from("{}", "utf8").toString("base64");\n`
: "";
if (ast.kind === "layout") {
out.push(`export const __wrnexusLayout = ${JSON.stringify(ast.name)};`);
}
else {
out.push(`export const __wrnexusComponent = ${JSON.stringify(ast.name)};`);
}
out.push(`export const __wrnexusRuntime = ${JSON.stringify(ast.runtime ?? "universal")};`);
out.push(`export const __wrnexusRender = ${JSON.stringify(ast.renderMode ?? "hybrid")};`);
out.push(`export const __wrnexusHydrate = ${JSON.stringify(["static", "server"].includes(ast.renderMode ?? "") ? "none" : (ast.hydrate ?? "load"))};`);
out.push(`export const __wrnexusHydrationId = ${JSON.stringify(hydrationId(ast))};`);
if (Object.keys(ast.cache ?? {}).length > 0)
out.push(`export const __wrnexusCache = ${JSON.stringify(ast.cache, null, 2)};`);
if (Object.keys(ast.security).length > 0) {
out.push(`export const __wrnexusSecurity = ${JSON.stringify(ast.security, null, 2)};`);
}
if (Object.keys(ast.navigation).length > 0) {
out.push(`export const __wrnexusNavigation = ${JSON.stringify(ast.navigation, null, 2)};`);
}
const componentStyleExport = localStyleExport(ast, styles);
if (componentStyleExport)
out.push(componentStyleExport);
if (behavior) {
out.push(`export const __wrnexusBehavior = ${JSON.stringify(behavior, null, 2)};`);
}
const typeSource = ast.types
.map((body) => body.trim())
.filter(Boolean)
.join("\n\n");
if (typeSource)
out.push(typeSource);
if (effectiveProps.length > 0) {
out.push(`export interface ${ast.name}Props {\n [attribute: string]: unknown;\n${effectiveProps
.map((prop) => ` ${JSON.stringify(prop.name)}${prop.required ? "" : "?"}: ${prop.valueType ?? "unknown"};`)
.join("\n")}\n}`);
}
if (ast.outputs.length > 0) {
out.push(`export interface ${ast.name}Outputs {\n${ast.outputs
.map((output) => ` ${JSON.stringify(output.name)}(${output.payload ? `${output.payload.name}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`)
.join("\n")}\n}`);
}
out.push(`function __coerce(v: unknown, def: unknown, declared: string = "unknown", propName: string = "prop"): unknown {
if (v === undefined || v === null) {
return def;
}
if (declared === "number" || typeof def === "number") {
const parsed = Number(v);
if (!Number.isFinite(parsed)) throw new TypeError("Expected a finite number prop");
return parsed;
}
if (declared === "boolean" || typeof def === "boolean") {
if (v === true || v === "" || v === "true" || v === 1 || v === "1") return true;
if (v === false || v === "false" || v === 0 || v === "0") return false;
throw new TypeError("Expected a boolean prop");
}
if (declared === "array" || Array.isArray(def)) {
if (Array.isArray(v)) {
return v;
}
if (typeof v === "string") {
try {
const parsed = JSON.parse(v);
if (!Array.isArray(parsed)) throw new TypeError("Expected an array prop '" + propName + "'");
return parsed;
} catch {
throw new TypeError("Expected an array prop '" + propName + "'");
}
}
throw new TypeError("Expected an array prop '" + propName + "'");
}
if (declared === "object" || (def !== null && typeof def === "object")) {
if (
v !== null &&
typeof v === "object" &&
!Array.isArray(v)
) {
return v;
}
if (typeof v === "string") {
try {
const parsed = JSON.parse(v);
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new TypeError("Expected an object prop '" + propName + "'");
}
return parsed;
} catch {
throw new TypeError("Expected an object prop '" + propName + "'");
}
}
throw new TypeError("Expected an object prop '" + propName + "'");
}
if (declared === "bigint") return BigInt(v as string | number | bigint | boolean);
if (declared === "function" && typeof v !== "function") {
throw new TypeError("Expected a function prop");
}
return declared === "unknown" && def === undefined ? v : String(v);
}
function __restProps(
props: Record<string, unknown>,
declared: Set<string>,
): Record<string, unknown> {
return Object.fromEntries(
Object.entries(props).filter(([name]) => !declared.has(name)),
);
}
function __wrnHtml(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>]/g,
(c) =>
c === "&"
? "&amp;"
: c === "<"
? "&lt;"
: "&gt;",
);
}
function __wrnAttr(v: unknown): string {
return String(v == null ? "" : v).replace(
/[&<>"]/g,
(c) =>
c === "&"
? "&amp;"
: c === "<"
? "&lt;"
: c === ">"
? "&gt;"
: "&quot;",
);
}
function __wrnBooleanAttr(name: string, value: unknown): string {
return value === true ||
value === "true" ||
value === "" ||
value === 1 ||
value === "1" ||
value === name
? " " + name
: "";
}
function __wrnSpreadAttrs(value: unknown): string {
if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
const booleanAttributes = new Set(${JSON.stringify([...HTML_BOOLEAN_ATTRIBUTES])});
const attributes: string[] = [];
for (const [name, raw] of Object.entries(value)) {
const lowerName = name.toLowerCase();
if (
!/^[A-Za-z_:][A-Za-z0-9_.:-]*$/.test(name) ||
lowerName.startsWith("on") ||
lowerName === "style" ||
lowerName === "slot" ||
lowerName === "data-component" ||
// Internal markers must not leak through a spread. Parent-owned output
// and prop bindings ride from the mount onto the
// rendered child root so the mounting scope can bind them there.
(lowerName.startsWith("data-wrn") &&
!lowerName.startsWith("data-wrn-out-") &&
!lowerName.startsWith("data-wrn-prop-bind-"))
) {
continue;
}
if (booleanAttributes.has(lowerName)) {
attributes.push(__wrnBooleanAttr(name, raw));
continue;
}
if (raw === false || raw === null || raw === undefined) continue;
attributes.push(" " + name + '="' + __wrnAttr(raw) + '"');
}
return attributes.join("");
}
function __wrnProp(v: unknown): string {
const value =
v !== null && typeof v === "object"
? JSON.stringify(v)
: String(v == null ? "" : v);
return __wrnAttr(value);
}
function __wrnRaw(v: unknown): string {
return String(v == null ? "" : v);
}`);
if (hasServerEach) {
out.push(`function __wrnexusEncodeLoopLocals(value: Record<string, unknown>): string {
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`);
}
if (needsScope) {
out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) {
return "undefined";
}
if (value === null) {
return "null";
}
if (typeof value === "number") {
return Number.isFinite(value)
? String(value)
: "null";
}
if (typeof value === "boolean") {
return value ? "true" : "false";
}
if (typeof value === "string") {
return JSON.stringify(value);
}
try {
const serialized = JSON.stringify(value);
return serialized === undefined
? "undefined"
: serialized;
} catch {
return "null";
}
}
function __wrnexusScopeDecl(obj: Record<string, unknown>): string {
return Object.keys(obj)
.map(
(key) =>
key +
": " +
__wrnexusSerializeScopeValue(
obj[key],
),
)
.join(", ")
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}`);
}
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record<string, unknown>"}): string {\n` +
` const __p = props || {};\n` +
(decls.length > 0 ? decls.join("\n") + "\n" : "") +
serverFunctionSource +
scopeLine +
` return ${returnExpr};\n` +
`}`);
out.push(`export default { name: ${JSON.stringify(ast.name)}, kind: ${JSON.stringify(ast.kind)}, render };`);
return out.join("\n\n") + "\n";
}
function __wrnRaw(v) {
return String(v == null ? "" : v);
}
function wholeAttributeExpression(value) {
const match = /^\s*\{([\s\S]+)\}\s*$/.exec(value);
return match?.[1]?.trim() || null;
}
function __wrnHtml(v) {
return String(v == null ? "" : v).replace(/[&<>]/g, (c) => c === "&" ? "&amp;" : c === "<" ? "&lt;" : "&gt;");
}
function __wrnAttr(v) {
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) => c === "&" ? "&amp;" : c === "<" ? "&lt;" : c === ">" ? "&gt;" : "&quot;");
}
function __wrnProp(v) {
const value = v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v);
return __wrnAttr(value);
}
function renderPageComponentAttr(attr, dynamicExpressions) {
if (attr.event) {
return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
}
if (attr.boolean) {
return ` ${attr.name}`;
}
const expression = wholeAttributeExpression(attr.value);
if (!expression) {
return ` ${attr.name}="${attrEscape(safeAttributeValue(attr.name, attr.value))}"`;
}
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
return ` ${attr.name}="${marker}"`;
}
},
"packages/compiler/src/component-contract.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createComponentContract = createComponentContract;
function unionOptions(type) {
if (!type || !type.includes("|"))
return undefined;
const values = type
.split("|")
.map((part) => part.trim())
.filter((part) => /^(?:"[^"]*"|'[^']*')$/.test(part))
.map((part) => part.slice(1, -1));
return values.length ? values : undefined;
}
function createComponentContract(ast) {
return {
name: ast.name,
kind: ast.kind,
props: ast.props.map((prop) => ({
name: prop.name,
type: prop.valueType ?? "unknown",
required: prop.required,
...(prop.default !== "undefined" ? { default: prop.default } : {}),
...(unionOptions(prop.valueType) ? { options: unionOptions(prop.valueType) } : {}),
})),
outputs: ast.outputs.map((output) => ({
name: output.name,
...(output.payload
? { payloadName: output.payload.name, payloadType: output.payload.valueType }
: {}),
})),
functions: ast.runtimeFunctions.map((fn) => ({
name: fn.name,
runtime: fn.runtime,
async: fn.async,
parameters: fn.parameters.map((param) => ({
name: param.name,
type: param.valueType ?? "unknown",
optional: param.optional,
})),
returnType: fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown"),
})),
states: ast.states.map((state) => ({
name: state.name,
runtime: state.runtime,
type: state.valueType ?? "unknown",
initializer: state.expr,
})),
computed: ast.computed.map((entry) => ({
name: entry.name,
type: entry.valueType ?? "unknown",
expression: entry.expr,
})),
imports: ast.structuredImports.map((entry) => ({
source: entry.source,
typeOnly: entry.typeOnly,
...(entry.defaultImport ? { defaultImport: entry.defaultImport } : {}),
namedImports: entry.namedImports.map((named) => named.local),
})),
};
}
},
"packages/compiler/src/import-resolver.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveWrnImport = resolveWrnImport;
exports.resolveWrnImports = resolveWrnImports;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
function candidates(path) {
return (0, node_path_1.extname)(path)
? [path]
: [
path,
`${path}.wrn`,
`${path}.ts`,
`${path}.tsx`,
`${path}.d.ts`,
(0, node_path_1.join)(path, "index.wrn"),
(0, node_path_1.join)(path, "index.ts"),
(0, node_path_1.join)(path, "index.tsx"),
];
}
function resolveWrnImport(declaration, importer, options) {
const source = declaration.source;
const aliases = {
"@": "./app",
...(options.aliases ?? {}),
};
const alias = Object.keys(aliases)
.filter((key) => key.length > 0 && (source === key || source.startsWith(`${key}/`)))
.sort((left, right) => right.length - left.length)[0];
if (!source.startsWith(".") && !alias)
return { declaration, resolved: source };
const base = alias
? (0, node_path_1.resolve)(options.appRoot, aliases[alias], source === alias ? "" : source.slice(alias.length + 1))
: (0, node_path_1.resolve)((0, node_path_1.dirname)(importer), source);
const found = candidates(base).find((candidate) => {
if (!(0, node_fs_1.existsSync)(candidate))
return false;
try {
return (0, node_fs_1.statSync)(candidate).isFile();
}
catch {
return false;
}
});
if (found) {
const resolved = (0, node_fs_1.realpathSync)(found);
return resolved.endsWith(".tsx")
? { declaration, resolved, kind: "island" }
: { declaration, resolved };
}
const severity = (options.mode ?? "compatible") === "explicit" ? "error" : "warning";
return {
declaration,
diagnostic: {
code: "WRN-IMPORT-NOT-FOUND",
message: `Cannot resolve import '${source}' from ${importer}`,
severity,
},
};
}
function resolveWrnImports(declarations, importer, options) {
return declarations.map((declaration) => resolveWrnImport(declaration, importer, options));
}
},
"packages/compiler/src/index.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
/**
* @wrnexus/compiler — the `.wrn` language compiler.
*
* Parsing and language diagnostics are provided by the canonical
* `@wrnexus/syntax` package. This package owns platform-specific codegen.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DependencyGraph = exports.createCompilationCache = exports.compilationKey = exports.runtimeTypeOf = exports.inferredRuntimeType = exports.eraseFunctionTypes = exports.LexError = exports.Lexer = exports.NativeCompileError = exports.generateNative = exports.runtimeCapabilities = exports.analyzeRuntimeImports = exports.optimizeAst = exports.analyzeRuntimeRequirements = exports.analyzeOptimizations = exports.createWrnSourceMap = exports.resolveWrnImports = exports.resolveWrnImport = exports.createComponentContract = exports.generateStoreModule = exports.generateStoreBrowserModule = exports.generateDeclarations = exports.rpcManifest = exports.generateServerFunctionsModule = exports.generateBrowserModule = exports.generateTargets = exports.generate = exports.ParseError = exports.parse = exports.formatDiagnostic = exports.diagnosticFromError = exports.diagnose = exports.assertValidAst = exports.formatWrn = void 0;
exports.compileNativeWrnFile = compileNativeWrnFile;
exports.compileWrnFile = compileWrnFile;
exports.compile = compile;
const syntax_1 = require("@wrnexus/syntax");
var syntax_2 = require("@wrnexus/syntax");
Object.defineProperty(exports, "formatWrn", { enumerable: true, get: function () { return syntax_2.formatWrn; } });
const codegen_ts_1 = require("./codegen.js");
const native_codegen_ts_1 = require("./native-codegen.js");
var syntax_3 = require("@wrnexus/syntax");
Object.defineProperty(exports, "assertValidAst", { enumerable: true, get: function () { return syntax_3.assertValidAst; } });
Object.defineProperty(exports, "diagnose", { enumerable: true, get: function () { return syntax_3.diagnose; } });
Object.defineProperty(exports, "diagnosticFromError", { enumerable: true, get: function () { return syntax_3.diagnosticFromError; } });
Object.defineProperty(exports, "formatDiagnostic", { enumerable: true, get: function () { return syntax_3.formatDiagnostic; } });
Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return syntax_3.parse; } });
Object.defineProperty(exports, "ParseError", { enumerable: true, get: function () { return syntax_3.ParseError; } });
var codegen_ts_2 = require("./codegen.js");
Object.defineProperty(exports, "generate", { enumerable: true, get: function () { return codegen_ts_2.generate; } });
var targets_ts_1 = require("./targets.js");
Object.defineProperty(exports, "generateTargets", { enumerable: true, get: function () { return targets_ts_1.generateTargets; } });
var client_codegen_ts_1 = require("./client-codegen.js");
Object.defineProperty(exports, "generateBrowserModule", { enumerable: true, get: function () { return client_codegen_ts_1.generateBrowserModule; } });
var server_codegen_ts_1 = require("./server-codegen.js");
Object.defineProperty(exports, "generateServerFunctionsModule", { enumerable: true, get: function () { return server_codegen_ts_1.generateServerFunctionsModule; } });
Object.defineProperty(exports, "rpcManifest", { enumerable: true, get: function () { return server_codegen_ts_1.rpcManifest; } });
var type_codegen_ts_1 = require("./type-codegen.js");
Object.defineProperty(exports, "generateDeclarations", { enumerable: true, get: function () { return type_codegen_ts_1.generateDeclarations; } });
var store_codegen_ts_1 = require("./store-codegen.js");
Object.defineProperty(exports, "generateStoreBrowserModule", { enumerable: true, get: function () { return store_codegen_ts_1.generateStoreBrowserModule; } });
Object.defineProperty(exports, "generateStoreModule", { enumerable: true, get: function () { return store_codegen_ts_1.generateStoreModule; } });
var component_contract_ts_1 = require("./component-contract.js");
Object.defineProperty(exports, "createComponentContract", { enumerable: true, get: function () { return component_contract_ts_1.createComponentContract; } });
var import_resolver_ts_1 = require("./import-resolver.js");
Object.defineProperty(exports, "resolveWrnImport", { enumerable: true, get: function () { return import_resolver_ts_1.resolveWrnImport; } });
Object.defineProperty(exports, "resolveWrnImports", { enumerable: true, get: function () { return import_resolver_ts_1.resolveWrnImports; } });
var source_map_ts_1 = require("./source-map.js");
Object.defineProperty(exports, "createWrnSourceMap", { enumerable: true, get: function () { return source_map_ts_1.createWrnSourceMap; } });
var analysis_ts_1 = require("./analysis.js");
Object.defineProperty(exports, "analyzeOptimizations", { enumerable: true, get: function () { return analysis_ts_1.analyzeOptimizations; } });
Object.defineProperty(exports, "analyzeRuntimeRequirements", { enumerable: true, get: function () { return analysis_ts_1.analyzeRuntimeRequirements; } });
Object.defineProperty(exports, "optimizeAst", { enumerable: true, get: function () { return analysis_ts_1.optimizeAst; } });
var runtime_capabilities_ts_1 = require("./runtime-capabilities.js");
Object.defineProperty(exports, "analyzeRuntimeImports", { enumerable: true, get: function () { return runtime_capabilities_ts_1.analyzeRuntimeImports; } });
Object.defineProperty(exports, "runtimeCapabilities", { enumerable: true, get: function () { return runtime_capabilities_ts_1.runtimeCapabilities; } });
var native_codegen_ts_2 = require("./native-codegen.js");
Object.defineProperty(exports, "generateNative", { enumerable: true, get: function () { return native_codegen_ts_2.generateNative; } });
Object.defineProperty(exports, "NativeCompileError", { enumerable: true, get: function () { return native_codegen_ts_2.NativeCompileError; } });
var syntax_4 = require("@wrnexus/syntax");
Object.defineProperty(exports, "Lexer", { enumerable: true, get: function () { return syntax_4.Lexer; } });
Object.defineProperty(exports, "LexError", { enumerable: true, get: function () { return syntax_4.LexError; } });
var syntax_5 = require("@wrnexus/syntax");
Object.defineProperty(exports, "eraseFunctionTypes", { enumerable: true, get: function () { return syntax_5.eraseFunctionTypes; } });
Object.defineProperty(exports, "inferredRuntimeType", { enumerable: true, get: function () { return syntax_5.inferredRuntimeType; } });
Object.defineProperty(exports, "runtimeTypeOf", { enumerable: true, get: function () { return syntax_5.runtimeTypeOf; } });
/** Compile `.wrn` source into an Expo Router React Native screen. */
function compileNativeWrnFile(source) {
const ast = (0, syntax_1.parse)(source);
(0, syntax_1.assertValidAst)(ast);
return (0, native_codegen_ts_1.generateNative)(ast);
}
/**
* Compile `.wrn` source into TypeScript source. Errors include a stable code,
* source location, code frame, and actionable hint whenever available.
*/
function compileWrnFile(source, filePath = "<inline .wrn>") {
try {
const ast = (0, syntax_1.parse)(source);
(0, syntax_1.assertValidAst)(ast, { file: filePath, accessibility: true });
return `// compiled from .wrn\n${(0, codegen_ts_1.generate)(ast)}`;
}
catch (error) {
const diagnostic = (0, syntax_1.diagnosticFromError)(source, error, { file: filePath });
throw new Error(`Failed to parse ${filePath}:\n\n${(0, syntax_1.formatDiagnostic)(source, diagnostic)}`, {
cause: error,
});
}
}
/** Richer entry point returning the AST and structured diagnostics. */
function compile(source, filePath = "<inline .wrn>") {
const richDiagnostics = (0, syntax_1.diagnose)(source, { file: filePath, accessibility: true });
const errors = richDiagnostics.filter((diagnostic) => diagnostic.severity === "error");
if (errors.length > 0) {
throw new syntax_1.ParseError(errors.map((diagnostic) => diagnostic.message).join("\n"), errors[0].code);
}
const ast = (0, syntax_1.parse)(source);
return {
code: `// compiled from .wrn\n${(0, codegen_ts_1.generate)(ast)}`,
ast,
diagnostics: richDiagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`),
richDiagnostics,
};
}
var cache_ts_1 = require("./cache.js");
Object.defineProperty(exports, "compilationKey", { enumerable: true, get: function () { return cache_ts_1.compilationKey; } });
Object.defineProperty(exports, "createCompilationCache", { enumerable: true, get: function () { return cache_ts_1.createCompilationCache; } });
Object.defineProperty(exports, "DependencyGraph", { enumerable: true, get: function () { return cache_ts_1.DependencyGraph; } });
},
"packages/compiler/src/native-codegen.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NativeCompileError = void 0;
exports.generateNative = generateNative;
class NativeCompileError extends Error {
constructor(message) {
super(message);
this.name = "NativeCompileError";
}
}
exports.NativeCompileError = NativeCompileError;
const tagMap = {
div: "View",
main: "View",
section: "View",
article: "View",
nav: "View",
header: "View",
footer: "View",
aside: "View",
form: "View",
ul: "View",
ol: "View",
li: "View",
p: "Text",
span: "Text",
strong: "Text",
em: "Text",
small: "Text",
label: "Text",
h1: "Text",
h2: "Text",
h3: "Text",
h4: "Text",
h5: "Text",
h6: "Text",
button: "Pressable",
a: "Pressable",
input: "TextInput",
textarea: "TextInput",
img: "Image",
view: "View",
text: "Text",
pressable: "Pressable",
textinput: "TextInput",
image: "Image",
scrollview: "ScrollView",
safeareaview: "SafeAreaView",
flatlist: "FlatList",
activityindicator: "ActivityIndicator",
};
const attrMap = {
class: "style",
className: "style",
src: "source",
alt: "accessibilityLabel",
placeholder: "placeholder",
disabled: "disabled",
value: "value",
href: "__href",
"aria-label": "accessibilityLabel",
};
function expression(value) {
const exact = /^\{([\s\S]+)\}$/.exec(value.trim());
return exact?.[1]?.trim() ?? null;
}
function textJsx(value) {
const pieces = [];
let last = 0;
for (const match of value.matchAll(/\{([^{}]+)\}/g)) {
if (match.index > last)
pieces.push(value.slice(last, match.index));
const expr = match[1].trim();
pieces.push(expr.startsWith("t:") ? `{${JSON.stringify(expr.slice(2).trim())}}` : `{${expr}}`);
last = match.index + match[0].length;
}
pieces.push(value.slice(last));
return pieces.join("").replace(/([<>])/g, (char) => (char === "<" ? "&lt;" : "&gt;"));
}
function eventBody(value, states) {
let body = expression(value) ?? value;
for (const state of states) {
const cap = state[0].toUpperCase() + state.slice(1);
body = body
.replace(new RegExp(`\\b${state}\\+\\+`, "g"), `set${cap}(value => value + 1)`)
.replace(new RegExp(`\\b${state}--`, "g"), `set${cap}(value => value - 1)`)
.replace(new RegExp(`\\b${state}\\s*=\\s*([^;]+)`, "g"), `set${cap}($1)`);
}
return `() => { ${body} }`;
}
function renderAttrs(attrs, states) {
return attrs
.map((attr) => {
if (attr.event) {
if (attr.name.startsWith("browser-"))
return "";
const eventName = attr.name.startsWith("mobile-") ? attr.name.slice(7) : attr.name;
const event = eventName === "click" || eventName === "press"
? "onPress"
: eventName === "input" || eventName === "change"
? "onChangeText"
: `on${eventName[0].toUpperCase()}${eventName.slice(1)}`;
return ` ${event}={${eventBody(attr.value, states)}}`;
}
if (attr.name === "data-native-browser" || attr.name.startsWith("data-native-on-browser-"))
return "";
if (attr.name === "data-native-options" ||
attr.name === "data-native-only" ||
attr.name === "data-native-requires" ||
attr.name === "data-native-unsupported")
return "";
if (attr.name === "data-native-mobile") {
throw new NativeCompileError(`Declarative native capability "${attr.value}" currently targets browser/Capacitor pages. In Expo output, call the installed Expo package from an @mobile-event handler.`);
}
const name = attrMap[attr.name] ?? attr.name;
if (name === "__href")
return ` onPress={() => router.push(${JSON.stringify(attr.value)})}`;
if (name === "source") {
const expr = expression(attr.value);
return ` source={${expr ? `{ uri: ${expr} }` : `{ uri: ${JSON.stringify(attr.value)} }`}}`;
}
if (name === "style" && attr.name !== "style") {
return ` style={[${attr.value
.split(/\s+/)
.filter(Boolean)
.map((value) => `styles[${JSON.stringify(value)}]`)
.join(", ")} ]}`;
}
if (name === "style") {
const inlineExpression = expression(attr.value);
if (inlineExpression)
return ` style={${inlineExpression}}`;
throw new NativeCompileError('Inline CSS strings are not portable to native; use class="name" and a page style block');
}
if (attr.boolean)
return ` ${name}`;
const expr = expression(attr.value);
return expr ? ` ${name}={${expr}}` : ` ${name}=${JSON.stringify(attr.value)}`;
})
.join("");
}
function renderNode(node, states, key) {
if (node.type === "text")
return textJsx(node.value);
if (node.type === "each") {
const params = node.index ? `${node.item}, ${node.index}` : `${node.item}, __index`;
const body = node.body
.map((child, index) => renderNode(child, states, index === 0 ? (node.index ?? "__index") : undefined))
.join("");
const empty = node.empty.map((child) => renderNode(child, states)).join("");
return `{(${node.list})?.length ? (${node.list}).map((${params}) => <>${body}</>) : <>${empty}</>}`;
}
if (node.type === "if") {
const result = node.branches.reduceRight((fallback, branch) => branch.cond === null
? `<>${branch.body.map((child) => renderNode(child, states)).join("")}</>`
: `(${branch.cond}) ? <>${branch.body.map((child) => renderNode(child, states)).join("")}</> : ${fallback}`, "null");
return `{${result}}`;
}
const nativeOnly = node.attrs.find((attr) => !attr.event && attr.name === "data-native-only")?.value;
if (nativeOnly === "browser" || nativeOnly === "web")
return "";
const nativeTag = tagMap[node.tag.toLowerCase()] ?? (/^[A-Z]/.test(node.tag) ? node.tag : undefined);
if (!nativeTag)
throw new NativeCompileError(`HTML element <${node.tag}> has no native equivalent`);
const attrs = renderAttrs(node.attrs, states) + (key ? ` key={${key}}` : "");
if (nativeTag === "TextInput" || nativeTag === "Image" || nativeTag === "ActivityIndicator")
return `<${nativeTag}${attrs} />`;
const children = node.children
.map((child) => {
if (child.type !== "text")
return renderNode(child, states);
if (!child.value.trim())
return "";
const text = textJsx(child.value);
return nativeTag === "Text" ? text : `<Text>${text}</Text>`;
})
.join("");
return `<${nativeTag}${attrs}>${children}</${nativeTag}>`;
}
function nativeStyles(blocks) {
const entries = [];
for (const block of blocks) {
for (const match of block.matchAll(/\.([A-Za-z_][\w-]*)\s*\{([^}]*)\}/g)) {
const props = [];
for (const declaration of match[2].split(";")) {
const colon = declaration.indexOf(":");
if (colon < 0)
continue;
const name = declaration
.slice(0, colon)
.trim()
.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
let value = declaration.slice(colon + 1).trim();
if (/^-?\d+(?:\.\d+)?px$/.test(value))
value = Number(value.slice(0, -2));
props.push(`${JSON.stringify(name)}: ${typeof value === "number" ? value : JSON.stringify(value)}`);
}
entries.push(`${JSON.stringify(match[1])}: { ${props.join(", ")} }`);
}
}
return `const styles = StyleSheet.create({ ${entries.join(",\n")} });`;
}
/** Compile a parsed `.wrn` page to an Expo Router React Native screen. */
function generateNative(ast) {
if (ast.kind !== "page")
throw new NativeCompileError("Native route compilation currently accepts page files only");
if (ast.dataApis.length)
throw new NativeCompileError("Data API blocks are not yet portable to native screens; fetch through the generated native backend helper");
const states = new Set(ast.states.map((state) => state.name));
const hooks = ast.states
.map((state) => {
const cap = state.name[0].toUpperCase() + state.name.slice(1);
return ` const [${state.name}, set${cap}] = useState${state.valueType ? `<${state.valueType}>` : ""}(${state.expr});`;
})
.join("\n");
const body = ast.view.map((node) => renderNode(node, states)).join("");
const typeSource = ast.types
.map((block) => block.trim())
.filter(Boolean)
.join("\n\n");
return `// generated from .wrn for Expo/React Native\nimport React, { useState } from "react";\nimport { ActivityIndicator, FlatList, Image, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from "react-native";\nimport { useRouter } from "expo-router";\n${ast.imports.join("\n")}\n\n${typeSource}\n\nexport default function ${ast.name}() {\n const router = useRouter();\n${hooks}\n return <>${body}</>;\n}\n\n${nativeStyles(ast.styles)}\n`;
}
},
"packages/compiler/src/parser.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
/** @deprecated Import parser APIs from @wrnexus/syntax. */
__exportStar(require("@wrnexus/syntax/parser"), exports);
},
"packages/compiler/src/runtime-capabilities.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runtimeCapabilities = runtimeCapabilities;
exports.analyzeRuntimeImports = analyzeRuntimeImports;
const CAPABILITIES = {
bun: new Set([
"filesystem",
"tcp",
"process",
"websocket",
"crypto",
"streams",
"background-tasks",
]),
node: new Set([
"filesystem",
"tcp",
"process",
"websocket",
"crypto",
"streams",
"background-tasks",
]),
edge: new Set(["websocket", "crypto", "streams", "background-tasks"]),
worker: new Set(["websocket", "crypto", "streams", "background-tasks"]),
"service-worker": new Set(["crypto", "streams", "background-tasks"]),
browser: new Set(["websocket", "crypto", "streams"]),
};
const MODULE_CAPABILITIES = [
[/^(?:node:)?(?:fs|path|os)(?:\/|$)/, "filesystem"],
[/^(?:node:)?(?:net|tls|dgram|http2)(?:\/|$)/, "tcp"],
[/^(?:node:)?(?:child_process|cluster|worker_threads)(?:\/|$)/, "process"],
];
function runtimeCapabilities(runtime) {
return CAPABILITIES[runtime];
}
function analyzeRuntimeImports(source, runtime) {
const modules = [
...source.matchAll(/\b(?:import\s+(?:[\s\S]*?\s+from\s+)?|require\s*\()\s*["']([^"']+)["']/g),
].map((match) => match[1]);
const available = runtimeCapabilities(runtime);
return modules.flatMap((module) => {
const requirement = MODULE_CAPABILITIES.find(([pattern]) => pattern.test(module));
if (!requirement || available.has(requirement[1]))
return [];
return [
{
code: "WRN-RUNTIME-CAPABILITY",
runtime,
module,
capability: requirement[1],
message: `Module '${module}' requires ${requirement[1]}, which is unavailable in the ${runtime} runtime.`,
},
];
});
}
},
"packages/compiler/src/server-codegen.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.remotelyReferencedServerFunctions = remotelyReferencedServerFunctions;
exports.rpcManifest = rpcManifest;
exports.generateServerFunctionsModule = generateServerFunctionsModule;
const syntax_1 = require("@wrnexus/syntax");
function stableId(value) {
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.
*/
function remotelyReferencedServerFunctions(ast) {
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;
}
function rpcManifest(ast) {
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"),
}));
}
function generateServerFunctionsModule(ast) {
const source = ast.functions
.map((body) => (0, syntax_1.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`;
}
},
"packages/compiler/src/source-map.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWrnSourceMap = createWrnSourceMap;
function createWrnSourceMap(source, generated) {
const sourceLines = source.split(/\r?\n/).length;
const generatedLines = generated.split(/\r?\n/).length;
const mappings = Array.from({ length: Math.min(sourceLines, generatedLines) }, (_, index) => ({
generatedLine: index + 1,
sourceLine: index + 1,
sourceColumn: 1,
kind: "line",
}));
return { version: 1, source, generated, mappings };
}
},
"packages/compiler/src/store-codegen.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateStoreModule = generateStoreModule;
exports.generateStoreBrowserModule = generateStoreBrowserModule;
const syntax_1 = require("@wrnexus/syntax");
const type_codegen_ts_1 = require("./type-codegen.js");
const server_codegen_ts_1 = require("./server-codegen.js");
const RESERVED_BINDINGS = new Set([
"await",
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"implements",
"import",
"in",
"instanceof",
"interface",
"let",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"static",
"super",
"switch",
"this",
"throw",
"true",
"try",
"typeof",
"var",
"void",
"while",
"with",
"yield",
]);
function safeBinding(name) {
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_BINDINGS.has(name);
}
function stateObject(ast, runtime) {
const entries = ast.states
.filter((state) => state.runtime === runtime)
.map((state) => `${JSON.stringify(state.name)}: (${state.expr})`);
return `{ ${entries.join(", ")} }`;
}
function actionSource(fn, stateNames, eraseTypes = false) {
const parameterNames = new Set(fn.parameters.map((param) => param.name));
const params = fn.parameters.map((param) => param.name).join(", ");
const aliases = stateNames.filter((name) => safeBinding(name) && !parameterNames.has(name));
const aliasSource = aliases.length ? `let { ${aliases.join(", ")} } = context.state;` : "";
const runtimeAliases = ["server"]
.filter((name) => !parameterNames.has(name))
.map((name) => `const ${name} = context.${name};`)
.join("\n");
const copyBack = aliases.map((name) => `context.state.${name} = ${name};`).join("\n");
const body = eraseTypes ? (0, syntax_1.eraseFunctionTypes)(fn.body) : fn.body;
return `{ runtime: ${JSON.stringify(fn.runtime)}, handler: ${fn.async ? "async " : ""}function(context${params ? `, ${params}` : ""}) { ${runtimeAliases}\n${aliasSource}\ntry { ${body} } finally { ${copyBack} } } }`;
}
function persistedCallback(source, functionName) {
if (!source?.trim())
return undefined;
const body = (0, syntax_1.eraseFunctionTypes)(source);
if (functionName === "migrate") {
return `(value, fromVersion, toVersion) => {\n${body}\nif (typeof migrate === "function") return migrate(value, fromVersion, toVersion);\nreturn value;\n}`;
}
return `(value) => {\n${body}\nif (typeof validate === "function") return validate(value);\nreturn value && typeof value === "object" && !Array.isArray(value) ? value : null;\n}`;
}
function persistenceSource(ast) {
if (!ast.persist)
return "undefined";
const migrate = persistedCallback(ast.persist.migrations, "migrate");
const validate = persistedCallback(ast.persist.validation, "validate");
return `{ storage: ${JSON.stringify(ast.persist.storage)}, include: ${JSON.stringify(ast.persist.include)}, version: ${ast.persist.version}${migrate ? `, migrate: ${migrate}` : ""}${validate ? `, validate: ${validate}` : ""} }`;
}
function lifecycleSource(ast, stateNames, browser) {
return Object.entries(ast.storeLifecycle)
.filter(([name]) => !browser || name !== "serverInit")
.map(([name, body]) => {
const aliases = stateNames.filter(safeBinding);
const aliasSource = aliases.length ? `let { ${aliases.join(", ")} } = context.state;` : "";
const runtimeAliases = browser ? "const server = context.server;" : "";
const copyBack = aliases.map((state) => `context.state.${state} = ${state};`).join("\n");
const emittedBody = browser ? (0, syntax_1.eraseFunctionTypes)(body) : body;
return `${name}: async (context) => { ${runtimeAliases} ${aliasSource} try { ${emittedBody} } finally { ${copyBack} } }`;
})
.join(",\n");
}
function generateStoreModule(ast) {
if (ast.kind !== "global-store" && ast.kind !== "page-store") {
throw new Error("generateStoreModule requires a store AST");
}
const stateNames = ast.states.map((state) => state.name);
const safeStateNames = stateNames.filter(safeBinding);
const computed = ast.computed
.map((entry) => `${JSON.stringify(entry.name)}: (state) => { ${safeStateNames.length ? `const { ${safeStateNames.join(", ")} } = state;` : ""} return (${entry.expr}); }`)
.join(",\n");
const actionGroups = new Map();
for (const fn of ast.runtimeFunctions) {
const group = actionGroups.get(fn.name) ?? [];
group.push(fn);
actionGroups.set(fn.name, group);
}
const actions = Array.from(actionGroups, ([name, functions]) => `${JSON.stringify(name)}: [${functions.map((fn) => actionSource(fn, stateNames)).join(", ")}]`).join(",\n");
const persistence = persistenceSource(ast);
const lifecycle = lifecycleSource(ast, stateNames, false);
const manifest = (0, server_codegen_ts_1.rpcManifest)(ast);
const remoteFunctions = manifest.map((entry) => entry.function);
const rpcWrappers = remoteFunctions
.map((name) => `${JSON.stringify(name)}: async (...received) => {
const rpcContext = received.pop();
if (!rpcContext || !rpcContext.request) throw new Error("WRN-RPC-CONTEXT: request context is required");
let container = __wrnexusRpcContainers.get(rpcContext.request);
if (!container) {
const url = new URL(rpcContext.request.url);
container = createRequestStoreContainer(rpcContext.request, url.pathname + url.search);
__wrnexusRpcContainers.set(rpcContext.request, container);
}
const store = await container.use(${ast.name});
const action = store.actions[${JSON.stringify(name)}];
if (typeof action !== "function") throw new Error(${JSON.stringify(`WRN-RPC-FUNCTION: store action '${name}' is unavailable on the server`)});
return action(...received);
}`)
.join(",\n");
return `${ast.imports.join("\n")}\nimport { defineStore } from "@wrnexus/store";\nimport { createRequestStoreContainer } from "@wrnexus/store/server";\n\n${ast.types.join("\n\n")}\n\nexport const ${ast.name} = defineStore({\n name: ${JSON.stringify(ast.name)},\n kind: ${JSON.stringify(ast.storeKind)},\n createSharedState: () => (${stateObject(ast, "shared")}),\n createClientState: () => (${stateObject(ast, "client")}),\n createServerState: () => (${stateObject(ast, "server")}),\n computed: { ${computed} },\n actions: { ${actions} },\n persist: ${persistence},\n lifecycle: { ${lifecycle} },\n});\n\nexport default ${ast.name};\n\nconst __wrnexusRpcContainers = new WeakMap();\nexport const __wrnexusServerFunctions = {\n${rpcWrappers}\n};\nexport const __wrnexusRpcManifest = ${JSON.stringify(manifest, null, 2)};\n\n${(0, type_codegen_ts_1.generateDeclarations)(ast)}\n`;
}
/** Standalone browser artifact for an imported `.wrn` store. */
function generateStoreBrowserModule(ast) {
if (ast.kind !== "global-store" && ast.kind !== "page-store") {
throw new Error("generateStoreBrowserModule requires a store AST");
}
const browserStates = ast.states.filter((state) => state.runtime !== "server");
const stateNames = browserStates.map((state) => state.name);
const safeStateNames = stateNames.filter(safeBinding);
const initialState = `{ ${browserStates.map((state) => `${JSON.stringify(state.name)}: (${state.expr})`).join(", ")} }`;
const computed = ast.computed
.map((entry) => `${JSON.stringify(entry.name)}: (state) => { ${safeStateNames.length ? `const { ${safeStateNames.join(", ")} } = state;` : ""} return (${entry.expr}); }`)
.join(",\n");
const groups = new Map();
for (const fn of ast.runtimeFunctions.filter((entry) => ["client", "shared", "legacy"].includes(entry.runtime))) {
const group = groups.get(fn.name) ?? [];
group.push(fn);
groups.set(fn.name, group);
}
const actions = Array.from(groups, ([name, functions]) => `${JSON.stringify(name)}: [${functions.map((fn) => actionSource(fn, stateNames, true)).join(", ")}]`).join(",\n");
const persistence = persistenceSource(ast);
const lifecycleEntries = lifecycleSource(ast, stateNames, true);
return `// generated WRNexusJS browser store module for ${ast.name}
const __root = globalThis;
const __registry = __root.__wrnexusStoreRegistry || (__root.__wrnexusStoreRegistry = new Map());
const __hydrationNode = typeof document !== "undefined" ? document.querySelector("script[data-wrnexus-store-hydration]") : null;
let __hydration = {};
try { __hydration = __hydrationNode ? JSON.parse(__hydrationNode.textContent || "{}") : {}; } catch (_) {}
function __clone(value) { try { return structuredClone(value); } catch (_) { return JSON.parse(JSON.stringify(value)); } }
function __storage(kind) { if (typeof window === "undefined") return null; return kind === "local" ? window.localStorage : kind === "session" ? window.sessionStorage : null; }
function __diagnostic(code, message, details) {
const detail = { code, message, store: ${JSON.stringify(ast.name)}, details };
try { __root.dispatchEvent && __root.dispatchEvent(new CustomEvent("wrnexus:store-diagnostic", { detail })); } catch (_) {}
if (typeof console !== "undefined" && console.warn) console.warn("[wrnexus:store] " + code + ": " + message, details || "");
}
function __csrfToken() {
if (typeof document === "undefined") return undefined;
const match = /(?:^|;\\s*)wrn-csrf=([^;]+)/.exec(document.cookie || "");
return match ? decodeURIComponent(match[1]) : undefined;
}
async function __callServerFunction(storeName, functionName, args, options) {
options = options || {};
const csrf = options.csrfToken || __csrfToken();
const traceId = options.traceId || (globalThis.crypto && crypto.randomUUID ? crypto.randomUUID() : String(Date.now()));
const response = await fetch(options.endpoint || "/__wrnexus/rpc", {
method: "POST",
credentials: "same-origin",
signal: options.signal,
headers: Object.assign({ "content-type": "application/json", "x-request-id": traceId }, csrf ? { "x-csrf-token": csrf } : {}, options.headers || {}),
body: JSON.stringify({ component: storeName, function: functionName, args: args }),
});
const payload = await response.json().catch(function () { return null; });
if (!response.ok || !payload || !payload.ok) {
const error = new Error(payload && payload.error && payload.error.message || "Server call failed (" + response.status + ")");
error.code = payload && payload.error && payload.error.code || "WRN-RPC-FAILED";
error.status = response.status;
error.details = payload && payload.error && payload.error.details;
error.traceId = payload && payload.error && payload.error.traceId || traceId;
throw error;
}
return payload.value;
}
function __compatible(expected, value) {
if (expected === null || value === null) return expected === value || expected === null;
if (Array.isArray(expected)) return Array.isArray(value);
return typeof expected === typeof value;
}
function __create(definition) {
const routeId = typeof location !== "undefined" ? location.pathname + location.search : "default";
const key = definition.kind === "page" ? definition.name + "@" + routeId : definition.name;
if (__registry.has(key)) return __registry.get(key);
let currentDefinition = definition;
const listeners = new Set();
const initial = currentDefinition.createState();
let restored = null;
if (currentDefinition.persist) {
try {
const storage = __storage(currentDefinition.persist.storage);
const rawValue = storage && storage.getItem("wrnexus:store:" + currentDefinition.name);
const parsed = rawValue ? JSON.parse(rawValue) : null;
if (parsed) {
let candidate = parsed.state;
const fromVersion = Number(parsed.version || 0);
if (fromVersion !== currentDefinition.persist.version) {
if (typeof currentDefinition.persist.migrate === "function") candidate = currentDefinition.persist.migrate(candidate, fromVersion, currentDefinition.persist.version);
else { __diagnostic("WRN-PERSIST-VERSION", "Persisted state version cannot be restored without a migration.", { fromVersion, toVersion: currentDefinition.persist.version }); candidate = null; }
}
if (candidate && typeof currentDefinition.persist.validate === "function") candidate = currentDefinition.persist.validate(candidate);
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
restored = {};
currentDefinition.persist.include.forEach(function (name) { if (Object.prototype.hasOwnProperty.call(candidate, name)) restored[name] = candidate[name]; });
} else if (candidate != null) {
__diagnostic("WRN-PERSIST-INVALID", "Persisted state failed validation and was reset.", candidate);
}
}
} catch (error) { __diagnostic("WRN-PERSIST-RESTORE", "Persisted state could not be restored and was reset.", error); }
}
const raw = Object.assign({}, initial, restored || {}, __hydration[currentDefinition.name] || {});
let mutable = false;
let actionName = "direct";
function persistState() {
if (!currentDefinition.persist) return;
try {
const picked = {};
currentDefinition.persist.include.forEach(function (name) { picked[name] = raw[name]; });
const storage = __storage(currentDefinition.persist.storage);
if (storage) storage.setItem("wrnexus:store:" + currentDefinition.name, JSON.stringify({ version: currentDefinition.persist.version, state: picked }));
} catch (error) { __diagnostic("WRN-PERSIST-WRITE", "Persisted state could not be written.", error); }
}
const state = new Proxy(raw, {
set(target, property, value) {
if (!mutable) throw new TypeError("WRN-STORE-READONLY: " + currentDefinition.name + "." + String(property) + " must be changed by a store action.");
if (Object.is(target[property], value)) return true;
target[property] = value;
persistState();
listeners.forEach(function (listener) { listener(instance.snapshot(), { store: currentDefinition.name, action: actionName, changed: [String(property)] }); });
return true;
},
deleteProperty(target, property) {
if (!mutable) throw new TypeError("WRN-STORE-READONLY: store state is readonly outside actions");
return Reflect.deleteProperty(target, property);
},
});
const actions = {};
const server = new Proxy({}, { get: function (_target, property) { return function () { return __callServerFunction(currentDefinition.name, String(property), Array.prototype.slice.call(arguments)); }; } });
function installActions() {
Object.keys(actions).forEach(function (name) { delete actions[name]; });
Object.entries(currentDefinition.actions || {}).forEach(function (pair) {
const name = pair[0], candidates = pair[1];
const selected = candidates.find(function (entry) { return entry.runtime === "client"; }) || candidates.find(function (entry) { return entry.runtime === "shared"; }) || candidates.find(function (entry) { return entry.runtime === "legacy"; });
if (!selected) return;
actions[name] = async function () {
const args = Array.prototype.slice.call(arguments);
const previousMutable = mutable, previousAction = actionName;
mutable = true; actionName = name;
try { return await selected.handler({ state, snapshot: function () { return __clone(state); }, reset: function () { return instance.reset(); }, runtime: "client", routeId, server }, ...args); }
finally { mutable = previousMutable; actionName = previousAction; }
};
});
}
installActions();
const core = {
name: currentDefinition.name,
kind: currentDefinition.kind,
state,
actions,
whenReady: Promise.resolve(),
reset() {
mutable = true; actionName = "$reset";
try {
const next = currentDefinition.createState();
Object.keys(raw).forEach(function (name) { if (!(name in next)) delete raw[name]; });
Object.assign(raw, next); persistState();
listeners.forEach(function (listener) { listener(instance.snapshot(), { store: currentDefinition.name, action: "$reset", changed: Object.keys(next) }); });
} finally { mutable = false; actionName = "direct"; }
},
snapshot() { return Object.freeze(__clone(raw)); },
subscribe(listener) { listeners.add(listener); return function () { listeners.delete(listener); }; },
async dispose() {
mutable = true; actionName = "$dispose";
try { await currentDefinition.lifecycle && currentDefinition.lifecycle.dispose && currentDefinition.lifecycle.dispose({ state, runtime: "client", routeId, server }); }
finally { mutable = false; actionName = "direct"; listeners.clear(); __registry.delete(key); }
},
async __hotUpdate(nextDefinition) {
const previous = __clone(raw);
const nextShape = nextDefinition.createState();
const preserved = [], reset = [], added = [], removed = [];
Object.keys(previous).forEach(function (name) {
if (!(name in nextShape)) { removed.push(name); return; }
if (__compatible(nextShape[name], previous[name])) { nextShape[name] = previous[name]; preserved.push(name); }
else reset.push(name);
});
Object.keys(nextShape).forEach(function (name) { if (!(name in previous)) added.push(name); });
currentDefinition = nextDefinition;
mutable = true; actionName = "$hmr";
try {
Object.keys(raw).forEach(function (name) { delete raw[name]; });
Object.assign(raw, nextShape);
installActions(); persistState();
} finally { mutable = false; actionName = "direct"; }
const result = { store: currentDefinition.name, preserved, reset, added, removed };
listeners.forEach(function (listener) { listener(instance.snapshot(), { store: currentDefinition.name, action: "$hmr", changed: added.concat(reset, removed) }); });
try { __root.dispatchEvent && __root.dispatchEvent(new CustomEvent("wrnexus:store-hmr", { detail: result })); } catch (_) {}
return result;
},
};
const instance = new Proxy(core, {
get(target, property, receiver) {
if (Reflect.has(target, property)) return Reflect.get(target, property, receiver);
if (property in actions) return actions[property];
if (property in (currentDefinition.computed || {})) return currentDefinition.computed[property](state);
return state[property];
},
set() { throw new TypeError("WRN-STORE-READONLY: store state is readonly outside actions"); },
});
__registry.set(key, instance);
const hydrationSource = __hydration[currentDefinition.name];
const init = async function () {
const run = async function (name, hook) {
if (!hook) return;
mutable = true; actionName = name;
try { await hook({ state, runtime: "client", routeId, server }); }
finally { mutable = false; actionName = "direct"; }
};
await run("$clientInit", currentDefinition.lifecycle && currentDefinition.lifecycle.clientInit);
if (hydrationSource) await run("$hydrate", currentDefinition.lifecycle && currentDefinition.lifecycle.hydrate);
};
core.whenReady = init();
return instance;
}
if (!__root.__wrnexusApplyStoreHotUpdate) {
__root.__wrnexusApplyStoreHotUpdate = async function (name, definition) {
const results = [];
for (const item of Array.from(__registry.values())) if (item.name === name && typeof item.__hotUpdate === "function") results.push(await item.__hotUpdate(definition));
return results;
};
}
if (!__root.__wrnexusStoreContainer) {
__root.__wrnexusStoreContainer = {
async disposePageStores() { for (const item of Array.from(__registry.values())) if (item.kind === "page") await item.dispose(); },
async hotUpdate(name, definition) { return __root.__wrnexusApplyStoreHotUpdate(name, definition); },
inspect() { return Array.from(__registry.values()).map(function (item) { return { name: item.name, kind: item.kind, state: item.snapshot() }; }); },
};
}
export const ${ast.name}Definition = {
name: ${JSON.stringify(ast.name)},
kind: ${JSON.stringify(ast.storeKind)},
createState: () => (${initialState}),
computed: { ${computed} },
actions: { ${actions} },
persist: ${persistence},
lifecycle: { ${lifecycleEntries} },
};
export const ${ast.name} = __create(${ast.name}Definition);
export default ${ast.name};
`;
}
},
"packages/compiler/src/targets.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateTargets = generateTargets;
const component_contract_ts_1 = require("./component-contract.js");
const client_codegen_ts_1 = require("./client-codegen.js");
const server_codegen_ts_1 = require("./server-codegen.js");
const type_codegen_ts_1 = require("./type-codegen.js");
const store_codegen_ts_1 = require("./store-codegen.js");
function generateTargets(ast) {
return {
server: ast.kind === "global-store" || ast.kind === "page-store"
? (0, store_codegen_ts_1.generateStoreModule)(ast)
: (0, server_codegen_ts_1.generateServerFunctionsModule)(ast),
browser: ast.kind === "global-store" || ast.kind === "page-store"
? (0, store_codegen_ts_1.generateStoreBrowserModule)(ast)
: (0, client_codegen_ts_1.generateBrowserModule)(ast),
declarations: (0, type_codegen_ts_1.generateDeclarations)(ast),
contract: (0, component_contract_ts_1.createComponentContract)(ast),
rpc: (0, server_codegen_ts_1.rpcManifest)(ast),
};
}
},
"packages/compiler/src/tokenizer.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
/** @deprecated Import tokenizer APIs from @wrnexus/syntax. */
__exportStar(require("@wrnexus/syntax/tokenizer"), exports);
},
"packages/compiler/src/type-codegen.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateDeclarations = generateDeclarations;
function member(name) {
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
}
function params(astParams) {
return astParams
.map((param) => `${member(param.name)}${param.optional ? "?" : ""}: ${param.valueType ?? "unknown"}`)
.join(", ");
}
function generateDeclarations(ast) {
const inline = ast.types
.map((body) => body.trim())
.filter(Boolean)
.join("\n\n");
if (ast.kind === "global-store" || ast.kind === "page-store") {
const state = ast.states
.filter((entry) => entry.runtime !== "server")
.map((entry) => ` readonly ${member(entry.name)}: ${entry.valueType ?? "unknown"};`)
.join("\n");
const computed = ast.computed
.map((entry) => ` readonly ${member(entry.name)}: ${entry.valueType ?? "unknown"};`)
.join("\n");
const actions = ast.runtimeFunctions
.filter((fn) => fn.runtime !== "server")
.map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown")};`)
.join("\n");
return `${inline ? `${inline}\n\n` : ""}export interface ${ast.name}State {\n${state}\n}\n\nexport interface ${ast.name}Computed {\n${computed}\n}\n\nexport interface ${ast.name}Actions {\n${actions}\n}\n\nexport interface ${ast.name}Instance extends ${ast.name}State, ${ast.name}Computed, ${ast.name}Actions {\n reset(): void;\n snapshot(): Readonly<${ast.name}State>;\n}\n\ndeclare const store: ${ast.name}Instance;\nexport default store;\n`;
}
const props = ast.props
.map((prop) => ` readonly ${member(prop.name)}${prop.required ? "" : "?"}: ${prop.valueType ?? "unknown"};`)
.join("\n");
const outputs = ast.outputs
.map((output) => ` ${member(output.name)}(${output.payload ? `${member(output.payload.name)}${output.payload.optional ? "?" : ""}: ${output.payload.valueType}` : ""}): void;`)
.join("\n");
const clientFunctions = ast.runtimeFunctions
.filter((fn) => fn.runtime === "client" || fn.runtime === "shared" || fn.runtime === "legacy")
.map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise<unknown>" : "unknown")};`)
.join("\n");
const serverFunctions = ast.runtimeFunctions
.filter((fn) => fn.runtime === "server")
.map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): Promise<Awaited<${fn.returnType ?? "unknown"}>>;`)
.join("\n");
return `${inline ? `${inline}\n\n` : ""}export interface ${ast.name}Props {\n${props}\n}\n\nexport interface ${ast.name}Outputs {\n${outputs}\n}\n\nexport interface ${ast.name}ClientFunctions {\n${clientFunctions}\n}\n\nexport interface ${ast.name}ServerCalls {\n${serverFunctions}\n}\n`;
}
},
"packages/compiler/src/types.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
/** @deprecated Import language type utilities from @wrnexus/syntax. */
__exportStar(require("@wrnexus/syntax/types"), exports);
},
"packages/syntax/src/diagnostics.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.containsReadonlyPropMutation = containsReadonlyPropMutation;
exports.positionAt = positionAt;
exports.classifyParseError = classifyParseError;
exports.diagnosticFromError = diagnosticFromError;
exports.diagnose = diagnose;
exports.assertValidAst = assertValidAst;
exports.isHydrationStrategy = isHydrationStrategy;
exports.isRuntimeTarget = isRuntimeTarget;
exports.formatDiagnostic = formatDiagnostic;
const parser_ts_1 = require("./parser.js");
const spec_ts_1 = require("./spec.js");
function stripAsciiControlAndSpace(value) {
let result = "";
for (const character of value) {
if (character.charCodeAt(0) > 0x20)
result += character;
}
return result;
}
function maskJavaScriptTrivia(source) {
let result = "";
let index = 0;
let quote = null;
let lineComment = false;
let blockComment = false;
while (index < source.length) {
const char = source[index];
const next = source[index + 1];
if (lineComment) {
if (char === "\n") {
lineComment = false;
result += "\n";
}
else
result += " ";
index++;
continue;
}
if (blockComment) {
if (char === "*" && next === "/") {
result += " ";
index += 2;
blockComment = false;
}
else {
result += char === "\n" ? "\n" : " ";
index++;
}
continue;
}
if (quote) {
if (char === "\\") {
result += " ";
index += Math.min(2, source.length - index);
}
else if (char === quote) {
result += " ";
index++;
quote = null;
}
else {
result += char === "\n" ? "\n" : " ";
index++;
}
continue;
}
if (char === "/" && next === "/") {
result += " ";
index += 2;
lineComment = true;
continue;
}
if (char === "/" && next === "*") {
result += " ";
index += 2;
blockComment = true;
continue;
}
if (char === "'" || char === '"' || char === "`") {
quote = char;
result += " ";
index++;
continue;
}
result += char;
index++;
}
return result;
}
function containsReadonlyPropMutation(body, propName, parameterNames) {
const code = maskJavaScriptTrivia(body);
const escaped = propName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const operator = String.raw `(?:\+\+|--|(?:\*\*|&&|\|\||\?\?|[+\-*/%&|^])?=(?!=|>))`;
if (new RegExp(String.raw `\bprops\.${escaped}\s*${operator}`).test(code))
return true;
if (parameterNames.has(propName))
return false;
if (new RegExp(String.raw `\b(?:const|let|var)\s+${escaped}\b`).test(code))
return false;
return new RegExp(String.raw `(?:^|[^\w$.])${escaped}\s*${operator}`, "m").test(code);
}
function positionAt(source, offset) {
const safe = Math.max(0, Math.min(offset, source.length));
const before = source.slice(0, safe);
const lines = before.split(/\r?\n/);
return { offset: safe, line: lines.length, column: (lines.at(-1)?.length ?? 0) + 1 };
}
function offsetFromMessage(message) {
const match = /offset\s+(\d+)/i.exec(message);
return match ? Number(match[1]) : undefined;
}
function classifyParseError(message) {
if (/Expected 'page', 'component', or 'layout'/.test(message))
return spec_ts_1.WRN_DIAGNOSTIC_CODES.root;
if (/Unknown (?:page|component|layout|ssr|client) member/.test(message)) {
return spec_ts_1.WRN_DIAGNOSTIC_CODES.member;
}
if (/prop initializer|Expected eq/.test(message))
return spec_ts_1.WRN_DIAGNOSTIC_CODES.propInitializer;
if (/State '.+' requires an initializer/.test(message)) {
return spec_ts_1.WRN_DIAGNOSTIC_CODES.stateInitializer;
}
if (/Cannot watch undeclared state/.test(message))
return spec_ts_1.WRN_DIAGNOSTIC_CODES.watchUndeclared;
return spec_ts_1.WRN_DIAGNOSTIC_CODES.parse;
}
function diagnosticFromError(source, error, options = {}) {
const message = error instanceof Error ? error.message : String(error);
const offset = error instanceof parser_ts_1.ParseError && error.offset !== undefined
? error.offset
: offsetFromMessage(message);
return {
code: error instanceof parser_ts_1.ParseError ? error.code : classifyParseError(message),
severity: "error",
message,
file: options.file,
...(offset === undefined ? {} : { position: positionAt(source, offset) }),
};
}
function walk(nodes, visit) {
for (const node of nodes) {
visit(node);
if (node.type === "element")
walk(node.children, visit);
else if (node.type === "each") {
walk(node.body, visit);
walk(node.empty, visit);
}
else if (node.type === "if") {
for (const branch of node.branches)
walk(branch.body, visit);
}
}
}
function astDiagnostics(ast, options) {
const diagnostics = [];
const seen = new Map();
for (const [kind, declarations] of [
["prop", ast.props],
["state", ast.states],
["computed", ast.computed],
]) {
for (const declaration of declarations) {
const previous = seen.get(declaration.name);
if (previous) {
diagnostics.push({
code: spec_ts_1.WRN_DIAGNOSTIC_CODES.duplicateSymbol,
severity: "error",
message: `Duplicate symbol '${declaration.name}' (${previous} and ${kind}).`,
hint: "Rename one declaration so every prop, state, and computed value is unique.",
file: options.file,
});
}
else {
seen.set(declaration.name, kind);
}
}
}
if (ast.hydrate && !isHydrationStrategy(ast.hydrate)) {
diagnostics.push({
code: spec_ts_1.WRN_DIAGNOSTIC_CODES.invalidHydration,
severity: "error",
message: `Unknown hydration strategy '${ast.hydrate}'.`,
hint: "Use load, idle, visible, interaction, none, or media:<query>.",
file: options.file,
});
}
if (ast.runtime && !isRuntimeTarget(ast.runtime)) {
diagnostics.push({
code: spec_ts_1.WRN_DIAGNOSTIC_CODES.invalidRuntime,
severity: "error",
message: `Unknown runtime target '${ast.runtime}'.`,
hint: "Use server, client, or universal.",
file: options.file,
});
}
let interactive = ast.states.length > 0 || ast.effects.length > 0 || ast.watches.length > 0;
const urlAttributes = new Set([
"href",
"src",
"action",
"formaction",
"poster",
"cite",
"background",
"xlink:href",
]);
walk(ast.view, (node) => {
if (node.type !== "element")
return;
if (node.attrs.some((attribute) => attribute.event))
interactive = true;
const tag = node.tag.toLowerCase();
for (const attribute of node.attrs) {
if (attribute.event && (attribute.name === "for" || attribute.name === "key")) {
diagnostics.push({
code: "WRN-TEMPLATE-LOOP-DIRECTIVE",
severity: "error",
message: `@${attribute.name} is an event binding, not a loop directive.`,
hint: attribute.name === "for"
? 'Use data-for="item in items".'
: 'Use data-key="item.id" alongside data-for.',
file: options.file,
});
}
if (attribute.event || attribute.boolean || !urlAttributes.has(attribute.name.toLowerCase()))
continue;
if (attribute.value.includes("{"))
continue;
const value = stripAsciiControlAndSpace(attribute.value.trim()).toLowerCase();
if (/^(?:javascript|vbscript|file):/.test(value) ||
/^data:(?!image\/(?:png|gif|jpeg|webp|avif);)/.test(value)) {
diagnostics.push({
code: "WRN-SEC-UNSAFE-URL",
severity: "error",
message: `Unsafe URL protocol in ${attribute.name} on <${node.tag}>.`,
hint: "Use a relative URL, https:, mailto:, tel:, or a framework-validated URL helper.",
file: options.file,
});
}
}
if (tag === "a") {
const target = node.attrs.find((attribute) => attribute.name === "target")?.value;
const rel = node.attrs.find((attribute) => attribute.name === "rel")?.value ?? "";
if (target === "_blank" && !/\bnoopener\b/i.test(rel)) {
diagnostics.push({
code: "WRN-SEC-BLANK-REL",
severity: "warning",
message: "A target=_blank link should include rel=noopener.",
hint: 'Add rel="noopener noreferrer".',
file: options.file,
});
}
}
if (!options.accessibility)
return;
if (tag === "img") {
if (!node.attrs.some((attribute) => attribute.name === "alt")) {
diagnostics.push({
code: spec_ts_1.WRN_DIAGNOSTIC_CODES.accessibility,
severity: "warning",
message: "Image is missing an alt attribute.",
hint: 'Add alt text, or alt="" for a decorative image.',
file: options.file,
});
}
const hasWidth = node.attrs.some((attribute) => attribute.name === "width");
const hasHeight = node.attrs.some((attribute) => attribute.name === "height");
if (!hasWidth || !hasHeight) {
diagnostics.push({
code: "WRN-PERF-IMAGE-DIMENSIONS",
severity: "warning",
message: "Image width and height are required to prevent layout shifts.",
hint: "Declare intrinsic width and height, or use @wrnexus/image.",
file: options.file,
});
}
}
});
if (ast.runtime === "server" && interactive) {
diagnostics.push({
code: spec_ts_1.WRN_DIAGNOSTIC_CODES.serverInteractive,
severity: "error",
message: "A server-only WRN root cannot contain client state, effects, watches, or event handlers.",
hint: 'Use runtime = "universal" or remove interactive behavior.',
file: options.file,
});
}
const outputs = new Set(ast.outputs.map((output) => output.name));
for (const fn of ast.runtimeFunctions) {
for (const call of fn.body.matchAll(/\boutput\.([A-Za-z_$][\w$]*)\s*\(/g)) {
const outputName = call[1];
if (fn.runtime === "server") {
diagnostics.push({
code: "WRN-OUTPUT-SERVER-CALL",
severity: "error",
message: `Server function '${fn.name}' cannot call output.${outputName}().`,
hint: "Return a typed value to the browser and call the output from a client function.",
file: options.file,
});
}
else if (!outputs.has(outputName)) {
diagnostics.push({
code: "WRN-OUTPUT-UNKNOWN",
severity: "error",
message: `Unknown output '${outputName}' called from '${fn.name}'.`,
hint: `Declare ${outputName}(payload) inside outputs { ... }.`,
file: options.file,
});
}
}
if (fn.runtime === "client" && /\b(?:process|Bun|Deno|__dirname|require)\b/.test(fn.body)) {
diagnostics.push({
code: "WRN-CLIENT-SERVER-API",
severity: "error",
message: `Client function '${fn.name}' references a server-only API.`,
hint: "Move that operation into a server function and call it through server.name(...).",
file: options.file,
});
}
if (fn.runtime === "server" &&
/\b(?:window|document|localStorage|sessionStorage|navigator)\b/.test(fn.body)) {
diagnostics.push({
code: "WRN-SERVER-BROWSER-API",
severity: "error",
message: `Server function '${fn.name}' references a browser-only API.`,
hint: "Move that code into a client function.",
file: options.file,
});
}
if (fn.runtime !== "server" &&
/\b(?:eval\s*\(|new\s+Function\s*\(|document\.write\s*\(|\.innerHTML\s*=|\.outerHTML\s*=|insertAdjacentHTML\s*\()/.test(fn.body)) {
diagnostics.push({
code: "WRN-SEC-DOM-SINK",
severity: "error",
message: `Client function '${fn.name}' uses an unsafe dynamic-code or HTML sink.`,
hint: "Use compiled templates, textContent, typed outputs, or a reviewed TrustedHTML sanitizer.",
file: options.file,
});
}
if (fn.runtime !== "server" && /\b(?:setTimeout|setInterval)\s*\(\s*["'`]/.test(fn.body)) {
diagnostics.push({
code: "WRN-SEC-STRING-TIMER",
severity: "error",
message: `Client function '${fn.name}' passes a string to a timer.`,
hint: "Pass a function instead of executable text.",
file: options.file,
});
}
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
for (const prop of ast.props) {
if (containsReadonlyPropMutation(fn.body, prop.name, parameterNames)) {
diagnostics.push({
code: "WRN-PROP-READONLY",
severity: "error",
message: `Function '${fn.name}' attempts to mutate readonly prop '${prop.name}'.`,
hint: "Copy the prop into state before mutating it.",
file: options.file,
});
}
}
}
for (const state of ast.states) {
if (state.runtime === "shared" &&
/^(?:new\s+(?:Map|Set|WeakMap|WeakSet)|(?:async\s+)?function\b|.*=>)/.test(state.expr.trim())) {
diagnostics.push({
code: "WRN-STATE-NON-SERIALIZABLE",
severity: "error",
message: `Shared state '${state.name}' is not safely serializable.`,
hint: "Use JSON-compatible data or move the value into client/server state.",
file: options.file,
});
}
if (state.runtime !== "server" &&
/\b(?:process\.env|Bun\.env|Deno\.env|ctx\.env|import\.meta\.env)\b/.test(state.expr)) {
diagnostics.push({
code: "WRN-SEC-SERVER-SECRET-SOURCE",
severity: "error",
message: `Browser-visible state '${state.name}' reads from a server environment source.`,
hint: "Move environment-backed values into server state and return only an explicitly safe result.",
file: options.file,
});
}
}
if (ast.persist) {
const stateNames = new Set(ast.states.filter((state) => state.runtime !== "server").map((state) => state.name));
for (const name of ast.persist.include)
if (!stateNames.has(name))
diagnostics.push({
code: "WRN-PERSIST-UNKNOWN-FIELD",
severity: "error",
message: `Persist include references unknown or server-only state '${name}'.`,
hint: "Persist only declared shared/client state fields.",
file: options.file,
});
for (const name of ast.persist.include)
if (/token|password|secret|otp|api.?key/i.test(name))
diagnostics.push({
code: "WRN-PERSIST-SENSITIVE",
severity: "error",
message: `Sensitive field '${name}' cannot be persisted.`,
hint: "Remove secrets, tokens, passwords, OTPs, and API keys from persistence.",
file: options.file,
});
}
return diagnostics;
}
function diagnose(source, options = {}) {
try {
return astDiagnostics((0, parser_ts_1.parse)(source), options);
}
catch (error) {
return [diagnosticFromError(source, error, options)];
}
}
function assertValidAst(ast, options = {}) {
const errors = astDiagnostics(ast, options).filter((diagnostic) => diagnostic.severity === "error");
if (!errors.length)
return;
const first = errors[0];
throw new parser_ts_1.ParseError(first.message, first.code);
}
function isHydrationStrategy(value) {
return (spec_ts_1.WRN_HYDRATION_STRATEGIES.includes(value) ||
(value.startsWith("media:") && value.length > "media:".length));
}
function isRuntimeTarget(value) {
return spec_ts_1.WRN_RUNTIME_TARGETS.includes(value);
}
function formatDiagnostic(source, diagnostic) {
const location = diagnostic.position
? `${diagnostic.file ?? "<inline .wrn>"}:${diagnostic.position.line}:${diagnostic.position.column}`
: (diagnostic.file ?? "<inline .wrn>");
const lines = [
`${diagnostic.code} ${diagnostic.severity.toUpperCase()}`,
"",
diagnostic.message,
"",
location,
];
if (diagnostic.position) {
const sourceLine = source.split(/\r?\n/)[diagnostic.position.line - 1] ?? "";
lines.push("", sourceLine, `${" ".repeat(Math.max(0, diagnostic.position.column - 1))}^`);
}
if (diagnostic.hint)
lines.push("", `Hint: ${diagnostic.hint}`);
return lines.join("\n");
}
},
"packages/syntax/src/formatter.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
// The formatter intentionally operates on partially written source. Its small
// scanner values are dynamically shaped, while the public API below remains typed.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatWrn = formatWrn;
const VOID_ELEMENTS = new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr",
]);
function splitPropDeclarations(value) {
const declarations = [];
let start = 0;
let index = 0;
let quote = null;
let escaped = false;
let square = 0;
let brace = 0;
let paren = 0;
let segmentHasColon = false;
let segmentHasEquals = false;
const isIdentifierStart = (character) => /[A-Za-z_]/.test(character || "");
const isIdentifierPart = (character) => /[A-Za-z0-9_]/.test(character || "");
const beginsDeclaration = (position) => {
let cursor = position;
while (cursor < value.length && /[ \t]/.test(value[cursor]))
cursor += 1;
if (value.slice(cursor).startsWith("@event")) {
cursor += "@event".length;
if (!/\s/.test(value[cursor] || ""))
return false;
while (cursor < value.length && /\s/.test(value[cursor]))
cursor += 1;
if (!isIdentifierStart(value[cursor]))
return false;
cursor += 1;
while (cursor < value.length && isIdentifierPart(value[cursor]))
cursor += 1;
while (cursor < value.length && /[ \t]/.test(value[cursor]))
cursor += 1;
return value[cursor] === "=" ? "=" : null;
}
if (!isIdentifierStart(value[cursor]))
return false;
cursor += 1;
while (cursor < value.length && isIdentifierPart(value[cursor]))
cursor += 1;
if (value[cursor] === "?")
cursor += 1;
while (cursor < value.length && /[ \t]/.test(value[cursor]))
cursor += 1;
return value[cursor] === "=" || value[cursor] === ":" ? value[cursor] : null;
};
while (index < value.length) {
const character = value[index];
if (quote !== null) {
if (escaped)
escaped = false;
else if (character === "\\")
escaped = true;
else if (character === quote)
quote = null;
index += 1;
continue;
}
if (character === '"' || character === "'" || character === "`") {
quote = character;
index += 1;
continue;
}
if (character === "[")
square += 1;
else if (character === "]" && square > 0)
square -= 1;
else if (character === "{")
brace += 1;
else if (character === "}" && brace > 0)
brace -= 1;
else if (character === "(")
paren += 1;
else if (character === ")" && paren > 0)
paren -= 1;
const topLevel = square === 0 && brace === 0 && paren === 0;
if (topLevel && character === ":")
segmentHasColon = true;
if (topLevel && character === "=")
segmentHasEquals = true;
const candidateDelimiter = topLevel && /\s/.test(character) ? beginsDeclaration(index) : null;
const beginsNext = candidateDelimiter === ":" ||
(candidateDelimiter === "=" && (segmentHasEquals || !segmentHasColon));
if (topLevel &&
/\s/.test(character) &&
value.slice(start, index).trim() !== "@event" &&
beginsNext) {
const declaration = value.slice(start, index).trim();
if (declaration)
declarations.push(declaration);
while (index < value.length && /\s/.test(value[index]))
index += 1;
start = index;
segmentHasColon = false;
segmentHasEquals = false;
continue;
}
index += 1;
}
const declaration = value.slice(start).trim();
if (declaration)
declarations.push(declaration);
return declarations;
}
function splitOutputDeclarations(value) {
const declarations = [];
let start = 0;
let paren = 0;
let angle = 0;
let square = 0;
let quote = null;
let escaped = false;
const startsOutput = (position) => {
let cursor = position;
while (cursor < value.length && /\s/.test(value[cursor]))
cursor += 1;
if (!/[A-Za-z_$]/.test(value[cursor] || ""))
return false;
cursor += 1;
while (cursor < value.length && /[A-Za-z0-9_$]/.test(value[cursor] || ""))
cursor += 1;
while (cursor < value.length && /\s/.test(value[cursor]))
cursor += 1;
return value[cursor] === "(";
};
for (let index = 0; index < value.length; index += 1) {
const character = value[index];
if (quote !== null) {
if (escaped)
escaped = false;
else if (character === "\\")
escaped = true;
else if (character === quote)
quote = null;
continue;
}
if (character === '"' || character === "'" || character === "`") {
quote = character;
continue;
}
if (character === "(")
paren += 1;
else if (character === ")" && paren > 0)
paren -= 1;
else if (character === "[")
square += 1;
else if (character === "]" && square > 0)
square -= 1;
else if (character === "<")
angle += 1;
else if (character === ">" && angle > 0)
angle -= 1;
if (paren === 0 && square === 0 && angle === 0 && /\s/.test(character) && startsOutput(index)) {
const declaration = value.slice(start, index).trim();
if (declaration)
declarations.push(declaration);
while (index < value.length && /\s/.test(value[index]))
index += 1;
start = index;
index -= 1;
}
}
const finalDeclaration = value.slice(start).trim();
if (finalDeclaration)
declarations.push(finalDeclaration);
return declarations;
}
function formatInlineDeclarationBlock(value, unit, depth) {
const match = /^(props|state|computed|outputs)\s*\{([\s\S]*)\}$/.exec(value.trim());
if (!match)
return null;
const declarations = match[1] === "outputs"
? splitOutputDeclarations(match[2].trim())
: splitPropDeclarations(match[2].trim());
return [
`${unit.repeat(depth)}${match[1]} {`,
...declarations.map((declaration) => `${unit.repeat(depth + 1)}${declaration}`),
`${unit.repeat(depth)}}`,
];
}
function formatInlinePropsBlock(value, unit, depth) {
const match = /^props\s*\{([\s\S]*)\}$/.exec(value.trim());
if (!match)
return null;
const declarations = splitPropDeclarations(match[1].trim());
if (declarations.length === 0) {
return [`${unit.repeat(depth)}props {`, `${unit.repeat(depth)}}`];
}
return [
`${unit.repeat(depth)}props {`,
...declarations.map((declaration) => `${unit.repeat(depth + 1)}${declaration}`),
`${unit.repeat(depth)}}`,
];
}
function findOpeningTagEnd(value) {
let quote = null;
let escaped = false;
for (let index = 0; index < value.length; index += 1) {
const character = value[index];
if (quote !== null) {
if (escaped) {
escaped = false;
continue;
}
if (character === "\\") {
escaped = true;
continue;
}
if (character === quote) {
quote = null;
}
continue;
}
if (character === '"' || character === "'") {
quote = character;
continue;
}
if (character === ">") {
return index;
}
}
return -1;
}
function parseAttributes(value) {
const attributes = [];
let index = 0;
while (index < value.length) {
while (index < value.length && /\s/.test(value[index]))
index += 1;
if (index >= value.length)
break;
const start = index;
while (index < value.length && !/[\s=]/.test(value[index]))
index += 1;
while (index < value.length && /\s/.test(value[index]))
index += 1;
if (value[index] === "=") {
index += 1;
while (index < value.length && /\s/.test(value[index]))
index += 1;
const quote = value[index];
if (quote === '"' || quote === "'") {
index += 1;
let escaped = false;
while (index < value.length) {
const character = value[index++];
if (escaped)
escaped = false;
else if (character === "\\")
escaped = true;
else if (character === quote)
break;
}
}
else if (value[index] === "{") {
let depth = 0;
let expressionQuote = null;
let escaped = false;
while (index < value.length) {
const character = value[index++];
if (expressionQuote !== null) {
if (escaped)
escaped = false;
else if (character === "\\")
escaped = true;
else if (character === expressionQuote)
expressionQuote = null;
continue;
}
if (character === '"' || character === "'" || character === "`") {
expressionQuote = character;
}
else if (character === "{") {
depth += 1;
}
else if (character === "}" && --depth === 0) {
break;
}
}
}
else {
while (index < value.length && !/\s/.test(value[index]))
index += 1;
}
}
const attribute = value.slice(start, index).trim();
if (attribute)
attributes.push(attribute);
}
return attributes;
}
function parseStructuredAttribute(attribute) {
const match = /^([^\s=]+)\s*=\s*\{([\s\S]*)\}$/.exec(attribute);
if (!match)
return null;
const expression = match[2].trim();
if (!expression.startsWith("[") && !expression.startsWith("{"))
return null;
try {
return {
name: match[1],
value: JSON.parse(expression),
};
}
catch {
return null;
}
}
function formatAttribute(attribute, indentation, unit) {
const structured = parseStructuredAttribute(attribute);
if (!structured)
return [`${indentation}${attribute}`];
const jsonLines = JSON.stringify(structured.value, null, unit).split("\n");
if (jsonLines.length === 1) {
return [`${indentation}${structured.name}={${jsonLines[0]}}`];
}
return [
`${indentation}${structured.name}={${jsonLines[0]}`,
...jsonLines.slice(1, -1).map((line) => `${indentation}${line}`),
`${indentation}${jsonLines.at(-1)}}`,
];
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function parseOpeningTag(value) {
const endIndex = findOpeningTagEnd(value);
if (endIndex === -1) {
return null;
}
const openingPart = value.slice(0, endIndex + 1);
const remainder = value.slice(endIndex + 1).trim();
const match = /^<([A-Za-z][\w$:.-]*)([\s\S]*?)(\/?)>$/.exec(openingPart);
if (!match) {
return null;
}
const tagName = match[1];
const attributes = parseAttributes(match[2].trim());
const selfClosing = match[3] === "/";
const escapedTagName = escapeRegExp(tagName);
const immediateClosing = new RegExp(`^<\\/${escapedTagName}\\s*>`, "i").test(remainder);
const trailingClosingMatch = new RegExp(`^([\\s\\S]*?)<\\/${escapedTagName}\\s*>$`, "i").exec(remainder);
const trailingClosing = trailingClosingMatch !== null;
const inlineContent = trailingClosing ? trailingClosingMatch[1].trim() : "";
const inlineClosing = trailingClosing && inlineContent.length === 0;
const closesInRemainder = immediateClosing || trailingClosing;
return {
tagName,
attributes,
selfClosing,
inlineClosing,
immediateClosing,
trailingClosing,
inlineContent,
closesInRemainder,
remainder,
};
}
function formatOpeningTag(value, unit, depth, printWidth = 100, multilineAttributes = true) {
const parsed = parseOpeningTag(value);
if (!parsed) {
return {
lines: [`${unit.repeat(depth)}${value.trim()}`],
opensElement: false,
};
}
const baseIndent = unit.repeat(depth);
const childIndent = unit.repeat(depth + 1);
const normalizedOpening = `<${parsed.tagName}` +
`${parsed.attributes.length ? ` ${parsed.attributes.join(" ")}` : ""}` +
`${parsed.selfClosing ? " /" : ""}>`;
const normalizedSingleLine = `${normalizedOpening}${parsed.remainder}`;
const shouldBreak = value.includes("\n") ||
(multilineAttributes && parsed.attributes.length > 0) ||
baseIndent.length + normalizedSingleLine.length > printWidth;
const opensElement = !parsed.selfClosing &&
!parsed.closesInRemainder &&
!VOID_ELEMENTS.has(parsed.tagName.toLowerCase());
if (!shouldBreak) {
return {
lines: [`${baseIndent}${normalizedSingleLine}`],
opensElement,
};
}
if (parsed.attributes.length === 0 && !parsed.selfClosing) {
const lines = [`${baseIndent}<${parsed.tagName}>`];
if (parsed.trailingClosing) {
if (parsed.inlineContent)
lines.push(`${childIndent}${parsed.inlineContent}`);
lines.push(`${baseIndent}</${parsed.tagName}>`);
}
else if (parsed.remainder) {
lines.push(`${childIndent}${parsed.remainder}`);
}
return { lines, opensElement };
}
const lines = [
`${baseIndent}<${parsed.tagName}`,
...parsed.attributes.flatMap((attribute) => formatAttribute(attribute, childIndent, unit)),
];
if (parsed.selfClosing) {
lines.push(`${baseIndent}/>`);
return {
lines,
opensElement,
};
}
lines.push(`${baseIndent}>`);
if (parsed.trailingClosing) {
if (parsed.inlineContent) {
lines.push(`${childIndent}${parsed.inlineContent}`);
}
lines.push(`${baseIndent}</${parsed.tagName}>`);
}
else if (parsed.remainder) {
lines.push(`${parsed.immediateClosing ? baseIndent : childIndent}${parsed.remainder}`);
}
return {
lines,
opensElement,
};
}
function isMultilineOpeningTagStart(value) {
if (!value.startsWith("<")) {
return false;
}
if (value.startsWith("</") ||
value.startsWith("<!--") ||
value.startsWith("<!") ||
value.startsWith("<?")) {
return false;
}
return findOpeningTagEnd(value) === -1;
}
function isClosingTag(value) {
return /^<\/[A-Za-z][\w$:.-]*\s*>/.test(value);
}
/**
* Preserve compact, already-balanced HTML fragments as one line.
*
* A line such as `<span>Page</span><b>→</b><span>API</span>` is valid and
* intentionally compact. Expanding only its first opening tag makes later
* formatter passes treat the remaining siblings as children, causing runaway
* indentation and false closing-tag diagnostics. Balanced fragments are kept
* intact while normal multiline opening tags continue through the formatter.
*/
function isBalancedInlineHtmlFragment(value) {
if (!value.startsWith("<") || value.startsWith("</") || value.startsWith("<!--")) {
return false;
}
const stack = [];
let tagCount = 0;
let rootCount = 0;
let hasNestedElement = false;
let hasOutsideText = false;
let index = 0;
while (index < value.length) {
const tagStart = value.indexOf("<", index);
if (tagStart === -1) {
if (stack.length === 0 && value.slice(index).trim())
hasOutsideText = true;
break;
}
if (stack.length === 0 && value.slice(index, tagStart).trim())
hasOutsideText = true;
if (value.startsWith("<!--", tagStart)) {
const commentEnd = value.indexOf("-->", tagStart + 4);
if (commentEnd === -1)
return false;
index = commentEnd + 3;
continue;
}
const relativeEnd = findOpeningTagEnd(value.slice(tagStart));
if (relativeEnd === -1)
return false;
const tag = value.slice(tagStart, tagStart + relativeEnd + 1);
const match = /^<\/?([A-Za-z][\w$:.-]*)[\s\S]*?>$/.exec(tag);
if (!match) {
index = tagStart + 1;
continue;
}
tagCount += 1;
const tagName = match[1];
const normalizedName = /^[a-z]/.test(tagName) ? tagName.toLowerCase() : tagName;
const closing = tag.startsWith("</");
const selfClosing = /\/\s*>$/.test(tag);
const voidElement = VOID_ELEMENTS.has(tagName.toLowerCase());
if (closing) {
if (stack.at(-1) !== normalizedName)
return false;
stack.pop();
}
else {
if (stack.length === 0)
rootCount += 1;
else
hasNestedElement = true;
if (!selfClosing && !voidElement)
stack.push(normalizedName);
}
index = tagStart + relativeEnd + 1;
}
return (tagCount >= 2 && stack.length === 0 && (rootCount > 1 || hasNestedElement || hasOutsideText));
}
function isControlBlockOpen(value) {
return /^\{#(?:if|each)\b[\s\S]*\}$/.test(value);
}
function isControlBlockMiddle(value) {
return /^\{:(?:else(?:\s+if\b[\s\S]*)?|empty)\}$/.test(value);
}
function isControlBlockClose(value) {
return /^\{\/(?:if|each)\}$/.test(value);
}
function countLeadingClosingBraces(value) {
let index = 0;
let count = 0;
while (index < value.length) {
while (index < value.length && /\s/.test(value[index])) {
index += 1;
}
if (value[index] !== "}" && value[index] !== "]") {
break;
}
count += 1;
index += 1;
}
return count;
}
/**
* Count braces outside strings and HTML comments.
*
* This supports WRN blocks, function bodies, lifecycle hooks,
* watcher bodies and multiline JavaScript object literals.
*/
function countStructuralBraces(value) {
let openings = 0;
let closings = 0;
let quote = null;
let escaped = false;
let htmlComment = false;
for (let index = 0; index < value.length; index += 1) {
if (!quote && !htmlComment && value.startsWith("<!--", index)) {
htmlComment = true;
index += 3;
continue;
}
if (htmlComment && value.startsWith("-->", index)) {
htmlComment = false;
index += 2;
continue;
}
if (htmlComment) {
continue;
}
const character = value[index];
if (quote !== null) {
if (escaped) {
escaped = false;
continue;
}
if (character === "\\") {
escaped = true;
continue;
}
if (character === quote) {
quote = null;
}
continue;
}
if (character === '"' || character === "'" || character === "`") {
quote = character;
continue;
}
if (character === "{") {
openings += 1;
}
else if (character === "}") {
closings += 1;
}
else if (character === "[") {
openings += 1;
}
else if (character === "]") {
closings += 1;
}
}
return {
openings,
closings,
};
}
function collectOpeningTag(inputLines, startIndex) {
const collected = [inputLines[startIndex].trim()];
let index = startIndex;
while (index + 1 < inputLines.length) {
const joined = collected.join(" ");
if (findOpeningTagEnd(joined) !== -1) {
break;
}
index += 1;
collected.push(inputLines[index].trim());
}
return {
// Preserve the fact that the opening tag was already multiline so a
// second formatter pass cannot collapse it back to one line.
value: collected.join("\n"),
endIndex: index,
};
}
function isPreservedRawBlockStart(value) {
return (/<pre(?:\s|>)/i.test(value) &&
!/<\/pre\s*>/i.test(value.slice(0, value.search(/<pre(?:\s|>)/i))));
}
function hasPreservedRawBlockEnd(value) {
return /<\/pre\s*>/i.test(value);
}
function transformOutsidePreservedRawBlocks(lines, transformLine) {
const output = [];
let preserving = false;
for (const line of lines) {
if (preserving) {
output.push(line);
if (hasPreservedRawBlockEnd(line))
preserving = false;
continue;
}
if (isPreservedRawBlockStart(line)) {
output.push(line);
preserving = !hasPreservedRawBlockEnd(line);
continue;
}
output.push(...transformLine(line));
}
return output;
}
function collectPreservedRawBlock(lines, startIndex) {
const collected = [lines[startIndex]];
let index = startIndex;
while (!hasPreservedRawBlockEnd(collected.at(-1) || "") && index + 1 < lines.length) {
index += 1;
collected.push(lines[index]);
}
return { lines: collected, endIndex: index };
}
/**
* Put WRN template control markers on their own lines before indentation.
*
* Authors commonly write compact fragments such as
* `{#if loading}<span>…</span>{/if}`. Treating that as one line prevents the
* normal HTML and control-block formatters from seeing its structure.
*/
function expandInlineControlBlocks(lines) {
const marker = /(\{#(?:if|each)\b[^}]*\}|\{:(?:else(?:\s+if\b[^}]*)?|empty)\}|\{\/(?:if|each)\})/g;
return transformOutsidePreservedRawBlocks(lines, (line) => {
if (!marker.test(line))
return [line];
marker.lastIndex = 0;
const indentation = line.match(/^\s*/)?.[0] ?? "";
const segments = line
.split(marker)
.map((segment) => segment.trim())
.filter(Boolean);
return segments.map((segment) => `${indentation}${segment}`);
});
}
function expandStructuredStateDeclarations(lines, unit) {
return transformOutsidePreservedRawBlocks(lines, (line) => {
const match = /^(\s*state\s+[A-Za-z_$][\w$]*\s*=\s*)([\\[{][\s\S]*)$/.exec(line);
if (!match)
return [line];
try {
const parsed = JSON.parse(match[2].trim());
const jsonLines = JSON.stringify(parsed, null, unit).split("\n");
if (jsonLines.length === 1)
return [`${match[1]}${jsonLines[0]}`];
const leading = match[1].match(/^\s*/)?.[0] ?? "";
return [
`${match[1]}${jsonLines[0]}`,
...jsonLines.slice(1).map((jsonLine) => `${leading}${jsonLine}`),
];
}
catch {
return [line];
}
});
}
function formatWrnPass(source, options = {}) {
const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize ?? 4);
const printWidth = options.printWidth ?? 100;
const multilineAttributes = options.multilineAttributes !== false;
let codeDepth = 0;
let htmlDepth = 0;
let controlDepth = 0;
let index = 0;
const sourceLines = source.replace(/\r\n/g, "\n").split("\n");
const inputLines = expandInlineControlBlocks(expandStructuredStateDeclarations(sourceLines, unit));
const output = [];
let previousWasBlank = false;
while (index < inputLines.length) {
const originalLine = inputLines[index];
let value = originalLine.trim();
if (value === "") {
if (!previousWasBlank && output.length > 0) {
output.push("");
}
previousWasBlank = true;
index += 1;
continue;
}
previousWasBlank = false;
if (/^import\b/.test(value)) {
const importLines = [value];
while (!/(?:\bfrom\s+)?["'][^"']+["']\s*;?$/.test(importLines[importLines.length - 1]) &&
index + 1 < inputLines.length) {
index += 1;
importLines.push(inputLines[index].trim());
}
output.push(importLines[0], ...importLines.slice(1).map((line) => `${unit}${line}`));
index += 1;
continue;
}
if (isPreservedRawBlockStart(value)) {
const collected = collectPreservedRawBlock(inputLines, index);
const depth = codeDepth + htmlDepth + controlDepth;
output.push(`${unit.repeat(depth)}${collected.lines[0].trimStart()}`);
output.push(...collected.lines.slice(1));
index = collected.endIndex + 1;
continue;
}
if (isMultilineOpeningTagStart(value)) {
const collected = collectOpeningTag(inputLines, index);
value = collected.value;
index = collected.endIndex;
}
const inlineDeclaration = formatInlineDeclarationBlock(value, unit, codeDepth + htmlDepth);
const inlineProps = inlineDeclaration ?? formatInlinePropsBlock(value, unit, codeDepth + htmlDepth);
if (inlineProps) {
output.push(...inlineProps);
index += 1;
continue;
}
const leadingClosingBraces = countLeadingClosingBraces(value);
const closesControlBlock = isControlBlockClose(value);
const continuesControlBlock = isControlBlockMiddle(value);
const lineControlDepth = closesControlBlock || continuesControlBlock ? Math.max(0, controlDepth - 1) : controlDepth;
const lineCodeDepth = Math.max(0, codeDepth - leadingClosingBraces);
let lineHtmlDepth = htmlDepth;
if (isClosingTag(value)) {
lineHtmlDepth = Math.max(0, htmlDepth - 1);
}
const depth = lineCodeDepth + lineHtmlDepth + lineControlDepth;
const inlineFragmentFits = unit.repeat(depth).length + value.length <= printWidth;
if (isBalancedInlineHtmlFragment(value) && inlineFragmentFits) {
output.push(`${unit.repeat(depth)}${value}`);
}
else if (value.startsWith("<") &&
!value.startsWith("</") &&
!value.startsWith("<!--") &&
!value.startsWith("<!") &&
!value.startsWith("<?")) {
const formattedTag = formatOpeningTag(value, unit, depth, printWidth, multilineAttributes);
output.push(...formattedTag.lines);
if (formattedTag.opensElement) {
htmlDepth += 1;
}
}
else {
output.push(`${unit.repeat(depth)}${value}`);
}
if (isClosingTag(value)) {
htmlDepth = lineHtmlDepth;
}
const braces = countStructuralBraces(value);
codeDepth = Math.max(0, lineCodeDepth + braces.openings - Math.max(0, braces.closings - leadingClosingBraces));
if (isControlBlockOpen(value) || continuesControlBlock) {
controlDepth = lineControlDepth + 1;
}
else if (closesControlBlock) {
controlDepth = lineControlDepth;
}
index += 1;
}
while (output.length > 0 && output[output.length - 1] === "") {
output.pop();
}
return `${output.join("\n")}\n`;
}
/** Format to a bounded fixed point so one call is always safe for editor-on-save and migrations. */
function formatWrn(source, options = {}) {
let current = source;
const seen = new Set();
for (let pass = 0; pass < 8; pass++) {
const formatted = formatWrnPass(current, options);
if (formatted === current)
return formatted;
if (seen.has(formatted))
return [...seen, formatted].sort()[0];
seen.add(current);
current = formatted;
}
return current;
}
},
"packages/syntax/src/index.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.stripRuntimeFunctionModifiers = exports.parseStructuredImports = exports.parseStoreLifecycle = exports.parseStateDeclarations = exports.parseRuntimeFunctions = exports.parsePersist = exports.parseOutputs = exports.parseComputedDeclarations = exports.supportsSyntaxFeature = exports.diagnosticSummary = exports.sliceSource = exports.createSourceRange = exports.WRN_SYNTAX_FEATURES = exports.WRN_SYNTAX_VERSION = exports.positionAt = exports.isRuntimeTarget = exports.isHydrationStrategy = exports.formatDiagnostic = exports.diagnosticFromError = exports.diagnose = exports.containsReadonlyPropMutation = exports.classifyParseError = exports.assertValidAst = exports.validateTypedInitializer = exports.runtimeTypeOf = exports.inferredRuntimeType = exports.eraseFunctionTypes = exports.VOID_ELEMENTS = exports.ParseError = exports.parseHtmlView = exports.parse = exports.formatWrn = exports.LexError = exports.Lexer = void 0;
var tokenizer_ts_1 = require("./tokenizer.js");
Object.defineProperty(exports, "Lexer", { enumerable: true, get: function () { return tokenizer_ts_1.Lexer; } });
Object.defineProperty(exports, "LexError", { enumerable: true, get: function () { return tokenizer_ts_1.LexError; } });
var formatter_ts_1 = require("./formatter.js");
Object.defineProperty(exports, "formatWrn", { enumerable: true, get: function () { return formatter_ts_1.formatWrn; } });
var parser_ts_1 = require("./parser.js");
Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parser_ts_1.parse; } });
Object.defineProperty(exports, "parseHtmlView", { enumerable: true, get: function () { return parser_ts_1.parseHtmlView; } });
Object.defineProperty(exports, "ParseError", { enumerable: true, get: function () { return parser_ts_1.ParseError; } });
Object.defineProperty(exports, "VOID_ELEMENTS", { enumerable: true, get: function () { return parser_ts_1.VOID_ELEMENTS; } });
var types_ts_1 = require("./types.js");
Object.defineProperty(exports, "eraseFunctionTypes", { enumerable: true, get: function () { return types_ts_1.eraseFunctionTypes; } });
Object.defineProperty(exports, "inferredRuntimeType", { enumerable: true, get: function () { return types_ts_1.inferredRuntimeType; } });
Object.defineProperty(exports, "runtimeTypeOf", { enumerable: true, get: function () { return types_ts_1.runtimeTypeOf; } });
Object.defineProperty(exports, "validateTypedInitializer", { enumerable: true, get: function () { return types_ts_1.validateTypedInitializer; } });
var diagnostics_ts_1 = require("./diagnostics.js");
Object.defineProperty(exports, "assertValidAst", { enumerable: true, get: function () { return diagnostics_ts_1.assertValidAst; } });
Object.defineProperty(exports, "classifyParseError", { enumerable: true, get: function () { return diagnostics_ts_1.classifyParseError; } });
Object.defineProperty(exports, "containsReadonlyPropMutation", { enumerable: true, get: function () { return diagnostics_ts_1.containsReadonlyPropMutation; } });
Object.defineProperty(exports, "diagnose", { enumerable: true, get: function () { return diagnostics_ts_1.diagnose; } });
Object.defineProperty(exports, "diagnosticFromError", { enumerable: true, get: function () { return diagnostics_ts_1.diagnosticFromError; } });
Object.defineProperty(exports, "formatDiagnostic", { enumerable: true, get: function () { return diagnostics_ts_1.formatDiagnostic; } });
Object.defineProperty(exports, "isHydrationStrategy", { enumerable: true, get: function () { return diagnostics_ts_1.isHydrationStrategy; } });
Object.defineProperty(exports, "isRuntimeTarget", { enumerable: true, get: function () { return diagnostics_ts_1.isRuntimeTarget; } });
Object.defineProperty(exports, "positionAt", { enumerable: true, get: function () { return diagnostics_ts_1.positionAt; } });
__exportStar(require("./spec.js"), exports);
var versioning_ts_1 = require("./versioning.js");
Object.defineProperty(exports, "WRN_SYNTAX_VERSION", { enumerable: true, get: function () { return versioning_ts_1.WRN_SYNTAX_VERSION; } });
Object.defineProperty(exports, "WRN_SYNTAX_FEATURES", { enumerable: true, get: function () { return versioning_ts_1.WRN_SYNTAX_FEATURES; } });
Object.defineProperty(exports, "createSourceRange", { enumerable: true, get: function () { return versioning_ts_1.createSourceRange; } });
Object.defineProperty(exports, "sliceSource", { enumerable: true, get: function () { return versioning_ts_1.sliceSource; } });
Object.defineProperty(exports, "diagnosticSummary", { enumerable: true, get: function () { return versioning_ts_1.diagnosticSummary; } });
Object.defineProperty(exports, "supportsSyntaxFeature", { enumerable: true, get: function () { return versioning_ts_1.supportsSyntaxFeature; } });
var v060_ts_1 = require("./v060.js");
Object.defineProperty(exports, "parseComputedDeclarations", { enumerable: true, get: function () { return v060_ts_1.parseComputedDeclarations; } });
Object.defineProperty(exports, "parseOutputs", { enumerable: true, get: function () { return v060_ts_1.parseOutputs; } });
Object.defineProperty(exports, "parsePersist", { enumerable: true, get: function () { return v060_ts_1.parsePersist; } });
Object.defineProperty(exports, "parseRuntimeFunctions", { enumerable: true, get: function () { return v060_ts_1.parseRuntimeFunctions; } });
Object.defineProperty(exports, "parseStateDeclarations", { enumerable: true, get: function () { return v060_ts_1.parseStateDeclarations; } });
Object.defineProperty(exports, "parseStoreLifecycle", { enumerable: true, get: function () { return v060_ts_1.parseStoreLifecycle; } });
Object.defineProperty(exports, "parseStructuredImports", { enumerable: true, get: function () { return v060_ts_1.parseStructuredImports; } });
Object.defineProperty(exports, "stripRuntimeFunctionModifiers", { enumerable: true, get: function () { return v060_ts_1.stripRuntimeFunctionModifiers; } });
},
"packages/syntax/src/parser.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParseError = exports.VOID_ELEMENTS = void 0;
exports.parse = parse;
exports.parseHtmlView = parseHtmlView;
const spec_ts_1 = require("./spec.js");
/**
* Recursive-descent parser for `.wrn`, producing a small AST.
*
* Grammar (subset of the vision, but real):
*
* page <Name> {
* types { <TypeScript declarations> }
* props { <ident>: <type> [= <expr>] } // no default means required
* state <ident>: <type> = <expr> // type annotation is optional
* view { <html> } // plain HTML (see parseHtmlView)
* seo { title = "Home" description = "..." }
* ssr { api <name> <METHOD> <path> { <render js> } functions { <raw js> } }
* client { api <name> <METHOD> <path> { <render js> } functions { <raw js> } }
* style { <raw css> } // zero or more, inlined with the page
* functions { <raw js> } // zero or more, shared helpers
* api <METHOD> <path> { <raw js> } // zero or more
* realtime <name> { on <evt>(<args>) { <raw js> } * } // zero or more
* }
*
* The `view` block is written as ordinary HTML — nothing new to learn. Text may
* contain `{expr}` interpolation, attributes may be hyphenated (`data-*`), and
* `@event="..."` declares a client event binding. See `parseHtmlView`.
*/
const tokenizer_ts_1 = require("./tokenizer.js");
const types_ts_1 = require("./types.js");
const v060_ts_1 = require("./v060.js");
/**
* HTML void elements: they have no children and no closing tag.
* @see https://html.spec.whatwg.org/multipage/syntax.html#void-elements
*/
exports.VOID_ELEMENTS = new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr",
]);
class ParseError extends Error {
code;
offset;
constructor(message, code = "WRN-PARSE-001") {
super(message);
this.name = "ParseError";
this.code = code;
const match = /offset\s+(\d+)/i.exec(message);
this.offset = match ? Number(match[1]) : undefined;
}
}
exports.ParseError = ParseError;
function parseSeoBlock(body) {
const out = {};
const pair = /([A-Za-z][A-Za-z0-9_-]*)\s*=\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\n;]+))/g;
for (const match of body.matchAll(pair)) {
const key = match[1];
const rawValue = match[2] ?? match[3] ?? match[4] ?? "";
out[key] = unescapeSeoValue(rawValue.trim());
}
return out;
}
function unescapeSeoValue(value) {
return value.replace(/\\(["'\\nrt])/g, (_match, ch) => {
if (ch === "n")
return "\n";
if (ch === "r")
return "\r";
if (ch === "t")
return "\t";
return ch;
});
}
function parse(source) {
const lx = new tokenizer_ts_1.Lexer(source);
const imports = [];
const importPattern = /import\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?["'][^"'\r\n]+["']\s*;?/y;
while (true) {
while (/\s/u.test(source[lx.pos] ?? ""))
lx.pos++;
if (source.startsWith("//", lx.pos)) {
while (lx.pos < source.length && source[lx.pos] !== "\n")
lx.pos++;
continue;
}
importPattern.lastIndex = lx.pos;
const statement = importPattern.exec(source);
if (!statement)
break;
imports.push(statement[0].trim());
lx.pos = importPattern.lastIndex;
}
const expect = (type) => {
const t = lx.next();
if (t.type !== type) {
throw new ParseError(`Expected ${type} but got '${t.value || t.type}' at offset ${t.pos}`);
}
return t;
};
const expectKeyword = (kw) => {
const t = lx.next();
if (t.type !== "ident" || t.value !== kw) {
throw new ParseError(`Expected '${kw}' but got '${t.value || t.type}' at offset ${t.pos}`);
}
};
try {
// A file may contain a page, component, layout, global store, or page store.
const opener = lx.next();
if (opener.type !== "ident" ||
!["page", "component", "layout", "global"].includes(opener.value)) {
throw new ParseError(`Expected 'page', 'component', 'layout', 'global store', or 'page store' but got '${opener.value || opener.type}' at offset ${opener.pos}`);
}
let kind;
let storeKind;
let name;
if (opener.value === "global") {
expectKeyword("store");
kind = "global-store";
storeKind = "global";
name = expect("ident").value;
}
else if (opener.value === "page" &&
lx.peek().type === "ident" &&
lx.peek().value === "store") {
lx.next();
kind = "page-store";
storeKind = "page";
name = expect("ident").value;
}
else {
kind = opener.value;
name = expect("ident").value;
}
expect("lbrace");
let layout;
let layoutIsSymbol = false;
let runtime;
let renderMode;
let hydrate;
const props = [];
const events = [];
const outputs = [];
const types = [];
const states = [];
const computed = [];
const effects = [];
const loads = [];
const actions = [];
const security = {};
const cache = {};
const navigation = {};
const seo = {};
const view = [];
const styles = [];
const functions = [];
const runtimeFunctions = [];
const dataApis = [];
const modeFunctions = [];
const lifecycle = {};
let storeLifecycle = {};
let persist;
const watches = [];
const apis = [];
const realtimes = [];
while (lx.peek().type !== "rbrace") {
const kw = lx.peek();
if (kw.type === "eof")
throw new ParseError(`Unexpected end of input inside ${kind}`);
if (kw.type !== "ident") {
throw new ParseError(`Expected a ${kind} member keyword at offset ${kw.pos}`);
}
switch (kw.value) {
case "layout": {
// layout = "public" — selects app/layouts/<name>.wrn for this page.
lx.next();
expect("eq");
const layoutToken = lx.next();
if (layoutToken.type !== "string" && layoutToken.type !== "ident") {
throw new ParseError(`Expected a layout string or imported symbol at offset ${layoutToken.pos}`);
}
layout = layoutToken.value;
layoutIsSymbol = layoutToken.type === "ident";
break;
}
case "runtime": {
lx.next();
expect("eq");
const value = expect("string").value;
if (!spec_ts_1.WRN_RUNTIME_TARGETS.includes(value)) {
throw new ParseError(`Unknown runtime target '${value}' at offset ${kw.pos}`, "WRN-RUNTIME-TARGET");
}
runtime = value;
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;
break;
}
case "hydrate": {
lx.next();
expect("eq");
const value = expect("string").value;
hydrate = value === "never" ? "none" : value;
break;
}
case "props": {
// props { name: Type = <default>; @event name = function }
lx.next();
expect("lbrace");
while (true) {
if (lx.startsWithBlockComment()) {
throw new ParseError("Block comments are not allowed inside props {}; use // line comments instead", "WRN-PROPS-BLOCK-COMMENT");
}
if (lx.peek().type === "rbrace")
break;
const t = lx.peek();
if (t.type === "eof")
throw new ParseError("Unexpected end of input inside props");
if (t.type === "at") {
lx.next();
const declarationKind = expect("ident");
if (declarationKind.value !== "event") {
throw new ParseError(`Expected '@event' but got '@${declarationKind.value}' at offset ${declarationKind.pos}`);
}
const eventName = expect("ident").value;
expect("eq");
const marker = lx.readPropInitializer();
if (marker !== "function") {
throw new ParseError(`Event '${eventName}' must be declared as '@event ${eventName} = function'`);
}
events.push({ name: eventName });
continue;
}
if (t.type !== "ident") {
throw new ParseError(`Expected a prop name at offset ${t.pos}`);
}
const pName = expect("ident").value;
const optional = lx.peek().type === "question" ? (lx.next(), true) : false;
let valueType;
let hasDefault = false;
if (lx.peek().type === "colon") {
lx.next();
const annotation = lx.readTypeAnnotation();
valueType = annotation.type;
hasDefault = annotation.hasDefault;
}
else {
expect("eq");
hasDefault = true;
}
const defaultValue = hasDefault ? lx.readPropInitializer() : "undefined";
props.push({
name: pName,
valueType,
required: !hasDefault && !optional,
default: defaultValue,
});
}
expect("rbrace");
break;
}
case "state": {
lx.next();
if (lx.peek().type === "lbrace") {
const grouped = (0, v060_ts_1.parseStateDeclarations)(lx.readBalancedBraces(), "shared");
states.push(...grouped);
break;
}
const sName = expect("ident").value;
let valueType;
if (lx.peek().type === "colon") {
lx.next();
const annotation = lx.readTypeAnnotation();
valueType = annotation.type;
if (!annotation.hasDefault)
throw new ParseError(`State '${sName}' requires an initializer`);
}
else {
expect("eq");
}
states.push({
name: sName,
valueType,
expr: lx.readPropInitializer(),
runtime: "shared",
});
break;
}
case "computed": {
lx.next();
if (lx.peek().type === "lbrace") {
computed.push(...(0, v060_ts_1.parseComputedDeclarations)(lx.readBalancedBraces()));
}
else {
const cName = expect("ident").value;
let valueType;
if (lx.peek().type === "colon") {
lx.next();
const annotation = lx.readTypeAnnotation();
valueType = annotation.type;
if (!annotation.hasDefault)
throw new ParseError(`Computed '${cName}' requires an expression`);
}
else
expect("eq");
computed.push({ name: cName, valueType, expr: lx.readPropInitializer() });
}
break;
}
case "outputs": {
lx.next();
try {
outputs.push(...(0, v060_ts_1.parseOutputs)(lx.readBalancedBraces()));
}
catch (error) {
throw new ParseError(error instanceof Error ? error.message : String(error), "WRN-OUTPUT-DECLARATION");
}
break;
}
case "effect": {
lx.next();
effects.push({ body: lx.readBalancedBraces() });
break;
}
case "types": {
lx.next();
types.push(lx.readBalancedBraces());
break;
}
case "view": {
lx.next();
expect("lbrace");
// The view body is plain HTML. Parse it straight off the source
// (the token lexer isn't used for markup), then resume after the
// block's closing `}`.
const { nodes, endPos } = parseHtmlView(lx.src, lx.pos);
view.push(...nodes);
lx.pos = endPos;
expect("rbrace");
break;
}
case "seo": {
lx.next();
Object.assign(seo, parseSeoBlock(lx.readBalancedBraces()));
break;
}
case "security": {
lx.next();
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 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 = [];
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,
name,
...(dependsOn.length ? { dependsOn } : {}),
...(deferred ? { deferred: true } : {}),
body: lx.readBalancedBraces(),
});
break;
}
case "action": {
lx.next();
const actionName = expect("ident").value;
const args = [];
if (lx.peek().type === "lparen") {
lx.next();
while (lx.peek().type !== "rparen") {
args.push(expect("ident").value);
if (lx.peek().type === "comma")
lx.next();
}
expect("rparen");
}
let schema;
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": {
lx.next();
const method = expect("ident").value.toUpperCase();
const path = lx.readPath();
const body = lx.readBalancedBraces();
apis.push({ method, path, body });
break;
}
case "ssr":
case "client":
case "server": {
const rawMode = kw.value;
const mode = rawMode === "client" ? "client" : "ssr";
lx.next();
if ((rawMode === "client" || rawMode === "server") &&
lx.peek().type === "ident" &&
lx.peek().value === "state") {
lx.next();
if (lx.peek().type !== "lbrace")
throw new ParseError(`Expected a grouped ${rawMode} state block`);
states.push(...(0, v060_ts_1.parseStateDeclarations)(lx.readBalancedBraces(), rawMode));
break;
}
if (mode === "client" && lx.peek().type === "eq") {
lx.next();
hydrate = expect("string").value;
break;
}
expect("lbrace");
while (lx.peek().type !== "rbrace") {
const member = lx.peek();
if (member.type === "eof") {
throw new ParseError(`Unexpected end of input inside ${mode} block`);
}
if (member.type !== "ident") {
throw new ParseError(`Expected a ${mode} member keyword at offset ${member.pos}`);
}
switch (member.value) {
case "api": {
lx.next();
const name = expect("ident").value;
const method = expect("ident").value.toUpperCase();
const path = lx.readPath();
const body = lx.readBalancedBraces();
dataApis.push({ mode, name, method, path, body });
break;
}
case "functions": {
lx.next();
modeFunctions.push({ mode, body: lx.readBalancedBraces() });
break;
}
default:
throw new ParseError(`Unknown ${mode} member '${member.value}' at offset ${member.pos}`);
}
}
expect("rbrace");
break;
}
case "shared": {
lx.next();
const member = expect("ident");
if (member.value !== "state")
throw new ParseError(`Expected 'state' after shared at offset ${member.pos}`);
states.push(...(0, v060_ts_1.parseStateDeclarations)(lx.readBalancedBraces(), "shared"));
break;
}
case "realtime": {
lx.next();
const rName = expect("ident").value;
expect("lbrace");
const handlers = [];
while (lx.peek().type !== "rbrace") {
expectKeyword("on");
const event = expect("ident").value;
expect("lparen");
const args = [];
while (lx.peek().type !== "rparen") {
args.push(expect("ident").value);
if (lx.peek().type === "comma")
lx.next();
}
expect("rparen");
handlers.push({ event, args, body: lx.readBalancedBraces() });
}
expect("rbrace");
realtimes.push({ name: rName, handlers });
break;
}
case "style": {
lx.next();
styles.push(lx.readBalancedBraces());
break;
}
case "lifecycle": {
lx.next();
const body = lx.readBalancedBraces();
if (kind === "global-store" || kind === "page-store") {
storeLifecycle = (0, v060_ts_1.parseStoreLifecycle)(body);
const allowedStoreHooks = new Set(["serverInit", "clientInit", "hydrate", "dispose"]);
const hookLexer = new tokenizer_ts_1.Lexer(body);
while (hookLexer.peek().type !== "eof") {
const token = hookLexer.next();
if (token.type !== "ident") {
throw new ParseError(`Expected a lifecycle hook at offset ${token.pos}`);
}
if (!allowedStoreHooks.has(token.value)) {
throw new ParseError(`Unknown store lifecycle hook '${token.value}'`);
}
hookLexer.readBalancedBraces();
}
}
else {
const allowedComponentHooks = new Set([
"mount",
"update",
"unmount",
"clientInit",
"dispose",
]);
const hookLexer = new tokenizer_ts_1.Lexer(body);
while (hookLexer.peek().type !== "eof") {
const token = hookLexer.next();
if (token.type !== "ident") {
throw new ParseError(`Expected a lifecycle hook at offset ${token.pos}`);
}
if (!allowedComponentHooks.has(token.value)) {
throw new ParseError(`Unknown lifecycle hook '${token.value}'`);
}
const hookBody = hookLexer.readBalancedBraces();
const hook = token.value === "clientInit"
? "mount"
: token.value === "dispose"
? "unmount"
: token.value;
if (lifecycle[hook] !== undefined) {
throw new ParseError(`Duplicate lifecycle hook '${hook}'`);
}
lifecycle[hook] = hookBody;
}
}
break;
}
case "watch": {
lx.next();
const stateName = expect("ident").value;
const body = lx.readBalancedBraces();
watches.push({
state: stateName,
body,
});
break;
}
case "functions": {
lx.next();
const body = lx.readBalancedBraces();
functions.push(body);
try {
runtimeFunctions.push(...(0, v060_ts_1.parseRuntimeFunctions)(body));
}
catch (error) {
throw new ParseError(error instanceof Error ? error.message : String(error), "WRN-FUNCTION-DECLARATION");
}
break;
}
case "persist": {
lx.next();
persist = (0, v060_ts_1.parsePersist)(lx.readBalancedBraces());
break;
}
default:
throw new ParseError(`Unknown page member '${kw.value}' at offset ${kw.pos}`);
}
}
expect("rbrace");
const declaredStates = new Set(states.map((state) => state.name));
if (declaredStates.has("page")) {
throw new ParseError("State name 'page' collides with the WRN 'page' keyword; choose another state name", "WRN-STATE-RESERVED-NAME");
}
for (const watcher of watches) {
if (!declaredStates.has(watcher.state)) {
throw new ParseError(`Cannot watch undeclared state '${watcher.state}'`);
}
}
for (const prop of props) {
const problem = (0, types_ts_1.validateTypedInitializer)(`Prop '${prop.name}'`, prop.valueType, prop.default);
if (problem)
throw new ParseError(problem);
}
for (const state of states) {
const problem = (0, types_ts_1.validateTypedInitializer)(`State '${state.name}'`, state.valueType, state.expr);
if (problem)
throw new ParseError(problem);
}
const symbols = new Set();
for (const declaration of [...props, ...states, ...computed]) {
if (symbols.has(declaration.name)) {
throw new ParseError(`Duplicate symbol '${declaration.name}'`, "WRN-SYMBOL-DUPLICATE");
}
symbols.add(declaration.name);
}
const outputNames = new Set();
for (const output of outputs) {
if (outputNames.has(output.name))
throw new ParseError(`Duplicate output '${output.name}'`, "WRN-OUTPUT-DUPLICATE");
outputNames.add(output.name);
}
const functionKeys = new Set();
for (const fn of runtimeFunctions) {
const key = `${fn.runtime}:${fn.name}`;
if (functionKeys.has(key))
throw new ParseError(`Duplicate ${fn.runtime} function '${fn.name}'`, "WRN-FUNCTION-DUPLICATE");
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();
const visited = new Set();
const visitLoad = (name) => {
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,
structuredImports: (0, v060_ts_1.parseStructuredImports)(imports),
kind,
storeKind,
name,
layout,
layoutIsSymbol,
runtime,
renderMode,
hydrate,
cache,
props,
events,
outputs,
types,
states,
computed,
effects,
loads,
actions,
security,
navigation,
seo,
view,
styles,
functions,
runtimeFunctions,
dataApis,
modeFunctions,
lifecycle,
storeLifecycle,
persist,
watches,
apis,
realtimes,
};
}
catch (err) {
if (err instanceof tokenizer_ts_1.LexError)
throw new ParseError(err.message);
throw err;
}
}
/**
* Parse the body of a `view { ... }` block as plain HTML.
*
* `src` is the whole `.wrn` source; `pos` points just past the view block's
* opening `{`. Returns the parsed nodes plus the index of the block's closing
* `}` (left for the caller to consume). It is intentionally lenient — you write
* markup the way you already know:
*
* - `<tag attr="v" @event="expr">children</tag>` — elements with attributes
* - `<tag/>` and HTML void elements (`<br>`, `<img>`, …) — no closing tag
* - text may contain `{expr}` interpolation, kept verbatim for the runtime
* - `@event="..."` becomes a client event binding; hyphenated names are fine
* - `<!-- comments -->` are dropped
*
* `{` and `}` in text are reserved for interpolation; a lone `<` that isn't a
* tag is treated as literal text.
*/
function parseHtmlView(src, pos) {
let i = pos;
const isNameStart = (c) => /[A-Za-z_]/.test(c);
const isTagNamePart = (c) => /[A-Za-z0-9_$:.-]/.test(c);
const isWs = (c) => c === " " || c === "\t" || c === "\n" || c === "\r";
const fail = (msg) => {
throw new ParseError(`${msg} at offset ${i}`);
};
const skipWs = () => {
while (i < src.length && isWs(src[i]))
i++;
};
/** Read a `{...}` interpolation (brace-balanced and quote-aware), braces included. */
const readInterpolation = () => {
const start = i;
let depth = 0;
let quote = null;
for (; i < src.length; i++) {
const char = src[i];
if (quote) {
if (char === "\\" && i + 1 < src.length) {
i++;
continue;
}
if (char === quote)
quote = null;
continue;
}
if (char === '"' || char === "'" || char === "`") {
quote = char;
continue;
}
if (char === "{")
depth++;
else if (char === "}" && --depth === 0) {
i++;
return src.slice(start, i);
}
}
return fail("Unterminated `{` interpolation in view");
};
const readQuoted = () => {
const quote = src[i];
if (quote !== '"' && quote !== "'")
return fail("Expected a quoted attribute value");
i++;
const start = i;
while (i < src.length && src[i] !== quote)
i++;
if (i >= src.length)
return fail("Unterminated attribute value");
const value = src.slice(start, i);
i++; // closing quote
return value;
};
const readTagName = () => {
if (i >= src.length || !isNameStart(src[i])) {
return fail("Expected a tag name");
}
const start = i++;
while (i < src.length && isTagNamePart(src[i])) {
i++;
}
return src.slice(start, i);
};
const readAttributeName = () => {
if (i >= src.length) {
return fail("Expected an attribute name");
}
const start = i;
while (i < src.length) {
const char = src[i];
const next = src[i + 1];
if (char === "=" ||
char === ">" ||
char === '"' ||
char === "'" ||
char === " " ||
char === "\t" ||
char === "\n" ||
char === "\r" ||
(char === "/" && next === ">")) {
break;
}
i++;
}
if (i === start) {
return fail("Expected an attribute name");
}
return src.slice(start, i);
};
const parseTag = () => {
i++; // consume '<'
const tag = readTagName();
const attrs = [];
for (;;) {
skipWs();
const c = src[i];
if (c === undefined)
return fail(`Unterminated <${tag}> tag`);
if (c === ">") {
i++;
break;
}
if (c === "/" && src[i + 1] === ">") {
i += 2;
return { type: "element", tag, attrs, children: [] };
}
if (c === "@") {
i++;
const name = readAttributeName();
skipWs();
if (src[i] !== "=")
return fail(`Expected '=' after @${name}`);
i++;
skipWs();
attrs.push({ name, value: readQuoted(), event: true });
continue;
}
const name = readAttributeName();
skipWs();
if (src[i] === "=") {
i++;
skipWs();
const value = src[i] === '"' || src[i] === "'"
? readQuoted()
: src[i] === "{"
? readInterpolation()
: fail(`Expected a quoted value or {...} expression after '${name}='`);
attrs.push({ name, value, event: false });
}
else {
attrs.push({ name, value: "", event: false, boolean: true });
}
}
if (exports.VOID_ELEMENTS.has(tag.toLowerCase())) {
return { type: "element", tag, attrs, children: [] };
}
const children = parseNodeList("element");
// parseNodeList stops at the parent's closing tag `</`.
if (src[i] !== "<" || src[i + 1] !== "/")
return fail(`Expected </${tag}>`);
i += 2;
skipWs();
const close = readTagName();
if (close !== tag)
return fail(`Mismatched </${close}>, expected </${tag}>`);
skipWs();
if (src[i] !== ">")
return fail(`Expected '>' to close </${tag}>`);
i++;
return { type: "element", tag, attrs, children };
};
const EACH_HEADER = /^\{#each\s+([\s\S]+?)\s+as\s+([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*))?(?:\s+key\s+([\s\S]+?))?\s*\}$/;
/** Parse `{#each <list> as <item>[, <index>] [key <expr>]} …body… {:empty} …empty… {/each}`. */
function parseEach() {
const header = readInterpolation(); // reads the full `{#each …}`
const m = EACH_HEADER.exec(header);
if (!m)
return fail(`Invalid {#each …} header: ${header}`);
const list = m[1].trim();
const item = m[2];
const index = m[3];
const key = m[4]?.trim();
const body = parseNodeList("each"); // stops at {:empty} or {/each}
let empty = [];
if (src.startsWith("{:empty}", i)) {
i += "{:empty}".length;
empty = parseNodeList("each"); // stops at {/each}
}
if (!src.startsWith("{/each}", i))
return fail("Expected `{/each}` to close `{#each}`");
i += "{/each}".length;
return { type: "each", list, item, index, key, body, empty };
}
/** Parse `{#if <expr>} … {:else if <expr>} … {:else} … {/if}`. */
function parseIf() {
const header = readInterpolation(); // reads the full `{#if …}`
const m = /^\{#if\s+([\s\S]+?)\s*\}$/.exec(header);
if (!m)
return fail(`Invalid {#if …} header: ${header}`);
const branches = [
{ cond: m[1].trim(), body: parseNodeList("if") },
];
for (;;) {
if (src.startsWith("{:else if", i)) {
const h = readInterpolation();
const mm = /^\{:else if\s+([\s\S]+?)\s*\}$/.exec(h);
if (!mm)
return fail(`Invalid {:else if …}: ${h}`);
branches.push({ cond: mm[1].trim(), body: parseNodeList("if") });
continue;
}
if (src.startsWith("{:else}", i)) {
i += "{:else}".length;
branches.push({ cond: null, body: parseNodeList("if") });
continue;
}
break;
}
if (!src.startsWith("{/if}", i))
return fail("Expected `{/if}` to close `{#if}`");
i += "{/if}".length;
return { type: "if", branches };
}
/**
* Parse a run of nodes. `mode` sets the terminator:
* - "root": stops at the view block's closing `}`
* - "element": stops at the parent element's closing tag (`</`)
* - "each": stops (without consuming) at `{:empty}` or `{/each}`
* - "if": stops (without consuming) at `{:else …}` or `{/if}`
* `{#each …}` and `{#if …}` start nested blocks in any mode.
*/
function parseNodeList(mode) {
const nodes = [];
let text = "";
const flush = () => {
if (text.length > 0) {
nodes.push({ type: "text", value: text });
text = "";
}
};
for (;;) {
if (i >= src.length) {
return mode === "root"
? fail("Unexpected end of view (missing `}`)")
: fail("Unclosed block");
}
const c = src[i];
if (c === "<") {
const next = src[i + 1];
if (next === "/") {
flush();
break; // parent's closing tag
}
if (src.startsWith("<!--", i)) {
const end = src.indexOf("-->", i + 4);
i = end === -1 ? src.length : end + 3;
continue;
}
if (next !== undefined && (isNameStart(next) || next === "!")) {
flush();
nodes.push(parseTag());
continue;
}
// A lone `<` that doesn't start a tag: treat as literal text.
text += c;
i++;
continue;
}
if (c === "{") {
if (src.startsWith("{#each", i)) {
flush();
nodes.push(parseEach());
continue;
}
if (src.startsWith("{#if", i)) {
flush();
nodes.push(parseIf());
continue;
}
if (mode === "each" && (src.startsWith("{:empty}", i) || src.startsWith("{/each}", i))) {
flush();
break; // loop-section terminator; left for parseEach
}
if (mode === "if" && (src.startsWith("{:else", i) || src.startsWith("{/if}", i))) {
flush();
break; // conditional-section terminator; left for parseIf
}
text += readInterpolation();
continue;
}
if (c === "}" && mode === "root") {
flush();
break; // view terminator; leave `}` for the caller
}
text += c;
i++;
}
return nodes;
}
const nodes = parseNodeList("root");
return { nodes, endPos: i };
}
},
"packages/syntax/src/spec.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WRN_DIAGNOSTIC_CODES = exports.WRN_RUNTIME_TARGETS = exports.WRN_HYDRATION_STRATEGIES = exports.WRN_ROOT_MEMBERS = exports.WRN_ROOT_KINDS = exports.WRN_LANGUAGE_VERSION = void 0;
/** Canonical, machine-readable WRN language capabilities. */
exports.WRN_LANGUAGE_VERSION = "0.6";
exports.WRN_ROOT_KINDS = [
"page",
"component",
"layout",
"global-store",
"page-store",
];
exports.WRN_ROOT_MEMBERS = [
"layout",
"runtime",
"hydrate",
"client",
"types",
"props",
"outputs",
"state",
"shared",
"server",
"computed",
"effect",
"watch",
"lifecycle",
"view",
"seo",
"security",
"load",
"action",
"api",
"ssr",
"realtime",
"style",
"functions",
"persist",
];
exports.WRN_HYDRATION_STRATEGIES = ["load", "idle", "visible", "interaction", "none"];
exports.WRN_RUNTIME_TARGETS = [
"server",
"client",
"universal",
"edge",
"worker",
"service-worker",
];
exports.WRN_DIAGNOSTIC_CODES = {
parse: "WRN-PARSE-001",
root: "WRN-PARSE-ROOT",
member: "WRN-PARSE-MEMBER",
propInitializer: "WRN-PROP-INITIALIZER",
stateInitializer: "WRN-STATE-INITIALIZER",
watchUndeclared: "WRN-WATCH-UNDECLARED",
duplicateSymbol: "WRN-SYMBOL-DUPLICATE",
invalidHydration: "WRN-HYDRATE-STRATEGY",
invalidRuntime: "WRN-RUNTIME-TARGET",
serverInteractive: "WRN-RUNTIME-SERVER-INTERACTIVE",
accessibility: "WRN-A11Y-001",
import: "WRN-IMPORT-001",
function: "WRN-FUNCTION-001",
client: "WRN-CLIENT-001",
server: "WRN-SERVER-001",
output: "WRN-OUTPUT-001",
type: "WRN-TYPE-001",
state: "WRN-STATE-001",
component: "WRN-COMPONENT-001",
template: "WRN-TEMPLATE-001",
store: "WRN-STORE-001",
persist: "WRN-PERSIST-001",
rpc: "WRN-RPC-001",
hydration: "WRN-HYDRATION-001",
migration: "WRN-MIGRATION-001",
};
},
"packages/syntax/src/tokenizer.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
/**
* Lexer for the `.wrn` language.
*
* `.wrn` mixes a small structural grammar (page/state/view/api/realtime) with
* raw JavaScript bodies. A pure token stream can't represent the raw JS, so the
* lexer is driven on demand by the parser: it yields structural tokens via
* `next()`/`peek()`, and exposes `readBalancedBraces()`, `readPath()` and
* `readToLineEnd()` for the parser to grab raw spans when grammar demands it.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Lexer = exports.LexError = void 0;
class LexError extends Error {
}
exports.LexError = LexError;
const isWs = (c) => c === " " || c === "\t" || c === "\n" || c === "\r";
const isIdentStart = (c) => /[A-Za-z_]/.test(c);
const isIdentPart = (c) => /[A-Za-z0-9_]/.test(c);
class Lexer {
src;
pos = 0;
constructor(src) {
this.src = src;
}
/** Skip whitespace and `// line comments`. */
skipTrivia() {
const { src } = this;
while (this.pos < src.length) {
const c = src[this.pos];
if (isWs(c)) {
this.pos++;
continue;
}
if (c === "/" && src[this.pos + 1] === "/") {
while (this.pos < src.length && src[this.pos] !== "\n")
this.pos++;
continue;
}
break;
}
}
/** True when the next non-trivia characters open a block comment. */
startsWithBlockComment() {
this.skipTrivia();
return this.src[this.pos] === "/" && this.src[this.pos + 1] === "*";
}
/** Read and consume the next structural token. */
next() {
this.skipTrivia();
const { src } = this;
const pos = this.pos;
if (pos >= src.length)
return { type: "eof", value: "", pos };
const c = src[pos];
switch (c) {
case "{":
this.pos++;
return { type: "lbrace", value: c, pos };
case "}":
this.pos++;
return { type: "rbrace", value: c, pos };
case "(":
this.pos++;
return { type: "lparen", value: c, pos };
case ")":
this.pos++;
return { type: "rparen", value: c, pos };
case "@":
this.pos++;
return { type: "at", value: c, pos };
case "=":
this.pos++;
return { type: "eq", value: c, pos };
case ":":
this.pos++;
return { type: "colon", value: c, pos };
case ",":
this.pos++;
return { type: "comma", value: c, pos };
case "?":
this.pos++;
return { type: "question", value: c, pos };
case '"':
case "'":
return this.readString(c, pos);
}
if (isIdentStart(c)) {
let v = "";
while (this.pos < src.length && isIdentPart(src[this.pos]))
v += src[this.pos++];
return { type: "ident", value: v, pos };
}
throw new LexError(`Unexpected character '${c}' at offset ${pos} (line ${this.lineAt(pos)})`);
}
/** Look at the next token without consuming it. */
peek() {
const save = this.pos;
const t = this.next();
this.pos = save;
return t;
}
readString(quote, pos) {
const { src } = this;
let v = "";
this.pos++; // opening quote
while (this.pos < src.length) {
const c = src[this.pos++];
if (c === "\\") {
const n = src[this.pos++];
v += n === "n" ? "\n" : n === "t" ? "\t" : n;
continue;
}
if (c === quote)
return { type: "string", value: v, pos };
v += c;
}
throw new LexError(`Unterminated string at offset ${pos}`);
}
/** Read a route path like `/users/[id]` up to whitespace or `{`. */
readPath() {
this.skipTrivia();
const { src } = this;
let v = "";
while (this.pos < src.length && !isWs(src[this.pos]) && src[this.pos] !== "{") {
v += src[this.pos++];
}
if (!v)
throw new LexError(`Expected a path at offset ${this.pos}`);
return v;
}
/**
* Read a prop default initializer. The initializer may contain nested arrays,
* objects, calls, strings, or template literals. At top level it ends at a
* newline, the closing brace of the props block, or the next inline prop
* declaration (`name = ...` / `name: Type = ...`).
*/
readPropInitializer() {
const { src } = this;
while (this.pos < src.length && (src[this.pos] === " " || src[this.pos] === "\t")) {
this.pos++;
}
const start = this.pos;
let square = 0;
let brace = 0;
let paren = 0;
let angle = 0;
let quote = null;
const atTopLevel = () => square === 0 && brace === 0 && paren === 0 && angle === 0;
while (this.pos < src.length) {
const c = src[this.pos];
if (quote) {
this.pos++;
if (c === "\\" && this.pos < src.length) {
this.pos++;
}
else if (c === quote) {
quote = null;
}
continue;
}
if (c === '"' || c === "'" || c === "`") {
quote = c;
this.pos++;
continue;
}
if (atTopLevel()) {
if (c === "\n" || c === "\r" || c === "}")
break;
if (c === " " || c === "\t") {
let look = this.pos;
while (look < src.length && (src[look] === " " || src[look] === "\t"))
look++;
const rest = src.slice(look);
if (/^[A-Za-z_][A-Za-z0-9_]*(?:\s*:[^=\r\n{}]+)?\s*=/.test(rest))
break;
}
}
if (c === "[")
square++;
else if (c === "]" && square > 0)
square--;
else if (c === "{")
brace++;
else if (c === "}" && brace > 0)
brace--;
else if (c === "(")
paren++;
else if (c === ")" && paren > 0)
paren--;
else if (c === "<")
angle++;
else if (c === ">" && angle > 0)
angle--;
this.pos++;
}
const value = src.slice(start, this.pos).trim();
if (!value)
throw new LexError(`Expected a prop initializer at offset ${start}`);
return value;
}
/** Read the rest of the current line (used for `state x = <expr>`). */
readToLineEnd() {
const { src } = this;
let v = "";
while (this.pos < src.length && src[this.pos] !== "\n")
v += src[this.pos++];
return v.trim();
}
/**
* Read a TypeScript-style type annotation after `:`. Reading stops at a
* top-level `=` or line ending, while nested object/tuple/generic syntax is
* preserved. The optional `=` is consumed for the caller.
*/
readTypeAnnotation() {
const { src } = this;
let value = "";
let angle = 0;
let square = 0;
let brace = 0;
let paren = 0;
let quote = null;
while (this.pos < src.length) {
const c = src[this.pos];
if (quote) {
value += c;
this.pos++;
if (c === "\\" && this.pos < src.length)
value += src[this.pos++];
else if (c === quote)
quote = null;
continue;
}
if (c === '"' || c === "'" || c === "`") {
quote = c;
value += c;
this.pos++;
continue;
}
if (c === "}" && angle === 0 && square === 0 && brace === 0 && paren === 0)
break;
if (c === "<")
angle++;
else if (c === ">" && angle > 0)
angle--;
else if (c === "[")
square++;
else if (c === "]" && square > 0)
square--;
else if (c === "{")
brace++;
else if (c === "}" && brace > 0)
brace--;
else if (c === "(")
paren++;
else if (c === ")" && paren > 0)
paren--;
if (angle === 0 && square === 0 && brace === 0 && paren === 0) {
if (c === " " || c === "\t") {
let look = this.pos;
while (look < src.length && (src[look] === " " || src[look] === "\t"))
look++;
if (/^[A-Za-z_][A-Za-z0-9_]*\??\s*:/.test(src.slice(look)))
break;
}
if (c === "=") {
this.pos++;
const type = value.trim();
if (!type)
throw new LexError(`Expected a type annotation at offset ${this.pos}`);
return { type, hasDefault: true };
}
if (c === "\n" || c === "\r")
break;
}
value += c;
this.pos++;
}
const type = value.trim();
if (!type)
throw new LexError(`Expected a type annotation at offset ${this.pos}`);
return { type, hasDefault: false };
}
/**
* Read a `{ ... }` block and return its INNER text (no outer braces), with
* brace counting that respects string and template literals so a `}` inside a
* string doesn't end the block early.
*
* Comments are skipped as well. Without that, an apostrophe in ordinary
* prose — `/* the panel's color *\/`, `// the Input's slot` — opened a
* string that ran to the next apostrophe, swallowing every brace in between
* and failing the whole component with "Unbalanced braces" pointing at the
* block's opening line. Comments are where apostrophes actually occur, so
* that error was almost always a false alarm.
*
* A `//` line comment is only recognised at the start of a line (after
* whitespace), which is where every comment in a `.wrn` file is written.
* Recognising it mid-line would break the far more common case of a bare
* URL in view text, where `https://…` is not inside quotes.
*/
readBalancedBraces() {
this.skipTrivia();
const { src } = this;
if (src[this.pos] !== "{") {
throw new LexError(`Expected '{' at offset ${this.pos}`);
}
const start = this.pos + 1;
let depth = 0;
let i = this.pos;
let str = null;
/** True while only whitespace has been seen since the last newline. */
let atLineStart = false;
for (; i < src.length; i++) {
const c = src[i];
if (str) {
if (c === "\\") {
i++;
continue;
}
if (c === str)
str = null;
continue;
}
if (c === "\n") {
atLineStart = true;
continue;
}
if (c === "/" && src[i + 1] === "*") {
const close = src.indexOf("*/", i + 2);
if (close === -1)
break; // unterminated: fall through to the error
i = close + 1;
atLineStart = false;
continue;
}
if (atLineStart && c === "/" && src[i + 1] === "/") {
const newline = src.indexOf("\n", i + 2);
if (newline === -1)
break;
i = newline - 1; // let the loop's own increment land on the newline
continue;
}
if (c !== " " && c !== "\t" && c !== "\r")
atLineStart = false;
if (c === '"' || c === "'" || c === "`") {
str = c;
continue;
}
if (c === "{")
depth++;
else if (c === "}") {
depth--;
if (depth === 0) {
this.pos = i + 1;
return src.slice(start, i);
}
}
}
throw new LexError(`Unbalanced braces starting at offset ${this.pos}`);
}
lineAt(pos) {
let line = 1;
for (let i = 0; i < pos && i < this.src.length; i++) {
if (this.src[i] === "\n")
line++;
}
return line;
}
}
exports.Lexer = Lexer;
},
"packages/syntax/src/types.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
/** Utilities shared by typed `.wrn` parsing, validation, and code generation. */
Object.defineProperty(exports, "__esModule", { value: true });
exports.runtimeTypeOf = runtimeTypeOf;
exports.inferredRuntimeType = inferredRuntimeType;
exports.validateTypedInitializer = validateTypedInitializer;
exports.eraseFunctionTypes = eraseFunctionTypes;
function runtimeTypeOf(annotation) {
if (!annotation)
return "unknown";
const type = annotation.trim().replace(/^readonly\s+/, "");
const unionParts = type.split("|").map((part) => part.trim());
const concreteParts = unionParts.filter((part) => !/^(?:null|undefined)$/.test(part));
if (/^(?:string|String)(?:\s*\|\s*(?:null|undefined))*$/.test(type) ||
(concreteParts.length > 0 && concreteParts.every((part) => /^(?:"[^"]*"|'[^']*')$/.test(part))))
return "string";
if (/^(?:number|Number)(?:\s*\|\s*(?:null|undefined))*$/.test(type) ||
(concreteParts.length > 0 &&
concreteParts.every((part) => /^-?(?:\d+\.?\d*|\.\d+)$/.test(part))))
return "number";
if (/^(?:boolean|Boolean)(?:\s*\|\s*(?:null|undefined))*$/.test(type) ||
(concreteParts.length > 0 && concreteParts.every((part) => /^(?:true|false)$/.test(part))))
return "boolean";
if (/^bigint(?:\s*\|\s*(?:null|undefined))*$/.test(type))
return "bigint";
if (/^(?:Array\s*<|ReadonlyArray\s*<|.+\[\])/.test(type) || /^\[/.test(type))
return "array";
if (/^(?:Record\s*<|object\b|\{)/.test(type))
return "object";
if (/=>|^(?:Function|\([^)]*\)\s*=>)/.test(type))
return "function";
return "unknown";
}
function inferredRuntimeType(expression) {
const value = expression.trim();
if (/^["'`]/.test(value))
return "string";
if (/^-?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(value))
return "number";
if (/^(?:true|false)$/.test(value))
return "boolean";
if (/^-?\d+n$/.test(value))
return "bigint";
if (value.startsWith("["))
return "array";
if (value.startsWith("{") || /^new\s+(?:Map|Set|Date)\b/.test(value))
return "object";
if (/^(?:async\s+)?(?:function\b|\([^)]*\)\s*=>|[A-Za-z_$][\w$]*\s*=>)/.test(value)) {
return "function";
}
return "unknown";
}
function validateTypedInitializer(name, annotation, expression) {
if (!annotation || expression.trim() === "undefined" || expression.trim() === "null")
return null;
const expected = runtimeTypeOf(annotation);
const actual = inferredRuntimeType(expression);
if (expected === "unknown" || actual === "unknown" || expected === actual)
return null;
return `${name} is declared as ${annotation}, but its initializer is ${actual}`;
}
/**
* Browser behavior is evaluated as JavaScript, so erase TypeScript annotations
* from ordinary function declarations before serializing it into HTML.
* Server output retains the original typed source.
*/
function eraseFunctionTypes(source) {
return source
.replace(/\bcatch\s*\(\s*([A-Za-z_$][\w$]*)\s*:\s*(?:any|unknown)\s*\)/g, "catch ($1)")
.replace(/(\b(?:async\s+)?function\s+[A-Za-z_$][\w$]*\s*\()([^)]*)(\)\s*)(?::\s*([^{}=>]+)\s*)?(\{)/g, (_whole, open, params, close, _returnType, brace) => {
const plainParams = params
.split(",")
.map((param) => param.replace(/([A-Za-z_$][\w$]*)(\?)?\s*:\s*([^=]+?)(?=\s*=|$)/, "$1").trim())
.join(", ");
return `${open}${plainParams}${close}${brace}`;
});
}
},
"packages/syntax/src/v060.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseRuntimeFunctions = parseRuntimeFunctions;
exports.stripRuntimeFunctionModifiers = stripRuntimeFunctionModifiers;
exports.parseOutputs = parseOutputs;
exports.parseStructuredImports = parseStructuredImports;
exports.parseStateDeclarations = parseStateDeclarations;
exports.parseComputedDeclarations = parseComputedDeclarations;
exports.parsePersist = parsePersist;
exports.parseStoreLifecycle = parseStoreLifecycle;
const tokenizer_ts_1 = require("./tokenizer.js");
function splitTopLevel(input, separator = ",") {
const parts = [];
let start = 0;
let quote = null;
let angle = 0;
let square = 0;
let brace = 0;
let paren = 0;
for (let i = 0; i < input.length; i++) {
const c = input[i];
if (quote) {
if (c === "\\")
i++;
else if (c === quote)
quote = null;
continue;
}
if (c === '"' || c === "'" || c === "`") {
quote = c;
continue;
}
if (c === "<")
angle++;
else if (c === ">" && angle > 0)
angle--;
else if (c === "[")
square++;
else if (c === "]" && square > 0)
square--;
else if (c === "{")
brace++;
else if (c === "}" && brace > 0)
brace--;
else if (c === "(")
paren++;
else if (c === ")" && paren > 0)
paren--;
else if (c === separator && angle === 0 && square === 0 && brace === 0 && paren === 0) {
parts.push(input.slice(start, i).trim());
start = i + 1;
}
}
const tail = input.slice(start).trim();
if (tail)
parts.push(tail);
return parts;
}
function findTopLevelChar(input, wanted) {
let quote = null;
let angle = 0;
let square = 0;
let brace = 0;
let paren = 0;
for (let i = 0; i < input.length; i++) {
const c = input[i];
if (quote) {
if (c === "\\")
i++;
else if (c === quote)
quote = null;
continue;
}
if (c === '"' || c === "'" || c === "`") {
quote = c;
continue;
}
if (c === "<")
angle++;
else if (c === ">" && angle > 0)
angle--;
else if (c === "[")
square++;
else if (c === "]" && square > 0)
square--;
else if (c === "{")
brace++;
else if (c === "}" && brace > 0)
brace--;
else if (c === "(")
paren++;
else if (c === ")" && paren > 0)
paren--;
if (c === wanted && angle === 0 && square === 0 && brace === 0 && paren === 0)
return i;
}
return -1;
}
function parseParameters(source) {
return splitTopLevel(source)
.filter(Boolean)
.map((entry) => {
const eq = findTopLevelChar(entry, "=");
const declaration = (eq >= 0 ? entry.slice(0, eq) : entry).trim();
const defaultValue = eq >= 0 ? entry.slice(eq + 1).trim() : undefined;
const colon = findTopLevelChar(declaration, ":");
const rawName = (colon >= 0 ? declaration.slice(0, colon) : declaration).trim();
const optional = rawName.endsWith("?");
const name = optional ? rawName.slice(0, -1).trim() : rawName;
const valueType = colon >= 0 ? declaration.slice(colon + 1).trim() : undefined;
return {
name,
optional,
...(valueType ? { valueType } : {}),
...(defaultValue ? { default: defaultValue } : {}),
};
});
}
function skipTrivia(source, start) {
let i = start;
while (i < source.length) {
if (/\s/.test(source[i])) {
i++;
continue;
}
if (source.startsWith("//", i)) {
const end = source.indexOf("\n", i + 2);
i = end < 0 ? source.length : end + 1;
continue;
}
if (source.startsWith("/*", i)) {
const end = source.indexOf("*/", i + 2);
i = end < 0 ? source.length : end + 2;
continue;
}
break;
}
return i;
}
function readWord(source, start) {
const match = /^[A-Za-z_$][\w$]*/.exec(source.slice(start));
return match ? { word: match[0], end: start + match[0].length } : null;
}
function readBalanced(source, start, open, close) {
if (source[start] !== open)
throw new Error(`Expected '${open}' at offset ${start}`);
let depth = 0;
let quote = null;
for (let i = start; i < source.length; i++) {
const c = source[i];
if (quote) {
if (c === "\\")
i++;
else if (c === quote)
quote = null;
continue;
}
if (c === '"' || c === "'" || c === "`") {
quote = c;
continue;
}
if (c === open)
depth++;
else if (c === close && --depth === 0)
return { inner: source.slice(start + 1, i), end: i + 1 };
}
throw new Error(`Unbalanced '${open}${close}' starting at offset ${start}`);
}
function parseRuntimeFunctions(source) {
const declarations = [];
let i = 0;
while (i < source.length) {
i = skipTrivia(source, i);
const start = i;
let token = readWord(source, i);
if (!token) {
i++;
continue;
}
let runtime = "legacy";
if (["client", "server", "shared"].includes(token.word)) {
runtime = token.word;
i = skipTrivia(source, token.end);
token = readWord(source, i);
if (!token)
continue;
}
let isAsync = false;
if (token.word === "async") {
isAsync = true;
i = skipTrivia(source, token.end);
token = readWord(source, i);
if (!token)
continue;
}
if (token.word !== "function") {
i = token.end;
continue;
}
i = skipTrivia(source, token.end);
const nameToken = readWord(source, i);
if (!nameToken)
throw new Error(`Expected function name at offset ${i}`);
const name = nameToken.word;
i = skipTrivia(source, nameToken.end);
const params = readBalanced(source, i, "(", ")");
i = skipTrivia(source, params.end);
let returnType;
if (source[i] === ":") {
i++;
const typeStart = i;
let quote = null;
let angle = 0;
let square = 0;
let paren = 0;
while (i < source.length) {
const c = source[i];
if (quote) {
if (c === "\\")
i++;
else if (c === quote)
quote = null;
i++;
continue;
}
if (c === '"' || c === "'" || c === "`")
quote = c;
else if (c === "<")
angle++;
else if (c === ">" && angle > 0)
angle--;
else if (c === "[")
square++;
else if (c === "]" && square > 0)
square--;
else if (c === "(")
paren++;
else if (c === ")" && paren > 0)
paren--;
else if (c === "{" && angle === 0 && square === 0 && paren === 0)
break;
i++;
}
returnType = source.slice(typeStart, i).trim();
}
i = skipTrivia(source, i);
const body = readBalanced(source, i, "{", "}");
i = body.end;
declarations.push({
name,
runtime,
async: isAsync,
parameters: parseParameters(params.inner),
...(returnType ? { returnType } : {}),
body: body.inner,
source: source.slice(start, body.end).trim(),
});
}
return declarations;
}
function stripRuntimeFunctionModifiers(source, include) {
const allowed = new Set(include);
return parseRuntimeFunctions(source)
.filter((entry) => allowed.has(entry.runtime))
.map((entry) => {
const params = entry.parameters
.map((param) => `${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : ""}${param.default ? ` = ${param.default}` : ""}`)
.join(", ");
return `${entry.async ? "async " : ""}function ${entry.name}(${params})${entry.returnType ? `: ${entry.returnType}` : ""} {${entry.body}}`;
})
.join("\n\n");
}
function parseOutputs(source) {
const out = [];
let i = 0;
while (i < source.length) {
i = skipTrivia(source, i);
if (i >= source.length)
break;
const nameToken = readWord(source, i);
if (!nameToken)
throw new Error(`Expected output name at offset ${i}`);
i = skipTrivia(source, nameToken.end);
const args = readBalanced(source, i, "(", ")");
i = args.end;
const parameters = parseParameters(args.inner);
if (parameters.length > 1)
throw new Error(`Output '${nameToken.word}' accepts zero or one payload`);
const payload = parameters[0];
if (payload && !payload.valueType)
throw new Error(`Output '${nameToken.word}' payload requires a type`);
out.push({
name: nameToken.word,
...(payload
? {
payload: {
name: payload.name,
valueType: payload.valueType,
optional: payload.optional,
},
}
: {}),
});
}
return out;
}
function parseStructuredImports(imports) {
return imports.map((raw) => {
const sourceMatch = /\sfrom\s+["']([^"']+)["']|^import\s+["']([^"']+)["']/.exec(raw);
const source = sourceMatch?.[1] ?? sourceMatch?.[2] ?? "";
const typeOnly = /^import\s+type\b/.test(raw);
const clause = raw
.replace(/^import\s+(?:type\s+)?/, "")
.replace(/\s+from\s+["'][^"']+["']\s*;?$/, "")
.trim();
const declaration = { source, typeOnly, namedImports: [], raw };
if (!clause || clause.startsWith('"') || clause.startsWith("'"))
return declaration;
if (clause.startsWith("*")) {
declaration.namespaceImport = /\*\s+as\s+([A-Za-z_$][\w$]*)/.exec(clause)?.[1];
return declaration;
}
let rest = clause;
if (!rest.startsWith("{")) {
const comma = findTopLevelChar(rest, ",");
declaration.defaultImport = (comma < 0 ? rest : rest.slice(0, comma)).trim();
rest = comma < 0 ? "" : rest.slice(comma + 1).trim();
}
const named = /^\{([\s\S]*)\}$/.exec(rest)?.[1];
if (named !== undefined) {
declaration.namedImports = splitTopLevel(named).map((item) => {
const localTypeOnly = /^type\s+/.test(item);
const cleaned = item.replace(/^type\s+/, "").trim();
const [imported, local] = cleaned.split(/\s+as\s+/);
return {
imported: imported.trim(),
local: (local ?? imported).trim(),
typeOnly: typeOnly || localTypeOnly,
};
});
}
return declaration;
});
}
function parseStateDeclarations(source, runtime) {
const lx = new tokenizer_ts_1.Lexer(source);
const out = [];
while (lx.peek().type !== "eof") {
const nameToken = lx.next();
if (nameToken.type !== "ident")
throw new tokenizer_ts_1.LexError(`Expected a state name at offset ${nameToken.pos}`);
let valueType;
if (lx.peek().type === "colon") {
lx.next();
const annotation = lx.readTypeAnnotation();
valueType = annotation.type;
if (!annotation.hasDefault)
throw new tokenizer_ts_1.LexError(`State '${nameToken.value}' requires an initializer`);
}
else {
const eq = lx.next();
if (eq.type !== "eq")
throw new tokenizer_ts_1.LexError(`Expected '=' after state '${nameToken.value}' at offset ${eq.pos}`);
}
out.push({
name: nameToken.value,
...(valueType ? { valueType } : {}),
expr: lx.readPropInitializer(),
runtime,
});
}
return out;
}
function parseComputedDeclarations(source) {
const lx = new tokenizer_ts_1.Lexer(source);
const out = [];
while (lx.peek().type !== "eof") {
const nameToken = lx.next();
if (nameToken.type !== "ident")
throw new tokenizer_ts_1.LexError(`Expected a computed name at offset ${nameToken.pos}`);
let valueType;
if (lx.peek().type === "colon") {
lx.next();
const annotation = lx.readTypeAnnotation();
valueType = annotation.type;
if (!annotation.hasDefault)
throw new tokenizer_ts_1.LexError(`Computed '${nameToken.value}' requires an expression`);
}
else {
const eq = lx.next();
if (eq.type !== "eq")
throw new tokenizer_ts_1.LexError(`Expected '=' after computed '${nameToken.value}' at offset ${eq.pos}`);
}
out.push({
name: nameToken.value,
...(valueType ? { valueType } : {}),
expr: lx.readPropInitializer(),
});
}
return out;
}
function nestedBlock(source, name) {
const match = new RegExp(`\\b${name}\\s*\\{`).exec(source);
if (!match)
return undefined;
const brace = source.indexOf("{", match.index);
return readBalanced(source, brace, "{", "}").inner.trim() || undefined;
}
function parsePersist(source) {
const storage = /\bstorage\s*=\s*["'](memory|session|local)["']/.exec(source)?.[1];
const includeRaw = /\binclude\s*=\s*\[([\s\S]*?)\]/.exec(source)?.[1] ?? "";
const include = Array.from(includeRaw.matchAll(/["']([^"']+)["']/g), (match) => match[1]);
const version = Number(/\bversion\s*=\s*(\d+)/.exec(source)?.[1] ?? "1");
const migrations = nestedBlock(source, "migrations");
const validation = nestedBlock(source, "validate");
return {
storage: storage ?? "memory",
include,
version,
...(migrations ? { migrations } : {}),
...(validation ? { validation } : {}),
};
}
function parseStoreLifecycle(source) {
const out = {};
for (const hook of ["serverInit", "clientInit", "hydrate", "dispose"]) {
const start = new RegExp(`\\b${hook}\\s*\\{`).exec(source);
if (!start)
continue;
const brace = source.indexOf("{", start.index);
out[hook] = readBalanced(source, brace, "{", "}").inner;
}
return out;
}
},
"packages/syntax/src/versioning.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WRN_SYNTAX_FEATURES = exports.WRN_SYNTAX_VERSION = void 0;
exports.createSourceRange = createSourceRange;
exports.sliceSource = sliceSource;
exports.diagnosticSummary = diagnosticSummary;
exports.supportsSyntaxFeature = supportsSyntaxFeature;
/** Current stable syntax contract. Bump only when parsers/codegen need migration. */
exports.WRN_SYNTAX_VERSION = "0.4";
exports.WRN_SYNTAX_FEATURES = Object.freeze({
"typed-declarations": true,
layouts: true,
"server-client-blocks": true,
effects: true,
watch: true,
lifecycle: true,
"embedded-api": true,
realtime: true,
"runtime-markers": true,
});
function createSourceRange(start, end) {
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) {
throw new RangeError(`Invalid source range ${start}..${end}`);
}
return { start, end };
}
function sliceSource(source, range) {
return source.slice(range.start, range.end);
}
function diagnosticSummary(diagnostics) {
const summary = { errors: 0, warnings: 0, info: 0, codes: {} };
for (const diagnostic of diagnostics) {
if (diagnostic.severity === "error")
summary.errors++;
else if (diagnostic.severity === "warning")
summary.warnings++;
else
summary.info++;
summary.codes[diagnostic.code] = (summary.codes[diagnostic.code] ?? 0) + 1;
}
return summary;
}
function supportsSyntaxFeature(feature) {
return Object.prototype.hasOwnProperty.call(exports.WRN_SYNTAX_FEATURES, feature);
}
}
};
const __aliases = {
"@wrnexus/syntax": "packages/syntax/src/index.ts",
"@wrnexus/syntax/parser": "packages/syntax/src/parser.ts",
"@wrnexus/syntax/tokenizer": "packages/syntax/src/tokenizer.ts",
"@wrnexus/syntax/types": "packages/syntax/src/types.ts",
"@wrnexus/syntax/diagnostics": "packages/syntax/src/diagnostics.ts",
"@wrnexus/syntax/spec": "packages/syntax/src/spec.ts"
};
const __cache = Object.create(null);
function __normalize(id) {
const normalized = id.split("\\").join("/");
return normalized.startsWith("./") ? normalized.slice(2) : normalized;
}
function __resolve(request, parent) {
if (__aliases[request]) return __aliases[request];
if (!request.startsWith(".")) return null;
const base = __normalize(__path.posix.join(__path.posix.dirname(parent), request));
const candidates = [
base,
base.endsWith(".js") ? base.slice(0, -3) + ".ts" : base,
base.endsWith(".ts") ? base : base + ".ts",
(base.endsWith("/") ? base.slice(0, -1) : base) + "/index.ts"
];
for (const candidate of candidates) {
if (__modules[candidate]) return candidate;
}
return null;
}
function __load(id) {
if (__cache[id]) return __cache[id].exports;
const factory = __modules[id];
if (!factory) throw new Error("WRN editor compiler module not found: " + id);
const module = { exports: {} };
__cache[id] = module;
const localRequire = (request) => {
const resolved = __resolve(request, id);
return resolved ? __load(resolved) : __nodeRequire(request);
};
factory(module, module.exports, localRequire, id, __path.posix.dirname(id));
return module.exports;
}
module.exports = __load("packages/compiler/src/index.ts");