The dependency graph
Most frameworks make you keep the whole request in your head: which middleware ran, whether req.user exists by now, what order things fire in, which plugin quietly deleted your body parser. That bookkeeping is where bugs live.
green-tea puts the request on the page. You declare what each unit needs and produces; the framework computes the order, checks the wiring, and can print the whole thing.
needs / provides
Section titled “needs / provides”Every node declares its inputs and outputs:
- A provider produces an app-scope value (a database handle, a config object) — resolved once at boot and cached.
- A step reads the request context and merges new keys into it (authentication producing
user, say) — runs per request. - A handler (a route method) declares, through its argument decorators, exactly which keys it depends on.
green-tea builds a directed graph from these declarations and topologically sorts it. You never write “put this before that” — order is derived from needs/provides.
@Step({ provides: 'user', needs: ['db', 'req'] }) // user depends on db and reqclass Authenticate { /* ... */ }Why it matters
Section titled “Why it matters”- No ordering bugs. There’s no positional
app.use()sequence to get wrong; the sort is the order. - Each route runs only its slice. A route runs the transitive closure of what its handler
@needs(plus always-run observer steps) — an auth step doesn’t run on a public route. - Boot fails loudly. If a handler needs a key nothing provides,
createAppthrows at boot with a “did you mean…?” hint — you never serveundefined. - You can read it.
app.explain(route)prints the ordered chain with origins;app.graph()/GET /__graph__render it as a diagram. See Graph introspection.
Two layers
Section titled “Two layers”green-tea exposes the graph two ways:
- The typed functional core,
flow— the compile-time guarantee. A handler that readsctx.userfails to compile if no step producesuser. - The declarative decorator layer —
@Provider/@Step/@Module/@Route/@Getplus argument decorators. Emits runtime metadata, builds and sorts the graph, and validates at boot.
Both describe the same graph; pick the ergonomics you want.
Why legacy decorators
Section titled “Why legacy decorators”green-tea uses legacy (experimental) TypeScript decorators — set experimentalDecorators: true in your tsconfig. This is a design decision, not inertia.
The argument-injection API (@param, @query, @body, @header, @needs, @ctx, @inbound, @abort) relies on parameter decorators, and the TC39 standard decorators proposal (Stage 3) deliberately does not include them. There is no standards-track way to decorate a parameter today, so handler(@param('id') id: string) is only expressible with legacy decorators.
“Stage 3” also means not finalized — the proposal can still change before engines ship it. green-tea tracks it and will revisit if parameter injection ever gets a standard path. It does not rely on emitDecoratorMetadata / design:type reflection — argument positions are recorded explicitly — so this is the only legacy surface it depends on.