# Middleware Model
URL: /docs/concepts/middleware-model



Middleware Model [#middleware-model]

Middleware in @taucad/runtime uses the onion model: each layer wraps the next, and code after the inner call runs on the return journey. This pattern enables caching, transformation, and cross-cutting concerns without modifying kernel code.

Context and Motivation [#context-and-motivation]

Kernel operations (getParameters, createGeometry, exportGeometry) often need cross-cutting behavior: caching results, transforming coordinates, adding edge data for rendering. Hard-coding these into each kernel would duplicate logic and couple kernels to UI concerns. Middleware provides a single interception point where such behavior can be composed declaratively. The onion model ensures that even short-circuited results flow through upstream middleware for consistent post-processing.

How It Works [#how-it-works]

Each middleware can define wrap-style hooks: `wrapGetParameters`, `wrapCreateGeometry`, and `wrapExportGeometry`. A hook receives `(input, handler, runtime)` and must call `handler(input)` to continue the chain. Code before `handler()` runs on the way down; code after runs on the way back up.

<Mermaid
  chart="flowchart TB
    subgraph Request[&#x22;Request Journey (outside-in)&#x22;]
        R1[&#x22;Middleware A: pre&#x22;]
        R2[&#x22;Middleware B: pre&#x22;]
        R3[&#x22;Middleware C: pre&#x22;]
        R4[&#x22;Kernel&#x22;]
    end

    subgraph Response[&#x22;Response Journey (inside-out)&#x22;]
        S4[&#x22;Kernel&#x22;]
        S3[&#x22;Middleware C: post&#x22;]
        S2[&#x22;Middleware B: post&#x22;]
        S1[&#x22;Middleware A: post&#x22;]
    end

    R1 --> R2
    R2 --> R3
    R3 --> R4
    R4 --> S4
    S4 --> S3
    S3 --> S2
    S2 --> S1"
/>

Execution order for `[A, B, C]` with A outermost:

1. A pre -> B pre -> C pre -> kernel
2. kernel returns
3. C post -> B post -> A post -> result to caller

wrapCreateGeometry, wrapGetParameters, wrapExportGeometry [#wrapcreategeometry-wrapgetparameters-wrapexportgeometry]

The three hooks mirror the three kernel operations:

* **wrapCreateGeometry** -- Wraps geometry computation. Use for caching (short-circuit on hit), coordinate transforms, or edge detection. Receives `CreateGeometryInput`; returns `CreateGeometryResult`.
* **wrapGetParameters** -- Wraps parameter extraction. Use for parameter caching or schema transformation. Receives `GetParametersInput`; returns `GetParametersResult`.
* **wrapExportGeometry** -- Wraps export. Use for format conversion or post-processing of export blobs. Receives `ExportGeometryInput`; returns `ExportGeometryResult`.

A middleware can implement any subset. Unimplemented hooks are skipped; the chain passes through directly to the kernel (or the next middleware that implements the hook).

KernelMiddlewareRuntime [#kernelmiddlewareruntime]

Each wrap hook receives a `KernelMiddlewareRuntime` as the third argument, providing:

* `logger` -- Logger with the middleware name pre-configured
* `filesystem` -- [`RuntimeFileSystem`](../api/filesystem) for file I/O (e.g., reading/writing cache files)
* `state` -- Type-safe `MiddlewareState<T>` with `.value` and `.update(partial)`, validated against `stateSchema`
* `options` -- Parsed options from `optionsSchema`
* `dependencies` -- Read-only array of `Dependency` objects for the current operation
* `dependencyHash` -- SHA-256 hash of all dependencies, useful as a cache key

State Management with Zod stateSchema [#state-management-with-zod-stateschema]

Middleware that needs to persist data during an operation can define `stateSchema` (a Zod object schema). The framework creates a `state` object per operation with `state.value` and `state.update(partial)`. Updates are validated against the schema. State is scoped to a single operation; it does not persist across renders.

Comparison to Express/Koa Middleware [#comparison-to-expresskoa-middleware]

Express and Koa use a similar pattern: `(req, res, next) => { ...; next(); ... }`. The @taucad/runtime model differs in two ways:

1. **Wrap style** -- Instead of `next()`, you receive `handler` and call `handler(input)`. The return value flows back through the chain. This makes async composition and result transformation natural.
2. **Typed input/output** -- Each hook has a specific input and output type. No generic request/response object; the types reflect the operation.

Key Relationships [#key-relationships]

* **Middleware and Kernel**: Middleware never calls the kernel directly. It calls `handler(input)`, which eventually reaches the kernel. The kernel is unaware of middleware.
* **Middleware and Runtime**: Each middleware receives a `KernelMiddlewareRuntime` with the services listed above.
* **Middleware Order**: Registration order determines wrapping order. First registered = outermost. For caching, put caches early (outer) so they wrap the expensive inner computation.

Implications [#implications]

* **Short-circuiting** -- A cache hit can return without calling `handler()`. The result still flows through upstream middleware post-processing, so coordinate transforms and edge detection apply to cached results too.
* **No shared mutable state** -- State is per-operation. For cross-operation caches, use the filesystem or an external store.
* **Error handling** -- If middleware throws, the framework catches it and returns a structured error. Upstream middleware does not run post-processing for that path.

Further Reading [#further-reading]

* [Architecture](./architecture) -- Where middleware sits in the stack
* [Plugin System](./plugin-system) -- How middleware plugins register
* [Use Middleware](../guides/using-middleware) -- Add built-in middleware to your client
* [Create Custom Middleware](../guides/custom-middleware) -- Implement middleware with `defineMiddleware`
* [API: Middleware](../api/middleware) -- `defineMiddleware` and wrap hook types
