Compare commits

...
65 Commits
Author SHA1 Message Date
Clintchiz 84e16ebc41 chore(release): publish ui 0.8.56
Quality / quality (ubuntu-latest) (push) Failing after 9m53s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 19:36:04 +05:30
Clintchiz 7993a303ac fix(ui): preserve viewport anchoring for centered navbar
Quality / quality (ubuntu-latest) (push) Failing after 9m57s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 19:35:43 +05:30
Clintchiz a99290a216 chore(release): publish ui 0.8.55
Quality / quality (ubuntu-latest) (push) Failing after 10m16s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 19:34:17 +05:30
Clintchiz c648efc240 fix(ui): contain long mega menu labels
Quality / quality (ubuntu-latest) (push) Failing after 10m41s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 19:33:59 +05:30
Clintchiz decbee2fd1 chore(release): publish ui 0.8.54
Quality / quality (ubuntu-latest) (push) Failing after 9m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 19:31:38 +05:30
Clintchiz e62e7f5d27 fix(ui): center desktop navbar menus
Quality / quality (ubuntu-latest) (push) Failing after 9m53s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 19:31:17 +05:30
Clintchiz b2c74a3a29 chore(release): publish ui 0.8.53
Quality / quality (ubuntu-latest) (push) Failing after 9m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 17:59:43 +05:30
Clintchiz fe86570f45 fix(ui): render dynamic tab panels and ids
Quality / quality (ubuntu-latest) (push) Failing after 9m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 17:59:10 +05:30
Clintchiz bc1211ea8b chore(release): publish ui 0.8.52
Quality / quality (ubuntu-latest) (push) Failing after 10m0s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 17:37:55 +05:30
Clintchiz d2e24eb089 feat(ui): refine timeline and blockquote layouts
Quality / quality (ubuntu-latest) (push) Failing after 10m2s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 17:37:29 +05:30
Clintchiz e71885d51c chore(release): publish ui 0.8.51
Quality / quality (ubuntu-latest) (push) Failing after 9m56s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 17:31:26 +05:30
Clintchiz 138b5d0950 fix(ui): prevent full bleed hero overflow
Quality / quality (ubuntu-latest) (push) Failing after 10m42s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 17:30:34 +05:30
Clintchiz 7e1eb899b3 fix(ui): align stats surface within max width
Quality / quality (ubuntu-latest) (push) Failing after 9m52s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 16:54:23 +05:30
Clintchiz 3f6612cfe7 fix(ui): honor stats bar desktop gutters
Quality / quality (ubuntu-latest) (push) Failing after 9m51s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 16:50:10 +05:30
Clintchiz 510b3ba777 fix(ui): keep breadcrumb separators visible
Quality / quality (ubuntu-latest) (push) Failing after 10m21s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 16:34:44 +05:30
Clintchiz 1ff2c3339c fix(ui): render active breadcrumb pages
Quality / quality (ubuntu-latest) (push) Failing after 10m0s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 16:21:53 +05:30
Clintchiz 027873459d fix(ui): collapse empty page header slots
Quality / quality (ubuntu-latest) (push) Failing after 9m57s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 15:56:57 +05:30
Clintchiz d0bccfe37e fix(csr): suppress inactive theme token warnings
Quality / quality (ubuntu-latest) (push) Failing after 9m53s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 15:48:48 +05:30
Clintchiz 120889060a fix(ui): normalize form field label sizing
Quality / quality (ubuntu-latest) (push) Failing after 9m53s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 15:25:24 +05:30
Clintchiz 26ec18c24b chore(editor): release vscode extension 0.8.11
Quality / quality (ubuntu-latest) (push) Failing after 9m51s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 15:22:15 +05:30
Clintchiz 7658c5d499 fix(editor): type nested attribute expressions
Quality / quality (ubuntu-latest) (push) Failing after 9m52s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 15:16:38 +05:30
Clintchiz 3cb6ba1233 fix(ui): render navbar brand prop outside slot
Quality / quality (ubuntu-latest) (push) Failing after 10m32s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 14:00:46 +05:30
Clintchiz 64db7da08d fix(ui): refine responsive catalog navigation
Quality / quality (ubuntu-latest) (push) Failing after 9m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-25 13:46:19 +05:30
Clintchiz 9f6552ea62 fix(ui): separate catalog icon masks from tiles
Quality / quality (ubuntu-latest) (push) Failing after 9m52s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 23:30:33 +05:30
Clintchiz 5bc391e74f fix(ui): inherit catalog icon contrast
Quality / quality (ubuntu-latest) (push) Failing after 22s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 23:23:43 +05:30
Clintchiz 3327ec0d37 refactor(ui): clarify catalog navigation hierarchy
Quality / quality (ubuntu-latest) (push) Failing after 10m18s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 23:16:22 +05:30
Clintchiz ceb865f4f1 fix(ui): prevent navigation surface overflow
Quality / quality (ubuntu-latest) (push) Failing after 9m53s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 23:08:00 +05:30
Clintchiz 45309b3f72 refactor(ui): compact mega menu layouts
Quality / quality (ubuntu-latest) (push) Failing after 9m53s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 23:06:01 +05:30
Clintchiz 8f3a13c854 fix(ui): render only supplied mega menu metadata
Quality / quality (ubuntu-latest) (push) Failing after 9m55s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 22:57:13 +05:30
Clintchiz eca4447dcf fix(ui): stabilize catalog mega menu interaction
Quality / quality (ubuntu-latest) (push) Failing after 9m52s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 22:50:41 +05:30
Clintchiz 904bca21b2 refactor(ui): refine catalog mega menu navigation
Quality / quality (ubuntu-latest) (push) Failing after 10m52s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 22:40:58 +05:30
Clintchiz b98d417185 fix(ui): eliminate mega menu flash and lag
Quality / quality (ubuntu-latest) (push) Failing after 9m55s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 22:33:02 +05:30
Clintchiz 7d737ec0a0 fix(ui): make catalog mega menus interactive
Quality / quality (ubuntu-latest) (push) Failing after 6m15s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 22:18:30 +05:30
Clintchiz f5538ed621 fix(ui): keep mega menu open across hover gap
Quality / quality (ubuntu-latest) (push) Failing after 9m51s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 22:08:46 +05:30
Clintchiz d2d830e7e2 fix(ui): pin navbar mega panels to viewport
Quality / quality (ubuntu-latest) (push) Failing after 12m5s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 21:56:27 +05:30
Clintchiz 0618782355 fix(ui): make mega menus work under strict CSP
Quality / quality (ubuntu-latest) (push) Failing after 9m55s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 21:52:25 +05:30
Clintchiz 9067e68f69 fix(ui): import navbar mega menu dependency
Quality / quality (ubuntu-latest) (push) Failing after 10m39s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 21:37:26 +05:30
Clintchiz 9d64ec60c4 feat(ui): add typed navbar menu items
Quality / quality (ubuntu-latest) (push) Failing after 11m12s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 21:36:46 +05:30
Clintchiz fc1eea3939 chore(editor): refresh language server bundle hash
Quality / quality (ubuntu-latest) (push) Failing after 10m8s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 21:25:53 +05:30
Clintchiz 4ec5dad60a fix(formatter): expand large structured prop defaults
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 21:25:07 +05:30
Clintchiz a1a2947910 feat(ui): expand mega menu layout system
Quality / quality (ubuntu-latest) (push) Failing after 11m2s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 21:15:50 +05:30
Clintchiz 270040fb5f feat(ui): add in-flow floating navbar
Quality / quality (ubuntu-latest) (push) Failing after 9m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 19:47:47 +05:30
Clintchiz f9e285b182 feat(ui): own responsive marketing layout APIs
Quality / quality (ubuntu-latest) (push) Failing after 10m42s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 19:44:26 +05:30
Clintchiz 3b402b4e36 fix(ui): collapse empty navbar topbar and add CTA spacing
Quality / quality (ubuntu-latest) (push) Failing after 11m48s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 18:28:39 +05:30
Clintchiz fa535575c5 fix(cli): resolve package updates on Windows
Quality / quality (ubuntu-latest) (push) Failing after 9m56s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 17:23:35 +05:30
Clintchiz 46ef4b775b fix(ui): harden public page components
Quality / quality (ubuntu-latest) (push) Failing after 10m19s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 17:13:14 +05:30
Clintchiz 7a36c1905f fix(cli): handle version flags before workspace discovery
Quality / quality (ubuntu-latest) (push) Failing after 9m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 15:03:38 +05:30
Clintchiz 1a8c75a958 chore: refresh editor language server bundle 2026-08-24 14:58:03 +05:30
Clintchiz b3c93e9b18 test: harden package boundaries and audit budgets
Quality / quality (ubuntu-latest) (push) Failing after 9m52s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 12:05:20 +05:30
Clintchiz 613ff7ae5b fix: invoke npm reliably from Bun on Windows
Quality / quality (ubuntu-latest) (push) Failing after 9m51s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 11:37:06 +05:30
Clintchiz e372ae571a chore: harden release checks and package coverage
Quality / quality (ubuntu-latest) (push) Failing after 9m57s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-24 11:36:13 +05:30
Clintchiz 354082ebc3 fix: recognize globally registered components
Quality / quality (ubuntu-latest) (push) Failing after 10m22s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 23:03:45 +05:30
Clintchiz aa21bc4a18 chore: release cli with fixed dev runtime
Quality / quality (ubuntu-latest) (push) Failing after 11m3s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 22:58:09 +05:30
Clintchiz 1be482caab fix: defer reactive props in branch pre-rendering
Quality / quality (ubuntu-latest) (push) Failing after 10m4s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 22:51:25 +05:30
Clintchiz 2d1cda0eab chore: restore production release gate 2026-08-23 22:35:11 +05:30
Clintchiz d26403fce2 chore: update public API baseline 2026-08-23 22:24:51 +05:30
Clintchiz f52e1d50e4 feat: centralize mail credential and sandbox policy
Quality / quality (ubuntu-latest) (push) Failing after 9m52s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 21:51:37 +05:30
Clintchiz fe44bc2091 fix: make payment event reduction monotonic
Quality / quality (ubuntu-latest) (push) Failing after 9m53s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 21:34:59 +05:30
Clintchiz 98e0813061 feat: add gateway-neutral payment package
Quality / quality (ubuntu-latest) (push) Failing after 9m55s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 21:05:27 +05:30
Clintchiz a9670c2a1c fix: close durable queue and runtime gaps
Quality / quality (ubuntu-latest) (push) Failing after 9m54s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 20:47:00 +05:30
Clintchiz 1a94179b5f fix: synchronize forwarded component props
Quality / quality (ubuntu-latest) (push) Failing after 10m57s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 20:20:43 +05:30
Clintchiz f13e83fd74 fix: stage CLI release dependencies
Quality / quality (ubuntu-latest) (push) Failing after 9m52s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 20:09:26 +05:30
Clintchiz 7351ed1566 chore: release synchronized client runtime
Quality / quality (ubuntu-latest) (push) Failing after 10m49s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 20:07:18 +05:30
Clintchiz 53b9947ef9 fix: synchronize mounted component props
Quality / quality (ubuntu-latest) (push) Failing after 9m51s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 19:42:39 +05:30
Clintchiz 56cde5aaf8 fix(authz): reserve role inheritance namespace
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
2026-08-23 19:35:03 +05:30
143 changed files with 6481 additions and 571 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:
bun-version: 1.3.14 bun-version: 1.4.0
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 24
+114 -100
View File
@@ -9,7 +9,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@types/bun": "^1.3.14", "@types/bun": "^1.4.0",
"@types/react": "^19.2.18", "@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4", "@types/react-dom": "^19.2.4",
"eslint": "^10.8.1", "eslint": "^10.8.1",
@@ -133,7 +133,7 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"@wrnexus/cli": "^0.8.35", "@wrnexus/cli": "^0.8.36",
"eslint": "^10.8.1", "eslint": "^10.8.1",
"prettier": "^3.9.6", "prettier": "^3.9.6",
"typescript": "^6.0.3", "typescript": "^6.0.3",
@@ -145,27 +145,27 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@wrnexus/ai": "^0.8.9", "@wrnexus/ai": "^0.8.9",
"@wrnexus/auth": "^0.8.11", "@wrnexus/auth": "^0.8.12",
"@wrnexus/authz": "^0.8.9", "@wrnexus/authz": "^0.8.9",
"@wrnexus/captcha": "^0.8.10", "@wrnexus/captcha": "^0.8.11",
"@wrnexus/core": "^0.8.9", "@wrnexus/core": "^0.8.9",
"@wrnexus/csr": "^0.8.21", "@wrnexus/csr": "^0.8.21",
"@wrnexus/db": "^0.8.14", "@wrnexus/db": "^0.8.15",
"@wrnexus/dev-server": "^0.8.32", "@wrnexus/dev-server": "^0.8.33",
"@wrnexus/encryption": "^0.8.8", "@wrnexus/encryption": "^0.8.9",
"@wrnexus/helpers": "^0.8.8", "@wrnexus/helpers": "^0.8.8",
"@wrnexus/i18n": "^0.8.10", "@wrnexus/i18n": "^0.8.11",
"@wrnexus/image": "^0.8.9", "@wrnexus/image": "^0.8.10",
"@wrnexus/jwt": "^0.8.8", "@wrnexus/jwt": "^0.8.9",
"@wrnexus/observability": "^0.8.8", "@wrnexus/observability": "^0.8.8",
"@wrnexus/realtime": "^0.8.9", "@wrnexus/realtime": "^0.8.10",
"@wrnexus/security": "^0.8.8", "@wrnexus/security": "^0.8.8",
"@wrnexus/store": "^0.8.8", "@wrnexus/store": "^0.8.8",
"@wrnexus/styles": "^0.8.15", "@wrnexus/styles": "^0.8.15",
"@wrnexus/tracking": "^0.8.8", "@wrnexus/tracking": "^0.8.8",
"@wrnexus/ui": "^0.8.19", "@wrnexus/ui": "^0.8.19",
"@wrnexus/uploader": "^0.8.9", "@wrnexus/uploader": "^0.8.10",
"@wrnexus/validation": "^0.8.9", "@wrnexus/validation": "^0.8.10",
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
@@ -173,7 +173,7 @@
"@iconify/tailwind4": "^1.2.3", "@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.3.3", "@tailwindcss/cli": "^4.3.3",
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"@wrnexus/cli": "^0.8.35", "@wrnexus/cli": "^0.8.36",
"eslint": "^10.8.1", "eslint": "^10.8.1",
"prettier": "^3.9.6", "prettier": "^3.9.6",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",
@@ -186,27 +186,27 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@wrnexus/ai": "^0.8.9", "@wrnexus/ai": "^0.8.9",
"@wrnexus/auth": "^0.8.11", "@wrnexus/auth": "^0.8.12",
"@wrnexus/authz": "^0.8.9", "@wrnexus/authz": "^0.8.9",
"@wrnexus/captcha": "^0.8.10", "@wrnexus/captcha": "^0.8.11",
"@wrnexus/core": "^0.8.9", "@wrnexus/core": "^0.8.9",
"@wrnexus/csr": "^0.8.21", "@wrnexus/csr": "^0.8.21",
"@wrnexus/db": "^0.8.14", "@wrnexus/db": "^0.8.15",
"@wrnexus/dev-server": "^0.8.32", "@wrnexus/dev-server": "^0.8.33",
"@wrnexus/encryption": "^0.8.8", "@wrnexus/encryption": "^0.8.9",
"@wrnexus/helpers": "^0.8.8", "@wrnexus/helpers": "^0.8.8",
"@wrnexus/i18n": "^0.8.10", "@wrnexus/i18n": "^0.8.11",
"@wrnexus/image": "^0.8.9", "@wrnexus/image": "^0.8.10",
"@wrnexus/jwt": "^0.8.8", "@wrnexus/jwt": "^0.8.9",
"@wrnexus/observability": "^0.8.8", "@wrnexus/observability": "^0.8.8",
"@wrnexus/realtime": "^0.8.9", "@wrnexus/realtime": "^0.8.10",
"@wrnexus/security": "^0.8.8", "@wrnexus/security": "^0.8.8",
"@wrnexus/store": "^0.8.8", "@wrnexus/store": "^0.8.8",
"@wrnexus/styles": "^0.8.15", "@wrnexus/styles": "^0.8.15",
"@wrnexus/tracking": "^0.8.8", "@wrnexus/tracking": "^0.8.8",
"@wrnexus/ui": "^0.8.19", "@wrnexus/ui": "^0.8.19",
"@wrnexus/uploader": "^0.8.9", "@wrnexus/uploader": "^0.8.10",
"@wrnexus/validation": "^0.8.9", "@wrnexus/validation": "^0.8.10",
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
@@ -214,7 +214,7 @@
"@iconify/tailwind4": "^1.2.3", "@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.3.3", "@tailwindcss/cli": "^4.3.3",
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"@wrnexus/cli": "^0.8.35", "@wrnexus/cli": "^0.8.36",
"eslint": "^10.8.1", "eslint": "^10.8.1",
"prettier": "^3.9.6", "prettier": "^3.9.6",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",
@@ -257,7 +257,7 @@
}, },
"packages/authz": { "packages/authz": {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.14", "version": "0.8.15",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/db": "workspace:*", "@wrnexus/db": "workspace:*",
@@ -277,7 +277,7 @@
}, },
"packages/captcha": { "packages/captcha": {
"name": "@wrnexus/captcha", "name": "@wrnexus/captcha",
"version": "0.8.11", "version": "0.8.14",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -292,7 +292,7 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.57", "version": "0.8.64",
"bin": { "bin": {
"wrnexus": "src/index.ts", "wrnexus": "src/index.ts",
}, },
@@ -320,7 +320,7 @@
}, },
"packages/compiler": { "packages/compiler": {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.19", "version": "0.8.22",
"dependencies": { "dependencies": {
"@wrnexus/csr": "workspace:*", "@wrnexus/csr": "workspace:*",
"@wrnexus/store": "workspace:*", "@wrnexus/store": "workspace:*",
@@ -342,7 +342,7 @@
}, },
"packages/csr": { "packages/csr": {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.32", "version": "0.8.35",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
@@ -357,7 +357,7 @@
}, },
"packages/dev-server": { "packages/dev-server": {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.50", "version": "0.8.56",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
"@wrnexus/cache": "workspace:*", "@wrnexus/cache": "workspace:*",
@@ -372,6 +372,7 @@
"@wrnexus/pubsub": "workspace:*", "@wrnexus/pubsub": "workspace:*",
"@wrnexus/pwa": "workspace:*", "@wrnexus/pwa": "workspace:*",
"@wrnexus/queue": "workspace:*", "@wrnexus/queue": "workspace:*",
"@wrnexus/react": "workspace:*",
"@wrnexus/router": "workspace:*", "@wrnexus/router": "workspace:*",
"@wrnexus/rpc": "workspace:*", "@wrnexus/rpc": "workspace:*",
"@wrnexus/security": "workspace:*", "@wrnexus/security": "workspace:*",
@@ -385,7 +386,7 @@
}, },
"packages/dev-toolbar": { "packages/dev-toolbar": {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.13", "version": "0.8.16",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^6.0.3", "typescript": "^6.0.3",
@@ -487,8 +488,9 @@
}, },
"packages/mail": { "packages/mail": {
"name": "@wrnexus/mail", "name": "@wrnexus/mail",
"version": "0.8.1", "version": "0.8.3",
"dependencies": { "dependencies": {
"@wrnexus/encryption": "workspace:*",
"@wrnexus/queue": "workspace:*", "@wrnexus/queue": "workspace:*",
}, },
}, },
@@ -528,6 +530,14 @@
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/payment": {
"name": "@wrnexus/payment",
"version": "0.8.1",
"dependencies": {
"@wrnexus/db": "workspace:*",
"@wrnexus/plugin": "workspace:*",
},
},
"packages/playground": { "packages/playground": {
"name": "@wrnexus/playground", "name": "@wrnexus/playground",
"version": "0.8.8", "version": "0.8.8",
@@ -561,7 +571,7 @@
}, },
"packages/queue": { "packages/queue": {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.13", "version": "0.8.14",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/db": "workspace:*", "@wrnexus/db": "workspace:*",
@@ -645,11 +655,11 @@
}, },
"packages/store": { "packages/store": {
"name": "@wrnexus/store", "name": "@wrnexus/store",
"version": "0.8.9", "version": "0.8.12",
}, },
"packages/styles": { "packages/styles": {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.19", "version": "0.8.20",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -658,11 +668,11 @@
}, },
"packages/syntax": { "packages/syntax": {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.13", "version": "0.8.18",
}, },
"packages/test": { "packages/test": {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.11", "version": "0.8.12",
}, },
"packages/tracking": { "packages/tracking": {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
@@ -670,7 +680,7 @@
}, },
"packages/typecheck": { "packages/typecheck": {
"name": "@wrnexus/typecheck", "name": "@wrnexus/typecheck",
"version": "0.8.13", "version": "0.8.16",
"dependencies": { "dependencies": {
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
"typescript": "^6.0.3", "typescript": "^6.0.3",
@@ -678,14 +688,14 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.8.23", "version": "0.8.56",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/uploader": { "packages/uploader": {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.8.10", "version": "0.8.13",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -786,7 +796,7 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
@@ -812,7 +822,7 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@iconify-json/lucide": ["@iconify-json/lucide@1.2.123", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-0CozmpKXEOEEhltrfT2zt+/t1hez67Y+hM2VJWoIcBKdBrb83VYuKf+b5A0QU9HfQm+RNn2GjFWlTOwi+Ss6IA=="], "@iconify-json/lucide": ["@iconify-json/lucide@1.2.125", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-tOCk1QKMtKnCfPAgZRHgjRkQTP7wF5IO+iPKvvp8vxGZYPkSLhx4HTV3Ng0pIZ3wNWrS6kVpHkunJ1dc19L1og=="],
"@iconify/tailwind4": ["@iconify/tailwind4@1.2.3", "", { "dependencies": { "@iconify/tools": "^5.0.5", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.0" }, "peerDependencies": { "tailwindcss": ">= 4.0.0" } }, "sha512-z8SKiMHRASJKF/IY//87MF88lcB7ulxh8vlhQXXLWsBkNtOh6ese9R41MyGpQeqXdRvQVt+/fX2glQtHFjQ+MA=="], "@iconify/tailwind4": ["@iconify/tailwind4@1.2.3", "", { "dependencies": { "@iconify/tools": "^5.0.5", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.0" }, "peerDependencies": { "tailwindcss": ">= 4.0.0" } }, "sha512-z8SKiMHRASJKF/IY//87MF88lcB7ulxh8vlhQXXLWsBkNtOh6ese9R41MyGpQeqXdRvQVt+/fX2glQtHFjQ+MA=="],
@@ -832,6 +842,8 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="],
"@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], "@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="],
"@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
@@ -862,79 +874,79 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="], "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
"@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "@peculiar/asn1-x509-attr": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA=="], "@peculiar/asn1-cms": ["@peculiar/asn1-cms@2.9.4", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.4", "@peculiar/asn1-x509": "^2.9.4", "@peculiar/asn1-x509-attr": "^2.9.4", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-cben7oxmQsUGZqotus7yt0srYdncOT6RNWcTQ77T2RFOXejYVYkXadrfePdRcrVpO9K95IRLKKglG2k38jKXuw=="],
"@peculiar/asn1-csr": ["@peculiar/asn1-csr@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg=="], "@peculiar/asn1-csr": ["@peculiar/asn1-csr@2.9.4", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.4", "@peculiar/asn1-x509": "^2.9.4", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-xd4YN4vpRjkDAQWVfZZkeu12IEND7DOpkqaHSIHxZl1uggUNa9Ju0QxY2jHvDAS9pP0zhRBytg8ifsnGo3V0jw=="],
"@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ=="], "@peculiar/asn1-ecc": ["@peculiar/asn1-ecc@2.9.4", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.4", "@peculiar/asn1-x509": "^2.9.4", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-JJXefFshRAuVAjWQo/39bkg1ywc1VaiO44S8RRC+Ykvf/u2KDmYffoDb0ZBPCR5uJy4AGKQhl8mX+Q8ShcWaXQ=="],
"@peculiar/asn1-pfx": ["@peculiar/asn1-pfx@2.8.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.8.0", "@peculiar/asn1-pkcs8": "^2.8.0", "@peculiar/asn1-rsa": "^2.8.0", "@peculiar/asn1-schema": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg=="], "@peculiar/asn1-pfx": ["@peculiar/asn1-pfx@2.9.4", "", { "dependencies": { "@peculiar/asn1-cms": "^2.9.4", "@peculiar/asn1-pkcs8": "^2.9.4", "@peculiar/asn1-rsa": "^2.9.4", "@peculiar/asn1-schema": "^2.9.4", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-khuGzHTzNzk4GDlIBEILyIs6Lce0yn0ZBdoI9v93kmNncfZRhD+AQ5ODFqdhvoE8cMJF/JMTQ8yA+t1D14kqCw=="],
"@peculiar/asn1-pkcs8": ["@peculiar/asn1-pkcs8@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA=="], "@peculiar/asn1-pkcs8": ["@peculiar/asn1-pkcs8@2.9.4", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.4", "@peculiar/asn1-x509": "^2.9.4", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-duRdotlUx9eDZe6QrQpQKl61RbWykCHBCkKayP8V8XdEFwlKHZ8qGGDMyS6Pye7OX7nLFttTTpRkJeet78ckwQ=="],
"@peculiar/asn1-pkcs9": ["@peculiar/asn1-pkcs9@2.8.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.8.0", "@peculiar/asn1-pfx": "^2.8.0", "@peculiar/asn1-pkcs8": "^2.8.0", "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "@peculiar/asn1-x509-attr": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ=="], "@peculiar/asn1-pkcs9": ["@peculiar/asn1-pkcs9@2.9.4", "", { "dependencies": { "@peculiar/asn1-cms": "^2.9.4", "@peculiar/asn1-pfx": "^2.9.4", "@peculiar/asn1-pkcs8": "^2.9.4", "@peculiar/asn1-schema": "^2.9.4", "@peculiar/asn1-x509": "^2.9.4", "@peculiar/asn1-x509-attr": "^2.9.4", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-kaL4cNxBpdQE2dKlyZBqz4ygCrwffO+8wfoxTEqM1Z8RadvCeELBRzcv0dzM8aY9azHMwODO5nxU65zXmhToOQ=="],
"@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg=="], "@peculiar/asn1-rsa": ["@peculiar/asn1-rsa@2.9.4", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.4", "@peculiar/asn1-x509": "^2.9.4", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-pZ96eD1PptovcWQ/GSmuNFXd/7EQJNlKfDaNCyE2rx3W0v6QFelkzquVqRSRyyDXXCYD69ZXJDzZ8GhIiQzKoA=="],
"@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.8.0", "", { "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q=="], "@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.9.4", "", { "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg=="],
"@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg=="], "@peculiar/asn1-x509": ["@peculiar/asn1-x509@2.9.4", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.4", "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-CxhBo/RdEbMMob7T31ZdQjGuoyRFLVwrDzTn25bihzBasRg9kRm/0IxIPvhgQtcK/9dNcO1XQL2fuPugwELL0Q=="],
"@peculiar/asn1-x509-attr": ["@peculiar/asn1-x509-attr@2.8.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.8.0", "@peculiar/asn1-x509": "^2.8.0", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA=="], "@peculiar/asn1-x509-attr": ["@peculiar/asn1-x509-attr@2.9.4", "", { "dependencies": { "@peculiar/asn1-schema": "^2.9.4", "@peculiar/asn1-x509": "^2.9.4", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-ehQXbpQaQYycgu8OrvigwSPTFfVRcu0ECNYCWw+yzBp02Lw5paRqzzhUpfOgO2K38+WfFZuEz/0RPtam5g0OMg=="],
"@peculiar/utils": ["@peculiar/utils@2.0.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ=="], "@peculiar/utils": ["@peculiar/utils@2.0.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ=="],
"@peculiar/x509": ["@peculiar/x509@1.14.3", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-csr": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.0", "@peculiar/asn1-pkcs9": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA=="], "@peculiar/x509": ["@peculiar/x509@1.14.3", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-csr": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.0", "@peculiar/asn1-pkcs9": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.2", "", { "os": "android", "cpu": "arm" }, "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg=="], "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.5", "", { "os": "android", "cpu": "arm" }, "sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.5", "", { "os": "android", "cpu": "arm64" }, "sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A=="], "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA=="], "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw=="], "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg=="], "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg=="], "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.5", "", { "os": "linux", "cpu": "arm" }, "sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.2", "", { "os": "linux", "cpu": "arm" }, "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA=="], "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.5", "", { "os": "linux", "cpu": "arm" }, "sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA=="], "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ=="], "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg=="], "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ=="], "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A=="], "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w=="], "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg=="], "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q=="], "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.5", "", { "os": "linux", "cpu": "none" }, "sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg=="], "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A=="], "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.5", "", { "os": "linux", "cpu": "x64" }, "sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg=="], "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.5", "", { "os": "linux", "cpu": "x64" }, "sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg=="], "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.2", "", { "os": "none", "cpu": "arm64" }, "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA=="], "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.5", "", { "os": "none", "cpu": "arm64" }, "sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg=="], "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q=="], "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg=="], "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.5", "", { "os": "win32", "cpu": "x64" }, "sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA=="], "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.5", "", { "os": "win32", "cpu": "x64" }, "sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg=="],
"@tailwindcss/cli": ["@tailwindcss/cli@4.3.3", "", { "dependencies": { "@parcel/watcher": "2.5.1", "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "enhanced-resolve": "^5.24.1", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.3.3" }, "bin": { "tailwindcss": "./dist/index.mjs" } }, "sha512-ZvS/n1ZHOBKcVlhkt8l5NNr1EDXk1NboYO5CYDOs6NUmvT9z6bzkwsosaJftY57T/3gWNzWMJzIXLodZC8ssdw=="], "@tailwindcss/cli": ["@tailwindcss/cli@4.3.3", "", { "dependencies": { "@parcel/watcher": "2.5.1", "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "enhanced-resolve": "^5.24.1", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.3.3" }, "bin": { "tailwindcss": "./dist/index.mjs" } }, "sha512-ZvS/n1ZHOBKcVlhkt8l5NNr1EDXk1NboYO5CYDOs6NUmvT9z6bzkwsosaJftY57T/3gWNzWMJzIXLodZC8ssdw=="],
@@ -966,7 +978,7 @@
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="], "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="],
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/bun": ["@types/bun@1.4.0", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="],
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
@@ -974,11 +986,11 @@
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@26.0.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw=="], "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="],
"@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="],
"@types/react-dom": ["@types/react-dom@19.2.4", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw=="], "@types/react-dom": ["@types/react-dom@19.2.5", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg=="],
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
@@ -1066,6 +1078,8 @@
"@wrnexus/observability": ["@wrnexus/observability@workspace:packages/observability"], "@wrnexus/observability": ["@wrnexus/observability@workspace:packages/observability"],
"@wrnexus/payment": ["@wrnexus/payment@workspace:packages/payment"],
"@wrnexus/playground": ["@wrnexus/playground@workspace:packages/playground"], "@wrnexus/playground": ["@wrnexus/playground@workspace:packages/playground"],
"@wrnexus/plugin": ["@wrnexus/plugin@workspace:packages/plugin"], "@wrnexus/plugin": ["@wrnexus/plugin@workspace:packages/plugin"],
@@ -1108,7 +1122,7 @@
"@wrnexus/validation": ["@wrnexus/validation@workspace:packages/validation"], "@wrnexus/validation": ["@wrnexus/validation@workspace:packages/validation"],
"acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
@@ -1134,7 +1148,7 @@
"buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="],
"bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="],
@@ -1166,7 +1180,7 @@
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
@@ -1188,7 +1202,7 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@10.8.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ=="], "eslint": ["eslint@10.9.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw=="],
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
@@ -1224,7 +1238,7 @@
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -1232,7 +1246,7 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"happy-dom": ["happy-dom@20.11.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-7MB+bJLkxu3SowAfBJbjW+c55kNz5tkR45gu2qzrxznezhLeN5YIlJbwUgSzlGc+qWoZ8Ykg71H5ezz69xixrw=="], "happy-dom": ["happy-dom@20.11.6", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-Hldbg8AdAa5a5oDcZpjqnGitp7JB0hqWmfv/8qr+kft4vzSD8BHsbdRfzYvL/0QcbKcURC/yyoygbeDQarPvYg=="],
"i18n-showcase": ["i18n-showcase@workspace:examples/i18n-showcase"], "i18n-showcase": ["i18n-showcase@workspace:examples/i18n-showcase"],
@@ -1304,7 +1318,7 @@
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
"mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
@@ -1340,7 +1354,7 @@
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
@@ -1358,7 +1372,7 @@
"pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="],
"pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], "pvutils": ["pvutils@1.2.0", "", {}, "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg=="],
"react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="], "react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
@@ -1370,9 +1384,9 @@
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
"rollup": ["rollup@4.62.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="], "rollup": ["rollup@4.62.5", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.62.5", "@rollup/rollup-android-arm64": "4.62.5", "@rollup/rollup-darwin-arm64": "4.62.5", "@rollup/rollup-darwin-x64": "4.62.5", "@rollup/rollup-freebsd-arm64": "4.62.5", "@rollup/rollup-freebsd-x64": "4.62.5", "@rollup/rollup-linux-arm-gnueabihf": "4.62.5", "@rollup/rollup-linux-arm-musleabihf": "4.62.5", "@rollup/rollup-linux-arm64-gnu": "4.62.5", "@rollup/rollup-linux-arm64-musl": "4.62.5", "@rollup/rollup-linux-loong64-gnu": "4.62.5", "@rollup/rollup-linux-loong64-musl": "4.62.5", "@rollup/rollup-linux-ppc64-gnu": "4.62.5", "@rollup/rollup-linux-ppc64-musl": "4.62.5", "@rollup/rollup-linux-riscv64-gnu": "4.62.5", "@rollup/rollup-linux-riscv64-musl": "4.62.5", "@rollup/rollup-linux-s390x-gnu": "4.62.5", "@rollup/rollup-linux-x64-gnu": "4.62.5", "@rollup/rollup-linux-x64-musl": "4.62.5", "@rollup/rollup-openbsd-x64": "4.62.5", "@rollup/rollup-openharmony-arm64": "4.62.5", "@rollup/rollup-win32-arm64-msvc": "4.62.5", "@rollup/rollup-win32-ia32-msvc": "4.62.5", "@rollup/rollup-win32-x64-gnu": "4.62.5", "@rollup/rollup-win32-x64-msvc": "4.62.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw=="],
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "sax": ["sax@1.6.1", "", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
@@ -1448,27 +1462,27 @@
"wrnexus-crm-example": ["wrnexus-crm-example@workspace:examples/crm-app"], "wrnexus-crm-example": ["wrnexus-crm-example@workspace:examples/crm-app"],
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"@antfu/install-pkg/tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], "@antfu/install-pkg/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" }, "bundled": true }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
+182 -2
View File
@@ -968,6 +968,7 @@
}, },
"@wrnexus/cli": { "@wrnexus/cli": {
".": [], ".": [],
"./test-setup": [],
"./workspace": [ "./workspace": [
"ResolvedWorkspaceApp", "ResolvedWorkspaceApp",
"ResolvedWorkspaceConfig", "ResolvedWorkspaceConfig",
@@ -1352,6 +1353,8 @@
"ActionClientError", "ActionClientError",
"ActionClientOptions", "ActionClientOptions",
"ActionResult", "ActionResult",
"ApiClientOptions",
"ApiRequest",
"ClientModuleScope", "ClientModuleScope",
"HydrationScopeApi", "HydrationScopeApi",
"NAV_RUNTIME", "NAV_RUNTIME",
@@ -1365,6 +1368,7 @@
"callServerFunction", "callServerFunction",
"collectRefs", "collectRefs",
"createActionClient", "createActionClient",
"createApiClient",
"createOutputProxy", "createOutputProxy",
"createServerProxy", "createServerProxy",
"getActionRuntime", "getActionRuntime",
@@ -1375,11 +1379,13 @@
"invalidateClientModule", "invalidateClientModule",
"invokeOutput", "invokeOutput",
"loadClientFunctions", "loadClientFunctions",
"registerOutputHandler" "registerOutputHandler",
"useFetch"
] ]
}, },
"@wrnexus/db": { "@wrnexus/db": {
".": [ ".": [
"AddSeedOptions",
"BaseType", "BaseType",
"Column", "Column",
"ColumnDef", "ColumnDef",
@@ -1390,6 +1396,7 @@
"Dialect", "Dialect",
"Driver", "Driver",
"ExecResult", "ExecResult",
"LedgerOptions",
"Migration", "Migration",
"MigrationRunOptions", "MigrationRunOptions",
"MigrationSafetyIssue", "MigrationSafetyIssue",
@@ -1406,7 +1413,13 @@
"RelationOptions", "RelationOptions",
"Repository", "Repository",
"Row", "Row",
"SeedDatabase",
"SeedRow",
"SeedStep",
"SeedUserAccount",
"TxHandle", "TxHandle",
"UpsertSeedOptions",
"addSeedData",
"analyzeMigrationSafety", "analyzeMigrationSafety",
"analyzeMigrations", "analyzeMigrations",
"appliedMigrations", "appliedMigrations",
@@ -1421,6 +1434,8 @@
"cursorPaginate", "cursorPaginate",
"databaseHealth", "databaseHealth",
"databaseNames", "databaseNames",
"defineLedger",
"defineSeed",
"exists", "exists",
"firstOrThrow", "firstOrThrow",
"generateQueriesFile", "generateQueriesFile",
@@ -1438,15 +1453,20 @@
"queryOperation", "queryOperation",
"registerDb", "registerDb",
"registerLazyDb", "registerLazyDb",
"removeSeedData",
"resetDbPerformanceSnapshot", "resetDbPerformanceSnapshot",
"retryTransaction", "retryTransaction",
"rollback", "rollback",
"runSeedQuery",
"scaffoldMigration", "scaffoldMigration",
"seedIfMissing",
"seedUsers",
"setDb", "setDb",
"softDeleteClause", "softDeleteClause",
"status", "status",
"table", "table",
"tenantScope", "tenantScope",
"upsertSeedData",
"v", "v",
"withTransaction" "withTransaction"
], ],
@@ -1647,6 +1667,7 @@
"EncryptionKey", "EncryptionKey",
"EncryptionKeyring", "EncryptionKeyring",
"ReplayStore", "ReplayStore",
"SecretResourceOptions",
"createEncryptedRequest", "createEncryptedRequest",
"createKeyring", "createKeyring",
"createMemoryReplayStore", "createMemoryReplayStore",
@@ -1654,6 +1675,7 @@
"decryptEncryptedResponse", "decryptEncryptedResponse",
"decryptHttpBody", "decryptHttpBody",
"decryptRequest", "decryptRequest",
"defineSecretResource",
"deriveKey", "deriveKey",
"encrypt", "encrypt",
"encryptHttpBody", "encryptHttpBody",
@@ -1687,26 +1709,39 @@
"@wrnexus/helpers": { "@wrnexus/helpers": {
".": [ ".": [
"AllowedHosts", "AllowedHosts",
"ApiHandler",
"HttpError",
"LoginRedirectOptions", "LoginRedirectOptions",
"OriginalRequestOptions", "OriginalRequestOptions",
"RequestContext", "RequestContext",
"ResourceDefinition",
"ResourceHandlers",
"RetryOptions", "RetryOptions",
"appOrigin", "appOrigin",
"appUrl", "appUrl",
"authorized",
"backoffDelay", "backoffDelay",
"clamp", "clamp",
"currentAppName", "currentAppName",
"currentAppOrigin", "currentAppOrigin",
"defineApiRoute",
"defineResource",
"getOriginalRequestMethod", "getOriginalRequestMethod",
"getOriginalRequestOrigin", "getOriginalRequestOrigin",
"getOriginalRequestPath", "getOriginalRequestPath",
"getOriginalRequestUrl", "getOriginalRequestUrl",
"json",
"once", "once",
"redirectToLogin", "redirectToLogin",
"requireJson",
"requireParam",
"requirePermission",
"requireUser",
"retry", "retry",
"safeJsonParse", "safeJsonParse",
"sleep", "sleep",
"stableStringify", "stableStringify",
"subjectId",
"withTimeout", "withTimeout",
"workspaceAppOrigins", "workspaceAppOrigins",
"workspaceRootDomain" "workspaceRootDomain"
@@ -1902,6 +1937,25 @@
"isApiReferenceAt" "isApiReferenceAt"
] ]
}, },
"@wrnexus/mail": {
".": [
"MailDriver",
"MailMessage",
"MailOptions",
"MailSandboxDecision",
"MailTemplate",
"SealedMailCredentialsOptions",
"SealedMailDriverOptions",
"defineMail",
"defineMailTemplate",
"defineSealedMailCredentials",
"interpolateTemplate",
"mailSandboxDecision",
"queuedMail",
"sealedMailDriver",
"storedMailTemplate"
]
},
"@wrnexus/mcp": { "@wrnexus/mcp": {
".": [ ".": [
"McpServer", "McpServer",
@@ -1913,6 +1967,20 @@
"runMcpStdio" "runMcpStdio"
] ]
}, },
"@wrnexus/metering": {
".": [
"EntitlementPlan",
"EntitlementsOptions",
"MeterKind",
"MeterOptions",
"MeterPack",
"MeterResult",
"MeterStore",
"defineEntitlements",
"defineMeter",
"definePacks"
]
},
"@wrnexus/mobile": { "@wrnexus/mobile": {
".": [ ".": [
"CapacitorBridge", "CapacitorBridge",
@@ -2113,6 +2181,70 @@
"traceMiddleware" "traceMiddleware"
] ]
}, },
"@wrnexus/payment": {
".": [
"CreateIntentInput",
"CustomerInput",
"GatewayCapabilities",
"GatewayContractFixture",
"GatewayContractReport",
"GatewayIntent",
"GatewayMethod",
"GatewayRefund",
"GatewaySupport",
"InitializePaymentInput",
"Money",
"PayPalConfig",
"PaymentEvent",
"PaymentGateway",
"PaymentIntent",
"PaymentMethod",
"PaymentOptions",
"PaymentRefund",
"PaymentResult",
"PaymentService",
"PaymentStatus",
"PaymentStore",
"RazorpayConfig",
"RefundInput",
"SandboxOptions",
"StripeConfig",
"WebhookEvent",
"configurePayment",
"databasePaymentStore",
"defineGateway",
"definePayment",
"derivePaymentStatus",
"exerciseGatewayContract",
"getPaymentService",
"memoryPaymentStore",
"paymentPlugin",
"paypalGateway",
"razorpayGateway",
"sandboxGateway",
"signSandboxWebhook",
"stripeGateway",
"verifyGatewayContract"
],
"./adapters": [
"PayPalConfig",
"RazorpayConfig",
"SandboxOptions",
"StripeConfig",
"paypalGateway",
"razorpayGateway",
"sandboxGateway",
"signSandboxWebhook",
"stripeGateway"
],
"./plugin": [
"default",
"paymentPlugin"
],
"./runtime-webhook": [
"POST"
]
},
"@wrnexus/playground": { "@wrnexus/playground": {
".": [ ".": [
"PlaygroundCompilation", "PlaygroundCompilation",
@@ -2277,21 +2409,41 @@
"@wrnexus/queue": { "@wrnexus/queue": {
".": [ ".": [
"AddOptions", "AddOptions",
"DefineQueueOptions",
"DefinedJob",
"DefinedJobOptions",
"DefinedJobs",
"DefinedQueue",
"DurableQueue", "DurableQueue",
"DurableQueueOptions", "DurableQueueOptions",
"Job", "Job",
"JobContext", "JobContext",
"JobDefinition", "JobDefinition",
"JobHandler", "JobHandler",
"JobStateMachineOptions",
"JobStatus",
"OutboxEntry",
"OutboxWriter",
"POSTGRES_QUEUE_SCHEMA", "POSTGRES_QUEUE_SCHEMA",
"Queue", "Queue",
"QueueAdminOptions",
"QueueDashboardSnapshot", "QueueDashboardSnapshot",
"QueueEvent",
"QueueEventType",
"QueueHealth",
"QueueJobDefinitions",
"QueueJobRecord",
"QueueJobState",
"QueueOptions", "QueueOptions",
"QueueScheduler", "QueueScheduler",
"QueueStorage",
"QueueStorageConfig",
"QueueStore", "QueueStore",
"RedisQueueClient", "RedisQueueClient",
"SagaStep",
"ScheduledJob", "ScheduledJob",
"SqlQueueClient", "SqlQueueClient",
"SqliteQueueClient",
"SubjectJob", "SubjectJob",
"SubjectQueue", "SubjectQueue",
"WorkerDefinition", "WorkerDefinition",
@@ -2303,24 +2455,46 @@
"WorkflowStep", "WorkflowStep",
"WorkflowStore", "WorkflowStore",
"addBatch", "addBatch",
"configureQueueStorage",
"configuredQueueStore",
"createDurableQueue", "createDurableQueue",
"createQueue", "createQueue",
"createQueueAdminHandler",
"createQueueScheduler", "createQueueScheduler",
"createWorkflowEngine", "createWorkflowEngine",
"cronToInterval", "cronToInterval",
"databaseQueueStore",
"defineDurableWorkflow", "defineDurableWorkflow",
"defineJob", "defineJob",
"defineJobStateMachine",
"defineQueue",
"defineSaga",
"defineWorker", "defineWorker",
"defineWorkflow", "defineWorkflow",
"drainOutbox",
"installDatabaseQueueSchema",
"installOutboxSchema",
"installSqliteQueueSchema",
"memoryQueueStore", "memoryQueueStore",
"memoryWorkflowStore", "memoryWorkflowStore",
"postgresQueueStore", "postgresQueueStore",
"queueDashboardSnapshot", "queueDashboardSnapshot",
"queueStorageConfig",
"redisQueueStore", "redisQueueStore",
"renderQueueDashboard", "renderQueueDashboard",
"runQueueDaemon", "runQueueDaemon",
"runWorker", "runWorker",
"subjectQueue" "sqliteQueueSchema",
"sqliteQueueStore",
"subjectQueue",
"testQueue",
"withOutbox"
],
"./sqlite": [
"SqliteQueueClient",
"installSqliteQueueSchema",
"sqliteQueueSchema",
"sqliteQueueStore"
] ]
}, },
"@wrnexus/react": { "@wrnexus/react": {
@@ -2363,6 +2537,7 @@
".": [ ".": [
"AnimationTimeline", "AnimationTimeline",
"Cleanup", "Cleanup",
"FormState",
"HistorySignal", "HistorySignal",
"ReactiveContext", "ReactiveContext",
"ReactiveScope", "ReactiveScope",
@@ -2389,6 +2564,7 @@
"transition", "transition",
"untrack", "untrack",
"urlSignal", "urlSignal",
"useForm",
"watch" "watch"
] ]
}, },
@@ -2916,11 +3092,14 @@
".": [ ".": [
"BrowserArtifactPage", "BrowserArtifactPage",
"Deferred", "Deferred",
"DetectMutationsOptions",
"FetchMock", "FetchMock",
"Harness", "Harness",
"HarnessOptions", "HarnessOptions",
"JsonResponse", "JsonResponse",
"MemoryCookieJar", "MemoryCookieJar",
"MutationCase",
"MutationReport",
"TestRequestOptions", "TestRequestOptions",
"TransactionalDatabase", "TransactionalDatabase",
"WaitForOptions", "WaitForOptions",
@@ -2938,6 +3117,7 @@
"createTestContext", "createTestContext",
"deferred", "deferred",
"describe", "describe",
"detectMutations",
"expect", "expect",
"expectProblem", "expectProblem",
"it", "it",
+30 -30
View File
@@ -4,22 +4,22 @@
"artifacts": { "artifacts": {
"packages/ui/components/Accordion.wrn": "09251f76a2f9385e97eb67e06c0cf868e1771ac5f420647221bec5160287034b", "packages/ui/components/Accordion.wrn": "09251f76a2f9385e97eb67e06c0cf868e1771ac5f420647221bec5160287034b",
"packages/ui/components/AdvancedSelect.wrn": "b952b894a5b35050fc2f6e43c4182101eeee05c75662e2aca4abfe61512b305b", "packages/ui/components/AdvancedSelect.wrn": "b952b894a5b35050fc2f6e43c4182101eeee05c75662e2aca4abfe61512b305b",
"packages/ui/components/AnnouncementBar.wrn": "cb66b8d0619ee798a115b43f0ad93fda93dfb46571589e7a9f0a8d65ec3c84c9", "packages/ui/components/AnnouncementBar.wrn": "5b3219db555e0fcdb94ec41dc18186ee1b7c0cf9c260469f7fb1fc43435c28c4",
"packages/ui/components/AuthForm.wrn": "46a3e1db1c9e69efc52473cbf2fababdcbfe7144e7571469638d2c3addd4d7f0", "packages/ui/components/AuthForm.wrn": "46a3e1db1c9e69efc52473cbf2fababdcbfe7144e7571469638d2c3addd4d7f0",
"packages/ui/components/AuthSplitLayout.wrn": "724938921bc38cc5a0422de69c237b630f099f2df94c66977362b048e4a420a5", "packages/ui/components/AuthSplitLayout.wrn": "724938921bc38cc5a0422de69c237b630f099f2df94c66977362b048e4a420a5",
"packages/ui/components/AvatarGroup.wrn": "30b702e06a1adff8b9bae32820206020e086a69560cbdcbadb19f04f25951f97", "packages/ui/components/AvatarGroup.wrn": "30b702e06a1adff8b9bae32820206020e086a69560cbdcbadb19f04f25951f97",
"packages/ui/components/BackToTop.wrn": "5b760ec7941ffd8e58c5dedae1b322ee46e072e927533d7ec077b511a816e436", "packages/ui/components/BackToTop.wrn": "d4b8d1fd37ff26c89b4276ce54ce6ac9581dc2ff8bbcbe160e072778bba76eab",
"packages/ui/components/Blockquote.wrn": "3761bf13babb6eb07ce7392258ee439ef5478064cf589eff63f945b950e00d2e", "packages/ui/components/Blockquote.wrn": "3761bf13babb6eb07ce7392258ee439ef5478064cf589eff63f945b950e00d2e",
"packages/ui/components/Breadcrumb.wrn": "9f6c23550ebfc635c36ca8660953775170b4a471c0cd0206b9a7ba760994980a", "packages/ui/components/Breadcrumb.wrn": "9f6c23550ebfc635c36ca8660953775170b4a471c0cd0206b9a7ba760994980a",
"packages/ui/components/ButtonGroup.wrn": "fc23565ad56bd8483fcb777d9e02ccba2f9ea476c085754d42503bb83b718f72", "packages/ui/components/ButtonGroup.wrn": "fc23565ad56bd8483fcb777d9e02ccba2f9ea476c085754d42503bb83b718f72",
"packages/ui/components/CTASection.wrn": "d0665f2aa33eea95e6f84120f739b3dd9c8723c882138bcb1a7f80dcd7909904", "packages/ui/components/CTASection.wrn": "f6872b980432d5ee9d88388250be9838ccce51bd706349b283ef472ee692b237",
"packages/ui/components/Card.wrn": "3cc2ff238bda279c169026a462236144ad11ec0046deef00ab00e001a07e8a4c", "packages/ui/components/Card.wrn": "be8cd6a438d1ede4c284a302b76f0fbc235a2423b83a7ced21131763fecc8a90",
"packages/ui/components/Carousel.wrn": "e7d921f802aa19210f3f415b59b70750a2c8d78e30684ad334809741c70a6bf9", "packages/ui/components/Carousel.wrn": "e7d921f802aa19210f3f415b59b70750a2c8d78e30684ad334809741c70a6bf9",
"packages/ui/components/Chart.wrn": "07bc01247b5a1c2b82c2ba8386b7fcdbf480efead75d52b7da04d471072c7ee4", "packages/ui/components/Chart.wrn": "07bc01247b5a1c2b82c2ba8386b7fcdbf480efead75d52b7da04d471072c7ee4",
"packages/ui/components/ChatBubble.wrn": "d71f7d6567d76ac0eb3ecc8e2e567fec12bb3236ea05f4d0a72286abc0b7cbb1", "packages/ui/components/ChatBubble.wrn": "d71f7d6567d76ac0eb3ecc8e2e567fec12bb3236ea05f4d0a72286abc0b7cbb1",
"packages/ui/components/Clipboard.wrn": "5a93f2bae337d7ce364229b796bac673f60dde6dc135f0969f4e106e04854d30", "packages/ui/components/Clipboard.wrn": "5a93f2bae337d7ce364229b796bac673f60dde6dc135f0969f4e106e04854d30",
"packages/ui/components/Collapse.wrn": "26df087a61c82f1aa1749785f4c9be654c4f934859c1a47acfcb8cc67eecc75d", "packages/ui/components/Collapse.wrn": "26df087a61c82f1aa1749785f4c9be654c4f934859c1a47acfcb8cc67eecc75d",
"packages/ui/components/ColorPicker.wrn": "557e36adbdee66d8ba9dcdc2446c13f3385a243f2766777feef8504908006c99", "packages/ui/components/ColorPicker.wrn": "ccac7eb49c0f443b404004f1298fa905fde45c52b51466194920939c920348c9",
"packages/ui/components/Columns.wrn": "8ccd9e53f6799b09f8b996740d12e4e73ee782f6fd3e57595c6c16f788b9b812", "packages/ui/components/Columns.wrn": "8ccd9e53f6799b09f8b996740d12e4e73ee782f6fd3e57595c6c16f788b9b812",
"packages/ui/components/Combobox.wrn": "58488dbb60e41dacbf223afdb4a05abdbec53b7f54cd79a0db659add7057b1c1", "packages/ui/components/Combobox.wrn": "58488dbb60e41dacbf223afdb4a05abdbec53b7f54cd79a0db659add7057b1c1",
"packages/ui/components/Confetti.wrn": "0e81426a3ee8aff672385a5466cf96085c25ba17677a2fef432bcddf13f4d58e", "packages/ui/components/Confetti.wrn": "0e81426a3ee8aff672385a5466cf96085c25ba17677a2fef432bcddf13f4d58e",
@@ -28,7 +28,7 @@
"packages/ui/components/CustomScrollbar.wrn": "2a3a2719580c21823d079c92ebb000c7a5a7d9d16e3fa9275b8fb7d4449cb437", "packages/ui/components/CustomScrollbar.wrn": "2a3a2719580c21823d079c92ebb000c7a5a7d9d16e3fa9275b8fb7d4449cb437",
"packages/ui/components/DataMap.wrn": "338f0d0a3307289dd0387b7fe7ba631fa0b65b6be8c7b9ee3feb2203306f76a3", "packages/ui/components/DataMap.wrn": "338f0d0a3307289dd0387b7fe7ba631fa0b65b6be8c7b9ee3feb2203306f76a3",
"packages/ui/components/DataTable.wrn": "e929fdb4e59613878a03fb6a11e09c42f773c06ca7b20e43ae16a266835f2af4", "packages/ui/components/DataTable.wrn": "e929fdb4e59613878a03fb6a11e09c42f773c06ca7b20e43ae16a266835f2af4",
"packages/ui/components/DatePicker.wrn": "91bcfd3832d3bccc4de5402c0c06209947bde593bad4f869b4c29ee14a5c0bef", "packages/ui/components/DatePicker.wrn": "cefbbb2abb57ad4c57864a0c65589592a20f9ea1695f9fc7693c3135dd82e71a",
"packages/ui/components/DeviceFrame.wrn": "c38d25419ee44b05d7c443cc4e854aeeb7a53a9289e374d71834de2dd2e4d979", "packages/ui/components/DeviceFrame.wrn": "c38d25419ee44b05d7c443cc4e854aeeb7a53a9289e374d71834de2dd2e4d979",
"packages/ui/components/Divider.wrn": "bfa36cf16ccd48c996bb0b08a438f1b281ea1811049ed69fc7680925b1eba019", "packages/ui/components/Divider.wrn": "bfa36cf16ccd48c996bb0b08a438f1b281ea1811049ed69fc7680925b1eba019",
"packages/ui/components/DragAndDrop.wrn": "84305f07d43cdb9eff1f63c19458d88b0aec013424d05a49a2cafbecedc60513", "packages/ui/components/DragAndDrop.wrn": "84305f07d43cdb9eff1f63c19458d88b0aec013424d05a49a2cafbecedc60513",
@@ -36,15 +36,15 @@
"packages/ui/components/Dropdown.wrn": "978367a9dde22448cc664d686e4dd9c579fe8ba0b2cf02d610d95a3501f8dbb0", "packages/ui/components/Dropdown.wrn": "978367a9dde22448cc664d686e4dd9c579fe8ba0b2cf02d610d95a3501f8dbb0",
"packages/ui/components/FeatureCard.wrn": "1bc548c115f924a0c7ade4c557852757083755c60ca0ee747b150a7cca3ebced", "packages/ui/components/FeatureCard.wrn": "1bc548c115f924a0c7ade4c557852757083755c60ca0ee747b150a7cca3ebced",
"packages/ui/components/FeatureGrid.wrn": "15b5d6f9ddb7e3697682e7247fdf3c2dd080b770335f84ba9fab2692166fe897", "packages/ui/components/FeatureGrid.wrn": "15b5d6f9ddb7e3697682e7247fdf3c2dd080b770335f84ba9fab2692166fe897",
"packages/ui/components/FeatureIconCard.wrn": "3b4f3fa62c6729e686886a5b848e26c255766684829cec2b715967b4e73d6f07", "packages/ui/components/FeatureIconCard.wrn": "5f65d64235babc3e653a5695204412202015e6f3ffadca566fc18c7f9ce70428",
"packages/ui/components/FileInput.wrn": "866a292a3280527bf429893469e7c50f7cf38965d8adb37b45a35ede1606d5b8", "packages/ui/components/FileInput.wrn": "611047c362458a4e2513b741cc3ef43fbe54c10ddafc13775e4ff39c9de34931",
"packages/ui/components/FileUploadProgress.wrn": "e5da29c562a521cdd6bb50b8f4d217aa1ab981d7b2a2432af47391472f0a8034", "packages/ui/components/FileUploadProgress.wrn": "ba00557afebf60e6221385d6efcf08635789328deee6e551fc53fbb71e1f413b",
"packages/ui/components/Footer.wrn": "f7c5a77064a09e244f689d42475c4c20143f4c8859cac451c10a50c14652db33", "packages/ui/components/Footer.wrn": "e3515e05ef3ef51e6dad71d67ddfe110c80fdcf69e952aecdc7973e69d4f08c7",
"packages/ui/components/Grid.wrn": "83d4f5f2656d539538723f5791ba3c238901432e9d8c55c74f068d3eb5a74517", "packages/ui/components/Grid.wrn": "83d4f5f2656d539538723f5791ba3c238901432e9d8c55c74f068d3eb5a74517",
"packages/ui/components/Hero.wrn": "345478b212701817ff57060f87982a987baf01a7179c979768e1cd4218b50900", "packages/ui/components/Hero.wrn": "345478b212701817ff57060f87982a987baf01a7179c979768e1cd4218b50900",
"packages/ui/components/HeroActions.wrn": "67cd31400ccb6abdbbf16219267dee946b44b064b79f25276d20ceb7d6e8a790", "packages/ui/components/HeroActions.wrn": "67cd31400ccb6abdbbf16219267dee946b44b064b79f25276d20ceb7d6e8a790",
"packages/ui/components/Image.wrn": "361179c478d7674c3b998891a366d73de9514fc6cc8786440c6c50b9ef065357", "packages/ui/components/Image.wrn": "361179c478d7674c3b998891a366d73de9514fc6cc8786440c6c50b9ef065357",
"packages/ui/components/InputGroup.wrn": "30e9f2e4364542179180b6d6e796002d0ffe1e30305146749874697cde6629ea", "packages/ui/components/InputGroup.wrn": "9e259d15207500d07ff626f09c2e2f5eaf4e471f805a7bdbec154b1a23ce0f8d",
"packages/ui/components/InputNumber.wrn": "069195cc483f3f7d6f305d03fbe5a821906a030376fafb6f0d969e4c95b68fd0", "packages/ui/components/InputNumber.wrn": "069195cc483f3f7d6f305d03fbe5a821906a030376fafb6f0d969e4c95b68fd0",
"packages/ui/components/Kbd.wrn": "6b402cb93f0fa76bb978d1ba483215f70ec7214b1c5e84b568fa3e26e87ed9d9", "packages/ui/components/Kbd.wrn": "6b402cb93f0fa76bb978d1ba483215f70ec7214b1c5e84b568fa3e26e87ed9d9",
"packages/ui/components/LayoutSplitter.wrn": "41d070e801a271552023e4392e4c0f67327c61e2e8eaee5f8b87af254b1d79c1", "packages/ui/components/LayoutSplitter.wrn": "41d070e801a271552023e4392e4c0f67327c61e2e8eaee5f8b87af254b1d79c1",
@@ -55,38 +55,38 @@
"packages/ui/components/Map.wrn": "3e6d5de5a21dd050c5c65950d6fb2f89af4024e1856a48f055b65b88fcd7b890", "packages/ui/components/Map.wrn": "3e6d5de5a21dd050c5c65950d6fb2f89af4024e1856a48f055b65b88fcd7b890",
"packages/ui/components/MarketingSectionHeader.wrn": "39b79499b77dc1c7d34e4e5ecb45a9ddc6182f78c2a2586671c4fa07f44679ad", "packages/ui/components/MarketingSectionHeader.wrn": "39b79499b77dc1c7d34e4e5ecb45a9ddc6182f78c2a2586671c4fa07f44679ad",
"packages/ui/components/Marquee.wrn": "81cd80732be34c8d7e4bf28b00347e1a9273166afdf66589d9f5d5774b69403b", "packages/ui/components/Marquee.wrn": "81cd80732be34c8d7e4bf28b00347e1a9273166afdf66589d9f5d5774b69403b",
"packages/ui/components/MegaMenu.wrn": "6a6db166259f8f2bd7ac8fbb21dba6d915c4b936d87b19805caa78189f54730a", "packages/ui/components/MegaMenu.wrn": "a34a84f31f7a3016cf5a607a22f8f7b2322552c550278f6e8c5e6d6cb18848a8",
"packages/ui/components/MetricCard.wrn": "513a6e99be3d9db46d96af9942b51331a0a048b184603e77e814506f0a52bdc5", "packages/ui/components/MetricCard.wrn": "513a6e99be3d9db46d96af9942b51331a0a048b184603e77e814506f0a52bdc5",
"packages/ui/components/MetricGrid.wrn": "c71e53249835908833055b553e64a8ee55fdf11ac4605436a9581ebb87a0608b", "packages/ui/components/MetricGrid.wrn": "1fcebf51dcbe8520fe16bda904c1ef206c25c3900438d53877492c64ea21a1a5",
"packages/ui/components/Modal.wrn": "7fa4b877772736f29f690e24d8742922b310397ef3083f0b78acb67a9f0d14b2", "packages/ui/components/Modal.wrn": "7fa4b877772736f29f690e24d8742922b310397ef3083f0b78acb67a9f0d14b2",
"packages/ui/components/Nav.wrn": "c45b0ecb42250f4b3ede33c8932a025f789dda9ef48dbf332459ae458163e69a", "packages/ui/components/Nav.wrn": "c45b0ecb42250f4b3ede33c8932a025f789dda9ef48dbf332459ae458163e69a",
"packages/ui/components/Navbar.wrn": "01903230210cd2e5e6d9325a0708f85af9623d4118e3a53f1adb770fd545425d", "packages/ui/components/Navbar.wrn": "b68a824e479ce6957afc3e9710442281cdc1a8d02cd96501fe060271f54368e4",
"packages/ui/components/PageHeader.wrn": "321cde36ce8d521a57901033d46e8b437f42e558cca27f49ca26f7172aff1d54", "packages/ui/components/PageHeader.wrn": "29a93cdca7285548b79ebe5b6421eef6dbb4d5ae8be2508fa2d681e1068ba07f",
"packages/ui/components/Pagination.wrn": "d54226705d4556f76ee5f0d6ae82d101ca6d006397756d547a9aa5954415ba39", "packages/ui/components/Pagination.wrn": "d54226705d4556f76ee5f0d6ae82d101ca6d006397756d547a9aa5954415ba39",
"packages/ui/components/PinInput.wrn": "4dc398456f6392d7925db941debb484c7cb0358ecef527f696fe5ec4800a7602", "packages/ui/components/PinInput.wrn": "4dc398456f6392d7925db941debb484c7cb0358ecef527f696fe5ec4800a7602",
"packages/ui/components/Popover.wrn": "1246b99b4236bc7109a5d1427d013bb07bbea7a2da4f821517d2545ee0b5c019", "packages/ui/components/Popover.wrn": "1246b99b4236bc7109a5d1427d013bb07bbea7a2da4f821517d2545ee0b5c019",
"packages/ui/components/PortalDashboard.wrn": "f04171eb1f8cabef06de3c20e4694b766def3407e1e20d2c9aaebc9bb7da03ee", "packages/ui/components/PortalDashboard.wrn": "f04171eb1f8cabef06de3c20e4694b766def3407e1e20d2c9aaebc9bb7da03ee",
"packages/ui/components/PreferenceSwitcher.wrn": "77229e15449182563ebf49159507139928eb74e799e4438e00e9aa81fd60f982", "packages/ui/components/PreferenceSwitcher.wrn": "519cf911390f529fff3fd16e11fb58e79d618fe851902202dc37552ee1182395",
"packages/ui/components/PublicPageShell.wrn": "3d3f25c03d48f3d3d7e3c567e79cb137c7fce1867e054fd6d440fa1d2e961077", "packages/ui/components/PublicPageShell.wrn": "3d3f25c03d48f3d3d7e3c567e79cb137c7fce1867e054fd6d440fa1d2e961077",
"packages/ui/components/RangeSlider.wrn": "bbf646c5d5a89a62986290db811d77b8f635ebcaea9ca2d334b569aa5edc785b", "packages/ui/components/RangeSlider.wrn": "e8ab41782c9d6e58638f0bfccf6e3172af4ebf552189d2199cc6c96bd1652067",
"packages/ui/components/Rating.wrn": "902b55c64e3798f3097f09b1558d1a2372f66d4f760662de085783108ee04587", "packages/ui/components/Rating.wrn": "902b55c64e3798f3097f09b1558d1a2372f66d4f760662de085783108ee04587",
"packages/ui/components/Scrollspy.wrn": "b9c6400ee92a7c228362610a2051f37bff1d3baba1dbacd66d58397dfda5d856", "packages/ui/components/Scrollspy.wrn": "4b452fcbd524feda9c9f2c81894ee01747a51888e3b86fafb37c5d4d192a3a51",
"packages/ui/components/SearchBox.wrn": "3f88340e0b69c0cd552023c72bd22dc511bc00422d46060097bcf0777e683675", "packages/ui/components/SearchBox.wrn": "3f88340e0b69c0cd552023c72bd22dc511bc00422d46060097bcf0777e683675",
"packages/ui/components/Section.wrn": "45b7a1977c25a6020d5cc8bab2ce9c80730e7ec2d51ad53e8c9f64cd9b5b64cd", "packages/ui/components/Section.wrn": "78da13eb8bc1acafe8e6151cbaeed4baaac2ab860e3209f1d13c467f279d2e68",
"packages/ui/components/SectionHeader.wrn": "aca2df8eb35806096f5b9af466b018e5c22a19422e8665f488380da577a0ceb5", "packages/ui/components/SectionHeader.wrn": "aca2df8eb35806096f5b9af466b018e5c22a19422e8665f488380da577a0ceb5",
"packages/ui/components/Sidebar.wrn": "e0250a973aea589a4d7dcc8b30dd01f22592bf22ba012e54a672b2a2288d8af6", "packages/ui/components/Sidebar.wrn": "e0250a973aea589a4d7dcc8b30dd01f22592bf22ba012e54a672b2a2288d8af6",
"packages/ui/components/SplitHero.wrn": "843387dc7b920b3cf6a1b2b785b8653c281982e524479edc2638a89381ff3577", "packages/ui/components/SplitHero.wrn": "7127cb869d1228522d066e028818dd9e512de8b6dd8f99e3dec500e6da5a3cb0",
"packages/ui/components/StatsBar.wrn": "c273bd6ca417a0a20e5e6f1214414f678741b73bedd885fea1037575fa8c5b95", "packages/ui/components/StatsBar.wrn": "6fabcb4374b1e074f58ebcaba968f235c8a3409762163aea51298e2515a14435",
"packages/ui/components/Stepper.wrn": "7ec0902acd7cb74b44d490215b6421dae9a41731735ad1e9aec17e8bfc3fa142", "packages/ui/components/Stepper.wrn": "7ec0902acd7cb74b44d490215b6421dae9a41731735ad1e9aec17e8bfc3fa142",
"packages/ui/components/StrongPassword.wrn": "407eabcc13affc9323a346ddba8e39c81b885a59ef7043d2bd5567d4b7553282", "packages/ui/components/StrongPassword.wrn": "cc31cb080c4b4116c544b4340b8d740d08fec1b05563372b3280c50759416c87",
"packages/ui/components/StyledIcon.wrn": "0874f9a234b053a7c8b9c6983be5d664453a64c4576dff1a049101e249a2866d", "packages/ui/components/StyledIcon.wrn": "0874f9a234b053a7c8b9c6983be5d664453a64c4576dff1a049101e249a2866d",
"packages/ui/components/Tabs.wrn": "f85214960e62da2fac41228deba511a50c57fd94f2830a8233b75048f6a542e9", "packages/ui/components/Tabs.wrn": "f85214960e62da2fac41228deba511a50c57fd94f2830a8233b75048f6a542e9",
"packages/ui/components/TextLink.wrn": "9d561631c5f65a2bcdc59f5c0b166a5dd794a396f42093f2cd26388386f6f954", "packages/ui/components/TextLink.wrn": "9d561631c5f65a2bcdc59f5c0b166a5dd794a396f42093f2cd26388386f6f954",
"packages/ui/components/TimePicker.wrn": "ec0860a4a82a042a65acf9aa81519a5c815b61e0b8080cf6f1c986d26ce7f0e8", "packages/ui/components/TimePicker.wrn": "33bd7a7069b7abe858cf1617b5b950733b37a8d522469ed673cff00b4dfcad0e",
"packages/ui/components/Timeline.wrn": "fb553f69763840532eef1c0aafa79013f57411dc5335e7cb7e7c20c5a3753008", "packages/ui/components/Timeline.wrn": "fb553f69763840532eef1c0aafa79013f57411dc5335e7cb7e7c20c5a3753008",
"packages/ui/components/Toaster.wrn": "5e4c9b2c1bfd5675a81a32e57684b99393ac7fe45d8420c7409a4e95d47d080c", "packages/ui/components/Toaster.wrn": "5e4c9b2c1bfd5675a81a32e57684b99393ac7fe45d8420c7409a4e95d47d080c",
"packages/ui/components/ToggleCount.wrn": "e577195ee05ddb43cceec47145f6b90c0149f2f17055733d58911fcd02de2c7f", "packages/ui/components/ToggleCount.wrn": "e577195ee05ddb43cceec47145f6b90c0149f2f17055733d58911fcd02de2c7f",
"packages/ui/components/TogglePassword.wrn": "6bd4d9fa82b0c8e80178430d5a42b1b3ec9725fb62be158585f68af9338c901a", "packages/ui/components/TogglePassword.wrn": "caa58479e5c8b1e3e504181a56a7d6bcf71d0a230010733710961c31e1cc58ab",
"packages/ui/components/TreeView.wrn": "f54d8495730203b923629bcfb167d5ca41236af0101ea2723d33354413555b4b", "packages/ui/components/TreeView.wrn": "f54d8495730203b923629bcfb167d5ca41236af0101ea2723d33354413555b4b",
"packages/ui/components/Typography.wrn": "9676af57f2a937b21f45512e0a290b5bee4a16267fbf404850297d29645d88b5", "packages/ui/components/Typography.wrn": "9676af57f2a937b21f45512e0a290b5bee4a16267fbf404850297d29645d88b5",
"packages/ui/components/WysiwygEditor.wrn": "a858ebd4e6e2672463ba57d4d049176ca1cc98dac32ab3df3638d6a9cdcd0f2d", "packages/ui/components/WysiwygEditor.wrn": "a858ebd4e6e2672463ba57d4d049176ca1cc98dac32ab3df3638d6a9cdcd0f2d",
@@ -94,15 +94,15 @@
"packages/ui/components/avatar.wrn": "8bc705e459b13c7f063c57e1506ce540e50fa8ef9020ae85b6e8b6a3579fc5dc", "packages/ui/components/avatar.wrn": "8bc705e459b13c7f063c57e1506ce540e50fa8ef9020ae85b6e8b6a3579fc5dc",
"packages/ui/components/badge.wrn": "e44e33633fb34e897696cd9290f210108e35a3e2dc3b2a4a367411f45c69a1e1", "packages/ui/components/badge.wrn": "e44e33633fb34e897696cd9290f210108e35a3e2dc3b2a4a367411f45c69a1e1",
"packages/ui/components/button.wrn": "65570c58cb2547f1e17cd158eec7cf7b1ffc721e6976c6486c9df564627b1219", "packages/ui/components/button.wrn": "65570c58cb2547f1e17cd158eec7cf7b1ffc721e6976c6486c9df564627b1219",
"packages/ui/components/checkbox.wrn": "18046396b75d0b9c6bb2bdb09dbff1846c84fb07490f390a25ca7b5367fd2ef4", "packages/ui/components/checkbox.wrn": "da735354b57f33946591e1bd018b521ff709bae1325e2317735824673188377a",
"packages/ui/components/input.wrn": "e4c14527f009b610b4aa96d8d7b7d5ac3ca1ffd34237bab348ee9dc4ddc55847", "packages/ui/components/input.wrn": "2f17353cbf0711ca50a935ae03ee9ee53594d7e35eb73f5df573fadb7958fac2",
"packages/ui/components/progress.wrn": "6307a90585197d7aab19a8710b2430f5d4ed27ce77e9b90b1414ea0eed876492", "packages/ui/components/progress.wrn": "6307a90585197d7aab19a8710b2430f5d4ed27ce77e9b90b1414ea0eed876492",
"packages/ui/components/radio.wrn": "09425a78358de5bbd2f47482f313e80065135335b969ce5ccdd5c7cc3ea5232c", "packages/ui/components/radio.wrn": "df8e562324b6d99f5a157b0971d63bd715daa0cf6189e7b0d4df04d6f0411388",
"packages/ui/components/select.wrn": "1d8d47a74d5ab9ed58d0ba11f46910897233b96652d17f5962f5dda60bd4cf8a", "packages/ui/components/select.wrn": "747321d6292b8052e44e8014e87ce25f4b56d8e07f86f1061e0ed0cec7651356",
"packages/ui/components/skeleton.wrn": "4fc5e0846eeefd7830c038e1789be995c4f9d833aa913ff079eb4863baa65648", "packages/ui/components/skeleton.wrn": "4fc5e0846eeefd7830c038e1789be995c4f9d833aa913ff079eb4863baa65648",
"packages/ui/components/spinner.wrn": "2322645da7ef53f7c06035ff071d9a2f6ffe2901ff9338daed84037369367105", "packages/ui/components/spinner.wrn": "2322645da7ef53f7c06035ff071d9a2f6ffe2901ff9338daed84037369367105",
"packages/ui/components/switch.wrn": "874504e4828e9db6a570d78984c4d3a76d0ceb39d70baa877649cb3798c82c85", "packages/ui/components/switch.wrn": "243f521431ffb644089df7f629bace0c07094638dfdd27e75b2017efa9cbba4f",
"packages/ui/components/textarea.wrn": "9870d37664471a103d44435a3977bf40b087a743acd76ee4d77df9b56cccbb2b", "packages/ui/components/textarea.wrn": "aa3a75214e998a0b57c50e416476d6f85fa0313aaf97262f2f9222150c6c9e26",
"packages/ui/components/tooltip.wrn": "f3dfdfa5cd9661fe5e95ef3580eef4c7437c069726421370c2d7328fbe7d840d", "packages/ui/components/tooltip.wrn": "f3dfdfa5cd9661fe5e95ef3580eef4c7437c069726421370c2d7328fbe7d840d",
"packages/ui/styles/SelectStyles.wrn": "074fe0d67de4ef5e9f9cfe879beb72fd5c83352888687d3a1a4b7722c87af3ca", "packages/ui/styles/SelectStyles.wrn": "074fe0d67de4ef5e9f9cfe879beb72fd5c83352888687d3a1a4b7722c87af3ca",
"packages/ui/ui.css": "9ea591404e9cf675bbf1003e15e9fad00327094ff217dc20733f0fc87c0f4a62" "packages/ui/ui.css": "9ea591404e9cf675bbf1003e15e9fad00327094ff217dc20733f0fc87c0f4a62"
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "wrnexus", "name": "wrnexus",
"displayName": "WRNexus Language Support", "displayName": "WRNexus Language Support",
"description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.", "description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.",
"version": "0.8.9", "version": "0.8.11",
"publisher": "wrnexus", "publisher": "wrnexus",
"private": true, "private": true,
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
+130 -20
View File
@@ -1,7 +1,7 @@
"use strict"; "use strict";
// Generated by scripts/build-editor-compiler.mjs. Do not edit directly. // Generated by scripts/build-editor-compiler.mjs. Do not edit directly.
// WRN editor compiler source hash: 1e7593d6b6bd1c9b8439fc34a30f9db9f7ae64f1788365a74115170aa34cd185 // WRN editor compiler source hash: 3d346a264807ba7b107584dc9a7b505b1d14057b2ad1aa85c5ea890f9b09c272
// WRN editor compiler generator hash: a54ca847c758bc98d8e353ad6d70088df31de1820f6cf9d1c3462505f563e6b8 // WRN editor compiler generator hash: f8c1094b47e695151205899de28cd63d574356d26ac49219c4a43db0f2391ef5
// Generated with TypeScript: 6.0.3 // Generated with TypeScript: 6.0.3
const __nodeRequire = require; const __nodeRequire = require;
const __path = __nodeRequire("node:path"); const __path = __nodeRequire("node:path");
@@ -1148,6 +1148,31 @@ function componentEventAttribute(name) {
} }
return `data-wrn-out-${name}`; return `data-wrn-out-${name}`;
} }
/** Expand bind:value/bind:checked into a reactive prop plus assignment handler. */
function expandBindings(attrs) {
const output = [];
for (const attr of attrs) {
if (attr.event || !attr.name.startsWith("bind:")) {
output.push(attr);
continue;
}
const target = attr.name.slice("bind:".length);
if (target !== "value" && target !== "checked") {
throw new Error(`Unknown .wrn binding '${attr.name}'. Use bind:value or bind:checked.`);
}
const expression = wholeAttributeExpression(attr.value) ?? attr.value.trim();
if (!/^[A-Za-z_$][\w$]*$/.test(expression)) {
throw new Error(`${attr.name} requires a writable state name, received '${expression}'.`);
}
output.push({ name: target, value: `{${expression}}`, event: false });
output.push({
name: target === "checked" ? "change" : "input",
value: `${expression} = payload.${target}`,
event: true,
});
}
return output;
}
function reactiveAttrValue(raw, reactive) { function reactiveAttrValue(raw, reactive) {
let found = false; let found = false;
const value = raw.replace(/\{([^{}]+)\}/g, (whole, inner) => { const value = raw.replace(/\{([^{}]+)\}/g, (whole, inner) => {
@@ -1166,6 +1191,7 @@ function reactiveAttrValue(raw, reactive) {
return found ? value : null; return found ? value : null;
} }
function renderAttrs(attrs, csrId, reactive = null, dynamicExpressions) { function renderAttrs(attrs, csrId, reactive = null, dynamicExpressions) {
attrs = expandBindings(attrs);
let bindIndex = 0; let bindIndex = 0;
const rendered = attrs const rendered = attrs
.map((attr) => { .map((attr) => {
@@ -1302,7 +1328,7 @@ function renderLoopBody(node, locals = []) {
return compileIfExpr(node, locals); return compileIfExpr(node, locals);
} }
const componentTag = isComponentTag(node.tag); const componentTag = isComponentTag(node.tag);
const attrs = node.attrs const attrs = expandBindings(node.attrs)
.filter((attr) => attr.name !== "data-component") .filter((attr) => attr.name !== "data-component")
.map((attr) => { .map((attr) => {
const name = attr.event const name = attr.event
@@ -1562,9 +1588,9 @@ function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindin
const island = islandMarkerFor(node); const island = islandMarkerFor(node);
if (island) if (island)
return island; return island;
const attrs = node.attrs const attrs = expandBindings(node.attrs)
.filter((attr) => attr.name !== "data-component") .filter((attr) => attr.name !== "data-component")
.map((attr) => renderPageComponentAttr(attr, loops)) .map((attr) => renderPageComponentAttr(attr, loops, reactive))
.join(""); .join("");
const inner = node.children const inner = node.children
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)) .map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
@@ -1577,7 +1603,7 @@ function renderNestedComponentInvocation(node, ctx) {
if (island) if (island)
return island; return island;
let bindIndex = 0; let bindIndex = 0;
const attrs = node.attrs const attrs = expandBindings(node.attrs)
.filter((attr) => attr.name !== "data-component") .filter((attr) => attr.name !== "data-component")
.map((attr) => { .map((attr) => {
const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(attr.name); const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(attr.name);
@@ -2446,6 +2472,8 @@ ${exposed
${declarations} ${declarations}
${visible.length ${visible.length
? ` const __values = await Promise.all([${visible.map((entry) => `__load_${entry.name}()`).join(", ")}]); ? ` const __values = await Promise.all([${visible.map((entry) => `__load_${entry.name}()`).join(", ")}]);
const __response = __values.find((value) => value instanceof Response);
if (__response) return __response;
return { ${visible.map((entry, index) => `${JSON.stringify(entry.name)}: __values[${index}]`).join(", ")} };` return { ${visible.map((entry, index) => `${JSON.stringify(entry.name)}: __values[${index}]`).join(", ")} };`
: ""} : ""}
}`; }`;
@@ -2765,7 +2793,7 @@ function renderClientControlTemplate(nodes) {
} }
const componentTag = isComponentTag(node.tag); const componentTag = isComponentTag(node.tag);
let bindIndex = 0; let bindIndex = 0;
const attrs = node.attrs const attrs = expandBindings(node.attrs)
.map((attribute) => { .map((attribute) => {
const name = attribute.event const name = attribute.event
? componentTag ? componentTag
@@ -2803,19 +2831,21 @@ function encodeClientControl(value) {
} }
function renderComponentIfNode(node, ctx) { function renderComponentIfNode(node, ctx) {
let expression = "``"; let expression = "``";
const clientBranches = [];
for (let index = node.branches.length - 1; index >= 0; index--) { for (let index = node.branches.length - 1; index >= 0; index--) {
const branch = node.branches[index]; const branch = node.branches[index];
const body = branch.body.map((child) => renderComponentNode(child, ctx)).join(""); const body = branch.body.map((child) => renderComponentNode(child, ctx)).join("");
const bodyExpression = "`" + body + "`"; const bodyExpression = "`" + body + "`";
clientBranches.unshift(`{ cond: ${branch.cond === null ? "null" : JSON.stringify(branch.cond)}, body: ${bodyExpression} }`);
expression = expression =
branch.cond === null branch.cond === null
? bodyExpression ? bodyExpression
: `(${ctx.resolveExpr(branch.cond)}) ? ${bodyExpression} : ${expression}`; : `(${ctx.resolveExpr(branch.cond)}) ? ${bodyExpression} : ${expression}`;
} }
const definition = encodeClientControl(node.branches.map((branch) => ({ // Render all branches on the server into an inert, encoded definition.
cond: branch.cond, // A branch first selected in the browser therefore contains the real nested
body: renderClientControlTemplate(branch.body), // component HTML and scope metadata, not a data-component placeholder.
}))); const definition = `\${__wrnexusEncodeControl([${clientBranches.join(",")}])}`;
return `<template data-wrn-if="${definition}"></template>${"${" + expression + "}"}<template data-wrn-control-end></template>`; return `<template data-wrn-if="${definition}"></template>${"${" + expression + "}"}<template data-wrn-control-end></template>`;
} }
function renderComponentEachNode(node, ctx) { function renderComponentEachNode(node, ctx) {
@@ -2955,7 +2985,7 @@ function renderComponentNode(node, ctx) {
} }
} }
const isExplicitComponentMount = node.attrs.some((attribute) => attribute.name === "data-component"); const isExplicitComponentMount = node.attrs.some((attribute) => attribute.name === "data-component");
const attrs = node.attrs const attrs = expandBindings(node.attrs)
.filter((a) => a.name !== "class" && !a.name.startsWith("class:")) .filter((a) => a.name !== "class" && !a.name.startsWith("class:"))
.map((a) => { .map((a) => {
const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(a.name); const spread = /^\{\.\.\.([A-Za-z_$][\w$]*)\}$/.exec(a.name);
@@ -3413,6 +3443,9 @@ function __wrnRaw(v: unknown): string {
}`); }`);
} }
if (needsScope) { if (needsScope) {
out.push(`function __wrnexusEncodeControl(value: unknown): string {
return __WrnexusBuffer.from(JSON.stringify(value), "utf8").toString("base64");
}`);
out.push(`function __wrnexusSerializeScopeValue(value: unknown): string { out.push(`function __wrnexusSerializeScopeValue(value: unknown): string {
if (value === undefined) { if (value === undefined) {
return "undefined"; return "undefined";
@@ -3495,7 +3528,7 @@ function __wrnProp(v) {
const value = v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v); const value = v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v);
return __wrnAttr(value); return __wrnAttr(value);
} }
function renderPageComponentAttr(attr, dynamicExpressions) { function renderPageComponentAttr(attr, dynamicExpressions, _reactive) {
if (attr.event) { if (attr.event) {
return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`; return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
} }
@@ -3508,7 +3541,11 @@ function renderPageComponentAttr(attr, dynamicExpressions) {
} }
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`); dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`; const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
return ` ${attr.name}="${marker}"`; // The mount disappears during SSR. Preserve every dynamic component prop
// as a parent-owned binding; server-only expressions simply fail closed in
// the browser, while page state can continue driving the mounted child.
const binding = ` data-wrn-prop-bind-${dynamicExpressions.length - 1}="${attrEscape(JSON.stringify([attr.name, attr.value]))}"`;
return ` ${attr.name}="${marker}"${binding}`;
} }
}, },
@@ -4030,6 +4067,7 @@ function islandNamesFrom(imports) {
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.NativeCompileError = void 0; exports.NativeCompileError = void 0;
exports.generateNative = generateNative; exports.generateNative = generateNative;
const syntax_1 = require("@wrnexus/syntax");
class NativeCompileError extends Error { class NativeCompileError extends Error {
constructor(message) { constructor(message) {
super(message); super(message);
@@ -4095,12 +4133,12 @@ function expression(value) {
function textJsx(value) { function textJsx(value) {
const pieces = []; const pieces = [];
let last = 0; let last = 0;
for (const match of value.matchAll(/\{([^{}]+)\}/g)) { for (const segment of (0, syntax_1.extractInterpolations)(value)) {
if (match.index > last) if (segment.start > last)
pieces.push(value.slice(last, match.index)); pieces.push(value.slice(last, segment.start));
const expr = match[1].trim(); const expr = segment.expression;
pieces.push(expr.startsWith("t:") ? `{${JSON.stringify(expr.slice(2).trim())}}` : `{${expr}}`); pieces.push(expr.startsWith("t:") ? `{${JSON.stringify(expr.slice(2).trim())}}` : `{${expr}}`);
last = match.index + match[0].length; last = segment.end;
} }
pieces.push(value.slice(last)); pieces.push(value.slice(last));
return pieces.join("").replace(/([<>])/g, (char) => (char === "<" ? "&lt;" : "&gt;")); return pieces.join("").replace(/([<>])/g, (char) => (char === "<" ? "&lt;" : "&gt;"));
@@ -6190,6 +6228,26 @@ function expandStructuredStateDeclarations(lines, unit) {
} }
}); });
} }
function formatStructuredPropDeclaration(value, unit, depth, printWidth) {
const match = /^([A-Za-z_$][\w$]*\??\s*(?::\s*[^=]+?)?\s*=\s*)([\[{][\s\S]*)$/.exec(value);
if (!match)
return null;
try {
const parsed = JSON.parse(match[2].trim());
const compact = JSON.stringify(parsed);
const indentation = unit.repeat(depth);
if (indentation.length + match[1].length + compact.length <= printWidth)
return null;
const jsonLines = JSON.stringify(parsed, null, unit).split("\n");
return [
`${indentation}${match[1]}${jsonLines[0]}`,
...jsonLines.slice(1).map((jsonLine) => `${indentation}${jsonLine}`),
];
}
catch {
return null;
}
}
function formatWrnPass(source, options = {}) { function formatWrnPass(source, options = {}) {
const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize ?? 4); const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize ?? 4);
const printWidth = options.printWidth ?? 100; const printWidth = options.printWidth ?? 100;
@@ -6245,6 +6303,12 @@ function formatWrnPass(source, options = {}) {
index += 1; index += 1;
continue; continue;
} }
const structuredProp = formatStructuredPropDeclaration(value, unit, codeDepth + htmlDepth + controlDepth, printWidth);
if (structuredProp) {
output.push(...structuredProp);
index += 1;
continue;
}
const leadingClosingBraces = countLeadingClosingBraces(value); const leadingClosingBraces = countLeadingClosingBraces(value);
const closesControlBlock = isControlBlockClose(value); const closesControlBlock = isControlBlockClose(value);
const continuesControlBlock = isControlBlockMiddle(value); const continuesControlBlock = isControlBlockMiddle(value);
@@ -6325,7 +6389,7 @@ 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); for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
}; };
Object.defineProperty(exports, "__esModule", { value: true }); 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; 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.extractInterpolations = 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"); var tokenizer_ts_1 = require("./tokenizer.js");
Object.defineProperty(exports, "Lexer", { enumerable: true, get: function () { return tokenizer_ts_1.Lexer; } }); 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, "LexError", { enumerable: true, get: function () { return tokenizer_ts_1.LexError; } });
@@ -6337,6 +6401,8 @@ Object.defineProperty(exports, "parse", { enumerable: true, get: function () { r
Object.defineProperty(exports, "parseHtmlView", { enumerable: true, get: function () { return parser_ts_1.parseHtmlView; } }); 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, "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; } }); Object.defineProperty(exports, "VOID_ELEMENTS", { enumerable: true, get: function () { return parser_ts_1.VOID_ELEMENTS; } });
var interpolation_ts_1 = require("./interpolation.js");
Object.defineProperty(exports, "extractInterpolations", { enumerable: true, get: function () { return interpolation_ts_1.extractInterpolations; } });
var types_ts_1 = require("./types.js"); var types_ts_1 = require("./types.js");
Object.defineProperty(exports, "eraseFunctionTypes", { enumerable: true, get: function () { return types_ts_1.eraseFunctionTypes; } }); 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, "inferredRuntimeType", { enumerable: true, get: function () { return types_ts_1.inferredRuntimeType; } });
@@ -6370,6 +6436,50 @@ Object.defineProperty(exports, "parseStoreLifecycle", { enumerable: true, get: f
Object.defineProperty(exports, "parseStructuredImports", { enumerable: true, get: function () { return v060_ts_1.parseStructuredImports; } }); 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; } }); Object.defineProperty(exports, "stripRuntimeFunctionModifiers", { enumerable: true, get: function () { return v060_ts_1.stripRuntimeFunctionModifiers; } });
},
"packages/syntax/src/interpolation.ts": function (module, exports, require, __filename, __dirname) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractInterpolations = extractInterpolations;
/** Extract top-level `{...}` expressions while preserving nested object/array literals. */
function extractInterpolations(value) {
const segments = [];
let index = 0;
while (index < value.length) {
if (value[index] !== "{") {
index++;
continue;
}
const start = index++;
let depth = 1;
let quote = null;
while (index < value.length && depth > 0) {
const char = value[index];
if (quote) {
if (char === "\\" && index + 1 < value.length)
index += 2;
else {
if (char === quote)
quote = null;
index++;
}
continue;
}
if (char === '"' || char === "'" || char === "`")
quote = char;
else if (char === "{")
depth++;
else if (char === "}")
depth--;
index++;
}
if (depth === 0) {
segments.push({ expression: value.slice(start + 1, index - 1).trim(), start, end: index });
}
}
return segments;
}
}, },
"packages/syntax/src/parser.ts": function (module, exports, require, __filename, __dirname) { "packages/syntax/src/parser.ts": function (module, exports, require, __filename, __dirname) {
"use strict"; "use strict";
File diff suppressed because it is too large Load Diff
+145 -8
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
// WRN editor language server source hash: 3c6060ff9db332cf1dc01a467d795fb2d002300400072a4b87437c48d61ab7f9 // WRN editor language server source hash: 5258b3e76cec47df752d4bb886fbd57a3f3c4af9ca9b6b2bf6742c8d78025ff1
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72 // WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
// @bun @bun-cjs // @bun @bun-cjs
(function(exports, require, module, __filename, __dirname) {var __create = Object.create; (function(exports, require, module, __filename, __dirname) {var __create = Object.create;
@@ -23,12 +23,14 @@ var __toESM = (mod, isNodeMode, target) => {
} }
target = mod != null ? __create(__getProtoOf(mod)) : {}; target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target; const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
if (mod && typeof mod === "object" || typeof mod === "function") {
for (let key of __getOwnPropNames(mod)) for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key)) if (!__hasOwnProp.call(to, key))
__defProp(to, key, { __defProp(to, key, {
get: __accessProp.bind(mod, key), get: __accessProp.bind(mod, key),
enumerable: true enumerable: true
}); });
}
if (canCache) if (canCache)
cache.set(mod, to); cache.set(mod, to);
return to; return to;
@@ -66,7 +68,7 @@ var __export = (target, all) => {
}; };
// node_modules/.bun/typescript@6.0.3/node_modules/typescript/lib/typescript.js // node_modules/.bun/typescript@6.0.3/node_modules/typescript/lib/typescript.js
var require_typescript = __commonJS((exports2, module2) => { var require_typescript = __commonJS(function(exports2, module2) {
var __dirname = "E:\\WireJS\\node_modules\\.bun\\typescript@6.0.3\\node_modules\\typescript\\lib", __filename = "E:\\WireJS\\node_modules\\.bun\\typescript@6.0.3\\node_modules\\typescript\\lib\\typescript.js"; var __dirname = "E:\\WireJS\\node_modules\\.bun\\typescript@6.0.3\\node_modules\\typescript\\lib", __filename = "E:\\WireJS\\node_modules\\.bun\\typescript@6.0.3\\node_modules\\typescript\\lib\\typescript.js";
/*! ***************************************************************************** /*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved. Copyright (c) Microsoft Corporation. All rights reserved.
@@ -169514,7 +169516,7 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
}); });
// node_modules/.bun/@vscode+l10n@0.0.18/node_modules/@vscode/l10n/dist/main.js // node_modules/.bun/@vscode+l10n@0.0.18/node_modules/@vscode/l10n/dist/main.js
var require_main = __commonJS((exports2, module2) => { var require_main = __commonJS(function(exports2, module2) {
var __defProp2 = Object.defineProperty; var __defProp2 = Object.defineProperty;
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
var __getOwnPropNames2 = Object.getOwnPropertyNames; var __getOwnPropNames2 = Object.getOwnPropertyNames;
@@ -169658,9 +169660,9 @@ var require_main = __commonJS((exports2, module2) => {
// packages/language-server/src/server.ts // packages/language-server/src/server.ts
var exports_server = {}; var exports_server = {};
__export(exports_server, { __export(exports_server, {
isApiReferenceAt: () => isApiReferenceAt, apiCallCompletions: () => apiCallCompletions,
apiCallHover: () => apiCallHover, apiCallHover: () => apiCallHover,
apiCallCompletions: () => apiCallCompletions isApiReferenceAt: () => isApiReferenceAt
}); });
module.exports = __toCommonJS(exports_server); module.exports = __toCommonJS(exports_server);
@@ -170720,6 +170722,26 @@ function expandStructuredStateDeclarations(lines, unit) {
} }
}); });
} }
function formatStructuredPropDeclaration(value, unit, depth, printWidth) {
const match = /^([A-Za-z_$][\w$]*\??\s*(?::\s*[^=]+?)?\s*=\s*)([\[{][\s\S]*)$/.exec(value);
if (!match)
return null;
try {
const parsed = JSON.parse(match[2].trim());
const compact = JSON.stringify(parsed);
const indentation = unit.repeat(depth);
if (indentation.length + match[1].length + compact.length <= printWidth)
return null;
const jsonLines = JSON.stringify(parsed, null, unit).split(`
`);
return [
`${indentation}${match[1]}${jsonLines[0]}`,
...jsonLines.slice(1).map((jsonLine) => `${indentation}${jsonLine}`)
];
} catch {
return null;
}
}
function formatWrnPass(source, options = {}) { function formatWrnPass(source, options = {}) {
const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize ?? 4); const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize ?? 4);
const printWidth = options.printWidth ?? 100; const printWidth = options.printWidth ?? 100;
@@ -170776,6 +170798,12 @@ function formatWrnPass(source, options = {}) {
index += 1; index += 1;
continue; continue;
} }
const structuredProp = formatStructuredPropDeclaration(value, unit, codeDepth + htmlDepth + controlDepth, printWidth);
if (structuredProp) {
output.push(...structuredProp);
index += 1;
continue;
}
const leadingClosingBraces = countLeadingClosingBraces(value); const leadingClosingBraces = countLeadingClosingBraces(value);
const closesControlBlock = isControlBlockClose(value); const closesControlBlock = isControlBlockClose(value);
const continuesControlBlock = isControlBlockMiddle(value); const continuesControlBlock = isControlBlockMiddle(value);
@@ -172391,6 +172419,44 @@ function parseHtmlView(src, pos) {
const nodes = parseNodeList("root"); const nodes = parseNodeList("root");
return { nodes, endPos: i }; return { nodes, endPos: i };
} }
// packages/syntax/src/interpolation.ts
function extractInterpolations(value) {
const segments = [];
let index = 0;
while (index < value.length) {
if (value[index] !== "{") {
index++;
continue;
}
const start = index++;
let depth = 1;
let quote = null;
while (index < value.length && depth > 0) {
const char = value[index];
if (quote) {
if (char === "\\" && index + 1 < value.length)
index += 2;
else {
if (char === quote)
quote = null;
index++;
}
continue;
}
if (char === '"' || char === "'" || char === "`")
quote = char;
else if (char === "{")
depth++;
else if (char === "}")
depth--;
index++;
}
if (depth === 0) {
segments.push({ expression: value.slice(start + 1, index - 1).trim(), start, end: index });
}
}
return segments;
}
// packages/syntax/src/diagnostics.ts // packages/syntax/src/diagnostics.ts
function stripAsciiControlAndSpace(value) { function stripAsciiControlAndSpace(value) {
let result = ""; let result = "";
@@ -172988,7 +173054,7 @@ function runtimeNamespace(runtime) {
return `__wrn_${runtime}`; return `__wrn_${runtime}`;
} }
function functionDeclaration(fn) { function functionDeclaration(fn) {
const params = fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": CustomEvent<any> & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`).join(", "); const params = fn.parameters.map((param) => `${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": Event & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`).join(", ");
return `export ${fn.async ? "async " : ""}function ${fn.name}(${params})${fn.returnType ? `: ${fn.returnType}` : ""} {${fn.body}}`; return `export ${fn.async ? "async " : ""}function ${fn.name}(${params})${fn.returnType ? `: ${fn.returnType}` : ""} {${fn.body}}`;
} }
function retainedImports(ast) { function retainedImports(ast) {
@@ -173048,6 +173114,27 @@ var RESERVED_BINDING_NAMES = new Set([
function safeBindingName(name) { function safeBindingName(name) {
return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED_BINDING_NAMES.has(name); return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED_BINDING_NAMES.has(name);
} }
function safeViewExpression(expression, reservedProps) {
if (!reservedProps.size)
return expression;
const scanner = import_typescript.default.createScanner(import_typescript.default.ScriptTarget.Latest, false, import_typescript.default.LanguageVariant.Standard, expression);
let result = "";
let copied = 0;
for (let token = scanner.scan();token !== import_typescript.default.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
const start = scanner.getTokenPos();
const end = scanner.getTextPos();
const text = scanner.getTokenText();
if (!reservedProps.has(text))
continue;
const previous = expression.slice(0, start).trimEnd().at(-1);
const following = expression.slice(end).trimStart().at(0);
if (previous === "." || following === ":")
continue;
result += expression.slice(copied, start) + `props.${text}`;
copied = end;
}
return result + expression.slice(copied);
}
function virtualTypeScriptModule(source, filePath = "component.wrn", appRoot = findAppRoot(filePath)) { function virtualTypeScriptModule(source, filePath = "component.wrn", appRoot = findAppRoot(filePath)) {
const ast = parse(source); const ast = parse(source);
const chunks = []; const chunks = [];
@@ -173103,6 +173190,8 @@ function virtualTypeScriptModule(source, filePath = "component.wrn", appRoot = f
append(`declare const api: { ${apiType} };`); append(`declare const api: { ${apiType} };`);
append(`declare const props: Readonly<${ast.name}Props>;`); append(`declare const props: Readonly<${ast.name}Props>;`);
append("declare const refs: Record<string, Element | null>;"); append("declare const refs: Record<string, Element | null>;");
append("declare const toast: ((message: string, options?: Record<string, unknown>) => unknown) & { success(message: string, options?: Record<string, unknown>): unknown; error(message: string, options?: Record<string, unknown>): unknown; danger(message: string, options?: Record<string, unknown>): unknown; warning(message: string, options?: Record<string, unknown>): unknown; info(message: string, options?: Record<string, unknown>): unknown; dismiss(id: string | number): void; clear(): void };");
append("declare function useFetch(path: string, method?: string | { method?: string; query?: unknown; params?: unknown; body?: unknown; data?: unknown }, input?: unknown): Promise<any>;");
append(ast.kind === "global-store" || ast.kind === "page-store" ? storeContract(ast) : componentContract(ast)); append(ast.kind === "global-store" || ast.kind === "page-store" ? storeContract(ast) : componentContract(ast));
append(importedWrnDeclarations(ast, filePath, appRoot)); append(importedWrnDeclarations(ast, filePath, appRoot));
const sharedNames = new Set(ast.runtimeFunctions.filter((fn) => fn.runtime === "shared").map((fn) => fn.name)); const sharedNames = new Set(ast.runtimeFunctions.filter((fn) => fn.runtime === "shared").map((fn) => fn.name));
@@ -173119,6 +173208,53 @@ ${sharedAliases}`);
append(functionDeclaration(fn), fn.source); append(functionDeclaration(fn), fn.source);
append("}"); append("}");
} }
const viewFunctions = new Map;
for (const fn of ast.runtimeFunctions) {
if (fn.runtime !== "server")
viewFunctions.set(fn.name, fn.runtime);
}
for (const [name, runtime] of viewFunctions) {
append(`declare const ${name}: typeof ${runtimeNamespace(runtime)}.${name};`);
}
let bindingIndex = 0;
const reservedProps = new Set(ast.props.map((prop) => prop.name).filter((name) => !safeBindingName(name)));
const appendViewBindings = (nodes, locals) => {
for (const node of nodes) {
if (node.type === "element") {
for (const attr of node.attrs) {
const expressions = attr.event ? [attr.value] : extractInterpolations(attr.value).map((segment) => segment.expression);
for (const expression of expressions) {
if (!expression.trim())
continue;
const declarations = [
...[...locals].map((name) => `declare const ${name}: any;`),
...attr.event ? [
"declare const payload: any;",
"declare const event: Event & { target: HTMLElement };"
] : []
].join(" ");
append(`namespace __wrn_view_${bindingIndex++} { ${declarations} void (${safeViewExpression(expression, reservedProps)}); }`, attr.value);
}
}
appendViewBindings(node.children, locals);
} else if (node.type === "if") {
for (const branch of node.branches) {
if (branch.cond)
append(`namespace __wrn_view_${bindingIndex++} { void (${safeViewExpression(branch.cond, reservedProps)}); }`, branch.cond);
appendViewBindings(branch.body, locals);
}
} else if (node.type === "each") {
append(`namespace __wrn_view_${bindingIndex++} { void (${safeViewExpression(node.list, reservedProps)}); }`, node.list);
const nested = new Set(locals);
nested.add(node.item);
if (node.index)
nested.add(node.index);
appendViewBindings(node.body, nested);
appendViewBindings(node.empty, locals);
}
}
};
appendViewBindings(ast.view, new Set);
return { return {
ast, ast,
fileName: filePath.replace(/\.wrn$/i, ".wrn.ts"), fileName: filePath.replace(/\.wrn$/i, ".wrn.ts"),
@@ -173224,7 +173360,7 @@ function componentUsageDiagnostics(source, ast, filePath, appRoot) {
const shape = shapes.get(node.tag); const shape = shapes.get(node.tag);
if (!shape) if (!shape)
return; return;
const attributes = new Map(node.attrs.filter((attr) => !attr.event).map((attr) => [attr.name, attr])); const attributes = new Map(node.attrs.filter((attr) => !attr.event).map((attr) => [attr.name.startsWith("bind:") ? attr.name.slice(5) : attr.name, attr]));
const outputNames = new Set(shape.outputs.map((output) => output.name)); const outputNames = new Set(shape.outputs.map((output) => output.name));
const position = lineAt(source, `<${node.tag}`); const position = lineAt(source, `<${node.tag}`);
for (const prop of shape.props) { for (const prop of shape.props) {
@@ -173261,7 +173397,8 @@ function componentUsageDiagnostics(source, ast, filePath, appRoot) {
} }
if (/^(?:class|id|style|slot|data-|aria-)/.test(attr.name) || attr.name === "attrs") if (/^(?:class|id|style|slot|data-|aria-)/.test(attr.name) || attr.name === "attrs")
continue; continue;
const prop = known.get(attr.name); const propName = attr.name.startsWith("bind:") ? attr.name.slice(5) : attr.name;
const prop = known.get(propName);
if (!prop) { if (!prop) {
diagnostics.push({ diagnostics.push({
code: "WRN-COMPONENT-UNKNOWN-PROP", code: "WRN-COMPONENT-UNKNOWN-PROP",
+14
View File
@@ -20,6 +20,20 @@ test("preserves nested prop defaults while formatting", () => {
assert.equal(formatWrn(formatted, { insertSpaces: true, tabSize: 2 }), formatted); assert.equal(formatWrn(formatted, { insertSpaces: true, tabSize: 2 }), formatted);
}); });
test("formats large typed JSON prop defaults with the bundled formatter", () => {
const source = `component Catalog {
props {
categories: unknown[] = [{"label":"Platform & Core","apps":[{"label":"Home","href":"/home"},{"label":"SSO","href":"/sso"}]},{"label":"Sales","apps":[{"label":"CRM","href":"/crm"}]}]
}
view { <div></div> }
}`;
const formatted = formatWrn(source, { insertSpaces: true, tabSize: 2, printWidth: 70 });
assert.match(formatted, /categories: unknown\[\] = \[\n \{/);
assert.match(formatted, /"apps": \[\n \{/);
assert.equal(formatWrn(formatted, { insertSpaces: true, tabSize: 2, printWidth: 70 }), formatted);
});
test("formats native JSON state values with readable indentation", () => { test("formats native JSON state values with readable indentation", () => {
const source = `component Footer { const source = `component Footer {
state footerItems = [{"label":"Accessibility","href":"/accessibility","value":"accessibility"},{"label":"Privacy","href":"/privacy","value":"privacy"}] state footerItems = [{"label":"Accessibility","href":"/accessibility","value":"accessibility"},{"label":"Privacy","href":"/privacy","value":"privacy"}]
+2 -2
View File
@@ -55,7 +55,7 @@
"generate:ui-visual": "node scripts/check-ui-visual-contract.mjs --write", "generate:ui-visual": "node scripts/check-ui-visual-contract.mjs --write",
"sbom": "node scripts/generate-sbom.mjs", "sbom": "node scripts/generate-sbom.mjs",
"benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs --write", "benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs --write",
"check:production": "bun run check:workspace && bun run check:generated-types && bun run check:public-api && bun run check:ui-visual && bun run validate:0.8 && bun run security:framework && bun run security:asvs && bun run check:editor-compiler && bun run check:editor-language-server && bun run check:editor-extension && bun run check && bun run test:examples", "check:production": "bun run check:workspace && bun run check:generated-types && bun run check:public-api && bun run check:ui-visual && bun run validate:0.8 && bun run security:framework && bun run security:asvs && bun run check:editor-compiler && bun run check:editor-language-server && bun run check:editor-extension && bun run check && bun run test:examples && bun run test:services && bun run test:editor",
"validate:staging": "node --experimental-transform-types scripts/test-package-integrity.mjs", "validate:staging": "node --experimental-transform-types scripts/test-package-integrity.mjs",
"stage:packages": "bun run scripts/publish-packages.ts", "stage:packages": "bun run scripts/publish-packages.ts",
"test:staged-consumers": "node scripts/test-staged-consumers.mjs", "test:staged-consumers": "node scripts/test-staged-consumers.mjs",
@@ -70,7 +70,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@types/bun": "^1.3.14", "@types/bun": "^1.4.0",
"@types/react": "^19.2.18", "@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4", "@types/react-dom": "^19.2.4",
"eslint": "^10.8.1", "eslint": "^10.8.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.14", "version": "0.8.15",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+5
View File
@@ -16,6 +16,11 @@ export function defineAuthz<Subject = any, Resource = any>(
const bindings = module.bindings ?? {}; const bindings = module.bindings ?? {};
for (const id of Object.keys(permissions)) { for (const id of Object.keys(permissions)) {
if (id.startsWith("role:")) {
throw new Error(
`WRN-AUTHZ-DECL: permission id '${id}' uses the reserved 'role:' prefix; role grants use 'role:<name>' for inheritance.`,
);
}
if (id.includes("*")) { if (id.includes("*")) {
throw new Error( throw new Error(
`WRN-AUTHZ-DECL: permission id '${id}' must not contain a wildcard; wildcards belong in roles.`, `WRN-AUTHZ-DECL: permission id '${id}' must not contain a wildcard; wildcards belong in roles.`,
+6
View File
@@ -26,6 +26,12 @@ describe("defineAuthz", () => {
expect(() => defineAuthz({ permissions: { "post:*": {} } })).toThrow(/wildcard/i); expect(() => defineAuthz({ permissions: { "post:*": {} } })).toThrow(/wildcard/i);
}); });
test("rejects permission ids that collide with role inheritance", () => {
expect(() => defineAuthz({ permissions: { "role:assign": {} } })).toThrow(
/reserved 'role:' prefix/i,
);
});
test("rejects a role granting an unknown-shaped entry", () => { test("rejects a role granting an unknown-shaped entry", () => {
expect(() => defineAuthz({ roles: { editor: [""] } })).toThrow(/role 'editor'/i); expect(() => defineAuthz({ roles: { editor: [""] } })).toThrow(/role 'editor'/i);
}); });
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/captcha", "name": "@wrnexus/captcha",
"version": "0.8.11", "version": "0.8.14",
"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,
@@ -0,0 +1,63 @@
import { describe, expect, test } from "bun:test";
import {
captchaContext,
captchaResultResponse,
captchaTokenFrom,
verifyCaptchaOrThrow,
} from "../src/helpers.ts";
describe("CAPTCHA helper boundaries", () => {
test("extracts JSON request fallbacks without consuming the caller's request", async () => {
const request = new Request("https://example.test/verify", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ captchaToken: 12345 }),
});
expect(await captchaTokenFrom(request)).toBe("12345");
expect(await request.json()).toEqual({ captchaToken: 12345 });
});
test("turns verification failures into stable errors and no-store responses", async () => {
const provider = {
name: "custom" as const,
client: { responseField: "captchaToken" },
verify: async () => ({
success: false as const,
code: "expired",
message: "Try again",
provider: "custom" as const,
action: "login",
}),
};
await expect(
verifyCaptchaOrThrow(provider, { providerToken: "x", action: "login" }),
).rejects.toThrow("WRN-CAPTCHA-EXPIRED: Try again");
const response = captchaResultResponse({
success: false,
code: "invalid",
provider: "custom",
action: "login",
});
expect(response.status).toBe(403);
expect(response.headers.get("cache-control")).toBe("no-store");
expect(
captchaContext({
locals: { captcha: { success: true, provider: "custom", action: "login" } },
} as never),
).toEqual({ success: true, provider: "custom", action: "login" });
expect(captchaContext({ locals: { captcha: "forged" } } as never)).toBeNull();
});
test("does not convert provider transport failures into successful verification", async () => {
const provider = {
name: "custom" as const,
client: { responseField: "captchaToken" },
verify: async () => {
throw new Error("provider timeout");
},
};
await expect(
verifyCaptchaOrThrow(provider, { providerToken: "x", action: "checkout" }),
).rejects.toThrow("provider timeout");
});
});
+3 -2
View File
@@ -1,11 +1,12 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.57", "version": "0.8.64",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
"./workspace": "./src/workspace.ts" "./workspace": "./src/workspace.ts",
"./test-setup": "./src/test-setup.ts"
}, },
"bin": { "bin": {
"wrnexus": "src/index.ts" "wrnexus": "src/index.ts"
+5
View File
@@ -113,6 +113,11 @@ Update options: --dry-run previews changes; --no-verify skips post-update check/
async function main(): Promise<void> { async function main(): Promise<void> {
const [command, ...rest] = process.argv.slice(2); const [command, ...rest] = process.argv.slice(2);
if (command === "--version" || command === "-v" || command === "version") {
console.log(currentCliVersion());
return;
}
notifyIfUpdateAvailable(currentCliVersion(), command); notifyIfUpdateAvailable(currentCliVersion(), command);
switch (command) { switch (command) {
+14
View File
@@ -0,0 +1,14 @@
import { registerDb, setDb } from "@wrnexus/db";
import { connectFromConfig } from "@wrnexus/db/connect";
import { loadAppConfig, loadEnv } from "@wrnexus/styles";
const root = process.env.WRNEXUS_TEST_ROOT;
if (root) {
const profile = process.env.WRNEXUS_PROFILE || "test";
loadEnv(root, profile);
const config = await loadAppConfig(root, profile);
if (config.db) setDb(connectFromConfig(config.db, root));
for (const [name, database] of Object.entries(config.databases ?? {})) {
registerDb(name, connectFromConfig(database, root));
}
}
+21 -2
View File
@@ -20,6 +20,7 @@ export interface TestCommandPlan {
level?: TestLevel; level?: TestLevel;
files: string[]; files: string[];
setup?: { command: string; args: string[] }; setup?: { command: string; args: string[] };
env?: Record<string, string>;
} }
function shardFiles(files: string[], value?: string): string[] { function shardFiles(files: string[], value?: string): string[] {
@@ -69,6 +70,11 @@ export function createTestPlan(appRoot: string, args: string[]): TestCommandPlan
const root = resolve(appRoot); const root = resolve(appRoot);
const level = args.find((value): value is TestLevel => TEST_LEVELS.includes(value as TestLevel)); const level = args.find((value): value is TestLevel => TEST_LEVELS.includes(value as TestLevel));
const watch = args.includes("--watch"); const watch = args.includes("--watch");
const profile = args.find((value) => value.startsWith("--profile="))?.slice(10) || "test";
const setupSource = join(import.meta.dir, "test-setup.ts");
const setupModule = existsSync(setupSource)
? setupSource
: join(import.meta.dir, "test-setup.js");
const shard = args.find((value) => value.startsWith("--shard="))?.slice(8); const shard = args.find((value) => value.startsWith("--shard="))?.slice(8);
const browsers = (args.find((value) => value.startsWith("--browsers="))?.slice(11) ?? "chromium") const browsers = (args.find((value) => value.startsWith("--browsers="))?.slice(11) ?? "chromium")
.split(",") .split(",")
@@ -120,10 +126,18 @@ export function createTestPlan(appRoot: string, args: string[]): TestCommandPlan
); );
return { return {
command: process.execPath, command: process.execPath,
args: ["test", ...(watch ? ["--watch"] : []), ...(pattern ? files : []), ...passthrough], args: [
"test",
"--preload",
setupModule,
...(watch ? ["--watch"] : []),
...(pattern ? files : []),
...passthrough,
],
cwd: root, cwd: root,
level, level,
files, files,
env: { WRNEXUS_TEST_ROOT: root, WRNEXUS_PROFILE: profile },
}; };
} }
@@ -136,7 +150,12 @@ export function runTests(appRoot: string, args: string[]): ChildProcess | null {
process.exitCode = 1; process.exitCode = 1;
return null; return null;
} }
const launch = () => spawn(plan.command, plan.args, { stdio: "inherit", cwd: plan.cwd }); const launch = () =>
spawn(plan.command, plan.args, {
stdio: "inherit",
cwd: plan.cwd,
env: { ...process.env, ...plan.env },
});
if (plan.setup) { if (plan.setup) {
const setup = spawn(plan.setup.command, plan.setup.args, { stdio: "inherit", cwd: plan.cwd }); const setup = spawn(plan.setup.command, plan.setup.args, { stdio: "inherit", cwd: plan.cwd });
setup.on("exit", (code, signal) => { setup.on("exit", (code, signal) => {
+13 -4
View File
@@ -63,14 +63,16 @@ const execFileAsync = promisify(execFile);
/** Resolve every installed WRNexus dependency independently and in parallel. */ /** Resolve every installed WRNexus dependency independently and in parallel. */
export async function latestPackageVersions(names: string[]): Promise<Map<string, string>> { export async function latestPackageVersions(names: string[]): Promise<Map<string, string>> {
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
const unique = [...new Set(names.filter((name) => name.startsWith("@wrnexus/")))]; const unique = [...new Set(names.filter((name) => name.startsWith("@wrnexus/")))];
const entries = await Promise.all( const entries = await Promise.all(
unique.map(async (name) => { unique.map(async (name) => {
try { try {
const { stdout } = await execFileAsync(npm, ["view", name, "version", "--json"], { const command = process.platform === "win32" ? (process.env.ComSpec ?? "cmd.exe") : "npm";
encoding: "utf8", const args =
}); process.platform === "win32"
? ["/d", "/s", "/c", "npm", "view", name, "version", "--json"]
: ["view", name, "version", "--json"];
const { stdout } = await execFileAsync(command, args, { encoding: "utf8" });
const parsed = JSON.parse(stdout.trim()) as string | string[]; const parsed = JSON.parse(stdout.trim()) as string | string[];
const version = Array.isArray(parsed) ? parsed.at(-1) : parsed; const version = Array.isArray(parsed) ? parsed.at(-1) : parsed;
return typeof version === "string" && /^\d+\.\d+\.\d+/.test(version) return typeof version === "string" && /^\d+\.\d+\.\d+/.test(version)
@@ -1274,6 +1276,13 @@ export async function runUpdate(dir: string, args: string[]): Promise<void> {
? new Map<string, string>() ? new Map<string, string>()
: await latestPackageVersions(dependencyNames); : await latestPackageVersions(dependencyNames);
if (!versionArg && dependencyNames.length > 0 && packageVersions.size === 0) {
throw new Error(
`Unable to resolve any of ${new Set(dependencyNames).size} installed WRNexus package ` +
`version(s) from npm. Check the @wrnexus registry and authentication before retrying.`,
);
}
console.log(`\n ⚡ wrnexus update → migrations ${target}${dryRun ? " (dry run)" : ""}`); console.log(`\n ⚡ wrnexus update → migrations ${target}${dryRun ? " (dry run)" : ""}`);
if (!versionArg) { if (!versionArg) {
console.log( console.log(
+10 -1
View File
@@ -50,6 +50,15 @@ describe("test command planning", () => {
test("keeps the unfiltered legacy command", async () => { test("keeps the unfiltered legacy command", async () => {
const plan = createTestPlan(await fixture(), ["--watch"]); const plan = createTestPlan(await fixture(), ["--watch"]);
expect(plan.level).toBeUndefined(); expect(plan.level).toBeUndefined();
expect(plan.args).toEqual(["test", "--watch"]); expect(plan.args[0]).toBe("test");
expect(plan.args).toContain("--preload");
expect(plan.args.at(-1)).toBe("--watch");
expect(plan.env?.WRNEXUS_PROFILE).toBe("test");
});
test("passes the selected profile to automatic database setup", async () => {
const plan = createTestPlan(await fixture(), ["--profile=ci"]);
expect(plan.env?.WRNEXUS_PROFILE).toBe("ci");
expect(plan.args).not.toContain("--profile=ci");
}); });
}); });
+24 -2
View File
@@ -1,8 +1,12 @@
import { afterEach, expect, test } from "bun:test"; import { afterEach, expect, test } from "bun:test";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join, resolve } from "node:path";
import { compareVersions, notifyIfUpdateAvailable } from "../src/update-notifier.ts"; import {
compareVersions,
currentCliVersion,
notifyIfUpdateAvailable,
} from "../src/update-notifier.ts";
const originalCache = process.env.WRNEXUS_UPDATE_CACHE; const originalCache = process.env.WRNEXUS_UPDATE_CACHE;
@@ -35,3 +39,21 @@ test("cached update checks suggest the exact upgrade command", () => {
rmSync(root, { recursive: true, force: true }); rmSync(root, { recursive: true, force: true });
} }
}); });
test("--version prints the package version outside a WrNexus workspace", () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-version-"));
try {
const result = Bun.spawnSync({
cmd: [process.execPath, resolve(import.meta.dir, "../src/index.ts"), "--version"],
cwd: root,
env: { ...process.env, WRNEXUS_NO_UPDATE_CHECK: "1" },
stdout: "pipe",
stderr: "pipe",
});
expect(result.exitCode).toBe(0);
expect(result.stdout.toString().trim()).toBe(currentCliVersion());
expect(result.stderr.toString()).toBe("");
} finally {
rmSync(root, { recursive: true, force: true });
}
});
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.19", "version": "0.8.22",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+15 -3
View File
@@ -834,7 +834,7 @@ function renderPageComponentInvocation(
const attrs = expandBindings(node.attrs) const attrs = expandBindings(node.attrs)
.filter((attr) => attr.name !== "data-component") .filter((attr) => attr.name !== "data-component")
.map((attr) => renderPageComponentAttr(attr, loops)) .map((attr) => renderPageComponentAttr(attr, loops, reactive))
.join(""); .join("");
const inner = node.children const inner = node.children
@@ -1857,6 +1857,8 @@ ${declarations}
${ ${
visible.length visible.length
? ` const __values = await Promise.all([${visible.map((entry) => `__load_${entry.name}()`).join(", ")}]); ? ` const __values = await Promise.all([${visible.map((entry) => `__load_${entry.name}()`).join(", ")}]);
const __response = __values.find((value) => value instanceof Response);
if (__response) return __response;
return { ${visible.map((entry, index) => `${JSON.stringify(entry.name)}: __values[${index}]`).join(", ")} };` return { ${visible.map((entry, index) => `${JSON.stringify(entry.name)}: __values[${index}]`).join(", ")} };`
: "" : ""
} }
@@ -3170,7 +3172,11 @@ function __wrnProp(v: unknown): string {
return __wrnAttr(value); return __wrnAttr(value);
} }
function renderPageComponentAttr(attr: Attr, dynamicExpressions: string[]): string { function renderPageComponentAttr(
attr: Attr,
dynamicExpressions: string[],
_reactive: PageReactive | null,
): string {
if (attr.event) { if (attr.event) {
return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`; return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
} }
@@ -3188,6 +3194,12 @@ function renderPageComponentAttr(attr: Attr, dynamicExpressions: string[]): stri
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`); dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`; const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
// The mount disappears during SSR. Preserve every dynamic component prop
// as a parent-owned binding; server-only expressions simply fail closed in
// the browser, while page state can continue driving the mounted child.
const binding = ` data-wrn-prop-bind-${dynamicExpressions.length - 1}="${attrEscape(
JSON.stringify([attr.name, attr.value]),
)}"`;
return ` ${attr.name}="${marker}"`; return ` ${attr.name}="${marker}"${binding}`;
} }
+5 -5
View File
@@ -1,4 +1,4 @@
import type { Attr, PageAst, ViewNode } from "@wrnexus/syntax"; import { extractInterpolations, type Attr, type PageAst, type ViewNode } from "@wrnexus/syntax";
export class NativeCompileError extends Error { export class NativeCompileError extends Error {
constructor(message: string) { constructor(message: string) {
@@ -68,11 +68,11 @@ function expression(value: string): string | null {
function textJsx(value: string): string { function textJsx(value: string): string {
const pieces: string[] = []; const pieces: string[] = [];
let last = 0; let last = 0;
for (const match of value.matchAll(/\{([^{}]+)\}/g)) { for (const segment of extractInterpolations(value)) {
if (match.index! > last) pieces.push(value.slice(last, match.index)); if (segment.start > last) pieces.push(value.slice(last, segment.start));
const expr = match[1]!.trim(); const expr = segment.expression;
pieces.push(expr.startsWith("t:") ? `{${JSON.stringify(expr.slice(2).trim())}}` : `{${expr}}`); pieces.push(expr.startsWith("t:") ? `{${JSON.stringify(expr.slice(2).trim())}}` : `{${expr}}`);
last = match.index! + match[0].length; last = segment.end;
} }
pieces.push(value.slice(last)); pieces.push(value.slice(last));
return pieces.join("").replace(/([<>])/g, (char) => (char === "<" ? "&lt;" : "&gt;")); return pieces.join("").replace(/([<>])/g, (char) => (char === "<" ? "&lt;" : "&gt;"));
@@ -56,6 +56,17 @@ test("named loads support memoized dependencies and deferred execution", () => {
expect(code).toContain('return { "audit": __values[0] }'); expect(code).toContain('return { "audit": __values[0] }');
}); });
test("named server loads propagate redirect responses instead of wrapping them as data", () => {
const code = generate(
parse(`page Private {
load server gate { return Response.redirect("/login", 303) }
view { <p>Private</p> }
}`),
);
expect(code).toContain("__values.find((value) => value instanceof Response)");
expect(code).toContain("if (__response) return __response");
});
test("load dependency cycles and cross-phase server dependencies fail compilation", () => { test("load dependency cycles and cross-phase server dependencies fail compilation", () => {
expect(() => expect(() =>
parse( parse(
+10
View File
@@ -1151,6 +1151,16 @@ page Home {
expect(output).toContain("__wrnexusPropAttr(["); expect(output).toContain("__wrnexusPropAttr([");
}); });
test("page state passed to a component retains a parent-owned prop binding", () => {
const output = generate(
parse(`page Home {
state count = 1
view { <Child value={count} /> }
}`),
);
expect(output).toContain("data-wrn-prop-bind-");
expect(output).toContain("{count}");
});
test("nested component props retain parent-owned reactive bindings", () => { test("nested component props retain parent-owned reactive bindings", () => {
const output = generate( const output = generate(
parse(`component Parent { parse(`component Parent {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.32", "version": "0.8.35",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -90,7 +90,7 @@ const CONTROLLER_LOADER = String.raw`
if (!componentControllerPromise) { if (!componentControllerPromise) {
componentControllerPromise = new Promise(function (resolve, reject) { componentControllerPromise = new Promise(function (resolve, reject) {
var script = document.createElement("script"); var script = document.createElement("script");
script.src = componentControllerUrl; script.src = trustedScriptURL(componentControllerUrl);
script.defer = true; script.defer = true;
script.onload = resolve; script.onload = resolve;
script.onerror = reject; script.onerror = reject;
+63 -10
View File
@@ -26,6 +26,40 @@ export const REACTIVE_RUNTIME = String.raw`
var behaviorObserver; var behaviorObserver;
var clientModuleCache = new Map(); var clientModuleCache = new Map();
/*
* Production enables require-trusted-types-for 'script'. Every HTML write
* below uses compiler-emitted template markup (or the explicit data-html
* escape hatch), and controller URLs are generated by the framework. Route
* those known sinks through the policy allowed by the default security
* preset instead of letting the browser reject hydration midway through a
* state update.
*/
var wrnexusTrustedTypesPolicy = null;
if (window.trustedTypes && typeof window.trustedTypes.createPolicy === "function") {
try {
wrnexusTrustedTypesPolicy = window.trustedTypes.createPolicy("wrnexus", {
createHTML: function (value) { return value; },
createScriptURL: function (value) { return value; }
});
} catch (_) {
wrnexusTrustedTypesPolicy = window.trustedTypes.defaultPolicy || null;
}
}
function trustedHTML(value) {
var source = value == null ? "" : String(value);
return wrnexusTrustedTypesPolicy
? wrnexusTrustedTypesPolicy.createHTML(source)
: source;
}
function trustedScriptURL(value) {
var source = value == null ? "" : String(value);
return wrnexusTrustedTypesPolicy
? wrnexusTrustedTypesPolicy.createScriptURL(source)
: source;
}
/* /*
* Two builtin chains the runtime reaches for constantly. Aliasing them is * Two builtin chains the runtime reaches for constantly. Aliasing them is
* not only shorter: hasOwn keeps prototype keys from reading as data, and * not only shorter: hasOwn keeps prototype keys from reading as data, and
@@ -234,8 +268,21 @@ export const REACTIVE_RUNTIME = String.raw`
Array.prototype.forEach.call(rules || [], function (rule) { Array.prototype.forEach.call(rules || [], function (rule) {
var cssText = rule.cssText || ""; var cssText = rule.cssText || "";
var match; var match;
var pattern = /var\(\s*(--wrn-[A-Za-z0-9_-]+)/g; // Only inspect declarations that can affect the current document. A
while ((match = pattern.exec(cssText))) referenced[match[1]] = true; // component stylesheet may contain optional variants that are not
// rendered on this page, and variables with fallbacks are intentionally
// optional rather than missing theme tokens.
if (rule.selectorText) {
try {
if (!document.querySelector(rule.selectorText)) return;
} catch (_) {
// Keep inspecting selectors that the DOM implementation cannot parse.
}
}
var pattern = /var\(\s*(--wrn-[A-Za-z0-9_-]+)\s*(,)?/g;
while ((match = pattern.exec(cssText))) {
if (!match[2]) referenced[match[1]] = true;
}
if (rule.style) { if (rule.style) {
Array.prototype.forEach.call(rule.style, function (property) { Array.prototype.forEach.call(rule.style, function (property) {
if (String(property).indexOf("--wrn-") === 0) declared[property] = true; if (String(property).indexOf("--wrn-") === 0) declared[property] = true;
@@ -1818,7 +1865,7 @@ export const REACTIVE_RUNTIME = String.raw`
var next = markup == null ? "" : String(markup); var next = markup == null ? "" : String(markup);
if (htmlNode.innerHTML !== next) { if (htmlNode.innerHTML !== next) {
htmlNode.innerHTML = next; htmlNode.innerHTML = trustedHTML(next);
} }
}); });
runHtml(); runHtml();
@@ -2002,7 +2049,7 @@ export const REACTIVE_RUNTIME = String.raw`
} }
function clearControlContent() { function clearControlContent() {
if (!rangeEnd) { block.innerHTML = ""; return; } if (!rangeEnd) { block.innerHTML = trustedHTML(""); return; }
while (block.nextSibling && block.nextSibling !== rangeEnd) { while (block.nextSibling && block.nextSibling !== rangeEnd) {
block.parentNode.removeChild(block.nextSibling); block.parentNode.removeChild(block.nextSibling);
} }
@@ -2010,7 +2057,7 @@ export const REACTIVE_RUNTIME = String.raw`
function appendControlContent(markup, locals) { function appendControlContent(markup, locals) {
var template = document.createElement("template"); var template = document.createElement("template");
template.innerHTML = markup || ""; template.innerHTML = trustedHTML(markup || "");
var fragment = template.content; var fragment = template.content;
var elements = toArray(fragment.childNodes).filter(function (node) { var elements = toArray(fragment.childNodes).filter(function (node) {
return node.nodeType === 1; return node.nodeType === 1;
@@ -2399,7 +2446,7 @@ export const REACTIVE_RUNTIME = String.raw`
var markup = value == null ? "" : String(value); var markup = value == null ? "" : String(value);
if (node.innerHTML !== markup) { if (node.innerHTML !== markup) {
node.innerHTML = markup; node.innerHTML = trustedHTML(markup);
} }
}); });
}); });
@@ -2775,9 +2822,15 @@ export const REACTIVE_RUNTIME = String.raw`
// server forwards these markers onto the rendered child root; evaluate // server forwards these markers onto the rendered child root; evaluate
// them here and write changes into the child's prop signals. // them here and write changes into the child's prop signals.
toArray(el.querySelectorAll("*")) toArray(el.querySelectorAll("*"))
.filter(isScopeRoot) .filter(function (node) {
return toArray(node.attributes).some(function (attr) {
return attr.name.indexOf("data-wrn-prop-bind-") === 0;
});
})
.forEach(function (node) { .forEach(function (node) {
if (!node.parentNode || ownerScope(node.parentNode) !== el) return; var targetScope = isScopeRoot(node) ? node : ownerScope(node);
if (!targetScope || targetScope === el) return;
if (!targetScope.parentNode || ownerScope(targetScope.parentNode) !== el) return;
toArray(node.attributes).forEach(function (attr) { toArray(node.attributes).forEach(function (attr) {
if (attr.name.indexOf("data-wrn-prop-bind-") !== 0) return; if (attr.name.indexOf("data-wrn-prop-bind-") !== 0) return;
var binding = pairBinding(attr.value); var binding = pairBinding(attr.value);
@@ -2796,9 +2849,9 @@ export const REACTIVE_RUNTIME = String.raw`
}); });
} catch (_) { return; } } catch (_) { return; }
var apply = function () { var apply = function () {
if (node.__wrnexusScopeApi) node.__wrnexusScopeApi.set(propName, value); if (targetScope.__wrnexusScopeApi) targetScope.__wrnexusScopeApi.set(propName, value);
}; };
if (node.__wrnexusScopeApi) apply(); if (targetScope.__wrnexusScopeApi) apply();
else queueMicrotask(apply); else queueMicrotask(apply);
}); });
}); });
+55
View File
@@ -62,6 +62,13 @@ test("split runtime hydrates a controller only from the controller asset", () =>
expect(win.document.activeElement).toBe(buttons[1]); expect(win.document.activeElement).toBe(buttons[1]);
}); });
test("production hydration uses Trusted Types for HTML and controller URLs", () => {
const runtime = getReactiveRuntime();
expect(runtime).toContain('createPolicy("wrnexus"');
expect(runtime).toContain('template.innerHTML = trustedHTML(markup || "")');
expect(runtime).toContain("script.src = trustedScriptURL(componentControllerUrl)");
});
test("a page without controller markers does not request the controller asset", () => { test("a page without controller markers does not request the controller asset", () => {
const win = mount( const win = mount(
`<main data-scope="count: 1"><span>{count}</span></main>`, `<main data-scope="count: 1"><span>{count}</span></main>`,
@@ -454,6 +461,22 @@ test("parent state updates a mounted child's reactive prop", async () => {
expect(win.document.querySelector("#child-value")?.textContent).toBe("2"); expect(win.document.querySelector("#child-value")?.textContent).toBe("2");
}); });
test("a prop marker forwarded onto a child control still updates the child scope", async () => {
const win = mount(
`<div data-scope="enabled: false">` +
`<button data-on-click="enabled = true">enable</button>` +
`<div data-scope="disabled: true">` +
`<button id="child" disabled data-wrn-bind-0='["disabled","{disabled}"]' ` +
`data-wrn-prop-bind-0='["disabled","{!enabled}"]'>child</button>` +
`</div></div>`,
);
await Promise.resolve();
const buttons = win.document.querySelectorAll("button");
expect((buttons[1] as unknown as HTMLButtonElement).disabled).toBe(true);
(buttons[0] as unknown as HTMLElement).click();
expect((buttons[1] as unknown as HTMLButtonElement).disabled).toBe(false);
});
test("independent signals in one scope update correctly (dependency tracking)", () => { test("independent signals in one scope update correctly (dependency tracking)", () => {
const win = mount( const win = mount(
`<div data-scope="a: 0, b: 100"> `<div data-scope="a: 0, b: 100">
@@ -1065,6 +1088,38 @@ test("development runtime accepts component-local and inline wrn variables", ()
expect(warnings).toHaveLength(0); expect(warnings).toHaveLength(0);
}); });
test("development runtime ignores fallback and inactive component variables", () => {
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.head.innerHTML =
`<style>.visible { color: var(--wrn-optional, red); } ` +
`.not-rendered { color: var(--wrn-inactive); }</style>`;
win.document.body.innerHTML = `<div class="visible" data-scope=""></div>`;
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
(globalThis as Record<string, unknown>).location = win.location;
(globalThis as Record<string, unknown>).NodeFilter = (
win as unknown as { NodeFilter: unknown }
).NodeFilter;
(globalThis as Record<string, unknown>).MutationObserver = (
win as unknown as { MutationObserver: unknown }
).MutationObserver;
(globalThis as Record<string, unknown>).CustomEvent = (
win as unknown as { CustomEvent: unknown }
).CustomEvent;
const warnings: unknown[][] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => warnings.push(args);
try {
(0, eval)(getReactiveRuntime(true));
(
win as unknown as { __wrnexusHydrateScopes?: (root: unknown) => void }
).__wrnexusHydrateScopes?.(win.document);
} finally {
console.warn = originalWarn;
}
expect(warnings).toHaveLength(0);
});
test("production runtime strips development diagnostics", () => { test("production runtime strips development diagnostics", () => {
const production = getReactiveRuntime(); const production = getReactiveRuntime();
expect(production).not.toContain("WRN-DEV-"); expect(production).not.toContain("WRN-DEV-");
+10 -2
View File
@@ -15,9 +15,17 @@ function fakeDb() {
test("seed helpers parameterize object rows and removals", async () => { test("seed helpers parameterize object rows and removals", async () => {
const { db, calls } = fakeDb(); const { db, calls } = fakeDb();
await addSeedData(db, [{ code: "free", credits: 10 }, { code: "pro", credits: 20 }], "plans", { await addSeedData(
db,
[
{ code: "free", credits: 10 },
{ code: "pro", credits: 20 },
],
"plans",
{
conflict: "ignore", conflict: "ignore",
}); },
);
await removeSeedData(db, { code: "free" }, "plans"); await removeSeedData(db, { code: "free" }, "plans");
expect(calls[0]).toEqual({ expect(calls[0]).toEqual({
sql: "INSERT OR IGNORE INTO plans (code, credits) VALUES (?, ?)", sql: "INSERT OR IGNORE INTO plans (code, credits) VALUES (?, ?)",
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.50", "version": "0.8.56",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
@@ -25,6 +25,7 @@
"@wrnexus/uploader": "workspace:*", "@wrnexus/uploader": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
"@wrnexus/queue": "workspace:*", "@wrnexus/queue": "workspace:*",
"@wrnexus/react": "workspace:*",
"@wrnexus/store": "workspace:*", "@wrnexus/store": "workspace:*",
"@wrnexus/security": "workspace:*", "@wrnexus/security": "workspace:*",
"@wrnexus/observability": "workspace:*", "@wrnexus/observability": "workspace:*",
+6
View File
@@ -367,6 +367,12 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
externalRoutes: pluginContributions.routes, externalRoutes: pluginContributions.routes,
middlewareFiles: pluginContributions.middleware, middlewareFiles: pluginContributions.middleware,
}); });
// UI and plugin components are intentionally registered application-wide;
// using them without a source import is not an implicit-import diagnostic.
setCompileImportOptions(appRoot, {
...importConfig,
globalComponents: router.components.map((component) => component.name),
});
const styleEntry = opts.styleEntry ?? null; const styleEntry = opts.styleEntry ?? null;
const devToolbarConfig = resolveDevToolbarConfig(mode, opts.devToolbar); const devToolbarConfig = resolveDevToolbarConfig(mode, opts.devToolbar);
+18 -3
View File
@@ -86,6 +86,7 @@ interface CompileImportOptions {
mode: ImportMode; mode: ImportMode;
aliases: Record<string, string>; aliases: Record<string, string>;
autoImport: boolean; autoImport: boolean;
globalComponents: Set<string>;
} }
const compileImportOptions = new Map<string, CompileImportOptions>(); const compileImportOptions = new Map<string, CompileImportOptions>();
const warnedImportDiagnostics = new Set<string>(); const warnedImportDiagnostics = new Set<string>();
@@ -104,12 +105,18 @@ export function setDevCompilerPipeline(pipeline: DevCompilerPipeline | null): vo
export function setCompileImportOptions( export function setCompileImportOptions(
appRoot: string, appRoot: string,
options: { mode?: ImportMode; aliases?: Record<string, string>; autoImport?: boolean } = {}, options: {
mode?: ImportMode;
aliases?: Record<string, string>;
autoImport?: boolean;
globalComponents?: Iterable<string>;
} = {},
): void { ): void {
compileImportOptions.set(resolve(appRoot), { compileImportOptions.set(resolve(appRoot), {
mode: options.mode ?? "compatible", mode: options.mode ?? "compatible",
aliases: { "@": "./app", ...(options.aliases ?? {}) }, aliases: { "@": "./app", ...(options.aliases ?? {}) },
autoImport: options.autoImport ?? true, autoImport: options.autoImport ?? true,
globalComponents: new Set(options.globalComponents ?? []),
}); });
} }
@@ -141,11 +148,18 @@ function importOptionsHash(file: string): string {
mode: "compatible" as const, mode: "compatible" as const,
aliases: { "@": "./app" }, aliases: { "@": "./app" },
autoImport: true, autoImport: true,
globalComponents: new Set<string>(),
}; };
const aliases = Object.fromEntries( const aliases = Object.fromEntries(
Object.entries(options.aliases).sort(([left], [right]) => left.localeCompare(right)), Object.entries(options.aliases).sort(([left], [right]) => left.localeCompare(right)),
); );
return hashPath(JSON.stringify({ ...options, aliases })); return hashPath(
JSON.stringify({
...options,
aliases,
globalComponents: [...options.globalComponents].sort(),
}),
);
} }
/** Island bundles already built this dev session, keyed by resolved source. */ /** Island bundles already built this dev session, keyed by resolved source. */
@@ -493,7 +507,8 @@ function validateConfiguredImports(_source: string, ast: PageAst, file: string):
"Transition", "Transition",
]); ]);
const missing = [...usedComponents].filter( const missing = [...usedComponents].filter(
(name) => !compilerBuiltins.has(name) && !imported.has(name), (name) =>
!compilerBuiltins.has(name) && !options.globalComponents.has(name) && !imported.has(name),
); );
if (ast.layoutIsSymbol && ast.layout && !imported.has(ast.layout)) missing.push(ast.layout); if (ast.layoutIsSymbol && ast.layout && !imported.has(ast.layout)) missing.push(ast.layout);
if (!missing.length) return; if (!missing.length) return;
+30 -2
View File
@@ -1739,6 +1739,15 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
// Props may carry `{t:key}` i18n markers — resolve them for the active // Props may carry `{t:key}` i18n markers — resolve them for the active
// language before handing them to the component. // language before handing them to the component.
const props = resolveTProps(parseComponentProps(attrStr!), translate); const props = resolveTProps(parseComponentProps(attrStr!), translate);
// Client control templates retain reactive expressions (for example
// `metrics="{metrics}"`) because their parent state is not available
// while we pre-render the inert branch metadata. Do not feed those
// expression strings into typed component coercion. Rendering with the
// component defaults creates the real child boundary; the bridged prop
// binding synchronizes the live value when the branch is instantiated.
for (const [key, value] of Object.entries(props)) {
if (/^\{[\s\S]*\}$/.test(value) && !/^\{t:/.test(value)) delete props[key];
}
if (normalizedName === "languageswitcher" && deps.i18n) { if (normalizedName === "languageswitcher" && deps.i18n) {
props.locales ??= JSON.stringify( props.locales ??= JSON.stringify(
deps.i18n.langs.map((locale) => ({ deps.i18n.langs.map((locale) => ({
@@ -1769,7 +1778,20 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
}, },
) )
: renderComponent(); : renderComponent();
result += await renderComponents(rendered, translate, language, depth + 1); // Prop bindings are authored by the parent mount but consumed by the
// rendered child's scope. The mount itself is replaced during SSR, so
// carry those reserved attributes onto the child's scope root.
const propBindings = Array.from(
attrStr!.matchAll(/\s(data-wrn-prop-bind-[A-Za-z0-9_-]+(?:="[^"]*")?)/g),
(match) => ` ${match[1]}`,
).join("");
const bridged = propBindings
? rendered.replace(
/(<[A-Za-z][A-Za-z0-9-]*\b[^>]*\bdata-scope="[^"]*")/,
`$1${propBindings}`,
)
: rendered;
result += await renderComponents(bridged, translate, language, depth + 1);
} catch (err) { } catch (err) {
console.error(`[wrnexus] component '${name}' failed to render`, err); console.error(`[wrnexus] component '${name}' failed to render`, err);
deps.devToolbar?.collector.add( deps.devToolbar?.collector.add(
@@ -1950,6 +1972,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
).getOrLoad(`route:${matched.route.raw}`, () => load(pageCtx)); ).getOrLoad(`route:${matched.route.raw}`, () => load(pageCtx));
} }
(pageCtx as Context & { data?: unknown }).data = data; (pageCtx as Context & { data?: unknown }).data = data;
if (data instanceof Response) return data;
if (data && typeof data === "object") Object.assign(pageCtx, data); if (data && typeof data === "object") Object.assign(pageCtx, data);
} }
let body = await renderComponents(String(await component(pageCtx)), ctx.t, ctx.lang); let body = await renderComponents(String(await component(pageCtx)), ctx.t, ctx.lang);
@@ -2668,7 +2691,12 @@ export function parseComponentProps(attrStr: string): Record<string, string> {
const props: Record<string, string> = {}; const props: Record<string, string> = {};
for (const m of attrStr.matchAll(/([A-Za-z_][\w-]*)(?:="([^"]*)")?/g)) { for (const m of attrStr.matchAll(/([A-Za-z_][\w-]*)(?:="([^"]*)")?/g)) {
const key = m[1]!; const key = m[1]!;
if (key === "data-component") continue; if (
key === "data-component" ||
key.startsWith("data-wrn-bind-") ||
key.startsWith("data-wrn-prop-bind-")
)
continue;
props[key] = decodeHtmlEntities(m[2] ?? ""); props[key] = decodeHtmlEntities(m[2] ?? "");
} }
return props; return props;
@@ -90,6 +90,92 @@ test("a component in an initially hidden client branch is server-prepared for in
expect(branches[0].body).not.toContain("data-component="); expect(branches[0].body).not.toContain("data-component=");
}); });
test("typed components in client branches use defaults until reactive props are available", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-component-reactive-branch-"));
roots.push(root);
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
mkdirSync(join(app, "components"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n");
writeFileSync(join(app, "components/Panel.wrn"), "component Panel { view { <section /> } }\n");
const marker = "[&quot;items&quot;,&quot;{items}&quot;]";
const definition = Buffer.from(
JSON.stringify([
{
cond: "open",
body: `<div data-component="Panel" items="{items}" visible="{true}" data-wrn-prop-bind-0="${marker}"></div>`,
},
]),
"utf8",
).toString("base64");
let observed: Record<string, string> | undefined;
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
loadModule: async (file) =>
basename(file) === "Panel.wrn"
? {
render: (props: Record<string, string>) => {
observed = props;
return '<section data-scope="items: [], visible: false" data-wrn-scope="e30="></section>';
},
}
: {
default: () =>
`<template data-wrn-if="${definition}"></template><template data-wrn-control-end></template>`,
},
getMiddleware: async () => [],
assets: { serve: async () => null },
} satisfies RuntimeDeps);
const response = await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
});
const html = await response!.text();
expect(observed).toEqual({});
const encoded = /data-wrn-if="([A-Za-z0-9+/=]+)"/.exec(html)?.[1];
const branches = JSON.parse(Buffer.from(encoded!, "base64").toString("utf8"));
expect(branches[0].body).toContain("<section");
expect(branches[0].body).toContain("data-wrn-prop-bind-0");
expect(branches[0].body).not.toContain("data-component=");
});
test("SSR carries a parent prop binding onto the rendered child scope", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-component-prop-"));
roots.push(root);
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
mkdirSync(join(app, "components"), { recursive: true });
writeFileSync(join(app, "pages/index.ts"), "export default () => '';\n");
writeFileSync(
join(app, "components/Child.wrn"),
"component Child { props { value = 0 } view { <b>{value}</b> } }\n",
);
const marker = "[&quot;value&quot;,&quot;{count}&quot;]";
const handlers = createHandlers({
mode: "development",
hmr: false,
router: buildRouter(app),
loadModule: async (file) =>
basename(file) === "Child.wrn"
? { render: () => '<div data-scope="value: 1" data-wrn-scope="e30="><b>1</b></div>' }
: {
default: () =>
`<div data-component="Child" value="1" data-wrn-prop-bind-0="${marker}"></div>`,
},
getMiddleware: async () => [],
assets: { serve: async () => null },
} satisfies RuntimeDeps);
const response = await handlers.fetch(new Request("https://example.test/"), {
upgrade: () => false,
});
const html = await response!.text();
expect(html).toContain(`data-wrn-prop-bind-0="${marker}"`);
expect(html).not.toContain('data-component="Child"');
});
test("translated text is present in the raw server response", async () => { test("translated text is present in the raw server response", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-i18n-")); const root = mkdtempSync(join(tmpdir(), "wrnexus-ssr-i18n-"));
roots.push(root); roots.push(root);
@@ -71,6 +71,13 @@ test("compiler-native reactive elements do not require application imports", ()
expect(() => compileWrnArtifacts(page, 5)).not.toThrow(); expect(() => compileWrnArtifacts(page, 5)).not.toThrow();
}); });
test("globally registered plugin components do not require source imports", () => {
const { root, page } = fixture();
writeFileSync(page, "page Home { view { <SignIn /> } }");
setCompileImportOptions(root, { mode: "explicit", globalComponents: ["SignIn"] });
expect(() => compileWrnArtifacts(page, 6)).not.toThrow();
});
test("component examples in comments do not require imports", () => { test("component examples in comments do not require imports", () => {
const { root, page } = fixture(); const { root, page } = fixture();
writeFileSync( writeFileSync(
@@ -32,6 +32,32 @@ test("server loader data is available to page rendering", async () => {
expect(await response!.text()).toContain("Ada,Lin / 2"); expect(await response!.text()).toContain("Ada,Lin / 2");
}); });
test("a server loader can return a redirect Response", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-load-redirect-"));
roots.push(root);
const app = join(root, "app");
mkdirSync(join(app, "pages"), { recursive: true });
writeFileSync(join(app, "pages/private.ts"), "export default () => '';\n");
const handlers = createHandlers({
mode: "production",
hmr: false,
router: buildRouter(app),
loadModule: async () => ({
__wrnexusLoad: async () => Response.redirect("https://example.test/login", 303),
default: () => {
throw new Error("redirected pages must not render");
},
}),
getMiddleware: async () => [],
assets: { serve: async () => null },
} satisfies RuntimeDeps);
const response = await handlers.fetch(new Request("https://example.test/private"), {
upgrade: () => false,
});
expect(response?.status).toBe(303);
expect(response?.headers.get("location")).toBe("https://example.test/login");
});
test("HMR page synchronization initializes request-scoped loader caching", async () => { test("HMR page synchronization initializes request-scoped loader caching", async () => {
const root = mkdtempSync(join(tmpdir(), "wrnexus-hmr-load-runtime-")); const root = mkdtempSync(join(tmpdir(), "wrnexus-hmr-load-runtime-"));
roots.push(root); roots.push(root);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.13", "version": "0.8.16",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
@@ -0,0 +1,41 @@
import { expect, test } from "bun:test";
import { createServerIssue, issueFromError } from "../../src/server/issues.ts";
test("server issues have stable fingerprints and retain actionable error context", () => {
const input = {
ruleId: "server/query",
category: "server" as const,
severity: "warning" as const,
title: "Slow query",
message: "Query exceeded budget",
pathname: "/orders",
source: { file: "app/api/orders.ts", line: 12, column: 4 },
};
const first = createServerIssue(input);
const second = createServerIssue(input);
expect(first.id).not.toBe(second.id);
expect(first.fingerprint).toBe(second.fingerprint);
expect(first.metadata?.pathname).toBe("/orders");
const error = issueFromError(new Error("database offline"), { pathname: "/orders" });
expect(error.severity).toBe("error");
expect(error.message).toBe("database offline");
expect(error.metadata?.stack).toContain("database offline");
expect(error.recommendation).toContain("stack trace");
});
test("non-Error throws are normalized without losing caller context", () => {
const issue = issueFromError(
{ reason: "offline" },
{
ruleId: "server/dependency",
title: "Dependency failed",
pathname: "/health",
source: { file: "app/api/health.ts", line: 4 },
},
);
expect(issue.ruleId).toBe("server/dependency");
expect(issue.title).toBe("Dependency failed");
expect(issue.message).toBe("[object Object]");
expect(issue.fingerprint).toContain("app/api/health.ts:4:0");
});
+2 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mail", "name": "@wrnexus/mail",
"version": "0.8.1", "version": "0.8.3",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
@@ -8,6 +8,7 @@
".": "./src/index.ts" ".": "./src/index.ts"
}, },
"dependencies": { "dependencies": {
"@wrnexus/encryption": "workspace:*",
"@wrnexus/queue": "workspace:*" "@wrnexus/queue": "workspace:*"
} }
} }
+114 -5
View File
@@ -1,4 +1,5 @@
import type { DefinedJob } from "@wrnexus/queue"; import type { DefinedJob } from "@wrnexus/queue";
import { createKeyring, open, seal, type EncryptionKeyring } from "@wrnexus/encryption";
export interface MailMessage { export interface MailMessage {
from?: string; from?: string;
@@ -14,6 +15,41 @@ export interface MailDriver {
test?(): Promise<{ ok: boolean; error?: string }>; test?(): Promise<{ ok: boolean; error?: string }>;
} }
export interface SealedMailDriverOptions {
/** Ciphertext stored by the application; plaintext is never retained here. */
sealedCredential: string;
unseal(value: string): Promise<string>;
create(credential: string): MailDriver | Promise<MailDriver>;
/** Cache the initialized driver. Defaults to true. */
cache?: boolean;
}
/**
* Lazily opens a sealed credential only at the transport boundary. This keeps
* encryption policy and SMTP/vendor choice independent while preventing every
* app from rebuilding the same decrypt-on-send lifecycle.
*/
export function sealedMailDriver(options: SealedMailDriverOptions): MailDriver {
let cached: Promise<MailDriver> | undefined;
const create = async () => options.create(await options.unseal(options.sealedCredential));
const driver = () =>
options.cache === false
? create()
: (cached ??= create().catch((error) => {
cached = undefined;
throw error;
}));
return {
async send(message) {
return (await driver()).send(message);
},
async test() {
const resolved = await driver();
return resolved.test?.() ?? { ok: true };
},
};
}
export interface MailTemplate<T = Record<string, unknown>> { export interface MailTemplate<T = Record<string, unknown>> {
subject(data: T): string; subject(data: T): string;
html?(data: T): string; html?(data: T): string;
@@ -26,14 +62,87 @@ export interface MailOptions {
sandbox?: { enabled?: boolean; allowlist?: readonly string[] }; sandbox?: { enabled?: boolean; allowlist?: readonly string[] };
} }
export function defineMail(options: MailOptions) { export interface MailSandboxDecision {
allowed: boolean;
reason?: string;
}
/** Fail-closed, case-insensitive sandbox policy shared by mail workers and defineMail. */
export function mailSandboxDecision(
sandbox: { enabled?: boolean | number; allowlist?: readonly string[] | string },
recipient: unknown,
): MailSandboxDecision {
if (!sandbox.enabled) return { allowed: true };
if (typeof recipient !== "string" || !recipient.trim()) {
return {
allowed: false,
reason: "sandbox mode is on and no valid recipient address was given",
};
}
let source: unknown = sandbox.allowlist ?? [];
if (typeof source === "string") {
try {
source = JSON.parse(source || "[]");
} catch {
source = [];
}
}
const allowed = new Set( const allowed = new Set(
(options.sandbox?.allowlist ?? []).map((value) => value.trim().toLowerCase()), (Array.isArray(source) ? source : [])
.filter((value): value is string => typeof value === "string")
.map((value) => value.trim().toLowerCase())
.filter(Boolean),
); );
const normalized = recipient.trim().toLowerCase();
return allowed.has(normalized)
? { allowed: true }
: {
allowed: false,
reason: `sandbox mode is on and ${recipient} is not in the sandbox allow-list`,
};
}
export interface SealedMailCredentialsOptions {
/** Environment variable holding a base64 AES key. */
env?: string;
keyId?: string;
}
/** Owns validated, lazy encryption-key setup for credentials stored at rest. */
export function defineSealedMailCredentials(options: SealedMailCredentialsOptions = {}) {
const env = options.env ?? "APP_ENCRYPTION_KEY";
let cached: EncryptionKeyring | undefined;
const keyring = () => {
if (cached) return cached;
const secret = process.env[env];
if (!secret)
throw new Error(`${env} is not set; it is required to seal mail credentials at rest.`);
let length: number;
try {
length = atob(secret).length;
} catch {
throw new Error(`${env} must be a valid base64-encoded 16, 24 or 32 byte key.`);
}
if (![16, 24, 32].includes(length)) {
throw new Error(
`${env} must decode to 16, 24 or 32 bytes; the current value decodes to ${length}.`,
);
}
return (cached = createKeyring([{ id: options.keyId ?? "primary", secret, active: true }]));
};
return {
keyring,
seal: (plain: string) => seal(plain, keyring()),
open: (sealed: string) => open(sealed, keyring()),
};
}
export function defineMail(options: MailOptions) {
const assertAllowed = (recipients: readonly string[]) => { const assertAllowed = (recipients: readonly string[]) => {
if (!options.sandbox?.enabled) return; for (const recipient of recipients) {
const denied = recipients.find((recipient) => !allowed.has(recipient.trim().toLowerCase())); const decision = mailSandboxDecision(options.sandbox ?? {}, recipient);
if (denied) throw new Error(`WRN-MAIL-SANDBOX: recipient '${denied}' is not allow-listed`); if (!decision.allowed) throw new Error(`WRN-MAIL-SANDBOX: ${decision.reason}`);
}
}; };
return { return {
async send(message: MailMessage) { async send(message: MailMessage) {
+56 -1
View File
@@ -1,5 +1,11 @@
import { expect, test } from "bun:test"; import { expect, test } from "bun:test";
import { defineMail, defineMailTemplate } from "../src/index.ts"; import {
defineMail,
defineMailTemplate,
defineSealedMailCredentials,
mailSandboxDecision,
sealedMailDriver,
} from "../src/index.ts";
test("mail templates render and sandbox blocks accidental recipients", async () => { test("mail templates render and sandbox blocks accidental recipients", async () => {
const sent: unknown[] = []; const sent: unknown[] = [];
@@ -16,3 +22,52 @@ test("mail templates render and sandbox blocks accidental recipients", async ()
expect(sent).toHaveLength(1); expect(sent).toHaveLength(1);
await expect(mail.send({ to: "real@example.com", subject: "no" })).rejects.toThrow("SANDBOX"); await expect(mail.send({ to: "real@example.com", subject: "no" })).rejects.toThrow("SANDBOX");
}); });
test("mail owns fail-closed sandbox decisions and sealed credential setup", async () => {
expect(
mailSandboxDecision({ enabled: 1, allowlist: '["Test@Example.com"]' }, "test@example.com"),
).toEqual({ allowed: true });
expect(
mailSandboxDecision({ enabled: true, allowlist: "not-json" }, "x@example.com").allowed,
).toBe(false);
const previous = process.env.TEST_MAIL_ENCRYPTION_KEY;
process.env.TEST_MAIL_ENCRYPTION_KEY = btoa(String.fromCharCode(...new Uint8Array(32).fill(7)));
try {
const credentials = defineSealedMailCredentials({ env: "TEST_MAIL_ENCRYPTION_KEY" });
const sealed = await credentials.seal("smtp-secret");
expect(sealed).not.toContain("smtp-secret");
expect(await credentials.open(sealed)).toBe("smtp-secret");
} finally {
if (previous === undefined) delete process.env.TEST_MAIL_ENCRYPTION_KEY;
else process.env.TEST_MAIL_ENCRYPTION_KEY = previous;
}
});
test("sealed drivers decrypt lazily once and delegate send/test", async () => {
let opens = 0;
const sent: string[] = [];
const driver = sealedMailDriver({
sealedCredential: "ciphertext",
async unseal(value) {
opens++;
expect(value).toBe("ciphertext");
return "plaintext-secret";
},
create(credential) {
expect(credential).toBe("plaintext-secret");
return {
async send(message) {
sent.push(message.subject);
},
async test() {
return { ok: true };
},
};
},
});
expect(opens).toBe(0);
await driver.send({ to: "safe@example.test", subject: "one" });
expect(await driver.test?.()).toEqual({ ok: true });
expect(opens).toBe(1);
expect(sent).toEqual(["one"]);
});
+40
View File
@@ -0,0 +1,40 @@
# @wrnexus/metering
Framework-native entitlement catalogs, credit packs, and usage metering for WrNexus applications.
The package keeps policy separate from persistence: applications provide a `MeterStore`, while the metering facade validates unit amounts and reports insufficient balances separately from storage faults. All amounts are positive safe integers except explicit adjustments, which may be positive or negative but never zero.
## Usage
```ts
import { defineEntitlements, defineMeter, definePacks } from "@wrnexus/metering";
const entitlements = defineEntitlements({
plans: () => [
{ code: "free", name: "Free", features: [], allowance: 10 },
{ code: "pro", name: "Pro", features: ["exports"], allowance: 1_000 },
],
subscriptionFor: async (subjectId) => subscriptions.planFor(subjectId),
fallback: "free",
});
const packs = definePacks([
{ code: "starter", units: 100 },
{ code: "plus", units: 500 },
]);
const meter = defineMeter({ store });
if (await entitlements.enabled("user-1", "exports")) {
const result = await meter.reserve("user-1", 1, "export report");
if (!result.ok) console.error(result.reason);
}
```
## API
- `defineEntitlements(options)` resolves a subject's plan, features, and allowance from a server-owned catalog, with an explicit fallback plan.
- `definePacks(entries)` creates an immutable, prototype-safe lookup of purchasable unit packs. Clients select a pack code rather than supplying an amount.
- `defineMeter(options)` exposes `balance`, `reserve`, `grant`, `purchase`, `refund`, and `adjust` operations over an application-provided store.
Meter operations return `{ ok: true }` on success or `{ ok: false, reason, fault? }` on refusal. A result with `fault: true` identifies an underlying storage error; use `onFault` for operational reporting without exposing exception-based control flow to callers.
+7
View File
@@ -0,0 +1,7 @@
# @wrnexus/payment
Gateway-neutral, webhook-authoritative payments for WrNexus. Amounts are integer minor units. Every initialize and refund operation requires an idempotency key. The package never accepts or stores card numbers, CVVs, or expiry input; its components navigate to provider-hosted checkout.
Built-in adapters: offline `sandbox`, Stripe, Razorpay, and PayPal. Applications may add adapters with `defineGateway`. Capabilities are explicit, unsupported operations are refused, signed webhooks are mandatory in production, and package-owned migrations provision the append-only event ledger.
Webhook events are deduplicated by the provider's event ID and reduced in provider occurrence/sequence order. Captured and refund states are monotonic, so a delayed retry of an older event cannot regress a terminal payment.
+8
View File
@@ -0,0 +1,8 @@
component PayNow {
props { checkoutUrl: string label?: string = "Pay now" disabled?: boolean = false }
view {
<a class="wrn-payment-button" href={checkoutUrl} aria-disabled={disabled}>
{label}
</a>
}
}
@@ -0,0 +1,4 @@
component PaymentHistory {
props { payments: array = [] }
view { <ol class="wrn-payment-history">{#each payments as payment}<li>{payment.currency} {payment.amount} — {payment.status}</li>{/each}</ol> }
}
@@ -0,0 +1,4 @@
component PaymentMethodList {
props { methods: array = [] }
view { <ul class="wrn-payment-methods">{#each methods as method}<li>{method.brand} ending {method.last4}</li>{/each}</ul> }
}
@@ -0,0 +1,4 @@
component PaymentSheet {
props { checkoutUrl: string gateway: string }
view { <section class="wrn-payment-sheet" data-gateway={gateway}><p>Secure payment is completed on the payment provider.</p><a href={checkoutUrl}>Continue securely</a></section> }
}
@@ -0,0 +1,4 @@
component PaymentStatus {
props { status: string label?: string = "Payment status" }
view { <div class="wrn-payment-status" role="status"><strong>{label}</strong><span>{status}</span></div> }
}
@@ -0,0 +1,4 @@
component PaymentSummary {
props { captured: number = 0 refunded: number = 0 net: number = 0 currency: string }
view { <dl><dt>Captured</dt><dd>{currency} {captured}</dd><dt>Refunded</dt><dd>{currency} {refunded}</dd><dt>Net</dt><dd>{currency} {net}</dd></dl> }
}
@@ -0,0 +1,4 @@
component RefundButton {
props { paymentId: string refundable: number partial?: boolean = false action?: string = "/api/payments/refund" }
view { <form method="post" action={action}><input type="hidden" name="paymentId" value={paymentId} /><label>Reason<input name="reason" required /></label>{#if partial}<label>Amount<input name="amount" type="number" min="1" max={refundable} /></label>{/if}<button type="submit">Refund</button></form> }
}
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@wrnexus/payment",
"version": "0.8.1",
"private": true,
"type": "module",
"main": "src/index.ts",
"files": [
"src",
"components",
"README.md"
],
"exports": {
".": "./src/index.ts",
"./adapters": "./src/adapters.ts",
"./plugin": "./src/plugin.ts",
"./runtime-webhook": "./src/runtime-webhook.ts"
},
"dependencies": {
"@wrnexus/db": "workspace:*",
"@wrnexus/plugin": "workspace:*"
}
}
+556
View File
@@ -0,0 +1,556 @@
import { defineGateway } from "./core.ts";
import type {
GatewayCapabilities,
GatewayIntent,
GatewayRefund,
PaymentGateway,
PaymentStatus,
} from "./types.ts";
const encoder = new TextEncoder();
const hex = (bytes: ArrayBuffer) =>
[...new Uint8Array(bytes)].map((value) => value.toString(16).padStart(2, "0")).join("");
async function hmac(body: string, secret: string) {
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
return hex(await crypto.subtle.sign("HMAC", key, encoder.encode(body)));
}
function equal(left: string, right: string) {
if (left.length !== right.length) return false;
let difference = 0;
for (let i = 0; i < left.length; i++) difference |= left.charCodeAt(i) ^ right.charCodeAt(i);
return difference === 0;
}
const CAPABILITIES: GatewayCapabilities = {
authorizeThenCapture: false,
partialRefund: true,
multipleRefunds: true,
storedMethods: false,
customerVault: false,
hostedFields: false,
hostedCheckout: true,
webhookSignature: true,
payouts: false,
disputes: false,
};
const statusOf = (value: string): PaymentStatus =>
(({
succeeded: "captured",
captured: "captured",
paid: "captured",
authorized: "authorized",
requires_capture: "authorized",
cancelled: "cancelled",
canceled: "cancelled",
failed: "failed",
refunded: "refunded",
})[value] as PaymentStatus | undefined) ?? "pending";
export interface SandboxOptions {
secret?: string;
failure?: "create" | "refund" | "capture";
authorize?: boolean;
}
export function sandboxGateway(options: SandboxOptions = {}): PaymentGateway {
const intents = new Map<string, GatewayIntent>();
const refunds = new Map<string, GatewayRefund>();
return defineGateway({
id: "sandbox",
capabilities: {
...CAPABILITIES,
authorizeThenCapture: true,
storedMethods: true,
customerVault: true,
hostedFields: true,
},
supports: { currencies: "any", countries: "any" },
async createIntent(input) {
if (options.failure === "create") throw new Error("sandbox create failure");
const value = {
ref: `sb_${input.id}`,
status: (options.authorize || input.capture === "manual"
? "authorized"
: "pending") as PaymentStatus,
amount: input.amount,
currency: input.currency,
checkoutUrl: `https://sandbox.wrnexus.test/pay/${input.id}`,
clientSecret: `sandbox_${input.id}`,
};
intents.set(value.ref, value);
return value;
},
async fetchIntent(ref) {
const value = intents.get(ref);
if (!value) throw new Error("sandbox intent not found");
return structuredClone(value);
},
async cancelIntent(ref) {
const value = await this.fetchIntent(ref);
const next = { ...value, status: "cancelled" as const };
intents.set(ref, next);
return next;
},
async capture(ref, amount) {
if (options.failure === "capture") throw new Error("sandbox capture failure");
const value = await this.fetchIntent(ref);
const next = {
...value,
status: "captured" as const,
amount: amount?.amount ?? value.amount,
};
intents.set(ref, next);
return next;
},
async refund(input) {
if (options.failure === "refund") throw new Error("sandbox refund failure");
const existing = refunds.get(input.idempotencyKey);
if (existing) return existing;
const value = {
ref: `sbr_${crypto.randomUUID()}`,
status: "succeeded" as const,
amount: input.amount,
};
refunds.set(input.idempotencyKey, value);
return value;
},
async verifyWebhook(req, expected) {
const body = await req.text();
const signature = req.headers.get("x-wrnexus-signature") ?? "";
if (!equal(signature, await hmac(body, expected)))
throw new Error("invalid sandbox signature");
const payload = JSON.parse(body);
const intent = intents.get(payload.intentRef);
if (intent) intents.set(payload.intentRef, { ...intent, status: payload.status });
return {
id: String(payload.id),
intentRef: String(payload.intentRef),
type: String(payload.type ?? "payment.updated"),
status: payload.status,
occurredAt: Number(payload.occurredAt ?? Date.now()),
sequence: payload.sequence,
payload,
};
},
async listMethods() {
return [];
},
async attachMethod(_customer, token) {
return { ref: `sbm_${token}`, brand: "sandbox", last4: token.slice(-4) };
},
async detachMethod() {},
async createCustomer(input) {
return `sb_customer_${input.subject}`;
},
clientConfig(intent) {
return {
gateway: "sandbox",
clientSecret: intent.clientSecret ?? "",
checkoutUrl: intent.checkoutUrl ?? "",
};
},
});
}
export async function signSandboxWebhook(payload: unknown, secret = "sandbox-secret") {
const body = JSON.stringify(payload);
return { body, signature: await hmac(body, secret) };
}
async function jsonRequest(url: string, init: RequestInit) {
const response = await fetch(url, init);
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(`payment gateway request failed (${response.status})`);
return data as any;
}
export interface StripeConfig {
secretKey: string;
publishableKey: string;
webhookSecret: string;
apiVersion?: string;
}
export function stripeGateway(config: StripeConfig): PaymentGateway<StripeConfig> {
const auth = {
authorization: `Bearer ${config.secretKey}`,
"content-type": "application/x-www-form-urlencoded",
"stripe-version": config.apiVersion ?? "2026-03-31",
};
const form = (data: Record<string, unknown>) =>
new URLSearchParams(
Object.entries(data)
.filter(([, v]) => v !== undefined)
.map(([k, v]) => [k, String(v)]),
);
return defineGateway({
id: "stripe",
config,
capabilities: {
...CAPABILITIES,
authorizeThenCapture: true,
storedMethods: true,
customerVault: true,
hostedFields: true,
disputes: true,
},
supports: { currencies: "any", countries: "any" },
async createIntent(input) {
const row = await jsonRequest("https://api.stripe.com/v1/payment_intents", {
method: "POST",
headers: { ...auth, "idempotency-key": input.idempotencyKey },
body: form({
amount: input.amount,
currency: input.currency.toLowerCase(),
capture_method: input.capture === "manual" ? "manual" : "automatic",
"metadata[wrnexus_id]": input.id,
}),
});
return {
ref: row.id,
status: statusOf(row.status),
amount: row.amount,
currency: String(row.currency).toUpperCase(),
clientSecret: row.client_secret,
raw: row,
};
},
async fetchIntent(ref) {
const row = await jsonRequest(
`https://api.stripe.com/v1/payment_intents/${encodeURIComponent(ref)}`,
{ headers: auth },
);
return {
ref: row.id,
status: statusOf(row.status),
amount: row.amount,
currency: String(row.currency).toUpperCase(),
raw: row,
};
},
async cancelIntent(ref, reason) {
const row = await jsonRequest(
`https://api.stripe.com/v1/payment_intents/${encodeURIComponent(ref)}/cancel`,
{ method: "POST", headers: auth, body: form({ cancellation_reason: reason }) },
);
return {
ref: row.id,
status: "cancelled",
amount: row.amount,
currency: String(row.currency).toUpperCase(),
raw: row,
};
},
async capture(ref, amount) {
const row = await jsonRequest(
`https://api.stripe.com/v1/payment_intents/${encodeURIComponent(ref)}/capture`,
{ method: "POST", headers: auth, body: form({ amount_to_capture: amount?.amount }) },
);
return {
ref: row.id,
status: statusOf(row.status),
amount: row.amount_received,
currency: String(row.currency).toUpperCase(),
raw: row,
};
},
async refund(input) {
const row = await jsonRequest("https://api.stripe.com/v1/refunds", {
method: "POST",
headers: { ...auth, "idempotency-key": input.idempotencyKey },
body: form({
payment_intent: input.intentRef,
amount: input.amount,
reason: "requested_by_customer",
}),
});
return {
ref: row.id,
status: row.status === "succeeded" ? "succeeded" : "pending",
amount: row.amount,
raw: row,
};
},
async verifyWebhook(req, secret) {
const body = await req.text();
const header = req.headers.get("stripe-signature") ?? "";
const timestamp = /t=([^,]+)/.exec(header)?.[1] ?? "";
const signature = /v1=([^,]+)/.exec(header)?.[1] ?? "";
if (!timestamp || !equal(signature, await hmac(`${timestamp}.${body}`, secret)))
throw new Error("invalid Stripe signature");
const row = JSON.parse(body),
object = row.data.object;
return {
id: row.id,
intentRef: object.payment_intent ?? object.id,
type: row.type,
status: statusOf(object.status),
occurredAt: Number(row.created ?? timestamp) * 1000,
payload: row,
};
},
async listMethods(customerRef) {
const row = await jsonRequest(
`https://api.stripe.com/v1/payment_methods?customer=${encodeURIComponent(customerRef)}&type=card`,
{ headers: auth },
);
return (row.data ?? []).map((method: any) => ({
ref: method.id,
brand: method.card?.brand,
last4: method.card?.last4,
expires: method.card ? `${method.card.exp_month}/${method.card.exp_year}` : undefined,
}));
},
async attachMethod(customerRef, token) {
const row = await jsonRequest(
`https://api.stripe.com/v1/payment_methods/${encodeURIComponent(token)}/attach`,
{ method: "POST", headers: auth, body: form({ customer: customerRef }) },
);
return {
ref: row.id,
brand: row.card?.brand,
last4: row.card?.last4,
expires: row.card ? `${row.card.exp_month}/${row.card.exp_year}` : undefined,
};
},
async detachMethod(methodRef) {
await jsonRequest(
`https://api.stripe.com/v1/payment_methods/${encodeURIComponent(methodRef)}/detach`,
{ method: "POST", headers: auth },
);
},
async createCustomer(input) {
const row = await jsonRequest("https://api.stripe.com/v1/customers", {
method: "POST",
headers: auth,
body: form({ email: input.email, name: input.name, "metadata[subject]": input.subject }),
});
return row.id;
},
clientConfig(intent) {
return {
gateway: "stripe",
publishableKey: config.publishableKey,
clientSecret: intent.clientSecret ?? "",
};
},
});
}
export interface RazorpayConfig {
keyId: string;
keySecret: string;
webhookSecret: string;
theme?: { color?: string };
}
export function razorpayGateway(config: RazorpayConfig): PaymentGateway<RazorpayConfig> {
const auth = `Basic ${btoa(`${config.keyId}:${config.keySecret}`)}`;
const request = (path: string, init: RequestInit = {}) =>
jsonRequest(`https://api.razorpay.com/v1${path}`, {
...init,
headers: { authorization: auth, "content-type": "application/json", ...(init.headers ?? {}) },
});
return defineGateway({
id: "razorpay",
config,
capabilities: {
...CAPABILITIES,
authorizeThenCapture: false,
storedMethods: false,
customerVault: false,
},
supports: { currencies: ["INR"], countries: ["IN"] },
async createIntent(input) {
const row = await request("/orders", {
method: "POST",
body: JSON.stringify({
amount: input.amount,
currency: input.currency,
receipt: input.id,
notes: input.metadata,
}),
});
return {
ref: row.id,
status: "pending",
amount: row.amount,
currency: row.currency,
raw: row,
};
},
async fetchIntent(ref) {
const row = await request(`/orders/${encodeURIComponent(ref)}`);
return {
ref: row.id,
status: statusOf(row.status),
amount: row.amount,
currency: row.currency,
raw: row,
};
},
async cancelIntent(ref) {
const row = await this.fetchIntent(ref);
return { ...row, status: "cancelled" };
},
async refund(input) {
const row = await request(`/payments/${encodeURIComponent(input.intentRef)}/refund`, {
method: "POST",
headers: { "X-Razorpay-Idempotency": input.idempotencyKey },
body: JSON.stringify({ amount: input.amount, notes: { reason: input.reason } }),
});
return {
ref: row.id,
status: row.status === "processed" ? "succeeded" : "pending",
amount: row.amount,
raw: row,
};
},
async verifyWebhook(req, secret) {
const body = await req.text();
if (!equal(req.headers.get("x-razorpay-signature") ?? "", await hmac(body, secret)))
throw new Error("invalid Razorpay signature");
const row = JSON.parse(body),
payment = row.payload?.payment?.entity ?? row.payload?.order?.entity;
return {
id: row.id ?? `${row.event}:${payment.id}`,
intentRef: payment.order_id ?? payment.id,
type: row.event,
status: statusOf(payment.status),
occurredAt:
Number(payment.created_at ?? row.created_at ?? Math.floor(Date.now() / 1000)) * 1000,
payload: row,
};
},
clientConfig(intent) {
return {
gateway: "razorpay",
keyId: config.keyId,
orderId: intent.ref,
themeColor: config.theme?.color ?? "",
};
},
});
}
export interface PayPalConfig {
clientId: string;
clientSecret: string;
webhookId: string;
environment?: "sandbox" | "live";
}
export function paypalGateway(config: PayPalConfig): PaymentGateway<PayPalConfig> {
const base =
config.environment === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com";
let token = "";
async function headers() {
if (!token) {
const row = await jsonRequest(`${base}/v1/oauth2/token`, {
method: "POST",
headers: {
authorization: `Basic ${btoa(`${config.clientId}:${config.clientSecret}`)}`,
"content-type": "application/x-www-form-urlencoded",
},
body: "grant_type=client_credentials",
});
token = row.access_token;
}
return { authorization: `Bearer ${token}`, "content-type": "application/json" };
}
const request = async (path: string, init: RequestInit = {}) =>
jsonRequest(`${base}${path}`, {
...init,
headers: { ...(await headers()), ...(init.headers ?? {}) },
});
return defineGateway({
id: "paypal",
config,
capabilities: { ...CAPABILITIES, partialRefund: true, multipleRefunds: true },
supports: { currencies: "any", countries: "any" },
async createIntent(input) {
const row = await request("/v2/checkout/orders", {
method: "POST",
headers: { "PayPal-Request-Id": input.idempotencyKey },
body: JSON.stringify({
intent: "CAPTURE",
purchase_units: [
{
reference_id: input.id,
amount: { currency_code: input.currency, value: (input.amount / 100).toFixed(2) },
},
],
application_context: { return_url: input.returnUrl },
}),
});
return {
ref: row.id,
status: statusOf(String(row.status).toLowerCase()),
amount: input.amount,
currency: input.currency,
checkoutUrl: row.links?.find((x: any) => x.rel === "approve")?.href,
raw: row,
};
},
async fetchIntent(ref) {
const row = await request(`/v2/checkout/orders/${encodeURIComponent(ref)}`);
const unit = row.purchase_units?.[0]?.amount;
return {
ref: row.id,
status: statusOf(String(row.status).toLowerCase()),
amount: Math.round(Number(unit?.value ?? 0) * 100),
currency: unit?.currency_code ?? "USD",
raw: row,
};
},
async cancelIntent(ref) {
const row = await this.fetchIntent(ref);
return { ...row, status: "cancelled" };
},
async refund(input) {
const row = await request(
`/v2/payments/captures/${encodeURIComponent(input.intentRef)}/refund`,
{
method: "POST",
headers: { "PayPal-Request-Id": input.idempotencyKey },
body: JSON.stringify({
amount: { value: (input.amount / 100).toFixed(2), currency_code: input.currency },
note_to_payer: input.reason,
}),
},
);
return {
ref: row.id,
status: String(row.status).toUpperCase() === "COMPLETED" ? "succeeded" : "pending",
amount: input.amount,
raw: row,
};
},
async verifyWebhook(req) {
const row = await req.json();
const verification = await request("/v1/notifications/verify-webhook-signature", {
method: "POST",
body: JSON.stringify({
auth_algo: req.headers.get("paypal-auth-algo"),
cert_url: req.headers.get("paypal-cert-url"),
transmission_id: req.headers.get("paypal-transmission-id"),
transmission_sig: req.headers.get("paypal-transmission-sig"),
transmission_time: req.headers.get("paypal-transmission-time"),
webhook_id: config.webhookId,
webhook_event: row,
}),
});
if (verification.verification_status !== "SUCCESS")
throw new Error("invalid PayPal signature");
return {
id: row.id,
intentRef: row.resource?.supplementary_data?.related_ids?.order_id ?? row.resource?.id,
type: row.event_type,
status: statusOf(String(row.resource?.status ?? "").toLowerCase()),
occurredAt: Date.parse(row.create_time ?? "") || Date.now(),
payload: row,
};
},
clientConfig(intent) {
return { gateway: "paypal", clientId: config.clientId, orderId: intent.ref };
},
});
}
+118
View File
@@ -0,0 +1,118 @@
import type { CreateIntentInput, GatewayIntent, PaymentGateway } from "./types.ts";
export interface GatewayContractReport {
ok: boolean;
issues: string[];
}
/** Structural half of the shared adapter suite, usable by third-party adapters in CI. */
export function verifyGatewayContract(adapter: PaymentGateway): GatewayContractReport {
const issues: string[] = [];
const capability = (name: string, declared: boolean, present: boolean) => {
if (declared !== present)
issues.push(
`${adapter.id}: capability '${name}' is ${declared ? "declared but not implemented" : "implemented but not declared"}`,
);
};
capability(
"authorizeThenCapture",
adapter.capabilities.authorizeThenCapture,
typeof adapter.capture === "function",
);
capability(
"storedMethods",
adapter.capabilities.storedMethods,
typeof adapter.listMethods === "function" &&
typeof adapter.attachMethod === "function" &&
typeof adapter.detachMethod === "function",
);
capability(
"customerVault",
adapter.capabilities.customerVault,
typeof adapter.createCustomer === "function",
);
if (!adapter.capabilities.hostedFields && !adapter.capabilities.hostedCheckout)
issues.push(`${adapter.id}: a hosted payment surface is required`);
if (!adapter.capabilities.webhookSignature)
issues.push(`${adapter.id}: signed webhooks are required`);
return { ok: issues.length === 0, issues };
}
export interface GatewayContractFixture {
input: CreateIntentInput;
webhook(intent: GatewayIntent): Request | Promise<Request>;
}
/** Executable lifecycle contract shared by built-in and third-party adapters. */
export async function exerciseGatewayContract(
adapter: PaymentGateway,
fixture: GatewayContractFixture,
): Promise<GatewayContractReport> {
const report = verifyGatewayContract(adapter);
const issues = [...report.issues];
const check = (condition: unknown, message: string) => {
if (!condition) issues.push(`${adapter.id}: ${message}`);
};
try {
const intent = await adapter.createIntent(fixture.input);
check(Boolean(intent.ref), "createIntent returned no reference");
check(intent.amount === fixture.input.amount, "createIntent changed the amount");
check(intent.currency === fixture.input.currency, "createIntent changed the currency");
const duplicate = await adapter.createIntent(fixture.input);
check(duplicate.ref === intent.ref, "createIntent did not honor its idempotency key");
const fetched = await adapter.fetchIntent(intent.ref);
check(fetched.ref === intent.ref, "fetchIntent returned a different reference");
const client = adapter.clientConfig(intent);
const configuredSecrets = Object.entries((adapter.config ?? {}) as Record<string, unknown>)
.filter(([key, value]) => /secret|password/i.test(key) && typeof value === "string")
.map(([, value]) => value);
check(
!configuredSecrets.some((secret) => Object.values(client).includes(secret as string)),
"clientConfig exposed a configured secret",
);
if (adapter.capabilities.authorizeThenCapture) {
const captured = await adapter.capture!(intent.ref);
check(Boolean(captured.ref), "capture returned no reference");
}
const cancellable = await adapter.createIntent({
...fixture.input,
id: `${fixture.input.id}-cancel`,
idempotencyKey: `${fixture.input.idempotencyKey}:cancel`,
});
const cancelled = await adapter.cancelIntent(cancellable.ref, "contract");
check(cancelled.status === "cancelled", "cancelIntent did not return cancelled state");
const refund = await adapter.refund({
intentRef: intent.ref,
amount: Math.max(1, Math.floor(intent.amount / 2)),
currency: intent.currency,
reason: "contract",
idempotencyKey: `${fixture.input.idempotencyKey}:refund`,
});
check(Boolean(refund.ref), "refund returned no reference");
if (adapter.capabilities.customerVault) {
const customer = await adapter.createCustomer!({ subject: fixture.input.subject });
check(Boolean(customer), "createCustomer returned no reference");
}
if (adapter.capabilities.storedMethods) {
const customer = "contract-customer";
const method = await adapter.attachMethod!(customer, "contract-token-4242");
check(Boolean(method.ref), "attachMethod returned no reference");
check(
Array.isArray(await adapter.listMethods!(customer)),
"listMethods did not return an array",
);
await adapter.detachMethod!(method.ref);
}
const event = await adapter.verifyWebhook(
await fixture.webhook(intent),
"contract-webhook-secret",
);
check(Boolean(event.id), "verified webhook has no gateway event id");
check(Boolean(event.intentRef), "verified webhook has no intent reference");
check(Number.isFinite(event.occurredAt), "verified webhook has no gateway occurrence time");
} catch (error) {
issues.push(
`${adapter.id}: lifecycle threw ${error instanceof Error ? error.message : String(error)}`,
);
}
return { ok: issues.length === 0, issues };
}
+435
View File
@@ -0,0 +1,435 @@
import type {
GatewayMethod,
Money,
PaymentEvent,
PaymentGateway,
PaymentIntent,
PaymentMethod,
PaymentRefund,
PaymentResult,
PaymentStatus,
PaymentStore,
WebhookEvent,
} from "./types.ts";
export interface InitializePaymentInput extends Money {
subject: string;
idempotencyKey: string;
gateway?: string;
country?: string;
capture?: "automatic" | "manual";
metadata?: Record<string, string>;
returnUrl?: string;
}
export interface PaymentOptions {
store: PaymentStore;
adapters: readonly PaymentGateway[];
default: string;
route?(input: InitializePaymentInput): string;
webhookSecrets?: Record<string, string>;
environment?: "development" | "test" | "production";
}
const validMoney = (money: Money) =>
Number.isSafeInteger(money.amount) && money.amount > 0 && /^[A-Z]{3}$/.test(money.currency);
const refusal = <T>(reason: string): PaymentResult<T> => ({ ok: false, fault: "refused", reason });
const gatewayFault = <T>(reason: string): PaymentResult<T> => ({
ok: false,
fault: "gateway",
reason,
});
export function derivePaymentStatus(events: readonly PaymentEvent[]): PaymentStatus {
const ordered = [...events].sort((left, right) => {
if (left.sequence !== undefined && right.sequence !== undefined) {
const numeric = Number(left.sequence) - Number(right.sequence);
if (Number.isFinite(numeric) && numeric !== 0) return numeric;
const lexical = String(left.sequence).localeCompare(String(right.sequence));
if (lexical) return lexical;
}
return (
(left.occurredAt ?? left.at) - (right.occurredAt ?? right.at) ||
left.id.localeCompare(right.id)
);
});
let status: PaymentStatus = "created";
for (const event of ordered) {
if (status === "refunded") continue;
if (
status === "partially_refunded" &&
!["partially_refunded", "refunded"].includes(event.status)
)
continue;
if (
status === "captured" &&
["created", "pending", "authorized", "failed", "cancelled"].includes(event.status)
)
continue;
if (status === "authorized" && ["created", "pending"].includes(event.status)) continue;
status = event.status;
}
return status;
}
export function defineGateway<T extends PaymentGateway>(gateway: T): T {
if (!gateway.id.trim()) throw new Error("WRN-PAYMENT-GATEWAY: id is required");
if (!gateway.capabilities.webhookSignature && process.env.NODE_ENV === "production")
throw new Error(`WRN-PAYMENT-WEBHOOK: gateway '${gateway.id}' cannot verify signatures`);
return Object.freeze(gateway);
}
export function definePayment(options: PaymentOptions) {
const adapters = new Map(options.adapters.map((adapter) => [adapter.id, adapter]));
if (adapters.size !== options.adapters.length)
throw new Error("WRN-PAYMENT-CONFIG: duplicate gateway id");
if (!adapters.has(options.default))
throw new Error(`WRN-PAYMENT-CONFIG: default gateway '${options.default}' is not registered`);
if ((options.environment ?? process.env.NODE_ENV) === "production")
for (const adapter of adapters.values())
if (!adapter.capabilities.webhookSignature)
throw new Error(`WRN-PAYMENT-WEBHOOK: gateway '${adapter.id}' cannot run in production`);
const adapterFor = (id: string) => adapters.get(id);
const status = async (id: string) => derivePaymentStatus(await options.store.events(id));
async function initializePayment(
input: InitializePaymentInput,
): Promise<PaymentResult<PaymentIntent>> {
if (!validMoney(input))
return refusal("amount must be positive minor units and currency must be ISO-4217");
if (!input.subject.trim() || !input.idempotencyKey.trim())
return refusal("subject and idempotencyKey are required");
try {
const existing = await options.store.findIntentByKey(input.idempotencyKey);
if (existing) return { ok: true, value: existing };
const gatewayId = input.gateway ?? options.route?.(input) ?? options.default;
const adapter = adapterFor(gatewayId);
if (!adapter) return refusal(`gateway '${gatewayId}' is not configured`);
if (
adapter.supports.currencies !== "any" &&
!adapter.supports.currencies.includes(input.currency)
)
return refusal(`gateway '${gatewayId}' does not support ${input.currency}`);
if (
input.country &&
adapter.supports.countries !== "any" &&
!adapter.supports.countries.includes(input.country)
)
return refusal(`gateway '${gatewayId}' does not support country ${input.country}`);
if (input.capture === "manual" && !adapter.capabilities.authorizeThenCapture)
return refusal(`gateway '${gatewayId}' does not support authorize-then-capture`);
const id = `pay_${crypto.randomUUID()}`;
const remote = await adapter.createIntent({ ...input, id });
const intent: PaymentIntent = {
id,
subject: input.subject,
amount: input.amount,
currency: input.currency,
gateway: gatewayId,
gatewayRef: remote.ref,
idempotencyKey: input.idempotencyKey,
createdAt: Date.now(),
checkoutUrl: remote.checkoutUrl,
client: adapter.clientConfig(remote),
metadata: { ...(input.metadata ?? {}) },
};
await options.store.putIntent(intent);
await options.store.appendEvent({
id: `local:${id}:created`,
intentId: id,
type: "payment.created",
status: remote.status,
at: Date.now(),
});
return { ok: true, value: intent };
} catch (error) {
return gatewayFault(error instanceof Error ? error.message : "gateway failed");
}
}
async function getPayment(id: string) {
return options.store.getIntent(id);
}
async function checkPayment(id: string): Promise<PaymentStatus> {
return status(id);
}
async function syncPayment(id: string): Promise<PaymentResult<PaymentIntent>> {
try {
const intent = await options.store.getIntent(id);
if (!intent) return refusal("payment not found");
const remote = await adapterFor(intent.gateway)!.fetchIntent(intent.gatewayRef);
await options.store.appendEvent({
id: `sync:${intent.gateway}:${remote.ref}:${remote.status}`,
intentId: id,
type: "payment.synced",
status: remote.status,
at: Date.now(),
payload: remote.raw,
});
return { ok: true, value: intent };
} catch (error) {
return gatewayFault(error instanceof Error ? error.message : "sync failed");
}
}
const confirmPayment = syncPayment;
async function capturePayment(id: string, amount?: Money): Promise<PaymentResult<PaymentIntent>> {
try {
const intent = await options.store.getIntent(id);
if (!intent) return refusal("payment not found");
const adapter = adapterFor(intent.gateway)!;
if (!adapter.capabilities.authorizeThenCapture || !adapter.capture)
return refusal(`gateway '${adapter.id}' does not support capture`);
const remote = await adapter.capture(intent.gatewayRef, amount);
await options.store.appendEvent({
id: `capture:${remote.ref}:${Date.now()}`,
intentId: id,
type: "payment.captured",
status: remote.status,
at: Date.now(),
payload: remote.raw,
});
return { ok: true, value: intent };
} catch (error) {
return gatewayFault(error instanceof Error ? error.message : "capture failed");
}
}
async function cancelPayment(id: string, reason: string): Promise<PaymentResult<PaymentIntent>> {
try {
const intent = await options.store.getIntent(id);
if (!intent) return refusal("payment not found");
if (!reason.trim()) return refusal("cancellation reason is required");
const remote = await adapterFor(intent.gateway)!.cancelIntent(intent.gatewayRef, reason);
await options.store.appendEvent({
id: `cancel:${remote.ref}:${Date.now()}`,
intentId: id,
type: "payment.cancelled",
status: remote.status,
at: Date.now(),
payload: remote.raw,
});
return { ok: true, value: intent };
} catch (error) {
return gatewayFault(error instanceof Error ? error.message : "cancellation failed");
}
}
async function refundableAmount(id: string): Promise<Money> {
const intent = await options.store.getIntent(id);
if (!intent) return { amount: 0, currency: "XXX" };
const captured =
(await status(id)) === "captured" ||
(await status(id)) === "partially_refunded" ||
(await status(id)) === "refunded"
? intent.amount
: 0;
const refunded = (await options.store.refunds(id))
.filter((row) => row.status !== "failed")
.reduce((sum, row) => sum + row.amount, 0);
return { amount: Math.max(0, captured - refunded), currency: intent.currency };
}
async function refundPayment(input: {
id: string;
amount?: number;
reason: string;
idempotencyKey: string;
}): Promise<PaymentResult<PaymentRefund>> {
try {
const intent = await options.store.getIntent(input.id);
if (!intent) return refusal("payment not found");
if (!input.reason.trim() || !input.idempotencyKey.trim())
return refusal("refund reason and idempotencyKey are required");
const prior = (await options.store.refunds(input.id)).find(
(row) => row.idempotencyKey === input.idempotencyKey,
);
if (prior) return { ok: true, value: prior };
const available = await refundableAmount(input.id);
const amount = input.amount ?? available.amount;
if (!Number.isSafeInteger(amount) || amount <= 0 || amount > available.amount)
return refusal("refund exceeds the refundable amount");
const adapter = adapterFor(intent.gateway)!;
if (amount < intent.amount && !adapter.capabilities.partialRefund)
return refusal(`gateway '${adapter.id}' does not support partial refunds`);
if (
(await options.store.refunds(input.id)).some((row) => row.status !== "failed") &&
!adapter.capabilities.multipleRefunds
)
return refusal(`gateway '${adapter.id}' does not support multiple refunds`);
const refund: PaymentRefund = {
id: `refund_${crypto.randomUUID()}`,
intentId: intent.id,
gatewayRef: "",
amount,
currency: intent.currency,
reason: input.reason,
idempotencyKey: input.idempotencyKey,
status: "pending",
createdAt: Date.now(),
};
if (!(await options.store.reserveRefund(refund, intent.amount)))
return refusal("refund is duplicated or exceeds the captured amount");
const remote = await adapter.refund({
intentRef: intent.gatewayRef,
amount,
currency: intent.currency,
reason: input.reason,
idempotencyKey: input.idempotencyKey,
});
await options.store.updateRefund(refund.id, remote.status, remote.ref);
const completed = { ...refund, status: remote.status, gatewayRef: remote.ref };
const remaining = available.amount - amount;
await options.store.appendEvent({
id: `refund:${remote.ref}`,
intentId: intent.id,
type: "payment.refunded",
status: remaining === 0 ? "refunded" : "partially_refunded",
at: Date.now(),
payload: remote.raw,
});
return { ok: true, value: completed };
} catch (error) {
return gatewayFault(error instanceof Error ? error.message : "refund failed");
}
}
async function acceptWebhook(gatewayId: string, event: WebhookEvent): Promise<boolean> {
const intent = (await options.store.listIntents({ gateway: gatewayId })).find(
(row) => row.gatewayRef === event.intentRef,
);
if (!intent) return false;
return options.store.appendEvent({
id: `${gatewayId}:${event.id}`,
intentId: intent.id,
type: event.type,
status: event.status,
at: Date.now(),
occurredAt: event.occurredAt,
sequence: event.sequence,
payload: event.payload,
});
}
function paymentWebhookHandler(gatewayId: string) {
return async (ctx: { req: Request }) => {
const adapter = adapterFor(gatewayId);
const secret = options.webhookSecrets?.[gatewayId];
if (!adapter || !secret)
return Response.json({ error: "gateway webhook is not configured" }, { status: 404 });
try {
const event = await adapter.verifyWebhook(ctx.req, secret);
const accepted = await acceptWebhook(gatewayId, event);
return Response.json({ received: true, duplicate: !accepted });
} catch {
return Response.json({ error: "invalid webhook" }, { status: 400 });
}
};
}
async function paymentMethods(subject: string): Promise<PaymentMethod[]> {
return options.store.methods(subject);
}
async function attachPaymentMethod(
subject: string,
gatewayId: string,
customerRef: string,
token: string,
): Promise<PaymentResult<PaymentMethod>> {
try {
const adapter = adapterFor(gatewayId);
if (!adapter?.capabilities.storedMethods || !adapter.attachMethod)
return refusal(`gateway '${gatewayId}' does not support stored methods`);
const remote: GatewayMethod = await adapter.attachMethod(customerRef, token);
const method: PaymentMethod = {
id: `method_${crypto.randomUUID()}`,
subject,
gateway: gatewayId,
gatewayRef: remote.ref,
brand: remote.brand,
last4: remote.last4,
expires: remote.expires,
default: false,
};
await options.store.putMethod(method);
return { ok: true, value: method };
} catch (error) {
return gatewayFault(error instanceof Error ? error.message : "method attach failed");
}
}
async function detachPaymentMethod(id: string): Promise<PaymentResult<void>> {
const method = await options.store.getMethod(id);
if (!method) return refusal("payment method not found");
const adapter = adapterFor(method.gateway);
try {
await adapter?.detachMethod?.(method.gatewayRef);
await options.store.removeMethod(id);
return { ok: true, value: undefined };
} catch (error) {
return gatewayFault(error instanceof Error ? error.message : "method detach failed");
}
}
async function setDefaultPaymentMethod(subject: string, id: string) {
await options.store.defaultMethod(subject, id);
}
async function listPayments(filter?: { subject?: string; gateway?: string }) {
return options.store.listIntents(filter);
}
async function listRefunds(id: string) {
return options.store.refunds(id);
}
async function paymentTotals(filter?: { subject?: string; gateway?: string }) {
const rows = await options.store.listIntents(filter);
const byCurrency: Record<string, { captured: number; refunded: number; net: number }> = {};
for (const row of rows) {
const state = await status(row.id);
const captured = ["captured", "partially_refunded", "refunded"].includes(state)
? row.amount
: 0;
const refunded = (await options.store.refunds(row.id))
.filter((x) => x.status === "succeeded")
.reduce((s, x) => s + x.amount, 0);
const total = (byCurrency[row.currency] ??= { captured: 0, refunded: 0, net: 0 });
total.captured += captured;
total.refunded += refunded;
total.net = total.captured - total.refunded;
}
return { byCurrency };
}
async function reconcilePayments() {
const discrepancies: unknown[] = [];
for (const intent of await options.store.listIntents()) {
const local = await status(intent.id);
const remote = (await adapterFor(intent.gateway)!.fetchIntent(intent.gatewayRef)).status;
if (local !== remote)
discrepancies.push({ id: intent.id, local, remote, gateway: intent.gateway });
}
return discrepancies;
}
return {
initializePayment,
getPayment,
listPayments,
checkPayment,
confirmPayment,
syncPayment,
capturePayment,
cancelPayment,
refundPayment,
listRefunds,
refundableAmount,
paymentMethods,
attachPaymentMethod,
detachPaymentMethod,
setDefaultPaymentMethod,
paymentTotals,
reconcilePayments,
paymentWebhookHandler,
capabilitiesOf: (id: string) => adapterFor(id)?.capabilities,
};
}
export type PaymentService = ReturnType<typeof definePayment>;
const SERVICE = Symbol.for("@wrnexus/payment:service:v1");
type PaymentGlobal = typeof globalThis & { [SERVICE]?: PaymentService };
export function configurePayment(options: PaymentOptions): PaymentService {
const service = definePayment(options);
(globalThis as PaymentGlobal)[SERVICE] = service;
return service;
}
export function getPaymentService(): PaymentService {
const service = (globalThis as PaymentGlobal)[SERVICE];
if (!service)
throw new Error("WRN-PAYMENT-SETUP: call configurePayment() during application startup");
return service;
}
+220
View File
@@ -0,0 +1,220 @@
import { getDb } from "@wrnexus/db";
import type { PaymentIntent, PaymentRefund, PaymentStore } from "./types.ts";
const parse = <T>(value: unknown, fallback: T): T => {
try {
return typeof value === "string" ? JSON.parse(value) : (value as T);
} catch {
return fallback;
}
};
export function databasePaymentStore(database = "default"): PaymentStore {
const db = () => getDb(database);
const intent = (row: any): PaymentIntent => ({
id: String(row.id),
subject: String(row.subject),
gateway: String(row.gateway),
gatewayRef: String(row.gateway_ref),
amount: Number(row.amount),
currency: String(row.currency),
idempotencyKey: String(row.idempotency_key),
createdAt: Number(row.created_at),
checkoutUrl: row.checkout_url ? String(row.checkout_url) : undefined,
client: parse(row.client_json, {}),
metadata: parse(row.metadata_json, {}),
});
const refund = (row: any): PaymentRefund => ({
id: String(row.id),
intentId: String(row.intent_id),
gatewayRef: String(row.gateway_ref ?? ""),
amount: Number(row.amount),
currency: String(row.currency),
reason: String(row.reason),
idempotencyKey: String(row.idempotency_key),
status: row.status,
createdAt: Number(row.created_at),
});
return {
async findIntentByKey(key) {
const row = await db().one("SELECT * FROM wrn_payment_intent WHERE idempotency_key = ?", [
key,
]);
return row ? intent(row) : null;
},
async putIntent(row) {
await db().exec(
"INSERT INTO wrn_payment_intent (id,subject,gateway,gateway_ref,amount,currency,idempotency_key,created_at,checkout_url,client_json,metadata_json) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
[
row.id,
row.subject,
row.gateway,
row.gatewayRef,
row.amount,
row.currency,
row.idempotencyKey,
row.createdAt,
row.checkoutUrl ?? null,
JSON.stringify(row.client),
JSON.stringify(row.metadata),
],
);
},
async getIntent(id) {
const row = await db().one("SELECT * FROM wrn_payment_intent WHERE id = ?", [id]);
return row ? intent(row) : null;
},
async listIntents(filter = {}) {
const clauses: string[] = [],
values: unknown[] = [];
if (filter.subject) {
clauses.push("subject = ?");
values.push(filter.subject);
}
if (filter.gateway) {
clauses.push("gateway = ?");
values.push(filter.gateway);
}
return (
await db().all(
`SELECT * FROM wrn_payment_intent${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY created_at DESC`,
values,
)
).map(intent);
},
async appendEvent(row) {
try {
const result = await db().exec(
"INSERT INTO wrn_payment_event (id,intent_id,type,status,created_at,occurred_at,gateway_sequence,payload_json) VALUES (?,?,?,?,?,?,?,?) ON CONFLICT(id) DO NOTHING",
[
row.id,
row.intentId,
row.type,
row.status,
row.at,
row.occurredAt ?? row.at,
row.sequence === undefined ? null : String(row.sequence),
JSON.stringify(row.payload ?? null),
],
);
return result.changes > 0;
} catch {
return false;
}
},
async events(id) {
return (
await db().all<any>(
"SELECT * FROM wrn_payment_event WHERE intent_id = ? ORDER BY occurred_at ASC, gateway_sequence ASC, id ASC",
[id],
)
).map((row) => ({
id: String(row.id),
intentId: String(row.intent_id),
type: String(row.type),
status: row.status,
at: Number(row.created_at),
occurredAt: Number(row.occurred_at ?? row.created_at),
sequence: row.gateway_sequence == null ? undefined : String(row.gateway_sequence),
payload: parse(row.payload_json, null),
}));
},
async reserveRefund(row, captured) {
try {
const result = await db().exec(
"INSERT INTO wrn_payment_refund (id,intent_id,gateway_ref,amount,currency,reason,idempotency_key,status,created_at) SELECT ?,?,?,?,?,?,?,?,? WHERE ? >= COALESCE((SELECT SUM(amount) FROM wrn_payment_refund WHERE intent_id = ? AND status <> 'failed'),0) + ?",
[
row.id,
row.intentId,
row.gatewayRef,
row.amount,
row.currency,
row.reason,
row.idempotencyKey,
row.status,
row.createdAt,
captured,
row.intentId,
row.amount,
],
);
return result.changes > 0;
} catch {
return false;
}
},
async updateRefund(id, status, gatewayRef) {
await db().exec("UPDATE wrn_payment_refund SET status = ?, gateway_ref = ? WHERE id = ?", [
status,
gatewayRef,
id,
]);
},
async refunds(id) {
return (
await db().all("SELECT * FROM wrn_payment_refund WHERE intent_id = ? ORDER BY created_at", [
id,
])
).map(refund);
},
async methods(subject) {
return (
await db().all<any>(
"SELECT * FROM wrn_payment_method WHERE subject = ? ORDER BY is_default DESC, created_at",
[subject],
)
).map((row) => ({
id: String(row.id),
subject: String(row.subject),
gateway: String(row.gateway),
gatewayRef: String(row.gateway_ref),
brand: row.brand ? String(row.brand) : undefined,
last4: row.last4 ? String(row.last4) : undefined,
expires: row.expires ? String(row.expires) : undefined,
default: Boolean(row.is_default),
}));
},
async getMethod(id) {
const row = await db().one<any>("SELECT * FROM wrn_payment_method WHERE id = ?", [id]);
return row
? {
id: String(row.id),
subject: String(row.subject),
gateway: String(row.gateway),
gatewayRef: String(row.gateway_ref),
brand: row.brand ? String(row.brand) : undefined,
last4: row.last4 ? String(row.last4) : undefined,
expires: row.expires ? String(row.expires) : undefined,
default: Boolean(row.is_default),
}
: null;
},
async putMethod(row) {
await db().exec(
"INSERT INTO wrn_payment_method (id,subject,gateway,gateway_ref,brand,last4,expires,is_default,created_at) VALUES (?,?,?,?,?,?,?,?,?)",
[
row.id,
row.subject,
row.gateway,
row.gatewayRef,
row.brand ?? null,
row.last4 ?? null,
row.expires ?? null,
row.default ? 1 : 0,
Date.now(),
],
);
},
async removeMethod(id) {
await db().exec("DELETE FROM wrn_payment_method WHERE id = ?", [id]);
},
async defaultMethod(subject, id) {
await db().tx(async (tx) => {
await tx.exec("UPDATE wrn_payment_method SET is_default = 0 WHERE subject = ?", [subject]);
await tx.exec("UPDATE wrn_payment_method SET is_default = 1 WHERE subject = ? AND id = ?", [
subject,
id,
]);
});
},
};
}
+7
View File
@@ -0,0 +1,7 @@
export * from "./types.ts";
export * from "./store.ts";
export * from "./database.ts";
export * from "./core.ts";
export * from "./adapters.ts";
export * from "./contract.ts";
export { paymentPlugin } from "./plugin.ts";
+65
View File
@@ -0,0 +1,65 @@
import { basename, dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { definePlugin, type PluginContext } from "@wrnexus/plugin";
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const extension = basename(dirname(fileURLToPath(import.meta.url))) === "dist" ? ".js" : ".ts";
const webhookEntry = join(dirname(fileURLToPath(import.meta.url)), `runtime-webhook${extension}`);
const migration = `-- +up
CREATE TABLE IF NOT EXISTS wrn_payment_intent (id TEXT PRIMARY KEY, subject TEXT NOT NULL, gateway TEXT NOT NULL, gateway_ref TEXT NOT NULL, amount BIGINT NOT NULL, currency TEXT NOT NULL, idempotency_key TEXT NOT NULL UNIQUE, created_at BIGINT NOT NULL, checkout_url TEXT, client_json TEXT NOT NULL, metadata_json TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS wrn_payment_intent_subject ON wrn_payment_intent(subject, created_at);
CREATE TABLE IF NOT EXISTS wrn_payment_event (id TEXT PRIMARY KEY, intent_id TEXT NOT NULL, type TEXT NOT NULL, status TEXT NOT NULL, created_at BIGINT NOT NULL, payload_json TEXT, FOREIGN KEY(intent_id) REFERENCES wrn_payment_intent(id));
CREATE INDEX IF NOT EXISTS wrn_payment_event_intent ON wrn_payment_event(intent_id, created_at, id);
CREATE TABLE IF NOT EXISTS wrn_payment_refund (id TEXT PRIMARY KEY, intent_id TEXT NOT NULL, gateway_ref TEXT NOT NULL, amount BIGINT NOT NULL, currency TEXT NOT NULL, reason TEXT NOT NULL, idempotency_key TEXT NOT NULL UNIQUE, status TEXT NOT NULL, created_at BIGINT NOT NULL, FOREIGN KEY(intent_id) REFERENCES wrn_payment_intent(id));
CREATE TABLE IF NOT EXISTS wrn_payment_method (id TEXT PRIMARY KEY, subject TEXT NOT NULL, gateway TEXT NOT NULL, gateway_ref TEXT NOT NULL, brand TEXT, last4 TEXT, expires TEXT, is_default INTEGER NOT NULL DEFAULT 0, created_at BIGINT NOT NULL);
CREATE TABLE IF NOT EXISTS wrn_payment_customer (subject TEXT NOT NULL, gateway TEXT NOT NULL, gateway_ref TEXT NOT NULL, created_at BIGINT NOT NULL, PRIMARY KEY(subject,gateway));
-- +down
DROP TABLE IF EXISTS wrn_payment_customer;
DROP TABLE IF EXISTS wrn_payment_method;
DROP TABLE IF EXISTS wrn_payment_refund;
DROP TABLE IF EXISTS wrn_payment_event;
DROP TABLE IF EXISTS wrn_payment_intent;`;
const orderingMigration = `-- +up
ALTER TABLE wrn_payment_event ADD COLUMN occurred_at BIGINT;
ALTER TABLE wrn_payment_event ADD COLUMN gateway_sequence TEXT;
UPDATE wrn_payment_event SET occurred_at = created_at WHERE occurred_at IS NULL;
CREATE INDEX IF NOT EXISTS wrn_payment_event_gateway_order ON wrn_payment_event(intent_id, occurred_at, gateway_sequence, id);
-- +down
DROP INDEX IF EXISTS wrn_payment_event_gateway_order;`;
export function paymentPlugin() {
const key = "@wrnexus/payment:enabled";
return definePlugin({
name: "@wrnexus/payment",
version: "0.8.1",
componentDirs: [join(root, "components")],
routeEntries(context) {
return context.metadata.get(key)
? [{ kind: "api" as const, path: "/api/payments/webhook/:gateway", entry: webhookEntry }]
: [];
},
configure(config, context) {
const value = config.payment as
{ default?: string; gateways?: Record<string, unknown>; sandbox?: boolean } | undefined;
context.metadata.set(key, Boolean(value));
if (!value) return;
if (!value.default) throw new Error("WRN-PAYMENT-CONFIG: payment.default is required");
if (!value.gateways?.[value.default] && value.default !== "sandbox")
throw new Error(
`WRN-PAYMENT-CONFIG: default gateway '${value.default}' has no configuration`,
);
if (process.env.NODE_ENV === "production" && value.sandbox)
throw new Error("WRN-PAYMENT-CONFIG: sandbox cannot be enabled in production");
},
migrations(context: PluginContext) {
return context.metadata.get(key)
? [
{ id: "wrnexus-payment-001", source: migration },
{ id: "wrnexus-payment-002-event-order", source: orderingMigration },
]
: [];
},
documentation: [join(root, "README.md")],
});
}
export default paymentPlugin;
+7
View File
@@ -0,0 +1,7 @@
import { getPaymentService } from "./core.ts";
export async function POST(ctx: { req: Request; params?: Record<string, string> }) {
const gateway = ctx.params?.gateway?.trim();
if (!gateway) return Response.json({ error: "payment gateway is required" }, { status: 400 });
return getPaymentService().paymentWebhookHandler(gateway)(ctx);
}
+101
View File
@@ -0,0 +1,101 @@
import type {
PaymentEvent,
PaymentIntent,
PaymentMethod,
PaymentRefund,
PaymentStore,
} from "./types.ts";
export function memoryPaymentStore(): PaymentStore {
const intents = new Map<string, PaymentIntent>();
const keys = new Map<string, string>();
const eventRows = new Map<string, PaymentEvent>();
const refundRows = new Map<string, PaymentRefund>();
const methodRows = new Map<string, PaymentMethod>();
let lock = Promise.resolve();
const exclusive = async <T>(run: () => T | Promise<T>): Promise<T> => {
const previous = lock;
let release!: () => void;
lock = new Promise<void>((resolve) => (release = resolve));
await previous;
try {
return await run();
} finally {
release();
}
};
return {
async findIntentByKey(key) {
const id = keys.get(key);
return id ? structuredClone(intents.get(id)!) : null;
},
async putIntent(intent) {
intents.set(intent.id, structuredClone(intent));
keys.set(intent.idempotencyKey, intent.id);
},
async getIntent(id) {
const row = intents.get(id);
return row ? structuredClone(row) : null;
},
async listIntents(filter = {}) {
return [...intents.values()]
.filter(
(row) =>
(!filter.subject || row.subject === filter.subject) &&
(!filter.gateway || row.gateway === filter.gateway),
)
.map((row) => structuredClone(row));
},
async appendEvent(event) {
if (eventRows.has(event.id)) return false;
eventRows.set(event.id, structuredClone(event));
return true;
},
async events(intentId) {
return [...eventRows.values()]
.filter((row) => row.intentId === intentId)
.sort((a, b) => a.at - b.at)
.map((row) => structuredClone(row));
},
async reserveRefund(refund, captured) {
return exclusive(() => {
if ([...refundRows.values()].some((row) => row.idempotencyKey === refund.idempotencyKey))
return false;
const used = [...refundRows.values()]
.filter((row) => row.intentId === refund.intentId && row.status !== "failed")
.reduce((sum, row) => sum + row.amount, 0);
if (used + refund.amount > captured) return false;
refundRows.set(refund.id, structuredClone(refund));
return true;
});
},
async updateRefund(id, status, gatewayRef) {
const row = refundRows.get(id);
if (row) refundRows.set(id, { ...row, status, gatewayRef });
},
async refunds(intentId) {
return [...refundRows.values()]
.filter((row) => row.intentId === intentId)
.map((row) => structuredClone(row));
},
async methods(subject) {
return [...methodRows.values()]
.filter((row) => row.subject === subject)
.map((row) => structuredClone(row));
},
async getMethod(id) {
const row = methodRows.get(id);
return row ? structuredClone(row) : null;
},
async putMethod(method) {
methodRows.set(method.id, structuredClone(method));
},
async removeMethod(id) {
methodRows.delete(id);
},
async defaultMethod(subject, id) {
for (const [key, row] of methodRows)
if (row.subject === subject) methodRows.set(key, { ...row, default: key === id });
},
};
}
+162
View File
@@ -0,0 +1,162 @@
export type PaymentStatus =
| "created"
| "pending"
| "authorized"
| "captured"
| "failed"
| "cancelled"
| "partially_refunded"
| "refunded";
export interface Money {
amount: number;
currency: string;
}
export interface GatewayCapabilities {
authorizeThenCapture: boolean;
partialRefund: boolean;
multipleRefunds: boolean;
storedMethods: boolean;
customerVault: boolean;
hostedFields: boolean;
hostedCheckout: boolean;
webhookSignature: boolean;
payouts: boolean;
disputes: boolean;
}
export interface GatewaySupport {
currencies: readonly string[] | "any";
countries: readonly string[] | "any";
}
export interface CreateIntentInput extends Money {
id: string;
subject: string;
idempotencyKey: string;
country?: string;
capture?: "automatic" | "manual";
metadata?: Record<string, string>;
returnUrl?: string;
}
export interface GatewayIntent {
ref: string;
status: PaymentStatus;
amount: number;
currency: string;
checkoutUrl?: string;
clientSecret?: string;
raw?: unknown;
}
export interface GatewayRefund {
ref: string;
status: "pending" | "succeeded" | "failed";
amount: number;
raw?: unknown;
}
export interface GatewayMethod {
ref: string;
brand?: string;
last4?: string;
expires?: string;
}
export interface CustomerInput {
subject: string;
email?: string;
name?: string;
}
export interface RefundInput extends Money {
intentRef: string;
idempotencyKey: string;
reason: string;
}
export interface WebhookEvent {
id: string;
intentRef: string;
type: string;
status: PaymentStatus;
/** Gateway-authored event time, never local arrival time. */
occurredAt: number;
/** Gateway-authored ordering token when the provider exposes one. */
sequence?: string | number;
payload: unknown;
}
export interface PaymentGateway<TConfig = unknown> {
readonly id: string;
readonly config?: TConfig;
readonly capabilities: GatewayCapabilities;
readonly supports: GatewaySupport;
createIntent(input: CreateIntentInput): Promise<GatewayIntent>;
fetchIntent(intentRef: string): Promise<GatewayIntent>;
cancelIntent(intentRef: string, reason: string): Promise<GatewayIntent>;
refund(input: RefundInput): Promise<GatewayRefund>;
verifyWebhook(req: Request, secret: string): Promise<WebhookEvent>;
capture?(intentRef: string, amount?: Money): Promise<GatewayIntent>;
listMethods?(customerRef: string): Promise<GatewayMethod[]>;
attachMethod?(customerRef: string, token: string): Promise<GatewayMethod>;
detachMethod?(methodRef: string): Promise<void>;
createCustomer?(input: CustomerInput): Promise<string>;
clientConfig(intent: GatewayIntent): Record<string, string>;
}
export interface PaymentIntent extends Money {
id: string;
subject: string;
gateway: string;
gatewayRef: string;
idempotencyKey: string;
createdAt: number;
checkoutUrl?: string;
client: Record<string, string>;
metadata: Record<string, string>;
}
export interface PaymentEvent {
id: string;
intentId: string;
type: string;
status: PaymentStatus;
at: number;
occurredAt?: number;
sequence?: string | number;
payload?: unknown;
}
export interface PaymentRefund {
id: string;
intentId: string;
gatewayRef: string;
amount: number;
currency: string;
reason: string;
idempotencyKey: string;
status: "pending" | "succeeded" | "failed";
createdAt: number;
}
export interface PaymentMethod {
id: string;
subject: string;
gateway: string;
gatewayRef: string;
brand?: string;
last4?: string;
expires?: string;
default: boolean;
}
export type PaymentResult<T> =
| { ok: true; value: T }
| { ok: false; fault: "refused" | "gateway" | "storage" | "configuration"; reason: string };
export interface PaymentStore {
findIntentByKey(key: string): Promise<PaymentIntent | null>;
putIntent(intent: PaymentIntent): Promise<void>;
getIntent(id: string): Promise<PaymentIntent | null>;
listIntents(filter?: { subject?: string; gateway?: string }): Promise<PaymentIntent[]>;
appendEvent(event: PaymentEvent): Promise<boolean>;
events(intentId: string): Promise<PaymentEvent[]>;
reserveRefund(refund: PaymentRefund, captured: number): Promise<boolean>;
updateRefund(id: string, status: PaymentRefund["status"], gatewayRef: string): Promise<void>;
refunds(intentId: string): Promise<PaymentRefund[]>;
methods(subject: string): Promise<PaymentMethod[]>;
getMethod(id: string): Promise<PaymentMethod | null>;
putMethod(method: PaymentMethod): Promise<void>;
removeMethod(id: string): Promise<void>;
defaultMethod(subject: string, id: string): Promise<void>;
}
@@ -0,0 +1,237 @@
import { afterEach, expect, test } from "bun:test";
import {
exerciseGatewayContract,
paypalGateway,
razorpayGateway,
sandboxGateway,
signSandboxWebhook,
stripeGateway,
type GatewayIntent,
} from "../src/index.ts";
const originalFetch = globalThis.fetch;
afterEach(() => {
globalThis.fetch = originalFetch;
});
const json = (value: unknown, status = 200) =>
new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });
const encoder = new TextEncoder();
async function signature(body: string, secret: string) {
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
return [...new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder.encode(body)))]
.map((x) => x.toString(16).padStart(2, "0"))
.join("");
}
const input = {
id: "contract-pay",
subject: "contract-user",
amount: 1000,
currency: "USD",
idempotencyKey: "contract-init",
};
test("shared executable contract drives sandbox", async () => {
const adapter = sandboxGateway();
const report = await exerciseGatewayContract(adapter, {
input,
async webhook(intent) {
const signed = await signSandboxWebhook(
{
id: "sb-event",
intentRef: intent.ref,
type: "payment.captured",
status: "captured",
occurredAt: 100,
},
"contract-webhook-secret",
);
return new Request("https://test", {
method: "POST",
headers: { "x-wrnexus-signature": signed.signature },
body: signed.body,
});
},
});
expect(report).toEqual({ ok: true, issues: [] });
});
test("shared executable contract drives Stripe through its HTTP adapter", async () => {
globalThis.fetch = (async (value, init) => {
const url = String(value),
method = init?.method ?? "GET";
if (
url.includes("/payment_intents") &&
method === "POST" &&
!url.endsWith("/capture") &&
!url.endsWith("/cancel")
) {
const body = String(init?.body);
const id = body.includes("cancel") ? "pi_cancel" : "pi_contract";
return json({
id,
status: "requires_capture",
amount: 1000,
amount_received: 1000,
currency: "usd",
client_secret: "pi_client_safe",
});
}
if (url.endsWith("/capture"))
return json({
id: "pi_contract",
status: "succeeded",
amount_received: 1000,
currency: "usd",
});
if (url.endsWith("/cancel"))
return json({ id: "pi_cancel", status: "canceled", amount: 1000, currency: "usd" });
if (url.includes("/payment_intents/"))
return json({ id: "pi_contract", status: "requires_capture", amount: 1000, currency: "usd" });
if (url.endsWith("/refunds")) return json({ id: "re_1", status: "succeeded", amount: 500 });
if (url.endsWith("/customers")) return json({ id: "cus_1" });
if (url.includes("/payment_methods?")) return json({ data: [] });
if (url.includes("/payment_methods/") && url.endsWith("/attach"))
return json({
id: "pm_1",
card: { brand: "visa", last4: "4242", exp_month: 1, exp_year: 2030 },
});
if (url.includes("/payment_methods/") && url.endsWith("/detach")) return json({ id: "pm_1" });
return json({}, 404);
}) as typeof fetch;
const adapter = stripeGateway({
secretKey: "sk_test_secret",
publishableKey: "pk_test",
webhookSecret: "contract-webhook-secret",
});
const report = await exerciseGatewayContract(adapter, {
input,
async webhook(intent) {
const body = JSON.stringify({
id: "evt_1",
created: 100,
data: { object: { id: intent.ref, status: "succeeded" } },
type: "payment_intent.succeeded",
});
return new Request("https://test", {
method: "POST",
headers: {
"stripe-signature": `t=100,v1=${await signature(`100.${body}`, "contract-webhook-secret")}`,
},
body,
});
},
});
expect(report).toEqual({ ok: true, issues: [] });
});
test("shared executable contract drives Razorpay through its HTTP adapter", async () => {
globalThis.fetch = (async (value, init) => {
const url = String(value),
method = init?.method ?? "GET";
if (url.endsWith("/orders") && method === "POST") {
const body = String(init?.body);
return json({
id: body.includes("cancel") ? "order_cancel" : "order_1",
status: "created",
amount: 1000,
currency: "USD",
});
}
if (url.includes("/orders/"))
return json({
id: url.endsWith("order_cancel") ? "order_cancel" : "order_1",
status: "created",
amount: 1000,
currency: "USD",
});
if (url.includes("/refund")) return json({ id: "rfnd_1", status: "processed", amount: 500 });
return json({}, 404);
}) as typeof fetch;
const adapter = razorpayGateway({
keyId: "rzp_test",
keySecret: "key_secret",
webhookSecret: "contract-webhook-secret",
});
const report = await exerciseGatewayContract(adapter, {
input,
async webhook(intent) {
const body = JSON.stringify({
id: "rz_evt",
event: "payment.captured",
payload: {
payment: {
entity: { id: "pay_1", order_id: intent.ref, status: "captured", created_at: 100 },
},
},
});
return new Request("https://test", {
method: "POST",
headers: { "x-razorpay-signature": await signature(body, "contract-webhook-secret") },
body,
});
},
});
expect(report).toEqual({ ok: true, issues: [] });
});
test("shared executable contract drives PayPal through its HTTP adapter", async () => {
globalThis.fetch = (async (value, init) => {
const url = String(value),
method = init?.method ?? "GET";
if (url.endsWith("/oauth2/token")) return json({ access_token: "access" });
if (url.endsWith("/checkout/orders") && method === "POST") {
const body = String(init?.body);
return json({
id: body.includes("cancel") ? "PP-CANCEL" : "PP-ORDER",
status: "CREATED",
links: [{ rel: "approve", href: "https://paypal.test/approve" }],
});
}
if (url.includes("/checkout/orders/"))
return json({
id: url.endsWith("PP-CANCEL") ? "PP-CANCEL" : "PP-ORDER",
status: "CREATED",
purchase_units: [{ amount: { value: "10.00", currency_code: "USD" } }],
});
if (url.includes("/refund")) return json({ id: "PP-REFUND", status: "COMPLETED" });
if (url.endsWith("verify-webhook-signature")) return json({ verification_status: "SUCCESS" });
return json({}, 404);
}) as typeof fetch;
const adapter = paypalGateway({
clientId: "client",
clientSecret: "paypal_secret",
webhookId: "hook",
});
const report = await exerciseGatewayContract(adapter, {
input,
webhook(intent: GatewayIntent) {
return new Request("https://test", {
method: "POST",
headers: {
"paypal-auth-algo": "SHA256withRSA",
"paypal-cert-url": "https://paypal.test/cert",
"paypal-transmission-id": "tx",
"paypal-transmission-sig": "sig",
"paypal-transmission-time": "time",
},
body: JSON.stringify({
id: "WH-1",
create_time: "2026-08-23T00:00:00Z",
event_type: "PAYMENT.CAPTURE.COMPLETED",
resource: {
id: "CAPTURE",
status: "COMPLETED",
supplementary_data: { related_ids: { order_id: intent.ref } },
},
}),
});
},
});
expect(report).toEqual({ ok: true, issues: [] });
});
@@ -0,0 +1,111 @@
import { afterEach, expect, test } from "bun:test";
import { closeDatabases, setDb } from "@wrnexus/db";
import { connectFromConfig } from "@wrnexus/db/connect";
import { createPluginRunner } from "@wrnexus/plugin";
import { parse } from "@wrnexus/syntax";
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { databasePaymentStore, paymentPlugin } from "../src/index.ts";
afterEach(() => closeDatabases());
test("payment plugin contributes its schema only when configured", async () => {
const metadata = new Map<string, unknown>();
const context = {
root: process.cwd(),
mode: "development" as const,
command: "dev" as const,
metadata,
warn() {},
};
const runner = createPluginRunner(paymentPlugin(), context);
await runner.configure({ payment: { default: "sandbox", sandbox: true } } as any);
const contributions = await runner.contributions();
expect(contributions.migrations.map((row) => row.id)).toEqual([
"wrnexus-payment-001",
"wrnexus-payment-002-event-order",
]);
expect(contributions.componentDirs).toHaveLength(1);
expect(contributions.routes.map((row) => row.path)).toEqual(["/api/payments/webhook/:gateway"]);
const off = createPluginRunner(paymentPlugin(), { ...context, metadata: new Map() });
await off.configure({} as any);
expect((await off.contributions()).migrations).toHaveLength(0);
expect((await off.contributions()).routes).toHaveLength(0);
});
test("database store persists append-only events and atomically caps refunds", async () => {
const db = connectFromConfig({ driver: "sqlite", url: ":memory:" });
setDb(db);
const metadata = new Map<string, unknown>();
const runner = createPluginRunner(paymentPlugin(), {
root: process.cwd(),
mode: "development",
command: "dev",
metadata,
warn() {},
});
await runner.configure({ payment: { default: "sandbox" } } as any);
for (const migration of (await runner.contributions()).migrations) {
const up = migration.source!.split("-- +down")[0].replace("-- +up", "");
for (const statement of up
.split(";")
.map((x) => x.trim())
.filter(Boolean))
await db.exec(statement);
}
const store = databasePaymentStore();
const intent = {
id: "pay-1",
subject: "u",
gateway: "sandbox",
gatewayRef: "sb-1",
amount: 100,
currency: "USD",
idempotencyKey: "once",
createdAt: 1,
client: {},
metadata: {},
};
await store.putIntent(intent);
expect((await store.findIntentByKey("once"))?.id).toBe("pay-1");
expect(
await store.appendEvent({
id: "e1",
intentId: "pay-1",
type: "captured",
status: "captured",
at: 1,
}),
).toBe(true);
expect(
await store.appendEvent({
id: "e1",
intentId: "pay-1",
type: "captured",
status: "captured",
at: 1,
}),
).toBe(false);
const base = {
intentId: "pay-1",
gatewayRef: "",
currency: "USD",
reason: "test",
status: "pending" as const,
createdAt: 1,
};
const results = await Promise.all([
store.reserveRefund({ id: "r1", amount: 60, idempotencyKey: "r1", ...base }, 100),
store.reserveRefund({ id: "r2", amount: 60, idempotencyKey: "r2", ...base }, 100),
]);
expect(results.filter(Boolean)).toHaveLength(1);
});
test("all payment components parse and never collect raw card data", () => {
for (const file of readdirSync(join(import.meta.dir, "../components")).filter((x) =>
x.endsWith(".wrn"),
)) {
const source = readFileSync(join(import.meta.dir, "../components", file), "utf8");
expect(() => parse(source)).not.toThrow();
expect(source).not.toMatch(/cardNumber|cvv|\bpan\b/i);
}
});
+240
View File
@@ -0,0 +1,240 @@
import { expect, test } from "bun:test";
import {
defineGateway,
definePayment,
memoryPaymentStore,
paypalGateway,
razorpayGateway,
sandboxGateway,
signSandboxWebhook,
stripeGateway,
derivePaymentStatus,
verifyGatewayContract,
} from "../src/index.ts";
test("gateway occurrence order and sticky terminal states prevent webhook regression", () => {
const events = [
{
id: "refund",
intentId: "p",
type: "refunded",
status: "refunded" as const,
at: 100,
occurredAt: 300,
},
{
id: "late-capture-retry",
intentId: "p",
type: "captured",
status: "captured" as const,
at: 400,
occurredAt: 200,
},
];
expect(derivePaymentStatus(events)).toBe("refunded");
expect(
derivePaymentStatus([...events, { ...events[1], id: "new-id", at: 500, occurredAt: 500 }]),
).toBe("refunded");
});
test("sandbox drives webhook-authoritative capture and bounded partial refunds", async () => {
const store = memoryPaymentStore(),
gateway = sandboxGateway({ secret: "test-secret" });
const payment = definePayment({
store,
adapters: [gateway],
default: "sandbox",
webhookSecrets: { sandbox: "test-secret" },
environment: "test",
});
const created = await payment.initializePayment({
subject: "user-1",
amount: 1000,
currency: "USD",
idempotencyKey: "checkout-1",
});
expect(created.ok).toBe(true);
if (!created.ok) return;
expect(await payment.checkPayment(created.value.id)).toBe("pending");
// A browser redirect cannot advance local state.
expect(await payment.checkPayment(created.value.id)).toBe("pending");
const signed = await signSandboxWebhook(
{
id: "evt-1",
intentRef: created.value.gatewayRef,
type: "payment.captured",
status: "captured",
},
"test-secret",
);
const handler = payment.paymentWebhookHandler("sandbox");
expect(
(
await handler({
req: new Request("https://app.test/webhook", {
method: "POST",
headers: { "x-wrnexus-signature": signed.signature },
body: signed.body,
}),
})
).status,
).toBe(200);
expect(await payment.checkPayment(created.value.id)).toBe("captured");
const first = await payment.refundPayment({
id: created.value.id,
amount: 400,
reason: "partial return",
idempotencyKey: "refund-1",
});
expect(first.ok).toBe(true);
expect((await payment.refundableAmount(created.value.id)).amount).toBe(600);
const excessive = await payment.refundPayment({
id: created.value.id,
amount: 601,
reason: "too much",
idempotencyKey: "refund-2",
});
expect(excessive).toMatchObject({ ok: false, fault: "refused" });
expect(
(
await payment.refundPayment({
id: created.value.id,
amount: 600,
reason: "remaining return",
idempotencyKey: "refund-remaining",
})
).ok,
).toBe(true);
const stale = await signSandboxWebhook(
{
id: "evt-delayed-capture",
intentRef: created.value.gatewayRef,
type: "payment.captured",
status: "captured",
occurredAt: 1,
},
"test-secret",
);
await handler({
req: new Request("https://app.test/webhook", {
method: "POST",
headers: { "x-wrnexus-signature": stale.signature },
body: stale.body,
}),
});
expect(await payment.checkPayment(created.value.id)).toBe("refunded");
});
test("initialize, webhook, and refund replay change records once", async () => {
const store = memoryPaymentStore(),
gateway = sandboxGateway();
const payment = definePayment({
store,
adapters: [gateway],
default: "sandbox",
webhookSecrets: { sandbox: "sandbox-secret" },
});
const input = { subject: "user-2", amount: 500, currency: "EUR", idempotencyKey: "same" };
const one = await payment.initializePayment(input),
two = await payment.initializePayment(input);
expect(one.ok && two.ok && one.value.id).toBe(two.ok ? two.value.id : "");
if (!one.ok) return;
const signed = await signSandboxWebhook({
id: "evt-replay",
intentRef: one.value.gatewayRef,
type: "payment.captured",
status: "captured",
});
const request = () =>
new Request("https://app.test/webhook", {
method: "POST",
headers: { "x-wrnexus-signature": signed.signature },
body: signed.body,
});
const handler = payment.paymentWebhookHandler("sandbox");
await handler({ req: request() });
const replay = await handler({ req: request() });
expect(await replay.json()).toEqual({ received: true, duplicate: true });
const refund = await payment.refundPayment({
id: one.value.id,
amount: 100,
reason: "return",
idempotencyKey: "same-refund",
});
const duplicate = await payment.refundPayment({
id: one.value.id,
amount: 100,
reason: "return",
idempotencyKey: "same-refund",
});
expect(refund.ok && duplicate.ok && refund.value.id).toBe(duplicate.ok ? duplicate.value.id : "");
expect(await payment.listRefunds(one.value.id)).toHaveLength(1);
});
test("selection and capability differences are refused before money moves", async () => {
const sandbox = sandboxGateway();
const noPartial = defineGateway({
...sandbox,
id: "full-only",
capabilities: { ...sandbox.capabilities, partialRefund: false },
});
const payment = definePayment({
store: memoryPaymentStore(),
adapters: [sandbox, noPartial],
default: "sandbox",
route: (input) => (input.currency === "INR" ? "full-only" : "sandbox"),
});
const manual = await payment.initializePayment({
subject: "u",
amount: 100,
currency: "INR",
country: "IN",
capture: "manual",
idempotencyKey: "route",
});
expect(manual.ok).toBe(true);
expect(verifyGatewayContract(sandbox)).toEqual({ ok: true, issues: [] });
const broken = defineGateway({
...sandbox,
id: "broken",
capabilities: { ...sandbox.capabilities, authorizeThenCapture: false },
});
expect(verifyGatewayContract(broken).ok).toBe(false);
});
test("invalid webhook signatures never update payment status", async () => {
const gateway = sandboxGateway(),
payment = definePayment({
store: memoryPaymentStore(),
adapters: [gateway],
default: "sandbox",
webhookSecrets: { sandbox: "right" },
});
const created = await payment.initializePayment({
subject: "u",
amount: 100,
currency: "USD",
idempotencyKey: "bad-hook",
});
if (!created.ok) return;
const response = await payment.paymentWebhookHandler("sandbox")({
req: new Request("https://app.test", {
method: "POST",
headers: { "x-wrnexus-signature": "wrong" },
body: JSON.stringify({ id: "bad", intentRef: created.value.gatewayRef, status: "captured" }),
}),
});
expect(response.status).toBe(400);
expect(await payment.checkPayment(created.value.id)).toBe("pending");
});
test("every Tier 1 adapter passes the shared capability contract", () => {
const adapters = [
sandboxGateway(),
stripeGateway({ secretKey: "sk_test", publishableKey: "pk_test", webhookSecret: "whsec" }),
razorpayGateway({ keyId: "rzp_test", keySecret: "secret", webhookSecret: "hook" }),
paypalGateway({ clientId: "client", clientSecret: "secret", webhookId: "hook" }),
];
for (const adapter of adapters)
expect(verifyGatewayContract(adapter)).toEqual({ ok: true, issues: [] });
});
+2 -2
View File
@@ -38,13 +38,13 @@ function createQueue(options?: QueueOptions): Queue;
#### `QueueOptions` #### `QueueOptions`
| Option | Type | Default | Description | | Option | Type | Default | Description |
| ------------- | ------------------------------------ | ---------- | -------------------------------------------------------- | | ------------- | ------------------------------------ | ---------- | --------------------------------------------------------------------------------------- |
| `maxAttempts` | `number` | `3` | Default max attempts per job before it is dead-lettered. | | `maxAttempts` | `number` | `3` | Default max attempts per job before it is dead-lettered. |
| `backoffMs` | `number` | `1000` | Base retry backoff in ms; doubles per attempt. | | `backoffMs` | `number` | `1000` | Base retry backoff in ms; doubles per attempt. |
| `pollMs` | `number` | `250` | Poll interval used once `start()` is called (ms). | | `pollMs` | `number` | `250` | Poll interval used once `start()` is called (ms). |
| `onFailed` | `(job: Job, error: unknown) => void` | — | Called when a job exhausts its attempts. | | `onFailed` | `(job: Job, error: unknown) => void` | — | Called when a job exhausts its attempts. |
| `concurrency` | `number` | unlimited | Maximum jobs claimed by one `drain()` call. | | `concurrency` | `number` | unlimited | Maximum jobs claimed by one `drain()` call. |
| `capacity` | `number` | unlimited | Maximum queued plus active jobs before adds reject. | | `capacity` | `number` | unlimited | Optional maximum queued jobs; omitted queues never scan merely to enforce a hidden cap. |
| `now` | `() => number` | `Date.now` | Clock injection for deterministic tests. | | `now` | `() => number` | `Date.now` | Clock injection for deterministic tests. |
### `Queue` ### `Queue`
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.13", "version": "0.8.14",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -46,7 +46,7 @@ function lazyStore(resolve: () => Promise<QueueStore>): QueueStore {
return (await ready()).list(name); return (await ready()).list(name);
}, },
async findByIdempotencyKey(name, key) { async findByIdempotencyKey(name, key) {
return (await ready()).findByIdempotencyKey?.(name, key) ?? null; return (await ready()).findByIdempotencyKey(name, key);
}, },
async size() { async size() {
return (await ready()).size?.() ?? (await (await ready()).list()).length; return (await ready()).size?.() ?? (await (await ready()).list()).length;
+9 -6
View File
@@ -14,7 +14,8 @@ export interface QueueStore {
remove(id: string): Promise<void>; remove(id: string): Promise<void>;
due(now: number, limit: number): Promise<Job[]>; due(now: number, limit: number): Promise<Job[]>;
list(name?: string): Promise<Job[]>; list(name?: string): Promise<Job[]>;
findByIdempotencyKey?(name: string, key: string): Promise<Job | null>; /** Indexed lookup; implementations must not scan the queue. */
findByIdempotencyKey(name: string, key: string): Promise<Job | null>;
size?(): Promise<number>; size?(): Promise<number>;
claim?(id: string, worker: string, leaseUntil: number, now?: number): Promise<boolean>; claim?(id: string, worker: string, leaseUntil: number, now?: number): Promise<boolean>;
release?(id: string, worker: string): Promise<void>; release?(id: string, worker: string): Promise<void>;
@@ -175,7 +176,8 @@ export function createDurableQueue(options: DurableQueueOptions = {}): DurableQu
const workerId = options.workerId?.trim() || `worker-${crypto.randomUUID()}`; const workerId = options.workerId?.trim() || `worker-${crypto.randomUUID()}`;
const defaultMaxAttempts = positiveInteger(options.maxAttempts ?? 3, "queue maxAttempts"); const defaultMaxAttempts = positiveInteger(options.maxAttempts ?? 3, "queue maxAttempts");
const concurrency = positiveInteger(options.concurrency ?? 10, "queue concurrency"); const concurrency = positiveInteger(options.concurrency ?? 10, "queue concurrency");
const capacity = positiveInteger(options.capacity ?? 10_000, "queue capacity"); const capacity = options.capacity ?? Number.POSITIVE_INFINITY;
if (capacity !== Number.POSITIVE_INFINITY) positiveInteger(capacity, "queue capacity");
const leaseMs = positiveInteger(options.leaseMs ?? 30_000, "queue leaseMs"); const leaseMs = positiveInteger(options.leaseMs ?? 30_000, "queue leaseMs");
const pollMs = positiveInteger(options.pollMs ?? 250, "queue pollMs"); const pollMs = positiveInteger(options.pollMs ?? 250, "queue pollMs");
let sequence = 0; let sequence = 0;
@@ -280,12 +282,13 @@ export function createDurableQueue(options: DurableQueueOptions = {}): DurableQu
} }
if (add.idempotencyKey) { if (add.idempotencyKey) {
const existing = store.findByIdempotencyKey const existing = await store.findByIdempotencyKey(name, add.idempotencyKey);
? await store.findByIdempotencyKey(name, add.idempotencyKey)
: (await store.list(name)).find((job) => job.idempotencyKey === add.idempotencyKey);
if (existing) return existing as Job<T>; if (existing) return existing as Job<T>;
} }
if ((store.size ? await store.size() : (await store.list()).length) >= capacity) { if (
capacity !== Number.POSITIVE_INFINITY &&
(store.size ? await store.size() : (await store.list()).length) >= capacity
) {
throw new Error(`WRN-QUEUE-CAPACITY: queue capacity of ${capacity} reached`); throw new Error(`WRN-QUEUE-CAPACITY: queue capacity of ${capacity} reached`);
} }
+19 -8
View File
@@ -23,9 +23,19 @@ export function redisQueueStore(client: RedisQueueClient, prefix = "wrnexus:queu
const jobs = `${prefix}:jobs`; const jobs = `${prefix}:jobs`;
const due = `${prefix}:due`; const due = `${prefix}:due`;
const key = (id: string) => `${prefix}:job:${id}`; const key = (id: string) => `${prefix}:job:${id}`;
const idempotencyKey = (name: string, value: string) =>
`${prefix}:idempotency:${encodeURIComponent(name)}:${encodeURIComponent(value)}`;
return { return {
async put(job) { async put(job) {
const previousValue = await client.get(key(job.id));
const previous = previousValue ? (JSON.parse(previousValue) as Job) : null;
if (previous?.idempotencyKey && previous.idempotencyKey !== job.idempotencyKey) {
await client.del(idempotencyKey(previous.name, previous.idempotencyKey));
}
await client.set(key(job.id), JSON.stringify(job)); await client.set(key(job.id), JSON.stringify(job));
if (job.idempotencyKey) {
await client.set(idempotencyKey(job.name, job.idempotencyKey), job.id);
}
await client.sadd(jobs, job.id); await client.sadd(jobs, job.id);
await client.zadd(due, job.runAt, job.id); await client.zadd(due, job.runAt, job.id);
await client.del(`${prefix}:lease:${job.id}`); await client.del(`${prefix}:lease:${job.id}`);
@@ -35,7 +45,11 @@ export function redisQueueStore(client: RedisQueueClient, prefix = "wrnexus:queu
return value ? (JSON.parse(value) as Job) : null; return value ? (JSON.parse(value) as Job) : null;
}, },
async remove(id) { async remove(id) {
await client.del(key(id), `${prefix}:lease:${id}`); const value = await client.get(key(id));
const job = value ? (JSON.parse(value) as Job) : null;
const keys = [key(id), `${prefix}:lease:${id}`];
if (job?.idempotencyKey) keys.push(idempotencyKey(job.name, job.idempotencyKey));
await client.del(...keys);
await client.srem(jobs, id); await client.srem(jobs, id);
await client.zrem(due, id); await client.zrem(due, id);
}, },
@@ -55,13 +69,10 @@ export function redisQueueStore(client: RedisQueueClient, prefix = "wrnexus:queu
.filter((job) => !name || job.name === name); .filter((job) => !name || job.name === name);
}, },
async findByIdempotencyKey(name, idempotencyKey) { async findByIdempotencyKey(name, idempotencyKey) {
const ids = await client.smembers(jobs); const id = await client.get(
const values = await Promise.all(ids.map((id) => client.get(key(id)))); `${prefix}:idempotency:${encodeURIComponent(name)}:${encodeURIComponent(idempotencyKey)}`,
const found = values );
.filter((value): value is string => value !== null) return id ? this.get(id) : null;
.map((value) => JSON.parse(value) as Job)
.find((job) => job.name === name && job.idempotencyKey === idempotencyKey);
return found ?? null;
}, },
async size() { async size() {
return (await client.smembers(jobs)).length; return (await client.smembers(jobs)).length;
+15
View File
@@ -260,6 +260,21 @@ test("durable queue supports capacity, cancellation, and shutdown", async () =>
await expect(queue.add("work", {})).rejects.toThrow("WRN-QUEUE-CLOSED"); await expect(queue.add("work", {})).rejects.toThrow("WRN-QUEUE-CLOSED");
}); });
test("durable queue has no implicit capacity scan or 10k ceiling", async () => {
const base = memoryQueueStore();
let sizeCalls = 0;
const store = {
...base,
async size() {
sizeCalls++;
return 10_001;
},
};
const queue = createDurableQueue({ store });
await expect(queue.add("work", {})).resolves.toMatchObject({ name: "work" });
expect(sizeCalls).toBe(0);
});
test("force shutdown aborts an active durable job", async () => { test("force shutdown aborts an active durable job", async () => {
const store = memoryQueueStore(); const store = memoryQueueStore();
const queue = createDurableQueue({ store }); const queue = createDurableQueue({ store });
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/store", "name": "@wrnexus/store",
"version": "0.8.9", "version": "0.8.12",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
@@ -0,0 +1,62 @@
import { expect, test } from "bun:test";
import { StoreContainer, defineStore } from "../src/index.ts";
test("memory persistence includes only approved fields and migrates old state", async () => {
const original = defineStore({
name: "preferences-migration",
kind: "global" as const,
createSharedState: () => ({ theme: "dark", secret: "initial" }),
persist: { storage: "memory" as const, version: 1, include: ["theme"] },
actions: {
setTheme: {
runtime: "client" as const,
handler: ({ state }: any, theme: string) => void (state.theme = theme),
},
},
});
const first = await new StoreContainer({ runtime: "client" }).use(original);
await first.actions.setTheme("light");
const migrated = defineStore({
...original,
persist: {
storage: "memory" as const,
version: 2,
include: ["theme"],
migrate: (state: any) => ({ ...state, theme: `${state.theme}-v2` }),
},
});
const second = await new StoreContainer({ runtime: "client" }).use(migrated);
expect(second.state.theme).toBe("light-v2");
expect(second.state.secret).toBe("initial");
});
test("invalid persisted state fails closed to the store defaults", async () => {
const name = `preferences-validation-${crypto.randomUUID()}`;
const firstDefinition = defineStore({
name,
kind: "global" as const,
createSharedState: () => ({ theme: "dark" }),
persist: { storage: "memory" as const, version: 1, include: ["theme"] },
actions: {
setTheme: {
runtime: "client" as const,
handler: ({ state }: any, theme: string) => void (state.theme = theme),
},
},
});
const first = await new StoreContainer({ runtime: "client" }).use(firstDefinition);
await first.actions.setTheme("corrupt");
const validated = defineStore({
...firstDefinition,
persist: {
storage: "memory" as const,
version: 1,
include: ["theme"],
validate: (value: any) => (value?.theme === "light" ? value : null),
},
});
const second = await new StoreContainer({ runtime: "client" }).use(validated);
expect(second.state.theme).toBe("dark");
});
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.19", "version": "0.8.20",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+8
View File
@@ -313,6 +313,14 @@ export interface AppConfig {
queue?: string | boolean; queue?: string | boolean;
sandbox?: { enabled?: boolean; allowlist?: string[] }; sandbox?: { enabled?: boolean; allowlist?: string[] };
}; };
/** Payment gateway configuration consumed by @wrnexus/payment. */
payment?: {
default: string;
currency?: string;
sandbox?: boolean;
databaseName?: string;
gateways?: Record<string, Record<string, unknown>>;
};
/** /**
* File-upload storage. Declare named stores (local dir or S3-compatible), * File-upload storage. Declare named stores (local dir or S3-compatible),
* upload with `handleUpload`/`upload` from `@wrnexus/uploader`, and serve * upload with `handleUpload`/`upload` from `@wrnexus/uploader`, and serve
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.13", "version": "0.8.18",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+34
View File
@@ -767,6 +767,28 @@ function expandStructuredStateDeclarations(lines, unit) {
}); });
} }
function formatStructuredPropDeclaration(value, unit, depth, printWidth) {
const match = /^([A-Za-z_$][\w$]*\??\s*(?::\s*[^=]+?)?\s*=\s*)([\[{][\s\S]*)$/.exec(
value,
);
if (!match) return null;
try {
const parsed = JSON.parse(match[2].trim());
const compact = JSON.stringify(parsed);
const indentation = unit.repeat(depth);
if (indentation.length + match[1].length + compact.length <= printWidth) return null;
const jsonLines = JSON.stringify(parsed, null, unit).split("\n");
return [
`${indentation}${match[1]}${jsonLines[0]}`,
...jsonLines.slice(1).map((jsonLine) => `${indentation}${jsonLine}`),
];
} catch {
return null;
}
}
function formatWrnPass(source: string, options: FormatWrnOptions = {}): string { function formatWrnPass(source: string, options: FormatWrnOptions = {}): string {
const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize ?? 4); const unit = options.insertSpaces === false ? "\t" : " ".repeat(options.tabSize ?? 4);
@@ -844,6 +866,18 @@ function formatWrnPass(source: string, options: FormatWrnOptions = {}): string {
continue; continue;
} }
const structuredProp = formatStructuredPropDeclaration(
value,
unit,
codeDepth + htmlDepth + controlDepth,
printWidth,
);
if (structuredProp) {
output.push(...structuredProp);
index += 1;
continue;
}
const leadingClosingBraces = countLeadingClosingBraces(value); const leadingClosingBraces = countLeadingClosingBraces(value);
const closesControlBlock = isControlBlockClose(value); const closesControlBlock = isControlBlockClose(value);
+2
View File
@@ -2,6 +2,8 @@ export { Lexer, LexError, skipLiteralOrComment } from "./tokenizer.ts";
export { formatWrn } from "./formatter.ts"; export { formatWrn } from "./formatter.ts";
export type { FormatWrnOptions } from "./formatter.ts"; export type { FormatWrnOptions } from "./formatter.ts";
export { parse, parseHtmlView, ParseError, VOID_ELEMENTS } from "./parser.ts"; export { parse, parseHtmlView, ParseError, VOID_ELEMENTS } from "./parser.ts";
export { extractInterpolations } from "./interpolation.ts";
export type { InterpolationSegment } from "./interpolation.ts";
export type { export type {
ActionBlock, ActionBlock,
ApiBlock, ApiBlock,
+39
View File
@@ -0,0 +1,39 @@
export interface InterpolationSegment {
expression: string;
start: number;
end: number;
}
/** Extract top-level `{...}` expressions while preserving nested object/array literals. */
export function extractInterpolations(value: string): InterpolationSegment[] {
const segments: InterpolationSegment[] = [];
let index = 0;
while (index < value.length) {
if (value[index] !== "{") {
index++;
continue;
}
const start = index++;
let depth = 1;
let quote: string | null = null;
while (index < value.length && depth > 0) {
const char = value[index]!;
if (quote) {
if (char === "\\" && index + 1 < value.length) index += 2;
else {
if (char === quote) quote = null;
index++;
}
continue;
}
if (char === '"' || char === "'" || char === "`") quote = char;
else if (char === "{") depth++;
else if (char === "}") depth--;
index++;
}
if (depth === 0) {
segments.push({ expression: value.slice(start + 1, index - 1).trim(), start, end: index });
}
}
return segments;
}
@@ -0,0 +1,35 @@
import { expect, test } from "bun:test";
import { hasRequestSection, parseApiEntries, parseApiSections } from "../src/api-sections.ts";
test("api section scanning ignores keywords in literals, comments, and nested objects", () => {
const source = `
// request { body { forged: string } }
response {
return { label: "request { not a section }", nested: { body: true } }
}
`;
expect(hasRequestSection(source)).toBe(false);
expect(parseApiSections(source)?.response).toContain("nested");
});
test("api entries normalize methods and reject malformed request fields", () => {
expect(
parseApiEntries(`lookup get /users/:id {
response { return data }
}`)[0],
).toMatchObject({
name: "lookup",
method: "GET",
path: "/users/:id",
});
expect(() =>
parseApiSections("request { body { missingType } } response { return data }"),
).toThrow('Expected "name: type"');
});
test("api entries reject truncated blocks and bare response bodies", () => {
expect(() => parseApiEntries("lookup GET /users { response { return data }")).toThrow();
expect(() => parseApiEntries("lookup GET /users { return data }")).toThrow(
'declare a "response { }" section',
);
});
+15
View File
@@ -83,3 +83,18 @@ test("preserves balanced compact sibling markup without indentation drift", () =
expect(formatted).toContain('<div class="strip"><span>Page</span><b>→</b><span>API</span></div>'); expect(formatted).toContain('<div class="strip"><span>Page</span><b>→</b><span>API</span></div>');
expect(formatWrn(formatted, { tabSize: 2, printWidth: 80 })).toBe(formatted); expect(formatWrn(formatted, { tabSize: 2, printWidth: 80 })).toBe(formatted);
}); });
test("formats large typed JSON prop defaults and remains idempotent", () => {
const source = `component Catalog {
props {
categories: unknown[] = [{"label":"Platform & Core","value":"platform","apps":[{"label":"WRNexus Home / Business Command Center","href":"/products/home","number":"01"},{"label":"SSO & Account","href":"/products/sso","number":"02"}]},{"label":"Sales & Customer","value":"sales","apps":[{"label":"CRM","href":"/products/crm","number":"11"}]}]
}
view { <div></div> }
}`;
const formatted = formatWrn(source, options);
expect(formatted).toContain(`categories: unknown[] = [\n {\n "label": "Platform & Core"`);
expect(formatted).toContain(`"apps": [\n {`);
expect(formatWrn(formatted, options)).toBe(formatted);
expect(() => parse(formatted)).not.toThrow();
});
+12 -1
View File
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test"; import { expect, test } from "bun:test";
import { diagnose, formatDiagnostic, parse } from "../src/index.ts"; import { diagnose, extractInterpolations, formatDiagnostic, parse } from "../src/index.ts";
test("parses explicit rendering modes and the never hydration alias", () => { test("parses explicit rendering modes and the never hydration alias", () => {
const ast = parse(`page Marketing { const ast = parse(`page Marketing {
@@ -337,3 +337,14 @@ test("a block comment inside props is still refused with its own message", () =>
`; `;
expect(() => parse(source)).toThrow("Block comments are not allowed inside props"); expect(() => parse(source)).toThrow("Block comments are not allowed inside props");
}); });
test("extracts nested object and array interpolation as one expression", () => {
const value = `prefix {[{ label: 'One', meta: { count: 1 } }, { label: "Two" }]} suffix`;
expect(extractInterpolations(value)).toEqual([
{
expression: `[{ label: 'One', meta: { count: 1 } }, { label: "Two" }]`,
start: 7,
end: value.length - 7,
},
]);
});
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.11", "version": "0.8.12",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+2
View File
@@ -205,3 +205,5 @@ export type {
} from "./advanced.ts"; } from "./advanced.ts";
export { withDatabaseRollback, createFactory, captureBrowserArtifacts } from "./platform.ts"; export { withDatabaseRollback, createFactory, captureBrowserArtifacts } from "./platform.ts";
export type { TransactionalDatabase, BrowserArtifactPage } from "./platform.ts"; export type { TransactionalDatabase, BrowserArtifactPage } from "./platform.ts";
export { detectMutations } from "./mutation.ts";
export type { MutationCase, MutationReport, DetectMutationsOptions } from "./mutation.ts";
+51
View File
@@ -0,0 +1,51 @@
export interface MutationCase<T> {
name: string;
value: T;
}
export interface MutationReport<O> {
baseline: O;
killed: string[];
survived: string[];
}
export interface DetectMutationsOptions<T, O> {
baseline: T;
mutations: Array<MutationCase<T>>;
exercise(value: T): O | Promise<O>;
equivalent?(left: O, right: O): boolean;
}
/**
* Run the same behavioural probe against a baseline and explicit mutations.
* A mutation survives when the observable result is unchanged. The helper
* throws with every survivor by default, making it suitable for an ordinary
* Bun test and CI without a second runner or source-rewriting process.
*/
export async function detectMutations<T, O>(
options: DetectMutationsOptions<T, O>,
): Promise<MutationReport<O>> {
if (!options.mutations.length) {
throw new Error("WRN-MUTATION-EMPTY: declare at least one meaningful mutation");
}
const equivalent = options.equivalent ?? ((left, right) => Object.is(left, right));
const baseline = await options.exercise(options.baseline);
const killed: string[] = [];
const survived: string[] = [];
for (const mutation of options.mutations) {
if (!mutation.name.trim()) throw new Error("WRN-MUTATION-NAME: every mutation needs a name");
let same = false;
try {
same = equivalent(baseline, await options.exercise(mutation.value));
} catch {
// A behavioural failure is a killed mutation.
}
(same ? survived : killed).push(mutation.name);
}
if (survived.length) {
throw Object.assign(new Error(`WRN-MUTATION-SURVIVED: ${survived.join(", ")}`), {
report: { baseline, killed, survived },
});
}
return { baseline, killed, survived };
}
+35
View File
@@ -0,0 +1,35 @@
import { expect, test } from "bun:test";
import { detectMutations } from "../src/mutation.ts";
test("detectMutations reports mutations whose behaviour changes", async () => {
const report = await detectMutations({
baseline: (amount: number) => amount,
mutations: [
{ name: "invert sign", value: (amount: number) => -amount },
{ name: "drop amount", value: () => 0 },
],
exercise: (adjust) => adjust(7),
});
expect(report.killed).toEqual(["invert sign", "drop amount"]);
expect(report.survived).toEqual([]);
});
test("detectMutations fails with the names of surviving mutations", async () => {
await expect(
detectMutations({
baseline: (amount: number) => amount,
mutations: [{ name: "wrong implementation", value: (amount: number) => amount }],
exercise: (adjust) => adjust(7),
}),
).rejects.toThrow(/WRN-MUTATION-SURVIVED: wrong implementation/);
});
test("detectMutations accepts structured observations through an equivalence function", async () => {
const report = await detectMutations({
baseline: { status: 200 },
mutations: [{ name: "deny request", value: { status: 403 } }],
exercise: async (response) => ({ ...response, volatile: crypto.randomUUID() }),
equivalent: (left, right) => left.status === right.status,
});
expect(report.killed).toEqual(["deny request"]);
});
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/typecheck", "name": "@wrnexus/typecheck",
"version": "0.8.13", "version": "0.8.16",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+46 -6
View File
@@ -3,6 +3,7 @@ import { dirname, join, normalize, resolve } from "node:path";
import ts from "typescript"; import ts from "typescript";
import { import {
containsReadonlyPropMutation, containsReadonlyPropMutation,
extractInterpolations,
inferredRuntimeType, inferredRuntimeType,
parse, parse,
runtimeTypeOf, runtimeTypeOf,
@@ -139,7 +140,7 @@ function functionDeclaration(fn: RuntimeFunctionDecl): string {
const params = fn.parameters const params = fn.parameters
.map( .map(
(param) => (param) =>
`${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": CustomEvent<any> & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`, `${param.name}${param.optional ? "?" : ""}${param.valueType ? `: ${param.valueType}` : param.name === "event" ? ": Event & { target: HTMLElement }" : ": any"}${param.default ? ` = ${param.default}` : ""}`,
) )
.join(", "); .join(", ");
// An omitted WRN parameter type follows JavaScript semantics. Emitting it as // An omitted WRN parameter type follows JavaScript semantics. Emitting it as
@@ -214,6 +215,31 @@ function safeBindingName(name: string): boolean {
return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED_BINDING_NAMES.has(name); return /^[A-Za-z_$][\w$]*$/.test(name) && !RESERVED_BINDING_NAMES.has(name);
} }
function safeViewExpression(expression: string, reservedProps: ReadonlySet<string>): string {
if (!reservedProps.size) return expression;
const scanner = ts.createScanner(
ts.ScriptTarget.Latest,
false,
ts.LanguageVariant.Standard,
expression,
);
let result = "";
let copied = 0;
for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
const start = scanner.getTokenPos();
const end = scanner.getTextPos();
const text = scanner.getTokenText();
if (!reservedProps.has(text)) continue;
const previous = expression.slice(0, start).trimEnd().at(-1);
const following = expression.slice(end).trimStart().at(0);
// Property access and object-literal keys are already valid TypeScript.
if (previous === "." || following === ":") continue;
result += expression.slice(copied, start) + `props.${text}`;
copied = end;
}
return result + expression.slice(copied);
}
export function virtualTypeScriptModule( export function virtualTypeScriptModule(
source: string, source: string,
filePath = "component.wrn", filePath = "component.wrn",
@@ -307,6 +333,9 @@ export function virtualTypeScriptModule(
if (ast.dataApis.length) append(`declare const api: { ${apiType} };`); if (ast.dataApis.length) append(`declare const api: { ${apiType} };`);
append(`declare const props: Readonly<${ast.name}Props>;`); append(`declare const props: Readonly<${ast.name}Props>;`);
append("declare const refs: Record<string, Element | null>;"); append("declare const refs: Record<string, Element | null>;");
append(
"declare const toast: ((message: string, options?: Record<string, unknown>) => unknown) & { success(message: string, options?: Record<string, unknown>): unknown; error(message: string, options?: Record<string, unknown>): unknown; danger(message: string, options?: Record<string, unknown>): unknown; warning(message: string, options?: Record<string, unknown>): unknown; info(message: string, options?: Record<string, unknown>): unknown; dismiss(id: string | number): void; clear(): void };",
);
append( append(
"declare function useFetch(path: string, method?: string | { method?: string; query?: unknown; params?: unknown; body?: unknown; data?: unknown }, input?: unknown): Promise<any>;", "declare function useFetch(path: string, method?: string | { method?: string; query?: unknown; params?: unknown; body?: unknown; data?: unknown }, input?: unknown): Promise<any>;",
); );
@@ -348,21 +377,29 @@ export function virtualTypeScriptModule(
// misspelled state/function name cannot silently become `undefined` at // misspelled state/function name cannot silently become `undefined` at
// runtime. Each/handler locals mirror the browser evaluator's bindings. // runtime. Each/handler locals mirror the browser evaluator's bindings.
let bindingIndex = 0; let bindingIndex = 0;
const reservedProps = new Set(
ast.props.map((prop) => prop.name).filter((name) => !safeBindingName(name)),
);
const appendViewBindings = (nodes: ViewNode[], locals: Set<string>): void => { const appendViewBindings = (nodes: ViewNode[], locals: Set<string>): void => {
for (const node of nodes) { for (const node of nodes) {
if (node.type === "element") { if (node.type === "element") {
for (const attr of node.attrs) { for (const attr of node.attrs) {
const expressions = attr.event const expressions = attr.event
? [attr.value] ? [attr.value]
: [...attr.value.matchAll(/\{([^{}]+)\}/g)].map((match) => match[1]!.trim()); : extractInterpolations(attr.value).map((segment) => segment.expression);
for (const expression of expressions) { for (const expression of expressions) {
if (!expression.trim()) continue; if (!expression.trim()) continue;
const declarations = [ const declarations = [
...[...locals].map((name) => `declare const ${name}: any;`), ...[...locals].map((name) => `declare const ${name}: any;`),
...(attr.event ? ["declare const payload: any;", "declare const event: Event;"] : []), ...(attr.event
? [
"declare const payload: any;",
"declare const event: Event & { target: HTMLElement };",
]
: []),
].join(" "); ].join(" ");
append( append(
`namespace __wrn_view_${bindingIndex++} { ${declarations} void (${expression}); }`, `namespace __wrn_view_${bindingIndex++} { ${declarations} void (${safeViewExpression(expression, reservedProps)}); }`,
attr.value, attr.value,
); );
} }
@@ -372,13 +409,16 @@ export function virtualTypeScriptModule(
for (const branch of node.branches) { for (const branch of node.branches) {
if (branch.cond) if (branch.cond)
append( append(
`namespace __wrn_view_${bindingIndex++} { void (${branch.cond}); }`, `namespace __wrn_view_${bindingIndex++} { void (${safeViewExpression(branch.cond, reservedProps)}); }`,
branch.cond, branch.cond,
); );
appendViewBindings(branch.body, locals); appendViewBindings(branch.body, locals);
} }
} else if (node.type === "each") { } else if (node.type === "each") {
append(`namespace __wrn_view_${bindingIndex++} { void (${node.list}); }`, node.list); append(
`namespace __wrn_view_${bindingIndex++} { void (${safeViewExpression(node.list, reservedProps)}); }`,
node.list,
);
const nested = new Set(locals); const nested = new Set(locals);
nested.add(node.item); nested.add(node.item);
if (node.index) nested.add(node.index); if (node.index) nested.add(node.index);
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test"; import { expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { mkdirSync, mkdtempSync } from "node:fs";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { checkWrnSource } from "../src/index.ts"; import { checkWrnSource } from "../src/index.ts";
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test"; import { expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
import { checkWrnSource } from "../src/index.ts"; import { checkWrnSource } from "../src/index.ts";
@@ -82,6 +82,23 @@ page Home {
expect(diagnostics.some((item) => item.code === "WRN-COMPONENT-PROP-TYPE")).toBe(false); expect(diagnostics.some((item) => item.code === "WRN-COMPONENT-PROP-TYPE")).toBe(false);
expect(diagnostics.some((item) => item.code === "WRN-TYPE-1389")).toBe(false); expect(diagnostics.some((item) => item.code === "WRN-TYPE-1389")).toBe(false);
expect(diagnostics.some((item) => item.code === "WRN-TYPE-1005")).toBe(false); expect(diagnostics.some((item) => item.code === "WRN-TYPE-1005")).toBe(false);
expect(
checkWrnSource(readFileSync(componentPath, "utf8"), { appRoot: root, filePath: componentPath }),
).toEqual([]);
}, 15_000);
test("implicit DOM events satisfy untyped event handler parameters", () => {
const root = fixture();
const filePath = join(root, "app", "components", "Submit.wrn");
const diagnostics = checkWrnSource(
`component Submit {
outputs { submit(payload: { event: Event }) }
functions { client function submitForm(event) { output.submit({ event }) } }
view { <form @submit="submitForm(event)"></form> }
}`,
{ appRoot: root, filePath },
);
expect(diagnostics).toEqual([]);
}, 15_000); }, 15_000);
test("does not treat UI component imports as JavaScript exports", () => { test("does not treat UI component imports as JavaScript exports", () => {
+29
View File
@@ -112,3 +112,32 @@ test("accepts handler payload, loop locals, and the public useFetch helper", ()
); );
expect(diagnostics.filter((diagnostic) => diagnostic.code === "WRN-TYPE-2304")).toEqual([]); expect(diagnostics.filter((diagnostic) => diagnostic.code === "WRN-TYPE-2304")).toEqual([]);
}); });
test("accepts multiline array props containing nested object literals", () => {
const root = app();
const diagnostics = checkWrnSource(
`page Contact {
view {
<div data-component="StatsBar" items="{[
{ label: 'Product catalog', value: '81', suffix: ' apps' },
{ label: 'Starting point', value: 'One workflow' }
]}"></div>
}
}`,
{ appRoot: root, filePath: join(root, "app", "pages", "contact.wrn") },
);
expect(diagnostics.filter((diagnostic) => diagnostic.code.startsWith("WRN-TYPE-"))).toEqual([]);
});
test("provides the toast API to view event expressions", () => {
const root = app();
const diagnostics = checkWrnSource(
`page Contact {
view {
<form @wrn:success="toast.success('Sent', { title: 'Request sent' })"></form>
}
}`,
{ appRoot: root, filePath: join(root, "app", "pages", "contact.wrn") },
);
expect(diagnostics.filter((diagnostic) => diagnostic.code.startsWith("WRN-TYPE-"))).toEqual([]);
});
+15 -15
View File
@@ -45,7 +45,7 @@ Secure multi-cell PIN and verification-code input with regex and paste support.
Theme-aware, responsive strong password component. Theme-aware, responsive strong password component.
- Mount: `data-component="StrongPassword"` - Mount: `data-component="StrongPassword"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Password"`, `name: string = "password"`, `value: string = ""`, `placeholder: string = "Create a strong password"`, `autocomplete: string = "new-password"`, `minLength: number = 8`, `specialCharactersSet: string = "!@#$%^&*()_+-=[]{}|;:,.<>?"`, `requireLowercase: boolean = true`, `requireUppercase: boolean = true`, `requireNumber: boolean = true`, `requireSpecialCharacter: boolean = true`, `showRequirements: boolean = true`, `presentation: string = "inline"`, `hintText: string = "Use a unique password you do not use elsewhere."`, `emptyLabel: string = "Enter a password"`, `weakLabel: string = "Weak"`, `fairLabel: string = "Fair"`, `goodLabel: string = "Good"`, `strongLabel: string = "Strong"`, `disabled: boolean = false`, `readonly: boolean = false`, `required: boolean = false`, `invalid: boolean = false`, `validationMessage: string = ""`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Password"`, `name: string = "password"`, `value: string = ""`, `placeholder: string = "Create a strong password"`, `autocomplete: string = "new-password"`, `minLength: number = 8`, `specialCharactersSet: string = "!@#$%^&*()_+-=[]{}|;:,.<>?"`, `requireLowercase: boolean = true`, `requireUppercase: boolean = true`, `requireNumber: boolean = true`, `requireSpecialCharacter: boolean = true`, `showRequirements: boolean = true`, `presentation: string = "inline"`, `hintText: string = "Use a unique password you do not use elsewhere."`, `emptyLabel: string = "Enter a password"`, `weakLabel: string = "Weak"`, `fairLabel: string = "Fair"`, `goodLabel: string = "Good"`, `strongLabel: string = "Strong"`, `disabled: boolean = false`, `readonly: boolean = false`, `required: boolean = false`, `invalid: boolean = false`, `validationMessage: string = ""`, `showLabel: string = "Show password"`, `hideLabel: string = "Hide password"`, `class: string = ""`
- Slots: None - Slots: None
- Outputs: `input({ value: string; score: number; maximumScore: number; percent: number; level: string })`, `change({ value: string; score: number; maximumScore: number; percent: number; level: string })`, `strength({ value: string; score: number; maximumScore: number; percent: number; level: string })` - Outputs: `input({ value: string; score: number; maximumScore: number; percent: number; level: string })`, `change({ value: string; score: number; maximumScore: number; percent: number; level: string })`, `strength({ value: string; score: number; maximumScore: number; percent: number; level: string })`
@@ -146,7 +146,7 @@ Theme-aware, responsive button group component.
Group related content in a responsive themed surface with title, description, content, and supporting slots. Group related content in a responsive themed surface with title, description, content, and supporting slots.
- Mount: `data-component="Card"` - Mount: `data-component="Card"`
- Props: `title: string = "Card title"`, `subtitle: string = ""`, `description: string = ""`, `header: string = ""`, `footer: string = ""`, `imageSrc: string = ""`, `imageAlt: string = ""`, `imagePosition: string = "top"`, `actionLabel: string = ""`, `actionHref: string = ""`, `headerActions: unknown[] = []`, `navigation: unknown[] = []`, `activeNav: string = ""`, `mobileNavigation: boolean = false`, `alertTitle: string = ""`, `alertDescription: string = ""`, `empty: boolean = false`, `emptyTitle: string = "No data to show"`, `emptyIcon: string = "icon-[lucide--inbox]"`, `items: unknown[] = []`, `size: string = "md"`, `color: string = "primary"`, `variant: string = "default"`, `layout: string = "vertical"`, `align: string = "left"`, `hover: string = "none"`, `scrollable: boolean = false`, `maxHeight: string = "18rem"`, `dismissible: boolean = false`, `ariaLabel: string = ""`, `class: string = ""` - Props: `title: string = ""`, `icon: string = ""`, `titleKey: string = ""`, `subtitle: string = ""`, `subtitleKey: string = ""`, `description: string = ""`, `descriptionKey: string = ""`, `header: string = ""`, `footer: string = ""`, `imageSrc: string = ""`, `imageAlt: string = ""`, `imagePosition: string = "top"`, `actionLabel: string = ""`, `actionLabelKey: string = ""`, `actionHref: string = ""`, `headerActions: unknown[] = []`, `navigation: unknown[] = []`, `activeNav: string = ""`, `mobileNavigation: boolean = false`, `alertTitle: string = ""`, `alertDescription: string = ""`, `empty: boolean = false`, `emptyTitle: string = "No data to show"`, `emptyIcon: string = "icon-[lucide--inbox]"`, `items: unknown[] = []`, `size: string = "md"`, `color: string = "primary"`, `variant: string = "default"`, `layout: string = "vertical"`, `align: string = "left"`, `hover: string = "none"`, `radius: string = "default"`, `shadow: string = "default"`, `equalHeight: boolean = false`, `scrollable: boolean = false`, `maxHeight: string = "18rem"`, `dismissible: boolean = false`, `ariaLabel: string = ""`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `click({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `action({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `navigate({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `dismiss({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `load({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)` - Outputs: `click({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `action({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `navigate({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `dismiss({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `load({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `error({ error: Error | string; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | Error | string)`
@@ -337,7 +337,7 @@ Reusable portal dashboard component.
Reusable preference switcher component. Reusable preference switcher component.
- Mount: `data-component="PreferenceSwitcher"` - Mount: `data-component="PreferenceSwitcher"`
- Props: `size: string = "default"`, `color: string = "primary"`, `themeLabel: string = "Theme"`, `colorLabel: string = "Accent color"`, `languageLabel: string = "Language"`, `languages: string = [`, `colors: string = [`, `compact: boolean = true`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `themeLabel: string = "Theme"`, `colorLabel: string = "Accent color"`, `languageLabel: string = "Language"`, `languages: string = [`, `colors: string = [`, `compact: boolean = true`, `iconOnly: boolean = true`, `themeIcon: string = "icon-[lucide--sun-moon]"`, `colorIcon: string = "icon-[lucide--palette]"`, `languageIcon: string = "icon-[lucide--languages]"`, `panelPlacement: string = "bottom-end"`, `mobilePanel: string = "dropdown"`, `showCurrentLanguage: boolean = true`, `class: string = ""`
- Slots: None - Slots: None
- Outputs: `theme({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `color({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `language({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])` - Outputs: `theme({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `color({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`, `language({ sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[])`
@@ -375,7 +375,7 @@ Arrange operational metrics, KPIs, public statistics, or service indicators in a
Present a compact responsive strip of key facts, counts, performance indicators, or trust signals. Present a compact responsive strip of key facts, counts, performance indicators, or trust signals.
- Mount: `data-component="StatsBar"` - Mount: `data-component="StatsBar"`
- Props: `items: unknown[] = []`, `columns: number = 4`, `compact: boolean = true`, `dividers: boolean = true`, `icons: boolean = true`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "raised"`, `maxWidth: string = "xl"`, `class: string = ""` - Props: `items: unknown[] = []`, `columns: number = 4`, `compact: boolean = true`, `dividers: boolean = true`, `icons: boolean = true`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "raised"`, `maxWidth: string = "xl"`, `radius: string = "default"`, `shadow: string = "default"`, `gutter: string = "md"`, `outerSpacing: string = "none"`, `mobileColumns: number = 1`, `class: string = ""`
- Slots: None - Slots: None
- Outputs: None - Outputs: None
@@ -606,7 +606,7 @@ Arrange feature cards, services, solutions, or benefits in a responsive equal-he
Render structured responsive footer navigation, pre and post content, copyright content, links, and public events. Render structured responsive footer navigation, pre and post content, copyright content, links, and public events.
- Mount: `data-component="Footer"` - Mount: `data-component="Footer"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Footer navigation"`, `items: unknown[] = []`, `columns: number = 3`, `maxWidth: string = "compact"`, `copyright: string = ""`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Footer navigation"`, `items: unknown[] = []`, `columns: number = 3`, `maxWidth: string = "compact"`, `mobileColumns: number = 2`, `mobileCopyrightAlign: string = "center"`, `copyright: string = ""`, `copyrightKey: string = ""`, `translationPrefix: string = ""`, `class: string = ""`
- Slots: `pre-footer`, `post-footer`, `copyright-left`, `copyright-right` - Slots: `pre-footer`, `post-footer`, `copyright-left`, `copyright-right`
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `action({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })` - Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`, `action({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
@@ -698,7 +698,7 @@ Apply consistent readable responsive typography, widths, columns, spacing, and e
Publish a responsive notice with badge, icon, supporting copy, action, dismiss behavior, and width controls. Publish a responsive notice with badge, icon, supporting copy, action, dismiss behavior, and width controls.
- Mount: `data-component="AnnouncementBar"` - Mount: `data-component="AnnouncementBar"`
- Props: `badge: string = ""`, `badgeIcon: string = ""`, `message: string = "Announcement"`, `description: string = ""`, `icon: string = "icon-[lucide--megaphone]"`, `actionLabel: string = ""`, `actionHref: string = ""`, `actionIcon: string = ""`, `dismissible: boolean = false`, `dismissLabel: string = "Dismiss announcement"`, `sticky: boolean = false`, `compact: boolean = false`, `size: string = "default"`, `width: string = "default"`, `color: string = "primary"`, `variant: string = "soft"`, `role: string = "status"`, `live: string = "polite"`, `class: string = ""` - Props: `badge: string = ""`, `badgeIcon: string = ""`, `message: string = "Announcement"`, `description: string = ""`, `icon: string = "icon-[lucide--megaphone]"`, `actionLabel: string = ""`, `actionHref: string = ""`, `actionIcon: string = ""`, `dismissible: boolean = false`, `dismissLabel: string = "Dismiss announcement"`, `sticky: boolean = false`, `compact: boolean = false`, `flush: boolean = false`, `mobileCompact: boolean = false`, `mobileMessageLines: number = 2`, `hideBadgeOnMobile: boolean = false`, `hideDescriptionOnMobile: boolean = false`, `mobileActionSize: string = "default"`, `gutter: string = "md"`, `contentGutter: string = "md"`, `flushMobile: boolean = false`, `size: string = "default"`, `width: string = "default"`, `color: string = "primary"`, `variant: string = "soft"`, `role: string = "status"`, `live: string = "polite"`, `class: string = ""`
- Slots: None - Slots: None
- Outputs: `dismiss({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })` - Outputs: `dismiss({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`
@@ -707,7 +707,7 @@ Publish a responsive notice with badge, icon, supporting copy, action, dismiss b
Close a page or major section with conversion-focused copy, actions, and optional supporting visual content. Close a page or major section with conversion-focused copy, actions, and optional supporting visual content.
- Mount: `data-component="CTASection"` - Mount: `data-component="CTASection"`
- Props: `eyebrow: string = ""`, `title: string = "Ready to get started?"`, `description: string = ""`, `icon: string = ""`, `align: string = "center"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "solid"`, `primaryLabel: string = "Get started"`, `primaryHref: string = "#"`, `primaryIcon: string = ""`, `secondaryLabel: string = ""`, `secondaryHref: string = ""`, `secondaryIcon: string = ""`, `backgroundImage: string = ""`, `visualImage: string = ""`, `visualAlt: string = ""`, `visualIcon: string = ""`, `visualTitle: string = ""`, `visualDescription: string = ""`, `visualItems: unknown[] = []`, `visualPosition: string = "right"`, `maxWidth: string = "xl"`, `fullBleed: boolean = false`, `class: string = ""` - Props: `eyebrow: string = ""`, `title: string = "Ready to get started?"`, `description: string = ""`, `icon: string = ""`, `align: string = "center"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "solid"`, `spacing: string = "none"`, `radius: string = "default"`, `titleMaxWidth: string = ""`, `contentPadding: string = "default"`, `mobilePadding: string = "default"`, `mobileActions: string = "stack"`, `primaryLabel: string = "Get started"`, `primaryHref: string = "#"`, `primaryIcon: string = ""`, `secondaryLabel: string = ""`, `secondaryHref: string = ""`, `secondaryIcon: string = ""`, `backgroundImage: string = ""`, `visualImage: string = ""`, `visualAlt: string = ""`, `visualIcon: string = ""`, `visualTitle: string = ""`, `visualDescription: string = ""`, `visualItems: unknown[] = []`, `visualPosition: string = "right"`, `maxWidth: string = "xl"`, `fullBleed: boolean = false`, `class: string = ""`
- Slots: `default`, `actions`, `visual`, `footer` - Slots: `default`, `actions`, `visual`, `footer`
- Outputs: None - Outputs: None
@@ -725,7 +725,7 @@ Present one linked feature or service with media, icon, badge, description, and
Present a compact feature or benefit with a styled icon, title, description, badge, and optional link. Present a compact feature or benefit with a styled icon, title, description, badge, and optional link.
- Mount: `data-component="FeatureIconCard"` - Mount: `data-component="FeatureIconCard"`
- Props: `icon: string = "icon-[lucide--sparkles]"`, `iconSize: string = "md"`, `iconVariant: string = "soft"`, `title: string = "Feature"`, `description: string = ""`, `href: string = ""`, `actionLabel: string = "Explore"`, `badge: string = ""`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `align: string = "left"`, `hover: string = "lift"`, `class: string = ""` - Props: `icon: string = "icon-[lucide--sparkles]"`, `iconSize: string = "md"`, `iconVariant: string = "soft"`, `title: string = "Feature"`, `description: string = ""`, `href: string = ""`, `actionLabel: string = "Explore"`, `badge: string = ""`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `align: string = "left"`, `hover: string = "lift"`, `radius: string = "default"`, `shadow: string = "default"`, `clickableSurface: boolean = false`, `descriptionTone: string = "default"`, `class: string = ""`
- Slots: `default`, `footer` - Slots: `default`, `footer`
- Outputs: None - Outputs: None
@@ -770,7 +770,7 @@ Introduce an internal or public page with breadcrumbs, icon, title, description,
Build a responsive two-column introduction balancing descriptive content with a visual, image, or structured data panel. Build a responsive two-column introduction balancing descriptive content with a visual, image, or structured data panel.
- Mount: `data-component="SplitHero"` - Mount: `data-component="SplitHero"`
- Props: `eyebrow: string = ""`, `eyebrowIcon: string = "icon-[lucide--sparkles]"`, `title: string = "A better digital experience"`, `highlight: string = ""`, `description: string = ""`, `primaryLabel: string = ""`, `primaryHref: string = ""`, `primaryIcon: string = ""`, `secondaryLabel: string = ""`, `secondaryHref: string = ""`, `secondaryIcon: string = ""`, `visualPosition: string = "right"`, `reverse: boolean = false`, `ratio: string = "balanced"`, `align: string = "left"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `maxWidth: string = "xl"`, `fullBleed: boolean = true`, `backgroundImage: string = ""`, `visualImage: string = ""`, `visualAlt: string = ""`, `visualIcon: string = ""`, `visualEyebrow: string = ""`, `visualTitle: string = ""`, `visualDescription: string = ""`, `visualItems: unknown[] = []`, `visualStyle: string = "panel"`, `trustItems: unknown[] = []`, `class: string = ""` - Props: `eyebrow: string = ""`, `eyebrowIcon: string = "icon-[lucide--sparkles]"`, `title: string = "A better digital experience"`, `highlight: string = ""`, `description: string = ""`, `primaryLabel: string = ""`, `primaryHref: string = ""`, `primaryIcon: string = ""`, `secondaryLabel: string = ""`, `secondaryHref: string = ""`, `secondaryIcon: string = ""`, `visualPosition: string = "right"`, `reverse: boolean = false`, `ratio: string = "balanced"`, `align: string = "left"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "default"`, `maxWidth: string = "xl"`, `fullBleed: boolean = true`, `gutter: string = "md"`, `contentGutter: string = "md"`, `flushMobile: boolean = false`, `titleSize: string = "default"`, `titleMaxWidth: string = ""`, `descriptionWeight: string = "default"`, `mobileTitleSize: string = "default"`, `mobileStack: boolean = true`, `backgroundImage: string = ""`, `visualImage: string = ""`, `visualAlt: string = ""`, `visualIcon: string = ""`, `visualEyebrow: string = ""`, `visualTitle: string = ""`, `visualDescription: string = ""`, `visualItems: unknown[] = []`, `visualStyle: string = "panel"`, `trustItems: unknown[] = []`, `class: string = ""`
- Slots: `default`, `actions`, `trust`, `visual` - Slots: `default`, `actions`, `trust`, `visual`
- Outputs: None - Outputs: None
@@ -799,7 +799,7 @@ Provide a responsive floating control that returns long pages to the top and can
Show responsive hierarchical navigation with home support, separators, current-page state, sizes, and selection events. Show responsive hierarchical navigation with home support, separators, current-page state, sizes, and selection events.
- Mount: `data-component="Breadcrumb"` - Mount: `data-component="Breadcrumb"`
- Props: `label: string = "Breadcrumb"`, `items: unknown[] = []`, `active: string = ""`, `separator: string = "chevron"`, `showHome: boolean = false`, `homeLabel: string = "Home"`, `homeHref: string = "/"`, `homeIcon: string = "icon-[lucide--house]"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "minimal"`, `class: string = ""` - Props: `label: string = "Breadcrumb"`, `items: unknown[] = []`, `active: string = ""`, `activeIcon: string = "icon-[lucide--file-text]"`, `separator: string = "chevron"`, `showHome: boolean = false`, `homeLabel: string = "Home"`, `homeHref: string = "/"`, `homeIcon: string = "icon-[lucide--house]"`, `size: string = "default"`, `color: string = "primary"`, `variant: string = "minimal"`, `class: string = ""`
- Slots: None - Slots: None
- Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)` - Outputs: `select({ value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)`
@@ -808,9 +808,9 @@ Show responsive hierarchical navigation with home support, separators, current-p
Theme-aware, responsive mega menu component. Theme-aware, responsive mega menu component.
- Mount: `data-component="MegaMenu"` - Mount: `data-component="MegaMenu"`
- Props: `color: string = "primary"`, `size: string = "default"`, `label: string = "Menu"`, `icon: string = ""`, `columns: unknown[] = []`, `footer: string = ""`, `defaultOpen: boolean = false`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `label: string = "Menu"`, `icon: string = ""`, `columns: unknown[] = []`, `rail: unknown[] = []`, `featured: Record<string, unknown> = {}`, `variant: string = "default"`, `density: string = "default"`, `panelWidth: string = "xl"`, `panelAlign: string = "start"`, `columnCount: number = 0`, `fullWidth: boolean = false`, `openOnHover: boolean = true`, `activeCategory: string = ""`, `actionLabel: string = ""`, `actionHref: string = ""`, `actionIcon: string = "icon-[lucide--arrow-right]"`, `footer: string = ""`, `defaultOpen: boolean = false`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `open({ sourceEvent: Event })`, `close({ reason: string })`, `select({ item: object; value: string })` - Outputs: `open({ sourceEvent: Event })`, `close({ reason: string })`, `select({ item: object; value: string })`, `category({ item: object; value: string; index: number })`
### Nav ### Nav
@@ -826,8 +826,8 @@ Theme-aware, responsive nav component.
Theme-aware, responsive navbar component. Theme-aware, responsive navbar component.
- Mount: `data-component="Navbar"` - Mount: `data-component="Navbar"`
- Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Primary navigation"`, `topbarLabel: string = "Utility navigation"`, `brand: Record<string, unknown> = {}`, `items: unknown[] = []`, `actions: unknown[] = []`, `active: string = ""`, `sticky: boolean = false`, `openOnHover: boolean = false`, `maxWidth: string = "full"`, `mobileLabel: string = "Toggle navigation"`, `class: string = ""` - Props: `size: string = "default"`, `color: string = "primary"`, `label: string = "Primary navigation"`, `topbarLabel: string = "Utility navigation"`, `brand: Record<string, unknown> = {}`, `items: unknown[] = []`, `actions: unknown[] = []`, `active: string = ""`, `sticky: boolean = false`, `floating: boolean = false`, `startFloating: string = "0px"`, `openOnHover: boolean = false`, `maxWidth: string = "full"`, `mobileLabel: string = "Toggle navigation"`, `collapseAt: string = "900px"`, `mobilePanel: string = "content"`, `mobileUtilitiesPosition: string = "header"`, `gutter: string = "md"`, `contentGutter: string = "md"`, `flushMobile: boolean = false`, `translationPrefix: string = ""`, `class: string = ""`
- Slots: `topbar`, `actions` - Slots: `topbar`, `actions`, `utilities`
- Outputs: `toggle({ open: boolean })`, `open({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `close({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `select({ item: string | number | boolean | null | object; value: string | number | boolean; level: string })`, `action({ item: string | number | boolean | null | object; value: string | number | boolean })` - Outputs: `toggle({ open: boolean })`, `open({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `close({ sourceEvent?: Event; [key: string]: string | number | boolean | null | object })`, `select({ item: string | number | boolean | null | object; value: string | number | boolean; level: string })`, `action({ item: string | number | boolean | null | object; value: string | number | boolean })`
### Pagination ### Pagination
@@ -844,7 +844,7 @@ Theme-aware, responsive pagination component.
Theme-aware, responsive scrollspy component. Theme-aware, responsive scrollspy component.
- Mount: `data-component="Scrollspy"` - Mount: `data-component="Scrollspy"`
- Props: `color: string = "primary"`, `size: string = "default"`, `items: unknown[] = []`, `active: string = ""`, `label: string = "On this page"`, `heading: string = ""`, `class: string = ""` - Props: `color: string = "primary"`, `size: string = "default"`, `items: unknown[] = []`, `active: string = ""`, `label: string = "On this page"`, `heading: string = ""`, `sticky: boolean = false`, `topOffset: string = "6rem"`, `sectionOffset: string = "7rem"`, `collapseAt: string = "800px"`, `class: string = ""`
- Slots: `default` - Slots: `default`
- Outputs: `change({ href: string; label: string })` - Outputs: `change({ href: string; label: string })`
+56
View File
@@ -90,6 +90,62 @@ the component directory at runtime.
For the full catalog, see [`COMPONENTS.md`](./COMPONENTS.md). The machine-readable For the full catalog, see [`COMPONENTS.md`](./COMPONENTS.md). The machine-readable
equivalent is exported as `@wrnexus/ui/component-reference.json`. equivalent is exported as `@wrnexus/ui/component-reference.json`.
### Typed Navbar items
Keep navigation data in a shared TypeScript module and validate it with the
package contract. `NavbarItem` is a discriminated union of `link`, `dropdown`,
and `mega`; a mega item accepts every `MegaMenu` layout option.
```ts
import type { NavbarItem } from "@wrnexus/ui";
export const navigation = [
{ type: "link", label: "Pricing", href: "/pricing" },
{
type: "dropdown",
label: "Company",
items: [
{ label: "About", href: "/about" },
{ label: "Contact", href: "/contact" },
],
},
{
type: "mega",
label: "Products",
mega: {
variant: "icon-grid",
panelWidth: "2xl",
columnCount: 3,
rail: [{ label: "All products", href: "/products" }],
columns: [
{
heading: "Work",
items: [
{
label: "Projects",
href: "/products/projects",
icon: "icon-[lucide--folders]",
description: "Plan and deliver work",
badge: "New",
},
],
},
],
featured: {
title: "What's new",
actionLabel: "Explore",
actionHref: "/new",
},
actionLabel: "View all products",
actionHref: "/products",
},
},
] satisfies NavbarItem[];
```
Importing from `@wrnexus/ui/navigation` is also supported. Existing untyped
Navbar data using `children` remains accepted at runtime for migration.
## API ## API
The JS module (`@wrnexus/ui`) exposes five helpers used by the build tooling to The JS module (`@wrnexus/ui`) exposes five helpers used by the build tooling to
+560 -3
View File
@@ -806,6 +806,69 @@
"default": "false", "default": "false",
"options": [] "options": []
}, },
{
"name": "flush",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "mobileCompact",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "mobileMessageLines",
"type": "number",
"required": false,
"default": "2",
"options": []
},
{
"name": "hideBadgeOnMobile",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "hideDescriptionOnMobile",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "mobileActionSize",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "gutter",
"type": "string",
"required": false,
"default": "\"md\"",
"options": []
},
{
"name": "contentGutter",
"type": "string",
"required": false,
"default": "\"md\"",
"options": []
},
{
"name": "flushMobile",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{ {
"name": "size", "name": "size",
"type": "string", "type": "string",
@@ -2180,6 +2243,48 @@
"default": "\"solid\"", "default": "\"solid\"",
"options": [] "options": []
}, },
{
"name": "spacing",
"type": "string",
"required": false,
"default": "\"none\"",
"options": []
},
{
"name": "radius",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "titleMaxWidth",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "contentPadding",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "mobilePadding",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "mobileActions",
"type": "string",
"required": false,
"default": "\"stack\"",
"options": []
},
{ {
"name": "primaryLabel", "name": "primaryLabel",
"type": "string", "type": "string",
@@ -2315,7 +2420,21 @@
"name": "title", "name": "title",
"type": "string", "type": "string",
"required": false, "required": false,
"default": "\"Card title\"", "default": "\"\"",
"options": []
},
{
"name": "icon",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "titleKey",
"type": "string",
"required": false,
"default": "\"\"",
"options": [] "options": []
}, },
{ {
@@ -2325,6 +2444,13 @@
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{
"name": "subtitleKey",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{ {
"name": "description", "name": "description",
"type": "string", "type": "string",
@@ -2332,6 +2458,13 @@
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{
"name": "descriptionKey",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{ {
"name": "header", "name": "header",
"type": "string", "type": "string",
@@ -2374,6 +2507,13 @@
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{
"name": "actionLabelKey",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{ {
"name": "actionHref", "name": "actionHref",
"type": "string", "type": "string",
@@ -2493,6 +2633,27 @@
"default": "\"none\"", "default": "\"none\"",
"options": [] "options": []
}, },
{
"name": "radius",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "shadow",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "equalHeight",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{ {
"name": "scrollable", "name": "scrollable",
"type": "boolean", "type": "boolean",
@@ -5833,6 +5994,34 @@
"default": "\"lift\"", "default": "\"lift\"",
"options": [] "options": []
}, },
{
"name": "radius",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "shadow",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "clickableSurface",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "descriptionTone",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -6198,6 +6387,20 @@
"default": "\"compact\"", "default": "\"compact\"",
"options": [] "options": []
}, },
{
"name": "mobileColumns",
"type": "number",
"required": false,
"default": "2",
"options": []
},
{
"name": "mobileCopyrightAlign",
"type": "string",
"required": false,
"default": "\"center\"",
"options": []
},
{ {
"name": "copyright", "name": "copyright",
"type": "string", "type": "string",
@@ -6205,6 +6408,20 @@
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{
"name": "copyrightKey",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "translationPrefix",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -8128,6 +8345,97 @@
"default": "[]", "default": "[]",
"options": [] "options": []
}, },
{
"name": "rail",
"type": "unknown[]",
"required": false,
"default": "[]",
"options": []
},
{
"name": "featured",
"type": "Record<string, unknown>",
"required": false,
"default": "{}",
"options": []
},
{
"name": "variant",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "density",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "panelWidth",
"type": "string",
"required": false,
"default": "\"xl\"",
"options": []
},
{
"name": "panelAlign",
"type": "string",
"required": false,
"default": "\"start\"",
"options": []
},
{
"name": "columnCount",
"type": "number",
"required": false,
"default": "0",
"options": []
},
{
"name": "fullWidth",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "openOnHover",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "activeCategory",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "actionLabel",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "actionHref",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "actionIcon",
"type": "string",
"required": false,
"default": "\"icon-[lucide--arrow-right]\"",
"options": []
},
{ {
"name": "footer", "name": "footer",
"type": "string", "type": "string",
@@ -8163,9 +8471,13 @@
{ {
"name": "select", "name": "select",
"payloadType": "{ item: object; value: string }" "payloadType": "{ item: object; value: string }"
},
{
"name": "category",
"payloadType": "{ item: object; value: string; index: number }"
} }
], ],
"events": ["open", "close", "select"], "events": ["open", "close", "select", "category"],
"source": "components/MegaMenu.wrn" "source": "components/MegaMenu.wrn"
}, },
{ {
@@ -8878,6 +9190,20 @@
"default": "false", "default": "false",
"options": [] "options": []
}, },
{
"name": "floating",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "startFloating",
"type": "string",
"required": false,
"default": "\"0px\"",
"options": []
},
{ {
"name": "openOnHover", "name": "openOnHover",
"type": "boolean", "type": "boolean",
@@ -8899,6 +9225,55 @@
"default": "\"Toggle navigation\"", "default": "\"Toggle navigation\"",
"options": [] "options": []
}, },
{
"name": "collapseAt",
"type": "string",
"required": false,
"default": "\"900px\"",
"options": []
},
{
"name": "mobilePanel",
"type": "string",
"required": false,
"default": "\"content\"",
"options": []
},
{
"name": "mobileUtilitiesPosition",
"type": "string",
"required": false,
"default": "\"header\"",
"options": []
},
{
"name": "gutter",
"type": "string",
"required": false,
"default": "\"md\"",
"options": []
},
{
"name": "contentGutter",
"type": "string",
"required": false,
"default": "\"md\"",
"options": []
},
{
"name": "flushMobile",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "translationPrefix",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -8907,7 +9282,7 @@
"options": [] "options": []
} }
], ],
"slots": ["topbar", "actions"], "slots": ["topbar", "actions", "utilities"],
"outputs": [ "outputs": [
{ {
"name": "toggle", "name": "toggle",
@@ -9826,6 +10201,55 @@
"default": "true", "default": "true",
"options": [] "options": []
}, },
{
"name": "iconOnly",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{
"name": "themeIcon",
"type": "string",
"required": false,
"default": "\"icon-[lucide--sun-moon]\"",
"options": []
},
{
"name": "colorIcon",
"type": "string",
"required": false,
"default": "\"icon-[lucide--palette]\"",
"options": []
},
{
"name": "languageIcon",
"type": "string",
"required": false,
"default": "\"icon-[lucide--languages]\"",
"options": []
},
{
"name": "panelPlacement",
"type": "string",
"required": false,
"default": "\"bottom-end\"",
"options": []
},
{
"name": "mobilePanel",
"type": "string",
"required": false,
"default": "\"dropdown\"",
"options": []
},
{
"name": "showCurrentLanguage",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -10532,6 +10956,34 @@
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{
"name": "sticky",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "topOffset",
"type": "string",
"required": false,
"default": "\"6rem\"",
"options": []
},
{
"name": "sectionOffset",
"type": "string",
"required": false,
"default": "\"7rem\"",
"options": []
},
{
"name": "collapseAt",
"type": "string",
"required": false,
"default": "\"800px\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -11346,6 +11798,62 @@
"default": "true", "default": "true",
"options": [] "options": []
}, },
{
"name": "gutter",
"type": "string",
"required": false,
"default": "\"md\"",
"options": []
},
{
"name": "contentGutter",
"type": "string",
"required": false,
"default": "\"md\"",
"options": []
},
{
"name": "flushMobile",
"type": "boolean",
"required": false,
"default": "false",
"options": []
},
{
"name": "titleSize",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "titleMaxWidth",
"type": "string",
"required": false,
"default": "\"\"",
"options": []
},
{
"name": "descriptionWeight",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "mobileTitleSize",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "mobileStack",
"type": "boolean",
"required": false,
"default": "true",
"options": []
},
{ {
"name": "backgroundImage", "name": "backgroundImage",
"type": "string", "type": "string",
@@ -11498,6 +12006,41 @@
"default": "\"xl\"", "default": "\"xl\"",
"options": [] "options": []
}, },
{
"name": "radius",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "shadow",
"type": "string",
"required": false,
"default": "\"default\"",
"options": []
},
{
"name": "gutter",
"type": "string",
"required": false,
"default": "\"md\"",
"options": []
},
{
"name": "outerSpacing",
"type": "string",
"required": false,
"default": "\"none\"",
"options": []
},
{
"name": "mobileColumns",
"type": "number",
"required": false,
"default": "1",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
@@ -11844,6 +12387,20 @@
"default": "\"\"", "default": "\"\"",
"options": [] "options": []
}, },
{
"name": "showLabel",
"type": "string",
"required": false,
"default": "\"Show password\"",
"options": []
},
{
"name": "hideLabel",
"type": "string",
"required": false,
"default": "\"Hide password\"",
"options": []
},
{ {
"name": "class", "name": "class",
"type": "string", "type": "string",
+24 -3
View File
@@ -16,6 +16,15 @@ badge: string = ""
dismissLabel: string = "Dismiss announcement" dismissLabel: string = "Dismiss announcement"
sticky: boolean = false sticky: boolean = false
compact: boolean = false compact: boolean = false
flush: boolean = false
mobileCompact: boolean = false
mobileMessageLines: number = 2
hideBadgeOnMobile: boolean = false
hideDescriptionOnMobile: boolean = false
mobileActionSize: string = "default"
gutter: string = "md"
contentGutter: string = "md"
flushMobile: boolean = false
size: string = "default" size: string = "default"
width: string = "default" width: string = "default"
color: string = "primary" color: string = "primary"
@@ -36,6 +45,7 @@ badge: string = ""
data-color='{color}' data-color='{color}'
data-variant='{variant}' data-variant='{variant}'
data-compact='{compact}' data-compact='{compact}'
data-flush='{flush ? "true" : "false"}' data-mobile-compact='{mobileCompact ? "true" : "false"}' data-mobile-message-lines='{mobileMessageLines}' data-hide-badge-mobile='{hideBadgeOnMobile ? "true" : "false"}' data-hide-description-mobile='{hideDescriptionOnMobile ? "true" : "false"}' data-mobile-action-size='{mobileActionSize}' data-gutter='{gutter}' data-content-gutter='{contentGutter}' data-flush-mobile='{flushMobile ? "true" : "false"}'
data-sticky='{sticky}' data-sticky='{sticky}'
data-show='{!dismissed}' data-show='{!dismissed}'
aria-hidden='{dismissed ? "true" : "false"}' aria-hidden='{dismissed ? "true" : "false"}'
@@ -152,9 +162,9 @@ badge: string = ""
position: relative; position: relative;
z-index: 40; z-index: 40;
display: block; display: block;
width: 100vw; width: 100%;
max-width: 100vw; max-width: 100%;
margin-inline: calc(50% - 50vw); margin-inline: 0;
color: var(--wrn-color-text); color: var(--wrn-color-text);
} }
@@ -163,6 +173,8 @@ badge: string = ""
margin-inline: auto; margin-inline: auto;
} }
.wrn-announcement[data-flush="true"] .wrn-announcement__surface { border-radius: 0; box-shadow: none; }
.wrn-announcement[data-width="wide"] .wrn-announcement__surface { .wrn-announcement[data-width="wide"] .wrn-announcement__surface {
width: min(calc(100% - 2rem), 90rem); width: min(calc(100% - 2rem), 90rem);
} }
@@ -573,6 +585,15 @@ badge: string = ""
} }
@media (max-width: 639px) { @media (max-width: 639px) {
.wrn-announcement[data-mobile-compact="true"] .wrn-announcement__inner,
.wrn-announcement[data-compact="true"] .wrn-announcement__inner { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 0.65rem; }
.wrn-announcement[data-hide-badge-mobile="true"] .wrn-announcement__badge,
.wrn-announcement[data-mobile-compact="true"] .wrn-announcement__badge { display: none; }
.wrn-announcement[data-hide-description-mobile="true"] .wrn-announcement__description,
.wrn-announcement[data-mobile-compact="true"] .wrn-announcement__description { display: none; }
.wrn-announcement[data-mobile-message-lines="2"] .wrn-announcement__message { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.wrn-announcement[data-mobile-action-size="sm"] .wrn-announcement__action { min-height: 2.25rem; padding-inline: 0.7rem; }
.wrn-announcement[data-flush-mobile="false"] .wrn-announcement__inner { padding-inline: 1rem; }
.wrn-announcement__surface, .wrn-announcement__surface,
.wrn-announcement[data-width="wide"] .wrn-announcement__surface { .wrn-announcement[data-width="wide"] .wrn-announcement__surface {
width: calc(100% - 1rem); width: calc(100% - 1rem);
+2 -2
View File
@@ -120,11 +120,11 @@ component BackToTop {
} }
.wrn-back-to-top[data-position="right"] { .wrn-back-to-top[data-position="right"] {
right: 1.5rem; right: max(1.5rem, env(safe-area-inset-right));
} }
.wrn-back-to-top[data-position="left"] { .wrn-back-to-top[data-position="left"] {
left: 1.5rem; left: max(1.5rem, env(safe-area-inset-left));
} }
.wrn-back-to-top[data-position="center"] { .wrn-back-to-top[data-position="center"] {
+20
View File
@@ -12,6 +12,7 @@ component Blockquote {
variant: string = "default" variant: string = "default"
quoteMark: boolean = true quoteMark: boolean = true
italic: boolean = true italic: boolean = true
maxWidth: string = "xl"
class: string = "" class: string = ""
} }
@@ -24,6 +25,7 @@ component Blockquote {
data-align="{align}" data-align="{align}"
data-variant="{variant}" data-variant="{variant}"
data-italic="{italic}" data-italic="{italic}"
data-max-width="{maxWidth}"
> >
<blockquote cite="{citationUrl}"> <blockquote cite="{citationUrl}">
{#if quoteMark} {#if quoteMark}
@@ -73,10 +75,17 @@ component Blockquote {
display: grid; display: grid;
gap: 1.25rem; gap: 1.25rem;
width: 100%; width: 100%;
max-width: var(--wrn-blockquote-max-width, 48rem);
color: var(--wrn-color-text); color: var(--wrn-color-text);
text-align: left; text-align: left;
} }
.wrn-next--blockquote[data-max-width="sm"] { --wrn-blockquote-max-width: 32rem; }
.wrn-next--blockquote[data-max-width="md"] { --wrn-blockquote-max-width: 40rem; }
.wrn-next--blockquote[data-max-width="lg"] { --wrn-blockquote-max-width: 48rem; }
.wrn-next--blockquote[data-max-width="xl"] { --wrn-blockquote-max-width: 64rem; }
.wrn-next--blockquote[data-max-width="full"] { --wrn-blockquote-max-width: 100%; }
.wrn-next--blockquote[data-size="xs"] { .wrn-next--blockquote[data-size="xs"] {
--wrn-blockquote-font-size: 0.875rem; --wrn-blockquote-font-size: 0.875rem;
--wrn-blockquote-mark-size: 3rem; --wrn-blockquote-mark-size: 3rem;
@@ -189,8 +198,19 @@ component Blockquote {
.wrn-next--blockquote[data-align="center"] { .wrn-next--blockquote[data-align="center"] {
text-align: center; text-align: center;
margin-inline: auto;
} }
.wrn-next--blockquote:is([data-variant="contained"], [data-variant="spotlight"], [data-variant="card"]) {
padding: clamp(1.5rem, 4vw, 3rem);
border: 1px solid color-mix(in srgb, var(--wrn-blockquote-color) 24%, var(--wrn-color-border));
border-radius: calc(var(--wrn-radius) * 2);
background: linear-gradient(145deg, color-mix(in srgb, var(--wrn-blockquote-color) 8%, var(--wrn-color-surface-raised)), var(--wrn-color-surface-raised));
box-shadow: var(--wrn-shadow-1);
}
.wrn-next--blockquote[data-variant="spotlight"] > blockquote { padding-top: calc(var(--wrn-blockquote-mark-size) * 0.55); }
.wrn-next--blockquote[data-align="right"], .wrn-next--blockquote[data-align="right"],
.wrn-next--blockquote[data-align="end"] { .wrn-next--blockquote[data-align="end"] {
text-align: right; text-align: right;
+32 -1
View File
@@ -7,6 +7,7 @@ component Breadcrumb {
label: string = "Breadcrumb" label: string = "Breadcrumb"
items: unknown[] = [] items: unknown[] = []
active: string = "" active: string = ""
activeIcon: string = "icon-[lucide--file-text]"
separator: string = "chevron" separator: string = "chevron"
showHome: boolean = false showHome: boolean = false
homeLabel: string = "Home" homeLabel: string = "Home"
@@ -61,7 +62,7 @@ label: string = "Breadcrumb"
{:else if separator === "arrow"} {:else if separator === "arrow"}
<span>→</span> <span>→</span>
{:else} {:else}
<span class="icon-[lucide--chevron-right] wrn-breadcrumb__separator-icon"></span> <span class="wrn-breadcrumb__separator-icon"></span>
{/if} {/if}
</span> </span>
{/if} {/if}
@@ -99,6 +100,30 @@ label: string = "Breadcrumb"
{/if} {/if}
</li> </li>
{/each} {/each}
{#if active && !items.some((item) => active === (item.value || item.label || item.title))}
<li class="wrn-breadcrumb__item" data-current="true">
{#if showHome || items.length > 0}
<span class="wrn-breadcrumb__separator" aria-hidden="true">
{#if separator === "slash"}
<span>/</span>
{:else if separator === "dot"}
<span>•</span>
{:else if separator === "arrow"}
<span>→</span>
{:else}
<span class="wrn-breadcrumb__separator-icon"></span>
{/if}
</span>
{/if}
<span class="wrn-breadcrumb__current" aria-current="page">
{#if activeIcon}
<span class='{activeIcon} wrn-breadcrumb__icon' aria-hidden="true"></span>
{/if}
<span class="wrn-breadcrumb__label">{active}</span>
</span>
</li>
{/if}
</ol> </ol>
</nav> </nav>
} }
@@ -207,8 +232,14 @@ label: string = "Breadcrumb"
} }
.wrn-breadcrumb__separator-icon { .wrn-breadcrumb__separator-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 0.9rem; width: 0.9rem;
height: 0.9rem; height: 0.9rem;
font-size: 1rem;
font-weight: 700;
line-height: 1;
} }
.wrn-breadcrumb__link, .wrn-breadcrumb__link,
+42 -3
View File
@@ -8,6 +8,12 @@ component CTASection {
size: string = "default" size: string = "default"
color: string = "primary" color: string = "primary"
variant: string = "solid" variant: string = "solid"
spacing: string = "none"
radius: string = "default"
titleMaxWidth: string = ""
contentPadding: string = "default"
mobilePadding: string = "default"
mobileActions: string = "stack"
primaryLabel: string = "Get started" primaryLabel: string = "Get started"
primaryHref: string = "#" primaryHref: string = "#"
primaryIcon: string = "" primaryIcon: string = ""
@@ -35,6 +41,9 @@ component CTASection {
data-size='{size}' data-size='{size}'
data-color='{color}' data-color='{color}'
data-variant='{variant}' data-variant='{variant}'
data-spacing='{spacing}'
data-radius='{radius}' data-content-padding='{contentPadding}' data-mobile-padding='{mobilePadding}' data-mobile-actions='{mobileActions}'
style='--wrn-cta-title-max-width: {titleMaxWidth || "18ch"}'
data-max-width='{maxWidth}' data-max-width='{maxWidth}'
data-full-bleed='{fullBleed ? "true" : "false"}' data-full-bleed='{fullBleed ? "true" : "false"}'
data-visual-position='{visualPosition}' data-visual-position='{visualPosition}'
@@ -186,6 +195,18 @@ component CTASection {
--cta-contrast: var(--wrn-color-secondary-contrast); --cta-contrast: var(--wrn-color-secondary-contrast);
} }
.wrn-cta-section[data-spacing="sm"] {
padding-block: 2rem;
}
.wrn-cta-section[data-spacing="md"] {
padding-block: 3rem;
}
.wrn-cta-section[data-spacing="lg"] {
padding-block: 4rem;
}
.wrn-cta-section[data-color="info"] { .wrn-cta-section[data-color="info"] {
--cta-accent: var(--wrn-color-info); --cta-accent: var(--wrn-color-info);
--cta-accent-hover: var(--wrn-color-info-hover); --cta-accent-hover: var(--wrn-color-info-hover);
@@ -223,6 +244,20 @@ component CTASection {
margin-inline: auto; margin-inline: auto;
} }
@media (max-width: 640px) {
.wrn-cta-section[data-spacing="sm"] {
padding-block: 1.5rem;
}
.wrn-cta-section[data-spacing="md"] {
padding-block: 2rem;
}
.wrn-cta-section[data-spacing="lg"] {
padding-block: 2.5rem;
}
}
.wrn-cta-section[data-max-width="compact"] .wrn-cta-section__outer { .wrn-cta-section[data-max-width="compact"] .wrn-cta-section__outer {
width: min(calc(100% - 2rem), 64rem); width: min(calc(100% - 2rem), 64rem);
} }
@@ -260,6 +295,8 @@ component CTASection {
0 1px 0 color-mix(in srgb, white 5%, transparent) inset, 0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
0 24px 70px color-mix(in srgb, black 15%, transparent); 0 24px 70px color-mix(in srgb, black 15%, transparent);
} }
.wrn-cta-section[data-radius="xl"] .wrn-cta-section__surface { border-radius: 1.5rem; }
.wrn-cta-section[data-content-padding="lg"] .wrn-cta-section__surface { padding: 3.5rem clamp(1.5rem, 5vw, 4rem); }
.wrn-cta-section[data-size="compact"] .wrn-cta-section__surface, .wrn-cta-section[data-size="compact"] .wrn-cta-section__surface,
.wrn-cta-section[data-size="sm"] .wrn-cta-section__surface { .wrn-cta-section[data-size="sm"] .wrn-cta-section__surface {
@@ -401,7 +438,7 @@ component CTASection {
} }
.wrn-cta-section__title { .wrn-cta-section__title {
max-width: 18ch; max-width: var(--wrn-cta-title-max-width);
margin: 0.75rem 0 0; margin: 0.75rem 0 0;
color: var(--wrn-color-text); color: var(--wrn-color-text);
font-size: clamp(2rem, 5vw, 4rem); font-size: clamp(2rem, 5vw, 4rem);
@@ -637,11 +674,11 @@ component CTASection {
line-height: 1.5; line-height: 1.5;
} }
.wrn-cta-section__footer:empty { .wrn-cta-section__footer:not(:has(> *)) {
display: none; display: none;
} }
.wrn-cta-section__footer:not(:empty) { .wrn-cta-section__footer:has(> *) {
margin-top: 2rem; margin-top: 2rem;
padding-top: 1.25rem; padding-top: 1.25rem;
border-top: 1px solid color-mix(in srgb, currentColor 14%, transparent); border-top: 1px solid color-mix(in srgb, currentColor 14%, transparent);
@@ -671,6 +708,8 @@ component CTASection {
} }
@media (max-width: 639px) { @media (max-width: 639px) {
.wrn-cta-section[data-mobile-padding="md"] .wrn-cta-section__surface { padding: 2.5rem 1.25rem; }
.wrn-cta-section[data-mobile-actions="inline"] .wrn-cta-section__action { width: auto; }
.wrn-cta-section__surface { .wrn-cta-section__surface {
padding: 1.6rem; padding: 1.6rem;
border-radius: 1.2rem; border-radius: 1.2rem;

Some files were not shown because too many files have changed in this diff Show More