release: WRNexusJS 0.4.0
This commit is contained in:
+10
-13
@@ -1,19 +1,16 @@
|
||||
# Framework integration
|
||||
|
||||
The current WRNexusJS component scanner always includes `packages/ui/components` and the application’s `app/components`. The installer therefore copies `Captcha.wrn` into `packages/ui/components/Captcha.wrn` while retaining the canonical package copy at `packages/captcha/components/Captcha.wrn`.
|
||||
WRNexusJS 0.4 discovers components and browser runtimes from installed package plugins.
|
||||
|
||||
A future framework improvement can read component directories registered by plugins and pass them to `createRouter({ componentDirs })`. `captchaPlugin()` already exposes the package directory through configuration and plugin metadata, so the package is ready for that change without changing its public API.
|
||||
`@wrnexus/captcha` is a complete example:
|
||||
|
||||
The installer adds these TypeScript aliases:
|
||||
- `packages/captcha/package.json` declares `wrnexus.plugin`.
|
||||
- `captchaPlugin()` contributes its component directory, client runtime, Tailwind source, and DevToolbar audit panel.
|
||||
- `Captcha.wrn` renders `data-wrnexus-runtime="captcha"`.
|
||||
- SSR injects the runtime only when the component appears.
|
||||
- CSR navigation mounts and unmounts the runtime.
|
||||
- Development serves the package source; production emits a hashed chunk.
|
||||
|
||||
```json
|
||||
{
|
||||
"@wrnexus/captcha": ["./packages/captcha/src/index.ts"],
|
||||
"@wrnexus/captcha/server": ["./packages/captcha/src/server/index.ts"],
|
||||
"@wrnexus/captcha/client": ["./packages/captcha/src/client/index.ts"],
|
||||
"@wrnexus/captcha/plugin": ["./packages/captcha/src/plugin.ts"],
|
||||
"@wrnexus/captcha/*": ["./packages/captcha/src/*"]
|
||||
}
|
||||
```
|
||||
There is intentionally no `packages/ui/components/Captcha.wrn`, no showcase-local copy, and no public `captcha.js` file.
|
||||
|
||||
After installation, regenerate the UI component reference so `Captcha` appears in generated component documentation.
|
||||
`integration/platform-upgrade.test.ts` exercises the 0.4 cross-package foundation.
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import {
|
||||
HealthRegistry,
|
||||
ServiceContainer,
|
||||
memoryIdempotencyStore,
|
||||
serviceToken,
|
||||
withIdempotency,
|
||||
} from "@wrnexus/core";
|
||||
import { signal, watch } from "@wrnexus/reactive";
|
||||
import { routeName } from "@wrnexus/router";
|
||||
import { backoffDelay, retry, stableStringify } from "@wrnexus/helpers";
|
||||
import { createDurableQueue } from "@wrnexus/queue";
|
||||
import { PresenceChannel } from "@wrnexus/pubsub";
|
||||
import { safeObjectKey, sniffContentType } from "@wrnexus/uploader";
|
||||
import { auditWireTokens, contrast } from "@wrnexus/styles";
|
||||
import { createPluginRunner, definePlugin, defaultClientRuntimePath } from "@wrnexus/plugin";
|
||||
import { WRN_SYNTAX_VERSION, createSourceRange, sliceSource } from "@wrnexus/syntax";
|
||||
|
||||
describe("WRNexusJS 0.4 platform upgrades", () => {
|
||||
test("publishes the 0.4 syntax contract", () => {
|
||||
expect(WRN_SYNTAX_VERSION).toBe("0.4");
|
||||
expect(sliceSource("abcdef", createSourceRange(1, 4))).toBe("bcd");
|
||||
});
|
||||
|
||||
test("supports scoped services and health checks", async () => {
|
||||
const token = serviceToken<{ name: string }>("example");
|
||||
const root = new ServiceContainer().set(token, { name: "root" });
|
||||
expect(root.scope().get(token).name).toBe("root");
|
||||
|
||||
const health = new HealthRegistry();
|
||||
health.register("database", () => ({ status: "up" }));
|
||||
expect((await health.check()).status).toBe("up");
|
||||
});
|
||||
|
||||
test("replays idempotent work", async () => {
|
||||
const store = memoryIdempotencyStore<number>();
|
||||
let executions = 0;
|
||||
const execute = async () => ++executions;
|
||||
expect((await withIdempotency(store, "request-1", execute)).replayed).toBe(false);
|
||||
expect((await withIdempotency(store, "request-1", execute)).replayed).toBe(true);
|
||||
expect(executions).toBe(1);
|
||||
});
|
||||
|
||||
test("runs watchers with cleanup", () => {
|
||||
const value = signal(1);
|
||||
const seen: number[] = [];
|
||||
const stop = watch(
|
||||
() => value.get(),
|
||||
(next) => {
|
||||
seen.push(next);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
value.set(2);
|
||||
stop();
|
||||
value.set(3);
|
||||
expect(seen).toEqual([1, 2]);
|
||||
});
|
||||
|
||||
test("provides deterministic routing and resilience helpers", async () => {
|
||||
expect(routeName("/teams/[teamId]/members/[memberId]")).toBe("teams.teamId.members.memberId");
|
||||
expect(stableStringify({ b: 2, a: 1 })).toBe('{"a":1,"b":2}');
|
||||
expect(backoffDelay(1, { minDelayMs: 10, jitter: 0 })).toBe(10);
|
||||
|
||||
let attempts = 0;
|
||||
const result = await retry(
|
||||
async () => {
|
||||
attempts++;
|
||||
if (attempts < 2) throw new Error("retry");
|
||||
return "ok";
|
||||
},
|
||||
{ attempts: 2, minDelayMs: 0, jitter: 0 },
|
||||
);
|
||||
expect(result).toBe("ok");
|
||||
});
|
||||
|
||||
test("drains durable jobs and tracks presence", async () => {
|
||||
const queue = createDurableQueue({ now: () => 100 });
|
||||
const output: number[] = [];
|
||||
queue.process<number>("number", async (job) => {
|
||||
output.push(job.data);
|
||||
});
|
||||
await queue.add("number", 7);
|
||||
expect(await queue.drain()).toBe(1);
|
||||
expect(output).toEqual([7]);
|
||||
|
||||
let now = 1;
|
||||
const presence = new PresenceChannel(10, () => now);
|
||||
presence.touch("user-1");
|
||||
expect(presence.list()).toHaveLength(1);
|
||||
now = 12;
|
||||
expect(presence.list()).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("audits uploads and styles", () => {
|
||||
expect(safeObjectKey("../My Report.pdf", "docs")).toMatch(/^docs\//);
|
||||
expect(sniffContentType(new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))).toBe(
|
||||
"image/png",
|
||||
);
|
||||
expect(auditWireTokens(":root{--wire-a:#fff}.x{color:var(--wire-b)}").missing).toEqual([
|
||||
"--wire-b",
|
||||
]);
|
||||
expect(contrast("#000", "#fff")?.level).toBe("aaa");
|
||||
});
|
||||
|
||||
test("normalizes and validates package runtimes", async () => {
|
||||
expect(defaultClientRuntimePath("captcha")).toBe("/__wrnexus/assets/captcha.js");
|
||||
const runner = createPluginRunner(
|
||||
definePlugin({
|
||||
name: "test-runtime",
|
||||
clientRuntimes: [{ id: "test", source: "window.test=true" }],
|
||||
}),
|
||||
{
|
||||
root: process.cwd(),
|
||||
mode: "development",
|
||||
command: "test",
|
||||
metadata: new Map(),
|
||||
warn: () => {},
|
||||
},
|
||||
);
|
||||
const contributions = await runner.contributions();
|
||||
expect(contributions.clientRuntimes[0]?.publicPath).toBe("/__wrnexus/assets/test.js");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user