Edit this page

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.ts

What an example can set

Field
givenRows that exist first, by entity (validated; defaults, indexes, and counters apply)
usersUser profiles in the directory
asThe caller: { userId, orgs, apiKey, profile }
requestparams, query, body, headers
mocksIntegration responses: { integration, operation, response | error }
secretsTest secrets, such as a webhook signing key
processAlso run the async work the request caused
expectstatus (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 sent

Local development

npx aleph dev app.ts --watch --seed seed.ts --data .aleph/dev.json --secret STRIPE_WEBHOOK_SECRET=whsec_test

aleph 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 3000The port
--watchRebuild when files change, keeping the data; a broken build keeps serving the last good one
--seed seed.tsInsert rows at start: a module (or JSON file) exporting { Entity: rows, users: { id: profile } }
--data file.jsonKeep the data in a file between restarts
--secret NAME=valueSet 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/reportThe manifest and its static bounds
GET /__aleph/activity?after=<seq>Requests, handler runs, deliveries, calls, logs, and reloads
POST /__aleph/mockSet an integration response: { integration, operation, response | error }
POST /__aleph/processRun due async work now
POST /__aleph/seedInsert rows
POST /__aleph/resetDrop all data

The example apps include a React inspector panel built on this API.