inkan

@inkanjs/next

An inkan app in Next.js: route handlers, the Pages Router, a typed client for Server Components.

An inkan app inside a Next.js project: as App Router route handlers, as a Pages Router API route, and as a typed client for Server Components. Contracts, validation, problems, /docs and the inspector all come along.

npm install @vxnsin/inkan @inkanjs/next

The app keeps its routes under /api, and its docs, document and inspector too, so every link on the docs page points where Next will route it:

// server/app.ts
import { , ,  } from "@vxnsin/inkan";

const  = ().("/teas/:id", { : .({ : .() }) }, ({  }) => findTea(.));

export const  = ({ : "/api/docs", : "/api/openapi.json", : "/api/_inkan" })
  .("/api", );

One catch-all route hands every method to it:

// app/api/[...path]/route.ts
import {  } from "@/server/app";
import {  } from "@inkanjs/next";

export const { , , , , , ,  } = ();

/api/docs is the docs page, /api/_inkan the inspector while next dev runs. Next does not tell a route handler who is asking, so the inspector, which only answers a loopback address, gets 127.0.0.1 in development and nothing in production. Pass handlers(app, { remote: (req) => … }) to decide yourself.

Pages Router

pages/api/[...path].ts does the same for the Pages Router. Next reads the body itself unless told not to, so the route says so; the contract then sees the bytes that came:

// pages/api/[...path].ts
import {  } from "@/server/app";
import {  } from "@inkanjs/next";

export const  = { : { : false, : true } };
export default ();

Next only reads config when it is written out in the file, which is why it is not exported from here. Left out, the route still works: the body Next parsed is written back for the app.

Server Components

direct(app) is the typed client, talking to the app in the same process: no port, no network, and still the same contracts, hooks and problems as a request from outside.

// app/teas/[id]/page.tsx
import {  } from "next/headers";
import {  } from "@inkanjs/next";
import {  } from "@/server/app";

export default async function ({  }: { : <{ : string }> }) {
  const  = (, { : async () => ({ : (await ()).() }) });
  const  = await .get("/api/teas/:id", { : { : ((await ).) } });
  if (!.ok) return <>{.problem.detail}</>;
  return <>{.data.name}</>; // typed from the contract
}

The paths are the app's, prefix and all. The type sees the routes of a chain and of mount; those a plugin adds with register work, but are not typed.

All three run in CI inside a real Next.js: next build, next start, then requests.

inkan check and inkan diff work on server/app.ts as they do anywhere.

On this page