Skip to content

arki-labs/dot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@arki/dot

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.

What is a pip?

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 lifecycleconfigurebootstartstopdispose;
  • 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 order

Each /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).

Installation

npm install @arki/dot
# or
bun add @arki/dot

Quick start

import { 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();

Pip authoring

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.

Lifecycle

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.

CLI

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

dot new <app-name>

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.

Architecture

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

Documentation

The full docs live in docs/:

  • Principlesread 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.
  • Diagnosticsapp.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.

License

MIT — see LICENSE.

About

[Read-only mirror] TypeScript-first application composition framework for the ARKI package family — lifecycle-aware pips, deterministic boot order, manifest/diagnostics envelopes, and an agent-native CLI. A DOT app is the sum of its pips.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors