Once upon a component

Small pieces, a whole application.

Ajo is a UI library built on JavaScript generators, and a small platform around it: Kit for routes and data, Engine to run sealed applications, and Server to host them on a machine you control.

Begin the story
A paper kite shaped like an application window, flying over a dune by the sea.A paper kite shaped like an application window, flying over a dune by the sea.

Chapter I · Ajo, the interface

State is just a variable.

A small UI library built on JSX and JavaScript generators. A stateful component is a generator: what you declare before its loop persists, and this.next() renders it again. No hooks, no dependency arrays, no rules about call order.

Control over reconciliation
key, memo and skip decide what is compared; everything else is updated where it stands.
Server rendering
ajo/html renders the same components to HTML, and the browser continues from that DOM.
Cloves
Shared behavior is a plain function of the component host, with no ordering rules and no registry.

Status Published on npm.

0 of 5 lanterns lit

lanterns.tsx
import type { Stateful } from 'ajo'

export const Lanterns: Stateful = function* () {

  let lit = 0

  const light = () => this.next(() => lit = lit === 5 ? 0 : lit + 1)

  while (true) yield (
    <>
      <p>{lit} of 5 lanterns lit</p>
      <div class="lanterns">
        {[1, 2, 3, 4, 5].map(n => <span key={n} class={n <= lit ? 'lit' : ''} />)}
      </div>
      <button set:onclick={light}>{lit === 5 ? 'Start over' : 'Light one'}</button>
    </>
  )
}
This component is the one rendered above, imported from the site’s own source. Its browser runtime, ajo 0.2.0, is 4.3 KB minified.
A small wooden pier linking three cabins over calm water.A small wooden pier linking three cabins over calm water.

Chapter II · Ajo Kit, the framework

Routes are files. Data is server truth.

A full-stack framework for Ajo: file routes and nested layouts, server loaders and form actions, SQLite migrations through Kysely, and live route updates over server-sent events. Vite runs development; the build stages a closed module graph for the engine.

Loaders are server truth
handler.ts returns the data a page renders; parent() reads what its layouts loaded.
SQLite and migrations
Kysely over SQLite, with up and down migrations from your App and its plugins in one history.
Live routes
Loaders track topics, actions emit them, and open pages receive a fresh route payload over SSE.

Status Published on npm. Pre-1.0: its API is still being refined.

src/notes/handler.ts
import type { ActionContext, Request, Response } from 'ajo-kit'
import { db } from '/src/data'

export async function page(req: Request) {
  req.track?.('notes')
  const notes = await db().selectFrom('notes').select(['id', 'text']).execute()
  return { notes }
}

export const actions = {
  async add(req: Request, _res: Response, action: ActionContext) {
    await db().insertInto('notes').values({ text: String(req.body.text) }).execute()
    action.emit('notes')
    return { ok: true }
  },
}
A brass clockwork mechanism sealing a scroll inside a glass bottle on the shore.A brass clockwork mechanism sealing a scroll inside a glass bottle on the shore.

Chapter III · Ajo Engine, the runtime

Sealed before it sails.

A focused runtime built on QuickJS-NG. Its compiler turns a Kit build into precompiled bytecode with a manifest of hashes, capabilities and filesystem roots; the engine loads nothing else and refuses to start when anything does not match.

Parserless production
The runtime executes precompiled QuickJS-NG bytecode. It has no parser, eval, REPL or Node module loader.
Declared authority
Runtime modules, filesystem roots and FIFOs must be declared; a module grant never bypasses a filesystem check.
Fail-closed startup
Missing or mismatched files, an unknown pin or missing required environment stop the App before it runs.

Status Pre-release. The native packages are verified on Linux x64 (Alpine musl and Freedesktop glibc).

  1. kit build.ajo/compiler.json

    A closed server graph, the client assets and the App’s declared authority.

  2. ajo-engine-compilerdist/ajo/manifest.json

    One bytecode chunk per module, each with its SHA-256, plus the exact engine pin.

  3. ajo-engine dist/ajoHTTP, SSE, SQLite

    Every file, hash, capability and root is checked before the entry module runs.

A small lighthouse whose tower is made of server racks, keeping watch over the sea.A small lighthouse whose tower is made of server racks, keeping watch over the sea.

Chapter IV · Ajo Server, the host

A lighthouse of your own.

An Alpine Linux host image with nginx and TLS, rootless Podman and an administration App. Deploy a named version from a Git branch, wait for the host’s receipt, and keep the previous image for rollback.

A reviewed image
An Alpine host built without network access from a reviewed package lock, with nginx, TLS and rootless Podman.
An administration App
Passkeys, live host data, typed operations and alerts, itself an Ajo Kit App running on the engine.
Named versions
Every arrival is versioned. If a new version does not answer, the host attempts to restore the previous image.

Status Experimental. Hosts come from signed releases, on this machine or on DigitalOcean; there is no automated host replacement yet.

Deploy from a named branch
pnpm add -D ajo-kit-server
pnpm add --save-optional --save-exact ajo-engine@0.2.0 ajo-engine-compiler@0.2.0

pnpm kit host create --provider digitalocean --name apps.example.com
pnpm kit deploy
A sandy path through the dunes toward a distant lighthouse.A sandy path through the dunes toward a distant lighthouse.

Every story begins at the shore.

Start with a single page, then follow the path as far as your application needs to go.