@nifrajs/skills 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +9 -0
- package/README.md +62 -0
- package/package.json +43 -0
- package/skills/nifra/SKILL.md +103 -0
- package/skills/nifra-api/SKILL.md +97 -0
- package/skills/nifra-verify/SKILL.md +90 -0
- package/skills/nifra-web/SKILL.md +93 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nifra",
|
|
3
|
+
"description": "Skills for building and verifying Nifra apps: contract-first APIs, full-stack SSR on five UI libraries, and the check/assure/capabilities gates. Prefers Nifra's live MCP server over model recall.",
|
|
4
|
+
"version": "2.11.0",
|
|
5
|
+
"homepage": "https://nifra.dev/docs/agents",
|
|
6
|
+
"repository": "https://github.com/nifrajs/nifra",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": ["nifra", "typescript", "bun", "web-framework", "mcp"]
|
|
9
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @nifrajs/skills
|
|
2
|
+
|
|
3
|
+
Portable agent skills for [Nifra](../../README.md) - one `SKILL.md` bundle, published to every agent
|
|
4
|
+
surface that reads a `skills/` directory.
|
|
5
|
+
|
|
6
|
+
A skill is loaded on demand: the agent sees only each skill's name and description until it decides
|
|
7
|
+
one applies, then reads the body. That keeps Nifra's conventions out of the context window until they
|
|
8
|
+
matter.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
skills/
|
|
12
|
+
nifra/SKILL.md entry point - MCP-first rule, the core loop, the non-negotiables
|
|
13
|
+
nifra-api/SKILL.md typed routes, schemas, middleware, WebSockets, the never-throwing client
|
|
14
|
+
nifra-web/SKILL.md file routing, loaders/actions, SSR, islands, the client/server boundary
|
|
15
|
+
nifra-verify/SKILL.md check, assure, capabilities, manifest diff, the verification ladder
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
**Pi**
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pi install npm:@nifrajs/skills
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Claude Code**
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
/plugin marketplace add nifrajs/nifra
|
|
30
|
+
/plugin install nifra@nifra
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Anything that reads a skills directory** (Cursor, `.agents/skills`, a project `.pi/skills`)
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
bun add -d @nifrajs/skills
|
|
37
|
+
cp -R node_modules/@nifrajs/skills/skills/* .agents/skills/
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## These skills are deliberately thin
|
|
41
|
+
|
|
42
|
+
They teach shape and rules, not signatures. Signatures come from the MCP server, which is typechecked
|
|
43
|
+
against the version installed in the project being edited:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
bunx @nifrajs/cli init-agents # register the MCP server in an existing app
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Not in a Nifra project? The docs tools are hosted at `https://mcp.nifra.dev`.
|
|
50
|
+
|
|
51
|
+
That split is the anti-drift design: a skill that restated the API would rot on the next release,
|
|
52
|
+
while a skill that says "ask `nifra_types`" stays correct. Keep it that way when editing these files -
|
|
53
|
+
if you find yourself pasting a signature in, reach for a tool reference instead.
|
|
54
|
+
|
|
55
|
+
## For AI agents
|
|
56
|
+
|
|
57
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card, one cheap read instead of the whole
|
|
58
|
+
corpus. For the wider framework: the repo's [`AGENTS.md`](../../AGENTS.md) is the copy-paste quick
|
|
59
|
+
reference, and [`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run
|
|
60
|
+
`nifra check` as the done-gate, or `nifra mcp` to give the agent live project tools.
|
|
61
|
+
|
|
62
|
+
MIT licensed.
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nifrajs/skills",
|
|
3
|
+
"version": "2.12.0",
|
|
4
|
+
"description": "Agent skills for nifra - portable SKILL.md bundles that teach a coding agent Nifra's contracts, boundaries, and CI gates, and point it at the live MCP server instead of stale recall.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"nifra",
|
|
8
|
+
"bun",
|
|
9
|
+
"typescript",
|
|
10
|
+
"ai-agent",
|
|
11
|
+
"agent-skills",
|
|
12
|
+
"skills",
|
|
13
|
+
"claude-code",
|
|
14
|
+
"cursor",
|
|
15
|
+
"mcp",
|
|
16
|
+
"agents-md",
|
|
17
|
+
"web-framework"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"files": [
|
|
21
|
+
"skills",
|
|
22
|
+
".claude-plugin"
|
|
23
|
+
],
|
|
24
|
+
"pi": {
|
|
25
|
+
"skills": [
|
|
26
|
+
"./skills"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/nifrajs/nifra.git",
|
|
37
|
+
"directory": "packages/skills"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/nifrajs/nifra/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://nifra.dev/docs/agents"
|
|
43
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nifra
|
|
3
|
+
description: Use when writing, reviewing, or debugging code in a Nifra project (@nifrajs/* packages, `nifra` CLI, server()/defineContract, loaders and actions, file routes under routes/). Explains how to reach Nifra's live MCP tools so signatures come from the installed version instead of memory, and which sibling skill to load for API, full-stack, or verification work.
|
|
4
|
+
metadata:
|
|
5
|
+
homepage: https://nifra.dev
|
|
6
|
+
docs: https://nifra.dev/docs
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Nifra
|
|
10
|
+
|
|
11
|
+
Nifra is a Bun-native, contract-first, framework-agnostic full-stack TypeScript framework. One schema
|
|
12
|
+
drives runtime validation, TypeScript types, the typed client, and OpenAPI. The whole lifecycle is
|
|
13
|
+
`app.fetch(Request): Promise<Response>`, so one app runs on Bun, Node, Deno, Cloudflare Workers, and
|
|
14
|
+
Vercel Edge.
|
|
15
|
+
|
|
16
|
+
## Rule zero: do not write Nifra from memory
|
|
17
|
+
|
|
18
|
+
Nifra ships a verified MCP server. Its answers are typechecked against the version installed in the
|
|
19
|
+
project you are editing, so they beat recall. Prefer it over guessing an API, and over reading
|
|
20
|
+
`node_modules/@nifrajs/**/*.d.ts`.
|
|
21
|
+
|
|
22
|
+
If the project already has `.mcp.json` or `.cursor/mcp.json`, the server is registered. If not:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
bunx @nifrajs/cli init-agents # writes .mcp.json, .cursor/mcp.json, AGENTS.md, CLAUDE.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Not inside a Nifra project (answering a question, comparing frameworks, writing a snippet)? The docs
|
|
29
|
+
tools are hosted at `https://mcp.nifra.dev` - same corpus, no checkout.
|
|
30
|
+
|
|
31
|
+
Tools worth knowing, in the order you usually need them:
|
|
32
|
+
|
|
33
|
+
| Tool | Use it for |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `nifra_context` | This project's live route index, schemas, and conventions. Call first in unfamiliar code. |
|
|
36
|
+
| `nifra_docs` | The prose doc for a concept, pinned to the installed version. |
|
|
37
|
+
| `nifra_example` | A snippet that compiled against this version in CI. Never hand-write what this returns. |
|
|
38
|
+
| `nifra_types` | The exact TypeScript of any `@nifrajs/*` symbol. |
|
|
39
|
+
| `nifra_run` | Execute a real request against the app in-process to check behaviour. |
|
|
40
|
+
| `nifra_check` | The done-gate. See `nifra-verify`. |
|
|
41
|
+
| `nifra_assure` | Per-route security evidence. See `nifra-verify`. |
|
|
42
|
+
|
|
43
|
+
No MCP available at all? Fall back to `https://nifra.dev/llms.txt` (index) or
|
|
44
|
+
`https://nifra.dev/llms-full.txt` (every doc page inlined), and to each package's `LLM.md` contract
|
|
45
|
+
card in `node_modules/@nifrajs/<pkg>/LLM.md`.
|
|
46
|
+
|
|
47
|
+
## The core loop
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// server.ts
|
|
51
|
+
import { server } from "@nifrajs/core/server"
|
|
52
|
+
import { t } from "@nifrajs/schema"
|
|
53
|
+
|
|
54
|
+
export const app = server()
|
|
55
|
+
.get("/users/:id", (c) => ({ id: c.params.id }))
|
|
56
|
+
.post("/users", { body: t.object({ name: t.string() }) }, (c) => {
|
|
57
|
+
// c.body is validated and typed - invalid input became a structured 422 before this ran
|
|
58
|
+
return { id: crypto.randomUUID(), name: c.body.name }
|
|
59
|
+
})
|
|
60
|
+
.listen(3000)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// anywhere.ts - inferred from the server, zero codegen
|
|
65
|
+
import { client } from "@nifrajs/client"
|
|
66
|
+
import type { app } from "./server"
|
|
67
|
+
|
|
68
|
+
const api = client<typeof app>("http://localhost:3000")
|
|
69
|
+
|
|
70
|
+
const res = await api.users({ id: "42" }).get()
|
|
71
|
+
if (res.ok) res.data.id // typed from the route
|
|
72
|
+
else res.error // failures are returned, never thrown
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Change a route and every caller stops compiling. That property is the point: it is what keeps an
|
|
76
|
+
agent-edited codebase correct.
|
|
77
|
+
|
|
78
|
+
## Non-negotiables when you edit a Nifra project
|
|
79
|
+
|
|
80
|
+
1. **Never hand-roll `fetch()` against this app's own API.** Use the typed client. `nifra check`
|
|
81
|
+
fails the build on hand-rolled calls, and it is right to.
|
|
82
|
+
2. **Validate at the boundary, not in the handler.** Put the schema in the route options so invalid
|
|
83
|
+
input is a `422` before your code runs.
|
|
84
|
+
3. **Failures are returned, not thrown.** Client results are `{ ok, data } | { ok: false, error }`.
|
|
85
|
+
Do not wrap client calls in try/catch expecting a throw.
|
|
86
|
+
4. **Server-only code stays out of `routes/`.** A `node:` or native import reaching the client bundle
|
|
87
|
+
is a build error, not a warning.
|
|
88
|
+
5. **Run the gate before you claim done.** `nifra check` (or the `nifra_check` tool). A failing check
|
|
89
|
+
means the work is not finished.
|
|
90
|
+
|
|
91
|
+
## Which skill next
|
|
92
|
+
|
|
93
|
+
- Typed JSON APIs, schemas, contracts, middleware, the client -> load `nifra-api`.
|
|
94
|
+
- File routes, loaders, actions, SSR, islands, React/Vue/Solid/Svelte/Preact -> load `nifra-web`.
|
|
95
|
+
- `nifra check`, `nifra assure`, capabilities, the manifest, CI gates -> load `nifra-verify`.
|
|
96
|
+
|
|
97
|
+
## Starting from scratch
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
bun create nifra my-app # full-stack: pick framework, runtime, DB, auth, CI
|
|
101
|
+
bun create nifra my-app --framework svelte # react | vue | solid | preact | svelte
|
|
102
|
+
bun add @nifrajs/core @nifrajs/schema @nifrajs/client # just a typed API
|
|
103
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nifra-api
|
|
3
|
+
description: Use when building or changing a typed JSON API in Nifra - routes with server() or defineContract, request validation with @nifrajs/schema or any Standard Schema library, middleware and plugins, WebSockets, OpenAPI, and consuming the API from the zero-codegen @nifrajs/client. Load after the `nifra` skill.
|
|
4
|
+
metadata:
|
|
5
|
+
docs: https://nifra.dev/docs/api
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Nifra: typed APIs and the typed client
|
|
9
|
+
|
|
10
|
+
Fetch exact signatures with `nifra_types` and compiling snippets with `nifra_example` before writing
|
|
11
|
+
anything non-trivial. This skill is the shape of the thing, not a substitute for either.
|
|
12
|
+
|
|
13
|
+
## Two ways to declare routes
|
|
14
|
+
|
|
15
|
+
**`server()`** - chainable, inference-first. Reach for this by default.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { server } from "@nifrajs/core/server"
|
|
19
|
+
import { t } from "@nifrajs/schema"
|
|
20
|
+
|
|
21
|
+
export const app = server()
|
|
22
|
+
.get("/notes", () => listNotes())
|
|
23
|
+
.get("/notes/:id", { params: t.object({ id: t.string() }) }, (c) => getNote(c.params.id))
|
|
24
|
+
.post("/notes", { body: t.object({ title: t.string(), body: t.string() }) }, (c) =>
|
|
25
|
+
createNote(c.body),
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**`defineContract`** - the same routes as a standalone, versionable artifact when the contract must
|
|
30
|
+
be published, shared across repos, or evolved independently of the handlers. Ask `nifra_docs` for
|
|
31
|
+
the contract page before choosing it; do not reach for it just because a route feels important.
|
|
32
|
+
|
|
33
|
+
## Validation belongs in the route options
|
|
34
|
+
|
|
35
|
+
`body`, `query`, `params`, `headers`, and `response` all take a schema. Anything that fails becomes a
|
|
36
|
+
structured `422` before your handler runs, so a handler never defensively re-checks its own input.
|
|
37
|
+
|
|
38
|
+
`@nifrajs/schema` (`t`) is TypeBox-backed and gives you JSON Schema and OpenAPI for free. Any
|
|
39
|
+
[Standard Schema](https://standardschema.dev) library works too - Zod and Valibot slot into the same
|
|
40
|
+
option with no adapter.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { z } from "zod"
|
|
44
|
+
app.post("/users", { body: z.object({ email: z.string().email() }) }, (c) => c.body.email)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Typed search params: declare `query` and read `c.query` typed. Do not parse `new URL(c.req.url)` by
|
|
48
|
+
hand.
|
|
49
|
+
|
|
50
|
+
## The client never throws
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const res = await api.notes({ id }).get()
|
|
54
|
+
if (!res.ok) return handle(res.error) // transport and HTTP failures both land here
|
|
55
|
+
res.data // typed from the route's return type
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Three consequences worth internalising:
|
|
59
|
+
|
|
60
|
+
- No `try`/`catch` around a client call for HTTP or network failure. Only genuinely exceptional code
|
|
61
|
+
throws.
|
|
62
|
+
- The client is browser-safe. It carries the server's *types*, never its code.
|
|
63
|
+
- Changing a route's shape breaks every caller at compile time. That is the feature. Fix the callers;
|
|
64
|
+
do not widen the type to silence `tsc`.
|
|
65
|
+
|
|
66
|
+
## Middleware
|
|
67
|
+
|
|
68
|
+
`@nifrajs/middleware` ships the production set: `requestId`, `logger`, `etag`, `bearer`, `apiKey`,
|
|
69
|
+
`basicAuth`, `jwt`/`jwks`, `csrf`, `ipRestriction`, `bodyLimit`, `cors`, `securityHeaders`,
|
|
70
|
+
`rateLimit`, `compression`, `cacheControl`, `cache`, `prettyJson`, `timing`, `methodOverride`,
|
|
71
|
+
`trailingSlash`, `language`, `poweredBy`, `combine`, `openapi`, `healthcheck`, `idempotency`.
|
|
72
|
+
|
|
73
|
+
Check that list before writing your own. Hand-rolled CORS, rate limiting, or CSRF is how a project
|
|
74
|
+
acquires a security hole. Custom cross-cutting behaviour goes through `definePlugin` (typed context,
|
|
75
|
+
idempotent registration), not a hand-patched handler wrapper.
|
|
76
|
+
|
|
77
|
+
## WebSockets
|
|
78
|
+
|
|
79
|
+
`app.ws(path, handler)` registers a typed WebSocket route: upgrade guard, portable socket,
|
|
80
|
+
contract-validated messages, topic pub/sub. Served on Bun, Deno, Node, and Workers. Ask
|
|
81
|
+
`nifra_example` for the current shape; the message-contract generics are easy to get wrong from memory.
|
|
82
|
+
|
|
83
|
+
## Runtimes
|
|
84
|
+
|
|
85
|
+
The app is `app.fetch(Request): Promise<Response>`. Bun is first-class; Node
|
|
86
|
+
(`@nifrajs/node`), Deno (`@nifrajs/deno`), Workers (`@nifrajs/workers`), Lambda
|
|
87
|
+
(`@nifrajs/aws-lambda`), and Vercel Edge are each one adapter import. Do not fork the app per target.
|
|
88
|
+
|
|
89
|
+
## Common mistakes
|
|
90
|
+
|
|
91
|
+
| Mistake | Do instead |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `fetch("/api/notes")` inside the same project | `client<typeof app>()`, or `ctx.api` in a loader |
|
|
94
|
+
| `try { await api.x.get() } catch` | Check `res.ok` |
|
|
95
|
+
| Validating in the handler body | Put the schema in the route options |
|
|
96
|
+
| Reading `c.req.headers.get("x-forwarded-for")` for the caller IP | Use the documented request helper; proxy headers are forgeable |
|
|
97
|
+
| Hand-writing an OpenAPI document | `openapi` middleware derives it from the schemas |
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nifra-verify
|
|
3
|
+
description: Use before declaring Nifra work complete, when a Nifra CI gate fails, or when wiring Nifra checks into CI - `nifra check` drift gate, `nifra assure` route security evidence, `nifra capabilities check` effect provenance, `nifra manifest diff` deploy promotion, `nifra doctor`, the verification ladder, and contract-derived testing with @nifrajs/testing. Load after the `nifra` skill.
|
|
4
|
+
metadata:
|
|
5
|
+
docs: https://nifra.dev/docs/verification
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Nifra: the gates
|
|
9
|
+
|
|
10
|
+
Nifra turns "did you do this right" into build failures. The gates exist so an agent can check its own
|
|
11
|
+
work instead of asserting success. Run them; do not route around them.
|
|
12
|
+
|
|
13
|
+
## The done-gate
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
nifra check --json # or the nifra_check MCP tool
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`nifra check` typechecks the frontend/backend contract and flags drift: a hand-rolled `fetch()` at
|
|
20
|
+
this app's own API, a server-only import inside `routes/`, a client call whose route changed shape.
|
|
21
|
+
Findings carry the fix.
|
|
22
|
+
|
|
23
|
+
**A failing check means the work is not done.** Do not suppress a finding, widen a type, or add a
|
|
24
|
+
cast to make it pass. If a finding is genuinely wrong for this codebase, say so explicitly to the
|
|
25
|
+
person you are working for rather than quietly silencing it.
|
|
26
|
+
|
|
27
|
+
`nifra doctor` is the other half: environment and build-state problems (stale `dist/`, duplicate
|
|
28
|
+
framework install, misconfigured adapter) that make correct code behave wrongly.
|
|
29
|
+
|
|
30
|
+
## Security evidence per route
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
$ nifra assure
|
|
34
|
+
✖ POST /notes (authenticated-write) is missing nifra.authenticated
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A policy file classifies every route by reflection, then `assure` fails CI naming exactly which
|
|
38
|
+
evidence is absent: authentication on a write, a rate limit, CSRF, a body cap. When you add a route,
|
|
39
|
+
you add its evidence in the same change. When `assure` names a gap, add the real control - never
|
|
40
|
+
reclassify the route to make the requirement disappear.
|
|
41
|
+
|
|
42
|
+
## Effect provenance
|
|
43
|
+
|
|
44
|
+
Routes declare the effects they perform:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
app.post("/notes", { capabilities: ["db.write"] }, handler)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`nifra capabilities check` compares what a route *declares* against what its module graph can
|
|
51
|
+
actually *reach*, pinned in a lockfile. A `GET` that can reach a domain write is an error.
|
|
52
|
+
|
|
53
|
+
The common cause of a surprise failure is a barrel file: importing one helper from `./lib` drags the
|
|
54
|
+
whole module graph's reach into the route. The fix is to narrow the import, not to widen the
|
|
55
|
+
declaration.
|
|
56
|
+
|
|
57
|
+
## Deploy promotion
|
|
58
|
+
|
|
59
|
+
`nifra manifest diff` emits one hash-verified artifact - contracts, assurance, effects, response
|
|
60
|
+
sensitivity - and fails closed on a breaking contract change, lost assurance, or a newly exposed
|
|
61
|
+
sensitive field. Run it between environments, not just at merge.
|
|
62
|
+
|
|
63
|
+
## Testing
|
|
64
|
+
|
|
65
|
+
`@nifrajs/testing` derives tests from the contract rather than from your assumptions:
|
|
66
|
+
adversarial/hostile input generation, response conformance, replay, shrinking, and a runtime-matrix
|
|
67
|
+
check that the same app behaves identically on Bun, Node, Deno, and Workers.
|
|
68
|
+
|
|
69
|
+
Prefer a contract-derived test to a hand-written one that re-asserts the schema. Hand-write the tests
|
|
70
|
+
covering business rules the contract cannot express.
|
|
71
|
+
|
|
72
|
+
## The verification ladder
|
|
73
|
+
|
|
74
|
+
Five cumulative levels, from "has a typed contract" up to "has contract-derived invariant tests".
|
|
75
|
+
`nifra levels` (or `nifra_levels`) reports where the project actually stands.
|
|
76
|
+
|
|
77
|
+
Use it honestly: report the level the project holds, not the one it aspires to. Raising a level means
|
|
78
|
+
adding the evidence, not editing the claim.
|
|
79
|
+
|
|
80
|
+
## Suggested CI order
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
nifra check # contract drift
|
|
84
|
+
nifra assure # route security evidence
|
|
85
|
+
nifra capabilities check
|
|
86
|
+
bun test # incl. contract-derived tests
|
|
87
|
+
nifra manifest diff # promotion gate, between environments
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Fail the build on each. A gate that only warns is a gate that gets ignored.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nifra-web
|
|
3
|
+
description: Use when building the full-stack side of a Nifra app - file routes, layouts, loaders and actions, forms, server functions, streaming SSR, islands, SSG and ISR, the query cache, and the React, Vue, Solid, Svelte, or Preact render adapters. Covers the client/server boundary rules and the hydration errors that boundary produces. Load after the `nifra` skill.
|
|
4
|
+
metadata:
|
|
5
|
+
docs: https://nifra.dev/docs/routing
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Nifra: full-stack rendering
|
|
9
|
+
|
|
10
|
+
`@nifrajs/web` is the framework-agnostic SSR core; `@nifrajs/web-react`, `-vue`, `-solid`,
|
|
11
|
+
`-svelte`, and `-preact` are render adapters over the same routing, loaders, streaming, and islands.
|
|
12
|
+
Switching UI library is an adapter import, not a rewrite.
|
|
13
|
+
|
|
14
|
+
Call `nifra_context` first in an existing app: it returns the actual route tree and conventions, which
|
|
15
|
+
beats inferring them from a directory listing.
|
|
16
|
+
|
|
17
|
+
## Data flows one way
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
route file -> loader (server) -> component (server render, then hydrate) -> action (server) -> revalidate
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **Loader** - runs on the server for the matched route. Returns typed data to the component. Reach
|
|
24
|
+
your own API through `ctx.api` (an in-process client - no HTTP hop, no base URL, no auth replay).
|
|
25
|
+
- **Action** - handles a mutation, then revalidation refreshes the loaders that matter.
|
|
26
|
+
- **Component** - renders on the server, then hydrates. It must be safe to run in both places.
|
|
27
|
+
|
|
28
|
+
Do not `fetch()` your own backend from a loader. `ctx.api` is the in-process path and it is typed.
|
|
29
|
+
|
|
30
|
+
## The boundary is enforced, not advisory
|
|
31
|
+
|
|
32
|
+
A server-only module that reaches the browser bundle is a **build error**. That is deliberate:
|
|
33
|
+
leaking a DB client or a secret into client JS is the failure mode this prevents.
|
|
34
|
+
|
|
35
|
+
Signals and their causes:
|
|
36
|
+
|
|
37
|
+
| Error | Cause |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `... reached the client bundle` | A `node:` or native import pulled in from a component or route module |
|
|
40
|
+
| `server-only module reached the client bundle` | A module carrying the server-only marker was imported from client code |
|
|
41
|
+
| `resolveDispatcher` / `Invalid hook call` | Two copies of the UI framework. The adapter dedupes it at build time - a stale `dist/` or a second install is the usual source |
|
|
42
|
+
|
|
43
|
+
Fixes: move the server work into a loader, an action, or a server function; keep secrets in modules
|
|
44
|
+
the client never imports; rebuild before blaming the code.
|
|
45
|
+
|
|
46
|
+
**Server functions** are the escape hatch that stays safe: write the function on the server, call it
|
|
47
|
+
from a component. The module body never ships, the arguments are validated, and the mounted function
|
|
48
|
+
is an ordinary route - so assurance, capabilities, and the effect ledger still apply to it.
|
|
49
|
+
|
|
50
|
+
## Forms work before hydration
|
|
51
|
+
|
|
52
|
+
There is a window between HTML arriving and JS booting. Nifra keeps it usable: progressive-enhancement
|
|
53
|
+
forms and links function during it, and a JS-only form whose native submit would break is guarded
|
|
54
|
+
automatically. Do not disable a submit button until hydration; do not build a form that only works
|
|
55
|
+
after JS. If you add `onSubmit` with `preventDefault`, make sure the action still handles a native
|
|
56
|
+
POST.
|
|
57
|
+
|
|
58
|
+
## Rendering modes
|
|
59
|
+
|
|
60
|
+
- **SSR** - per-request render. The default.
|
|
61
|
+
- **Streaming** - Suspense plus `defer()`, on every runtime including the edge. Stream the slow part;
|
|
62
|
+
do not block the shell on it.
|
|
63
|
+
- **SSG / ISR** - prerender static routes, enumerate dynamic ones, cache with stale-while-revalidate.
|
|
64
|
+
- **Islands** - ship interactivity for one component instead of the page.
|
|
65
|
+
|
|
66
|
+
## Query cache
|
|
67
|
+
|
|
68
|
+
`useQuery` / `createQuery` (naming follows the adapter's idiom): keyed cache, dedup, staleness,
|
|
69
|
+
invalidation. Use it rather than a `useEffect` fetch. An action's revalidation and the query cache
|
|
70
|
+
are the same invalidation story - do not run a second, hand-rolled one alongside them.
|
|
71
|
+
|
|
72
|
+
## Dev loop
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
nifra dev # Vite-backed HMR, state preserving
|
|
76
|
+
nifra build
|
|
77
|
+
nifra start
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`@nifrajs/web/dev` is the Bun-native HMR loop with no Vite dependency. Production builds use Bun.
|
|
81
|
+
|
|
82
|
+
If dev behaviour disagrees with a fresh build, suspect a stale `dist/` before suspecting the code -
|
|
83
|
+
run `nifra doctor`.
|
|
84
|
+
|
|
85
|
+
## Common mistakes
|
|
86
|
+
|
|
87
|
+
| Mistake | Do instead |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `fetch("/api/...")` in a loader | `ctx.api` |
|
|
90
|
+
| Data fetching in `useEffect` | Loader, or the query cache |
|
|
91
|
+
| Importing a DB client into a route component | Loader, action, or server function |
|
|
92
|
+
| Blocking the whole page on one slow query | `defer()` plus Suspense |
|
|
93
|
+
| Adding a second copy of React/Vue to fix a resolution error | Rebuild; the adapter dedupes at build time |
|