Quickstart
For @arki/[email protected] · source docs/quickstart.md
Build a two-plugin app, break it on purpose, read the error, fix it, and
inspect it with dot explain and dot doctor. It takes about ten minutes.
Every output on this page is printed by the block above it each time the docs
are tested (bun run test:docs). It is the real output, not a copy.
database ── provides db ──▶ orders
(boots 1st) (boots 2nd)1. Install
bun add @arki/dot2. Write the app
Create src/app.ts. Start with a database plugin. It provides one service,
db. Here it is an array in memory, so the tutorial needs no Postgres:
type Order = { readonly id: number; readonly cents: number };
export const database = plugin({
name: 'database',
provides: {
// A stand-in for a real client. It is a plain array, so nobody gets paged at 3 a.m.
db: () => {
const rows: Order[] = [];
return {
insert: (row: Order) => rows.push(row),
count: () => rows.length,
};
},
},
});Then an orders plugin. It needs db and provides orders:
export const orders = plugin({
name: 'orders',
needs: { db: database.provides.db }, // the handle, not a string: a typo cannot compile
provides: {
orders: ({ db }) => ({
place: (cents: number): Order => {
const order = { id: db.count() + 1, cents };
db.insert(order);
return order;
},
}),
},
});Mount both, provider first, and export the builder:
export default defineApp('shop').use(database).use(orders);Why export the builder and not a booted app: dot explain and dot doctor
own the lifecycle. They decide how far the app goes, and they clean up after
it.
3. Make the mistake
Swap the two .use() calls, so orders comes before the plugin it needs:
export default defineApp('shop').use(orders).use(database);In TypeScript, your editor rejects the .use(orders) line. The last line of
the type error says what to do:
Property ''DOT: this unit needs services that nothing before it provides. Mount their providers earlier (.use → .features → .serve)'' is missing in type ...The compiler is the first guard. The kernel is the second, for code the
compiler never sees: plain JavaScript, or TypeScript that Bun runs without a
type check. The example file is JavaScript, so it loads. Run dot doctor on
it:
dot doctor --app src/app.js --json | jq '.error.code, .error.data'This page runs the same command line in-process with program.main:
import { program } from '@arki/dot/cli';
let out = '';
await program.main(['doctor', '--app', 'docs/examples/quickstart-wrong-order.mjs', '--json'], {
stdout: text => {
out += text;
},
});
const { error } = JSON.parse(out);
console.log(error.code);
console.log(JSON.stringify(error.data));failure.unexpected_defect
nullfailure.unexpected_defect is the canonical managed defect. The process exits
with code 3. Its original cause is private, so JSON data is null. In process,
UnexpectedDefect.cause retains the kernel diagnostic error with stable
ARKI_DOT_E020 metadata naming both plugins and their positions. Read the local
cause or correct the wiring using the compiler's provider-order diagnostic.
The kernel caught it at configure, before any boot hook ran. Nothing was
opened, so there is nothing to clean up.
4. Fix it
Put database first again, as in step 2. The type error goes away, and the
kernel has nothing to say.
5. Read the graph: dot explain
dot explain --app src/app.ts --json | jq -c '.data.plugins[], .data.dependencies[]'Side effects: dot explain imports the app file, so its top-level code runs.
Then it calls configure on each plugin. It never boots: no provides
factory runs, no connection opens. A plugin that keeps the rules does no I/O
in configure, so this is safe to run against production config.
import { program } from '@arki/dot/cli';
let out = '';
await program.main(['explain', '--app', 'docs/examples/quickstart.ts', '--json'], {
stdout: text => {
out += text;
},
});
const { data } = JSON.parse(out);
for (const entry of [...data.plugins, ...data.dependencies]) console.log(JSON.stringify(entry));{"name":"database","dependencies":[],"provides":["db"]}
{"name":"orders","dependencies":["db"],"provides":["orders"]}
{"from":"orders","to":"database","kind":"requires","key":"db"}.data is the manifest itself. There is no .data.manifest.
6. Check it boots: dot doctor
dot doctor --app src/app.ts --json | jq -c '.data.status, (.data.plugins[] | {plugin, status}), .data.issues'Side effects: dot doctor boots the app for real. Every provides factory
and every boot hook runs, so connections open and a migrations plugin
applies its migrations. Then it disposes the app. It never calls start, so
workers and servers stay off. Point it at a database you are allowed to
change.
import { program } from '@arki/dot/cli';
let out = '';
await program.main(['doctor', '--app', 'docs/examples/quickstart.ts', '--json'], {
stdout: text => {
out += text;
},
});
const { data } = JSON.parse(out);
console.log(JSON.stringify(data.status));
for (const { plugin, status } of data.plugins) console.log(JSON.stringify({ plugin, status }));
console.log(JSON.stringify(data.issues));"success"
{"plugin":"database","status":"ok"}
{"plugin":"orders","status":"ok"}
[]An empty issues array means the app is healthy. .data is the diagnostics
snapshot itself. There is no .data.diagnostics.
Next steps
- Dependency injection: handles, tokens,
rename, lazy services and features. - Plugin authoring: write a plugin with all five hooks.
- The
dotcommand:dot newscaffolds a full app,dot devruns it. - Deploy an app: ship it in a container.