Skip to content

Middleware ordering and routing#397

Open
kriszyp wants to merge 10 commits intomainfrom
named-ordering
Open

Middleware ordering and routing#397
kriszyp wants to merge 10 commits intomainfrom
named-ordering

Conversation

@kriszyp
Copy link
Copy Markdown
Member

@kriszyp kriszyp commented Apr 23, 2026

Named middleware ordering and per-route middleware chains

Problem

Harper's HTTP middleware stack had two coarse ordering mechanisms: registration order and the runFirst flag (a boolean override to jump to the front). This made it impossible for a component like REST to declaratively state "I depend on authentication" ? the ordering was implicit and config-order-sensitive.

There was also no way to scope middleware to specific URL paths or virtual hosts. All components registered globally and were responsible for filtering requests themselves.

Changes

before/after dependency-based ordering (Server.ts, middlewareChain.ts)
Components can now declare before: 'name' or after: 'name' in their server.http() options. A Kahn's algorithm topological sort uses the original registration index as a tiebreaker, so config file order is preserved when no explicit constraints exist between two entries. runFirst is deprecated but continues to work.

Automatic component naming (Scope.ts)
The scope.server proxy now injects the plugin's name (and its urlPath/host config) into every server.http/request/ws/upgrade call. Plugins get named for free ? no extra code needed.

REST declares its auth dependency (REST.ts)
REST now carries after: 'authentication', ensuring auth always runs before REST regardless of config order.

Static no longer forces itself to the front (static.ts)
runFirst: true is removed. Static now participates in normal ordering; its before: 'authentication' default (configurable) keeps the preferred static ? auth ? rest chain when they appear in that order in config.

Per-route middleware chains (middlewareChain.ts)
Components configured with urlPath or host get their own isolated middleware chain. The dispatcher routes requests to the best-matching chain (host+path > host > path; longer prefix wins ties). Unmatched requests fall to the default chain. Sub-route chains automatically pull in transitive after dependencies from any route ? so if REST on /api declares after: 'authentication', auth is included in the /api chain even though auth registered on the default route. Auth runs once per request (requests hit exactly one chain).

Pure utility module + 38 unit tests (middlewareChain.ts, unitTests/server/middlewareChain.test.js)
All chain-building logic lives in a dependency-free module, directly importable in tests without mocking. Tests cover topoSort (ordering, cycle detection, first/last name semantics), dependency resolution, route matching, and full makeCallbackChain integration scenarios.

Addresses #395 and #416

kriszyp and others added 4 commits April 20, 2026 19:03
Components can now declare `before` and `after` dependencies in their
http/ws/upgrade registration options to control relative middleware order
without relying on registration order alone. Each scope's server proxy
automatically injects the plugin name, so other components can reference
it. REST now declares `after: 'authentication'`; static declares
`before: 'authentication'` (configurable). `runFirst` is deprecated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
kriszyp and others added 4 commits April 29, 2026 05:53
Components can now declare urlPath and host in their config (or directly
in server.http options) to register middleware on an isolated chain that
only handles matching requests. Sub-route chains auto-pull in transitive
`after` dependencies from any route: if REST on /api declares
`after: 'authentication'`, auth is included in the /api chain even
though it was registered on the default route.

The Scope proxy now injects urlPath and host from scope.options alongside
the plugin name, so per-app routing requires no extra code in the plugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pure chain-building functions (topoSort, buildLinearChain, resolveDeps,
matchesRoute, buildRoutedChain, makeCallbackChain) are now in
server/middlewareChain.ts with no Harper-specific dependencies, making
them directly importable in tests without rewire or mocking.

38 tests cover: topoSort ordering and cycle detection, buildLinearChain
call order and short-circuit, resolveDeps transitive pulling, matchesRoute
host/path matching, and makeCallbackChain integration scenarios including
before/after constraints, sub-route dispatch, auto-pulled auth deps, and
specificity ordering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kriszyp kriszyp changed the title Named ordering Middleware ordering and routing Apr 29, 2026
@kriszyp kriszyp changed the base branch from pluginify-built-ins to main April 29, 2026 12:26
@kriszyp kriszyp changed the base branch from main to pluginify-built-ins April 29, 2026 12:35
@kriszyp kriszyp marked this pull request as ready for review April 29, 2026 12:36
@kriszyp kriszyp requested a review from a team as a code owner April 29, 2026 12:36
Copy link
Copy Markdown
Member

@Ethan-Arrowood Ethan-Arrowood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work!

do any other built-in plugins need ordering updates? I noticed you modified static and rest, but should something like authentication be set first? What about other server middlewares like the websocket stuff in rest, or the rest ones in graphql? Or is that all follow up?

Base automatically changed from pluginify-built-ins to main April 29, 2026 22:48
kriszyp and others added 2 commits May 5, 2026 22:33
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When an application is configured with a urlPath (e.g. /foo), requests routed
to its sub-chain now have the prefix stripped from pathname and url, so REST
resources and static files registered at their own root paths resolve correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 6, 2026

Reviewed; no blockers found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants