Ajo Kit
Build and configuration
kit build, the engine descriptor, plugins and the Vite and Node APIs.
A Kit application has one production target: Ajo Engine. kit build prepares everything the engine compiler needs, checks that the server code can run there, and records what the application is allowed to touch. This page covers that build, the descriptor it writes, how to declare engine authority and plugins, and the Vite and Node APIs underneath the kit command.
kit build
The command runs Vite twice with your vite.config.ts, once for the browser and once for the server, and stages the results in .ajo/, which it clears first.
client/ transformed client assets and index.html
server/entry.js generated engine entry
server/chunks/ routes, handlers, middleware and their dependencies
server/migrations/ one module per migration, plugins and project
compiler.json the descriptor for ajo-engine-compilerThe server build bundles every dependency into one closed module graph and resolves packages through their ajo export condition, which selects the engine versions of modules such as ajo-kit/database. Kit then audits the emitted code and fails the build, listing each finding, when it contains:
- a Node builtin, such as
node:fs; - any other import that is neither relative nor a
runtime:*engine module; - a dynamic
import()whose argument is not a string literal; - a CSS or TypeScript import that survived the server build;
IntlAPIs other thanIntl.DateTimeFormatandIntl.RelativeTimeFormat, or a read ofnavigator.language.
Without options, kit build ends by printing the command that seals the staging tree:
ajo-engine-compiler --input .ajo/compiler.json --output dist/ajoWith --compiler <path> it removes any previous dist/ajo and runs the given compiler executable with those arguments. The official starter keeps this as a script, where the installed binary name resolves.
{
"scripts": {
"build": "kit build",
"artifact": "kit build --compiler ajo-engine-compiler"
}
}The compiler and engine are native Linux x64 tools. Seal and run covers installing them and running dist/ajo.
The descriptor
.ajo/compiler.json is the handoff between Kit and the compiler. It uses schema 1 and lists the staged modules, the compiled migrations and client assets, and the application’s declared authority. This abridged example comes from the official starter, which uses authentication, HTTP mail and the Ajo Server plugin:
{
"schema": 1,
"entry": "server/entry.js",
"modules": [
"server/entry.js",
"server/chunks/handler-C0AW08qE.js",
"server/migrations/migration-0001-Bx9cAYx6.js",
"server/migrations/migration-0007-7hM6PtWW.js"
],
"client": "client",
"migrations": [
{ "name": "plugin/ajo-kit-auth/0001_initial", "module": "server/migrations/migration-0001-Bx9cAYx6.js" },
{ "name": "project/0001_notes", "module": "server/migrations/migration-0007-7hM6PtWW.js" }
],
"env": {
"required": ["NODE_ENV", "APP_URL", "MAIL_FROM", "MAIL_TOKEN", "MAIL_URL"],
"optional": ["APP_SECRET", "DATABASE_PATH", "TRUST_PROXY", "AJO_TIMING", "HOST", "PORT", "AJO_ORIGINS_FILE"]
},
"data": { "required": true },
"fs": { "roots": ["/ajo/data", "/ajo/origin"] },
"ipc": { "pipes": [] },
"capabilities": ["runtime:net"]
}| Field | Contents |
|---|---|
modules | Every emitted server module, the entry first. |
client | The client asset directory, always client. |
migrations | Compiled migrations with their qualified names, in name order. Duplicate names fail the build. |
env | Kit’s base variables followed by the sorted names you declare. The base lists are NODE_ENV and APP_URL (required) and APP_SECRET, DATABASE_PATH, TRUST_PROXY, AJO_TIMING, HOST and PORT (optional). |
data.required | True when the server imports ajo-kit/database, the root middleware exports bootstrap, or there are migrations. The engine then refuses to start without AJO_DATA. |
fs.roots, ipc.pipes | The declared filesystem roots and named pipes, sorted. |
capabilities | runtime:net when the graph includes the HTTP transport of ajo-kit-mail; otherwise empty. |
Declaring engine authority
Beyond Kit’s base variables, everything the descriptor declares about the environment, filesystem roots and pipes comes from package.json#kit.engine, in the application and in its plugins. Every field is optional, and an absent block declares nothing.
{
"kit": {
"engine": {
"env": { "required": ["MAIL_FROM", "MAIL_URL", "MAIL_TOKEN"] },
"fs": { "roots": ["/ajo/data"] }
}
}
}| Field | Meaning |
|---|---|
env.required | Variables that must be set and non-empty, or the engine refuses to start. |
env.optional | Variables the application reads when they are set. |
fs.roots | Directories the engine’s filesystem and SQLite access may use. Declare /ajo/data for a database on disk. |
ipc.pipes | Named pipes (FIFOs) the application may write to. |
The build validates this block and fails with the name of the offending entry when:
- it contains a key other than
env,fsandipc, or other thanrequired/optional,rootsandpipesinside them; - a value is not an array of non-empty strings, or repeats an entry;
- a variable name does not match
^[A-Z_][A-Z0-9_]*$, repeats one of Kit’s base variables, or appears in both lists; - a path is not an absolute, normalized POSIX path: no backslashes, no
.or..segments, no repeated or trailing slash.
Declaring a root does not create it, and setting AJO_DATA grants nothing by itself: the data directory must exist and sit inside a declared root. Data on disk walks through the SQLite case.
Plugins
Kit discovers plugins among the packages your package.json lists in dependencies or devDependencies. A plugin is an installed package whose name starts with ajo- (other than ajo-kit) and whose manifest has a kit block. The installed manifest’s name must match the declared one, or Kit stops with an error.
| Field | Effect |
|---|---|
alias | Adds an @kit/<alias> import alias for the package. |
serverOnly | Blocks the package from the client module graph. |
migrations | A folder of migrations, stored as plugin/<package>/<name>. See sources and identities. |
commands | A module exporting register(cli), which adds subcommands to kit. |
engine | An env, fs and ipc block with the same shape and rules as the application’s. |
{
"kit": { "alias": "auth", "serverOnly": true, "migrations": "./dist/migrations/" }
}{
"kit": {
"serverOnly": true,
"commands": "dist/commands.js",
"engine": {
"env": { "optional": ["AJO_ORIGINS_FILE"] },
"fs": { "roots": ["/ajo/origin"] }
}
}
}Each plugin’s engine block is validated on its own, and errors carry the plugin’s name. The build then combines every declaration with the application’s. Shared entries appear once, and a variable that any contributor requires is required in the descriptor even if another lists it as optional.
That is how ajo-kit-server adds kit deploy and the managed origin file. When the combined declaration names both AJO_ORIGINS_FILE and the /ajo/origin root, the build loads Kit’s reader for that file; other applications never load it. The Ajo Server host provides /ajo/origin as a read-only mount, even for an App without custom domains. To run such an artifact directly on the engine, provide the same mount.
Vite API
ajo-kit/vite exports the plugin that turns a Vite project into a Kit application.
import { defineConfig } from 'vite'
import { kit } from 'ajo-kit/vite'
import unocss from 'unocss/vite'
export default defineConfig({
// The client entry owns a virtual route graph; keep it in Vite's plugin pipeline.
optimizeDeps: { exclude: ['ajo-kit/client'] },
plugins: [...kit({ css: ['virtual:uno.css'], guard: [/\/src\/database/] }), unocss()],
})kit(options?) returns an array of plugins: the route and handler registries, the @kit aliases, the server-only guard, CSS entries and hot updates for pages and layouts.
| Export | Purpose |
|---|---|
options.guard | Extra patterns for modules that must never reach the browser. Each is a RegExp, a substring, or a function of the module id. They add to the defaults, which cover every handler and wares file and every serverOnly plugin. |
options.css | Style entries imported by the client before the application hydrates. |
jsx | { jsx: 'automatic', jsxImportSource: 'ajo' }, for configurations that pass JSX options through Vite’s esbuild setting. |
defaults | The CLI’s paths: ./database.sqlite, db/migrations and db/seeds. |
A module named *.client.ts (or .js, .tsx, .jsx) is treated as safe for the browser and bypasses the guard patterns; its own imports are still checked. When the guard trips, the error shows the chain of imports that led there.
Node host API
ajo-kit/node exposes what the kit command uses, for test harnesses and custom scripts. Node is only the development, build and test host; there is no Node production server.
import { dev, listen } from 'ajo-kit/node'
const app = await dev({ hmr: false })
const port = await listen(app, 4173, { strict: true })| Export | Behavior |
|---|---|
dev(options?) | Creates the development server: Vite in middleware mode plus Kit’s server, rendering ./index.html (or a built-in template when it is missing). Reloads server routes when a handler, middleware, page or layout file changes. options.hmr is passed to Vite. |
listen(app, port = 5173, { strict }) | Starts an HTTP server and resolves the port it bound. When the port is taken it tries the next one, unless strict is set, in which case it rejects. |
build() | Runs the engine build in the current directory and resolves { descriptor, findings, staging }. |
compile(html) | Returns a function that fills the <!-- ssr:name --> slots of an HTML template; missing slots become empty. |
The default faces of ajo-kit/database and ajo-kit/platform are likewise Node shims for Vite, tests and CLI commands, not production runtimes.
Production environment
The engine validates its environment before it runs migrations or opens a port.
| Variable | Meaning |
|---|---|
NODE_ENV | Must be production. |
APP_URL | Required. The public origin as an absolute http or https URL; origin(req) builds canonical links from it. |
APP_SECRET | Required when the server includes ajo-kit-auth. Use a random value of at least 32 characters. |
AJO_DATA, DATABASE_PATH | The data directory and a database file relative to it; see Data on disk. |
HOST, PORT | The listen address, 0.0.0.0 and 8080 by default. |
TRUST_PROXY | 1 or true to trust X-Forwarded-For and X-Forwarded-Proto from your proxy. |
AJO_TIMING | Any value other than empty, 0, false or off adds Server-Timing and X-Ajo-Bytes headers and route timing logs. |
AJO_ORIGINS_FILE | Set by the host for managed origins; it must be /ajo/origin/origins.json. |
The engine checks the Host of every request before it serves assets or routes. Without managed origins the host must be APP_URL’s; anything else receives 421 Misdirected Request. Point APP_URL at the exact address clients use, including when you run the artifact locally.
Managed origins
An Ajo Server host can serve one App on several domains. It writes an ajo.origins/v1 manifest of one to nine unique HTTPS origins to /ajo/origin/origins.json and sets AJO_ORIGINS_FILE. Then APP_URL must be an exact HTTPS origin, without path, port or credentials, and must appear in that list. Requests for any listed host are admitted, and responses carry X-Ajo-Origins: v1.
- Seal and runInstall the native pair, seal the build and run it.
- The artifactWhat the sealed directory contains and how it loads.
- DatabaseMigrations and the data directory.
- Deploy an AppShip the artifact to an Ajo Server host.
Source of truth: ajo-kit README