TypeScript-first application composition framework for the ARKI package family.
@arki/dot is the kernel that wires pips, lifecycle hooks, dependency
graphs, and diagnostics into a deterministic application boot. It gives library
authors a stable contract for declaring how their package participates in an
app, and gives app developers a single place to wire those packages together.
A pip is the unit a DOT app is built from — one self-describing, lifecycle-aware piece of an application. Each pip:
- declares a name, version, and the kinds of services it provides;
- runs a 5-hook lifecycle —
configure→boot→start→stop→dispose; - publishes typed services to a shared, type-safe registry that later pips can read from;
- composes deterministically — pips boot in declaration order and dispose in reverse.
The name comes from the small dots on dice, dominoes, and music notation: each pip is one small mark, and the combination of pips is what gives the app its value. Two pips on a die make a value of two; six pips make six. The pips are the app — not optional add-ons to a hidden core.
import { defineApp } from '@arki/dot';
import { env } from '@arki/env/dot'; // env pip
import { db } from '@arki/db/dot'; // db pip
import { kv } from '@arki/kv/dot'; // kv pip
const app = await defineApp('orders')
.use(env({ schema: { /* ... */ } })) // 1st pip — provides services.env
.use(db({ relations })) // 2nd pip — provides services.db
.use(kv({ url: process.env.KV_URL! })) // 3rd pip — provides services.kv
.boot(); // pips boot in declaration order
await app.services.db.query(/* ... */);
await app.dispose(); // pips dispose in REVERSE orderEach /dot subpath exports a pip for that package. The subpath names
the framework the adapter targets (DOT), not the unit (which is a pip).
Why not "plugin"? Plugins suggest optional add-ons to a core. DOT's reality is the opposite: there is no hidden core — the pips are the app. "Pip" names that truth, and ties the framework to the DOT name etymologically (a dot, a pip, a small mark that gains meaning by combining with others).
npm install @arki/dot
# or
bun add @arki/dotimport { defineApp, defineDotPip } from '@arki/dot';
const billingPip = defineDotPip({
name: 'billing',
version: '1.0.0',
configure(ctx) {
// Validate config, register schemas, no I/O.
},
async boot(ctx) {
// Open connections, register providers.
return { stripe: makeStripeClient() };
},
async start(ctx) {
// Begin processing — workers, subscriptions, schedulers.
},
async stop() {
// Stop processing — drain workers, unsubscribe.
},
async dispose() {
// Close connections, free resources.
},
});
const app = defineApp('acme')
.use(billingPip);
await app.configure();
await app.boot();
await app.start();
// app.manifest, app.diagnostics — agent-friendly envelopes.
await app.stop();
await app.dispose();defineDotPip(config) accepts five lifecycle hooks. Each receives a
phase-specific context with services contributed by earlier pips:
| Hook | Purpose |
|---|---|
configure |
Validate static config; declare schemas, routes, services. No I/O. |
boot |
Open connections; register providers; return a typed kit. |
start |
Begin processing (workers, subscribers, schedulers). |
stop |
Stop processing in reverse dependency order. |
dispose |
Free resources after stop. |
Pips can declare dependencies on others by name. The kernel computes a topological order and runs hooks deterministically — same input, same order, every time.
defineApp(name) returns a builder. Calling .use(pip) accumulates
pips. The lifecycle then flows:
defined ──configure()──▶ configured ──boot()──▶ booted ──start()──▶ started
│
disposed ◀──dispose()── stopped ◀──stop()───────┘
boot() runs configure() implicitly if you skipped it. start() runs
boot() implicitly. stop() and dispose() always run in reverse dependency
order, even when an earlier hook failed — failure isolation is part of the
contract.
@arki/dot ships a small CLI for scaffolding and inspecting apps:
# Scaffold a minimal DOT app (package.json, tsconfig, app entrypoint,
# env schema, AGENTS.md, README, gitignore, vitest boot test).
dot new my-app
# Preview the file operations without writing anything.
dot new my-app --dry-run --json | jq '.operations'
# Print the app manifest as a structured envelope.
dot explain --app ./my-app.ts
# Run boot-time diagnostics; non-zero exit if any check fails.
dot doctor --app ./my-app.ts
# Every command supports --json for agent-friendly output.
dot explain --app ./my-app.ts --json | jq '.data.pips'The CLI emits the same envelope shape as the in-process diagnostics snapshot
(app.diagnostics), so the same downstream tools can consume either.
Scaffolds a minimal DOT app under <app-name>/ (override with --target).
The scaffold ships a defineApp(...) entrypoint wired to @arki/env/dot,
a vitest that boots the app and asserts the manifest shape, and an
AGENTS.md documenting verification commands and the public/private
boundary for agents working in the generated tree. Pass --dry-run --json
to inspect the exact file operations (path, action, contentHash,
contentBytes, reason) before committing them to disk; --force
overwrites pre-existing files in the target directory.
Templates live at templates/app-minimal/ and
ship with the published tarball.
@arki/dot is intentionally small: it defines the contracts (pip shape,
lifecycle hooks, manifest schema, diagnostics envelope) and runs them. Adapters
that bridge databases, queues, auth providers, and HTTP routers live in their
own packages and consume @arki/dot as a peer dependency.
This keeps the kernel free of optional dependencies and lets each adapter ship at its own cadence.
The full docs live in docs/:
- Principles — read first. The five rules every API, error, and PR is measured against. Slightly playful, very precise.
- Quickstart — boot your first app in five minutes.
- Pip authoring — write your own
DotPip. - Lifecycle — the 5-hook contract.
- Diagnostics —
app.manifest,app.diagnostics,dot explain,dot doctor. - Adapter authoring — expose your package as a DOT pip.
- Agent guide — how coding agents inspect, modify, and verify DOT apps.
- Release policy — SemVer and deprecation.
Agent-discoverable index: llms.txt.
MIT — see LICENSE.