Testing and local development
A route's examples are worked cases: given some data, this request gets this response. They live in the manifest next to the route, so they're documentation, tests, and an OpenAPI annotation at once.
a.route("POST /orgs/:orgId/members")
.member("orgId", { roles: ["owner"] })
.body({ email: t.string().min(3).max(200) })
.errors({ seatLimit: err.forbidden("No seats left", { seats: t.int() }) })
.example({
name: "a full free plan says how many seats it has",
as: { userId: "u1", orgs: { acme: "owner" } },
given: {
Org: [{ orgId: "acme", name: "Acme" }],
Member: [
{ orgId: "acme", email: "a@acme.test" },
{ orgId: "acme", email: "b@acme.test" },
{ orgId: "acme", email: "c@acme.test" },
],
},
request: { params: { orgId: "acme" }, body: { email: "d@acme.test" } },
expect: { status: 403, body: { error: "seatLimit", details: { seats: 3 } } },
})
.handle(($, { params, body, errors }) => {
const seats = Member.query({ prefix: [params.orgId], limit: 4 }).length;
$.ensure(seats.lt(3), errors.seatLimit, { seats });
return Member.insert({ orgId: params.orgId, email: body.email });
});npx aleph test app.tsWhat an example can set
| Field | |
|---|---|
given | Rows that exist first, by entity (validated; defaults, indexes, and counters apply) |
users | User profiles in the directory |
as | The caller: { userId, orgs, apiKey, profile } |
request | params, query, body, headers |
mocks | Integration responses: { integration, operation, response | error } |
secrets | Test secrets, such as a webhook signing key |
process | Also run the async work the request caused |
expect | status (default 200), body (a subset), and with process: runs and deliveries |
Bodies match as subsets: listed keys must be equal, others are ignored, and arrays match item by item.
What every example also checks
Each example runs on a fresh in-memory store, with a fixed clock (2023-11-14T22:13:20Z) and deterministic ids. Beyond the expected response, it fails if the request used more than its static bounds on any resource. Examples double as a check of the usage analysis.
In your test runner
import { runExamples } from "@aleph-lang/core";
import { expect, test } from "vitest";
import app from "../app.ts";
test("examples pass", () => {
expect(runExamples(app.toJSON()).filter((r) => r.failures.length > 0)).toEqual([]);
});For longer flows across many requests, use the dev engine directly:
import { DevEngine } from "@aleph-lang/core";
const engine = new DevEngine(app.build());
engine.seed("Product", [{ sku: "BK-1", price: "19.99" }]);
const response = engine.handle({ method: "POST", path: "/orders", caller: { userId: "ada" }, body: { /* ... */ } }, 1_000);
engine.process(2_000); // run due async work at t = 2s
engine.takeDeliveries(); // emails and messages sentLocal development
npx aleph dev app.ts --watch --seed seed.ts --data .aleph/dev.json --secret STRIPE_WEBHOOK_SECRET=whsec_testaleph dev serves your app on http://127.0.0.1:3000 with the same core a production runtime uses, compiled to WebAssembly, on an in-memory store. Async work (handlers, jobs, schedules, workflow steps, deliveries) runs right after each request and every second.
| Flag | |
|---|---|
--port 3000 | The port |
--watch | Rebuild when files change, keeping the data; a broken build keeps serving the last good one |
--seed seed.ts | Insert rows at start: a module (or JSON file) exporting { Entity: rows, users: { id: profile } } |
--data file.json | Keep the data in a file between restarts |
--secret NAME=value | Set a secret, such as a webhook signing key (repeatable) |
// seed.ts
export default {
Product: [{ sku: "BK-1", name: "Rust", price: "39.90", category: "books" }],
users: { ada: { email: "ada@example.com", name: "Ada" } },
};Callers
Pass the caller with headers: x-aleph-user, x-aleph-orgs, x-aleph-api-key, x-aleph-api-key-scopes, and x-aleph-profile. See Auth and callers.
What you see
Each request logs its route, status, and actual usage. Hot requests show their conflict retries, and $.log(...) values print under the request:
POST /orders → 201 post_orders (reads 3, writes 4, ops 61) 0.41ms
↳ log total: "40.00"
↳ send mail.send {"to":"ada@example.com","subject":"Order total 40.00"}Responses carry x-aleph-usage and x-aleph-route headers.
The inspection API
GET /__aleph/manifest, GET /__aleph/report | The manifest and its static bounds |
GET /__aleph/activity?after=<seq> | Requests, handler runs, deliveries, calls, logs, and reloads |
POST /__aleph/mock | Set an integration response: { integration, operation, response | error } |
POST /__aleph/process | Run due async work now |
POST /__aleph/seed | Insert rows |
POST /__aleph/reset | Drop all data |
The example apps include a React inspector panel built on this API.