← the tour · concept
concept xii
Vendored, not imported
Code lands in your repo. You own it, fix it, read it. The dependency you don't have can't hurt you.
Most frameworks ask you to install them. orion does something different: it arrives as code
in your repository. The belt, the patterns, the recipes — none of them live behind a module
boundary you didn't write. You can read every line with grep, fix a bug with one
edit, and delete anything you don't use. This is the shadcn move applied to a backend
toolbelt, and it has sharper consequences here than it does for UI components.
Three tiers, one move
There are three distinct tiers, and understanding the boundary between them is the whole model:
The belt
The belt is orion's zero-dependency core: the SSE writer, the router, the store adapter,
the pub/sub bus, the templating helpers, the auth primitives — roughly six hundred lines
of TypeScript that implement the Datastar protocol and the one loop with nothing but Node
builtins. Its package.json is literally {}. When you run
orion new, the belt is copied into your project. It isn't installed from npm;
it's in your tree. You own it outright.
Patterns
Patterns are pre-built UI interactions — toast notifications, modals, inline-edit fields,
comboboxes, presence avatars. Each one is a pair: a patterns/*.ts view-function
file with the server-side HTML and helpers, and a public/*.css file with its
semantic styles. orion gen pattern toast copies both into your project and
prints the <link> line to add to your layout. The Datastar attributes
arrive already correct — the dense ones (combobox keyboard navigation, inline-edit
commit/cancel) are exactly why these are generated rather than documented. After generation
they're yours: edit the HTML, adjust the CSS, delete the parts you don't need.
Recipes
Recipes cover the problems that do need an outside dependency — a job queue with durable
workflows, transactional email, billing webhooks. A recipe is a small glue file, generated
into your project by orion gen recipe <name>, that wires a specific library
into one of the belt's seams (Store, Bus, Jobs). The dependency lands in
your package.json. The belt's package.json never sees it.
Zero-dep stays literal.
Current recipe list: jobs-honker (durable queues and workflow
primitives inside your SQLite file), email-upyo (transactional mail),
billing-stripe (checkout + webhooks → entitlements),
config-database-url, flags-launchdarkly.
The seam test
The boundary between a recipe and documentation is sharp, and the test is simple: a recipe must wire into the loop. It must connect to a belt seam — the Store interface, the Bus interface, the Jobs interface — so a feature that uses it never changes a line when you swap the implementation underneath. "Here's a good library" with no loop integration is documentation, not a recipe. The belt still never imports it.
This matters because it keeps the belt honest. A recipe for honker gives you durable
workflow primitives inside your SQLite file and enqueue still participates in the command's
own transaction. A recipe for upyo gives you transactional email through the same
send() call your commands already know. The dependency is real; the seam means
no feature ever touches it directly.
enqueue() is a plain INSERT
on your store, atomic inside the command's own transaction. orion gen recipe jobs-honker
copies a glue file that swaps in honker's SQLite extension — NOTIFY/LISTEN wakeups instead
of polling, cron, durable workflow steps. Every feature that calls enqueue()
is unchanged. The belt imports nothing; your project now depends on honker.
Why this compounds
Three reasons, and they reinforce each other:
- Smaller security blast radius. Fewer dependencies, and the ones you keep are
glue you reviewed at generation time. A compromised npm release or a supply-chain attack
can't reach through a dependency you don't have. The belt's
package.json: {}is the strongest possible statement: there is no upstream to compromise. - You can fix it. A bug or a slow path in vendored code is a one-line edit in your tree — not a fork, an upstream issue, and a wait. There is no version to pin, no patch to upstream. The mechanism is where you work; you work where the mechanism is.
- Agent-legible. In an agentic codebase, the mechanism your AI assistant must
reason about is in the repo, greppable — not hidden behind a module boundary
in
node_modules. An agent can readbelt/sse.tsand understand exactly how SSE frames are written; it cannot usefully reason about a minified package it can't see. Vendoring keeps the core visible to every tool in your workflow.
The bet: a codebase whose mechanisms are readable is cheaper to maintain, cheaper to debug, and cheaper to hand to an agent than one that delegates understanding to a dependency graph. Vendoring is the posture; the three tiers are the shape it takes.
What the CLI does
The generator is the delivery mechanism. There's no install step for patterns or recipes — just a copy into your tree and a short list of things to wire:
orion gen feature todos # four-file slice, yours to edit
orion gen pattern combobox # SQL-backed, keyboard-navigable, vendored in
orion gen recipe jobs-honker # durable queues; the dep lands in your package.json
Each generator prints exactly what it added and, for recipes, the
npm install line. After that it's your code: grep it, edit it, delete what
you don't use. There is no eject step because there was never anything to
eject from.
The upgrade model
Because the belt is in your tree, upgrades are guided diffs, not semver bumps. The CLI
will eventually ship orion upgrade — a command that shows what changed in
the upstream belt since you vendored your copy and lets you accept, reject, or merge
changes file by file. This is the tradeoff made explicit: you give up automatic upgrades;
you gain the ability to see exactly what you're running and decide whether you want it.
For a backend toolbelt whose mechanisms participate in every request, that tradeoff is
worth making.