# 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.
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` 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