Explore the public boundaries that connect Application, Runtime, and Tooling.
Loutre is a TypeScript Application Graph Kernel. Application structure is compiled once into an Application Model; Runtime execution, CLI graph inspection, OpenAPI projection, and deployment tooling all consume that same model.
If you are building your first Application, start with Getting Started.
Overview
Loading diagram…
Core owns only portable Application concerns:
Application Definition and canonical Application Model
Module boundaries and Provider / DI
Environment and Arguments
Lifecycle
active execution lifetime
Runtime Capability identity
generic Layer composition
diagnostics and Graph projection
HTTP, Tasks, MessagePort, WebSocket, Queue consumers, and similar execution semantics are owned by Execution Extensions. Core does not branch on protocol identity.
An Application Definition does not own a listener, timer, or process. It contains the compiled model, which is the single source of truth used by Runtime and Tooling.
A Module declares Providers and Execution Definitions:
Execution Definitions are branded by their owning Extension. During model construction, the Extension compiles each definition into a protocol-neutral execution contribution containing its dependency edges, required Runtime Capabilities, compiled payload, and Graph metadata. An Extension may also expose same-Extension execution references; Core follows that reference closure recursively, so registering a Trigger automatically includes the Task it invokes without retaining raw definitions in the compiled model.
HTTP owns route compilation, request decoding, response finalization, middleware composition, authentication helpers, CORS, streaming, and OpenAPI metadata. The Application Model only sees an Execution Extension contribution.
Tasks Extension
Task, Trigger, and Queue Consumer semantics live in @loutrejs/loutre/tasks.
MessagePort is provided by @loutrejs/loutre/message-port; WebSocket is provided by @loutrejs/loutre/websocket. Both own their execution model, runtime, host API, and graph projection. WebSocket additionally depends on the public HTTP extension surface for its HTTP upgrade handshake; Core remains transport-agnostic.
This keeps transport semantics outside Core while preserving one Application Model.
DI and Module visibility
Providers are application-scoped by default. Dependencies declared with inject() are collected into the Application Model.
OpenAPI is also an Extension projection. Core does not know HTTP route semantics.
Design invariant
There is one canonical Application Model. Runtime adapters, CLI, build, graph projection, and Execution Extensions must not maintain a second parallel model of the Application.
Loutre is a TypeScript Application Graph Kernel. Application structure is compiled once into an
Application Model; Runtime execution, CLI graph inspection, OpenAPI projection, and deployment tooling all consume that same model.If you are building your first Application, start with Getting Started.
Overview
Core owns only portable Application concerns:
HTTP, Tasks, MessagePort, WebSocket, Queue consumers, and similar execution semantics are owned by Execution Extensions. Core does not branch on protocol identity.
Packages
@loutrejs/loutre@loutrejs/node@loutrejs/bullmq@loutrejs/clicreate-loutreThe main package exposes these important subpaths:
For the rationale behind separating published packages from subpath exports, see the package distribution architecture ADR (Japanese).
@loutrejs/loutre@loutrejs/loutre/http@loutrejs/loutre/tasks@loutrejs/loutre/message-port@loutrejs/loutre/websocket@loutrejs/loutre/graph@loutrejs/loutre/runtime@loutrejs/loutre/http/openapi@loutrejs/loutre/presentation@loutrejs/loutre/runtime/*Application Definition and Model
defineApplication()builds the canonical model immediately.const application = defineApplication({ modules: [AppModule], arguments: AppArgs, })An Application Definition does not own a listener, timer, or process. It contains the compiled
model, which is the single source of truth used by Runtime and Tooling.A Module declares Providers and Execution Definitions:
const AppModule = defineModule({ environment: [AppEnv], providers: [UsersService], executions: [UsersHttp, heartbeat], })Execution Definitions are branded by their owning Extension. During model construction, the Extension compiles each definition into a protocol-neutral execution contribution containing its dependency edges, required Runtime Capabilities, compiled payload, and Graph metadata. An Extension may also expose same-Extension execution references; Core follows that reference closure recursively, so registering a Trigger automatically includes the Task it invokes without retaining raw definitions in the compiled model.
HTTP Extension
HTTP semantics live in
@loutrejs/loutre/http.import { http } from '@loutrejs/loutre/http' import { z } from 'zod' export const UsersContract = http.contract({ get: { method: 'GET', path: '/users/{id}', request: { params: { id: z.string() }, }, responses: { found: { status: 200, body: z.object({ id: z.string(), name: z.string() }), }, }, }, }) export const UsersHttp = http.implementation({ name: 'UsersHttp', contract: UsersContract, factory: (users = inject(UsersService)) => ({ async get(ctx) { return ctx.response.found({ body: await users.get(ctx.input.params.id), }) }, }), })HTTP owns route compilation, request decoding, response finalization, middleware composition, authentication helpers, CORS, streaming, and OpenAPI metadata. The Application Model only sees an Execution Extension contribution.
Tasks Extension
Task, Trigger, and Queue Consumer semantics live in
@loutrejs/loutre/tasks.import { fixedDelay, task } from '@loutrejs/loutre/tasks' export const cleanup = task<void, void>({ name: 'cleanup', factory: () => async () => { // work }, }) export const heartbeat = fixedDelay({ name: 'heartbeat', delay: 5_000, immediate: true, task: cleanup, }) const WorkerModule = defineModule({ executions: [heartbeat], })A hosted Application receives the Extension-owned
tasksAPI only when the model contains Tasks executions.const app = await bootstrapApplication({ application }) await app.tasks.run(cleanup) await app.tasks.start()MessagePort and WebSocket
MessagePort is provided by
@loutrejs/loutre/message-port; WebSocket is provided by@loutrejs/loutre/websocket. Both own their execution model, runtime, host API, and graph projection. WebSocket additionally depends on the public HTTP extension surface for its HTTP upgrade handshake; Core remains transport-agnostic.This keeps transport semantics outside Core while preserving one Application Model.
DI and Module visibility
Providers are application-scoped by default. Dependencies declared with
inject()are collected into the Application Model.class UsersService { constructor(readonly repository = inject(UserRepository)) {} }Cross-module dependencies require an explicit
exports/importspath. Visibility is validated while the model is built, before Runtime startup.For using dependency-source metadata, see Accessing dependency sources in custom loggers (Japanese). For ownership responsibilities, see the ownership ADR (Japanese).
Environment and Arguments
Runtime inputs are declared as typed contracts and bound before Provider construction.
class AppEnv extends defineEnv( z.object({ PORT: z.coerce.number().int().positive() }), ) {} const AppModule = defineModule({ environment: [AppEnv], })Runtime adapters provide the environment source.
bootstrapApplication()can receive an explicit source for tests or embedded execution.Runtime and lifecycle
bootstrapApplication()creates and initializes the portable Kernel-hosted Application.const app = await bootstrapApplication({ application })Runtime adapters add platform ownership around that Kernel application. Node.js, for example, owns the HTTP listener:
const app = await nodeRuntime.create({ application }) await app.serve({ port: 3000 })Shutdown first drains Extension-owned work and active executions, then closes Extension runtimes, then runs Provider lifecycle cleanup.
Graph and tooling
The CLI loads the Application Definition, reads its compiled model, and projects it without rebuilding a second architecture representation.
OpenAPI is also an Extension projection. Core does not know HTTP route semantics.
Design invariant
There is one canonical Application Model. Runtime adapters, CLI, build, graph projection, and Execution Extensions must not maintain a second parallel model of the Application.