"use strict"; // Generated by scripts/build-editor-compiler.mjs. Do not edit directly. // WRN editor compiler source hash: 653a88ea2a34e9c446fc55b268f3ba9c5d27f9e114fc6aa9228ef560e1fd616d // WRN editor compiler generator hash: a54ca847c758bc98d8e353ad6d70088df31de1820f6cf9d1c3462505f563e6b8 // 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/browser-transpile.ts": function (module, exports, require, __filename, __dirname) { "use strict"; /** * Strip TypeScript from a generated browser module. * * A client function's body is emitted verbatim, so anything TypeScript-only * inside one -- an annotated local, an `as` cast, a local interface -- reaches * the browser module as TypeScript source. Codegen removes the types from the * function's *signature*, which is what made this easy to miss: the emitted * module looked transpiled, and only bodies carried types through. * * The artifact is written as `.mjs` and read back as plain JavaScript, so the * failure surfaced as a syntax error pointing at generated code rather than at * the `.wrn` line responsible. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.stripBrowserTypes = stripBrowserTypes; let transpiler = null; function stripBrowserTypes(code) { const bun = globalThis.Bun; if (!bun?.Transpiler) { throw new Error("WRN-CLIENT-TS: emitting a browser module needs the Bun transpiler to remove TypeScript from client function bodies."); } transpiler ??= new bun.Transpiler({ loader: "ts", target: "browser" }); return transpiler.transformSync(code); } }, "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 = "", 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 = "", 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", "api", "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) => ["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();" : ""} } }`; } /** * Blank out string/template literals and comments in a raw JS body, preserving * length and newlines, so a scanner walking the result never mistakes text * inside a string or comment for real code. Reuses the tokenizer's * comment/string-skipping rules (`skipLiteralOrComment`) instead of * reimplementing them — a second hand-rolled scanner is how apostrophes in * prose used to swallow braces elsewhere in this codebase. */ function maskStringsAndComments(src) { let out = ""; let i = 0; let atLineStart = true; while (i < src.length) { const c = src[i]; if (c === "\n") { out += c; atLineStart = true; i++; continue; } const skipped = (0, syntax_1.skipLiteralOrComment)(src, i, atLineStart); if (skipped !== null) { out += src.slice(i, skipped).replace(/[^\n]/g, " "); i = skipped; atLineStart = false; continue; } if (c !== " " && c !== "\t" && c !== "\r") atLineStart = false; out += c; i++; } return out; } /** * Block names the page's client functions actually call. * * A block's response and error bodies are page code. Emitting one the browser * never calls would ship a server-only transform to every visitor and grow the * bundle for nothing. */ function clientCalledApiNames(ast) { const called = new Set(); for (const fn of ast.runtimeFunctions.filter((fn) => ["client", "shared"].includes(fn.runtime))) { const masked = maskStringsAndComments(fn.body); for (const match of masked.matchAll(/\bapi\s*\.\s*([A-Za-z_$][A-Za-z0-9_$]*)/g)) { called.add(match[1]); } } return called; } /** * Refuse to compile a client/shared function whose body references `api` in * any form other than `api.` — e.g. `api["searchUsers"]()`, or * passing `api` to a helper. Usage-driven emission (see `clientCalledApiNames` * above) can only see `api.` calls; a dynamic or indirect * reference is invisible to it, so the referenced block would be silently * dropped from the browser bundle and the call would fail at runtime with * "api. is not a function". That failure direction is worse than a * loud compile error, so it is caught here instead. * * Strings and comments are masked out first so `api` appearing in prose or in * a quoted value never trips this check, and every `api.` access * is stripped before the standalone-word scan so a real, well-formed call * never does either. */ function assertNoDynamicApiAccess(ast) { // Only pages with a sectioned api block have anything at stake here: those // blocks are emitted solely because usage detection saw `api.`, so a // dynamic reference this scan can't see is the one that silently drops a // block from the bundle. A page with no api blocks at all may still declare // an ordinary `state api` (see the B5 regression test) where a bare "api" // identifier is just that state, not a missed block reference. if (!ast.dataApis.some((block) => block.sections)) return; for (const fn of ast.runtimeFunctions.filter((fn) => ["client", "shared"].includes(fn.runtime))) { const masked = maskStringsAndComments(fn.body); const withoutCalls = masked.replace(/\bapi\s*\.\s*[A-Za-z_$][A-Za-z0-9_$]*/g, (match) => match.replace(/[^\n]/g, " ")); if (/\bapi\b/.test(withoutCalls)) { throw new Error(`.wrn ${fn.runtime} function "${fn.name}" in page "${ast.name}" references "api" in a form other than "api.(...)". ` + `API blocks must be called as api.name(...) so the compiler can tell which ones the browser needs to receive; ` + `dynamic or indirect access (e.g. api["name"](), or passing api to a helper) cannot be detected and would silently drop the block from the browser bundle.`); } } } /** * A block is emitted into the browser module when it declares typed sections * and a client function actually calls it. `hasClientApi` below must use this * exact predicate so the `api` reserved-binding exclusion and the emitted * object can never disagree. */ function isClientEmittedApiBlock(block, called) { return Boolean(block.sections) && called.has(block.name); } /** * Client-mode and client-called any-mode api blocks become members of an * `api` object in client scope. * * Only the response and error bodies are emitted; the declared field types are * type-only and are consumed by the types generator instead. Anything * TypeScript reaching this module would be a syntax error in the .mjs artifact. */ function apiBindings(ast) { const called = clientCalledApiNames(ast); const members = ast.dataApis .filter((block) => isClientEmittedApiBlock(block, called)) .map((block) => { const sections = block.sections; const response = (0, syntax_1.eraseFunctionTypes)(sections.response).trim() || "return data;"; const error = (0, syntax_1.eraseFunctionTypes)(sections.error).trim(); const failure = error ? `(error) => { const status = error.status; const message = error.message; const data = error.data; ${error} }` : `(error) => { throw error; }`; return ` ${JSON.stringify(block.name)}: async (input) => context.callApi(${JSON.stringify(block.path)}, ${JSON.stringify(block.method)}, input).then((data) => { ${response} }, ${failure})`; }); return members.length ? `const api = {\n${members.join(",\n")}\n };` : ""; } function generateBrowserModule(ast) { assertNoDynamicApiAccess(ast); const functions = ast.runtimeFunctions.filter((fn) => ["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))]; // `api` is only defined as a client-scope binding when the page actually has // client-mode api blocks (see apiBindings below). A page that declares // `state api` without any client api blocks must keep reading/writing that // state as before, so only exclude the "api" name from destructuring when // there is a real `api` binding to shadow it. const hasClientApi = ast.dataApis.some((block) => isClientEmittedApiBlock(block, clientCalledApiNames(ast))); const localRuntimeBindings = hasClientApi ? RUNTIME_BINDINGS : new Set([...RUNTIME_BINDINGS].filter((name) => name !== "api")); const sharedState = state.filter((name) => safeIdentifier(name) && !localRuntimeBindings.has(name)); const sharedProps = ast.props .map((entry) => entry.name) .filter((name) => safeIdentifier(name) && !localRuntimeBindings.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; ${apiBindings(ast)} 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-="..." * "...{expr}..." -> text kept verbatim ({expr} is mustache for runtime) * api="" -> 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 syntax_1 = require("@wrnexus/syntax"); const store_codegen_ts_1 = require("./store-codegen.js"); const analysis_ts_1 = require("./analysis.js"); const island_codegen_ts_1 = require("./island-codegen.js"); const client_codegen_ts_1 = require("./client-codegen.js"); /** * Island names for the file currently being generated. * * Codegen is a synchronous single pass, so a module-scoped set avoids threading * an extra parameter through every render function. Always reset in generate(). */ let currentIslands = new Set(); /** * Builds the island placeholder for a component tag that was imported from a * .tsx file. Returns null for ordinary .wrn components. */ function islandMarkerFor(node) { if (!currentIslands.has(node.tag)) return null; const directives = node.attrs .map((attr) => attr.name) .filter((name) => name.startsWith("client:")); const props = {}; for (const attr of node.attrs) { if (attr.name.startsWith("client:")) continue; const parsed = (0, island_codegen_ts_1.islandPropValue)(attr.value); if ("dynamic" in parsed) { throw new Error(`WRN-ISLAND-PROPS: Island '${node.tag}' received a runtime expression for prop '${attr.name}'. ` + `Island props are serialized at build time, so they must be literal values ` + `(for example start={3} or title="Revenue"), not ${attr.value}.`); } props[attr.name] = parsed.value; } const serialized = (0, island_codegen_ts_1.serializeIslandProps)(node.tag, props); if ("diagnostic" in serialized) throw new Error(serialized.diagnostic.message); return (0, island_codegen_ts_1.renderIslandMarker)({ name: node.tag, strategy: (0, island_codegen_ts_1.parseIslandStrategy)(directives), propsJson: serialized.json, }); } 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, "&") .replace(/"/g, """) .replace(//g, ">"); } /** 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* * (``). * * 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 `` marker the * runtime resolves server-side. Other `{expr}` mustaches are left untouched. */ function substituteTMarkers(text) { return text.replace(/\{t:([^{}]+)\}/g, (_m, key) => ``); } /** Escape a value for safe embedding in HTML text. */ function htmlTextEscape(value) { return value.replace(/[&<>]/g, (c) => (c === "&" ? "&" : c === "<" ? "<" : ">")); } /** * 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 `${sentinel}`; } 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 `${baked}`; }); } /** * 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(``); } 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('') + inner + escLit("")); if (node.tag === "KeepAlive") { const key = node.attrs.find((attribute) => attribute.name === "key")?.value ?? "default"; return (escLit('
`) + inner + escLit("
")); } if (node.tag === "Portal") { const target = node.attrs.find((attribute) => attribute.name === "to")?.value ?? "body"; return (escLit('
') + inner + escLit("
")); } if (node.tag === "Transition") { const name = node.attrs.find((attribute) => attribute.name === "name")?.value ?? "wrn-transition"; return (escLit('
') + inner + escLit("
")); } if (node.tag === "Component") { const selected = node.attrs.find((attribute) => attribute.name === "is")?.value ?? ""; return (escLit('
') + inner + escLit("
")); } if (componentTag) { const island = islandMarkerFor(node); if (island) return escLit(island); return (escLit(`
") + inner + escLit("
")); } if (syntax_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) { return escLit(`<${node.tag}`) + attrs + escLit(">"); } return escLit(`<${node.tag}`) + attrs + escLit(">") + inner + escLit(``); } /** * 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 + "}"; } 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)); const definition = node.type === "each" ? { list: node.list, item: node.item, index: node.index, key: node.key, body: renderClientControlTemplate(node.body), empty: renderClientControlTemplate(node.empty), } : node.branches.map((branch) => ({ cond: branch.cond, body: renderClientControlTemplate(branch.body), })); const attribute = node.type === "each" ? "data-wrn-each" : "data-wrn-if"; 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 : `${inner}`; } 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 `${inner}`; } 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 `
${initial}
`; } 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 `
${inner}
`; } if (isComponentTag(node.tag)) { return renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive); } const apiAttr = attrValue(node.attrs, "api"); const parsedApi = apiAttr ? parseApiBinding(apiAttr) : null; if (apiAttr && !parsedApi) { throw new Error(`Invalid .wrn api binding "${apiAttr}"`); } const apiBinding = parsedApi ? apiBindings.get(parsedApi.name) : undefined; if (parsedApi && !apiBinding) { throw new Error(`Unknown .wrn api binding "${parsedApi.name}"`); } 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 = csrGet && csrText ? csrMarker(csrBindings, { method: "GET", path: apiRoutePath(csrGet), body: expressionBody(csrText), helpers: "", }) : undefined; // Void elements (
, , …) have no closing tag and no children. if (syntax_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) { return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive, loops)}>`; } const inner = apiBinding ? apiCallMarker(loops, parsedApi.name, parsedApi.args) : 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}`; } function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive) { const island = islandMarkerFor(node); if (island) return island; 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 `
${inner}
`; } function renderNestedComponentInvocation(node, ctx) { // Islands work inside .wrn components too, not just pages. const island = islandMarkerFor(node); if (island) return island; 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 (`
${inner}
`); } /** * Parses the three `api="…"` render-binding forms: a bare name, an empty call, * or a call carrying an argument expression. Mirrors the shape of `@click="fn()"`, * so no new escaping or attribute-naming rules are introduced. The argument * capture is greedy up to the outer parens, so nested braces/parens/quotes in * the argument expression (object literals, arrays, strings) are carried * through untouched rather than truncated at the first `)`. */ function parseApiBinding(value) { const match = /^\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*(?:\(([\s\S]*)\))?\s*$/.exec(value); if (!match) return null; return { name: match[1], args: (match[2] ?? "").trim() }; } /** * Emit a render-time call into the server `api` object (Task 4's generated * transport) for an `apis {}` (mode "any") binding, and return the sentinel * that the loop/expression-splicing mechanism swaps for the real `${…}` code. * This calls the same server `api.()` member a `load`/action block would * call -- it does not reimplement fetch/response handling -- so a block that is * both render-bound and called from code runs its own call each time (no * dedup is attempted; see apis-render-binding.test.ts). */ function apiCallMarker(loops, name, args) { loops.push(`\${__wrnexusEscapeHtml(await api.${name}(${args}))}`); return `\x00WRNEACH${loops.length - 1}\x00`; } function ssrMarker(bindings, binding) { const marker = ``; bindings.push({ marker, ...binding }); return marker; } function csrMarker(bindings, binding) { const id = String(bindings.length); bindings.push({ id, ...binding }); return id; } 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 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}"`); } const sectioned = block.sections; const errorSection = sectioned?.error.trim(); bindings.set(block.name, { mode: block.mode, method: block.method, path: apiRoutePath(block.path), // A sectioned block binds the payload to `data`; the legacy form keeps // the `with ($data)` injection, which cannot be typed. body: sectioned ? `const data = $data; ${sectioned.response.trim() || "return data;"}` : dataBody(block.body), // Only a sectioned block with a non-empty `error {}` gets a fallback — // legacy blocks and sectioned blocks without `error` keep failures // propagating exactly as before. ...(errorSection ? { errorBody: errorSection } : {}), helpers: sharedHelpers, }); } return bindings; } /** * Server-side `api` object. * * The transport dispatches in-process, so a call from a load block or an action * costs a function call rather than a network round trip. The request context * comes from AsyncLocalStorage because `ctx` is not in scope everywhere server * code runs. */ function serverApiBindings(ast) { const members = ast.dataApis .filter((block) => block.mode === "any") .map((block) => { const sections = block.sections; const response = sections.response.trim() || "return data;"; const error = sections.error.trim(); const failure = error ? `const status = (err as { status?: unknown } | null | undefined)?.status; const message = err instanceof Error ? err.message : String(err); const data = (err as { data?: unknown } | null | undefined)?.data; ${error}` : `throw err;`; return ` ${JSON.stringify(block.name)}: async (input?: unknown) => { const ctx = __wrnexusRequireRequestContext(${JSON.stringify(`api.${block.name}`)}) as __WrnexusContext; let data: any; try { data = await __wrnexusCallApi(${JSON.stringify(apiRoutePath(block.path))}, ${JSON.stringify(block.method)}, ctx, input); } catch (err) { ${failure} } ${response} }`; }); return members.length ? `const api = {\n${members.join(",\n")}\n};` : ""; } function ssrRuntimeSource() { return `const __wrnexusHtmlEscapes: Record = { "&": "&", "<": "<", ">": ">", "\\"": """, "'": "'" }; 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, input?: unknown) => Promise; 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 __wrnexusEvalError(err: unknown, body: string, helpers = "", ctx: __WrnexusContext): unknown { const adapters = { cookies: ctx.cookies, session: ctx.session, localStorage: ctx.localStorage, }; const status = (err as { status?: unknown } | null | undefined)?.status; const data = (err as { data?: unknown } | null | undefined)?.data; const message = err instanceof Error ? err.message : String(err); return new Function("$status", "$message", "$data", "$adapters", "const cookies = $adapters.cookies;\\nconst session = $adapters.session;\\nconst localStorage = $adapters.localStorage;\\nconst status = $status;\\nconst message = $message;\\nconst data = $data;\\n" + helpers + "\\n" + body)(status, message, 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 === "&" ? "&" : character === "<" ? "<" : character === ">" ? ">" : """, ); } async function __wrnexusCallApi( path: string, method: string, ctx: __WrnexusContext, input?: unknown, ): Promise { if (typeof ctx.__wrnexusCallApi === "function") { return await ctx.__wrnexusCallApi(path, method, input); } const built = __wrnexusBuildApiRequest(path, method, input as Record | undefined); const url = new URL(built.url, ctx.req.url); const headers = new Headers(ctx.req.headers); if (built.contentType) headers.set("content-type", built.contentType); const res = await fetch( new Request(url, { method, headers, ...(built.body === undefined ? {} : { body: built.body }), }), ); const type = res.headers.get("content-type") || ""; if (!res.ok) { const data = type.includes("application/json") ? await res.json().catch(() => undefined) : await res.text().catch(() => undefined); throw Object.assign(new Error(".wrn data API request failed with status " + res.status), { status: res.status, data, }); } return type.includes("application/json") ? await res.json() : await res.text(); } type __WrnexusApiCall = { path: string; method: string; body: string; helpers: string; errorBody?: string; }; type __WrnexusSsrBinding = __WrnexusApiCall & { marker: string }; // Shared by every ssr api-binding consumption site (marker replacement, // #each loop consts, ...) so the narrow try/catch -- only active when the // block declared an error section -- cannot drift between call sites. async function __wrnexusResolveApiBinding( binding: __WrnexusApiCall, ctx: __WrnexusContext, ): Promise { if (binding.errorBody) { let data: unknown; try { data = await __wrnexusCallApi(binding.path, binding.method, ctx); } catch (err) { return __wrnexusEvalError(err, binding.errorBody, binding.helpers, ctx); } return __wrnexusEvalData(data, binding.body, binding.helpers, ctx); } const data = await __wrnexusCallApi(binding.path, binding.method, ctx); return __wrnexusEvalData(data, binding.body, binding.helpers, ctx); } async function __wrnexusRenderSsrBindings(html: string, ctx: __WrnexusContext): Promise { for (const binding of __wrnexusSsrBindings) { const value = await __wrnexusResolveApiBinding(binding, 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 ``; } 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" ? ["client", "shared"] : ["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, options = {}) { currentIslands = options.islands ?? new Set(); try { return generateInner(ast); } finally { currentIslands = new Set(); } } function generateInner(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 hasServerApis = ast.dataApis.some((block) => block.mode === "any"); if (hasServerApis) { out.push(`import { requireRequestContext as __wrnexusRequireRequestContext } from "@wrnexus/core";`); } 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 = `
${html}
`; } const pageStyleTag = localStyleTag(ast, styles); if (pageStyleTag) { html = `${pageStyleTag}${html}`; } if (ast.renderMode === "client") { const clientRoot = hydrationId(ast); html = `
`; } 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(/]*>[\s\S]*?<\/wrn-dynamic-region>/gi, ''); 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"; 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); } } const loopConsts = []; const needsSsrRuntime = ssrBindings.length > 0 || loops.length > 0 || runtimeStateNames.size > 0; const needsRuntimeHelpers = needsSsrRuntime || hasServerApis; if (needsRuntimeHelpers) { out.push(`import { buildApiRequest as __wrnexusBuildApiRequest } from "@wrnexus/core";`); out.push(ssrRuntimeSource()); out.push(`const __wrnexusSsrBindings: __WrnexusSsrBinding[] = ${JSON.stringify(ssrBindings, null, 2)};`); } if (hasServerApis) { out.push(serverApiBindings(ast)); } if (needsSsrRuntime) { 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, "&") .replace(/"/g, """) .replace(//g, ">"); 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, "&") .replace(/"/g, """) .replace(//g, ">"); 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, "&") .replace(/"/g, """) .replace(//g, ">"); 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 | 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, 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` : "Record"}, Awaited>>("", ${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, syntax_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(``); } 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` → `0`. out += escLit(``) + `\${__wrnHtml(${ctx.resolveExpr(expr)})}` + escLit(``); } 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))); } /** * Serialize a control-block body as inert browser-side template markup. * Values deliberately remain as mustaches: the CSR runtime evaluates them * against the component scope (and `{#each}` locals) when it materializes the * template. The string is base64 encoded before it is placed in HTML. */ function renderClientControlTemplate(nodes) { const render = (node) => { if (node.type === "text") { return node.value.replace(/\{([^{}]+)\}/g, (whole, rawExpression) => { const expression = rawExpression.trim(); return expression.startsWith("t:") ? `` : `${whole}`; }); } if (node.type === "each") { return ``; } if (node.type === "if") { return ``; } const componentTag = isComponentTag(node.tag); let bindIndex = 0; const attrs = node.attrs .map((attribute) => { const name = attribute.event ? componentTag ? componentEventAttribute(attribute.name) : eventAttribute(attribute.name) : attribute.name; if (attribute.boolean) return ` ${name}`; if (attribute.name.startsWith("class:")) { const expression = unwrapDirectiveExpression(attribute.value); return ` data-wrn-class-${bindIndex++}="${attrEscape(JSON.stringify([attribute.name.slice("class:".length), expression]))}"`; } if (attribute.name === "data-show") { return ` data-show="${attrEscape(unwrapDirectiveExpression(attribute.value))}"`; } const rendered = ` ${name}="${attrEscape(attribute.value)}"`; return attribute.value.includes("{") ? `${rendered} data-wrn-bind-${bindIndex++}="${attrEscape(JSON.stringify([name, attribute.value]))}"` : rendered; }) .join(""); const children = node.children.map(render).join(""); if (node.tag === "Static") return children; if (componentTag) return `
${children}
`; if (syntax_1.VOID_ELEMENTS.has(node.tag.toLowerCase())) return `<${node.tag}${attrs}>`; return `<${node.tag}${attrs}>${children}`; }; return nodes.map(render).join(""); } function encodeClientControl(value) { return node_buffer_1.Buffer.from(JSON.stringify(value), "utf8").toString("base64"); } 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}`; } const definition = encodeClientControl(node.branches.map((branch) => ({ cond: branch.cond, body: renderClientControlTemplate(branch.body), }))); 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(""); const serverBody = "${(() => { const __wl = Array.isArray(" + list + ") ? (" + list + ") : []; return __wl.length ? __wl.map((" + item + ", " + index + ") => `" + body + '`).join("") : `' + empty + "`; })()}"; const definition = encodeClientControl({ list: node.list, item: node.item, index: node.index, key: node.key, body: renderClientControlTemplate(node.body), empty: renderClientControlTemplate(node.empty), }); return `${serverBody}`; } 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 : `${inner}`; } 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 `
${inner}
`; } 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 `
${inner}
`; } 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 (syntax_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}`; } 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, syntax_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 + `
` + viewCode + "
`" : "`" + 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, declared: Set, ): Record { 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 === "&" ? "&" : c === "<" ? "<" : ">", ); } function __wrnAttr(v: unknown): string { return String(v == null ? "" : v).replace( /[&<>"]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """, ); } 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 { 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 { return Object.keys(obj) .map( (key) => key + ": " + __wrnexusSerializeScopeValue( obj[key], ), ) .join(", ") .replace(/&/g, "&") .replace(/"/g, """) .replace(//g, ">"); }`); } const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : ""; out.push(`export function render(props: ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record"} = {} as ${effectiveProps.length > 0 ? `${ast.name}Props` : "Record"}): 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 === "&" ? "&" : c === "<" ? "<" : ">"); } function __wrnAttr(v) { return String(v == null ? "" : v).replace(/[&<>"]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """); } 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"), })), 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.routeNeedsIslands = exports.generateIslandEntry = exports.buildIslands = exports.assertReactAvailable = exports.serializeIslandProps = exports.renderIslandMarker = exports.parseIslandStrategy = exports.islandPropValue = exports.islandNamesFrom = 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.stripBrowserTypes = 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 browser_transpile_ts_1 = require("./browser-transpile.js"); Object.defineProperty(exports, "stripBrowserTypes", { enumerable: true, get: function () { return browser_transpile_ts_1.stripBrowserTypes; } }); 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 = "") { 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 = "") { 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; } }); var island_codegen_ts_1 = require("./island-codegen.js"); Object.defineProperty(exports, "islandNamesFrom", { enumerable: true, get: function () { return island_codegen_ts_1.islandNamesFrom; } }); Object.defineProperty(exports, "islandPropValue", { enumerable: true, get: function () { return island_codegen_ts_1.islandPropValue; } }); Object.defineProperty(exports, "parseIslandStrategy", { enumerable: true, get: function () { return island_codegen_ts_1.parseIslandStrategy; } }); Object.defineProperty(exports, "renderIslandMarker", { enumerable: true, get: function () { return island_codegen_ts_1.renderIslandMarker; } }); Object.defineProperty(exports, "serializeIslandProps", { enumerable: true, get: function () { return island_codegen_ts_1.serializeIslandProps; } }); var island_bundle_ts_1 = require("./island-bundle.js"); Object.defineProperty(exports, "assertReactAvailable", { enumerable: true, get: function () { return island_bundle_ts_1.assertReactAvailable; } }); Object.defineProperty(exports, "buildIslands", { enumerable: true, get: function () { return island_bundle_ts_1.buildIslands; } }); Object.defineProperty(exports, "generateIslandEntry", { enumerable: true, get: function () { return island_bundle_ts_1.generateIslandEntry; } }); var analysis_ts_2 = require("./analysis.js"); Object.defineProperty(exports, "routeNeedsIslands", { enumerable: true, get: function () { return analysis_ts_2.routeNeedsIslands; } }); }, "packages/compiler/src/island-bundle.ts": function (module, exports, require, __filename, __dirname) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateIslandEntry = generateIslandEntry; exports.reactJsxPlugin = reactJsxPlugin; exports.assertReactAvailable = assertReactAvailable; exports.buildIslands = buildIslands; const node_crypto_1 = require("node:crypto"); const node_fs_1 = require("node:fs"); const node_module_1 = require("node:module"); const node_path_1 = require("node:path"); /** * Generates the per-island browser entry. * * Never imports react-dom/server — islands are client-only. */ function generateIslandEntry(input) { return [ "/** @jsxImportSource react */", `import Component from ${JSON.stringify(input.sourcePath)};`, `export const name = ${JSON.stringify(input.name)};`, `export default Component;`, "", ].join("\n"); } /** * Compiles every island `.tsx` against React's JSX runtime. * * The repo's root tsconfig sets `jsxImportSource` to `@wrnexus/core`, so an * island would otherwise compile to WRNexus's HTML-string renderer and * silently never mount. A `@jsxImportSource` pragma applies only to the file * that carries it, so putting one in the generated entry does nothing for the * author's own component — the injection has to happen per source file, which * is what this plugin does. App-authored islands stay plain `.tsx`. */ function reactJsxPlugin() { return { name: "wrnexus-island-jsx", setup(build) { build.onLoad({ filter: /\.tsx$/ }, async (args) => { // Only first-party island sources need the pragma. Third-party .tsx // under node_modules is left alone so Bun's own handling is untouched. if (args.path.includes("node_modules")) return undefined; return { contents: `/** @jsxImportSource react */\n${await Bun.file(args.path).text()}`, loader: "tsx", }; }); }, }; } function assertReactAvailable(appRoot) { const require = (0, node_module_1.createRequire)((0, node_path_1.join)(appRoot, "package.json")); try { require.resolve("react"); require.resolve("react-dom"); return null; } catch { return { code: "WRN-ISLAND-REACT-MISSING", severity: "error", message: "This app imports a .tsx island but react and react-dom are not installed. " + "Run: bun add react react-dom", }; } } /** * Bundles island entries. `splitting: true` is required so React is emitted * once as a shared chunk rather than duplicated into every island. */ async function buildIslands(input) { if (input.islands.length === 0) return { assets: [], sharedChunks: [] }; // Each island gets its own generated entry file named after the island. // Passing the component sources directly would dedupe two islands that share // a source file, and output order is not guaranteed to match input order — // both of which silently mismatch island names to bundles. // // The entries live inside outDir so `react` resolves from the app that // installed it, exactly as the island's own imports do. const entryDir = (0, node_path_1.join)(input.outDir, ".entries"); (0, node_fs_1.mkdirSync)(entryDir, { recursive: true }); const islandNames = new Set(input.islands.map((island) => island.name)); for (const island of input.islands) { (0, node_fs_1.writeFileSync)((0, node_path_1.join)(entryDir, `${island.name}.tsx`), generateIslandEntry(island), "utf8"); } const entrypoints = input.islands.map((island) => (0, node_path_1.join)(entryDir, `${island.name}.tsx`)); if (input.appRoot) { const resolveFrom = (0, node_module_1.createRequire)((0, node_path_1.join)(input.appRoot, "package.json")); const runtimeEntry = (0, node_path_1.join)(entryDir, "runtime.ts"); (0, node_fs_1.writeFileSync)(runtimeEntry, `export * from ${JSON.stringify(resolveFrom.resolve("@wrnexus/react/browser"))}; `, "utf8"); entrypoints.push(runtimeEntry); } try { const result = await Bun.build({ entrypoints, outdir: input.outDir, target: "browser", format: "esm", splitting: true, minify: true, plugins: [reactJsxPlugin()], }); if (!result.success) { throw new AggregateError(result.logs, "Island bundling failed"); } const assets = []; const sharedChunks = []; for (const output of result.outputs) { if (output.kind === "entry-point") { // Bun names an entry's output after its entry file, so the basename // identifies the island unambiguously. const stem = (0, node_path_1.basename)(output.path).replace(/\.js$/, ""); if (stem === "runtime") continue; if (!islandNames.has(stem)) continue; assets.push({ name: stem, hash: (0, node_crypto_1.createHash)("sha256").update(output.path).digest("hex").slice(0, 16), path: output.path, }); } else if (output.kind === "chunk") { sharedChunks.push(output.path); } } return { assets, sharedChunks }; } finally { (0, node_fs_1.rmSync)(entryDir, { recursive: true, force: true }); } } }, "packages/compiler/src/island-codegen.ts": function (module, exports, require, __filename, __dirname) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseIslandStrategy = parseIslandStrategy; exports.islandPropValue = islandPropValue; exports.serializeIslandProps = serializeIslandProps; exports.renderIslandMarker = renderIslandMarker; exports.islandNamesFrom = islandNamesFrom; // Implemented locally rather than imported from @wrnexus/core: compiler modules // are bundled into the Node-only VS Code extension, which contains no other // packages, so a runtime import of core would break the editor compiler. const HTML_ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", }; function escapeHtml(value) { return value.replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]); } const SAFE_ISLAND_NAME = /^[A-Za-z0-9_-]+$/; function isSafeIslandName(name) { return SAFE_ISLAND_NAME.test(name); } const STRATEGIES = { "client:only": "only", "client:load": "load", "client:visible": "visible", "client:idle": "idle", }; function parseIslandStrategy(directives) { for (const directive of directives) { const match = STRATEGIES[directive]; if (match) return match; } return "only"; } function unsupportedProp(value) { const type = typeof value; if (type === "function" || type === "symbol" || type === "bigint" || type === "undefined") { return true; } if (value === null || type !== "object") return false; if (Array.isArray(value)) return value.some(unsupportedProp); const proto = Object.getPrototypeOf(value); if (proto !== Object.prototype && proto !== null) return true; return Object.values(value).some(unsupportedProp); } /** * Interprets an island attribute value with JSX semantics. * * `title="Revenue"` is a string, `start={3}` is a number, `flag` alone is * `true`. Without this every prop arrives as a string, so `start={3}` would be * `"3"` and arithmetic in the island silently concatenates. * * Returns `dynamic` for a `{…}` value that is not JSON: such expressions are * evaluated at runtime and cannot cross the serialization boundary. */ function islandPropValue(raw) { if (raw === undefined || raw === "") return { value: true }; const expression = /^\{([\s\S]*)\}$/.exec(raw); if (!expression) return { value: raw }; const inner = expression[1].trim(); try { return { value: JSON.parse(inner) }; } catch { return { dynamic: inner }; } } function serializeIslandProps(componentName, props) { const offenders = Object.entries(props) .filter(([, value]) => unsupportedProp(value)) .map(([key]) => key); if (offenders.length > 0) { return { diagnostic: { code: "WRN-ISLAND-PROPS", severity: "error", message: `Island '${componentName}' received non-serializable prop(s): ${offenders.join(", ")}. ` + `Island props cross a serialization boundary and must be JSON-safe ` + `(no functions, symbols, bigints, undefined, or class instances).`, }, }; } return { json: JSON.stringify(props) }; } function renderIslandMarker(input) { // The name becomes a path segment when the browser fetches // /__wrnexus/island/.js, so reuse the framework's conservative charset // rather than relying on escaping alone. if (!isSafeIslandName(input.name)) { throw new Error(`Island name '${input.name}' is not a safe identifier. ` + `Island names may only contain letters, digits, underscores, and hyphens.`); } return (`
`); } /** * Local binding names introduced by island imports. * * Codegen sees only component tag names, so it needs the set of names that came * from `.tsx` imports to tell an island apart from a `.wrn` component. */ function islandNamesFrom(imports) { const names = new Set(); for (const entry of imports) { if (entry.kind !== "island") continue; const local = entry.declaration.defaultImport; if (local) names.add(local); } return names; } }, "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 === "<" ? "<" : ">")); } 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}`; }) .join(""); return `<${nativeTag}${attrs}>${children}`; } 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/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.(...)`. * Server functions remain available to SSR/server modules without becoming * remotely callable by default. */ function remotelyReferencedServerFunctions(ast) { const browserSources = ast.runtimeFunctions .filter((fn) => ["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"), })); } function generateServerFunctionsModule(ast) { const source = ast.functions .map((body) => (0, syntax_1.stripRuntimeFunctionModifiers)(body, ["server", "shared"])) .filter(Boolean) .join("\n\n"); const names = ast.runtimeFunctions .filter((fn) => ["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"].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"; }); 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/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")};`) .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") .map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): ${fn.returnType ?? (fn.async ? "Promise" : "unknown")};`) .join("\n"); const serverFunctions = ast.runtimeFunctions .filter((fn) => fn.runtime === "server") .map((fn) => ` ${member(fn.name)}(${params(fn.parameters)}): Promise>;`) .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/syntax/src/api-sections.ts": function (module, exports, require, __filename, __dirname) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseApiSections = parseApiSections; exports.hasRequestSection = hasRequestSection; exports.parseApiEntries = parseApiEntries; /** * Parse the sectioned form of an `api` block body. * * Returns null when no section keyword is present, which is how the legacy * bare-body form stays valid: the caller keeps treating the body as the * response expression. * * Detection and slicing both drive the tokenizer's own string/comment-aware * scanning (`skipLiteralOrComment`, `Lexer.readBalancedBraces`) instead of a * second hand-rolled brace counter, so a `}` inside a string or a `request {` * mentioned in a comment can't be mistaken for a real section. */ const tokenizer_ts_1 = require("./tokenizer.js"); const SECTION_NAMES = ["request", "response", "error"]; const REQUEST_SUBSECTION_NAMES = ["parameters", "body"]; /** * Walk `source` at brace-depth 0, looking for `name { ... }` where `name` is * one of `names`. Strings, template literals, and comments are skipped via * `skipLiteralOrComment` — the same rules `readBalancedBraces` uses — so a * keyword mentioned inside a string or comment, or nested inside an unrelated * `{ }` (e.g. an object literal in a legacy body), is never mistaken for a * section. Matched blocks are sliced via `Lexer.readBalancedBraces()` itself, * not a reimplementation of it. */ function scanTopLevelBlocks(source, names) { const found = new Map(); const lx = new tokenizer_ts_1.Lexer(source); let depth = 0; let i = 0; let atLineStart = true; while (i < source.length) { const c = source[i]; if (c === "\n") { atLineStart = true; i++; continue; } const skipped = (0, tokenizer_ts_1.skipLiteralOrComment)(source, i, atLineStart); if (skipped !== null) { i = skipped; atLineStart = false; continue; } if (c !== " " && c !== "\t" && c !== "\r") atLineStart = false; if (depth === 0 && (0, tokenizer_ts_1.isIdentStart)(c)) { let j = i + 1; while (j < source.length && (0, tokenizer_ts_1.isIdentPart)(source[j])) j++; const word = source.slice(i, j); // Skip trivia between the identifier and a possible '{' without // treating anything in between as significant yet. let k = j; let lineStartAtK = false; while (k < source.length) { const kc = source[k]; if (kc === " " || kc === "\t" || kc === "\r") { k++; continue; } if (kc === "\n") { lineStartAtK = true; k++; continue; } const kSkipped = (0, tokenizer_ts_1.skipLiteralOrComment)(source, k, lineStartAtK); if (kSkipped !== null) { k = kSkipped; lineStartAtK = false; continue; } break; } if (names.includes(word) && source[k] === "{") { lx.pos = k; const start = k + 1; const text = lx.readBalancedBraces(); if (!found.has(word)) found.set(word, { text, start }); i = lx.pos; continue; } i = j; continue; } if (c === "{") depth++; else if (c === "}") depth = Math.max(0, depth - 1); i++; } return found; } /** Rebase a span captured from `outer.text` back onto the original source. */ function absolutize(span, outer) { if (!span) return undefined; return outer ? { text: span.text, start: outer.start + span.start } : span; } /** `name?: string` -> { name, optional, type }. Blank lines and comments are skipped. */ function parseFields(span) { if (!span) return []; const fields = []; let cursor = 0; for (const rawLine of span.text.split("\n")) { const lineOffset = span.start + cursor; cursor += rawLine.length + 1; const line = rawLine.trim().replace(/,$/, ""); if (!line || line.startsWith("//")) continue; const match = /^([A-Za-z_$][A-Za-z0-9_$]*)(\?)?\s*:\s*(.+)$/.exec(line); if (!match) { throw new tokenizer_ts_1.LexError(`Expected "name: type" in an api request section, got "${line}" at offset ${lineOffset}`); } fields.push({ name: match[1], optional: match[2] === "?", type: match[3].trim() }); } return fields; } function parseApiSections(source) { const top = scanTopLevelBlocks(source, SECTION_NAMES); if (top.size === 0) return null; const request = top.get("request"); const sub = request ? scanTopLevelBlocks(request.text, REQUEST_SUBSECTION_NAMES) : new Map(); return { parameters: parseFields(absolutize(sub.get("parameters"), request)), body: parseFields(absolutize(sub.get("body"), request)), response: top.get("response")?.text ?? "", error: top.get("error")?.text ?? "", }; } /** True when the block declares a `request` section. */ function hasRequestSection(source) { return scanTopLevelBlocks(source, SECTION_NAMES).has("request"); } /** * Parse the body of a page-level `apis { }` container: a sequence of * ` { ... }` entries with no leading `api` keyword * (the container supplies it). Each entry's braces are sliced with the * tokenizer's own `Lexer.readBalancedBraces()`, so the same string- and * comment-aware rules that protect `parseApiSections` apply here too. */ function parseApiEntries(source) { const entries = []; const lx = new tokenizer_ts_1.Lexer(source); while (lx.peek().type !== "eof") { const nameToken = lx.next(); if (nameToken.type !== "ident") { throw new tokenizer_ts_1.LexError(`Expected an api entry name at offset ${nameToken.pos}`); } const methodToken = lx.next(); if (methodToken.type !== "ident") { throw new tokenizer_ts_1.LexError(`Expected an HTTP method after "${nameToken.value}"`); } const path = lx.readPath(); const entryBody = lx.readBalancedBraces(); const sections = parseApiSections(entryBody); if (sections === null) { throw new tokenizer_ts_1.LexError(`Api entry "${nameToken.value}" has a bare body; declare a "response { }" section instead`); } entries.push({ mode: "any", name: nameToken.value, method: methodToken.value.toUpperCase(), path, body: "", sections, }); } return entries; } }, "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:.", 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 ?? ""}:${diagnostic.position.line}:${diagnostic.position.column}` : (diagnostic.file ?? ""); 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}`); } 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}`); } 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("/.test(value); } /** * Preserve compact, already-balanced HTML fragments as one line. * * A line such as `PageAPI` 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("", 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("$/.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 = 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 (/)/i.test(value) && !/<\/pre\s*>/i.test(value.slice(0, value.search(/)/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}{/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(" 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.skipLiteralOrComment = 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; } }); Object.defineProperty(exports, "skipLiteralOrComment", { enumerable: true, get: function () { return tokenizer_ts_1.skipLiteralOrComment; } }); 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"); const api_sections_ts_1 = require("./api-sections.js"); /** * Recursive-descent parser for `.wrn`, producing a small AST. * * Grammar (subset of the vision, but real): * * page { * types { } * props { : [= ] } // no default means required * state : = // type annotation is optional * view { } // plain HTML (see parseHtmlView) * seo { title = "Home" description = "..." } * ssr { api { } functions { } } * client { api { } functions { } } * style { } // zero or more, inlined with the page * functions { } // zero or more, shared helpers * api { } // zero or more * realtime { on () { } * } // 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/.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 = ; @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; 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 (rawMode === "client" && lx.peek().type === "eq") { lx.next(); hydrate = expect("string").value; break; } if (lx.peek().type === "lbrace") { throw new ParseError(`"${rawMode} { … }" data blocks were removed. Declare API calls in a page-level "apis { }" block, and move mode-scoped helpers into "functions { shared function … }".`); } throw new ParseError(`Expected a ${rawMode} state block or hydrate assignment`); } 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; } case "apis": { lx.next(); const body = lx.readBalancedBraces(); dataApis.push(...(0, api_sections_ts_1.parseApiEntries)(body)); 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); const seenApiNames = new Set(); for (const block of dataApis) { if (seenApiNames.has(block.name)) { throw new ParseError(`Duplicate api entry "${block.name}"`, "WRN-API-DUPLICATE"); } seenApiNames.add(block.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: * * - `children` — elements with attributes * - `` and HTML void elements (`
`, ``, …) — no closing tag * - text may contain `{expr}` interpolation, kept verbatim for the runtime * - `@event="..."` becomes a client event binding; hyphenated names are fine * - `` 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++; // Backslash-escapes the delimiter (and anything else) so an attribute // value -- e.g. an `api="fn({ a: \"b\" })"` call expression -- can carry // the same quote character it's wrapped in. let value = ""; while (i < src.length && src[i] !== quote) { if (src[i] === "\\" && i + 1 < src.length) { value += src[i + 1]; i += 2; continue; } value += src[i]; i++; } if (i >= src.length) return fail("Unterminated attribute value"); 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 ``); i += 2; skipWs(); const close = readTagName(); if (close !== tag) return fail(`Mismatched , expected `); skipWs(); if (src[i] !== ">") return fail(`Expected '>' to close `); 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 as [, ] [key ]} …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 } … {:else if } … {: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 (` { 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 + 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.isIdentPart = exports.isIdentStart = exports.LexError = void 0; exports.skipLiteralOrComment = skipLiteralOrComment; 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); exports.isIdentStart = isIdentStart; const isIdentPart = (c) => /[A-Za-z0-9_]/.test(c); exports.isIdentPart = isIdentPart; /** * Skip over a string/template literal or comment starting at `src[i]`, using * the exact rules `readBalancedBraces` needs to stay comment- and * string-aware: `/* block *\/` comments anywhere, `//` line comments only at * the start of a line (so a bare `https://…` in view text isn't mistaken for * one), and `"`, `'`, `` ` `` strings with backslash escapes. * * Returns the index just past what it skipped, or `null` when `src[i]` isn't * the start of one of those. Exported so any other raw-body scanner that * needs to walk `.wrn` source without tripping over strings or comments * (e.g. the `api` section scanner) shares this logic instead of * reimplementing it — a second hand-rolled scanner is how apostrophes in * prose used to swallow braces. */ function skipLiteralOrComment(src, i, atLineStart) { const c = src[i]; if (c === "/" && src[i + 1] === "*") { const close = src.indexOf("*/", i + 2); return close === -1 ? src.length : close + 2; } if (atLineStart && c === "/" && src[i + 1] === "/") { const newline = src.indexOf("\n", i + 2); return newline === -1 ? src.length : newline; } if (c === '"' || c === "'" || c === "`") { let j = i + 1; while (j < src.length) { if (src[j] === "\\") { j += 2; continue; } if (src[j] === c) return j + 1; j++; } return src.length; } return null; } 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 ((0, exports.isIdentStart)(c)) { let v = ""; while (this.pos < src.length && (0, exports.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 = `). */ 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; /** True while only whitespace has been seen since the last newline. */ let atLineStart = false; while (i < src.length) { const c = src[i]; if (c === "\n") { atLineStart = true; i++; continue; } const skipped = skipLiteralOrComment(src, i, atLineStart); if (skipped !== null) { i = skipped; atLineStart = false; continue; } if (c !== " " && c !== "\t" && c !== "\r") atLineStart = false; if (c === "{") depth++; else if (c === "}") { depth--; if (depth === 0) { this.pos = i + 1; return src.slice(start, i); } } 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 = "shared"; 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");