feat(wrn): support native structured prop expressions

This commit is contained in:
2026-07-29 16:46:18 +05:30
parent 6afe32f63f
commit 8fc6f15402
68 changed files with 210 additions and 118 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/authz — part of the WrNexus framework.", "description": "@wrnexus/authz — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+13 -13
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/cli — part of the WrNexus framework.", "description": "@wrnexus/cli — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -28,18 +28,18 @@
"wrnexus": "./dist/index.js" "wrnexus": "./dist/index.js"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.0", "@wrnexus/core": "^0.5.1",
"@wrnexus/router": "^0.5.0", "@wrnexus/router": "^0.5.1",
"@wrnexus/csr": "^0.5.0", "@wrnexus/csr": "^0.5.1",
"@wrnexus/compiler": "^0.5.0", "@wrnexus/compiler": "^0.5.1",
"@wrnexus/styles": "^0.5.0", "@wrnexus/styles": "^0.5.1",
"@wrnexus/dev-server": "^0.5.0", "@wrnexus/dev-server": "^0.5.1",
"@wrnexus/ui": "^0.5.0", "@wrnexus/ui": "^0.5.1",
"@wrnexus/validation": "^0.5.0", "@wrnexus/validation": "^0.5.1",
"@wrnexus/i18n": "^0.5.0", "@wrnexus/i18n": "^0.5.1",
"@wrnexus/db": "^0.5.0", "@wrnexus/db": "^0.5.1",
"@wrnexus/plugin": "^0.5.0", "@wrnexus/plugin": "^0.5.1",
"@wrnexus/syntax": "^0.5.0" "@wrnexus/syntax": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -164,8 +164,8 @@ A file opens with `page <Name>` or `component <Name>` followed by a `{ ... }` bo
- `types { <TypeScript declarations> }` — reusable interfaces and aliases for the current file. - `types { <TypeScript declarations> }` — reusable interfaces and aliases for the current file.
- `props { name: Type = <default> ... }` — typed component props. Omit `= <default>` to make a prop required. Legacy inferred props remain supported. - `props { name: Type = <default> ... }` — typed component props. Omit `= <default>` to make a prop required. Legacy inferred props remain supported.
- `@event name = function` inside `props` — declares a public component event. Emit it from component behavior with `name(detail)` or `$emit("name", detail)`, and consume it with `<Component @name="handler(event)" />`. - `@event name = function` inside `props` — declares a public component event. Emit it from component behavior with `name(detail)` or `$emit("name", detail)`, and consume it with `<Component @name="handler(event)" />`.
- `state <ident>: Type = <expr>` — typed reactive state seeded from a raw JS expression. The annotation is optional for backward compatibility. - `state <ident>: Type = <expr>` — typed reactive state seeded from a raw JS expression, including native array and object literals. The annotation is optional for backward compatibility.
- `view { <html> }` — plain HTML with `{expr}` interpolation in text and attributes, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and `<!-- comments -->`. Attribute expressions that reference `state` keep an SSR value and update reactively in the browser. - `view { <html> }` — plain HTML with `{expr}` interpolation in text and attributes, JSX-style component props such as `items={items}`, `items={[...]}`, and `options={{...}}`, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and `<!-- comments -->`. Structured component props are serialized safely for SSR; expressions that reference `state` retain their initial value and update reactively in the browser.
- `seo { key = "value" ... }` — metadata merged into the generated `meta`. - `seo { key = "value" ... }` — metadata merged into the generated `meta`.
- `style { <raw css> }` — inlined page/component stylesheet (repeatable). - `style { <raw css> }` — inlined page/component stylesheet (repeatable).
- `functions { <TypeScript> }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code. - `functions { <TypeScript> }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code.
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/compiler — part of the WrNexus framework.", "description": "@wrnexus/compiler — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/syntax": "^0.5.0" "@wrnexus/syntax": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/core — part of the WrNexus framework.", "description": "@wrnexus/core — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/csr — part of the WrNexus framework.", "description": "@wrnexus/csr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.0" "@wrnexus/core": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/db — part of the WrNexus framework.", "description": "@wrnexus/db — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+15 -15
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/dev-server — part of the WrNexus framework.", "description": "@wrnexus/dev-server — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -25,20 +25,20 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.0", "@wrnexus/core": "^0.5.1",
"@wrnexus/dev-toolbar": "^0.5.0", "@wrnexus/dev-toolbar": "^0.5.1",
"@wrnexus/router": "^0.5.0", "@wrnexus/router": "^0.5.1",
"@wrnexus/ssr": "^0.5.0", "@wrnexus/ssr": "^0.5.1",
"@wrnexus/csr": "^0.5.0", "@wrnexus/csr": "^0.5.1",
"@wrnexus/compiler": "^0.5.0", "@wrnexus/compiler": "^0.5.1",
"@wrnexus/styles": "^0.5.0", "@wrnexus/styles": "^0.5.1",
"@wrnexus/ui": "^0.5.0", "@wrnexus/ui": "^0.5.1",
"@wrnexus/validation": "^0.5.0", "@wrnexus/validation": "^0.5.1",
"@wrnexus/i18n": "^0.5.0", "@wrnexus/i18n": "^0.5.1",
"@wrnexus/db": "^0.5.0", "@wrnexus/db": "^0.5.1",
"@wrnexus/pubsub": "^0.5.0", "@wrnexus/pubsub": "^0.5.1",
"@wrnexus/uploader": "^0.5.0", "@wrnexus/uploader": "^0.5.1",
"@wrnexus/plugin": "^0.5.0" "@wrnexus/plugin": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.", "description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/encryption — part of the WrNexus framework.", "description": "@wrnexus/encryption — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.0" "@wrnexus/core": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/i18n — part of the WrNexus framework.", "description": "@wrnexus/i18n — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.0" "@wrnexus/core": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/jwt — part of the WrNexus framework.", "description": "@wrnexus/jwt — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/mobile — part of the WrNexus framework.", "description": "@wrnexus/mobile — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/native": "^0.5.0" "@wrnexus/native": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/native — part of the WrNexus framework.", "description": "@wrnexus/native — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/oauth — part of the WrNexus framework.", "description": "@wrnexus/oauth — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/plugin — part of the WrNexus framework.", "description": "@wrnexus/plugin — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -33,7 +33,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/syntax": "^0.5.0" "@wrnexus/syntax": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/pubsub — part of the WrNexus framework.", "description": "@wrnexus/pubsub — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/queue — part of the WrNexus framework.", "description": "@wrnexus/queue — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/reactive — part of the WrNexus framework.", "description": "@wrnexus/reactive — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/router — part of the WrNexus framework.", "description": "@wrnexus/router — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,8 +21,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/compiler": "^0.5.0", "@wrnexus/compiler": "^0.5.1",
"@wrnexus/core": "^0.5.0" "@wrnexus/core": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/ssr — part of the WrNexus framework.", "description": "@wrnexus/ssr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.0" "@wrnexus/core": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/styles — part of the WrNexus framework.", "description": "@wrnexus/styles — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,9 +21,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/uploader": "^0.5.0", "@wrnexus/uploader": "^0.5.1",
"@wrnexus/core": "^0.5.0", "@wrnexus/core": "^0.5.1",
"@wrnexus/plugin": "^0.5.0" "@wrnexus/plugin": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/syntax — part of the WrNexus framework.", "description": "@wrnexus/syntax — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/test — part of the WrNexus framework.", "description": "@wrnexus/test — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/tracking — part of the WrNexus framework.", "description": "@wrnexus/tracking — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/ui — part of the WrNexus framework.", "description": "@wrnexus/ui — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -26,7 +26,7 @@
"./ui.css": "./ui.css" "./ui.css": "./ui.css"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.0" "@wrnexus/core": "^0.5.1"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/uploader — part of the WrNexus framework.", "description": "@wrnexus/uploader — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.5.0" "@wrnexus/core": "^0.5.1"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"description": "@wrnexus/validation — part of the WrNexus framework.", "description": "@wrnexus/validation — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+13 -4
View File
@@ -15,14 +15,20 @@ WRNexusJS 0.3 keeps all previously supported declarations, including:
- `data-for`, `{#each}`, conditions, interpolations, events, and `class:*` - `data-for`, `{#each}`, conditions, interpolations, events, and `class:*`
New syntax is additive. Existing projects do not need to adopt it immediately. New syntax is additive. Existing projects do not need to adopt it immediately.
Dynamic attribute expressions should be single-quoted so braces remain unambiguous: Since WRNexusJS 0.5.1, dynamic component props may use JSX-style unquoted
expressions. Quoted expression attributes remain supported for compatibility:
```wrn ```wrn
<FeatureList items='{items}' primaryAction='{{ label: "Open" }}' /> state items = [{"label":"Accessibility","href":"/accessibility"}]
<FeatureList items={items} />
<FeatureList items={[{"label":"Accessibility","href":"/accessibility"}]} />
<FeatureList primaryAction={{"label":"Open","href":"/open"}} />
<FeatureList items="{items}" />
``` ```
The 0.3 updater only rewrites simple, unambiguous `prop={expression}` values. Nested Expression-valued component props serialize arrays and objects as JSON while preserving
brace expressions are left unchanged and reported for manual review. booleans, numbers, and strings. Literal HTML attributes continue to require quotes.
## File structure ## File structure
@@ -76,9 +82,12 @@ props {
} }
state open: boolean = false state open: boolean = false
state links = [{"label":"Home","href":"/"}]
state options = {"dense":true}
``` ```
A prop without an initializer is required. State always requires an initializer. A prop without an initializer is required. State always requires an initializer.
State initializers are JavaScript expressions, including native arrays and objects.
Typed initializers are validated when their literal type can be determined. Typed initializers are validated when their literal type can be determined.
## Computed values and effects ## Computed values and effects
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "wrnexus", "name": "wrnexus",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.", "description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.",
@@ -25,7 +25,7 @@
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"format": "prettier . --write", "format": "prettier . --write",
"format:check": "prettier . --check", "format:check": "prettier . --check",
"check": "bun run typecheck && bun run lint && bun run test && bun run format:check", "check": "bun run typecheck \u0026\u0026 bun run lint \u0026\u0026 bun run test \u0026\u0026 bun run format:check",
"verify:0.3": "node scripts/verify-0.3.mjs", "verify:0.3": "node scripts/verify-0.3.mjs",
"verify:0.4": "node scripts/verify-0.4.mjs", "verify:0.4": "node scripts/verify-0.4.mjs",
"validate:0.4": "node scripts/validate-0.4.mjs", "validate:0.4": "node scripts/validate-0.4.mjs",
@@ -44,7 +44,7 @@
"typescript-eslint": "latest" "typescript-eslint": "latest"
}, },
"engines": { "engines": {
"bun": ">=1.3.0" "bun": "\u003e=1.3.0"
}, },
"overrides": { "overrides": {
"esbuild": "0.28.1" "esbuild": "0.28.1"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/auth", "name": "@wrnexus/auth",
"version": "0.5.0", "version": "0.5.1",
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.", "description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
@@ -36,7 +36,7 @@
"scripts": { "scripts": {
"test": "bun test", "test": "bun test",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"check": "bun run typecheck && bun run test" "check": "bun run typecheck \u0026\u0026 bun run test"
}, },
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/captcha", "name": "@wrnexus/captcha",
"version": "0.5.0", "version": "0.5.1",
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.", "description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
@@ -38,7 +38,7 @@
"scripts": { "scripts": {
"test": "bun test", "test": "bun test",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"check": "bun run typecheck && bun run test" "check": "bun run typecheck \u0026\u0026 bun run test"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+9
View File
@@ -1094,6 +1094,15 @@ const MIGRATIONS: Migration[] = [
// Package, component, and development-runtime improvements apply automatically. // Package, component, and development-runtime improvements apply automatically.
}, },
}, },
{
version: "0.5.1",
id: "native-wrn-json-expressions",
description:
"Adds native array and object state initializers plus JSX-style expression props in WRN component markup.",
apply() {
// Syntax and compiler support are package-managed; existing applications remain compatible.
},
},
]; ];
/** Release tooling uses this to require an explicit migration entry per version. */ /** Release tooling uses this to require an explicit migration entry per version. */
+2 -2
View File
@@ -164,8 +164,8 @@ A file opens with `page <Name>` or `component <Name>` followed by a `{ ... }` bo
- `types { <TypeScript declarations> }` — reusable interfaces and aliases for the current file. - `types { <TypeScript declarations> }` — reusable interfaces and aliases for the current file.
- `props { name: Type = <default> ... }` — typed component props. Omit `= <default>` to make a prop required. Legacy inferred props remain supported. - `props { name: Type = <default> ... }` — typed component props. Omit `= <default>` to make a prop required. Legacy inferred props remain supported.
- `@event name = function` inside `props` — declares a public component event. Emit it from component behavior with `name(detail)` or `$emit("name", detail)`, and consume it with `<Component @name="handler(event)" />`. - `@event name = function` inside `props` — declares a public component event. Emit it from component behavior with `name(detail)` or `$emit("name", detail)`, and consume it with `<Component @name="handler(event)" />`.
- `state <ident>: Type = <expr>` — typed reactive state seeded from a raw JS expression. The annotation is optional for backward compatibility. - `state <ident>: Type = <expr>` — typed reactive state seeded from a raw JS expression, including native array and object literals. The annotation is optional for backward compatibility.
- `view { <html> }` — plain HTML with `{expr}` interpolation in text and attributes, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and `<!-- comments -->`. Attribute expressions that reference `state` keep an SSR value and update reactively in the browser. - `view { <html> }` — plain HTML with `{expr}` interpolation in text and attributes, JSX-style component props such as `items={items}`, `items={[...]}`, and `options={{...}}`, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and `<!-- comments -->`. Structured component props are serialized safely for SSR; expressions that reference `state` retain their initial value and update reactively in the browser.
- `seo { key = "value" ... }` — metadata merged into the generated `meta`. - `seo { key = "value" ... }` — metadata merged into the generated `meta`.
- `style { <raw css> }` — inlined page/component stylesheet (repeatable). - `style { <raw css> }` — inlined page/component stylesheet (repeatable).
- `functions { <TypeScript> }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code. - `functions { <TypeScript> }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+22
View File
@@ -464,6 +464,28 @@ component ParentCard {
expect(output).toContain("function __wireProp"); expect(output).toContain("function __wireProp");
}); });
test("native array and object props serialize through component mounts", async () => {
const mod = await compileAndImport(`
component NativeProps {
state items = [{"label":"Home","href":"/"}]
view {
<Navbar items={items} options={{"dense":true}} />
<Sidebar items={[{"label":"Cases","href":"/cases"}]} />
}
}
`);
const render = mod.render as (props?: Record<string, unknown>) => string;
const html = render();
expect(html).toContain(
'items="[{&quot;label&quot;:&quot;Home&quot;,&quot;href&quot;:&quot;/&quot;}]"',
);
expect(html).toContain('options="{&quot;dense&quot;:true}"');
expect(html).toContain(
'items="[{&quot;label&quot;:&quot;Cases&quot;,&quot;href&quot;:&quot;/cases&quot;}]"',
);
});
test("parses a layout block", () => { test("parses a layout block", () => {
const ast = parse(` const ast = parse(`
layout AppLayout { layout AppLayout {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
@@ -16,7 +16,7 @@
"scripts": { "scripts": {
"test": "bun test", "test": "bun test",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"check": "bun run typecheck && bun run test" "check": "bun run typecheck \u0026\u0026 bun run test"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "latest", "@types/bun": "latest",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.5.0", "version": "0.5.1",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+27 -4
View File
@@ -712,13 +712,30 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en
while (i < src.length && isWs(src[i]!)) i++; while (i < src.length && isWs(src[i]!)) i++;
}; };
/** Read a `{...}` interpolation (brace-balanced), braces included. */ /** Read a `{...}` interpolation (brace-balanced and quote-aware), braces included. */
const readInterpolation = (): string => { const readInterpolation = (): string => {
const start = i; const start = i;
let depth = 0; let depth = 0;
let quote: string | null = null;
for (; i < src.length; i++) { for (; i < src.length; i++) {
if (src[i] === "{") depth++; const char = src[i]!;
else if (src[i] === "}" && --depth === 0) {
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++; i++;
return src.slice(start, i); return src.slice(start, i);
} }
@@ -819,7 +836,13 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en
if (src[i] === "=") { if (src[i] === "=") {
i++; i++;
skipWs(); skipWs();
attrs.push({ name, value: readQuoted(), event: false }); 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 { } else {
attrs.push({ name, value: "", event: false, boolean: true }); attrs.push({ name, value: "", event: false, boolean: true });
} }
+29
View File
@@ -1,6 +1,35 @@
import { expect, test } from "bun:test"; import { expect, test } from "bun:test";
import { diagnose, formatDiagnostic, parse } from "../src/index.ts"; import { diagnose, formatDiagnostic, parse } from "../src/index.ts";
test("parses native array and object literals in state and component props", () => {
const ast = parse(`
component Navigation {
state items = [{"label":"Home","href":"/"}]
state options = {"dense":true,"nested":{"label":"A {brace}"}}
view {
<Navbar items={items} />
<Sidebar items={[{"label":"Cases","href":"/cases"}]} options={{"dense":true}} />
}
}
`);
expect(ast.states[0]?.expr).toBe('[{"label":"Home","href":"/"}]');
expect(ast.states[1]?.expr).toBe('{"dense":true,"nested":{"label":"A {brace}"}}');
const elements = ast.view.filter((node) => node.type === "element");
const navbar = elements[0];
const sidebar = elements[1];
expect(navbar?.type).toBe("element");
expect(sidebar?.type).toBe("element");
if (navbar?.type !== "element" || sidebar?.type !== "element") return;
expect(navbar.attrs.find((attr) => attr.name === "items")?.value).toBe("{items}");
expect(sidebar.attrs.find((attr) => attr.name === "items")?.value).toBe(
'{[{"label":"Cases","href":"/cases"}]}',
);
expect(sidebar.attrs.find((attr) => attr.name === "options")?.value).toBe('{{"dense":true}}');
});
test("parses WRN 0.3 execution, data, security, and reactivity blocks", () => { test("parses WRN 0.3 execution, data, security, and reactivity blocks", () => {
const ast = parse(`page Dashboard { const ast = parse(`page Dashboard {
runtime = "universal" runtime = "universal"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.5.0", "version": "0.5.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",