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/dot

2. 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
null

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

All docs for @arki/[email protected]