Compare commits
118
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
361129b6ac | ||
|
|
8069541cd9 | ||
|
|
4a82640c5b | ||
|
|
52660cbb8e | ||
|
|
ca2f9451ab | ||
|
|
b3116de354 | ||
|
|
f6993e6cdb | ||
|
|
f1d1081b67 | ||
|
|
b4d3cb3695 | ||
|
|
124da548b8 | ||
|
|
f4960f2fc5 | ||
|
|
45ef035bae | ||
|
|
b67a5e43eb | ||
|
|
7ac6e08544 | ||
|
|
f94004648d | ||
|
|
e8e1a2623b | ||
|
|
ecb93c7116 | ||
|
|
f01a308287 | ||
|
|
1fb1a8d2d0 | ||
|
|
949cf78636 | ||
|
|
296728d51d | ||
|
|
98205daef6 | ||
|
|
3eec9fd8c6 | ||
|
|
6aaf21aa06 | ||
|
|
ce68803471 | ||
|
|
7c4b484d0a | ||
|
|
fcf4ed3039 | ||
|
|
e01915823a | ||
|
|
9bc0f48514 | ||
|
|
9f599e02e8 | ||
|
|
83c99cc3e5 | ||
|
|
1393a8a3b8 | ||
|
|
2257ee871e | ||
|
|
40625e98ed | ||
|
|
e0bd84247e | ||
|
|
34d5bbc810 | ||
|
|
e16903b286 | ||
|
|
21ea8a84a0 | ||
|
|
dd8354477d | ||
|
|
796b19d923 | ||
|
|
3e1d7db537 | ||
|
|
a4c7d7b298 | ||
|
|
e1fca3eddf | ||
|
|
63e6148cdb | ||
|
|
87d33a2ce1 | ||
|
|
2ab7b1e762 | ||
|
|
2e93124080 | ||
|
|
2c339bee15 | ||
|
|
41b6e2ed2b | ||
|
|
3867e7c183 | ||
|
|
a7255fa1bd | ||
|
|
fd5e2b7128 | ||
|
|
57097c8204 | ||
|
|
226217ecbf | ||
|
|
daea59cf5d | ||
|
|
bc5437063d | ||
|
|
b9098382b3 | ||
|
|
e5d0654d2a | ||
|
|
3ef353de83 | ||
|
|
726b8a7d24 | ||
|
|
91e5c6e0c5 | ||
|
|
2fbf059c00 | ||
|
|
1cc0b97a72 | ||
|
|
134c5fa4bc | ||
|
|
205f4e2d4c | ||
|
|
f19462dff0 | ||
|
|
3fa3fce5df | ||
|
|
218f5e2dd6 | ||
|
|
e136fbc56a | ||
|
|
41fb82b9e9 | ||
|
|
e7743cdbb5 | ||
|
|
703baa1ead | ||
|
|
e05ddc7aa5 | ||
|
|
6f3a53b9ff | ||
|
|
13859ce7dc | ||
|
|
e15422ed8d | ||
|
|
3f1fcd0d2d | ||
|
|
798f56734a | ||
|
|
77b9e49bf2 | ||
|
|
9e3624e584 | ||
|
|
b7f3507b59 | ||
|
|
cc8085bcfa | ||
|
|
984c6236d3 | ||
|
|
cd82bec414 | ||
|
|
d6a2d05407 | ||
|
|
ae37c9b57a | ||
|
|
86b3dc1e6a | ||
|
|
c499f136fd | ||
|
|
6d8b6daba9 | ||
|
|
d7509421c7 | ||
|
|
d609a41222 | ||
|
|
e710756baf | ||
|
|
ba83038d8d | ||
|
|
f033197850 | ||
|
|
dc0771308a | ||
|
|
83f2951035 | ||
|
|
4362d49770 | ||
|
|
a01b7bc99e | ||
|
|
9b6b970cae | ||
|
|
1849213ce4 | ||
|
|
d694dda320 | ||
|
|
212fdaa5b5 | ||
|
|
0ac648bc26 | ||
|
|
10da210b0a | ||
|
|
b209936f86 | ||
|
|
c64434a131 | ||
|
|
72e4d3eceb | ||
|
|
4cebacadfe | ||
|
|
e8f630f12d | ||
|
|
4550a11460 | ||
|
|
3c6b659f36 | ||
|
|
1a1d2e9d08 | ||
|
|
fed1d5d3f4 | ||
|
|
b61020babd | ||
|
|
379f80cbd0 | ||
|
|
649e3d9127 | ||
|
|
b3e9b99e13 | ||
|
|
586a6db8ff |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "component-showcase",
|
||||
"runtimeExecutable": "bun",
|
||||
"runtimeArgs": ["run", "--cwd", "examples/component-showcase", "dev"],
|
||||
"port": 3000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Enforce LF in the working tree regardless of a contributor's core.autocrlf.
|
||||
# Without this, Git on Windows smudges every text file to CRLF on clone, stash
|
||||
# pop, or checkout, which fails `bun run format:check` (prettier endOfLine: lf).
|
||||
* text=auto eol=lf
|
||||
|
||||
# Binary assets Git must not touch.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.webp binary
|
||||
*.ico binary
|
||||
*.pdf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.db binary
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Quality
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.14
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
cache-dependency-path: editors/vscode/package-lock.json
|
||||
- name: Install framework dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
- name: Install editor dependencies
|
||||
run: npm ci --prefix editors/vscode
|
||||
- name: Typecheck
|
||||
run: bun run typecheck
|
||||
- name: Lint
|
||||
run: bun run lint
|
||||
- name: Formatting
|
||||
run: bun run format:check
|
||||
- name: Package and application tests
|
||||
run: bun run test:all
|
||||
- name: Package contracts
|
||||
run: bun run check:public-api && bun run check:ui-visual && bun run audit:packages && bun run test:package-kits && bun run validate:staging
|
||||
- name: Stage and test publishable packages
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: bun run stage:packages && bun run test:staged-consumers
|
||||
- name: Framework validation
|
||||
run: bun run validate:0.8
|
||||
- name: Dependency audit
|
||||
run: bun audit
|
||||
- name: Editor dependency audit
|
||||
run: npm audit --prefix editors/vscode --audit-level=high
|
||||
@@ -19,3 +19,8 @@ bun.lockb
|
||||
# Local focused typecheck helpers must never enter the repository.
|
||||
focus-shims.d.ts
|
||||
tsconfig.focus.json
|
||||
|
||||
# Scratch dirs for tests that must dynamically import scaffolded files using
|
||||
# "@wrnexus/*" bare specifiers (resolved via the root tsconfig.json `paths`,
|
||||
# which requires the scaffold to live inside the repo tree).
|
||||
**/test/.tmp-*/
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@wrnexus:registry=https://registry.npmjs.org/
|
||||
audit=true
|
||||
fund=false
|
||||
@@ -10,9 +10,14 @@ bun.lockb
|
||||
|
||||
# Generated code (queries.gen.ts, routes.gen.ts, etc.)
|
||||
**/*.gen.ts
|
||||
**/*.generated.d.ts
|
||||
|
||||
# Bundled .wire compiler for the VS Code extension (generated)
|
||||
editors/vscode/src/compiler.cjs
|
||||
editors/vscode/src/language-server.cjs
|
||||
editors/vscode/src/extension.bundle.cjs
|
||||
docs/public-api-0.8.json
|
||||
docs/ui-visual-contract-0.8.json
|
||||
*.svg
|
||||
**/.vscodeignore
|
||||
|
||||
@@ -21,3 +26,6 @@ focus-shims.d.ts
|
||||
**/focus-shims.d.ts
|
||||
tsconfig.focus.json
|
||||
**/tsconfig.focus.json
|
||||
|
||||
# SDD scratch workspace (git-ignored controller artifacts)
|
||||
.superpowers/
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @wrnexus/ai
|
||||
|
||||
Provider-neutral AI orchestration for OpenAI, Anthropic, Google and local OpenAI-compatible models,
|
||||
with streaming, structured output, tools, embeddings, vector search/RAG, conversation persistence,
|
||||
templates, guardrails, usage events, fallback, rate limits and evaluation reports.
|
||||
|
||||
> A tiny, zero-dependency Claude (Anthropic) client for WrNexus apps — generate and stream text with Claude from any server-side code.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
@@ -120,6 +124,34 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-provider client
|
||||
|
||||
`createAIClient` adds named-provider selection and fallback, capability discovery,
|
||||
validated JSON output, validated tool execution, abort-aware exponential retries,
|
||||
and per-provider circuit breakers. Attempt events intentionally contain metadata
|
||||
only: prompts, credentials, and raw model responses are never passed to telemetry.
|
||||
|
||||
```ts
|
||||
import { anthropicProvider, createAIClient } from "@wrnexus/ai";
|
||||
|
||||
const ai = createAIClient({
|
||||
providers: [anthropicProvider()],
|
||||
retry: { attempts: 3, baseDelayMs: 100, maxDelayMs: 2_000 },
|
||||
circuitBreaker: { failureThreshold: 5, resetAfterMs: 30_000 },
|
||||
});
|
||||
|
||||
const result = await ai.generateObject<{ title: string }>("Return a JSON title", {
|
||||
validate: (value): value is { title: string } =>
|
||||
typeof value === "object" && value !== null && "title" in value,
|
||||
});
|
||||
```
|
||||
|
||||
Providers can return normalized `usage` (`inputTokens`, `outputTokens`,
|
||||
`totalTokens`, and `costUsd`) and `toolCalls`. Use `executeTools` with a named,
|
||||
validated tool registry; unknown tools and invalid arguments are rejected before
|
||||
application code runs. `deterministicAIProvider` supplies ordered or computed
|
||||
offline responses for tests and examples without API keys or network calls.
|
||||
|
||||
## Usage
|
||||
|
||||
### Return generated JSON from an API route
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/ai",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/ai"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/ai",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"ai"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -18,9 +34,14 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./platform": {
|
||||
"types": "./dist/platform.d.ts",
|
||||
"import": "./dist/platform.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -149,3 +149,156 @@ app.put(
|
||||
- **Bun-only** — like the rest of WrNexus, this package targets the Bun runtime; Node is not supported.
|
||||
- Works with [`@wrnexus/core`](../core) — the guards return `Middleware` and read the subject from `ctx.user` on the request `Context`. Both types are imported from `@wrnexus/core`.
|
||||
- Policy combinators (`any`, `all`) and `authorize` are async-aware, so policies may return a `Promise<boolean>` (e.g. for a database ownership check).
|
||||
|
||||
## Declaring permissions
|
||||
|
||||
The RBAC/PBAC/ABAC surface above is the low-level toolkit. On top of it sits a
|
||||
declarative **registry + catalog + store + engine**: permissions, roles, and
|
||||
policies are declared once in code, merged into a frozen catalog at boot, and
|
||||
resolved per-request against a pluggable `PermissionStore` that holds who has
|
||||
what.
|
||||
|
||||
Put declarations in `app/authz/<name>.ts`; they are discovered automatically
|
||||
and merged (conflicting declarations of the same permission/role/policy across
|
||||
files fail the boot loudly, naming both source files).
|
||||
|
||||
```ts
|
||||
import { defineAuthz, owner } from "@wrnexus/authz";
|
||||
|
||||
export default defineAuthz({
|
||||
permissions: {
|
||||
"post:read": { title: "View posts", public: true },
|
||||
"post:delete": { title: "Delete posts", risk: "high" },
|
||||
},
|
||||
// "post:*" is a namespace wildcard grant, valid inside a role's list — it is
|
||||
// not itself a registered permission, so it can only ever grant permissions
|
||||
// that ARE declared above (e.g. "post:read", "post:delete").
|
||||
roles: { editor: ["post:*"], admin: ["role:editor"] },
|
||||
policies: { ownsPost: owner("id", "authorId") },
|
||||
bindings: { "post:delete": ["ownsPost"] },
|
||||
});
|
||||
```
|
||||
|
||||
`public: true` means anonymous callers may hold the permission — but any
|
||||
policy bound to it still runs, and can still veto the anonymous caller (e.g. a
|
||||
`notBanned` policy on a public `post:preview` permission).
|
||||
|
||||
## Checking permissions
|
||||
|
||||
Register `authzMiddleware` once, in `app/middleware/`, with the merged
|
||||
catalog and a `PermissionStore`. Like every other `app/middleware/*.ts` file,
|
||||
the registration is an eager, module-scope call — the same shape as
|
||||
`authzMiddleware({ catalog, store })` requires — so it must run after the
|
||||
catalog has been populated. Both the dev server and `wrnexus build`'s
|
||||
generated production entry guarantee `getAuthzCatalog()` is populated before
|
||||
any app middleware module evaluates. Name the file so it sorts after whatever
|
||||
middleware sets `ctx.user` (middleware runs in alphabetical filename order —
|
||||
`authz.ts` after `auth.ts`, for instance).
|
||||
|
||||
```ts
|
||||
// app/middleware/authz.ts
|
||||
import { authzMiddleware, getAuthzCatalog } from "@wrnexus/authz";
|
||||
import { dbPermissionStore } from "@wrnexus/authz/db";
|
||||
import { getDb } from "@wrnexus/db";
|
||||
|
||||
export default authzMiddleware({ catalog: getAuthzCatalog(), store: dbPermissionStore(getDb()) });
|
||||
```
|
||||
|
||||
> **`subject.id` must be a non-empty string.** The engine denies (and logs to
|
||||
> stderr) whenever `ctx.user.id` is present but not a non-empty string — this
|
||||
> includes the common case of an integer primary key. Coerce it before it
|
||||
> reaches `ctx.user`, e.g. `user.id = String(row.id)`, or every request for
|
||||
> that user denies with "Invalid subject" instead of resolving normally.
|
||||
> `owner()` (the built-in ownership policy) compares subject and resource ids
|
||||
> with `Object.is`, so both sides must be the same type too — `owner()` on a
|
||||
> numeric `resource.authorId` against a stringified `subject.id` never
|
||||
> matches even when they represent "the same" id.
|
||||
|
||||
There is no per-route `middleware` export — `app/middleware/*.ts` is the only
|
||||
place middleware is registered. To gate part of the app, branch on the
|
||||
request the same way any other conditional middleware does (compare
|
||||
`app/middleware/captcha-login.ts` in the auth showcase, which branches on
|
||||
method + path the same way):
|
||||
|
||||
```ts
|
||||
// app/middleware/protect-posts.ts
|
||||
import type { Context, Next } from "@wrnexus/core";
|
||||
import { guardPermission } from "@wrnexus/authz";
|
||||
|
||||
const guardPostWrite = guardPermission("post:write");
|
||||
|
||||
export default function protectPosts(ctx: Context, next: Next) {
|
||||
return ctx.url.pathname.startsWith("/api/posts") && ctx.req.method !== "GET"
|
||||
? guardPostWrite(ctx, next)
|
||||
: next();
|
||||
}
|
||||
```
|
||||
|
||||
Or check inline inside a route handler with the free function `can()`:
|
||||
|
||||
```ts
|
||||
// app/api/posts/[id].ts
|
||||
import type { Context } from "@wrnexus/core";
|
||||
import { can } from "@wrnexus/authz";
|
||||
|
||||
export const DELETE = async (ctx: Context) => {
|
||||
const post = { id: "1", authorId: "alice" }; // load your own resource here
|
||||
if (!(await can(ctx, "post:delete", post))) {
|
||||
return Response.json({ ok: false, error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
return Response.json({ ok: true });
|
||||
};
|
||||
```
|
||||
|
||||
`can()` is a free function taking `ctx`, not `ctx.can` — `@wrnexus/core` must
|
||||
not depend on `@wrnexus/authz`, so the per-request resolver lives in
|
||||
`ctx.locals` instead, reached through `can()` / `decideFor()` /
|
||||
`guardPermission()` / `filterCan()`. Calling any of them before
|
||||
`authzMiddleware` has run for that request throws a `WRN-AUTHZ-SETUP` error
|
||||
naming the missing registration, rather than silently denying.
|
||||
|
||||
See `examples/auth-showcase/app/authz/showcase.ts` and
|
||||
`examples/auth-showcase/app/middleware/authz.ts` for a complete, runnable
|
||||
version of this wiring.
|
||||
|
||||
## Precedence
|
||||
|
||||
1. An explicit deny wins over everything, including `*` — and honours the
|
||||
same namespace-wildcard matching as grants (denying `post:*` blocks
|
||||
`post:comment:delete`, not just `post:*` itself).
|
||||
2. A bound policy can veto a permission a role grants, and runs even for a
|
||||
`public: true` permission — including for an anonymous caller.
|
||||
3. Otherwise the permission must be held via a role or an explicit grant.
|
||||
4. Default deny.
|
||||
|
||||
Every failure — an unknown permission (outside strict/dev mode), a store
|
||||
outage, a thrown policy — denies rather than throwing through to the caller.
|
||||
|
||||
`permissionsFor()` (on the resolver returned by `createAuthzResolver`) is a
|
||||
coarse hint for hiding UI (e.g. a menu section), **never authoritative**. A
|
||||
`Set<string>` cannot represent "granted `post:*` except `post:delete`", so a
|
||||
narrow deny beneath a broad grant is invisible to it — the set still contains
|
||||
`post:*` while `can()` / `decide()` correctly refuse `post:delete`. Gate real
|
||||
actions with `can()`, `decideFor()`, or `filterCan()`; never by matching
|
||||
against `permissionsFor()`'s result.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
wrnexus authz list # every registered permission, role, and policy
|
||||
wrnexus authz generate # app/authz/permissions.gen.ts type unions
|
||||
wrnexus authz init # scaffold the assignment-table migration
|
||||
```
|
||||
|
||||
`wrnexus authz generate`'s output is a plain `Permission | Role` string-literal
|
||||
union — `can()`, `guardPermission()`, and `decideFor()` all take a bare
|
||||
`string` and nothing reads this file automatically, so import it to type your
|
||||
own helpers/constants against the registered catalog, e.g.:
|
||||
|
||||
```ts
|
||||
import type { Permission } from "app/authz/permissions.gen.ts";
|
||||
|
||||
function guard(permission: Permission) {
|
||||
return guardPermission(permission);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/authz",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/authz"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/authz",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"authz"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -18,9 +34,18 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./db": {
|
||||
"types": "./dist/db.d.ts",
|
||||
"import": "./dist/db.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/db": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
+54
-2
@@ -1,5 +1,18 @@
|
||||
# @wrnexus/cli
|
||||
|
||||
Production parity commands:
|
||||
|
||||
```bash
|
||||
wrnexus build .
|
||||
wrnexus preview . --port=3000
|
||||
wrnexus dev . --production-runtime
|
||||
```
|
||||
|
||||
`preview` refuses to start without `dist/server.js` and executes that exact
|
||||
artifact with the production profile. Production-runtime development rebuilds
|
||||
the same minified artifact after app, public, or configuration changes and
|
||||
keeps the last good server running when a rebuild fails.
|
||||
|
||||
> The `wrnexus` command-line tool that scaffolds, runs, builds, tests, and manages WrNexus apps.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
@@ -26,6 +39,30 @@ bunx wrnexus dev
|
||||
|
||||
## Commands
|
||||
|
||||
### Local production services
|
||||
|
||||
`wrnexus dev . --services` starts the application and the bounded local database,
|
||||
cache, mail, SMS, webhook, storage, queue, cron, authentication and metrics simulator.
|
||||
It generates a localhost/`*.localhost` development certificate under
|
||||
`.wrnexus/certificates/` and serves both the application and service console over HTTPS.
|
||||
Trust that certificate locally to remove the browser warning. Use `--services-http` only
|
||||
when an external development proxy already terminates TLS.
|
||||
|
||||
### Exact production runtime with live updates
|
||||
|
||||
`wrnexus dev . --production-runtime` rebuilds and executes `dist/server.js` with
|
||||
production resolution, serialization, caching, headers and assets. The supervisor keeps
|
||||
the last good process when a build fails. On a successful rebuild the opt-in production
|
||||
HMR socket reconnects, requests the new document and morphs it into the browser; ordinary
|
||||
`wrnexus preview` and deployed production servers never include that client.
|
||||
|
||||
### API platform
|
||||
|
||||
`wrnexus api generate [app-dir]` (or `api docs`) derives operations from file routes and
|
||||
emits `generated/api/openapi.json`, safe static documentation, Postman collection, curl examples,
|
||||
and TypeScript, JavaScript, Java, Go and Python SDKs. Generate one client with
|
||||
`wrnexus sdk generate <language> [app-dir]`.
|
||||
|
||||
Every command accepts an optional `[app-dir]` (defaults to `.`). Commands that read config or `.env` also accept `--profile=<name>` (see [Profiles](#profiles)).
|
||||
|
||||
| Command | Purpose |
|
||||
@@ -46,10 +83,17 @@ Every command accepts an optional `[app-dir]` (defaults to `.`). Commands that r
|
||||
| `wrnexus db <cmd>` | Database migrations and tooling (see [db](#wrnexus-db)). |
|
||||
| `wrnexus test [app-dir] [--watch]` | Run the app's tests via `bun test` (defaults to the `test` profile). |
|
||||
| `wrnexus profiles [app-dir]` | List config profiles and their `.env` files, marking the active one. |
|
||||
| `wrnexus compatibility check` | Check whether behavior defaults are explicitly pinned and current. |
|
||||
| `wrnexus compatibility explain` | Explain configured, effective, and current compatibility behavior. |
|
||||
| `wrnexus compatibility upgrade` | Back up config and explicitly opt into reviewed current behavior. |
|
||||
| `wrnexus help` | Print usage. |
|
||||
|
||||
`wrnexus g` is an alias for `wrnexus generate`.
|
||||
|
||||
Compatibility upgrades never happen implicitly. New applications pin
|
||||
`compatibilityDate` and `frameworkBehaviour`; existing applications use
|
||||
`wrnexus compatibility explain` before the backed-up, idempotent upgrade command.
|
||||
|
||||
### `wrnexus dev`
|
||||
|
||||
Supervises a child dev-server process (from `@wrnexus/dev-server`). The child owns file watching and HMR: CSS and client-island edits update the live page over a WebSocket with no restart; when a server module changes, the child exits with a restart code and the supervisor respawns it (the browser reconnects and morphs in the new HTML). On startup it regenerates typed DB queries and typed routes (best effort). Use `--port=` to change the port (default `3000`).
|
||||
@@ -76,7 +120,9 @@ bun dist/server.js # PORT env var optional
|
||||
|
||||
### `wrnexus create`
|
||||
|
||||
Scaffolds a new app from an inline (dependency-free) template — `package.json`, `.gitignore`, config, and starter `app/` files. Use `npm run dev` during development, `npm run build && npm start` for production, or `npm run production` to build and start in one command. The generated production server currently requires Bun even when npm is used to manage packages and scripts.
|
||||
Scaffolds a complete v0.8 app from an inline template. The generated project includes strict TypeScript, ESLint and Prettier, editor recommendations, environment templates, database migrations, locales, schemas, tests, API/middleware/realtime examples, Tailwind and Iconify, PWA/mobile defaults, and the framework package kits. Its `wrnexus.config.ts` documents the current imports, types, stores, performance, observability, tenancy, build, navigation, theme, i18n, database, storage, realtime, security, and profile configuration.
|
||||
|
||||
Use `bun run dev` during development, `bun run check` for the complete typecheck/lint/test/format gate, `bun run build && bun run start` for production, or `bun run production` to build and start in one command.
|
||||
|
||||
### `wrnexus update`
|
||||
|
||||
@@ -162,7 +208,7 @@ wrnexus db status --db=analytics
|
||||
|
||||
### `wrnexus workspace` and `wrnexus gateway`
|
||||
|
||||
`workspace <name>` scaffolds a monorepo: several WrNexus apps under `apps/*` and shared libraries under `packages/*`, plus a `wrnexus.workspace.ts` that maps each app to the domains it serves. `gateway` runs every app behind one port and routes by `Host` header, with optional per-app auth and gateway-wide security (trusted hosts, rate limit, security headers, access log).
|
||||
`workspace <name>` scaffolds a monorepo: complete v0.8 apps under `apps/*`, shared libraries under `packages/*`, root TypeScript/lint/format/editor/environment tooling, and a `wrnexus.workspace.ts` that maps each app to the domains it serves. `gateway` runs every app behind one port and routes by `Host` header, with optional per-app auth and gateway-wide security (trusted hosts, rate limit, security headers, access log). Newly added workspace apps use the same current scaffold.
|
||||
|
||||
```bash
|
||||
wrnexus workspace acme
|
||||
@@ -241,6 +287,12 @@ wrnexus update --latest
|
||||
wrnexus doctor
|
||||
```
|
||||
|
||||
Use `wrnexus doctor --fix [app-dir]` to apply conservative repairs before the
|
||||
health check: create missing `app/pages` and a default config, align skewed
|
||||
`@wrnexus/*` dependency ranges, record the current migration marker, and format
|
||||
only syntax-valid `.wrn` files. Invalid JSON or WRN sources are reported/skipped
|
||||
instead of overwritten; repeat runs are idempotent.
|
||||
|
||||
## Profiles
|
||||
|
||||
Pass `--profile=<name>` to `dev`, `build`, `db` (or set `WRNEXUS_PROFILE`) to select a config profile. The CLI publishes `WRNEXUS_PROFILE` so config loaders and the dev child pick it up, and loads that profile's `.env` cascade (`.env`, `.env.local`, `.env.<profile>`, `.env.<profile>.local`) into `process.env`.
|
||||
|
||||
+37
-14
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/cli"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/cli",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"cli"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -28,20 +44,27 @@
|
||||
"wrnexus": "./dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.7.0",
|
||||
"@wrnexus/router": "^0.7.0",
|
||||
"@wrnexus/csr": "^0.7.0",
|
||||
"@wrnexus/compiler": "^0.7.0",
|
||||
"@wrnexus/styles": "^0.7.0",
|
||||
"@wrnexus/dev-server": "^0.7.0",
|
||||
"@wrnexus/ui": "^0.7.0",
|
||||
"@wrnexus/validation": "^0.7.0",
|
||||
"@wrnexus/i18n": "^0.7.0",
|
||||
"@wrnexus/db": "^0.7.0",
|
||||
"@wrnexus/plugin": "^0.7.0",
|
||||
"@wrnexus/syntax": "^0.7.0"
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/router": "^0.8.5",
|
||||
"@wrnexus/csr": "^0.8.5",
|
||||
"@wrnexus/compiler": "^0.8.5",
|
||||
"@wrnexus/styles": "^0.8.5",
|
||||
"@wrnexus/dev-server": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5",
|
||||
"@wrnexus/validation": "^0.8.5",
|
||||
"@wrnexus/i18n": "^0.8.5",
|
||||
"@wrnexus/mcp": "^0.8.5",
|
||||
"@wrnexus/playground": "^0.8.5",
|
||||
"@wrnexus/db": "^0.8.5",
|
||||
"@wrnexus/authz": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/syntax": "^0.8.5",
|
||||
"@wrnexus/typecheck": "^0.8.5",
|
||||
"@wrnexus/security": "^0.8.5",
|
||||
"selfsigned": "^5.5.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,44 @@
|
||||
# @wrnexus/compiler
|
||||
|
||||
## Partial-static rendering
|
||||
|
||||
Pages can select `render = "partial-static"` and divide their view with `<Static>` and
|
||||
`<Dynamic>` boundaries. The compiler emits a build-only shell renderer that never evaluates
|
||||
dynamic-boundary children. `wrnexus build` expands static component mounts into
|
||||
`dist/partial-shells.json`, records byte/region evidence in `build-report.json`, and embeds
|
||||
the shell in the production route manifest. At request time the production runtime retains
|
||||
request-aware layouts, locale/theme metadata and security nonces while streaming dynamic
|
||||
regions into stable placeholders.
|
||||
|
||||
> Compiler for the `.wrn` language — tokenizes, parses, and lowers `.wrn` page and component files to TypeScript.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
|
||||
Production adapters use `analyzeRuntimeImports` before bundling. Edge, worker,
|
||||
service-worker, and browser targets reject Node filesystem, TCP, and process
|
||||
modules with `WRN-RUNTIME-CAPABILITY`. Package manifests can declare supported
|
||||
`wrnexus.runtimes` and required `wrnexus.requires` capabilities; discovery fails
|
||||
when the selected deployment cannot satisfy them.
|
||||
|
||||
## Server actions
|
||||
|
||||
```wrn
|
||||
action createUser using CreateUserSchema {
|
||||
const user = await users.create(input)
|
||||
invalidate("users")
|
||||
return user
|
||||
}
|
||||
|
||||
view {
|
||||
<form @submit="createUser">...</form>
|
||||
}
|
||||
```
|
||||
|
||||
The compiler produces a schema-aware server registry, a fully inferred action
|
||||
client, and progressively enhanced form metadata. The shared runtime performs
|
||||
validation, authentication/permission checks, CSRF verification, serialization,
|
||||
invalidation reporting, and browser lifecycle events.
|
||||
|
||||
## Overview
|
||||
|
||||
`@wrnexus/compiler` turns `.wrn` source into TypeScript that targets the framework's runtime primitives. A `.wrn` file declares either a `page` (a route) or a `component` (a reusable, prop-driven fragment) with blocks for `state`, `view` (plain HTML), `seo`, `style`, `functions`, `api`, `ssr`/`client` data bindings, and `realtime` websocket handlers. The pipeline is `source → Lexer → parse() → PageAst → generate() → TypeScript`. It is a build/server-side library — the WrNexus dev loader calls it to compile `.wrn` files on the fly, surfacing `ParseError` as a readable error page.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/compiler",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/compiler"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/compiler",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"compiler"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,10 +37,13 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/syntax": "^0.7.0",
|
||||
"@wrnexus/store": "^0.7.0"
|
||||
"@wrnexus/csr": "^0.8.5",
|
||||
"@wrnexus/syntax": "^0.8.5",
|
||||
"@wrnexus/store": "^0.8.5",
|
||||
"@wrnexus/validation": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -110,6 +110,37 @@ instances. The default store is process-local memory.
|
||||
(default `console.log`), `requestIdKey` (default `"requestId"`), `now`.
|
||||
`RequestRecord` = `{ time, id, method, path, status, durationMs }`.
|
||||
|
||||
### Resilience — `@wrnexus/core`
|
||||
|
||||
`resilientCall` standardizes cancellation-aware timeouts, controlled retries,
|
||||
fixed or exponential backoff, fallback responses, circuit breaking, and bounded
|
||||
concurrency. Reuse a declarative circuit/bulkhead options object, or an explicit
|
||||
`CircuitBreaker`/`Bulkhead` instance, wherever calls must share health and
|
||||
capacity state.
|
||||
|
||||
```ts
|
||||
import { resilientCall } from "@wrnexus/core";
|
||||
|
||||
const paymentCircuit = { failures: 5, resetAfter: "30s" } as const;
|
||||
|
||||
const status = await resilientCall({
|
||||
timeout: "5s",
|
||||
retries: 3,
|
||||
retryDelay: "100ms",
|
||||
backoff: "exponential",
|
||||
circuitBreaker: paymentCircuit,
|
||||
bulkhead: { concurrency: 20, queue: 100 },
|
||||
run: (signal) => paymentProvider.checkStatus({ signal }),
|
||||
fallback: () => ({ state: "unavailable" }),
|
||||
});
|
||||
```
|
||||
|
||||
`CircuitBreaker.snapshot()` reports `closed`, `open`, or `half-open`, failure
|
||||
and success counts, and the remaining retry delay for health endpoints and
|
||||
development tooling. Fail-fast conditions use stable `WRN-RESILIENCE-*` codes.
|
||||
Core's existing `HealthRegistry`, `withIdempotency`, and pluggable stores/locks
|
||||
cover health reporting, idempotent requests, and distributed coordination.
|
||||
|
||||
### Caching — `@wrnexus/core`
|
||||
|
||||
| Export | Kind | Notes |
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/core",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/core"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/core",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"core"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -29,6 +45,7 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
+44
-10
@@ -1,5 +1,34 @@
|
||||
# @wrnexus/csr
|
||||
|
||||
## Navigation state preservation
|
||||
|
||||
Pages can opt into restoration across client navigation:
|
||||
|
||||
```wrn
|
||||
page Users {
|
||||
navigation {
|
||||
preserve = ["filters", "pagination", "scroll", "tabs", "expanded"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Form-like categories restore named inputs, selects, and textareas. Password,
|
||||
file, hidden, CSRF/token/secret/credential fields, and elements marked
|
||||
`data-no-preserve` are never saved. For tab, expanded, or component UI state,
|
||||
mark stable elements with `data-wrn-preserve="key"`; their value and ARIA
|
||||
selected/expanded state are restored. State is scoped to pathname plus query.
|
||||
|
||||
## Typed server actions
|
||||
|
||||
`createActionClient<Input, Output>(route, name)` supports programmatic calls.
|
||||
Schema-backed WRN actions also export `__wrnexusActionClients`, whose input and
|
||||
output are inferred automatically. Enhanced forms expose
|
||||
`data-wrn-action-state="pending|success|error"` and dispatch bubbling
|
||||
`wrnexus:action:optimistic`, `:pending`, `:success`, and `:error` events.
|
||||
Success details contain returned data and invalidated cache tags; error details
|
||||
contain field errors. Without JavaScript, the same form posts to its page and
|
||||
receives a 303 redirect or accessible validation response.
|
||||
|
||||
> The browser-side client runtime for WrNexus — generic, self-contained JS that hydrates server-rendered pages with reactivity, client-side navigation, and realtime rooms.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
@@ -49,16 +78,21 @@ getRealtimeRuntime(): string // → REALTIME_RUNTIME
|
||||
|
||||
Applied to any subtree containing `data-scope`. Expressions are parsed by a tiny eval-free evaluator, so a strict CSP with no `unsafe-eval` works.
|
||||
|
||||
| Directive | Purpose |
|
||||
| -------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `data-scope="count: 0, name: 'x'"` | Declare reactive state on a subtree |
|
||||
| `data-on-<event>="count++"` | Run a statement in scope on a DOM event |
|
||||
| `data-text="expr"` | Bind an element's `textContent` to an expression |
|
||||
| `data-show="expr"` | Toggle visibility (`display`) on truthiness |
|
||||
| `data-for="item in list"` (opt. index and `key item.id`) | Per-item rendering; stable keys preserve DOM identity during reorder |
|
||||
| `data-key="item.id"` | Alternative key declaration for `data-for` templates |
|
||||
| `{{expr}}` or `{expr}` | Interpolation inside text nodes and attribute values |
|
||||
| `data-wrnexus-csr="id"` | Target for a generated CSR fetch binding (fetches `/__wrnexus/csr?...`) |
|
||||
| Directive | Purpose |
|
||||
| ---------------------------------- | ---------------------------------------------------- |
|
||||
| `data-scope="count: 0, name: 'x'"` | Declare reactive state on a subtree |
|
||||
| `data-on-<event>="count++"` | Run a statement in scope on a DOM event |
|
||||
| `data-text="expr"` | Bind an element's `textContent` to an expression |
|
||||
| `data-show="expr"` | Toggle visibility while preserving interactive state |
|
||||
|
||||
Compiled conditional rendering and dynamic component cases omit inactive elements from the live
|
||||
DOM. `data-show` is a visibility directive for stateful controls and keeps its element mounted.
|
||||
Neither mechanism is authorization: never place secrets in client-rendered branches. Authorize on
|
||||
the server and return only data the current request may access.
|
||||
| `data-for="item in list"` (opt. index and `key item.id`) | Per-item rendering; stable keys preserve DOM identity during reorder |
|
||||
| `data-key="item.id"` | Alternative key declaration for `data-for` templates |
|
||||
| `{{expr}}` or `{expr}` | Interpolation inside text nodes and attribute values |
|
||||
| `data-wrnexus-csr="id"` | Target for a generated CSR fetch binding (fetches `/__wrnexus/csr?...`) |
|
||||
|
||||
Supported expression features: literals, identifiers, member access (`a.b`, `a[b]`), function/method calls, arrays, objects, arithmetic, comparison, equality, logical (`&& ||`), unary (`! - +`), and ternary. Statements support `++`/`--`, assignment operators (`= += -= *= /= %=`), and bare expression/method calls. Rendering is dependency-tracked: a signal change only re-runs the renderers that actually read it.
|
||||
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/csr"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/csr",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"csr"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,9 +37,10 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.7.0"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
+75
-4
@@ -1,5 +1,14 @@
|
||||
# @wrnexus/db
|
||||
|
||||
## Rollout-safe migrations
|
||||
|
||||
Run `wrnexus db check` in CI before deployment. The analyzer reports stable
|
||||
diagnostics for drops, renames, type changes, new/enforced required columns,
|
||||
and potentially blocking index creation, with an expand/backfill/switch/contract
|
||||
recommendation. `wrnexus db migrate` blocks critical issues in pending
|
||||
migrations. `--allow-breaking` is an explicit operator override; already-applied
|
||||
migrations do not block later releases.
|
||||
|
||||
> The database layer for WrNexus: TS models as the single source of truth for DDL, validation, and result typing, plus a driver-based `Db` client, migrations, and a sqlc-style query generator.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
@@ -85,7 +94,8 @@ A `Driver` (implemented by adapters) exposes `dialect`, `query(sql, params?)`,
|
||||
- `exec(sql, params?)` — `Promise<ExecResult>` (`{ changes, lastInsertId? }`).
|
||||
- `tx(fn)` — run `fn(db)` in a transaction; rolls back on throw. Nested `tx` reuses the current transaction.
|
||||
- `createTable(model)` — runs the model's `CREATE TABLE IF NOT EXISTS` DDL.
|
||||
- `close()`.
|
||||
- `close()` — idempotently rejects new top-level work, drains active queries and
|
||||
transactions, then closes the underlying pool.
|
||||
|
||||
Every query is parameterized (positional params). `createTableSql(model, dialect, ifNotExists?)`
|
||||
renders `CREATE TABLE` directly; `Dialect` is `"sqlite" | "postgres" | "mysql"`.
|
||||
@@ -98,7 +108,9 @@ A process-wide registry the runtime configures at startup from `wrnexus.config.t
|
||||
- `setDb(db)` / `setDb(name, db)` — set the default or a named connection.
|
||||
- `registerDb(name, db)` — alias of `setDb(name, db)`.
|
||||
- `getDb(name = "default")` — the default or a named `Db` (throws if unconfigured).
|
||||
- `hasDb(name?)`, `databaseNames()`, `closeDatabases()`.
|
||||
- `hasDb(name?)`, `databaseNames()`, `closeDatabases()`. Registry shutdown clears
|
||||
registrations first, attempts every open database, and reports close failures
|
||||
together with `AggregateError` instead of leaking later pools.
|
||||
|
||||
```ts
|
||||
const users = await getDb().all("SELECT * FROM users");
|
||||
@@ -121,8 +133,8 @@ Migrations are `.sql` files (in e.g. `app/db/migrations`), each split into
|
||||
- `parseMigration(name, content)` → `Migration` (`{ name, up, down }`).
|
||||
- `loadMigrations(dir)` — parse all `.sql` files, sorted by filename.
|
||||
- `appliedMigrations(db)` — applied names, oldest first.
|
||||
- `migrate(db, dir)` — apply all pending (each in a transaction); returns applied names.
|
||||
- `rollback(db, dir)` — roll back the most recent; returns its name or `null`.
|
||||
- `migrate(db, dir, options?)` — apply all pending (each in a transaction); returns applied names.
|
||||
- `rollback(db, dir, options?)` — roll back the most recent; returns its name or `null`.
|
||||
- `status(db, dir)` — `{ name, applied }[]` for every migration file.
|
||||
- `scaffoldMigration(dir, name, dialect, models?)` — write a new numbered migration; with `models` it generates `CREATE`/`DROP` for every table (referenced tables first via topological sort). Returns the file path.
|
||||
|
||||
@@ -199,6 +211,21 @@ const pageTwo = await paginate(
|
||||
);
|
||||
```
|
||||
|
||||
For deployments, `{ dryRun: true }` reports pending names without applying
|
||||
their SQL, `signal` cancels safely between migrations, and the default
|
||||
database-backed lock prevents concurrent deploy runners. A live lock produces
|
||||
`WRN-DB-MIGRATION-LOCKED`; crash-stale locks expire after `lockTimeoutMs` (five
|
||||
minutes by default). Disable it with `lock: false` only when an external deploy
|
||||
coordinator already guarantees exclusivity.
|
||||
|
||||
```ts
|
||||
const pending = await migrate(db, "app/db/migrations", { dryRun: true });
|
||||
await migrate(db, "app/db/migrations", {
|
||||
signal: shutdownController.signal,
|
||||
lockTimeoutMs: 10 * 60_000,
|
||||
});
|
||||
```
|
||||
|
||||
MongoDB (document API):
|
||||
|
||||
```ts
|
||||
@@ -226,3 +253,47 @@ SQL driver — use `@wrnexus/db/mongo` directly.
|
||||
`wrnexus.config.ts`.
|
||||
- The `mongodb` npm package is an optional, lazily-imported peer — install it
|
||||
only if you use `@wrnexus/db/mongo`. The core package stays dependency-free.
|
||||
|
||||
## Repository and transaction helpers
|
||||
|
||||
Repositories accept an immutable equality `scope`, normally `{ column: "tenant_id", value:
|
||||
ctx.tenant.id }`. The scope is injected into every read, count, update and delete, while create
|
||||
overwrites any caller-supplied tenant value. This makes accidental cross-tenant CRUD through the
|
||||
repository API fail closed.
|
||||
|
||||
```ts
|
||||
import { createRepository, retryTransaction, databaseHealth, batch } from "@wrnexus/db";
|
||||
|
||||
const users = createRepository<User>(db, {
|
||||
table: "users",
|
||||
allowedColumns: ["email", "name", "active"],
|
||||
});
|
||||
|
||||
const user = await users.require(42);
|
||||
await users.update(42, { active: true });
|
||||
```
|
||||
|
||||
Repository identifiers are validated, writes may be restricted to an allowlist, and values always use query parameters. Infrastructure packages remain helper-only and do not add UI dependencies to server code.
|
||||
|
||||
## 0.8 repository and transaction helpers
|
||||
|
||||
```ts
|
||||
import { createRepository, databaseHealth, firstOrThrow, retryTransaction } from "@wrnexus/db";
|
||||
|
||||
const usersRepo = createRepository<User>(db, {
|
||||
table: "users",
|
||||
allowedColumns: ["email", "name", "active"],
|
||||
maxListLimit: 250,
|
||||
});
|
||||
|
||||
const users = await usersRepo.all({
|
||||
orderBy: "name",
|
||||
direction: "asc",
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
});
|
||||
```
|
||||
|
||||
Repository SQL identifiers are validated and values remain parameterized. Placeholder generation is dialect-aware: PostgreSQL uses `$1`, `$2`, and SQLite/MySQL use `?`. List limits are bounded.
|
||||
|
||||
`retryTransaction()` retries recognized serialization, deadlock, and database-lock errors by default. Supply `shouldRetry` for application-specific retryable errors; ordinary validation or business errors are not retried automatically.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/db",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/db — part of the WrNexus framework.",
|
||||
"description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/db"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/db",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"db"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -45,6 +61,7 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ interface RunningServer {
|
||||
|
||||
In development, `startServer` also connects `app/db/migrations` (and `app/db/<name>/migrations`) and auto-applies migrations, then starts an in-process file watcher. CSS edits hot-swap live. Page, component, layout, API, middleware, realtime, schema, locale, and public-asset edits invalidate only their cached modules, rescan routes where necessary, and morph fresh HTML through the existing HMR WebSocket. The server process and active gateway stay running.
|
||||
|
||||
`getWrnCompileMetrics()` exposes cumulative content-addressed compiler cache
|
||||
`hits`, `misses`, successful `compilations`, `errors`, `totalDurationMs`, and
|
||||
`lastDurationMs` for the DevToolbar or custom diagnostics. Tests and embedded
|
||||
servers can call `resetWrnCompileMetrics()` to establish a fresh measurement
|
||||
window.
|
||||
|
||||
### `createHandlers(deps)`
|
||||
|
||||
The core runtime shared by dev and prod. It handles CORS preflight, `/healthz` and `/__wrnexus/health`, request-body size limits (413), HMR socket upgrades (`/__wrnexus/hmr`), realtime WebSocket upgrades (`defineRoom` default export or a raw `websocket` export), the middleware pipeline, API routes (`/api/*`), framework assets (`/__wrnexus/*`), public assets, and full SSR page rendering (component mounts, layouts, slots, i18n markers, per-page script selection, ETag/304, gzip).
|
||||
@@ -296,7 +302,9 @@ Pages get only the scripts they use: `nav.js` always, `reactive.js` when a page
|
||||
|
||||
- **Bun-only.** Uses `Bun.serve` (HTTP + WebSocket), `Bun.file`, and `Bun.gzipSync`. The full app also relies on `bun:sqlite` / `Bun.SQL` via `@wrnexus/db`.
|
||||
- Orchestrates the whole framework: `@wrnexus/core` (context, security, realtime registry), `@wrnexus/router`, `@wrnexus/ssr` (`renderDocument`), `@wrnexus/csr` (client runtimes), `@wrnexus/compiler` (`.wrn` → TS), `@wrnexus/styles`, `@wrnexus/ui`, `@wrnexus/validation`, `@wrnexus/i18n`, `@wrnexus/db`, and `@wrnexus/pubsub` (Redis-backed cross-process realtime).
|
||||
- `.wrn` files are compiled to TypeScript into a hidden sibling `.wrnexus/` cache dir and dynamically imported; the module cache means each edited server module needs a fresh process (dev) — hence the restart-on-change model.
|
||||
- `.wrn` files compile into a content-addressed hidden `.wrnexus/` cache. Targeted
|
||||
invalidation gives changed modules a fresh import identity without restarting
|
||||
the development server.
|
||||
- Responses are gzipped when the client accepts it and the body is a buffered, compressible payload ≥ 1 KB; streaming/SSE responses opt out via `Cache-Control: no-transform`.
|
||||
</content>
|
||||
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-server",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/dev-server"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/dev-server",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"dev-server"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -25,25 +41,30 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.7.0",
|
||||
"@wrnexus/dev-toolbar": "^0.7.0",
|
||||
"@wrnexus/router": "^0.7.0",
|
||||
"@wrnexus/ssr": "^0.7.0",
|
||||
"@wrnexus/csr": "^0.7.0",
|
||||
"@wrnexus/compiler": "^0.7.0",
|
||||
"@wrnexus/styles": "^0.7.0",
|
||||
"@wrnexus/ui": "^0.7.0",
|
||||
"@wrnexus/validation": "^0.7.0",
|
||||
"@wrnexus/i18n": "^0.7.0",
|
||||
"@wrnexus/db": "^0.7.0",
|
||||
"@wrnexus/pubsub": "^0.7.0",
|
||||
"@wrnexus/uploader": "^0.7.0",
|
||||
"@wrnexus/plugin": "^0.7.0",
|
||||
"@wrnexus/store": "^0.7.0",
|
||||
"@wrnexus/security": "^0.7.0",
|
||||
"@wrnexus/observability": "^0.7.0"
|
||||
"@wrnexus/authz": "^0.8.5",
|
||||
"@wrnexus/rpc": "^0.8.5",
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/dev-toolbar": "^0.8.5",
|
||||
"@wrnexus/router": "^0.8.5",
|
||||
"@wrnexus/ssr": "^0.8.5",
|
||||
"@wrnexus/csr": "^0.8.5",
|
||||
"@wrnexus/compiler": "^0.8.5",
|
||||
"@wrnexus/styles": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5",
|
||||
"@wrnexus/validation": "^0.8.5",
|
||||
"@wrnexus/i18n": "^0.8.5",
|
||||
"@wrnexus/db": "^0.8.5",
|
||||
"@wrnexus/pubsub": "^0.8.5",
|
||||
"@wrnexus/uploader": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/store": "^0.8.5",
|
||||
"@wrnexus/security": "^0.8.5",
|
||||
"@wrnexus/observability": "^0.8.5",
|
||||
"@wrnexus/cache": "^0.8.5",
|
||||
"@wrnexus/pwa": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ Development-only page quality toolbar for WRNexusJS.
|
||||
- Runtime, resource and unhandled promise error capture
|
||||
- Accessibility, SEO, image, media, color, HTML, form, link, responsive and security checks
|
||||
- Performance and network observations
|
||||
- First-class application tabs for runtime, stores, cache, accessibility, SEO, performance,
|
||||
security, images, links, and JavaScript
|
||||
- Plugin-contributed applications with badges, descriptions, issue feeds, and structured data
|
||||
- Element highlighting and issue filtering
|
||||
- Server-side issue collector
|
||||
- Development-only asset strings for direct serving by `@wrnexus/dev-server`
|
||||
@@ -21,3 +24,7 @@ Serve `DEV_TOOLBAR_RUNTIME` at `/__wrnexus/dev-toolbar.js` and `DEV_TOOLBAR_CSS`
|
||||
```
|
||||
|
||||
The browser runtime exposes `window.__wrnexusDevToolbar`.
|
||||
|
||||
Plugin panels returned through `devToolbarPanels()` are automatically added to the application
|
||||
strip. Their issue category is filterable, and structured `data` is rendered as escaped diagnostic
|
||||
content so a plugin never needs to inject toolbar HTML.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/dev-toolbar",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/dev-toolbar"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/dev-toolbar",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"dev-toolbar"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -37,6 +53,7 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,80 +1,67 @@
|
||||
# @wrnexus/encryption
|
||||
|
||||
> Dependency-free crypto helpers for WrNexus: authenticated symmetric encryption (AES-256-GCM), hashing, and HMAC signing.
|
||||
Authenticated encryption, hashing, HMAC, key rotation, and optional encrypted HTTP exchanges for WRNexusJS.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
## Core helpers
|
||||
|
||||
## Overview
|
||||
- `generateKey()` — random 256-bit AES key encoded as base64.
|
||||
- `deriveKey(password, salt)` — PBKDF2-derived AES key.
|
||||
- `encrypt(plaintext, key)` / `decrypt(payload, key)` — AES-256-GCM.
|
||||
- `sha256(data)` — SHA-256 digest.
|
||||
- `hmacSign(data, secret)` / `hmacVerify(...)` — HMAC-SHA256.
|
||||
- `createKeyring(keys)` — active/previous key management.
|
||||
- `seal()` / `open()` — versioned ciphertext with key ID.
|
||||
|
||||
This package provides small, focused cryptographic primitives for server-side use: encrypting secrets/tokens/database fields at rest with AES-256-GCM, deriving keys from passwords via PBKDF2, computing SHA-256 digests, and signing/verifying payloads with HMAC-SHA256. It is built entirely on the standard **Web Crypto API** (`crypto.subtle`) plus `btoa`/`atob` and `TextEncoder`/`TextDecoder` — no third-party dependencies. Reach for it whenever you need to protect sensitive values or verify webhook signatures. All functions are `async` (Web Crypto is promise-based).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @wrnexus/encryption
|
||||
```
|
||||
|
||||
> Private package — the machine must be authenticated to the `wrnexus` npm org
|
||||
> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported).
|
||||
|
||||
## API
|
||||
|
||||
All keys are exchanged as **base64 strings** and all digests/signatures as **hex strings**.
|
||||
|
||||
| Export | Signature | Description |
|
||||
| ------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| `generateKey` | `() => Promise<string>` | Generate a random 256-bit AES key, base64-encoded. Store it as a secret. |
|
||||
| `deriveKey` | `(password: string, salt: string) => Promise<string>` | Derive a base64 AES-256 key from a password + salt using PBKDF2 (100,000 iterations, SHA-256). |
|
||||
| `encrypt` | `(plaintext: string, key: string) => Promise<string>` | AES-256-GCM encrypt a string. Returns base64 of `iv(12 bytes) ‖ ciphertext+tag`. A fresh random IV is used each call. |
|
||||
| `decrypt` | `(payload: string, key: string) => Promise<string>` | Decrypt a value produced by `encrypt`. Throws if the key is wrong or the data was tampered with. |
|
||||
| `sha256` | `(data: string) => Promise<string>` | SHA-256 hex digest of a string (e.g. content hashing, dedup keys). |
|
||||
| `hmacSign` | `(data: string, secret: string) => Promise<string>` | HMAC-SHA256 hex signature of `data` with `secret` (e.g. signing webhooks). |
|
||||
| `hmacVerify` | `(data: string, secret: string, signature: string) => Promise<boolean>` | Constant-time verify of an HMAC-SHA256 hex signature. |
|
||||
|
||||
Notes:
|
||||
|
||||
- `generateKey` produces a 32-byte (256-bit) key via `crypto.getRandomValues`.
|
||||
- `encrypt`/`decrypt` require a base64-encoded 256-bit key; anything else throws `"Encryption key must be a base64 256-bit key"`.
|
||||
- `decrypt` throws `"Invalid ciphertext"` if the payload is shorter than the 12-byte IV, and the underlying Web Crypto call throws on any authentication (tag) mismatch.
|
||||
- `hmacVerify` compares in constant time (length check plus XOR accumulation) to avoid timing leaks.
|
||||
|
||||
## Usage
|
||||
|
||||
Symmetric encryption of a secret at rest:
|
||||
## Encrypted HTTP envelope
|
||||
|
||||
```ts
|
||||
import { generateKey, encrypt, decrypt } from "@wrnexus/encryption";
|
||||
import {
|
||||
createEncryptedRequest,
|
||||
createKeyring,
|
||||
createMemoryReplayStore,
|
||||
decryptEncryptedResponse,
|
||||
encryptedExchange,
|
||||
} from "@wrnexus/encryption";
|
||||
|
||||
const key = await generateKey(); // store this safely (env/secret manager)
|
||||
const keyring = createKeyring([{ id: "2026-08", secret: process.env.API_BODY_KEY!, active: true }]);
|
||||
|
||||
const box = await encrypt("card #1234", key); // opaque base64 string, safe to persist
|
||||
const plain = await decrypt(box, key); // "card #1234"
|
||||
const replayStore = createMemoryReplayStore();
|
||||
|
||||
// Server middleware.
|
||||
app.use(
|
||||
encryptedExchange({
|
||||
keyring,
|
||||
replayStore,
|
||||
maxAgeMs: 60_000,
|
||||
maxBodyBytes: 1_048_576,
|
||||
}),
|
||||
);
|
||||
|
||||
// Controlled service/native client.
|
||||
const request = await createEncryptedRequest(
|
||||
"https://api.example.com/private/report",
|
||||
{ reportId: "report-1" },
|
||||
{ method: "POST", keyring },
|
||||
);
|
||||
const response = await fetch(request);
|
||||
const result = await decryptEncryptedResponse(response, request, { keyring });
|
||||
```
|
||||
|
||||
Deriving a key from a user password instead of a random key:
|
||||
The envelope binds authenticated ciphertext to:
|
||||
|
||||
```ts
|
||||
import { deriveKey, encrypt } from "@wrnexus/encryption";
|
||||
- HTTP method
|
||||
- URL path and query
|
||||
- request ID
|
||||
- timestamp and expiry window
|
||||
- encryption key ID
|
||||
- optional replay-store consumption
|
||||
|
||||
const key = await deriveKey("correct horse battery staple", "per-user-salt");
|
||||
const box = await encrypt("secret note", key);
|
||||
```
|
||||
`encryptedBody()` decrypts request bodies only. `encryptedExchange()` also encrypts successful downstream responses while allowing application exceptions to propagate normally. `encryptedFetch()` provides a convenient controlled-client call.
|
||||
|
||||
Hashing and webhook signature verification:
|
||||
## Security boundary
|
||||
|
||||
```ts
|
||||
import { sha256, hmacSign, hmacVerify } from "@wrnexus/encryption";
|
||||
Encrypted HTTP bodies **do not replace TLS/HTTPS**. Always use HTTPS.
|
||||
|
||||
const digest = await sha256("some content"); // 64-char hex string
|
||||
This layer is appropriate for service-to-service traffic, native/mobile applications, controlled agents, and selected fields protected with server-managed keys. It cannot conceal data from an end user when browser JavaScript receives the decryption key. Never ship a long-lived server encryption key to a browser.
|
||||
|
||||
const signature = await hmacSign(rawBody, webhookSecret);
|
||||
const ok = await hmacVerify(rawBody, webhookSecret, incomingSignatureHeader);
|
||||
if (!ok) throw new Error("Invalid webhook signature");
|
||||
```
|
||||
|
||||
## Requirements / Notes
|
||||
|
||||
- **Bun-only.** Relies on the Web Crypto API (`crypto.subtle`, `crypto.getRandomValues`) and the global `btoa`/`atob`, `TextEncoder`/`TextDecoder` — all available in Bun's runtime.
|
||||
- **No dependencies.** The package has an empty dependency set; nothing is bundled beyond standard runtime APIs.
|
||||
- Algorithms: AES-256-GCM (encryption), PBKDF2 with 100k SHA-256 iterations (key derivation), SHA-256 (digest), HMAC-SHA256 (signing).
|
||||
- Keep generated/derived keys and HMAC secrets out of source control; treat them as first-class secrets.
|
||||
Use a shared replay store such as Redis in multi-instance deployments. The memory replay store is process-local.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/encryption",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
||||
"description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/encryption"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/encryption",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"encryption"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -20,7 +36,11 @@
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/helpers",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/helpers"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/helpers",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"helpers"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,9 +37,10 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.7.0"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
+60
-134
@@ -1,167 +1,93 @@
|
||||
# @wrnexus/i18n
|
||||
|
||||
> Per-request translations plus locale-aware number, date, and currency formatting for WrNexus apps.
|
||||
Recursive locale loading, fallback resolution, SSR/browser translations, locale formatting, and language UI blocks for WRNexusJS.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
## Locale files
|
||||
|
||||
## Overview
|
||||
Both layouts can be used together:
|
||||
|
||||
`@wrnexus/i18n` loads locale files from `app/locales/<lang>.json`, resolves the
|
||||
active language for each request (cookie → `Accept-Language` → default), and
|
||||
builds a `t(key, params)` translator used both in server code and in `.wrn`
|
||||
views. It also ships Intl-based formatting helpers and a tiny client runtime that
|
||||
wires up a language switcher. Translation lookup, language resolution, and HTML
|
||||
marker rewriting run server-side; only the small `I18N_RUNTIME` snippet runs in
|
||||
the browser.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @wrnexus/i18n
|
||||
```text
|
||||
app/locales/en.json
|
||||
app/locales/en/common.json
|
||||
app/locales/en/auth.json
|
||||
app/locales/mr/common.json
|
||||
```
|
||||
|
||||
> Private package — the machine must be authenticated to the `wrnexus` npm org
|
||||
> (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported).
|
||||
|
||||
## API
|
||||
|
||||
### Loading & resolving
|
||||
|
||||
| Export | Signature | Description |
|
||||
| ------------- | -------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `loadLocales` | `(dir: string) => Record<string, Messages>` | Reads every `<lang>.json` in `dir` into a `{ lang: messages }` map. Missing dir → `{}`; a bad file is warned and skipped. |
|
||||
| `resolveI18n` | `(messages: Record<string, Messages>, config?: I18nConfig) => ResolvedI18n` | Merges loaded messages + config into a resolved bundle (default lang, supported langs, messages). |
|
||||
| `resolveLang` | `(i18n: ResolvedI18n, cookieValue: string \| undefined, acceptLanguage: string \| null) => string` | Picks the active language: matching cookie → best `Accept-Language` tag (falls back to base tag, e.g. `en-US` → `en`) → `i18n.default`. |
|
||||
| `makeT` | `(i18n: ResolvedI18n, lang: string) => TFunction` | Builds a translator resolving current language → default → the key itself, with `{param}` interpolation. |
|
||||
|
||||
### Types & constants
|
||||
|
||||
| Export | Kind | Notes |
|
||||
| -------------- | ----------- | ------------------------------------------------------------------------------ |
|
||||
| `Messages` | `type` | `Record<string, unknown>` — a locale's messages (supports nested/dotted keys). |
|
||||
| `I18nConfig` | `interface` | `{ default?: string; locales?: string[] }`. |
|
||||
| `ResolvedI18n` | `interface` | `{ default: string; langs: string[]; messages: Record<string, Messages> }`. |
|
||||
| `LANG_COOKIE` | `const` | `"wire-lang"` — the cookie the language is read from / written to. |
|
||||
| `I18N_JS_HREF` | `const` | `"/__wrnexus/i18n.js"` — URL the client runtime is served at. |
|
||||
|
||||
### HTML & client runtime
|
||||
|
||||
| Export | Signature | Description |
|
||||
| ---------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `translateHtml` | `(html: string, t: TFunction) => string` | Rewrites markers in rendered HTML: `t:<attr>="key"` → `<attr>="<translation>"` (attribute-escaped) and `<tag data-t="key">…</tag>` → element text becomes the translation (HTML-escaped). No-op unless a marker is present. |
|
||||
| `renderI18nData` | `(i18n: ResolvedI18n, lang: string) => string` | JS snippet setting `window.__wireI18n = { lang, langs, default }` for the client switcher. |
|
||||
| `I18N_RUNTIME` | `const string` | Browser IIFE that binds `[data-wire-lang-set="es"]` clicks and `select[data-wire-lang]` changes to set the `wire-lang` cookie and reload. Exposes `window.__wireLang.set(lang)`. |
|
||||
|
||||
### Formatting helpers (re-exported from `./format.ts`)
|
||||
|
||||
| Export | Signature | Example |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
||||
| `formatNumber` | `(value: number, lang: string, options?: Intl.NumberFormatOptions) => string` | `1234.5 → "1,234.5"` |
|
||||
| `formatCurrency` | `(value: number, currency: string, lang: string) => string` | `9.99, "USD" → "$9.99"` |
|
||||
| `formatDate` | `(value: Date \| number \| string, lang: string, options?: Intl.DateTimeFormatOptions) => string` | defaults to `{ dateStyle: "medium" }` |
|
||||
| `formatRelativeTime` | `(value: number, unit: Intl.RelativeTimeFormatUnit, lang: string) => string` | `-3, "day" → "3 days ago"` (`numeric: "auto"`) |
|
||||
| `plural` | `(count: number, forms: Partial<Record<Intl.LDMLPluralRule, string>>, lang: string) => string` | picks CLDR form; `#` is replaced by `count` |
|
||||
|
||||
## Usage
|
||||
|
||||
### Server: load, resolve, translate
|
||||
Namespaced files become keys such as `common.save` and `auth.signIn`.
|
||||
|
||||
```ts
|
||||
import {
|
||||
loadLocales,
|
||||
resolveI18n,
|
||||
resolveLang,
|
||||
makeT,
|
||||
translateHtml,
|
||||
LANG_COOKIE,
|
||||
} from "@wrnexus/i18n";
|
||||
import { loadLocales, makeT, resolveI18n, resolveLang } from "@wrnexus/i18n";
|
||||
|
||||
// app/locales/en.json, app/locales/es.json
|
||||
const messages = loadLocales("app/locales");
|
||||
const i18n = resolveI18n(messages, { default: "en", locales: ["en", "es"] });
|
||||
const i18n = resolveI18n(loadLocales("app/locales", { strict: true }), {
|
||||
default: "en",
|
||||
locales: ["en", "mr", "hi"],
|
||||
fallbacks: { "mr-IN": ["mr", "en"] },
|
||||
cookie: { name: "wire-lang", sameSite: "Lax", secure: true },
|
||||
});
|
||||
|
||||
// Per request:
|
||||
const lang = resolveLang(i18n, req.cookies?.[LANG_COOKIE], req.headers.get("accept-language"));
|
||||
const lang = resolveLang(i18n, cookieValue, request.headers.get("accept-language"));
|
||||
const t = makeT(i18n, lang);
|
||||
|
||||
t("nav.home"); // dotted key → "Home"
|
||||
t("greeting", { name: "Ada" }); // "Hello, {name}" → "Hello, Ada"
|
||||
|
||||
// After rendering a .wrn view, resolve translation markers in the HTML:
|
||||
const finalHtml = translateHtml(renderedHtml, t);
|
||||
t("common.hello", { name: "Ajay" });
|
||||
```
|
||||
|
||||
`app/locales/en.json`:
|
||||
## Resolution behavior
|
||||
|
||||
```json
|
||||
{
|
||||
"nav": { "home": "Home" },
|
||||
"greeting": "Hello, {name}"
|
||||
}
|
||||
```
|
||||
- normalized BCP-47-style locale names
|
||||
- cookie preference
|
||||
- weighted `Accept-Language`
|
||||
- wildcard language ranges
|
||||
- regional base fallback
|
||||
- explicit fallback chains
|
||||
- configured default language
|
||||
- automatic RTL for Arabic, Hebrew, Persian, Urdu, and related languages
|
||||
|
||||
### Views: translation markers
|
||||
Locale JSON is size-limited and rejects prototype-pollution keys. Recursive namespace collisions are resolved safely.
|
||||
|
||||
## Views and runtime
|
||||
|
||||
```html
|
||||
<h1 data-t="nav.home">Home</h1>
|
||||
<h1 data-t="dashboard.title">Dashboard</h1>
|
||||
<input t:placeholder="search.placeholder" />
|
||||
```
|
||||
|
||||
`translateHtml` replaces the element text for `data-t` and the attribute value for
|
||||
any `t:<attr>` (e.g. `t:placeholder`, `t:aria-label`).
|
||||
Text and translated attributes are resolved during SSR. Active/fallback messages are serialized safely for the language runtime, which rebinds `data-t` markers after client navigation.
|
||||
|
||||
### Client: language switcher
|
||||
Enable `i18nPlugin()` to use:
|
||||
|
||||
```ts
|
||||
import { renderI18nData, I18N_RUNTIME, I18N_JS_HREF } from "@wrnexus/i18n";
|
||||
- `<LanguageSwitcher />`
|
||||
- `<LocaleStatus />`
|
||||
|
||||
// In the document <head>:
|
||||
const head = `
|
||||
<script>${renderI18nData(i18n, lang)}</script>
|
||||
<script src="${I18N_JS_HREF}"></script>
|
||||
`;
|
||||
`LanguageSwitcher` renders a native `select[data-wire-lang]`. The packaged runtime validates the
|
||||
selection against the configured locales, writes the configured language cookie, updates the
|
||||
document `lang`/`dir` attributes, emits `wrnexus:language-change`, and reloads so the next SSR
|
||||
request uses the same cookie. No application-owned browser script is required.
|
||||
|
||||
// Serve I18N_RUNTIME at I18N_JS_HREF; then in markup:
|
||||
// <button data-wire-lang-set="es">Español</button>
|
||||
// <select data-wire-lang>…</select>
|
||||
```
|
||||
## Formatting
|
||||
|
||||
### Formatting
|
||||
- `formatNumber`
|
||||
- `formatCurrency`
|
||||
- `formatDate`
|
||||
- `formatRelativeTime`
|
||||
- `plural`
|
||||
- `createLocaleFormatter`
|
||||
- `translationCoverage`
|
||||
Localization tooling can extract statically discoverable `t("key")`,
|
||||
`i18n.t("key")`, and `data-i18n="key"` usage, compare every locale with a
|
||||
reference, and create layout-stressing pseudo-locales:
|
||||
|
||||
```ts
|
||||
import {
|
||||
formatNumber,
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
formatRelativeTime,
|
||||
plural,
|
||||
auditLocaleKeys,
|
||||
createPseudoLocale,
|
||||
extractTranslationKeysFromFiles,
|
||||
} from "@wrnexus/i18n";
|
||||
|
||||
formatNumber(1234.5, lang); // "1,234.5"
|
||||
formatCurrency(9.99, "USD", lang); // "$9.99"
|
||||
formatDate(Date.now(), lang); // "Jul 4, 2026"
|
||||
formatRelativeTime(-3, "day", lang); // "3 days ago"
|
||||
plural(2, { one: "# item", other: "# items" }, lang); // "2 items"
|
||||
const used = extractTranslationKeysFromFiles(sourceFiles);
|
||||
const coverage = auditLocaleKeys(messages, "en");
|
||||
const enXA = createPseudoLocale(messages.en);
|
||||
const arXB = createPseudoLocale(messages.en, { rtl: true });
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`resolveI18n` accepts an `I18nConfig`:
|
||||
|
||||
- `default` — fallback language; used when nothing else matches. Ignored if it has
|
||||
no loaded messages, in which case the first supported language is used.
|
||||
- `locales` — explicit supported-language list; defaults to the loaded locale names.
|
||||
|
||||
Language resolution order at request time (`resolveLang`): a supported `wire-lang`
|
||||
cookie value → the first matching `Accept-Language` tag (or its base subtag) → the
|
||||
resolved default.
|
||||
|
||||
## Requirements / Notes
|
||||
|
||||
- **Bun-only.** Locale loading uses `node:fs` (`existsSync`, `readdirSync`,
|
||||
`readFileSync`) and `node:path`; formatting relies on the platform `Intl` APIs.
|
||||
- Works with [`@wrnexus/core`](../core) — `TFunction` (the `t(key, params)` type)
|
||||
comes from core, and the resolved translator is exposed as `ctx.t` / `ctx.lang`
|
||||
in request handling.
|
||||
- Nested message objects are supported: keys are looked up whole first, then split
|
||||
on `.` to walk the object tree.
|
||||
Pseudo-localization preserves interpolation placeholders and markup tags. RTL
|
||||
pseudo output uses Unicode direction controls, while runtime direction detection
|
||||
continues to derive `rtl` from Arabic and other RTL language subtags.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/i18n",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/i18n — part of the WrNexus framework.",
|
||||
"description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/i18n"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/i18n",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"i18n"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -18,12 +34,28 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"./plugin": {
|
||||
"types": "./dist/plugin.d.ts",
|
||||
"import": "./dist/plugin.js"
|
||||
},
|
||||
"./components/*": "./components/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.7.0"
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5"
|
||||
},
|
||||
"wrnexus": {
|
||||
"plugin": {
|
||||
"plugin": "./dist/plugin.js",
|
||||
"export": "default",
|
||||
"factory": true
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md",
|
||||
"components"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -130,3 +130,62 @@ app.use(jwtAuth({ secret: process.env.JWT_SECRET!, required: false }));
|
||||
- Integrates with [`@wrnexus/core`](../core) for `Context`, `Middleware`, and
|
||||
`ctx.user`; it complements the framework's cookie/session auth with a
|
||||
stateless bearer-token flow for API and mobile clients.
|
||||
|
||||
## Access, refresh, scope, and cookie helpers
|
||||
|
||||
```ts
|
||||
import {
|
||||
createAccessToken,
|
||||
createRefreshToken,
|
||||
verifyAccessToken,
|
||||
verifyRefreshToken,
|
||||
extractBearerToken,
|
||||
requireScopes,
|
||||
jwtCookie,
|
||||
} from "@wrnexus/jwt";
|
||||
```
|
||||
|
||||
The helpers add explicit `type: "access" | "refresh"` claims, scope checks, refresh-token family metadata, no-store token responses, and secure cookie defaults. `__Host-` cookies are rejected unless they use `Path=/` and `Secure`; `SameSite=None` is rejected without `Secure`.
|
||||
|
||||
## 0.8 helper kit
|
||||
|
||||
```ts
|
||||
import {
|
||||
createTokenPair,
|
||||
verifyAccessToken,
|
||||
verifyRefreshToken,
|
||||
extractBearerToken,
|
||||
readJwtCookie,
|
||||
jwtCookie,
|
||||
clearJwtCookie,
|
||||
requireScopes,
|
||||
} from "@wrnexus/jwt";
|
||||
|
||||
const pair = await createTokenPair(user.id, {
|
||||
accessSecret: process.env.JWT_ACCESS_SECRET!,
|
||||
refreshSecret: process.env.JWT_REFRESH_SECRET!,
|
||||
scopes: ["profile:read"],
|
||||
family: sessionFamily,
|
||||
});
|
||||
```
|
||||
|
||||
The helper kit validates `__Host-` cookie invariants, cookie names and paths, `SameSite=None` security, typed access/refresh token types, scope requirements, and no-store token responses.
|
||||
In addition to local HS256 secrets/keyrings, the package verifies standards-based
|
||||
RS256 tokens through bounded remote JWKS caches:
|
||||
|
||||
```ts
|
||||
import { createRemoteJwks, verifyJwtWithJwks } from "@wrnexus/jwt";
|
||||
|
||||
const jwks = createRemoteJwks("https://issuer.example/.well-known/jwks.json");
|
||||
const claims = await verifyJwtWithJwks(token, jwks, {
|
||||
issuer: "https://issuer.example",
|
||||
audience: "my-api",
|
||||
maxAge: 300,
|
||||
});
|
||||
```
|
||||
|
||||
JWKS URLs must use HTTPS. Responses have key-count/byte limits, accept only
|
||||
RS256 signing RSA keys, deduplicate concurrent refreshes, cache imported public
|
||||
keys, and force an immediate refresh for an unknown `kid` so issuer rotation
|
||||
does not wait for cache expiry. Never use decoded-but-unverified claims for an
|
||||
authorization decision.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/jwt",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
||||
"description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/jwt"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/jwt",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"jwt"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -20,7 +36,11 @@
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -76,6 +76,14 @@ const status = network ? await network.getStatus() : { connected: true, connecti
|
||||
|
||||
Unavailable required plugins throw `MobileUnavailableError` with an actionable message.
|
||||
|
||||
The package also provides portable application-facing primitives:
|
||||
|
||||
- `listenDeepLinks` normalizes initial and live links with an allowed-scheme list.
|
||||
- `PushNotifications` performs permission gating and validates registrations.
|
||||
- `SecureStorage` namespaces and validates keys over an application-supplied encrypted
|
||||
Keychain/Keystore adapter; it does not mislabel browser `localStorage` as secure.
|
||||
- `OfflineQueue` persists bounded sync batches through a pluggable durable store.
|
||||
|
||||
## Requirements / Notes
|
||||
|
||||
- Capacitor plugin imports must remain in browser-owned modules.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/mobile",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/mobile"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/mobile",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"mobile"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,9 +37,10 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/native": "^0.7.0"
|
||||
"@wrnexus/native": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -79,6 +79,9 @@ const position = await native.run(
|
||||
Built-ins include `camera`, `clipboard.write`, `share`, `geolocation`, `network`,
|
||||
`haptics`, storage, filesystem, notifications, and device information.
|
||||
|
||||
`defineNativeManifest` declares required capabilities and typed permissions, while
|
||||
`PermissionManager` normalizes permission query/request flows across platform adapters.
|
||||
|
||||
## Requirements / Notes
|
||||
|
||||
Use `supports()` before showing optional controls. Mobile capabilities require their
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/native",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/native"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/native",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"native"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -29,6 +45,7 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -194,3 +194,24 @@ const gitlab = defineProvider({
|
||||
`verifier` between `startAuth` and `completeAuth` (session or signed cookie).
|
||||
- Pairs with [`@wrnexus/core`](../core) — feed the normalized `OAuthProfile` into
|
||||
`logIn` to establish a session.
|
||||
OIDC integrations can combine strict discovery with the rotating JWKS verifier:
|
||||
|
||||
```ts
|
||||
import { createRemoteJwks } from "@wrnexus/jwt";
|
||||
import { discoverOidc, verifyOidcIdToken } from "@wrnexus/oauth";
|
||||
|
||||
const metadata = await discoverOidc("https://issuer.example");
|
||||
const jwks = createRemoteJwks(metadata.jwks_uri);
|
||||
const claims = await verifyOidcIdToken(idToken, {
|
||||
issuer: metadata.issuer,
|
||||
clientId: "client-id",
|
||||
jwks,
|
||||
nonce: expectedNonce,
|
||||
accessToken,
|
||||
});
|
||||
```
|
||||
|
||||
Discovery requires an exact normalized issuer and HTTPS endpoints without URL
|
||||
credentials/fragments. ID-token verification checks the RS256 signature,
|
||||
expiry/not-before, issuer, audience, required OIDC claims, nonce, multi-audience
|
||||
`azp`, optional token age, and optional `at_hash` binding.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/oauth",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/oauth"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/oauth",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"oauth"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -20,7 +36,11 @@
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/jwt": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,60 @@
|
||||
# @wrnexus/plugin
|
||||
|
||||
## Least-privilege package permissions
|
||||
|
||||
Package manifests declare every framework capability they register:
|
||||
|
||||
```json
|
||||
{
|
||||
"wrnexus": {
|
||||
"permissions": ["routes", "migrations"],
|
||||
"routes": [{ "kind": "api", "path": "/api/example", "entry": "./route.ts" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Applications can enable fail-closed grants:
|
||||
|
||||
```ts
|
||||
export default {
|
||||
pluginPermissions: {
|
||||
enforce: true,
|
||||
grants: { "example-plugin": ["routes"] },
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Discovery rejects used-but-undeclared capabilities with
|
||||
`WRN-PLUGIN-PERMISSION-UNDECLARED` and ungranted capabilities with
|
||||
`WRN-PLUGIN-PERMISSION-DENIED`. Permissions cover components, browser runtime,
|
||||
assets, styles, routes, middleware, migrations, config, transforms,
|
||||
diagnostics/tooling, and server/build hooks.
|
||||
|
||||
## Compatibility matrices
|
||||
|
||||
Manifests can add `compatibility: { bunMin: "1.3.0", os: ["linux",
|
||||
"darwin"] }` alongside `runtimes` and `requires`. Use
|
||||
`testPluginCompatibility(manifest, targets)` in a package test to exercise the
|
||||
complete support matrix. Runtime discovery enforces the same Bun minimum, OS,
|
||||
runtime, and capability declarations used by the test kit.
|
||||
|
||||
Deterministic WRNexusJS plugin contracts for configuration, AST/code transforms,
|
||||
diagnostics, development servers, production builds, and DevToolbar extensions.
|
||||
|
||||
Use `definePlugin()` and declare `enforce`, `before`, or `after` when ordering matters.
|
||||
Duplicate names and dependency cycles are rejected.
|
||||
|
||||
## Complete lifecycle and contributions
|
||||
|
||||
Plugins may implement `setup`, `configure`, `configResolved`, `transformAst`,
|
||||
`transformCode`, `diagnostics`, `routes`, `configureServer`, `buildStart`,
|
||||
`buildEnd`, `render`, `deploy`, `shutdown`, and `hmrUpdate`. The runner preserves
|
||||
resolved plugin order for every hook and executes `setup` exactly once.
|
||||
|
||||
In addition to components, routes, middleware, assets, styles, runtimes, and
|
||||
migrations, plugins can contribute `directives`, `cliCommands`,
|
||||
`virtualModules`, `deploymentAdapters`, `configSchemas`, `documentation`, and
|
||||
`typeDefinitions`. Names are collision checked. Configuration schemas run after
|
||||
configuration resolution, CLI commands are callable as normal `wrnexus`
|
||||
commands, directives participate in AST transformation, and production builds
|
||||
materialize virtual modules and invoke matching contributed adapters.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/plugin",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/plugin — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/plugin"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/plugin",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"plugin"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -33,9 +49,10 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/syntax": "^0.7.0"
|
||||
"@wrnexus/syntax": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,13 +31,15 @@ Creates a bus over a driver. Defaults to `memoryDriver()` (in-process).
|
||||
interface PubSub {
|
||||
publish<T = unknown>(topic: string, message: T): Promise<void>;
|
||||
subscribe<T = unknown>(pattern: string, handler: Handler<T>): () => void;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
type Handler<T = unknown> = (message: T, topic: string) => void | Promise<void>;
|
||||
```
|
||||
|
||||
- `publish(topic, message)` — resolves once the driver has dispatched the message.
|
||||
- `publish(topic, message)` — resolves once the driver and in-memory async handlers finish.
|
||||
- `subscribe(pattern, handler)` — returns an unsubscribe function.
|
||||
- `close()` — idempotently rejects new work, clears local subscriptions, and closes the driver.
|
||||
|
||||
### Pattern matching
|
||||
|
||||
@@ -67,7 +69,7 @@ then `redis://localhost:6379`. The URL may carry a password and a database index
|
||||
(e.g. `redis://:secret@host:6379/2`).
|
||||
|
||||
```ts
|
||||
function redisDriver(url?: string): PubSubDriver & { close(): void };
|
||||
function redisDriver(url?: string, options?: RedisDriverOptions): PubSubDriver & { close(): void };
|
||||
```
|
||||
|
||||
- Exact topics use Redis `SUBSCRIBE`; wildcard patterns (`ns:*`, `*`) use
|
||||
@@ -75,6 +77,9 @@ function redisDriver(url?: string): PubSubDriver & { close(): void };
|
||||
- Messages are JSON-stringified on publish and `JSON.parse`d on receipt; a payload
|
||||
that isn't valid JSON is delivered as the raw string.
|
||||
- `close()` tears down both the subscriber and publisher connections.
|
||||
- Lost sockets reconnect with bounded exponential backoff and active subscriptions
|
||||
are replayed. `maxPending` bounds unavailable-connection writes (default 1000);
|
||||
`reconnectDelayMs` and `reconnectMaxDelayMs` tune recovery (100ms/5000ms).
|
||||
|
||||
### RESP codec (internal)
|
||||
|
||||
@@ -115,8 +120,8 @@ bus.subscribe("order:*", (msg, topic) => {
|
||||
|
||||
await bus.publish("order:created", { id: 7 });
|
||||
|
||||
// on shutdown
|
||||
driver.close();
|
||||
// on shutdown (also closes the driver)
|
||||
await bus.close();
|
||||
```
|
||||
|
||||
## Requirements / Notes
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/pubsub",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/pubsub"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/pubsub",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"pubsub"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -19,12 +35,17 @@
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./brokers": {
|
||||
"types": "./dist/brokers.d.ts",
|
||||
"import": "./dist/brokers.js"
|
||||
},
|
||||
"./redis": {
|
||||
"types": "./dist/redis.d.ts",
|
||||
"import": "./dist/redis.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
+61
-14
@@ -43,33 +43,45 @@ function createQueue(options?: QueueOptions): Queue;
|
||||
| `backoffMs` | `number` | `1000` | Base retry backoff in ms; doubles per attempt. |
|
||||
| `pollMs` | `number` | `250` | Poll interval used once `start()` is called (ms). |
|
||||
| `onFailed` | `(job: Job, error: unknown) => void` | — | Called when a job exhausts its attempts. |
|
||||
| `concurrency` | `number` | unlimited | Maximum jobs claimed by one `drain()` call. |
|
||||
| `capacity` | `number` | unlimited | Maximum queued plus active jobs before adds reject. |
|
||||
| `now` | `() => number` | `Date.now` | Clock injection for deterministic tests. |
|
||||
|
||||
### `Queue`
|
||||
|
||||
The object returned by `createQueue`.
|
||||
|
||||
| Method | Signature | Description |
|
||||
| --------- | -------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| `add` | `add<T>(name, data: T, options?: AddOptions): Promise<Job<T>>` | Enqueue a job under a worker name. Returns the created job. |
|
||||
| `process` | `process<T>(name, handler: JobHandler<T>): void` | Register the worker that runs jobs of the given name. |
|
||||
| `drain` | `drain(now?: number): Promise<number>` | Run every job whose `runAt ≤ now`, once. Returns how many ran. |
|
||||
| `start` | `start(): void` | Begin polling every `pollMs`. No-op if already started. |
|
||||
| `stop` | `stop(): void` | Stop the poll timer. |
|
||||
| `size` | `size(): number` | Number of jobs currently queued. |
|
||||
| Method | Signature | Description |
|
||||
| ---------- | -------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| `add` | `add<T>(name, data: T, options?: AddOptions): Promise<Job<T>>` | Enqueue a job under a worker name. Returns the created job. |
|
||||
| `process` | `process<T>(name, handler: JobHandler<T>): void` | Register the worker that runs jobs of the given name. |
|
||||
| `drain` | `drain(now?: number): Promise<number>` | Run every job whose `runAt ≤ now`, once. Returns how many ran. |
|
||||
| `start` | `start(): void` | Begin polling every `pollMs`. No-op if already started. |
|
||||
| `stop` | `stop(): void` | Stop the poll timer. |
|
||||
| `shutdown` | `shutdown({ force? }): Promise<void>` | Stop accepting jobs and await active work; force aborts it. |
|
||||
| `size` | `size(): number` | Number of jobs currently queued. |
|
||||
| `get/list` | `get(id)` / `list(name?)` | Inspect defensive copies of pending jobs. |
|
||||
| `cancel` | `cancel(id): boolean` | Remove queued work or abort an active handler. |
|
||||
| `failed` | `failed(): Job[]` | Inspect exhausted jobs in the dead-letter collection. |
|
||||
| `retry` | `retry(id): Promise<boolean>` | Reset and requeue a dead-lettered job. |
|
||||
|
||||
#### `AddOptions`
|
||||
|
||||
| Option | Type | Description |
|
||||
| ------------- | -------- | -------------------------------------------------------------------------- |
|
||||
| `delayMs` | `number` | Delay before the job becomes runnable (ms). |
|
||||
| `maxAttempts` | `number` | Max attempts before dead-lettering. Defaults to the queue's `maxAttempts`. |
|
||||
| `repeat` | `number` | Re-enqueue this job this many ms after each successful run (recurring). |
|
||||
| Option | Type | Description |
|
||||
| ---------------- | -------- | -------------------------------------------------------------------------- |
|
||||
| `delayMs` | `number` | Delay before the job becomes runnable (ms). |
|
||||
| `maxAttempts` | `number` | Max attempts before dead-lettering. Defaults to the queue's `maxAttempts`. |
|
||||
| `repeat` | `number` | Re-enqueue this job this many ms after each successful run (recurring). |
|
||||
| `priority` | `number` | Higher values are selected first among due jobs. |
|
||||
| `idempotencyKey` | `string` | Return the matching pending job instead of enqueueing a duplicate. |
|
||||
|
||||
#### `JobHandler<T>`
|
||||
|
||||
```ts
|
||||
type JobHandler<T = unknown> = (job: Job<T>) => void | Promise<void>;
|
||||
type JobHandler<T = unknown> = (
|
||||
job: Job<T>,
|
||||
context: { signal: AbortSignal },
|
||||
) => void | Promise<void>;
|
||||
```
|
||||
|
||||
#### `Job<T>`
|
||||
@@ -106,6 +118,18 @@ await queue.add("email", { to: "a@b.com" }, { delayMs: 5000, maxAttempts: 3 });
|
||||
queue.start(); // begin polling; queue.stop() to halt
|
||||
```
|
||||
|
||||
Use `context.signal` in network/database calls so forced shutdown and active
|
||||
cancellation finish promptly. For process termination, prefer
|
||||
`await queue.shutdown()`; use `{ force: true }` only after your grace period.
|
||||
|
||||
### Durable queue
|
||||
|
||||
`createDurableQueue({ store })` retains jobs until their handler succeeds and
|
||||
supports atomic leases when a driver implements `QueueStore.claim`. It exposes
|
||||
the same cancellation/shutdown behavior plus `list`, `failed`, and `retry`.
|
||||
The included `memoryQueueStore()` is useful for tests; production Redis/SQL
|
||||
drivers should make `claim()` atomic to prevent two workers executing one job.
|
||||
|
||||
### Recurring jobs
|
||||
|
||||
Pass `repeat` to re-enqueue a job a fixed interval after each successful run:
|
||||
@@ -145,6 +169,29 @@ clock = 5000;
|
||||
const ran = await queue.drain(); // => 1
|
||||
```
|
||||
|
||||
### Durable workflows and approvals
|
||||
|
||||
`createWorkflowEngine(store)` executes dependency-ordered steps and persists every transition,
|
||||
result, progress update, failure, cancellation, and approval record. Approval steps pause safely
|
||||
and can resume after a process restart because the snapshot lives in the supplied `WorkflowStore`.
|
||||
|
||||
```ts
|
||||
const workflow = defineDurableWorkflow({
|
||||
name: "publish-report",
|
||||
steps: [
|
||||
{ name: "build", run: buildReport },
|
||||
{ name: "approve", dependsOn: ["build"], approval: true, run: (report) => report },
|
||||
{ name: "publish", dependsOn: ["approve"], run: publishReport },
|
||||
],
|
||||
});
|
||||
|
||||
const run = await engine.start(workflow, input);
|
||||
await engine.approve(workflow, run.id, "approve", currentUser.id);
|
||||
```
|
||||
|
||||
Use `memoryWorkflowStore()` for tests. Production stores implement the small `get`, `put`, and
|
||||
`list` contract using the same transactional database or durable service as the application.
|
||||
|
||||
## Retry & backoff behavior
|
||||
|
||||
- On a thrown handler error, the job is retried while `attempts < maxAttempts`.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/queue",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/queue"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/queue",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"queue"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -20,7 +36,11 @@
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/reactive",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/reactive"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/reactive",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"reactive"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,6 +37,7 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/router",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/router — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/router"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/router",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"router"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,10 +37,11 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/compiler": "^0.7.0",
|
||||
"@wrnexus/core": "^0.7.0"
|
||||
"@wrnexus/compiler": "^0.8.5",
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/ssr"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/ssr",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"ssr"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -29,11 +45,12 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.7.0",
|
||||
"@wrnexus/store": "^0.7.0",
|
||||
"@wrnexus/security": "^0.7.0"
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/store": "^0.8.5",
|
||||
"@wrnexus/security": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# @wrnexus/styles
|
||||
|
||||
## Reusable layers and presets
|
||||
|
||||
Compose local or package foundations in order; later layers override earlier
|
||||
ones and the application has final base-config precedence:
|
||||
|
||||
```ts
|
||||
export default defineConfig({
|
||||
extends: ["@workroot/wrnexus-enterprise", "./layers/company"],
|
||||
profiles: { production: { port: 8080 } },
|
||||
});
|
||||
```
|
||||
|
||||
A directory layer exports `wrnexus.layer.ts` (JavaScript/MJS are supported).
|
||||
A package can provide that conventional file or declare
|
||||
`wrnexus.layer` in its `package.json`. Layers may extend other layers and carry
|
||||
the complete app configuration, including plugins that contribute layouts,
|
||||
components, routes, middleware, and migrations. `plugins` and `head` compose;
|
||||
other arrays intentionally replace earlier values. Cycles and missing/invalid
|
||||
entries fail with stable `WRN-CONFIG-LAYER-*` diagnostics. `wrnexus config
|
||||
--explain` lists every resolved layer source.
|
||||
|
||||
> Global CSS bundling, the `--wire-*` design-token theme system, and the `wrnexus.config.ts` app-config loader for WrNexus apps.
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/styles",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/styles"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/styles",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"styles"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,11 +37,12 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/uploader": "^0.7.0",
|
||||
"@wrnexus/core": "^0.7.0",
|
||||
"@wrnexus/plugin": "^0.7.0"
|
||||
"@wrnexus/uploader": "^0.8.5",
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/syntax",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/syntax — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/syntax"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/syntax",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"syntax"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -38,9 +54,14 @@
|
||||
"./spec": {
|
||||
"types": "./dist/spec.d.ts",
|
||||
"import": "./dist/spec.js"
|
||||
},
|
||||
"./formatter": {
|
||||
"types": "./dist/formatter.d.ts",
|
||||
"import": "./dist/formatter.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -103,6 +103,22 @@ Remember to `await app.close()` when done.
|
||||
|
||||
## Usage
|
||||
|
||||
The CLI supports focused suites by file or directory convention:
|
||||
|
||||
```bash
|
||||
wrnexus test unit # *.unit.test.ts or test/unit/**
|
||||
wrnexus test component # *.component.test.ts or test/component/**
|
||||
wrnexus test api # *.api.test.ts or test/api/**
|
||||
wrnexus test accessibility # *.a11y.test.ts / *.accessibility.test.ts
|
||||
wrnexus test performance # *.performance.test.ts / *.benchmark.test.ts
|
||||
wrnexus test browser # Playwright project when configured
|
||||
wrnexus test visual # Playwright tests tagged @visual
|
||||
```
|
||||
|
||||
Pass the application directory after the level, for example
|
||||
`wrnexus test component examples/basic-app`. A focused command fails clearly when no matching
|
||||
suite exists instead of silently running unrelated tests.
|
||||
|
||||
```ts
|
||||
import { test, expect, renderComponent, mountHtml, createHarness } from "@wrnexus/test";
|
||||
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/test",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/test — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/test"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/test",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"test"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,6 +37,7 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/tracking",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/tracking"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/tracking",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"tracking"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -21,6 +37,7 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
|
||||
+16
-16
@@ -359,6 +359,15 @@ Reusable preference switcher component.
|
||||
- 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[])`
|
||||
|
||||
### Toaster
|
||||
|
||||
Reusable toaster component.
|
||||
|
||||
- Mount: `data-component="Toaster"`
|
||||
- Props: `color: string = "info"`, `size: string = "default"`, `position: string = "bottom-right"`, `duration: number = 4500`, `max: number = 4`, `pauseOnHover: boolean = true`, `showIcon: boolean = true`, `successIcon: string = ""`, `dangerIcon: string = ""`, `warningIcon: string = ""`, `infoIcon: string = ""`, `closable: boolean = true`, `showProgress: boolean = true`, `closeLabel: string = "Dismiss notification"`, `class: string = ""`
|
||||
- Slots: None
|
||||
- Outputs: `show({ id: number; message: string; tone: string })`, `dismiss({ id: number; reason: string })`, `action({ id: number; sourceEvent: Event })`
|
||||
|
||||
## Data
|
||||
|
||||
### MetricCard
|
||||
@@ -554,15 +563,6 @@ Theme-aware, responsive data map component.
|
||||
- Slots: `default`
|
||||
- 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)`, `change({ 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)`
|
||||
|
||||
### DataTable
|
||||
|
||||
Theme-aware, responsive data table component.
|
||||
|
||||
- Mount: `data-component="DataTable"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `caption: string = "Data Table"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `striped: boolean = true`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `sort({ 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)`, `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)`, `change({ 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)`, `rowClick({ 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)`, `pageChange({ 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)`
|
||||
|
||||
### DragAndDrop
|
||||
|
||||
Theme-aware, responsive drag and drop component.
|
||||
@@ -945,7 +945,7 @@ Open an accessible keyboard-aware action menu from pointer or keyboard context i
|
||||
Present responsive modal side or bottom content with focus management, backdrop behavior, slots, and close events.
|
||||
|
||||
- Mount: `data-component="Drawer"`
|
||||
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `placement: string = "right"`, `size: string = "md"`, `color: string = "primary"`, `variant: string = "default"`, `title: string = "Drawer"`, `description: string = ""`, `icon: string = ""`, `label: string = "Drawer"`, `closeLabel: string = "Close drawer"`, `showClose: boolean = true`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `overlay: boolean = true`, `scrollable: boolean = true`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `class: string = ""`
|
||||
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `placement: string = "right"`, `size: string = "md"`, `color: string = "primary"`, `variant: string = "default"`, `title: string = "Drawer"`, `description: string = ""`, `icon: string = ""`, `label: string = "Drawer"`, `closeLabel: string = "Close drawer"`, `showClose: boolean = true`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `duration: number = 260`, `overlay: boolean = true`, `scrollable: boolean = true`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `class: string = ""`
|
||||
- Slots: `trigger`, `header`, `default`, `footer`
|
||||
- Outputs: `open({ placement: string; sourceEvent: Event })`, `close({ reason: string; placement: string; sourceEvent: Event })`, `cancel({ placement: string; sourceEvent: Event })`
|
||||
|
||||
@@ -963,7 +963,7 @@ Open an accessible anchored menu with keyboard navigation, item selection, actio
|
||||
Present an accessible modal dialog with focus management, confirmation, cancellation, slots, and responsive sizing.
|
||||
|
||||
- Mount: `data-component="Modal"`
|
||||
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `title: string = "Modal"`, `description: string = ""`, `icon: string = ""`, `label: string = "Modal dialog"`, `size: string = "md"`, `placement: string = "center"`, `color: string = "primary"`, `variant: string = "default"`, `showClose: boolean = true`, `closeLabel: string = "Close modal"`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `closeOnCancel: boolean = true`, `closeOnConfirm: boolean = false`, `showFooter: boolean = true`, `cancelLabel: string = "Cancel"`, `cancelIcon: string = ""`, `confirmLabel: string = "Confirm"`, `confirmIcon: string = ""`, `confirmDisabled: boolean = false`, `confirmLoading: boolean = false`, `destructive: boolean = false`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `scrollable: boolean = true`, `class: string = ""`
|
||||
- Props: `open: boolean = false`, `defaultOpen: boolean = false`, `title: string = "Modal"`, `description: string = ""`, `icon: string = ""`, `label: string = "Modal dialog"`, `size: string = "md"`, `placement: string = "center"`, `color: string = "primary"`, `variant: string = "default"`, `showClose: boolean = true`, `closeLabel: string = "Close modal"`, `closeOnBackdrop: boolean = true`, `closeOnEscape: boolean = true`, `closeOnCancel: boolean = true`, `closeOnConfirm: boolean = false`, `showFooter: boolean = true`, `cancelLabel: string = "Cancel"`, `cancelIcon: string = ""`, `confirmLabel: string = "Confirm"`, `confirmIcon: string = ""`, `confirmDisabled: boolean = false`, `confirmLoading: boolean = false`, `destructive: boolean = false`, `triggerLabel: string = ""`, `triggerIcon: string = ""`, `scrollable: boolean = true`, `scrollBehavior: string = "inside"`, `class: string = ""`
|
||||
- Slots: `trigger`, `header`, `default`, `footer`
|
||||
- Outputs: `open({ sourceEvent: Event })`, `close({ reason: string; sourceEvent: Event })`, `cancel({ sourceEvent: Event })`, `confirm({ sourceEvent: Event })`
|
||||
|
||||
@@ -987,11 +987,11 @@ Show concise accessible contextual help on hover, focus, click, or controlled op
|
||||
|
||||
## Tables
|
||||
|
||||
### Table
|
||||
### DataTable
|
||||
|
||||
Theme-aware, responsive table component.
|
||||
Sortable, filterable, paginated data table with row selection.
|
||||
|
||||
- Mount: `data-component="Table"`
|
||||
- Props: `size: string = "default"`, `color: string = "primary"`, `caption: string = "Table"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `striped: boolean = true`, `class: string = ""`
|
||||
- Mount: `data-component="DataTable"`
|
||||
- Props: `color: string = "primary"`, `size: string = "default"`, `columns: unknown[] = []`, `rows: unknown[] = []`, `rowKey: string = "id"`, `remote: boolean = false`, `loadingLabel: string = "Loading"`, `errorLabel: string = "Could not load this data"`, `retryLabel: string = "Try again"`, `caption: string = ""`, `description: string = ""`, `searchable: boolean = true`, `searchPlaceholder: string = "Search"`, `paginated: boolean = true`, `pageSize: number = 10`, `paginationStyle: string = "compact"`, `pageSizes: number[] = [10, 25, 50]`, `selectable: boolean = false`, `actions: unknown[] = []`, `striped: boolean = true`, `bordered: boolean = true`, `gridlines: string = "rows"`, `density: string = "default"`, `emptyLabel: string = "No records to show"`, `noResultsLabel: string = "No records match your search"`, `clearSearchLabel: string = "Clear search"`, `stickyFirstColumn: boolean = false`, `layout: string = "rows"`, `class: string = ""`
|
||||
- Slots: `default`
|
||||
- Outputs: `sort({ 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)`, `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)`, `rowClick({ 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: `sort({ key: string; direction: string })`, `search({ query: string })`, `pageChange({ page: number; pageSize: number })`, `select({ selected: Array<string | number>; all: boolean })`, `change({ page: number; pageSize: number; total: number; query: string; sortKey: string; sortDirection: string })`, `rowClick({ row: object; sourceEvent: Event })`, `action({ id: string; selected: Array<string | number>; rows: object[]; sourceEvent: Event })`, `request({ instanceId: number; page: number; pageSize: number; sortKey: string; sortDirection: string; query: string })`
|
||||
|
||||
@@ -168,8 +168,8 @@
|
||||
},
|
||||
{
|
||||
"name": "DataTable",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive data table component."
|
||||
"category": "tables",
|
||||
"purpose": "Sortable, filterable, paginated data table with row selection."
|
||||
},
|
||||
{
|
||||
"name": "DatePicker",
|
||||
@@ -471,11 +471,6 @@
|
||||
"category": "forms",
|
||||
"purpose": "Theme-aware, responsive switch component."
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
"category": "tables",
|
||||
"purpose": "Theme-aware, responsive table component."
|
||||
},
|
||||
{
|
||||
"name": "Tabs",
|
||||
"category": "navigation",
|
||||
@@ -511,6 +506,11 @@
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive toast notifications component."
|
||||
},
|
||||
{
|
||||
"name": "Toaster",
|
||||
"category": "core",
|
||||
"purpose": "Reusable toaster component."
|
||||
},
|
||||
{
|
||||
"name": "ToggleCount",
|
||||
"category": "advanced-forms",
|
||||
|
||||
@@ -4569,16 +4569,9 @@
|
||||
{
|
||||
"name": "DataTable",
|
||||
"mount": "DataTable",
|
||||
"category": "integrations",
|
||||
"purpose": "Theme-aware, responsive data table component.",
|
||||
"category": "tables",
|
||||
"purpose": "Sortable, filterable, paginated data table with row selection.",
|
||||
"props": [
|
||||
{
|
||||
"name": "size",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"type": "string",
|
||||
@@ -4587,10 +4580,10 @@
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "caption",
|
||||
"name": "size",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Data Table\"",
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
@@ -4607,6 +4600,111 @@
|
||||
"default": "[]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "rowKey",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"id\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "remote",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "loadingLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Loading\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "errorLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Could not load this data\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "retryLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Try again\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "caption",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "searchable",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "searchPlaceholder",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Search\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "paginated",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "pageSize",
|
||||
"type": "number",
|
||||
"required": false,
|
||||
"default": "10",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "paginationStyle",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"compact\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "pageSizes",
|
||||
"type": "number[]",
|
||||
"required": false,
|
||||
"default": "[10, 25, 50]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "selectable",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "actions",
|
||||
"type": "unknown[]",
|
||||
"required": false,
|
||||
"default": "[]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "striped",
|
||||
"type": "boolean",
|
||||
@@ -4614,6 +4712,62 @@
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "bordered",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "gridlines",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"rows\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "density",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "emptyLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"No records to show\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "noResultsLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"No records match your search\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "clearSearchLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Clear search\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "stickyFirstColumn",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "false",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "layout",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"rows\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
@@ -4626,26 +4780,47 @@
|
||||
"outputs": [
|
||||
{
|
||||
"name": "sort",
|
||||
"payloadType": "{ 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"
|
||||
"payloadType": "{ key: string; direction: string }"
|
||||
},
|
||||
{
|
||||
"name": "select",
|
||||
"payloadType": "{ 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"
|
||||
},
|
||||
{
|
||||
"name": "change",
|
||||
"payloadType": "{ 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"
|
||||
},
|
||||
{
|
||||
"name": "rowClick",
|
||||
"payloadType": "{ 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"
|
||||
"name": "search",
|
||||
"payloadType": "{ query: string }"
|
||||
},
|
||||
{
|
||||
"name": "pageChange",
|
||||
"payloadType": "{ 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"
|
||||
"payloadType": "{ page: number; pageSize: number }"
|
||||
},
|
||||
{
|
||||
"name": "select",
|
||||
"payloadType": "{ selected: Array<string | number>; all: boolean }"
|
||||
},
|
||||
{
|
||||
"name": "change",
|
||||
"payloadType": "{ page: number; pageSize: number; total: number; query: string; sortKey: string; sortDirection: string }"
|
||||
},
|
||||
{
|
||||
"name": "rowClick",
|
||||
"payloadType": "{ row: object; sourceEvent: Event }"
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"payloadType": "{ id: string; selected: Array<string | number>; rows: object[]; sourceEvent: Event }"
|
||||
},
|
||||
{
|
||||
"name": "request",
|
||||
"payloadType": "{ instanceId: number; page: number; pageSize: number; sortKey: string; sortDirection: string; query: string }"
|
||||
}
|
||||
],
|
||||
"events": ["sort", "select", "change", "rowClick", "pageChange"],
|
||||
"events": [
|
||||
"sort",
|
||||
"search",
|
||||
"pageChange",
|
||||
"select",
|
||||
"change",
|
||||
"rowClick",
|
||||
"action",
|
||||
"request"
|
||||
],
|
||||
"source": "components/DataTable.wrn"
|
||||
},
|
||||
{
|
||||
@@ -5220,6 +5395,13 @@
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "number",
|
||||
"required": false,
|
||||
"default": "260",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "overlay",
|
||||
"type": "boolean",
|
||||
@@ -8736,6 +8918,13 @@
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "scrollBehavior",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"inside\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
@@ -12009,80 +12198,6 @@
|
||||
"events": ["input", "change", "focus", "blur"],
|
||||
"source": "components/Switch.wrn"
|
||||
},
|
||||
{
|
||||
"name": "Table",
|
||||
"mount": "Table",
|
||||
"category": "tables",
|
||||
"purpose": "Theme-aware, responsive table component.",
|
||||
"props": [
|
||||
{
|
||||
"name": "size",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "color",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"primary\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "caption",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Table\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "columns",
|
||||
"type": "unknown[]",
|
||||
"required": false,
|
||||
"default": "[]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "rows",
|
||||
"type": "unknown[]",
|
||||
"required": false,
|
||||
"default": "[]",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "striped",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"slots": ["default"],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "sort",
|
||||
"payloadType": "{ 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"
|
||||
},
|
||||
{
|
||||
"name": "select",
|
||||
"payloadType": "{ 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"
|
||||
},
|
||||
{
|
||||
"name": "rowClick",
|
||||
"payloadType": "{ 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"
|
||||
}
|
||||
],
|
||||
"events": ["sort", "select", "rowClick"],
|
||||
"source": "components/table.wrn"
|
||||
},
|
||||
{
|
||||
"name": "Tabs",
|
||||
"mount": "Tabs",
|
||||
@@ -12893,6 +13008,136 @@
|
||||
"events": ["add", "dismiss", "clear", "action"],
|
||||
"source": "components/ToastNotifications.wrn"
|
||||
},
|
||||
{
|
||||
"name": "Toaster",
|
||||
"mount": "Toaster",
|
||||
"category": "core",
|
||||
"purpose": "Reusable toaster component.",
|
||||
"props": [
|
||||
{
|
||||
"name": "color",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"info\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"default\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "position",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"bottom-right\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"type": "number",
|
||||
"required": false,
|
||||
"default": "4500",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"type": "number",
|
||||
"required": false,
|
||||
"default": "4",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "pauseOnHover",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "showIcon",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "successIcon",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "dangerIcon",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "warningIcon",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "infoIcon",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "closable",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "showProgress",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"default": "true",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "closeLabel",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"Dismiss notification\"",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"name": "class",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"default": "\"\"",
|
||||
"options": []
|
||||
}
|
||||
],
|
||||
"slots": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "show",
|
||||
"payloadType": "{ id: number; message: string; tone: string }"
|
||||
},
|
||||
{
|
||||
"name": "dismiss",
|
||||
"payloadType": "{ id: number; reason: string }"
|
||||
},
|
||||
{
|
||||
"name": "action",
|
||||
"payloadType": "{ id: number; sourceEvent: Event }"
|
||||
}
|
||||
],
|
||||
"events": ["show", "dismiss", "action"],
|
||||
"source": "components/Toaster.wrn"
|
||||
},
|
||||
{
|
||||
"name": "ToggleCount",
|
||||
"mount": "ToggleCount",
|
||||
|
||||
@@ -45,8 +45,12 @@ component ContextMenu {
|
||||
sourceEvent.preventDefault()
|
||||
}
|
||||
if (placement === "pointer" && sourceEvent) {
|
||||
positionX = Math.max(12, Math.min(sourceEvent.clientX || 12, window.innerWidth - 340))
|
||||
positionY = Math.max(12, Math.min(sourceEvent.clientY || 12, window.innerHeight - 420))
|
||||
// Place the menu at the pointer and let the anchored clamp in the
|
||||
// runtime pull it back on screen once it has been laid out and can
|
||||
// actually be measured. Subtracting a guessed 340x420 here instead
|
||||
// pushed every menu that was not that size away from the pointer.
|
||||
positionX = Math.max(12, sourceEvent.clientX || 12)
|
||||
positionY = Math.max(12, sourceEvent.clientY || 12)
|
||||
}
|
||||
visible = true
|
||||
output.open({
|
||||
@@ -161,6 +165,7 @@ component ContextMenu {
|
||||
|
||||
<div
|
||||
class="wire-context-menu__panel"
|
||||
data-wrn-anchored="true"
|
||||
data-show='{open || visible}'
|
||||
role="menu"
|
||||
aria-label='{label}'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,8 @@ open: boolean = false
|
||||
showClose: boolean = true
|
||||
closeOnBackdrop: boolean = true
|
||||
closeOnEscape: boolean = true
|
||||
// Open/close animation length in ms. 0 disables the animation entirely.
|
||||
duration: number = 260
|
||||
overlay: boolean = true
|
||||
scrollable: boolean = true
|
||||
triggerLabel: string = ""
|
||||
@@ -79,7 +81,9 @@ open: boolean = false
|
||||
data-overlay='{overlay ? "true" : "false"}'
|
||||
data-scrollable='{scrollable ? "true" : "false"}'
|
||||
class='wire-drawer {class}'
|
||||
style='--drawer-duration: {duration}ms'
|
||||
>
|
||||
|
||||
{#if triggerLabel}
|
||||
<button
|
||||
type="button"
|
||||
@@ -101,7 +105,6 @@ open: boolean = false
|
||||
|
||||
<div
|
||||
class="wire-drawer__layer"
|
||||
data-show='{open || visible}'
|
||||
role="presentation"
|
||||
@keydown='handleKeydown(event)'
|
||||
>
|
||||
@@ -218,12 +221,32 @@ open: boolean = false
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
* The layer stays in the layout and is revealed by [data-open]; it used to
|
||||
* be toggled with data-show, which sets display:none, and display cannot
|
||||
* be transitioned -- the drawer simply snapped in and out. visibility is
|
||||
* delayed by the duration on the way out so the panel can finish sliding
|
||||
* before the layer is taken out of the hit-testing tree.
|
||||
*/
|
||||
.wire-drawer__layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1200;
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity var(--drawer-duration, 260ms) ease,
|
||||
visibility 0s linear var(--drawer-duration, 260ms);
|
||||
}
|
||||
|
||||
.wire-drawer[data-open="true"] .wire-drawer__layer {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition:
|
||||
opacity var(--drawer-duration, 260ms) ease,
|
||||
visibility 0s linear 0s;
|
||||
}
|
||||
|
||||
.wire-drawer__backdrop {
|
||||
@@ -262,6 +285,38 @@ open: boolean = false
|
||||
box-shadow: -28px 0 80px color-mix(in srgb, black 28%, transparent);
|
||||
pointer-events: auto;
|
||||
overflow: hidden;
|
||||
/* Slides in from whichever edge the placement puts it on. */
|
||||
transform: translateX(100%);
|
||||
transition: transform var(--drawer-duration, 260ms) cubic-bezier(0.32, 0.72, 0, 1);
|
||||
}
|
||||
|
||||
.wire-drawer[data-open="true"] .wire-drawer__panel {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.wire-drawer[data-placement="left"] .wire-drawer__panel {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.wire-drawer[data-placement="top"] .wire-drawer__panel {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.wire-drawer[data-placement="bottom"] .wire-drawer__panel {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.wire-drawer[data-open="true"][data-placement="left"] .wire-drawer__panel,
|
||||
.wire-drawer[data-open="true"][data-placement="top"] .wire-drawer__panel,
|
||||
.wire-drawer[data-open="true"][data-placement="bottom"] .wire-drawer__panel {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wire-drawer__layer,
|
||||
.wire-drawer__panel {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.wire-drawer[data-size="sm"] .wire-drawer__panel {
|
||||
@@ -395,12 +450,19 @@ open: boolean = false
|
||||
color: color-mix(in srgb, currentColor 76%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* padding is reset explicitly: an app-level `button { padding: ... }` rule
|
||||
* outranks the browser default and leaves this fixed-size button with a
|
||||
* content box of a couple of pixels, which squeezes the icon to a sliver
|
||||
* and reads as "the close button has no icon". Same trap as Modal.
|
||||
*/
|
||||
.wire-drawer__close {
|
||||
appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
padding: 0;
|
||||
width: 2.35rem;
|
||||
height: 2.35rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
@@ -417,10 +479,30 @@ open: boolean = false
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Never let the glyph be shrunk by the flex container. */
|
||||
.wire-drawer__close svg {
|
||||
flex: 0 0 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Slot content is authored by the host app, so the app global stylesheet
|
||||
* styles it too. A bare element selector there (p { color: ... }) beats
|
||||
* anything the panel merely *inherits*, which is how modal body copy ended
|
||||
* up muted grey on a saturated background. State the colour explicitly;
|
||||
* :where() keeps the specificity low enough that any class the app puts on
|
||||
* its own slot content still wins.
|
||||
*/
|
||||
.wire-drawer__body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 1.35rem;
|
||||
color: var(--wire-color-text);
|
||||
}
|
||||
|
||||
.wire-drawer__body :where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, span, label, code) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.wire-drawer[data-scrollable="true"] .wire-drawer__body {
|
||||
|
||||
@@ -171,6 +171,7 @@ items: unknown[] = []
|
||||
|
||||
<div
|
||||
class="wire-dropdown__panel"
|
||||
data-wrn-anchored="true"
|
||||
data-show='{open || visible}'
|
||||
role="menu"
|
||||
aria-label='{menuLabel}'
|
||||
|
||||
@@ -35,6 +35,7 @@ component Modal {
|
||||
triggerLabel: string = ""
|
||||
triggerIcon: string = ""
|
||||
scrollable: boolean = true
|
||||
scrollBehavior: string = "inside"
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
@@ -50,6 +51,16 @@ component Modal {
|
||||
output.open({ sourceEvent: sourceEvent })
|
||||
}
|
||||
|
||||
// Slot content can close the modal it sits in by dispatching a bubbling
|
||||
// wrnexus:modal:close event, e.g. from a form success handler:
|
||||
//
|
||||
// event.target.dispatchEvent(
|
||||
// new CustomEvent("wrnexus:modal:close", { bubbles: true })
|
||||
// )
|
||||
//
|
||||
// The listener is on the modal root, so the event only ever closes the
|
||||
// modal the dispatching element is actually inside -- no ids to wire up
|
||||
// and no way to close somebody else's modal by accident.
|
||||
client function hideModal(reason, sourceEvent) {
|
||||
visible = false
|
||||
output.close({
|
||||
@@ -87,21 +98,23 @@ component Modal {
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Modal"
|
||||
data-open='{open || visible ? "true" : "false"}'
|
||||
data-open='{isOpen() ? "true" : "false"}'
|
||||
data-size='{size}'
|
||||
data-placement='{placement}'
|
||||
data-color='{color}'
|
||||
data-variant='{variant}'
|
||||
data-scrollable='{scrollable ? "true" : "false"}'
|
||||
data-scroll='{scrollBehavior}'
|
||||
data-destructive='{destructive ? "true" : "false"}'
|
||||
class='wire-modal {class}'
|
||||
@wrnexus:modal:close='hideModal("api", event)'
|
||||
>
|
||||
{#if triggerLabel}
|
||||
<button
|
||||
type="button"
|
||||
class="wire-modal__trigger"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded='{open || visible ? "true" : "false"}'
|
||||
aria-expanded='{isOpen() ? "true" : "false"}'
|
||||
@click='showModal(event)'
|
||||
>
|
||||
{#if triggerIcon}
|
||||
@@ -127,7 +140,7 @@ component Modal {
|
||||
|
||||
<div
|
||||
class="wire-modal__layer"
|
||||
data-show='{open || visible}'
|
||||
data-show="isOpen()"
|
||||
role="presentation"
|
||||
@keydown='handleKeydown(event)'
|
||||
>
|
||||
@@ -183,11 +196,20 @@ component Modal {
|
||||
aria-label='{closeLabel}'
|
||||
@click='hideModal("close-button", event)'
|
||||
>
|
||||
<span
|
||||
class="icon-[lucide--x]"
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</span>
|
||||
<path d="M18 6 6 18" />
|
||||
<path d="M6 6 18 18" />
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</header>
|
||||
@@ -277,16 +299,26 @@ component Modal {
|
||||
--modal-contrast: var(--wire-color-secondary-contrast);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallbacks below (the second var() argument): the theme token generator
|
||||
* (packages/styles/src/theme.ts) only emits a real -contrast token for
|
||||
* primary and secondary. info, success, and danger have no contrast
|
||||
* token defined at all, so var(--wire-color-info-contrast) with no
|
||||
* fallback resolves to nothing, and --modal-contrast becomes invalid --
|
||||
* which made confirm-button and solid-panel text unreadable. White is a
|
||||
* safe default against these saturated colors until the theme package
|
||||
* defines real tokens for them.
|
||||
*/
|
||||
.wire-modal[data-color="info"] {
|
||||
--modal-accent: var(--wire-color-info);
|
||||
--modal-soft: var(--wire-color-info-soft);
|
||||
--modal-contrast: var(--wire-color-info-contrast);
|
||||
--modal-contrast: var(--wire-color-info-contrast, white);
|
||||
}
|
||||
|
||||
.wire-modal[data-color="success"] {
|
||||
--modal-accent: var(--wire-color-success);
|
||||
--modal-soft: var(--wire-color-success-soft);
|
||||
--modal-contrast: var(--wire-color-success-contrast);
|
||||
--modal-contrast: var(--wire-color-success-contrast, white);
|
||||
}
|
||||
|
||||
.wire-modal[data-color="warning"] {
|
||||
@@ -299,7 +331,7 @@ component Modal {
|
||||
.wire-modal[data-destructive="true"] {
|
||||
--modal-accent: var(--wire-color-danger);
|
||||
--modal-soft: var(--wire-color-danger-soft);
|
||||
--modal-contrast: var(--wire-color-on-danger);
|
||||
--modal-contrast: var(--wire-color-on-danger, white);
|
||||
}
|
||||
|
||||
.wire-modal__trigger,
|
||||
@@ -321,8 +353,35 @@ component Modal {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
transition: opacity 150ms ease, transform 150ms ease, box-shadow 150ms ease;
|
||||
}
|
||||
|
||||
.wire-modal__trigger:hover {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.wire-modal__trigger:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.wire-modal__trigger:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--modal-accent) 40%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* Hidden by default so the SSR-rendered HTML never paints the layer before
|
||||
* hydration runs. data-open on the wire-modal root is evaluated and
|
||||
* serialized to a real true or false string at render time -- a plain
|
||||
* bind, not the raw-expression data-show directive -- so this selector
|
||||
* is correct on first paint with zero flash, with no dependency on
|
||||
* client JS having run yet. The data-show attribute and client directive
|
||||
* still run after hydration to keep things in sync for state changes,
|
||||
* but visibility itself is driven by CSS here. visibility (rather than
|
||||
* display) is used so the open and close transitions below can actually
|
||||
* animate -- a box that starts at display: none has no prior frame to
|
||||
* transition from.
|
||||
*/
|
||||
.wire-modal__layer {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -331,6 +390,15 @@ component Modal {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 180ms ease, visibility 0s linear 180ms;
|
||||
}
|
||||
|
||||
.wire-modal[data-open="true"] .wire-modal__layer {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity 180ms ease, visibility 0s linear 0s;
|
||||
}
|
||||
|
||||
.wire-modal[data-placement="top"] .wire-modal__layer {
|
||||
@@ -338,6 +406,12 @@ component Modal {
|
||||
padding-top: clamp(1rem, 8vh, 5rem);
|
||||
}
|
||||
|
||||
.wire-modal[data-scroll="page"] .wire-modal__layer {
|
||||
align-items: flex-start;
|
||||
overflow-y: auto;
|
||||
padding: 2.5rem 1rem;
|
||||
}
|
||||
|
||||
.wire-modal__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -370,6 +444,18 @@ component Modal {
|
||||
0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
|
||||
0 40px 110px color-mix(in srgb, black 38%, transparent);
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transform: scale(0.96) translateY(10px);
|
||||
transition: opacity 180ms ease, transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.wire-modal[data-open="true"] .wire-modal__panel {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.wire-modal[data-size="xs"] .wire-modal__panel {
|
||||
width: min(19rem, calc(100vw - 2rem));
|
||||
}
|
||||
|
||||
.wire-modal[data-size="sm"] .wire-modal__panel {
|
||||
@@ -390,6 +476,10 @@ component Modal {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.wire-modal[data-scroll="page"] .wire-modal__panel {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.wire-modal[data-variant="soft"] .wire-modal__panel {
|
||||
background:
|
||||
linear-gradient(145deg, var(--modal-soft), transparent 68%),
|
||||
@@ -447,6 +537,7 @@ component Modal {
|
||||
}
|
||||
|
||||
.wire-modal__heading-copy h2 {
|
||||
color: inherit;
|
||||
font-size: 1.08rem;
|
||||
font-weight: 650;
|
||||
line-height: 1.3;
|
||||
@@ -462,32 +553,100 @@ component Modal {
|
||||
color: color-mix(in srgb, currentColor 76%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* padding is reset explicitly: an app-level `button { padding: … }` rule
|
||||
* outranks the browser default, and 1rem of horizontal padding left this
|
||||
* 2.15rem button with a ~2px content box -- which squeezed the icon to
|
||||
* 0.4px wide and read as "the close button has no icon".
|
||||
*/
|
||||
.wire-modal__close {
|
||||
appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: 2.35rem;
|
||||
height: 2.35rem;
|
||||
padding: 0;
|
||||
width: 2.15rem;
|
||||
height: 2.15rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
background: var(--wire-color-surface-soft);
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: 0.75rem;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease, color 150ms ease, border-color 150ms ease, transform 150ms ease;
|
||||
}
|
||||
|
||||
/*
|
||||
* On a solid panel the background is the accent color, so the muted-grey
|
||||
* default is close to invisible -- the dismiss affordance reads as
|
||||
* missing rather than subtle. Derive it from the panel contrast color
|
||||
* instead, and give it a faint ring so it is unmistakably a control.
|
||||
*/
|
||||
.wire-modal[data-variant="solid"] .wire-modal__close {
|
||||
color: color-mix(in srgb, currentColor 82%, transparent);
|
||||
border-color: color-mix(in srgb, currentColor 35%, transparent);
|
||||
}
|
||||
|
||||
.wire-modal[data-variant="solid"] .wire-modal__close:hover {
|
||||
color: currentColor;
|
||||
background: color-mix(in srgb, black 18%, transparent);
|
||||
border-color: color-mix(in srgb, currentColor 55%, transparent);
|
||||
}
|
||||
|
||||
.wire-modal__close:hover {
|
||||
color: var(--wire-color-text);
|
||||
background: var(--wire-color-surface-soft);
|
||||
}
|
||||
|
||||
.wire-modal__close:hover,
|
||||
.wire-modal__close:focus-visible {
|
||||
color: var(--modal-accent);
|
||||
border-color: color-mix(in srgb, var(--modal-accent) 34%, var(--wire-color-border));
|
||||
border-color: color-mix(in srgb, var(--modal-accent) 45%, transparent);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.wire-modal__close:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
/* Never let the glyph be shrunk by the flex container. */
|
||||
.wire-modal__close svg {
|
||||
flex: 0 0 auto;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Slot content is authored by the host app, so the app global stylesheet
|
||||
* styles it too. A bare element selector there (p { color: ... }) beats
|
||||
* anything the panel merely *inherits*, which is how solid-variant modals
|
||||
* ended up with muted grey body copy on a saturated accent background --
|
||||
* unreadable, and worst exactly where contrast matters most (the
|
||||
* destructive confirm). Setting the color on the body makes the panel
|
||||
* choice explicit instead of leaving it to inheritance.
|
||||
*
|
||||
* NOTE: apostrophes are avoided in .wrn style comments on purpose -- the
|
||||
* block scanner treats a quote as a string delimiter while it counts
|
||||
* braces, so a stray one breaks parsing of the whole component.
|
||||
*/
|
||||
.wire-modal__body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
padding: 1.4rem;
|
||||
color: var(--wire-color-text);
|
||||
}
|
||||
|
||||
/*
|
||||
* :where() keeps this at the specificity of .wire-modal__body alone, so it
|
||||
* outranks a global element selector but still yields to any class the app
|
||||
* puts on its own slot content (an error message, a muted caption). A
|
||||
* plain .wire-modal__body p list would have quietly overridden those.
|
||||
*/
|
||||
.wire-modal__body :where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, span, label, code) {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.wire-modal[data-variant="solid"] .wire-modal__body {
|
||||
color: var(--modal-contrast);
|
||||
}
|
||||
|
||||
.wire-modal[data-scrollable="true"] .wire-modal__body {
|
||||
@@ -548,6 +707,18 @@ component Modal {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
* On a solid-variant panel the panel background is also --modal-accent,
|
||||
* so a plain primary button (same color) has no visible edge against it.
|
||||
* Darken the fill slightly and add a light border so the button still
|
||||
* reads as a distinct, clickable pill instead of blending into the panel.
|
||||
*/
|
||||
.wire-modal[data-variant="solid"] .wire-modal__button--primary {
|
||||
background: color-mix(in srgb, black 18%, var(--modal-accent));
|
||||
border-color: color-mix(in srgb, white 32%, transparent);
|
||||
box-shadow: 0 1px 0 color-mix(in srgb, white 12%, transparent) inset;
|
||||
}
|
||||
|
||||
.wire-modal__button:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
@@ -575,6 +746,7 @@ component Modal {
|
||||
}
|
||||
|
||||
.wire-modal__panel,
|
||||
.wire-modal[data-size="xs"] .wire-modal__panel,
|
||||
.wire-modal[data-size="sm"] .wire-modal__panel,
|
||||
.wire-modal[data-size="lg"] .wire-modal__panel,
|
||||
.wire-modal[data-size="xl"] .wire-modal__panel {
|
||||
@@ -599,10 +771,17 @@ component Modal {
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wire-modal__button,
|
||||
.wire-modal__spinner {
|
||||
.wire-modal__spinner,
|
||||
.wire-modal__layer,
|
||||
.wire-modal__panel,
|
||||
.wire-modal__close {
|
||||
animation: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.wire-modal__panel {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,12 +123,13 @@ component Popover {
|
||||
|
||||
<section
|
||||
class="wire-popover__panel"
|
||||
data-wrn-anchored="true"
|
||||
data-show='{open || visible}'
|
||||
role="dialog"
|
||||
aria-label='{title || triggerLabel}'
|
||||
>
|
||||
{#if showArrow}
|
||||
<span class="wire-popover__arrow" aria-hidden="true"></span>
|
||||
<span class="wire-popover__arrow" data-wrn-anchor-arrow="true" aria-hidden="true"></span>
|
||||
{/if}
|
||||
|
||||
{#if title || description || icon || showClose}
|
||||
@@ -471,7 +472,13 @@ component Popover {
|
||||
color: color-mix(in srgb, currentColor 76%, transparent);
|
||||
}
|
||||
|
||||
/*
|
||||
* padding is reset explicitly: an app-level `button { padding: ... }` rule
|
||||
* outranks the browser default and crushes the icon inside this
|
||||
* fixed-size button. Same trap as Modal and Drawer.
|
||||
*/
|
||||
.wire-popover__close {
|
||||
padding: 0;
|
||||
appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
component Table {
|
||||
outputs {
|
||||
sort(payload: { 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)
|
||||
select(payload: { 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)
|
||||
rowClick(payload: { 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)
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
caption: string = "Table"
|
||||
columns: unknown[] = []
|
||||
rows: unknown[] = []
|
||||
striped: boolean = true
|
||||
class: string = ""
|
||||
}
|
||||
view {
|
||||
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--table {class}"><table><caption>{caption}</caption><thead><tr>{#each columns as column}<th>{column.label}</th>{/each}</tr></thead><tbody>{#each rows as row}<tr>{#each columns as column}<td>{row[column.key]}</td>{/each}</tr>{/each}</tbody></table><slot /></div>
|
||||
}
|
||||
}
|
||||
@@ -97,11 +97,12 @@ id: string = ""
|
||||
<span
|
||||
id='{id}'
|
||||
class="wire-tooltip__content"
|
||||
data-wrn-anchored="true"
|
||||
data-show='{open || visible}'
|
||||
role="tooltip"
|
||||
>
|
||||
{#if showArrow}
|
||||
<span class="wire-tooltip__arrow" aria-hidden="true"></span>
|
||||
<span class="wire-tooltip__arrow" data-wrn-anchor-arrow="true" aria-hidden="true"></span>
|
||||
{/if}
|
||||
|
||||
{#if title}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/ui",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/ui"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/ui",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"ui"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -19,6 +35,10 @@
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./registry": {
|
||||
"types": "./dist/registry.d.ts",
|
||||
"import": "./dist/registry.js"
|
||||
},
|
||||
"./components/*": "./components/*",
|
||||
"./component-catalog.json": "./component-catalog.json",
|
||||
"./component-migrations.json": "./component-migrations.json",
|
||||
@@ -26,10 +46,11 @@
|
||||
"./ui.css": "./ui.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.7.0"
|
||||
"@wrnexus/core": "^0.8.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"components",
|
||||
"ui.css",
|
||||
"component-catalog.json",
|
||||
|
||||
@@ -8037,28 +8037,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wire-next--table {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-md);
|
||||
}
|
||||
.wire-next--table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.wire-next--table caption,
|
||||
.wire-next--table th,
|
||||
.wire-next--table td {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid var(--wire-color-border);
|
||||
text-align: left;
|
||||
}
|
||||
.wire-next--table caption,
|
||||
.wire-next--table th {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@keyframes wire-shimmer {
|
||||
to {
|
||||
background-position: -200% 0;
|
||||
|
||||
@@ -108,6 +108,11 @@ export const GET = serveFromStore("docs"); // your middleware decides who gets i
|
||||
|
||||
## API
|
||||
|
||||
Uploads can participate in security and media pipelines without changing storage drivers. Pass a
|
||||
`scan` hook to reject malware/DLP findings before storage, and `afterStore` to enqueue image/video
|
||||
processing or indexing. If post-processing throws, WRNexus deletes the newly written object so a
|
||||
partially accepted upload is never left behind.
|
||||
|
||||
| Export | What |
|
||||
| --------------------------------------- | --------------------------------------------------------------- |
|
||||
| `handleUpload(opts)` | POST route handler → JSON `{ ok, files }` |
|
||||
@@ -123,3 +128,36 @@ export const GET = serveFromStore("docs"); // your middleware decides who gets i
|
||||
- SigV4 signing is implemented from scratch (no `@aws-sdk`); tested against local S3 semantics.
|
||||
Live AWS/R2 connectivity depends on your credentials + bucket policy.
|
||||
- v1 buffers each file in memory up to its size cap (fine for images/docs up to tens of MB).
|
||||
|
||||
## Helper and component kit
|
||||
|
||||
Use `formatFileSize`, `uploadAccept`, `uploadedFileMap`, `uploaderAttributes`, and `assertUploadedFiles` to keep upload forms and server validation consistent.
|
||||
|
||||
Enable `uploaderPlugin()` for:
|
||||
|
||||
- `<UploadDropzone />`
|
||||
- `<UploadStatus />`
|
||||
|
||||
The complete blocks compose `Card`, `Alert`, and `Badge` from `@wrnexus/ui`; the specialized upload runtime remains responsible for the native file input and secure transport behavior.
|
||||
Large files can use `createResumableUploadManager`. Sessions are bounded and
|
||||
expiring; chunks may arrive out of order, carry SHA-256 checksums, and are
|
||||
idempotent when retried. Conflicting retries reject, and the object is assembled
|
||||
only after every exact-sized chunk is present.
|
||||
|
||||
```ts
|
||||
const uploads = createResumableUploadManager({
|
||||
driver: getStore("documents").driver,
|
||||
sessions: redisUploadSessionStore,
|
||||
chunkSize: 5 * 1024 * 1024,
|
||||
maxBytes: 500 * 1024 * 1024,
|
||||
accept: ["application/pdf"],
|
||||
});
|
||||
|
||||
const session = await uploads.create({ name: "report.pdf", size, type });
|
||||
await uploads.uploadChunk(session.id, index, bytes, sha256);
|
||||
```
|
||||
|
||||
The included memory session store is intended for one-process apps and tests.
|
||||
Multi-instance production deployments should implement `ResumableSessionStore`
|
||||
with shared durable storage and atomic session updates, and periodically call
|
||||
`prune()` for abandoned uploads.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/uploader",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/uploader — part of the WrNexus framework.",
|
||||
"description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/uploader"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/uploader",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"uploader"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -18,12 +34,28 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"./plugin": {
|
||||
"types": "./dist/plugin.d.ts",
|
||||
"import": "./dist/plugin.js"
|
||||
},
|
||||
"./components/*": "./components/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.7.0"
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5"
|
||||
},
|
||||
"wrnexus": {
|
||||
"plugin": {
|
||||
"plugin": "./dist/plugin.js",
|
||||
"export": "default",
|
||||
"factory": true
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md",
|
||||
"components"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,31 @@
|
||||
|
||||
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
|
||||
|
||||
## Boundary contracts
|
||||
|
||||
Use `ContractRegistry` with `defineContract` or `defineEvent` to publish the
|
||||
same schema descriptors for APIs, actions, webhooks, realtime, queues, cron,
|
||||
pub/sub, plugins, configuration, and environment variables.
|
||||
|
||||
```ts
|
||||
import { ContractRegistry, defineEvent, v } from "@wrnexus/validation";
|
||||
|
||||
export const contracts = new ContractRegistry().register(
|
||||
defineEvent({
|
||||
name: "user.created",
|
||||
version: 1,
|
||||
consumers: ["notification-worker", "audit-service"],
|
||||
payload: v.object({ userId: v.string().uuid(), createdAt: v.string().date() }),
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
Export the registry from `app/contracts.ts`, then accept a baseline with
|
||||
`wrnexus contracts snapshot`. CI can run `wrnexus contracts check`; removed
|
||||
contracts/fields, required-field additions, type changes, narrowed enums, and
|
||||
tighter validation fail with stable `WRN-CONTRACT-*` diagnostics and list known
|
||||
consumers. A generated `wrnexus.contracts.json` can be used instead of a module.
|
||||
|
||||
## Overview
|
||||
|
||||
Define a schema once with the fluent `v` builder, then reuse it in three places: `.parse()` runs server-side and returns coerced values plus per-field errors; `.describe()` emits a plain-JSON `SchemaDescriptor` that the browser runtime interprets (no `eval`, no bundled validator); and helpers like `parseBody` and `parseEnv` wire schemas straight into API routes and startup config. The server rule logic (`applyRule`/`checkField`) and the client runtime (`VALIDATE_RUNTIME`) mirror each other exactly, so a form validates identically in both places. Schemas are conventionally kept in `app/schemas/`.
|
||||
@@ -178,3 +203,55 @@ const head = `<script>${renderSchemasScript({ signup: signupSchema.describe() })
|
||||
- Refinements (`refine`) run only server-side and are never serialized — client and server agree on every other rule because both interpret the same `RuleDescriptor` list.
|
||||
- No runtime dependencies. Ships as TypeScript source (`src/index.ts`) executed directly by Bun.
|
||||
- Pairs with the WrNexus server (`@wrnexus/core`) for route handlers and the SSR layer that injects `renderSchemasScript` / `VALIDATE_RUNTIME`.
|
||||
|
||||
## Helper and component kit
|
||||
|
||||
The public helper API includes `parseOrThrow`, `ValidationError`, `validationResponse`, `firstValidationError`, `validationSummary`, and `schemaFieldNames`.
|
||||
|
||||
Schema output is inferred automatically by `ObjectSchema`, `parseOrThrow`, `parseBody`, `parseEnv`, and `asyncSchema`. Use `InferSchema<typeof schema>` when a named output type is useful:
|
||||
|
||||
```ts
|
||||
const accountSchema = v.object({
|
||||
email: v.string().email(),
|
||||
attempts: v.number().integer(),
|
||||
});
|
||||
|
||||
type AccountInput = InferSchema<typeof accountSchema>;
|
||||
const account = parseOrThrow(accountSchema, input);
|
||||
// account.email: string
|
||||
// account.attempts: number
|
||||
```
|
||||
|
||||
Enable `validationPlugin()` for:
|
||||
|
||||
- `<ValidationSummary />`
|
||||
- `<FieldError />`
|
||||
|
||||
The summary block composes `Alert` from `@wrnexus/ui`, while `FieldError` remains a lightweight accessible field-level primitive.
|
||||
Schemas can drive external contracts without maintaining a second definition:
|
||||
|
||||
```ts
|
||||
import {
|
||||
localizeDescriptor,
|
||||
openApiRequestBody,
|
||||
parseDescriptor,
|
||||
toJsonSchema,
|
||||
} from "@wrnexus/validation";
|
||||
|
||||
const jsonSchema = toJsonSchema(contactSchema, {
|
||||
id: "urn:example:contact",
|
||||
title: "Contact request",
|
||||
});
|
||||
const requestBody = openApiRequestBody(contactSchema);
|
||||
|
||||
const mr = localizeDescriptor(contactSchema, (key, params) =>
|
||||
translations.t(`validation.${key}`, params),
|
||||
);
|
||||
const result = parseDescriptor(mr, input);
|
||||
```
|
||||
|
||||
JSON Schema output targets draft 2020-12, closes unknown object properties, and
|
||||
maps lengths/ranges/formats/enums/patterns/integer rules. OpenAPI request bodies
|
||||
reuse the same properties. Localized descriptors preserve explicit custom
|
||||
messages and fill default required, type-coercion, and rule messages; the same
|
||||
descriptor is consumable by server parsing and the eval-free browser runtime.
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
{
|
||||
"name": "@wrnexus/validation",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.5",
|
||||
"type": "module",
|
||||
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
||||
"description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS.git",
|
||||
"directory": "packages/validation"
|
||||
},
|
||||
"homepage": "https://wrnexusjs.dev/packages/validation",
|
||||
"bugs": {
|
||||
"url": "https://git.workroot.in/WorkRoot/WRNexusJS/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"wrnexus",
|
||||
"bun",
|
||||
"typescript",
|
||||
"validation"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -18,9 +34,28 @@
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./plugin": {
|
||||
"types": "./dist/plugin.d.ts",
|
||||
"import": "./dist/plugin.js"
|
||||
},
|
||||
"./components/*": "./components/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "^0.8.5",
|
||||
"@wrnexus/plugin": "^0.8.5",
|
||||
"@wrnexus/ui": "^0.8.5"
|
||||
},
|
||||
"wrnexus": {
|
||||
"plugin": {
|
||||
"plugin": "./dist/plugin.js",
|
||||
"export": "default",
|
||||
"factory": true
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"README.md",
|
||||
"components"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
param(
|
||||
[string]$Root = "E:\WireJS",
|
||||
[switch]$SkipValidation
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$PackageRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$Root = [System.IO.Path]::GetFullPath($Root)
|
||||
|
||||
if (-not (Test-Path (Join-Path $Root "packages\ui\components"))) {
|
||||
throw "WRNexusJS repository was not found at: $Root"
|
||||
}
|
||||
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$backupRoot = Join-Path $Root ".wrnexus\component-showcase-backup-$timestamp"
|
||||
New-Item -ItemType Directory -Path $backupRoot -Force | Out-Null
|
||||
|
||||
$files = @(
|
||||
"scripts/generate-ui-component-reference.mjs",
|
||||
"packages/ui/component-catalog.json",
|
||||
"examples/component-showcase/scripts/generate-showcase.mjs",
|
||||
"examples/component-showcase/scripts/showcase-profiles.mjs",
|
||||
"examples/component-showcase/public/playground.js",
|
||||
"examples/component-showcase/app/styles/global.css",
|
||||
"examples/component-showcase/test/showcase.test.ts",
|
||||
"examples/component-showcase/README.md"
|
||||
)
|
||||
|
||||
$generatedFiles = @(
|
||||
"packages/ui/component-reference.json",
|
||||
"packages/ui/COMPONENTS.md",
|
||||
"examples/component-showcase/showcase-manifest.json"
|
||||
)
|
||||
|
||||
function Convert-RelativePath([string]$relative) {
|
||||
return $relative.Replace("/", [System.IO.Path]::DirectorySeparatorChar)
|
||||
}
|
||||
|
||||
function Backup-Path([string]$relative) {
|
||||
$platformPath = Convert-RelativePath $relative
|
||||
$source = Join-Path $Root $platformPath
|
||||
if (-not (Test-Path $source)) { return }
|
||||
|
||||
$destination = Join-Path $backupRoot $platformPath
|
||||
$destinationParent = Split-Path -Parent $destination
|
||||
New-Item -ItemType Directory -Path $destinationParent -Force | Out-Null
|
||||
Copy-Item $source $destination -Recurse -Force
|
||||
}
|
||||
|
||||
foreach ($relative in $files) { Backup-Path $relative }
|
||||
foreach ($relative in $generatedFiles) { Backup-Path $relative }
|
||||
Backup-Path "examples/component-showcase/app/pages"
|
||||
Backup-Path "examples/component-showcase/app/layouts"
|
||||
|
||||
foreach ($relative in $files) {
|
||||
$platformPath = Convert-RelativePath $relative
|
||||
$source = Join-Path $PackageRoot $platformPath
|
||||
$destination = Join-Path $Root $platformPath
|
||||
if (-not (Test-Path $source)) {
|
||||
throw "Patch file is missing: $source"
|
||||
}
|
||||
New-Item -ItemType Directory -Path (Split-Path -Parent $destination) -Force | Out-Null
|
||||
Copy-Item $source $destination -Force
|
||||
Write-Host "Updated $relative" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Invoke-BunStep {
|
||||
param(
|
||||
[string]$Label,
|
||||
[string[]]$Arguments
|
||||
)
|
||||
Write-Host "`n$Label" -ForegroundColor Cyan
|
||||
& bun @Arguments
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "$Label failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
}
|
||||
|
||||
Push-Location $Root
|
||||
try {
|
||||
Invoke-BunStep "Generating the current UI component reference" @(
|
||||
"run",
|
||||
"scripts/generate-ui-component-reference.mjs"
|
||||
)
|
||||
|
||||
$showcaseRoot = Join-Path $Root "examples\component-showcase"
|
||||
Remove-Item (Join-Path $showcaseRoot ".wrnexus") -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item (Join-Path $showcaseRoot "dist") -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
Push-Location $showcaseRoot
|
||||
try {
|
||||
Invoke-BunStep "Generating component showcase pages and manifest" @("run", "generate")
|
||||
if (-not $SkipValidation) {
|
||||
Invoke-BunStep "Validating the complete component showcase" @("run", "check")
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Write-Host "`nComponent showcase update applied successfully." -ForegroundColor Green
|
||||
Write-Host "Backup: $backupRoot"
|
||||
if ($SkipValidation) {
|
||||
Write-Host "Validation was skipped. Run: bun run --cwd examples/component-showcase check" -ForegroundColor Yellow
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Root = "E:\WireJS"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Root = (Resolve-Path $Root).Path
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
Write-Host "Stopping Bun processes..." -ForegroundColor Cyan
|
||||
Get-Process bun -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
|
||||
Write-Host "Applying overlay reactivity fix..." -ForegroundColor Cyan
|
||||
node (Join-Path $ScriptDir "patch-overlay-showcase.mjs") $Root
|
||||
|
||||
Push-Location $Root
|
||||
try {
|
||||
Write-Host "Regenerating UI component reference..." -ForegroundColor Cyan
|
||||
bun run scripts/generate-ui-component-reference.mjs
|
||||
|
||||
Write-Host "Running focused UI tests..." -ForegroundColor Cyan
|
||||
bun test packages/ui/test/overlay-reactivity.test.ts
|
||||
|
||||
Push-Location (Join-Path $Root "examples\component-showcase")
|
||||
try {
|
||||
Write-Host "Regenerating showcase..." -ForegroundColor Cyan
|
||||
bun run generate
|
||||
bun test test/showcase.test.ts
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Write-Host "Done. Start with: cd E:\WireJS\examples\component-showcase; bun run dev" -ForegroundColor Green
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "WRNexusJS",
|
||||
"version": "0.6.0",
|
||||
"status": "developer-test-build",
|
||||
"sourceArchive": "WRNexusJS(4).zip",
|
||||
"frameworkPackages": 33,
|
||||
"uiComponents": 108,
|
||||
"showcaseComponentPages": 108,
|
||||
"showcaseLiveDemos": 417,
|
||||
"showcaseComponentCategories": 11,
|
||||
"showcaseV06Guides": 8,
|
||||
"wrnFilesParsed": 250,
|
||||
"wrnParseFailures": 0,
|
||||
"bunFullSuiteExecuted": false,
|
||||
"bunUnavailableInPackagingEnvironment": true
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Changelog
|
||||
|
||||
All notable framework changes are recorded here. Every release must also include an idempotent
|
||||
entry in the CLI migration registry.
|
||||
|
||||
## 0.8.3 - 2026-08-03
|
||||
|
||||
- Bundled hydrated browser modules so package aliases and filesystem imports do not leak into
|
||||
browser-native modules.
|
||||
- Fixed SSR computed values, Async branch aliases, typed object props in loops, reactive route
|
||||
state, loader invalidation, realtime identity isolation, and client-function hydration.
|
||||
- Fixed template-literal parsing in the reactive fallback evaluator and removed the unsupported
|
||||
default `unload` Permissions Policy directive.
|
||||
- Made official package components compatible with strict explicit imports and added regression
|
||||
coverage for the TeamSpace application failures.
|
||||
- Fixed Bun 1.3.14 and TypeScript 5.9 release-gate compatibility in client bundling, alias
|
||||
resolution, and Happy DOM event tests.
|
||||
- Fixed ESLint compatibility in Node release scripts by declaring Node globals and using explicit
|
||||
regex space quantifiers.
|
||||
- Fixed package component import validation after workspace installation by excluding generated and
|
||||
dependency directories such as nested `node_modules` from first-party source scans.
|
||||
- Made the VS Code embedded compiler freshness check deterministic across TypeScript patch
|
||||
versions, Windows/Linux line endings, and workspace environments by verifying normalized source
|
||||
and generator fingerprints while retaining exact-output checks for the same TypeScript version.
|
||||
- Fixed the VS Code language-server bundle so it executes under Node instead of exiting after
|
||||
defining an uninvoked Bun CommonJS wrapper, and added request-level crash containment.
|
||||
- Scoped HTML diagnostics to `view` blocks, ignored TypeScript generic syntax and WRN expressions,
|
||||
and preserved JavaScript-looking documentation inside `<pre><code>` examples.
|
||||
- Fixed formatter corruption of preformatted examples, balanced compact sibling markup, long bare
|
||||
tags, and repeated format-on-save indentation drift.
|
||||
- Fixed component prop intelligence for dynamic boolean/union expressions, boolean string literals,
|
||||
literal-union runtime types, and reserved prop names such as `class`.
|
||||
- Prevented bundled editor type checking from publishing TypeScript standard-library path failures or
|
||||
unmapped synthetic virtual-document diagnostics.
|
||||
- Fixed language-server virtual TypeScript inference for untyped dynamic handler parameters and
|
||||
indexed output dispatch, while retaining strict diagnostics for explicitly typed parameters.
|
||||
- Made bundled editor type checking resolve TypeScript standard libraries from the active workspace,
|
||||
and added a regression test that proves semantic diagnostics are actually running.
|
||||
- Fixed final release-gate lint failures by removing the obsolete editor `stripComments` helper and
|
||||
importing Node `Buffer` explicitly in the language-server bundle generator.
|
||||
- Reviewed the UI visual-contract change caused only by corrected `AuthForm.wrn` indentation and
|
||||
regenerated the 0.8 baseline after confirming no rendered component behavior changed.
|
||||
|
||||
## 0.8.0 - 2026-08-02
|
||||
|
||||
- Added package-owned helper and component kits across all 39 framework packages.
|
||||
- Added standalone realtime and package-aware auth, i18n, image, uploader, validation, JWT,
|
||||
encryption, database, and CAPTCHA improvements.
|
||||
- Added whole-application WRN syntax, import, and formatting modernization to the CLI update.
|
||||
- Added Windows/Linux CI, read-only package audits, governance documents, and security gates.
|
||||
@@ -1,421 +0,0 @@
|
||||
## R9 — ToggleCount animation stability
|
||||
|
||||
- Schedules animation frames by absolute offsets to prevent cumulative timer drift.
|
||||
- Cancels stale ToggleCount animation frames with per-animation tokens.
|
||||
- Binds generated browser peer functions through a scoped function table.
|
||||
|
||||
# WRNexusJS v0.6.0 changed files
|
||||
|
||||
- Canonicalized v0.6 syntax helpers through `@wrnexus/syntax`; removed the public `@wrnexus/syntax/v060` subpath and editor-bundle alias.
|
||||
|
||||
## R3 correction
|
||||
|
||||
- Regenerated/aligned `editors/vscode/src/compiler.cjs`.
|
||||
- Added `FIXES-0.6.0-VSCODE-COMPILER-BUNDLE.md`.
|
||||
|
||||
Comparison base: `WRNexusJS(4).zip`. Dependency directories, `.git`, and `.wrnexus` caches are excluded from this comparison.
|
||||
|
||||
- Added: **57** files
|
||||
- Modified: **284** files
|
||||
- Removed: **0** files
|
||||
|
||||
## Added files
|
||||
|
||||
- `MIGRATION-DRY-RUN-0.6.0.json`
|
||||
- `MIGRATION-DRY-RUN-0.6.0.md`
|
||||
- `OLD-PROJECT-TEST-CHECKLIST.md`
|
||||
- `PUBLISHING-0.6.0.md`
|
||||
- `RELEASE_MANIFEST.json`
|
||||
- `RELEASE_NOTES-0.6.0.md`
|
||||
- `ROLLBACK-0.6.0.md`
|
||||
- `UPGRADE-0.6.0.md`
|
||||
- `VALIDATION-0.6.0.md`
|
||||
- `docs/v0.6/architecture.md`
|
||||
- `docs/v0.6/language.md`
|
||||
- `examples/component-showcase/app/pages/v06-functions.wrn`
|
||||
- `examples/component-showcase/app/pages/v06-imports.wrn`
|
||||
- `examples/component-showcase/app/pages/v06-migration.wrn`
|
||||
- `examples/component-showcase/app/pages/v06-outputs.wrn`
|
||||
- `examples/component-showcase/app/pages/v06-runtime.wrn`
|
||||
- `examples/component-showcase/app/pages/v06-state.wrn`
|
||||
- `examples/component-showcase/app/pages/v06-stores.wrn`
|
||||
- `examples/component-showcase/app/pages/v06-types.wrn`
|
||||
- `integration/fixtures/v0.5-legacy-app/app/components/LegacyModal.wrn`
|
||||
- `integration/fixtures/v0.5-legacy-app/app/layouts/PublicLayout.wrn`
|
||||
- `integration/fixtures/v0.5-legacy-app/app/pages/index.wrn`
|
||||
- `integration/fixtures/v0.5-legacy-app/app/types/global.d.ts`
|
||||
- `integration/fixtures/v0.5-legacy-app/package.json`
|
||||
- `packages/cli/test/update-v060.test.ts`
|
||||
- `packages/compiler/src/client-codegen.ts`
|
||||
- `packages/compiler/src/component-contract.ts`
|
||||
- `packages/compiler/src/import-resolver.ts`
|
||||
- `packages/compiler/src/server-codegen.ts`
|
||||
- `packages/compiler/src/source-map.ts`
|
||||
- `packages/compiler/src/store-codegen.ts`
|
||||
- `packages/compiler/src/targets.ts`
|
||||
- `packages/compiler/src/type-codegen.ts`
|
||||
- `packages/compiler/test/v060-targets.test.ts`
|
||||
- `packages/csr/src/client-functions.ts`
|
||||
- `packages/csr/src/outputs.ts`
|
||||
- `packages/csr/src/refs.ts`
|
||||
- `packages/csr/src/server-client.ts`
|
||||
- `packages/csr/src/types.ts`
|
||||
- `packages/ssr/src/rpc.ts`
|
||||
- `packages/ssr/src/store-context.ts`
|
||||
- `packages/ssr/test/rpc-v060.test.ts`
|
||||
- `packages/store/package.json`
|
||||
- `packages/store/src/client.ts`
|
||||
- `packages/store/src/index.ts`
|
||||
- `packages/store/src/server.ts`
|
||||
- `packages/store/src/types.ts`
|
||||
- `packages/store/test/store.test.ts`
|
||||
- `packages/syntax/src/v060.ts`
|
||||
- `packages/syntax/test/v060.test.ts`
|
||||
- `packages/typecheck/package.json`
|
||||
- `packages/typecheck/src/contracts.ts`
|
||||
- `packages/typecheck/src/index.ts`
|
||||
- `packages/typecheck/src/project.ts`
|
||||
- `packages/typecheck/test/imports-and-components.test.ts`
|
||||
- `packages/typecheck/test/typecheck.test.ts`
|
||||
- `scripts/validate-0.6.mjs`
|
||||
|
||||
## Modified files
|
||||
|
||||
- `bun.lock`
|
||||
- `editors/vscode/package.json`
|
||||
- `editors/vscode/snippets/wrn.json`
|
||||
- `editors/vscode/src/completion.js`
|
||||
- `editors/vscode/src/diagnostics.js`
|
||||
- `editors/vscode/syntaxes/wrn.tmLanguage.json`
|
||||
- `examples/component-showcase/app/layouts/showcase.wrn`
|
||||
- `examples/component-showcase/app/pages/components/accordion.wrn`
|
||||
- `examples/component-showcase/app/pages/components/advanced-date-picker.wrn`
|
||||
- `examples/component-showcase/app/pages/components/advanced-range-slider.wrn`
|
||||
- `examples/component-showcase/app/pages/components/advanced-select.wrn`
|
||||
- `examples/component-showcase/app/pages/components/alert.wrn`
|
||||
- `examples/component-showcase/app/pages/components/announcement-bar.wrn`
|
||||
- `examples/component-showcase/app/pages/components/auth-form.wrn`
|
||||
- `examples/component-showcase/app/pages/components/auth-split-layout.wrn`
|
||||
- `examples/component-showcase/app/pages/components/avatar-group.wrn`
|
||||
- `examples/component-showcase/app/pages/components/avatar.wrn`
|
||||
- `examples/component-showcase/app/pages/components/back-to-top.wrn`
|
||||
- `examples/component-showcase/app/pages/components/badge.wrn`
|
||||
- `examples/component-showcase/app/pages/components/blockquote.wrn`
|
||||
- `examples/component-showcase/app/pages/components/breadcrumb.wrn`
|
||||
- `examples/component-showcase/app/pages/components/button-group.wrn`
|
||||
- `examples/component-showcase/app/pages/components/button.wrn`
|
||||
- `examples/component-showcase/app/pages/components/card.wrn`
|
||||
- `examples/component-showcase/app/pages/components/carousel.wrn`
|
||||
- `examples/component-showcase/app/pages/components/chart.wrn`
|
||||
- `examples/component-showcase/app/pages/components/chat-bubble.wrn`
|
||||
- `examples/component-showcase/app/pages/components/checkbox.wrn`
|
||||
- `examples/component-showcase/app/pages/components/clipboard.wrn`
|
||||
- `examples/component-showcase/app/pages/components/collapse.wrn`
|
||||
- `examples/component-showcase/app/pages/components/color-picker.wrn`
|
||||
- `examples/component-showcase/app/pages/components/columns.wrn`
|
||||
- `examples/component-showcase/app/pages/components/combo-box.wrn`
|
||||
- `examples/component-showcase/app/pages/components/confetti.wrn`
|
||||
- `examples/component-showcase/app/pages/components/container.wrn`
|
||||
- `examples/component-showcase/app/pages/components/context-menu.wrn`
|
||||
- `examples/component-showcase/app/pages/components/copy-markup.wrn`
|
||||
- `examples/component-showcase/app/pages/components/ctasection.wrn`
|
||||
- `examples/component-showcase/app/pages/components/custom-scrollbar.wrn`
|
||||
- `examples/component-showcase/app/pages/components/data-map.wrn`
|
||||
- `examples/component-showcase/app/pages/components/data-table.wrn`
|
||||
- `examples/component-showcase/app/pages/components/date-picker.wrn`
|
||||
- `examples/component-showcase/app/pages/components/device-frame.wrn`
|
||||
- `examples/component-showcase/app/pages/components/divider.wrn`
|
||||
- `examples/component-showcase/app/pages/components/drag-and-drop.wrn`
|
||||
- `examples/component-showcase/app/pages/components/drawer.wrn`
|
||||
- `examples/component-showcase/app/pages/components/dropdown.wrn`
|
||||
- `examples/component-showcase/app/pages/components/feature-card.wrn`
|
||||
- `examples/component-showcase/app/pages/components/feature-grid.wrn`
|
||||
- `examples/component-showcase/app/pages/components/feature-icon-card.wrn`
|
||||
- `examples/component-showcase/app/pages/components/file-input.wrn`
|
||||
- `examples/component-showcase/app/pages/components/file-upload-progress.wrn`
|
||||
- `examples/component-showcase/app/pages/components/file-upload.wrn`
|
||||
- `examples/component-showcase/app/pages/components/footer.wrn`
|
||||
- `examples/component-showcase/app/pages/components/grid.wrn`
|
||||
- `examples/component-showcase/app/pages/components/hero-actions.wrn`
|
||||
- `examples/component-showcase/app/pages/components/hero.wrn`
|
||||
- `examples/component-showcase/app/pages/components/image.wrn`
|
||||
- `examples/component-showcase/app/pages/components/input-group.wrn`
|
||||
- `examples/component-showcase/app/pages/components/input-number.wrn`
|
||||
- `examples/component-showcase/app/pages/components/input.wrn`
|
||||
- `examples/component-showcase/app/pages/components/kbd.wrn`
|
||||
- `examples/component-showcase/app/pages/components/layout-splitter.wrn`
|
||||
- `examples/component-showcase/app/pages/components/legend-indicator.wrn`
|
||||
- `examples/component-showcase/app/pages/components/link.wrn`
|
||||
- `examples/component-showcase/app/pages/components/list-group.wrn`
|
||||
- `examples/component-showcase/app/pages/components/list.wrn`
|
||||
- `examples/component-showcase/app/pages/components/map.wrn`
|
||||
- `examples/component-showcase/app/pages/components/marketing-section-header.wrn`
|
||||
- `examples/component-showcase/app/pages/components/marquee.wrn`
|
||||
- `examples/component-showcase/app/pages/components/mega-menu.wrn`
|
||||
- `examples/component-showcase/app/pages/components/metric-card.wrn`
|
||||
- `examples/component-showcase/app/pages/components/metric-grid.wrn`
|
||||
- `examples/component-showcase/app/pages/components/modal.wrn`
|
||||
- `examples/component-showcase/app/pages/components/nav.wrn`
|
||||
- `examples/component-showcase/app/pages/components/navbar.wrn`
|
||||
- `examples/component-showcase/app/pages/components/page-header.wrn`
|
||||
- `examples/component-showcase/app/pages/components/pagination.wrn`
|
||||
- `examples/component-showcase/app/pages/components/pin-input.wrn`
|
||||
- `examples/component-showcase/app/pages/components/popover.wrn`
|
||||
- `examples/component-showcase/app/pages/components/portal-dashboard.wrn`
|
||||
- `examples/component-showcase/app/pages/components/preference-switcher.wrn`
|
||||
- `examples/component-showcase/app/pages/components/progress.wrn`
|
||||
- `examples/component-showcase/app/pages/components/public-page-shell.wrn`
|
||||
- `examples/component-showcase/app/pages/components/radio.wrn`
|
||||
- `examples/component-showcase/app/pages/components/range-slider.wrn`
|
||||
- `examples/component-showcase/app/pages/components/rating.wrn`
|
||||
- `examples/component-showcase/app/pages/components/scrollspy.wrn`
|
||||
- `examples/component-showcase/app/pages/components/search-box.wrn`
|
||||
- `examples/component-showcase/app/pages/components/section-header.wrn`
|
||||
- `examples/component-showcase/app/pages/components/section.wrn`
|
||||
- `examples/component-showcase/app/pages/components/select.wrn`
|
||||
- `examples/component-showcase/app/pages/components/sidebar.wrn`
|
||||
- `examples/component-showcase/app/pages/components/skeleton.wrn`
|
||||
- `examples/component-showcase/app/pages/components/spinner.wrn`
|
||||
- `examples/component-showcase/app/pages/components/split-hero.wrn`
|
||||
- `examples/component-showcase/app/pages/components/stats-bar.wrn`
|
||||
- `examples/component-showcase/app/pages/components/stepper.wrn`
|
||||
- `examples/component-showcase/app/pages/components/strong-password.wrn`
|
||||
- `examples/component-showcase/app/pages/components/styled-icon.wrn`
|
||||
- `examples/component-showcase/app/pages/components/switch.wrn`
|
||||
- `examples/component-showcase/app/pages/components/table.wrn`
|
||||
- `examples/component-showcase/app/pages/components/tabs.wrn`
|
||||
- `examples/component-showcase/app/pages/components/text-link.wrn`
|
||||
- `examples/component-showcase/app/pages/components/textarea.wrn`
|
||||
- `examples/component-showcase/app/pages/components/time-picker.wrn`
|
||||
- `examples/component-showcase/app/pages/components/timeline.wrn`
|
||||
- `examples/component-showcase/app/pages/components/toast-notifications.wrn`
|
||||
- `examples/component-showcase/app/pages/components/toast.wrn`
|
||||
- `examples/component-showcase/app/pages/components/toggle-count.wrn`
|
||||
- `examples/component-showcase/app/pages/components/toggle-password.wrn`
|
||||
- `examples/component-showcase/app/pages/components/tooltip.wrn`
|
||||
- `examples/component-showcase/app/pages/components/tree-view.wrn`
|
||||
- `examples/component-showcase/app/pages/components/typography.wrn`
|
||||
- `examples/component-showcase/app/pages/components/wysiwyg-editor.wrn`
|
||||
- `examples/component-showcase/public/playground.js`
|
||||
- `examples/component-showcase/scripts/generate-showcase.mjs`
|
||||
- `llms.txt`
|
||||
- `package.json`
|
||||
- `packages/ai/package.json`
|
||||
- `packages/auth/package.json`
|
||||
- `packages/authz/package.json`
|
||||
- `packages/captcha/package.json`
|
||||
- `packages/cli/package.json`
|
||||
- `packages/cli/src/update.ts`
|
||||
- `packages/compiler/package.json`
|
||||
- `packages/compiler/src/codegen.ts`
|
||||
- `packages/compiler/src/index.ts`
|
||||
- `packages/core/package.json`
|
||||
- `packages/csr/package.json`
|
||||
- `packages/csr/src/index.ts`
|
||||
- `packages/csr/src/nav-runtime.ts`
|
||||
- `packages/csr/src/reactive-runtime.ts`
|
||||
- `packages/db/package.json`
|
||||
- `packages/dev-server/package.json`
|
||||
- `packages/dev-server/src/assets.ts`
|
||||
- `packages/dev-server/src/index.ts`
|
||||
- `packages/dev-server/src/pipeline.ts`
|
||||
- `packages/dev-server/src/runtime.ts`
|
||||
- `packages/dev-toolbar/package.json`
|
||||
- `packages/dev-toolbar/src/client/runtime.ts`
|
||||
- `packages/dev-toolbar/src/client/styles.ts`
|
||||
- `packages/dev-toolbar/src/types.ts`
|
||||
- `packages/encryption/package.json`
|
||||
- `packages/helpers/package.json`
|
||||
- `packages/i18n/package.json`
|
||||
- `packages/jwt/package.json`
|
||||
- `packages/mobile/package.json`
|
||||
- `packages/native/package.json`
|
||||
- `packages/oauth/package.json`
|
||||
- `packages/plugin/package.json`
|
||||
- `packages/pubsub/package.json`
|
||||
- `packages/queue/package.json`
|
||||
- `packages/reactive/package.json`
|
||||
- `packages/router/package.json`
|
||||
- `packages/ssr/package.json`
|
||||
- `packages/ssr/src/index.ts`
|
||||
- `packages/styles/package.json`
|
||||
- `packages/styles/src/config.ts`
|
||||
- `packages/syntax/package.json`
|
||||
- `packages/syntax/src/diagnostics.ts`
|
||||
- `packages/syntax/src/index.ts`
|
||||
- `packages/syntax/src/parser.ts`
|
||||
- `packages/syntax/src/spec.ts`
|
||||
- `packages/syntax/src/tokenizer.ts`
|
||||
- `packages/test/package.json`
|
||||
- `packages/tracking/package.json`
|
||||
- `packages/ui/COMPONENTS.md`
|
||||
- `packages/ui/component-reference.json`
|
||||
- `packages/ui/components/Accordion.wrn`
|
||||
- `packages/ui/components/AdvancedDatePicker.wrn`
|
||||
- `packages/ui/components/AdvancedRangeSlider.wrn`
|
||||
- `packages/ui/components/AdvancedSelect.wrn`
|
||||
- `packages/ui/components/AnnouncementBar.wrn`
|
||||
- `packages/ui/components/AuthForm.wrn`
|
||||
- `packages/ui/components/AuthSplitLayout.wrn`
|
||||
- `packages/ui/components/AvatarGroup.wrn`
|
||||
- `packages/ui/components/BackToTop.wrn`
|
||||
- `packages/ui/components/Badge.wrn`
|
||||
- `packages/ui/components/Blockquote.wrn`
|
||||
- `packages/ui/components/Breadcrumb.wrn`
|
||||
- `packages/ui/components/ButtonGroup.wrn`
|
||||
- `packages/ui/components/CTASection.wrn`
|
||||
- `packages/ui/components/Card.wrn`
|
||||
- `packages/ui/components/Chart.wrn`
|
||||
- `packages/ui/components/ChatBubble.wrn`
|
||||
- `packages/ui/components/Checkbox.wrn`
|
||||
- `packages/ui/components/Clipboard.wrn`
|
||||
- `packages/ui/components/Collapse.wrn`
|
||||
- `packages/ui/components/ColorPicker.wrn`
|
||||
- `packages/ui/components/Columns.wrn`
|
||||
- `packages/ui/components/Combobox.wrn`
|
||||
- `packages/ui/components/Confetti.wrn`
|
||||
- `packages/ui/components/Container.wrn`
|
||||
- `packages/ui/components/ContextMenu.wrn`
|
||||
- `packages/ui/components/CopyMarkup.wrn`
|
||||
- `packages/ui/components/CustomScrollbar.wrn`
|
||||
- `packages/ui/components/DataMap.wrn`
|
||||
- `packages/ui/components/DataTable.wrn`
|
||||
- `packages/ui/components/DatePicker.wrn`
|
||||
- `packages/ui/components/DeviceFrame.wrn`
|
||||
- `packages/ui/components/Divider.wrn`
|
||||
- `packages/ui/components/DragAndDrop.wrn`
|
||||
- `packages/ui/components/Drawer.wrn`
|
||||
- `packages/ui/components/Dropdown.wrn`
|
||||
- `packages/ui/components/FeatureCard.wrn`
|
||||
- `packages/ui/components/FeatureGrid.wrn`
|
||||
- `packages/ui/components/FeatureIconCard.wrn`
|
||||
- `packages/ui/components/FileInput.wrn`
|
||||
- `packages/ui/components/FileUpload.wrn`
|
||||
- `packages/ui/components/FileUploadProgress.wrn`
|
||||
- `packages/ui/components/Footer.wrn`
|
||||
- `packages/ui/components/Grid.wrn`
|
||||
- `packages/ui/components/Hero.wrn`
|
||||
- `packages/ui/components/HeroActions.wrn`
|
||||
- `packages/ui/components/Image.wrn`
|
||||
- `packages/ui/components/Input.wrn`
|
||||
- `packages/ui/components/InputGroup.wrn`
|
||||
- `packages/ui/components/InputNumber.wrn`
|
||||
- `packages/ui/components/Kbd.wrn`
|
||||
- `packages/ui/components/LayoutSplitter.wrn`
|
||||
- `packages/ui/components/LegendIndicator.wrn`
|
||||
- `packages/ui/components/Link.wrn`
|
||||
- `packages/ui/components/List.wrn`
|
||||
- `packages/ui/components/ListGroup.wrn`
|
||||
- `packages/ui/components/Map.wrn`
|
||||
- `packages/ui/components/MarketingSectionHeader.wrn`
|
||||
- `packages/ui/components/Marquee.wrn`
|
||||
- `packages/ui/components/MegaMenu.wrn`
|
||||
- `packages/ui/components/MetricCard.wrn`
|
||||
- `packages/ui/components/MetricGrid.wrn`
|
||||
- `packages/ui/components/Modal.wrn`
|
||||
- `packages/ui/components/Nav.wrn`
|
||||
- `packages/ui/components/Navbar.wrn`
|
||||
- `packages/ui/components/PageHeader.wrn`
|
||||
- `packages/ui/components/Pagination.wrn`
|
||||
- `packages/ui/components/PinInput.wrn`
|
||||
- `packages/ui/components/Popover.wrn`
|
||||
- `packages/ui/components/PortalDashboard.wrn`
|
||||
- `packages/ui/components/PreferenceSwitcher.wrn`
|
||||
- `packages/ui/components/PublicPageShell.wrn`
|
||||
- `packages/ui/components/Radio.wrn`
|
||||
- `packages/ui/components/RangeSlider.wrn`
|
||||
- `packages/ui/components/Rating.wrn`
|
||||
- `packages/ui/components/Scrollspy.wrn`
|
||||
- `packages/ui/components/SearchBox.wrn`
|
||||
- `packages/ui/components/Section.wrn`
|
||||
- `packages/ui/components/SectionHeader.wrn`
|
||||
- `packages/ui/components/Select.wrn`
|
||||
- `packages/ui/components/Sidebar.wrn`
|
||||
- `packages/ui/components/SplitHero.wrn`
|
||||
- `packages/ui/components/StatsBar.wrn`
|
||||
- `packages/ui/components/Stepper.wrn`
|
||||
- `packages/ui/components/StrongPassword.wrn`
|
||||
- `packages/ui/components/StyledIcon.wrn`
|
||||
- `packages/ui/components/Switch.wrn`
|
||||
- `packages/ui/components/Tabs.wrn`
|
||||
- `packages/ui/components/TextLink.wrn`
|
||||
- `packages/ui/components/Textarea.wrn`
|
||||
- `packages/ui/components/TimePicker.wrn`
|
||||
- `packages/ui/components/Timeline.wrn`
|
||||
- `packages/ui/components/Toast.wrn`
|
||||
- `packages/ui/components/ToastNotifications.wrn`
|
||||
- `packages/ui/components/ToggleCount.wrn`
|
||||
- `packages/ui/components/TogglePassword.wrn`
|
||||
- `packages/ui/components/Tooltip.wrn`
|
||||
- `packages/ui/components/TreeView.wrn`
|
||||
- `packages/ui/components/Typography.wrn`
|
||||
- `packages/ui/components/WysiwygEditor.wrn`
|
||||
- `packages/ui/components/alert.wrn`
|
||||
- `packages/ui/components/avatar.wrn`
|
||||
- `packages/ui/components/button.wrn`
|
||||
- `packages/ui/components/carousel.wrn`
|
||||
- `packages/ui/components/progress.wrn`
|
||||
- `packages/ui/components/skeleton.wrn`
|
||||
- `packages/ui/components/spinner.wrn`
|
||||
- `packages/ui/components/table.wrn`
|
||||
- `packages/ui/package.json`
|
||||
- `packages/uploader/package.json`
|
||||
- `packages/validation/package.json`
|
||||
- `scripts/generate-ui-component-reference.mjs`
|
||||
- `services/managed-captcha/package.json`
|
||||
- `tsconfig.json`
|
||||
- `update-package-versions.mjs`
|
||||
|
||||
## Removed files
|
||||
|
||||
- None
|
||||
|
||||
## R7 test-cascade corrections
|
||||
|
||||
- Restored synchronous CSR hydration for inline behavior and unresolved direct-compile module placeholders.
|
||||
- Restored strict component/store lifecycle hook validation.
|
||||
- Prevented browser runtime API names from colliding with generated prop/state aliases.
|
||||
- Preserved synchronous page exports when no imported store requires asynchronous initialization.
|
||||
- Allowed store dispose lifecycle hooks to mutate store state through the internal mutation context.
|
||||
- Updated UI tests and generated references for typed props and `outputs {}` contracts.
|
||||
- Added universal size/color support to FeatureGrid and complete RangeSlider output metadata.
|
||||
|
||||
## R8 UI compiler regression fixes
|
||||
|
||||
- Fixed readonly-prop false positives for comparisons, string literals, object/member access, and shadowing function parameters.
|
||||
- Restored compilation and runtime tests for AuthForm, Carousel, RangeSlider, InputNumber, Select, AdvancedSelect, ComboBox, ContextMenu, and Tooltip.
|
||||
- Synchronized component catalog and generated component reference with all 108 bundled UI declarations.
|
||||
- Added full UI compilation and native-attribute forwarding checks to `validate:0.6`.
|
||||
|
||||
## R10 peer-function scoped-state correction
|
||||
|
||||
- Peer client/shared functions are invoked through synchronized scoped wrappers.
|
||||
- Nested peer calls no longer have their state changes overwritten by stale outer aliases.
|
||||
- Synchronous, asynchronous, and throwing peer calls refresh shared state correctly.
|
||||
- Added an executable `validate:0.6` regression probe for the exact compiler failure.
|
||||
|
||||
## R11 peer-function test correction
|
||||
|
||||
- Corrected the compiler regression test export-stripping regex from an over-escaped literal `\\s` match to the intended whitespace `\s` match.
|
||||
- Confirmed the generated peer-function browser module executes and updates shared state from `0` to `1`.
|
||||
- Added a release validator guard for the exact test source regression.
|
||||
|
||||
## R13 release reference stability
|
||||
|
||||
- Prevented the UI reference generator from rewriting current files solely because of CRLF/LF differences.
|
||||
- Added an executable regression test proving line-ending-only differences are ignored and real stale content is repaired.
|
||||
|
||||
## 0.7.0 R2 typecheck correction
|
||||
|
||||
- Removed leaked focused-typecheck shim files that polluted the root TypeScript program.
|
||||
- Added a validation gate preventing those temporary files from entering future archives.
|
||||
|
||||
## 0.7.0 R4 Happy DOM event typing
|
||||
|
||||
- Fixed strict event-type compatibility in `packages/validation/test/validation.test.ts`.
|
||||
- Improved `check:workspace` dry-run wording.
|
||||
- Added a validation guard preventing browser DOM `Event` from being reintroduced as the Happy DOM helper return type.
|
||||
|
||||
## 0.7.0 R6 lint cleanup
|
||||
|
||||
See `FIXES-0.7.0-R6-LINT-CLEANUP.md` for the final lint and secure hydration corrections.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Code of conduct
|
||||
|
||||
Be respectful, constructive, and specific. Harassment, discrimination, threats, personal attacks,
|
||||
and publication of private information are not acceptable. Discuss technical decisions with
|
||||
evidence, assume good intent, and give contributors room to correct mistakes.
|
||||
|
||||
Report conduct concerns privately to the maintainers. Maintainers may remove content, limit
|
||||
participation, or ban contributors when necessary to protect the community.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Contributing
|
||||
|
||||
Install Bun 1.3.14 or newer and Node.js 24, then run:
|
||||
|
||||
```sh
|
||||
bun install --frozen-lockfile
|
||||
npm ci --prefix editors/vscode
|
||||
bun run typecheck
|
||||
bun run lint
|
||||
bun run format:check
|
||||
bun run test:all
|
||||
bun run validate:0.8
|
||||
```
|
||||
|
||||
Keep migrations conservative, backed up, idempotent, and covered by fixtures. Generated files
|
||||
must be produced by their documented `generate:*` command and committed with their source change.
|
||||
Security issues follow `SECURITY.md` and must not be disclosed publicly before a coordinated fix.
|
||||
@@ -1,65 +0,0 @@
|
||||
# WRNexusJS 0.6.0 focused fixes — issues 1 through 7
|
||||
|
||||
This developer-test archive fixes the seven blockers identified in `WRNexusJS-v0.6.0-validation-against-spec.md`.
|
||||
|
||||
## Fixed
|
||||
|
||||
1. **Generated browser-module syntax**
|
||||
- JavaScript reserved words such as the `class` prop are never destructured into invalid bindings.
|
||||
- Function parameters no longer collide with generated prop, state, output, server, props, or refs aliases.
|
||||
- All 108 UI component browser modules were regenerated and passed JavaScript syntax parsing.
|
||||
|
||||
2. **Browser-store RPC**
|
||||
- Generated store actions bind `server` from the store action context.
|
||||
- Browser store RPC sends same-origin structured requests with CSRF and request identifiers.
|
||||
- A live simulated store called a server function successfully and updated reactive state.
|
||||
|
||||
3. **Concrete UI output contracts**
|
||||
- No `unknown` remains inside any UI `outputs {}` declaration.
|
||||
- Component reference and showcase metadata were regenerated.
|
||||
- Legacy `$emit`, `@event`, `$event`, and `event.detail` remain absent from framework UI components.
|
||||
|
||||
4. **Configured import modes**
|
||||
- Dev compilation reads `imports.mode`, `imports.aliases`, and `imports.autoImport` from application configuration.
|
||||
- `legacy` accepts implicit discovery.
|
||||
- `compatible` accepts it with `WRN-IMPORT-IMPLICIT` diagnostics.
|
||||
- `explicit` rejects missing imports and accepts correctly imported components/layouts/stores.
|
||||
|
||||
5. **Store persistence migration and validation**
|
||||
- `persist { migrations { ... } validate { ... } }` is parsed and emitted.
|
||||
- Browser restore executes version migration before validation.
|
||||
- Invalid or incompatible persisted values are reset with diagnostics.
|
||||
- Include-only state is written with the current persistence version.
|
||||
|
||||
6. **Store HMR integration**
|
||||
- Store browser modules expose hot-update definitions.
|
||||
- The dev server broadcasts versioned `store-update` messages.
|
||||
- The browser HMR client imports changed store modules and applies updates without a mandatory document reload.
|
||||
- Compatible fields are preserved; added, removed, and incompatible fields are reported.
|
||||
|
||||
7. **Restricted RPC exposure**
|
||||
- RPC manifests include only server functions referenced through `server.name(...)` by browser-capable code.
|
||||
- Unreferenced server functions remain available for local SSR/server execution but are not remotely exposed.
|
||||
|
||||
## Focused validation completed
|
||||
|
||||
- 108 UI browser artifacts generated: **0 generation failures**.
|
||||
- 108 UI browser artifacts syntax checked: **0 syntax failures**.
|
||||
- 250 UI/showcase `.wrn` files parsed: **0 parser failures**.
|
||||
- Browser-store RPC/persistence/validation/HMR simulation: **passed**.
|
||||
- Import mode simulation for legacy/compatible/explicit: **passed**.
|
||||
- Restricted component and store RPC manifest probes: **passed**.
|
||||
- Generated server-store TypeScript transpilation: **0 diagnostics**.
|
||||
- VS Code extension Node tests: **28 passed**.
|
||||
- `node scripts/validate-0.6.mjs`: **12 passed, 1 Bun warning, 0 failed**.
|
||||
|
||||
## Remaining local release gate
|
||||
|
||||
Bun is unavailable in the packaging environment. Run the full point-8 suite locally before publishing:
|
||||
|
||||
```powershell
|
||||
bun install
|
||||
bun run validate:0.6
|
||||
bun run check
|
||||
bun run --cwd examples/component-showcase check
|
||||
```
|
||||
@@ -1,44 +0,0 @@
|
||||
# WRNexusJS v0.6.0 R6 lint corrections
|
||||
|
||||
This revision fixes the lint failures reported by `bun run check` after R5.
|
||||
|
||||
## Corrected files
|
||||
|
||||
- `editors/vscode/test/formatter.test.js`
|
||||
- Replaced hard-to-count literal regex spaces with quantified spaces.
|
||||
- `examples/component-showcase/scripts/generate-showcase.mjs`
|
||||
- Removed unused `uiComponentsPath` and `jsonAttribute` declarations.
|
||||
- `examples/component-showcase/scripts/showcase-profiles.mjs`
|
||||
- Removed the unused `actionSlot` declaration.
|
||||
- `packages/cli/src/update.ts`
|
||||
- Removed an unnecessary regex escape.
|
||||
- `packages/compiler/src/client-codegen.ts`
|
||||
- Rewrote store-path regexes without unnecessary character-class escapes.
|
||||
- `packages/compiler/src/codegen.ts`
|
||||
- Rewrote the store-path regex without unnecessary character-class escapes.
|
||||
- `packages/syntax/src/diagnostics.ts`
|
||||
- Corrected dynamic `RegExp` string escaping for word boundaries, property separators, and whitespace.
|
||||
- `packages/syntax/src/parser.ts`
|
||||
- Removed an unused `FunctionRuntime` type import.
|
||||
- `patch-overlay-showcase.mjs`
|
||||
- Added explicit Node imports for `process` and `console`.
|
||||
- `scripts/generate-ui-component-reference.mjs`
|
||||
- Removed an unnecessary closing-parenthesis escape in a regex character class.
|
||||
- `scripts/validate-0.6.mjs`
|
||||
- Added explicit Node imports for `console`, `process`, and `fileURLToPath`.
|
||||
- Replaced URL pathname manipulation with `fileURLToPath`, improving Windows path handling.
|
||||
|
||||
## Validation performed in the packaging environment
|
||||
|
||||
- `node scripts/validate-0.6.mjs`: 14 passes, zero failures; Bun unavailable warning only.
|
||||
- VS Code extension Node tests: 28 passes, zero failures.
|
||||
- VS Code extension validation: all checks passed.
|
||||
- All JavaScript and MJS syntax checks passed.
|
||||
- Exact regression guards for all 31 reported lint findings passed.
|
||||
|
||||
Run the complete release gate locally with Bun:
|
||||
|
||||
```powershell
|
||||
bun install
|
||||
bun run check
|
||||
```
|
||||
@@ -1,25 +0,0 @@
|
||||
# WRNexusJS v0.6.0 R10 peer-function state fix
|
||||
|
||||
## Failure
|
||||
|
||||
The compiler test `browser codegen binds peer client functions through the scoped function table` failed because an outer client function captured local state before calling a peer function. The peer updated `context.state`, but the outer function's `finally` block copied its stale local value back over the peer update.
|
||||
|
||||
## Fix
|
||||
|
||||
`packages/compiler/src/client-codegen.ts` now generates peer-function wrappers that:
|
||||
|
||||
1. synchronize the caller's local state into `context.state` before the peer call;
|
||||
2. invoke the peer through `context.functions`;
|
||||
3. refresh local aliases from `context.state` after synchronous completion, asynchronous completion, or a synchronous throw;
|
||||
4. copy local state back only when it actually changed from the function-entry snapshot.
|
||||
|
||||
This preserves shared scoped state for nested client/shared function calls without changing synchronous functions into promises.
|
||||
|
||||
## Regression validation
|
||||
|
||||
- Exact peer-call execution probe: passed (`run()` calls `increment()`, final state is `1`).
|
||||
- Focused TypeScript compiler check: passed.
|
||||
- UI browser targets generated and syntax-checked: 108/108.
|
||||
- UI and showcase WRN files parsed: 250/250.
|
||||
- VS Code tests: 28/28.
|
||||
- Root v0.6 validator: 18 passed, 0 failed (Bun availability warning only in the packaging environment).
|
||||
@@ -1,17 +0,0 @@
|
||||
# WRNexusJS 0.6.0 R11 peer-function regression test fix
|
||||
|
||||
The final failing compiler test used an over-escaped regular expression:
|
||||
|
||||
```ts
|
||||
targets.browser.replace(/^export\\s+/gm, "");
|
||||
```
|
||||
|
||||
That pattern matches a literal `\\s` sequence rather than whitespace, so generated `export` keywords remained in the source passed to `new Function()`.
|
||||
|
||||
R11 corrects the test to:
|
||||
|
||||
```ts
|
||||
targets.browser.replace(/^export\s+/gm, "");
|
||||
```
|
||||
|
||||
The generated browser module itself was already correct. The corrected executable probe returns `state.value === 1` after calling the peer-bound `run()` function.
|
||||
@@ -1,33 +0,0 @@
|
||||
# R12 release reference gate fix
|
||||
|
||||
## Failure
|
||||
|
||||
`bun run scripts/release.ts publish` could stop with:
|
||||
|
||||
```text
|
||||
UI component reference is stale.
|
||||
M packages/ui/component-reference.json
|
||||
```
|
||||
|
||||
even when the generated content was current.
|
||||
|
||||
## Root cause
|
||||
|
||||
The release gate used `git status --short` immediately after the generator ran. On Windows, generated files are written with LF while an `autocrlf` checkout may use CRLF. Git status can report the worktree file as modified even when its canonical Git content is unchanged.
|
||||
|
||||
The generator also writes `component-catalog.json`, but that file was not part of the release gate. Component ordering used `localeCompare`, which can vary by operating-system locale.
|
||||
|
||||
## Fix
|
||||
|
||||
- Compare generated files with `git diff --quiet`, which applies Git text normalization.
|
||||
- Show real differences with `git diff --name-status`.
|
||||
- Include all generated UI reference files:
|
||||
- `packages/ui/component-catalog.json`
|
||||
- `packages/ui/component-reference.json`
|
||||
- `packages/ui/COMPONENTS.md`
|
||||
- Replace locale-dependent sorting with a deterministic ASCII comparator.
|
||||
- Add validation guards for the complete release gate.
|
||||
|
||||
## Expected result
|
||||
|
||||
A line-ending-only rewrite does not block publication. A real component-reference, catalog, or documentation difference still blocks publication and prints the exact changed files.
|
||||
@@ -1,19 +0,0 @@
|
||||
# WRNexusJS 0.6.0 R13 - UI reference release stability
|
||||
|
||||
## Problem
|
||||
|
||||
`release:private` regenerated the UI component catalog and reference before publishing. On Windows, the generator always wrote LF output. When the checked-out JSON files used CRLF, Git reported both generated JSON files as modified even though their normalized content was already current.
|
||||
|
||||
## Fix
|
||||
|
||||
- Added normalized newline comparison to `scripts/generate-ui-component-reference.mjs`.
|
||||
- Generated files are no longer rewritten when their content differs only by line endings.
|
||||
- Real generated-content differences are still written and remain visible to the release gate.
|
||||
- Added `scripts/test-ui-reference-generation.mjs` to execute both the CRLF no-op case and the genuine stale-content repair case.
|
||||
- Wired the executable regression into `scripts/validate-0.6.mjs`.
|
||||
|
||||
## Verified behavior
|
||||
|
||||
- Current CRLF catalog/reference files remain byte-for-byte unchanged.
|
||||
- A stale reference count is regenerated to the correct value.
|
||||
- Release verification still blocks real generated-content drift.
|
||||
@@ -1,21 +0,0 @@
|
||||
# WRNexusJS v0.6.0 R5 store type regression fix
|
||||
|
||||
R4 accidentally restored an older `@wrnexus/store` type surface while changing the syntax package import boundary.
|
||||
|
||||
## Corrected
|
||||
|
||||
- Shared, client, and server store state now use separate generic types.
|
||||
- `StoreCombinedState<S, CS, SS>` is used by computed values, actions, persistence, and lifecycle hooks.
|
||||
- Store action inference defaults to callable `StoreFunction` values instead of `never`.
|
||||
- `createClientState` and `createServerState` no longer have to overlap with shared state.
|
||||
- The internal initialization promise is named `whenReady`, allowing application state to use `ready` safely.
|
||||
- Generated browser stores also expose `whenReady` rather than reserving `ready`.
|
||||
- `validate:0.6` now guards all of these signatures to prevent regression.
|
||||
|
||||
## Focused validation
|
||||
|
||||
- `packages/store/test/store.test.ts` passes strict TypeScript checking with a local `bun:test` type shim.
|
||||
- Runtime probe passes client/server action selection, computed values, client-only state, server-state serialization exclusion, and a boolean state field named `ready`.
|
||||
- VS Code tests: 28 passed.
|
||||
- VS Code validation: all checks passed.
|
||||
- Repository v0.6 validator: 14 passed, 0 failed; only the expected local Bun availability warning remains in the packaging environment.
|
||||
@@ -1,53 +0,0 @@
|
||||
# WRNexusJS v0.6.0 R7 test-cascade fixes
|
||||
|
||||
This revision addresses the 71 failures reported after running `bun run check` on R6.
|
||||
|
||||
## Root fixes
|
||||
|
||||
1. **Synchronous CSR hydration restored**
|
||||
- Scopes without a browser module hydrate immediately again.
|
||||
- The unresolved `__WRNEXUS_CLIENT_MODULE__` placeholder used by direct compiler/test rendering also hydrates inline behavior immediately.
|
||||
- Real resolved browser modules still load asynchronously before scope setup.
|
||||
|
||||
2. **Component event target selection corrected**
|
||||
- A scope with its own `data-wrn-events` now uses itself before searching descendants.
|
||||
|
||||
3. **Lifecycle validation restored**
|
||||
- Component lifecycle supports `mount`, `update`, and `unmount`.
|
||||
- Compatibility aliases `clientInit` and `dispose` map to mount/unmount.
|
||||
- Unknown component and store lifecycle hooks fail parsing.
|
||||
|
||||
4. **Browser binding collisions fixed**
|
||||
- Runtime names such as `output`, `server`, `props`, and `refs` are not generated as implicit state/prop aliases.
|
||||
- Function parameters may still intentionally use those names.
|
||||
|
||||
5. **Synchronous page exports preserved**
|
||||
- Pages remain synchronous unless imported stores require `await`.
|
||||
- This restores in-process WRN HMR module behavior.
|
||||
|
||||
6. **Store dispose lifecycle mutations fixed**
|
||||
- `dispose` now runs through the same internal mutation guard as initialization and hydration hooks.
|
||||
|
||||
7. **Typed UI contract tests updated**
|
||||
- Tests inspect parsed typed props and `outputs {}` declarations instead of matching legacy untyped declarations and `@event` lines.
|
||||
- Dropdown danger styling is asserted through its canonical `data-danger="true"` state.
|
||||
|
||||
8. **Component reference generation corrected**
|
||||
- Output payload parsing now supports nested parentheses and object/union types.
|
||||
- RangeSlider now documents `input`, `change`, `focus`, and `blur`.
|
||||
- FeatureGrid now includes the universal `size` and `color` props.
|
||||
|
||||
## Focused validation performed
|
||||
|
||||
- 250 WRN UI/showcase files parsed, generated, and browser-module syntax checked.
|
||||
- 108 UI browser modules generated without syntax errors.
|
||||
- Unknown lifecycle hook rejection passed.
|
||||
- Reserved output/prop browser codegen probe passed.
|
||||
- Synchronous non-store page generation probe passed.
|
||||
- Store clientInit/dispose mutation probe passed.
|
||||
- UI component reference exactly matches parsed public outputs.
|
||||
- Component reference regeneration is idempotent.
|
||||
- VS Code extension: 28 tests passed and all validation checks passed.
|
||||
- `node scripts/validate-0.6.mjs`: 15 passed, 0 failed; Bun warning only.
|
||||
|
||||
The complete Bun workspace test suite must still be run on a machine with installed dependencies.
|
||||
@@ -1,28 +0,0 @@
|
||||
# WRNexusJS v0.6.0 R8 UI compile fixes
|
||||
|
||||
R8 fixes the remaining UI test cascade reported after R7.
|
||||
|
||||
## Root causes
|
||||
|
||||
1. Readonly-prop diagnostics treated comparison operators such as `===` as assignments.
|
||||
2. Text inside JavaScript string literals, including selectors such as `input[name='...']`, was scanned as executable assignment syntax.
|
||||
3. Function parameters that intentionally shadowed prop names were treated as mutations of those props.
|
||||
4. `component-catalog.json` did not contain four bundled declarations even though the generated component reference did.
|
||||
|
||||
## Changes
|
||||
|
||||
- Added JavaScript trivia masking before readonly-prop mutation analysis.
|
||||
- Assignment recognition now distinguishes `=`, compound assignments, `++`, and `--` from `==`, `===`, `=>`, and comparisons.
|
||||
- `props.name = ...` is still rejected.
|
||||
- Bare prop assignment is ignored when the name is a function parameter or local variable.
|
||||
- Syntax and typecheck packages now share the same readonly-prop mutation detector.
|
||||
- Component catalog generation now includes every bundled component while preserving existing metadata.
|
||||
- Added R8 validation gates that compile all 108 UI components, verify native attribute forwarding, and compare catalog/reference declarations.
|
||||
|
||||
## Focused validation
|
||||
|
||||
- 108/108 UI components compile.
|
||||
- 108/108 UI components include native attribute forwarding.
|
||||
- Component catalog: 108 declarations.
|
||||
- Component reference: 108 declarations.
|
||||
- Generated showcase: 108 detail pages, 417 demos, 11 categories.
|
||||
@@ -1,25 +0,0 @@
|
||||
# WRNexusJS v0.6.0 R9 — ToggleCount animation stability
|
||||
|
||||
R9 fixes the final full-suite failure in `ToggleCount`.
|
||||
|
||||
## Root cause
|
||||
|
||||
The previous animation scheduled the next frame only after the preceding timeout fired. Under a heavily loaded full test suite, timer drift accumulated across every frame and the animation could finish later than `animationDuration`.
|
||||
|
||||
## Fix
|
||||
|
||||
- Every animation frame is scheduled immediately against its absolute offset within `animationDuration`.
|
||||
- The displayed value remains at the previous value synchronously after a toggle.
|
||||
- Every intermediate value is rounded to an integer.
|
||||
- The final value is independently scheduled at exactly `animationDuration`.
|
||||
- Animation tokens prevent stale timers from an earlier toggle overwriting a newer selection.
|
||||
- Browser modules now bind peer client/shared functions through `context.functions`, so generated functions can safely call one another.
|
||||
- `animateValueAt` is classified as a client function because it touches DOM nodes and schedules browser timers.
|
||||
|
||||
## Local validation
|
||||
|
||||
```powershell
|
||||
bun install
|
||||
bun run validate:0.6
|
||||
bun run check
|
||||
```
|
||||
@@ -1,18 +0,0 @@
|
||||
# WRNexusJS 0.6.0 syntax package root export fix
|
||||
|
||||
The v0.6 parser helpers are implementation details of `@wrnexus/syntax`, not a separate public versioned package surface.
|
||||
|
||||
## Canonical usage
|
||||
|
||||
```ts
|
||||
import {
|
||||
parseRuntimeFunctions,
|
||||
parseStateDeclarations,
|
||||
parseOutputs,
|
||||
stripRuntimeFunctionModifiers,
|
||||
} from "@wrnexus/syntax";
|
||||
```
|
||||
|
||||
The compiler now imports `stripRuntimeFunctionModifiers` from `@wrnexus/syntax`. The `./v060` package export and the VS Code standalone-bundle alias were removed. `packages/syntax/src/v060.ts` remains an internal source module and its public APIs are re-exported by `packages/syntax/src/index.ts`.
|
||||
|
||||
This keeps application and framework imports stable across future releases and avoids exposing version numbers in package import paths.
|
||||
@@ -1,17 +0,0 @@
|
||||
# WRNexusJS v0.6.0 typecheck correction R2
|
||||
|
||||
This correction addresses the first local Bun validation failures reported after the focused issues 1-7 build.
|
||||
|
||||
## Corrected
|
||||
|
||||
- Added a valid root `.vscode/settings.json` so the v0.6 JSON validator succeeds.
|
||||
- Store definitions now model shared, client-only, and server-only state as separate inferred object types.
|
||||
- Store actions no longer infer as `never`; action methods remain callable from returned store instances.
|
||||
- Renamed the internal initialization promise from `ready` to `whenReady` so application state may safely declare a field named `ready`.
|
||||
- Updated generated browser-store modules to use the same collision-safe `whenReady` field.
|
||||
|
||||
## Focused validation
|
||||
|
||||
- `packages/store/test/store.test.ts` passes strict TypeScript checking with a local Bun test declaration shim.
|
||||
- A live Node type-stripping probe passed server/client action dispatch, computed state, server-state exclusion, and the `ready` state collision case.
|
||||
- `node scripts/validate-0.6.mjs` reports 12 passed, 0 failed in the packaging environment; only the expected warning remains because Bun is unavailable there.
|
||||
@@ -1,11 +0,0 @@
|
||||
# WRNexusJS 0.6.0 VS Code compiler bundle fix
|
||||
|
||||
The standalone VS Code compiler bundle must resolve the public syntax package through:
|
||||
|
||||
```text
|
||||
@wrnexus/syntax -> packages/syntax/src/index.ts
|
||||
```
|
||||
|
||||
The v0.6 syntax helpers are re-exported by `packages/syntax/src/index.ts`. No public or bundled import uses a version-specific `@wrnexus/syntax/v060` path.
|
||||
|
||||
This makes the editor compiler self-contained while keeping the package API stable.
|
||||
@@ -1,30 +0,0 @@
|
||||
# WRNexusJS 0.7.0 R1 — Local environment audit boundary
|
||||
|
||||
## Problem
|
||||
|
||||
The security audit recursively scanned every file in the working directory. Local, ignored environment files such as `examples/basic-app/.env` and `.env.uat` therefore failed the release audit even though they were not tracked by Git and would not be published.
|
||||
|
||||
## Fix
|
||||
|
||||
- Release security checks now scan `git ls-files -z` when Git metadata is available.
|
||||
- Secret-like files tracked by Git still fail with `SEC-NO-TRACKED-SECRET-FILES`.
|
||||
- Ignored or untracked local secret files produce `SEC-LOCAL-SECRET-FILES` warnings only.
|
||||
- Source archives without `.git` metadata still scan every included file.
|
||||
- The JSON report schema is updated to version 2 and includes warnings separately from errors.
|
||||
|
||||
## Existing repositories
|
||||
|
||||
If old `.env` files are still tracked, remove them from the Git index while retaining local copies:
|
||||
|
||||
```powershell
|
||||
git rm --cached examples/basic-app/.env examples/basic-app/.env.uat
|
||||
git add examples/basic-app/.env.example examples/basic-app/.env.uat.example .gitignore
|
||||
git commit -m "security(examples): stop tracking local environment files"
|
||||
```
|
||||
|
||||
The root `.gitignore` now also explicitly allows environment templates for named environments:
|
||||
|
||||
```gitignore
|
||||
!.env.example
|
||||
!.env.*.example
|
||||
```
|
||||
@@ -1,19 +0,0 @@
|
||||
# WRNexusJS 0.7.0 R2 — Root Typecheck Fix
|
||||
|
||||
## Cause
|
||||
|
||||
The R1 source archive accidentally contained two temporary files used only for a dependency-free focused compile:
|
||||
|
||||
- `focus-shims.d.ts`
|
||||
- `tsconfig.focus.json`
|
||||
|
||||
The root `tsc --noEmit` command automatically included `focus-shims.d.ts`. Its intentionally broad declarations replaced or merged with the installed Bun, Node.js, TypeScript, Happy DOM, filesystem, child-process, and build-tool declarations. That caused callback parameters to become implicit `any`, removed real Bun APIs and generics, and produced the reported 78 errors across otherwise valid source files.
|
||||
|
||||
## Fix
|
||||
|
||||
- Removed `focus-shims.d.ts` from the source tree.
|
||||
- Removed `tsconfig.focus.json` from the source tree.
|
||||
- Added a `validate:0.7` release guard that fails if either temporary file is present.
|
||||
- Re-ran the root TypeScript program with typed Bun/Node/TypeScript module declarations.
|
||||
|
||||
No public framework API or runtime behavior changed in this correction.
|
||||
@@ -1,43 +0,0 @@
|
||||
# WRNexusJS 0.7.0 R3 — Workspace Repair and Typecheck Isolation
|
||||
|
||||
## Problem
|
||||
|
||||
Applying a source ZIP over an existing Git checkout cannot delete files or change the Git index. Two local focused-typecheck files and two previously tracked environment files therefore survived earlier archive updates:
|
||||
|
||||
- `focus-shims.d.ts`
|
||||
- `tsconfig.focus.json`
|
||||
- `examples/basic-app/.env`
|
||||
- `examples/basic-app/.env.uat`
|
||||
|
||||
The ambient declarations in `focus-shims.d.ts` replaced or weakened the installed Bun, Node.js, TypeScript, filesystem, child-process, Happy DOM, and tsup typings. That produced dozens of false `implicit any`, missing namespace member, and untyped `Bun.serve` errors.
|
||||
|
||||
## Corrections
|
||||
|
||||
1. Root `tsconfig.json` now has explicit include/exclude rules and always excludes focused-typecheck helper files.
|
||||
2. Added `bun run repair:workspace` to:
|
||||
- remove the two temporary typecheck files;
|
||||
- remove unsafe secret-like files and temporary shims from Git tracking;
|
||||
- preserve local `.env` file contents;
|
||||
- ensure the required `.gitignore` rules exist.
|
||||
3. Added `bun run check:workspace` for non-mutating CI/release verification.
|
||||
4. `validate:0.7` warns about local leftovers instead of allowing a TypeScript cascade, and verifies that the root typecheck excludes them.
|
||||
5. The security audit blocks tracked typecheck shims and tracked environment files with the repair command in its error message.
|
||||
6. Release prepare/publish now runs `check:workspace` before validation.
|
||||
7. Package staging rejects `focus-shims.d.ts` and `tsconfig.focus.json`.
|
||||
8. Added an executable Git regression test proving local environment values are retained while unsafe files are removed from tracking.
|
||||
|
||||
## Required one-time command for an existing checkout
|
||||
|
||||
```powershell
|
||||
bun run repair:workspace
|
||||
```
|
||||
|
||||
Then review and commit the index changes:
|
||||
|
||||
```powershell
|
||||
git status
|
||||
git add .gitignore package.json tsconfig.json scripts
|
||||
|
||||
git commit -m "fix(tooling): isolate temporary typecheck files and repair workspace state"
|
||||
git push origin main
|
||||
```
|
||||
@@ -1,29 +0,0 @@
|
||||
# WRNexusJS 0.7.0 R4 — Happy DOM Event Type Correction
|
||||
|
||||
## Problem
|
||||
|
||||
`packages/validation/test/validation.test.ts` returned the browser DOM `Event` type from its synthetic event helper. Happy DOM elements require Happy DOM's own event class, so strict TypeScript reported two incompatible `dispatchEvent()` calls.
|
||||
|
||||
## Fix
|
||||
|
||||
The helper now derives its event type directly from Happy DOM's own `document.dispatchEvent` parameter:
|
||||
|
||||
```ts
|
||||
type HappyDOMEvent = Parameters<Window["document"]["dispatchEvent"]>[0];
|
||||
type HappyDOMEventConstructor = new (type: string, init?: EventInit) => HappyDOMEvent;
|
||||
|
||||
function windowEvent(win: Window, type: string, init?: EventInit): HappyDOMEvent {
|
||||
const EventConstructor = (win as unknown as { Event: HappyDOMEventConstructor }).Event;
|
||||
return new EventConstructor(type, init);
|
||||
}
|
||||
```
|
||||
|
||||
This keeps browser DOM tests and Happy DOM tests type-safe without using an unsafe browser `Event` return type.
|
||||
|
||||
## Workspace repair
|
||||
|
||||
The two `.env` files and temporary focused-typecheck files shown by `check:workspace` are local leftovers. Run `bun run repair:workspace` once before the security audit. The command preserves local `.env` contents while removing unsafe files from Git tracking.
|
||||
|
||||
## Additional improvement
|
||||
|
||||
`check:workspace` now says `would remove` in check mode, instead of incorrectly implying files were already removed.
|
||||
@@ -1,21 +0,0 @@
|
||||
# WRNexusJS 0.7.0 R5 — Happy DOM element/event type alignment
|
||||
|
||||
## Problem
|
||||
|
||||
R4 returned Happy DOM `Event` objects from the test helper, but two inputs were still cast to the browser DOM `HTMLInputElement` type. TypeScript therefore rejected the Happy DOM event at `dispatchEvent()`.
|
||||
|
||||
## Correction
|
||||
|
||||
`packages/validation/test/validation.test.ts` now imports and consistently uses Happy DOM types for:
|
||||
|
||||
- `Event`
|
||||
- `IEventInit`
|
||||
- `HTMLElement`
|
||||
- `HTMLInputElement`
|
||||
- `HTMLFormElement`
|
||||
|
||||
The input, field, error and form nodes no longer use browser DOM global casts. Submit events also no longer cast back to the browser DOM `Event` type.
|
||||
|
||||
## Regression gate
|
||||
|
||||
`validate:0.7` now verifies that the validation tests use one Happy DOM type system and rejects the old `as unknown as HTMLInputElement`, `HTMLFormElement`, or `HTMLElement` casts.
|
||||
@@ -1,26 +0,0 @@
|
||||
# WRNexusJS 0.7.0 R6 - Lint cleanup
|
||||
|
||||
R6 fixes the remaining root ESLint findings reported after R5.
|
||||
|
||||
## Corrections
|
||||
|
||||
- Replaced control-character regular expressions in compiler, security URL validation, and syntax diagnostics with explicit ASCII character-code checks.
|
||||
- `renderStoreHydration()` now actually uses `serializeForHtml()` for bounded, prototype-safe, HTML-safe hydration JSON.
|
||||
- SBOM parse errors preserve the original caught error through `ErrorOptions.cause`.
|
||||
- The workspace repair regression imports `node:process` explicitly.
|
||||
- ESLint and Prettier ignore local `focus-shims.d.ts` and `tsconfig.focus.json` leftovers until `bun run repair:workspace` removes them.
|
||||
- Added `validate:0.7` guards for every R6 regression.
|
||||
|
||||
## Security behavior retained
|
||||
|
||||
URLs containing ASCII whitespace, C0 control characters, or DEL are rejected by `@wrnexus/security`. Static compiler and syntax checks continue to normalize embedded C0 characters before detecting dangerous protocols such as `javascript:`.
|
||||
|
||||
## Local validation
|
||||
|
||||
Run on the target Bun workspace:
|
||||
|
||||
```powershell
|
||||
bun run repair:workspace
|
||||
bun install
|
||||
bun run check
|
||||
```
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"release": "0.6.0",
|
||||
"scope": "Specification issues 1 through 7",
|
||||
"status": "passed-focused-validation",
|
||||
"fullBunSuiteExecuted": false,
|
||||
"results": {
|
||||
"frameworkPackages": 33,
|
||||
"uiComponents": 108,
|
||||
"generatedUiBrowserModules": 108,
|
||||
"browserModuleSyntaxFailures": 0,
|
||||
"wrnFilesParsed": 250,
|
||||
"wrnParseFailures": 0,
|
||||
"unknownTypesInsideUiOutputContracts": 0,
|
||||
"showcaseComponentPages": 108,
|
||||
"showcaseLiveDemos": 417,
|
||||
"showcaseCategories": 11,
|
||||
"vscodeNodeTestsPassed": 28,
|
||||
"repositoryValidatorPassed": 12,
|
||||
"repositoryValidatorWarnings": 1,
|
||||
"repositoryValidatorFailed": 0
|
||||
},
|
||||
"focusedProbes": {
|
||||
"browserStoreRpc": "passed",
|
||||
"persistenceMigration": "passed",
|
||||
"persistenceValidation": "passed",
|
||||
"storeHmr": "passed",
|
||||
"legacyImportMode": "passed",
|
||||
"compatibleImportMode": "passed",
|
||||
"explicitImportMode": "passed",
|
||||
"restrictedComponentRpcManifest": "passed",
|
||||
"restrictedStoreRpcManifest": "passed",
|
||||
"generatedServerStoreTypeScript": "passed"
|
||||
},
|
||||
"remainingReleaseGate": [
|
||||
"bun install",
|
||||
"bun run validate:0.6",
|
||||
"bun run check",
|
||||
"bun run --cwd examples/component-showcase check"
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"release": "0.7.0-r1",
|
||||
"fix": "Git-tracked secret-file audit boundary",
|
||||
"results": {
|
||||
"validate_0_7": {
|
||||
"passed": 35,
|
||||
"failed": 0
|
||||
},
|
||||
"clean_archive_security_audit": "passed",
|
||||
"ignored_local_env_files": {
|
||||
"result": "warning-only",
|
||||
"exitCode": 0
|
||||
},
|
||||
"git_tracked_env_files": {
|
||||
"result": "blocked",
|
||||
"exitCode": 1
|
||||
},
|
||||
"javascript_syntax": "passed"
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"release": "0.7.0-r2",
|
||||
"reportedTypeScriptErrors": 78,
|
||||
"reportedFiles": 28,
|
||||
"rootCause": [
|
||||
"focus-shims.d.ts was accidentally included in the release source and automatically loaded by the root TypeScript program",
|
||||
"tsconfig.focus.json was a temporary packaging-only configuration and should not have been distributed"
|
||||
],
|
||||
"removedFiles": ["focus-shims.d.ts", "tsconfig.focus.json"],
|
||||
"hardening": [
|
||||
"validate:0.7 now rejects temporary focused-typecheck files",
|
||||
"compressed response bodies are copied into a concrete ArrayBuffer for TypeScript 5.9 DOM compatibility",
|
||||
"Happy DOM event constructors are accessed through explicit typed window shapes",
|
||||
"Redis tests use the typed Bun global directly",
|
||||
"package publishing imports node:process explicitly"
|
||||
],
|
||||
"validation": {
|
||||
"rootTypeScriptProgram": "passed",
|
||||
"validate07": { "passed": 36, "failed": 0 },
|
||||
"securityFramework": { "passed": 10, "failed": 0 },
|
||||
"validate06Compatibility": { "passed": 20, "warnings": 1, "failed": 0 },
|
||||
"vscodeTests": { "passed": 28, "failed": 0 },
|
||||
"vscodeValidation": "passed",
|
||||
"temporaryFileGuard": {
|
||||
"temporaryFilePresentExitCode": 1,
|
||||
"temporaryFileAbsentExitCode": 0
|
||||
}
|
||||
},
|
||||
"localAuthoritativeCommand": "bun run check"
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"release": "0.7.0-r3",
|
||||
"focus": "workspace repair, tracked secret cleanup, and root typecheck isolation",
|
||||
"results": {
|
||||
"validate_0_7": {
|
||||
"passed": 37,
|
||||
"warnings": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"security_framework": "passed",
|
||||
"validate_0_6_compatibility": {
|
||||
"passed": 20,
|
||||
"warnings": 1,
|
||||
"failed": 0
|
||||
},
|
||||
"workspace_repair_git_regression": "passed",
|
||||
"package_staging_integrity": "passed",
|
||||
"temporary_shim_excluded_from_root_typescript_program": "passed",
|
||||
"vscode_tests": {
|
||||
"passed": 28,
|
||||
"failed": 0
|
||||
},
|
||||
"vscode_extension_validation": "passed",
|
||||
"sbom_components": 339
|
||||
},
|
||||
"local_full_bun_check_required": true,
|
||||
"required_command": "bun run repair:workspace && bun run check"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"release": "0.7.0-r4",
|
||||
"fix": "Happy DOM synthetic event type compatibility",
|
||||
"results": {
|
||||
"validate_0_7": {
|
||||
"passed": 38,
|
||||
"warnings": 0,
|
||||
"failed": 0
|
||||
},
|
||||
"security_framework_audit": "passed",
|
||||
"workspace_repair_regression": "passed",
|
||||
"validate_0_6_compatibility": {
|
||||
"passed": 20,
|
||||
"warnings": 1,
|
||||
"failed": 0
|
||||
},
|
||||
"vscode_tests": {
|
||||
"passed": 28,
|
||||
"failed": 0
|
||||
},
|
||||
"vscode_extension_validation": "passed",
|
||||
"happy_dom_event_type_structural_compile": "passed",
|
||||
"zip_integrity": "passed"
|
||||
},
|
||||
"environment_limit": "Bun and the repository dependency tree are unavailable in the packaging environment, so the complete bun run check remains required on the target Windows workspace."
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user