refactor: migrate legacy wire namespace to wrn
This commit is contained in:
@@ -71,7 +71,7 @@ Versioned, timestamped files with `up`/`down`. Two authoring modes:
|
||||
last applied state and emits the SQL for you to review.
|
||||
|
||||
`wrnexus db migrate` applies pending migrations inside a transaction and records
|
||||
them in a `_wire_migrations` table; `rollback` runs `down`; `status` lists state.
|
||||
them in a `_wrn_migrations` table; `rollback` runs `down`; `status` lists state.
|
||||
Dialect-aware SQL generation for create/alter/index.
|
||||
|
||||
## 5. sqlc-like typed queries (`app/db/queries/*.sql` → generated TS)
|
||||
@@ -130,7 +130,7 @@ A local Artifact/web UI to browse tables + run read-only queries in dev.
|
||||
1. **Core + SQLite adapter** — `@wrnexus/db`, `table()`/`v` model layer, `Driver`
|
||||
interface, `bun:sqlite` adapter, `db.one/all/exec/tx`, result→schema mapping.
|
||||
(SQLite first = zero external service to test against.)
|
||||
2. **Migrations** ✅ done — runner + `_wire_migrations`, `wrnexus db migrate/rollback/status/new`,
|
||||
2. **Migrations** ✅ done — runner + `_wrn_migrations`, `wrnexus db migrate/rollback/status/new`,
|
||||
`.sql` (`-- +up`/`-- +down`) + `--from-models` DDL generation. (Model-diffing beyond the
|
||||
initial CREATE TABLE is a later enhancement; needs topological ordering by FK refs.)
|
||||
3. **sqlc-like generator** ✅ done — `.sql` parsing (`-- name: X :one/:many/:exec`),
|
||||
@@ -144,7 +144,7 @@ A local Artifact/web UI to browse tables + run read-only queries in dev.
|
||||
5. **MongoDB adapter** ✅ built (unverified) — `@wrnexus/db/mongo`: a document
|
||||
collection API (`find/findOne/insert/update/delete/count`) keyed by models, reads
|
||||
coerced through `model.parse`, `_id`→`id`. Lazily imports `mongodb` (optional dep).
|
||||
Not wired into the SQL singleton/migrations/generator (Mongo isn't SQL). Needs
|
||||
Not connected into the SQL singleton/migrations/generator (Mongo isn't SQL). Needs
|
||||
`mongodb` + a server to test.
|
||||
6. **Polish** — ✅ FK-topological ordering in `--from-models` (referenced tables
|
||||
first). Deferred: `db studio` (web UI), richer relations/joins helpers.
|
||||
|
||||
+18
-18
@@ -6,7 +6,7 @@ validation. Built **foundation-first** so each subsystem lands solid and tested.
|
||||
Order (one subsystem per turn):
|
||||
|
||||
1. **Theme system** ✅ done — design tokens, SSR + client, no-flash, overridable.
|
||||
2. **Wire UI** (`@wrnexus/ui`) ✅ done — 17 components + layout primitives as `.wrn`,
|
||||
2. **WrNexus UI** (`@wrnexus/ui`) ✅ done — 17 components + layout primitives as `.wrn`,
|
||||
consuming theme tokens, overridable 4 ways; auto-discovered + `wrnexus eject`.
|
||||
3. **Layout system** ✅ done (folded into turn 2) — layout primitives (Container,
|
||||
Stack, HStack, Grid, Divider, Spacer) + page layouts (`app/layout.wrn` + `<slot>`).
|
||||
@@ -16,13 +16,13 @@ Order (one subsystem per turn):
|
||||
## 4. i18n (done)
|
||||
|
||||
- `@wrnexus/i18n`: locales in `app/locales/<lang>.json` (nested/flat keys, `{param}`
|
||||
interpolation). Per request the lang resolves from the `wire-lang` cookie →
|
||||
interpolation). Per request the lang resolves from the `wrn-lang` cookie →
|
||||
Accept-Language → config default (`wrnexus.config.ts` `i18n.default`).
|
||||
- **Server:** `ctx.lang` + `ctx.t(key, params)` on every request (pages + API).
|
||||
- **Views:** `{t:key}` text sugar compiles to a `<span data-t="key">` marker;
|
||||
`t:<attr>="key"` translates an attribute. `translateHtml` resolves both on the
|
||||
final HTML (after components + layout), and `<html lang>` is set (no flash).
|
||||
- **Switch:** `[data-wire-lang-set="es"]` (or `<select data-wire-lang>`) → the tiny
|
||||
- **Switch:** `[data-wrn-lang-set="es"]` (or `<select data-wrn-lang>`) → the tiny
|
||||
`/__wrnexus/i18n.js` sets the cookie + reloads; injected only when present.
|
||||
- Fallback chain: current lang → default lang → the key itself.
|
||||
|
||||
@@ -37,28 +37,28 @@ password: v.string().min(8) })`). `.parse(data)` runs server-side (coerce + erro
|
||||
parseBody(login, ctx.req)` → 400 `{errors}` or `{value}`. **Form:** `<form
|
||||
data-schema="login">` with `[data-error="field"]` spans.
|
||||
- The framework bakes all descriptors into `/__wrnexus/schemas.js`
|
||||
(`window.__wireSchemas`) and ships an eval-free `/__wrnexus/validate.js` that validates
|
||||
(`window.__wrnSchemas`) and ships an eval-free `/__wrnexus/validate.js` that validates
|
||||
`form[data-schema]` on submit/blur — injected only when a page has `data-schema`.
|
||||
|
||||
## 2 + 5. Wire UI + layout (done)
|
||||
## 2 + 5. WrNexus UI + layout (done)
|
||||
|
||||
- **Distribution:** `@wrnexus/ui` ships `.wrn` components under `components/`; the
|
||||
framework auto-discovers them (router `componentDirs`), app/components shadow by
|
||||
name, and `wrnexus eject <name>` copies one into the app to own it.
|
||||
- **Codegen:** components now bake prop-driven text/attributes server-side
|
||||
(`__wireHtml`/`__wireAttr`) so static components ship zero JS; state-referencing
|
||||
(`__wrnHtml`/`__wrnAttr`) so static components ship zero JS; state-referencing
|
||||
text stays a reactive mustache. Reserved-word props (`class`) get `__p_` refs;
|
||||
attribute `{expr}` enables `variant`/`size`/`class` composition.
|
||||
- **Slots:** `renderComponents` is nesting-aware and fills `<slot>` with mount
|
||||
children — this also powers page layouts (`app/layout.wrn` wraps every page).
|
||||
- **Styles:** one themed stylesheet `/__wrnexus/ui.css` (all `.wire-*` classes use
|
||||
`var(--wire-*)`), linked theme → ui → app so app CSS overrides win.
|
||||
- **Styles:** one themed stylesheet `/__wrnexus/ui.css` (all `.wrn-*` classes use
|
||||
`var(--wrn-*)`), linked theme → ui → app so app CSS overrides win.
|
||||
|
||||
---
|
||||
|
||||
## 1. Theme system (this turn)
|
||||
|
||||
**Tokens.** Kebab-case token keys become `--wire-<key>` CSS custom properties.
|
||||
**Tokens.** Kebab-case token keys become `--wrn-<key>` CSS custom properties.
|
||||
Framework ships default `light` + `dark` token sets (colors, radius, etc.); user
|
||||
config deep-merges over them and may add new themes.
|
||||
|
||||
@@ -79,23 +79,23 @@ and component styles can read/override it:
|
||||
|
||||
```css
|
||||
:root{ /* default theme tokens */ }
|
||||
[data-theme="light"]{ --wire-color-primary:#2563eb; color-scheme:light; ... }
|
||||
[data-theme="dark"]{ --wire-color-primary:#6c8cff; color-scheme:dark; ... }
|
||||
[data-theme="light"]{ --wrn-color-primary:#2563eb; color-scheme:light; ... }
|
||||
[data-theme="dark"]{ --wrn-color-primary:#6c8cff; color-scheme:dark; ... }
|
||||
```
|
||||
|
||||
**SSR (no flash).** Server reads the `wire-theme` cookie (falls back to config
|
||||
**SSR (no flash).** Server reads the `wrn-theme` cookie (falls back to config
|
||||
default) and renders `<html data-theme="…">`, so the correct theme paints on the
|
||||
first byte. The theme name is validated against configured names.
|
||||
|
||||
**Client** (`/__wrnexus/theme.js`, injected only when a page has a toggle):
|
||||
`window.wireTheme.{get,set,toggle}` — sets `data-theme` on `<html>`, persists the
|
||||
`wire-theme` cookie, and binds `[data-wire-theme-toggle]` /
|
||||
`[data-wire-theme-set="name"]` elements so a `.wrn` component can switch themes
|
||||
`window.wrnTheme.{get,set,toggle}` — sets `data-theme` on `<html>`, persists the
|
||||
`wrn-theme` cookie, and binds `[data-wrn-theme-toggle]` /
|
||||
`[data-wrn-theme-set="name"]` elements so a `.wrn` component can switch themes
|
||||
without writing JS.
|
||||
|
||||
**Override.** Users change tokens in config, or redefine any `--wire-*` variable
|
||||
in their own CSS (loaded after theme.css → wins). Wire UI components consume
|
||||
`var(--wire-*)`, so overriding a token restyles every component at once.
|
||||
**Override.** Users change tokens in config, or redefine any `--wrn-*` variable
|
||||
in their own CSS (loaded after theme.css → wins). WrNexus UI components consume
|
||||
`var(--wrn-*)`, so overriding a token restyles every component at once.
|
||||
|
||||
**Integration points:** `@wrnexus/styles` (theme module), `@wrnexus/ssr`
|
||||
(`renderDocument` html attrs), dev runtime/assets, prod server, `wrnexus build`
|
||||
@@ -270,7 +270,7 @@ disclosing policy structure to unauthenticated callers. This becomes opt-in via
|
||||
## Build order
|
||||
|
||||
1. `registry.ts`, `catalog.ts`, `store.ts` (memory), `engine.ts`, extended `guards.ts`.
|
||||
2. Resource-level policies wired to `filterAuthorized`; `audit.ts`.
|
||||
2. Resource-level policies connected to `filterAuthorized`; `audit.ts`.
|
||||
3. `db.ts` adapter, migrations, `wrnexus authz init`, codegen, `wrnexus authz list`.
|
||||
4. `.wrn` view integration (`can()` inside `{#if}`).
|
||||
5. Admin UI components.
|
||||
|
||||
@@ -3227,7 +3227,7 @@ export async function runAuthzCommand(
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Wire it into the CLI**
|
||||
- [ ] **Step 4: WrNexus it into the CLI**
|
||||
|
||||
In `packages/cli/src/index.ts`, add immediately after the `case "db"` block:
|
||||
|
||||
@@ -3257,7 +3257,7 @@ git commit -m "feat(cli): add wrnexus authz list/generate/init"
|
||||
|
||||
---
|
||||
|
||||
## Task 14: Wire the catalog into dev and prod boot
|
||||
## Task 14: WrNexus the catalog into dev and prod boot
|
||||
|
||||
**Files:**
|
||||
|
||||
@@ -3375,7 +3375,7 @@ git commit -m "feat(dev-server): load the authz catalog at boot"
|
||||
|
||||
---
|
||||
|
||||
## Task 15: Example app wiring and documentation
|
||||
## Task 15: Example app integration and documentation
|
||||
|
||||
**Files:**
|
||||
|
||||
|
||||
@@ -418,7 +418,7 @@ export class ServiceError extends Error {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a wire result. The message is replaced unless explicitly
|
||||
* Convert to a wrn result. The message is replaced unless explicitly
|
||||
* exposed: a handler's error text routinely names tables, hosts, or
|
||||
* credentials, and this value crosses an app boundary.
|
||||
*/
|
||||
@@ -1296,7 +1296,7 @@ describe("implement", () => {
|
||||
expect(result).toEqual({ ok: true, value: { sum: 3 } });
|
||||
});
|
||||
|
||||
test("coerces through the schema rather than trusting the wire", async () => {
|
||||
test("coerces through the schema rather than trusting the wrn", async () => {
|
||||
// "3" arrives as a string over JSON from a sloppy caller; the schema coerces.
|
||||
const result = await impl().invoke("add", { a: 1, b: "3" });
|
||||
expect(result).toEqual({ ok: true, value: { sum: 4 } });
|
||||
@@ -1360,7 +1360,7 @@ describe("implement", () => {
|
||||
|
||||
test("a guarded procedure with NO permission checker configured denies", async () => {
|
||||
// Fail closed: a contract that declares a permission must not run
|
||||
// unguarded just because the app forgot to wire a checker.
|
||||
// unguarded just because the app forgot to wrn a checker.
|
||||
const result = await impl(undefined).invoke("guarded", {});
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.code).toBe(RPC_ERROR_CODES.denied);
|
||||
@@ -1439,7 +1439,7 @@ export interface ImplementOptions {
|
||||
/** This app's name, used as the identity token's expected audience. */
|
||||
selfApp: string;
|
||||
/**
|
||||
* Resolve whether the subject holds a permission. Wire this to
|
||||
* Resolve whether the subject holds a permission. WrNexus this to
|
||||
* `@wrnexus/authz`. A procedure that DECLARES a permission but finds no
|
||||
* checker configured is denied — never run unguarded.
|
||||
*/
|
||||
@@ -2345,7 +2345,7 @@ export async function handleRpcRequest(
|
||||
Run: `bun test packages/dev-server/test/rpc-endpoint.test.ts`
|
||||
Expected: PASS, 9 tests
|
||||
|
||||
- [ ] **Step 5: Wire the dispatcher into the request pipeline**
|
||||
- [ ] **Step 5: WrNexus the dispatcher into the request pipeline**
|
||||
|
||||
In `packages/dev-server/src/runtime.ts`, inside the main `fetch` handler, call `handleRpcRequest` BEFORE normal page/API routing and return its result when non-null. Build the `Map<string, ServiceImplementation>` from `router.services` at boot, importing each module's default export. Follow the pattern `loadAppAuthzCatalog` uses for importing discovered modules, including routing hot-reload re-imports through `loadModule`.
|
||||
|
||||
@@ -2411,7 +2411,7 @@ const billing = defineService({
|
||||
},
|
||||
});
|
||||
|
||||
function wire(allowed: boolean) {
|
||||
function wrn(allowed: boolean) {
|
||||
const service = implement(
|
||||
billing,
|
||||
{
|
||||
@@ -2434,7 +2434,7 @@ describe("end to end", () => {
|
||||
const client = serviceClient(billing, {
|
||||
app: "billing",
|
||||
as: { user: { id: "u1" }, tenant: { id: "acme" }, locals: {} } as unknown as Context,
|
||||
transport: wire(true),
|
||||
transport: wrn(true),
|
||||
});
|
||||
expect(await client.createInvoice({ amountCents: 250 })).toEqual({
|
||||
invoiceId: "inv_250",
|
||||
@@ -2448,7 +2448,7 @@ describe("end to end", () => {
|
||||
const client = serviceClient(billing, {
|
||||
app: "billing",
|
||||
as: { user: { id: "u1" }, locals: {} } as unknown as Context,
|
||||
transport: wire(false),
|
||||
transport: wrn(false),
|
||||
});
|
||||
try {
|
||||
await client.createInvoice({ amountCents: 1 });
|
||||
@@ -2459,7 +2459,7 @@ describe("end to end", () => {
|
||||
});
|
||||
|
||||
test("invalid input never reaches the handler", async () => {
|
||||
const client = serviceClient(billing, { app: "billing", transport: wire(true) });
|
||||
const client = serviceClient(billing, { app: "billing", transport: wrn(true) });
|
||||
try {
|
||||
await client.createInvoice({ amountCents: "abc" } as never);
|
||||
throw new Error("should not reach here");
|
||||
|
||||
Reference in New Issue
Block a user