inkan

Examples are tests

Examples show up in the docs and run as tests.

npx inkan check src/app.ts            # every route
npx inkan check src/app.ts --only /teas
npx inkan check src/app.ts --json     # for CI
npx inkan check src/app.ts --strict   # a promised status without an example fails too

A route that promises a 404 no example ever shows is listed under the route, and on the docs page. Nothing tests that 404 yet; --strict turns it into a failure.

The entry file exports the app (export default app or export const app). app.listen() stays quiet while the CLI loads it. If the examples change data, export a beforeEach that puts it back. It runs before every example:

export function () {
  store.reset();
}

Or inside your own tests:

import {  } from "node:test";
import  from "node:assert/strict";
import { ,  } from "./app.ts";

("the contract holds", async () => {
  const  = await .({  });
  .(., 0);
});

Examples that build on each other

"Create a tea, then fetch it" needs the id from the first answer. One example keeps it, the next one runs after it and uses it as a placeholder:

// POST /teas
{ name: "a new oolong", body: { name: "Da Hong Pao", kind: "oolong" }, keep: { id: "body.id" } },

// GET /teas/:id
{ name: "the one just added", after: "POST /teas > a new oolong", params: { id: "{id}" }, expect: { name: "Da Hong Pao" } },

keep reads body.…, headers.…, cookies.… (a cookie the answer sets) or status. A placeholder alone ("{id}") keeps the value's type; inside text ("/teas/{id}") it becomes text. beforeEach runs once before the whole chain, so the examples in it see each other's data, and the order comes from after, never from the order of the files. The docs page runs the chain too when you press send.

Cookies

The examples of one chain share a cookie jar, as a browser would. A cookie an answer sets goes with the requests after it that its Path covers, until an answer clears it (Max-Age=0, or an Expires gone by). So a login and a /me after it pass without copying the session by hand:

// POST /login
{ name: "ana signs in", body: { name: "ana", password: "tea" }, keep: { sid: "cookies.sid" } },

// GET /me
{ name: "who she is", after: "POST /login > ana signs in", expect: { name: "ana" } },

Every chain starts with an empty jar, so nothing crosses into another chain. keep with cookies.<name> is for when you need the value itself, in a header or a body.

For everything the examples do not cover, app.inject() sends a request straight in, without a socket:

const  = await .({ : "POST", : "/teas", : { : "Gyokuro" } });
.; // 400
..errors; // [{ in: "body", path: "kind", message: "is required" }, …]

On this page