From 8fc6f154025c71e5b91ae8e544b6cc7627c0a974 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 29 Jul 2026 16:46:18 +0530 Subject: [PATCH] feat(wrn): support native structured prop expressions --- .publish/ai/package.json | 2 +- .publish/authz/package.json | 2 +- .publish/cli/package.json | 26 ++++++++++----------- .publish/compiler/README.md | 4 ++-- .publish/compiler/package.json | 4 ++-- .publish/core/package.json | 2 +- .publish/csr/package.json | 4 ++-- .publish/db/package.json | 2 +- .publish/dev-server/package.json | 30 ++++++++++++------------ .publish/dev-toolbar/package.json | 2 +- .publish/encryption/package.json | 2 +- .publish/helpers/package.json | 4 ++-- .publish/i18n/package.json | 4 ++-- .publish/jwt/package.json | 2 +- .publish/mobile/package.json | 4 ++-- .publish/native/package.json | 2 +- .publish/oauth/package.json | 2 +- .publish/plugin/package.json | 4 ++-- .publish/pubsub/package.json | 2 +- .publish/queue/package.json | 2 +- .publish/reactive/package.json | 2 +- .publish/router/package.json | 6 ++--- .publish/ssr/package.json | 4 ++-- .publish/styles/package.json | 8 +++---- .publish/syntax/package.json | 2 +- .publish/test/package.json | 2 +- .publish/tracking/package.json | 2 +- .publish/ui/package.json | 4 ++-- .publish/uploader/package.json | 4 ++-- .publish/validation/package.json | 2 +- docs/WRN-LANGUAGE-SPEC-1.0.md | 17 ++++++++++---- package.json | 6 ++--- packages/ai/package.json | 2 +- packages/auth/package.json | 4 ++-- packages/authz/package.json | 2 +- packages/captcha/package.json | 4 ++-- packages/cli/package.json | 2 +- packages/cli/src/update.ts | 9 +++++++ packages/compiler/README.md | 4 ++-- packages/compiler/package.json | 2 +- packages/compiler/test/compiler.test.ts | 22 ++++++++++++++++++ packages/core/package.json | 2 +- packages/csr/package.json | 2 +- packages/db/package.json | 2 +- packages/dev-server/package.json | 2 +- packages/dev-toolbar/package.json | 4 ++-- packages/encryption/package.json | 2 +- packages/helpers/package.json | 2 +- packages/i18n/package.json | 2 +- packages/jwt/package.json | 2 +- packages/mobile/package.json | 2 +- packages/native/package.json | 2 +- packages/oauth/package.json | 2 +- packages/plugin/package.json | 2 +- packages/pubsub/package.json | 2 +- packages/queue/package.json | 2 +- packages/reactive/package.json | 2 +- packages/router/package.json | 2 +- packages/ssr/package.json | 2 +- packages/styles/package.json | 2 +- packages/syntax/package.json | 2 +- packages/syntax/src/parser.ts | 31 +++++++++++++++++++++---- packages/syntax/test/syntax.test.ts | 29 +++++++++++++++++++++++ packages/test/package.json | 2 +- packages/tracking/package.json | 2 +- packages/ui/package.json | 2 +- packages/uploader/package.json | 2 +- packages/validation/package.json | 2 +- 68 files changed, 210 insertions(+), 118 deletions(-) diff --git a/.publish/ai/package.json b/.publish/ai/package.json index a0ed7329..d30609eb 100644 --- a/.publish/ai/package.json +++ b/.publish/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "license": "MIT", diff --git a/.publish/authz/package.json b/.publish/authz/package.json index 9495e31e..fcd87efd 100644 --- a/.publish/authz/package.json +++ b/.publish/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/authz — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/cli/package.json b/.publish/cli/package.json index 91f187b5..21cd4405 100644 --- a/.publish/cli/package.json +++ b/.publish/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/cli — part of the WrNexus framework.", "license": "MIT", @@ -28,18 +28,18 @@ "wrnexus": "./dist/index.js" }, "dependencies": { - "@wrnexus/core": "^0.5.0", - "@wrnexus/router": "^0.5.0", - "@wrnexus/csr": "^0.5.0", - "@wrnexus/compiler": "^0.5.0", - "@wrnexus/styles": "^0.5.0", - "@wrnexus/dev-server": "^0.5.0", - "@wrnexus/ui": "^0.5.0", - "@wrnexus/validation": "^0.5.0", - "@wrnexus/i18n": "^0.5.0", - "@wrnexus/db": "^0.5.0", - "@wrnexus/plugin": "^0.5.0", - "@wrnexus/syntax": "^0.5.0" + "@wrnexus/core": "^0.5.1", + "@wrnexus/router": "^0.5.1", + "@wrnexus/csr": "^0.5.1", + "@wrnexus/compiler": "^0.5.1", + "@wrnexus/styles": "^0.5.1", + "@wrnexus/dev-server": "^0.5.1", + "@wrnexus/ui": "^0.5.1", + "@wrnexus/validation": "^0.5.1", + "@wrnexus/i18n": "^0.5.1", + "@wrnexus/db": "^0.5.1", + "@wrnexus/plugin": "^0.5.1", + "@wrnexus/syntax": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/compiler/README.md b/.publish/compiler/README.md index 141d0023..7baf52ee 100644 --- a/.publish/compiler/README.md +++ b/.publish/compiler/README.md @@ -164,8 +164,8 @@ A file opens with `page ` or `component ` followed by a `{ ... }` bo - `types { }` — reusable interfaces and aliases for the current file. - `props { name: Type = ... }` — typed component props. Omit `= ` 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 ``. -- `state : Type = ` — typed reactive state seeded from a raw JS expression. The annotation is optional for backward compatibility. -- `view { }` — plain HTML with `{expr}` interpolation in text and attributes, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and ``. Attribute expressions that reference `state` keep an SSR value and update reactively in the browser. +- `state : Type = ` — typed reactive state seeded from a raw JS expression, including native array and object literals. The annotation is optional for backward compatibility. +- `view { }` — 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 ``. 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`. - `style { }` — inlined page/component stylesheet (repeatable). - `functions { }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code. diff --git a/.publish/compiler/package.json b/.publish/compiler/package.json index 57e656f7..c9d89e6a 100644 --- a/.publish/compiler/package.json +++ b/.publish/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/compiler — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/syntax": "^0.5.0" + "@wrnexus/syntax": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/core/package.json b/.publish/core/package.json index 2fe60532..52020a47 100644 --- a/.publish/core/package.json +++ b/.publish/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/core — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/csr/package.json b/.publish/csr/package.json index 78ffec1a..71123d31 100644 --- a/.publish/csr/package.json +++ b/.publish/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/csr — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.5.0" + "@wrnexus/core": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/db/package.json b/.publish/db/package.json index 7cfc6812..dea37566 100644 --- a/.publish/db/package.json +++ b/.publish/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/db — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/dev-server/package.json b/.publish/dev-server/package.json index f9ce9850..bcd62ac6 100644 --- a/.publish/dev-server/package.json +++ b/.publish/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/dev-server — part of the WrNexus framework.", "license": "MIT", @@ -25,20 +25,20 @@ } }, "dependencies": { - "@wrnexus/core": "^0.5.0", - "@wrnexus/dev-toolbar": "^0.5.0", - "@wrnexus/router": "^0.5.0", - "@wrnexus/ssr": "^0.5.0", - "@wrnexus/csr": "^0.5.0", - "@wrnexus/compiler": "^0.5.0", - "@wrnexus/styles": "^0.5.0", - "@wrnexus/ui": "^0.5.0", - "@wrnexus/validation": "^0.5.0", - "@wrnexus/i18n": "^0.5.0", - "@wrnexus/db": "^0.5.0", - "@wrnexus/pubsub": "^0.5.0", - "@wrnexus/uploader": "^0.5.0", - "@wrnexus/plugin": "^0.5.0" + "@wrnexus/core": "^0.5.1", + "@wrnexus/dev-toolbar": "^0.5.1", + "@wrnexus/router": "^0.5.1", + "@wrnexus/ssr": "^0.5.1", + "@wrnexus/csr": "^0.5.1", + "@wrnexus/compiler": "^0.5.1", + "@wrnexus/styles": "^0.5.1", + "@wrnexus/ui": "^0.5.1", + "@wrnexus/validation": "^0.5.1", + "@wrnexus/i18n": "^0.5.1", + "@wrnexus/db": "^0.5.1", + "@wrnexus/pubsub": "^0.5.1", + "@wrnexus/uploader": "^0.5.1", + "@wrnexus/plugin": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/dev-toolbar/package.json b/.publish/dev-toolbar/package.json index 6c0a232e..e17f604c 100644 --- a/.publish/dev-toolbar/package.json +++ b/.publish/dev-toolbar/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-toolbar", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/dev-toolbar — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/encryption/package.json b/.publish/encryption/package.json index 14a3b3a0..1e2bd293 100644 --- a/.publish/encryption/package.json +++ b/.publish/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/encryption — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/helpers/package.json b/.publish/helpers/package.json index 340f75e4..5139cd55 100644 --- a/.publish/helpers/package.json +++ b/.publish/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.5.0" + "@wrnexus/core": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/i18n/package.json b/.publish/i18n/package.json index e5c8cf1e..16e5bc43 100644 --- a/.publish/i18n/package.json +++ b/.publish/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/i18n — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.5.0" + "@wrnexus/core": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/jwt/package.json b/.publish/jwt/package.json index 11375c7d..610f5b82 100644 --- a/.publish/jwt/package.json +++ b/.publish/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/jwt — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/mobile/package.json b/.publish/mobile/package.json index 3bed2457..24e704ea 100644 --- a/.publish/mobile/package.json +++ b/.publish/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/mobile — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/native": "^0.5.0" + "@wrnexus/native": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/native/package.json b/.publish/native/package.json index 87feca02..9a1f9d34 100644 --- a/.publish/native/package.json +++ b/.publish/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/native — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/oauth/package.json b/.publish/oauth/package.json index 4ce74cfe..034cedbb 100644 --- a/.publish/oauth/package.json +++ b/.publish/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/oauth — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/plugin/package.json b/.publish/plugin/package.json index 3a8fdb8f..12411b4b 100644 --- a/.publish/plugin/package.json +++ b/.publish/plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/plugin", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/plugin — part of the WrNexus framework.", "license": "MIT", @@ -33,7 +33,7 @@ } }, "dependencies": { - "@wrnexus/syntax": "^0.5.0" + "@wrnexus/syntax": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/pubsub/package.json b/.publish/pubsub/package.json index 62544d9d..176c29dc 100644 --- a/.publish/pubsub/package.json +++ b/.publish/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/pubsub — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/queue/package.json b/.publish/queue/package.json index 1c0e1d41..19c2db64 100644 --- a/.publish/queue/package.json +++ b/.publish/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/queue — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/reactive/package.json b/.publish/reactive/package.json index fde097cf..35d3e818 100644 --- a/.publish/reactive/package.json +++ b/.publish/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/reactive — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/router/package.json b/.publish/router/package.json index 858667e6..074466d0 100644 --- a/.publish/router/package.json +++ b/.publish/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/router — part of the WrNexus framework.", "license": "MIT", @@ -21,8 +21,8 @@ } }, "dependencies": { - "@wrnexus/compiler": "^0.5.0", - "@wrnexus/core": "^0.5.0" + "@wrnexus/compiler": "^0.5.1", + "@wrnexus/core": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/ssr/package.json b/.publish/ssr/package.json index e39102e7..28afcd68 100644 --- a/.publish/ssr/package.json +++ b/.publish/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/ssr — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.5.0" + "@wrnexus/core": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/styles/package.json b/.publish/styles/package.json index 3bcf83a4..fd69119d 100644 --- a/.publish/styles/package.json +++ b/.publish/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/styles — part of the WrNexus framework.", "license": "MIT", @@ -21,9 +21,9 @@ } }, "dependencies": { - "@wrnexus/uploader": "^0.5.0", - "@wrnexus/core": "^0.5.0", - "@wrnexus/plugin": "^0.5.0" + "@wrnexus/uploader": "^0.5.1", + "@wrnexus/core": "^0.5.1", + "@wrnexus/plugin": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/syntax/package.json b/.publish/syntax/package.json index f3595afb..eb8ff6ae 100644 --- a/.publish/syntax/package.json +++ b/.publish/syntax/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/syntax", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/syntax — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/test/package.json b/.publish/test/package.json index db635655..ffd45fd8 100644 --- a/.publish/test/package.json +++ b/.publish/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/test — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/tracking/package.json b/.publish/tracking/package.json index c0ac46c9..a677da9c 100644 --- a/.publish/tracking/package.json +++ b/.publish/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/tracking — part of the WrNexus framework.", "license": "MIT", diff --git a/.publish/ui/package.json b/.publish/ui/package.json index eb4c0344..391a5d98 100644 --- a/.publish/ui/package.json +++ b/.publish/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/ui — part of the WrNexus framework.", "license": "MIT", @@ -26,7 +26,7 @@ "./ui.css": "./ui.css" }, "dependencies": { - "@wrnexus/core": "^0.5.0" + "@wrnexus/core": "^0.5.1" }, "files": [ "dist", diff --git a/.publish/uploader/package.json b/.publish/uploader/package.json index b369ded7..c36983b9 100644 --- a/.publish/uploader/package.json +++ b/.publish/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/uploader — part of the WrNexus framework.", "license": "MIT", @@ -21,7 +21,7 @@ } }, "dependencies": { - "@wrnexus/core": "^0.5.0" + "@wrnexus/core": "^0.5.1" }, "files": [ "dist" diff --git a/.publish/validation/package.json b/.publish/validation/package.json index 6aa564f3..4082e872 100644 --- a/.publish/validation/package.json +++ b/.publish/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "description": "@wrnexus/validation — part of the WrNexus framework.", "license": "MIT", diff --git a/docs/WRN-LANGUAGE-SPEC-1.0.md b/docs/WRN-LANGUAGE-SPEC-1.0.md index 1e352a17..d6be43b4 100644 --- a/docs/WRN-LANGUAGE-SPEC-1.0.md +++ b/docs/WRN-LANGUAGE-SPEC-1.0.md @@ -15,14 +15,20 @@ WRNexusJS 0.3 keeps all previously supported declarations, including: - `data-for`, `{#each}`, conditions, interpolations, events, and `class:*` 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 - +state items = [{"label":"Accessibility","href":"/accessibility"}] + + + + + ``` -The 0.3 updater only rewrites simple, unambiguous `prop={expression}` values. Nested -brace expressions are left unchanged and reported for manual review. +Expression-valued component props serialize arrays and objects as JSON while preserving +booleans, numbers, and strings. Literal HTML attributes continue to require quotes. ## File structure @@ -76,9 +82,12 @@ props { } 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. +State initializers are JavaScript expressions, including native arrays and objects. Typed initializers are validated when their literal type can be determined. ## Computed values and effects diff --git a/package.json b/package.json index ecc00170..6854251d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wrnexus", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "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", "format": "prettier . --write", "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.4": "node scripts/verify-0.4.mjs", "validate:0.4": "node scripts/validate-0.4.mjs", @@ -44,7 +44,7 @@ "typescript-eslint": "latest" }, "engines": { - "bun": ">=1.3.0" + "bun": "\u003e=1.3.0" }, "overrides": { "esbuild": "0.28.1" diff --git a/packages/ai/package.json b/packages/ai/package.json index 349c0fbc..7b419b15 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ai", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", diff --git a/packages/auth/package.json b/packages/auth/package.json index ed29c7e2..fce0aa7c 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "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.", "type": "module", "sideEffects": false, @@ -36,7 +36,7 @@ "scripts": { "test": "bun test", "typecheck": "tsc --noEmit", - "check": "bun run typecheck && bun run test" + "check": "bun run typecheck \u0026\u0026 bun run test" }, "dependencies": { "@wrnexus/authz": "workspace:*", diff --git a/packages/authz/package.json b/packages/authz/package.json index 3a436dcb..ed387181 100644 --- a/packages/authz/package.json +++ b/packages/authz/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/authz", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/captcha/package.json b/packages/captcha/package.json index e520544d..c832861e 100644 --- a/packages/captcha/package.json +++ b/packages/captcha/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/captcha", - "version": "0.5.0", + "version": "0.5.1", "description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.", "type": "module", "sideEffects": false, @@ -38,7 +38,7 @@ "scripts": { "test": "bun test", "typecheck": "tsc --noEmit", - "check": "bun run typecheck && bun run test" + "check": "bun run typecheck \u0026\u0026 bun run test" }, "dependencies": { "@wrnexus/core": "workspace:*", diff --git a/packages/cli/package.json b/packages/cli/package.json index c8ab6927..67c3ad0b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/cli", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/cli/src/update.ts b/packages/cli/src/update.ts index a8429559..8f44dbb5 100644 --- a/packages/cli/src/update.ts +++ b/packages/cli/src/update.ts @@ -1094,6 +1094,15 @@ const MIGRATIONS: Migration[] = [ // 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. */ diff --git a/packages/compiler/README.md b/packages/compiler/README.md index 141d0023..7baf52ee 100644 --- a/packages/compiler/README.md +++ b/packages/compiler/README.md @@ -164,8 +164,8 @@ A file opens with `page ` or `component ` followed by a `{ ... }` bo - `types { }` — reusable interfaces and aliases for the current file. - `props { name: Type = ... }` — typed component props. Omit `= ` 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 ``. -- `state : Type = ` — typed reactive state seeded from a raw JS expression. The annotation is optional for backward compatibility. -- `view { }` — plain HTML with `{expr}` interpolation in text and attributes, hyphenated attributes, boolean attributes, `@event="..."` client bindings, and ``. Attribute expressions that reference `state` keep an SSR value and update reactively in the browser. +- `state : Type = ` — typed reactive state seeded from a raw JS expression, including native array and object literals. The annotation is optional for backward compatibility. +- `view { }` — 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 ``. 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`. - `style { }` — inlined page/component stylesheet (repeatable). - `functions { }` — helpers with typed parameters and return values. Types remain in server output and are safely erased from browser behavior code. diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 229fcf6c..06175d14 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/compiler/test/compiler.test.ts b/packages/compiler/test/compiler.test.ts index 550580b8..b37e307f 100644 --- a/packages/compiler/test/compiler.test.ts +++ b/packages/compiler/test/compiler.test.ts @@ -464,6 +464,28 @@ component ParentCard { 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 { + + + } +} +`); + const render = mod.render as (props?: Record) => string; + const html = render(); + + expect(html).toContain( + 'items="[{"label":"Home","href":"/"}]"', + ); + expect(html).toContain('options="{"dense":true}"'); + expect(html).toContain( + 'items="[{"label":"Cases","href":"/cases"}]"', + ); +}); + test("parses a layout block", () => { const ast = parse(` layout AppLayout { diff --git a/packages/core/package.json b/packages/core/package.json index 2806c1ff..cc3b7396 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/core", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/csr/package.json b/packages/csr/package.json index 04b64e34..ce3a6e54 100644 --- a/packages/csr/package.json +++ b/packages/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/db/package.json b/packages/db/package.json index da9aa0d0..9e45ed3f 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/db", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 2bdbaae4..0f18607d 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/dev-toolbar/package.json b/packages/dev-toolbar/package.json index d8b6a8c6..27bda4aa 100644 --- a/packages/dev-toolbar/package.json +++ b/packages/dev-toolbar/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-toolbar", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "sideEffects": false, @@ -16,7 +16,7 @@ "scripts": { "test": "bun test", "typecheck": "tsc --noEmit", - "check": "bun run typecheck && bun run test" + "check": "bun run typecheck \u0026\u0026 bun run test" }, "devDependencies": { "@types/bun": "latest", diff --git a/packages/encryption/package.json b/packages/encryption/package.json index 323f309a..8ca046f2 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/encryption", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/helpers/package.json b/packages/helpers/package.json index f6e4ee1d..d8ffd825 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/helpers", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index e33cfa0d..5bda8d7e 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/i18n", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/jwt/package.json b/packages/jwt/package.json index b8153849..3728953f 100644 --- a/packages/jwt/package.json +++ b/packages/jwt/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/jwt", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/mobile/package.json b/packages/mobile/package.json index f1ca66f3..dd371439 100644 --- a/packages/mobile/package.json +++ b/packages/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/mobile", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/native/package.json b/packages/native/package.json index 64fda14c..82b90491 100644 --- a/packages/native/package.json +++ b/packages/native/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/native", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/oauth/package.json b/packages/oauth/package.json index 8e16f3f0..2bb2ad58 100644 --- a/packages/oauth/package.json +++ b/packages/oauth/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/oauth", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/plugin/package.json b/packages/plugin/package.json index d3639da2..3dc7a9ef 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/plugin", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/pubsub/package.json b/packages/pubsub/package.json index 4cb68504..b94e1a7c 100644 --- a/packages/pubsub/package.json +++ b/packages/pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/pubsub", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/queue/package.json b/packages/queue/package.json index 7f8df524..01065038 100644 --- a/packages/queue/package.json +++ b/packages/queue/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/queue", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/reactive/package.json b/packages/reactive/package.json index 0f9bb92c..e2d72af3 100644 --- a/packages/reactive/package.json +++ b/packages/reactive/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/reactive", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/router/package.json b/packages/router/package.json index 60a20778..312ef4f5 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/router", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/ssr/package.json b/packages/ssr/package.json index c2670df8..a02e73f5 100644 --- a/packages/ssr/package.json +++ b/packages/ssr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ssr", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/styles/package.json b/packages/styles/package.json index a1aaca6b..85588954 100644 --- a/packages/styles/package.json +++ b/packages/styles/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/styles", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/syntax/package.json b/packages/syntax/package.json index 4fe23de4..2fd0b785 100644 --- a/packages/syntax/package.json +++ b/packages/syntax/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/syntax", - "version": "0.5.0", + "version": "0.5.1", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/syntax/src/parser.ts b/packages/syntax/src/parser.ts index 0d1d072e..4cdeb369 100644 --- a/packages/syntax/src/parser.ts +++ b/packages/syntax/src/parser.ts @@ -712,13 +712,30 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en 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 start = i; let depth = 0; + let quote: string | null = null; for (; i < src.length; i++) { - if (src[i] === "{") depth++; - else if (src[i] === "}" && --depth === 0) { + 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); } @@ -819,7 +836,13 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en if (src[i] === "=") { i++; 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 { attrs.push({ name, value: "", event: false, boolean: true }); } diff --git a/packages/syntax/test/syntax.test.ts b/packages/syntax/test/syntax.test.ts index 37d963c6..4f25bfaa 100644 --- a/packages/syntax/test/syntax.test.ts +++ b/packages/syntax/test/syntax.test.ts @@ -1,6 +1,35 @@ import { expect, test } from "bun:test"; 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 { + + + } +} +`); + + 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", () => { const ast = parse(`page Dashboard { runtime = "universal" diff --git a/packages/test/package.json b/packages/test/package.json index c7b2939b..3ba47518 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/test", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/tracking/package.json b/packages/tracking/package.json index a0758025..3dc056ef 100644 --- a/packages/tracking/package.json +++ b/packages/tracking/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/tracking", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/ui/package.json b/packages/ui/package.json index 3f2f0218..54bd1423 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/ui", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/uploader/package.json b/packages/uploader/package.json index 66902874..14912c94 100644 --- a/packages/uploader/package.json +++ b/packages/uploader/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/uploader", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts", diff --git a/packages/validation/package.json b/packages/validation/package.json index ff668897..4338c40e 100644 --- a/packages/validation/package.json +++ b/packages/validation/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/validation", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "main": "src/index.ts",