npm.io
1.1.0 • Published yesterday

@nifrajs/core

Licence
MIT
Version
1.1.0
Deps
0
Size
596 kB
Vulns
0
Weekly
0
Stars
2

nifra

The full-stack TypeScript framework built for AI agents — and for the humans who work alongside them.

Coding agents drift. They call an endpoint that moved, expect a response shape that changed, or hand-roll fetch with ad-hoc types that fall out of sync the moment a route changes. nifra removes that class of bug at the framework level:

Typed client client<typeof app> infers every path, param, body, and response from your server's TypeScript type. Any mismatch is a compile error.
nifra check Runs typecheck + typed-client lint in one command. Add it to CI — it fails the moment the frontend and backend drift.
AGENTS.md Every scaffold ships a conventions file. Agents (Claude Code, Cursor, Copilot) read it and follow nifra's rules from the first prompt.
nifra context Prints this project's real API surface — routes + schemas — as Markdown. Paste into any agent prompt, or let nifra mcp deliver it automatically.
nifra mcp An MCP server that feeds Claude Code, Cursor, and Copilot Chat this project's live route and schema data.

The rest is a fast, contract-first full-stack TypeScript stack: routing, validated I/O, SSR, loaders/actions, auth, WebSockets, MDX, and multi-runtime deployment.

bun create nifra my-app

The backend

import { server } from "@nifrajs/core"
import { t } from "@nifrajs/schema"

export const app = server()
  .get("/users/:id", (c) => ({ id: c.params.id }))
  .post("/users", { body: t.object({ name: t.string() }) }, (c) => {
    // c.body is validated + typed — invalid input is rejected before this runs.
    return { id: crypto.randomUUID(), name: c.body.name }
  })
  .listen(3000)

export type App = typeof app

The typed client — the anti-drift seam

// client.ts — fully typed from the server, zero codegen
import { client } from "@nifrajs/client"
import type { App } from "./server"

const api = client<App>("http://localhost:3000")

const res = await api.users({ id: "42" }).get()
if (res.ok) res.data.id   // typed from the route's return — tsc fails if the route changes
else res.error            // errors are returned, never thrown

The client never throws — every call returns { ok, status, data, error }, so the happy path and the failure path are both in the types.

Agent tooling

nifra ships a purpose-built toolchain so coding agents stay correct as the codebase evolves.

AGENTS.md — generated per scaffold, teaches the agent nifra's non-obvious rules:

  • validate every input at the boundary with t or any Standard Schema
  • always call this app's own API through client<typeof app> — never hand-roll fetch
  • never top-level-import server-only code into a route module

Connect the MCP server so the agent reads your live routes, verifies endpoints, and gates drift from inside its tool loop. Run once from your project root:

# Claude Code
claude mcp add nifra -- bunx nifra mcp

# Cursor / Claude Desktop — add to .mcp.json (or claude_desktop_config.json):
# { "mcpServers": { "nifra": { "command": "bunx", "args": ["nifra", "mcp"] } } }

Once connected, the agent has twelve tools — no setup per prompt:

Tool What it does
nifra_context This project's live routes + schemas + the exact typed-client call signature per route (Markdown).
nifra_routes The same routes as structured JSON ({ method, path, call, body?, query?, response? }) — for programmatic use.
nifra_openapi OpenAPI 3.1 generated from backend route schemas, as JSON or YAML.
nifra_check Typecheck + drift lint, returned as structured JSON with safe fix suggestions.
nifra_doctor Flags packages imported in source but missing from package.json (resolve at runtime, break tsc).
nifra_run Calls a route in-process (via @nifrajs/runner) — the agent self-verifies an endpoint without booting a server.
nifra_render Server-renders a page to HTML — verify SSR output.
nifra_ws Opens a real Bun WebSocket against the current app, sends test frames, and returns structured evidence.
nifra_test Runs bounded bun test and returns structured stdout, stderr, timing, and summary.
nifra_scaffold URL pattern → the correct routes/ file for the chosen UI framework.
nifra_docs / nifra_example Search the docs / fetch a version-checked snippet that compiles as-is (no hallucinated APIs).

No MCP? The same data is available as plain commands — paste into any prompt, or run in CI:

nifra context          # routes + schemas (+ per-route call signatures) as Markdown
nifra check            # typecheck + typed-client drift lint; --json for agents, --lints-only to skip tsc
nifra doctor           # packages imported but not declared in package.json (--json for agents)

Install

bun add @nifrajs/core            # the server + router + contracts
bun add @nifrajs/client          # the typed client (browser-safe)
bun add @nifrajs/schema          # the `t` schema builder + OpenAPI (optional)
bun add @nifrajs/middleware      # CORS, security headers, rate limiting (optional)

nifra is ESM-only and Bun-native (it uses Bun.serve). It runs on Bun; the client is environment-agnostic.

Validate input with t (and get OpenAPI for free)

@nifrajs/schema's t is a TypeBox-backed builder: it validates at the request boundary and — because a TypeBox schema is a JSON Schema — generates OpenAPI with no extra work. Bring your own Standard Schema (zod, valibot, arktype) too; they validate identically.

import { server } from "@nifrajs/core"
import { t, toOpenAPI } from "@nifrajs/schema"

const app = server().post("/users", { body: t.object({ name: t.string() }) }, (c) => ({
  id: "u1",
  name: c.body.name, // typed as string, validated at runtime
}))

const openapi = toOpenAPI(app) // OpenAPI 3.1 document

Invalid bodies are rejected with a structured 400 before your handler runs.

Graduate to a contract — handlers unchanged

When you want a decoupled, versionable API surface, lift the same routes into a contract. Handlers written inline lift over unchanged.

import { defineContract, implement } from "@nifrajs/core"
import { t } from "@nifrajs/schema"

const contract = defineContract({
  getUser:    { method: "GET",  path: "/users/:id", response: t.object({ id: t.string(), name: t.string() }) },
  createUser: { method: "POST", path: "/users",     body: t.object({ name: t.string() }), response: t.object({ id: t.string(), name: t.string() }) },
})

const app = implement(contract, {
  getUser:    (c) => ({ id: c.params.id, name: "ada" }),
  createUser: (c) => ({ id: "new", name: c.body.name }),
})

The client can now be built from the contract alone (client(contract, url)) — no dependency on the server's source. This is the shape agents reference: nifra context emits the live contract; nifra check enforces it.

Harden it

import { server } from "@nifrajs/core"
import { cors, securityHeaders, rateLimit, MemoryStore } from "@nifrajs/middleware"

const app = server()
  .use(securityHeaders())
  .use(cors({ origin: ["https://app.example.com"], credentials: true }))
  .use(rateLimit({
    store: new MemoryStore(),
    max: 100,
    windowMs: 60_000,
    key: (req) => req.headers.get("x-user-id") ?? "anonymous",
  }))
  .get("/", () => ({ ok: true }))

// Graceful shutdown, request timeout, body-size cap, redacting logger are built in:
server({ requestTimeoutMs: 5_000, gracefulSignals: true })

Runs on the edge, too

Bun is the first-class runtime (app.listen()), but the whole lifecycle is app.fetch(Request): Promise<Response> with zero Bun APIs — so the same app deploys to Cloudflare Workers (export default app), Deno (Deno.serve(app.fetch)), or Node (via the @nifrajs/node adapter). See Deployment and Edge & bindings.

Principles (enforced, not aspirational)

  • Reject invalid input at three boundaries — compile-time (types), boot-time (config throws loudly), request-time (Standard Schema → structured 400). "Genuine fallback" is a documented whitelist; everything else rejects.
  • Tests everywhere, six kinds — unit, type-level (*.test-d.ts), property/fuzz, mode-conformance, benchmark-regression, security-guardrail.
  • Speed is a measured goal — tracked with the oha HTTP matrix (bun run bench:loadtest) across Bun, Node, and Deno against raw runtime handlers plus representative API framework baselines.
  • Production-grade by default — graceful shutdown, redacting logs, idempotent guards, integer-money discipline; nothing is "we'll fix it later".

Packages

Package What it is
@nifrajs/core Router, fully-inferred server, contracts, lifecycle middleware, hardening
@nifrajs/client End-to-end-typed, never-throwing client (Eden-style proxy)
@nifrajs/schema TypeBox-backed t builder + toOpenAPI
@nifrajs/middleware CORS, security headers, rate limiting
@nifrajs/node Run a nifra app on Node's http server (opt-in)
@nifrajs/cli nifra check, nifra context, nifra mcp — the agent toolchain

Examples

Runnable, type-checked apps live in examples/:

bun run examples/inline-server.ts
bun run examples/contract-client.ts
bun run examples/schema-openapi.ts
bun run examples/hardened.ts
bun run examples/edge.ts        # app.fetch as a universal handler

Develop

bun install
bun run check          # lint + typecheck (incl. type-level tests) + tests w/ coverage
bun run build          # emit dist/ (js + d.ts) for all packages
bun run check:publish  # build + publint + arethetypeswrong
bun run bench:loadtest # oha HTTP matrix across Bun/Node/Deno

MIT licensed.

Keywords