Uh oh!
There was an error while loading. Please reload this page.
docs: document middleware host and path routing - #595
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the documentation for Harper v5.2.0, introducing new middleware routing and ordering capabilities. It adds details and examples for configuring host and urlPath options across HTTP, WebSocket, and upgrade handlers, as well as explicit ordering options (name, before, after). The review feedback recommends clarifying that WsOptions and UpgradeOptions support all options from HttpOptions (including port configuration) rather than just routing and ordering options, to prevent ambiguity.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
🚀 Preview DeploymentYour preview deployment is ready! 🔗 Preview URL:https://preview.harper-documentation.harperfabric.com/pr-595 This preview will update automatically when you push new commits. |
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
🚀 Preview DeploymentYour preview deployment is ready! 🔗 Preview URL:https://preview.harper-documentation.harperfabric.com/pr-595 This preview will update automatically when you push new commits. |
Uh oh!
There was an error while loading. Please reload this page.
Ethan-Arrowood
left a comment
There was a problem hiding this comment.
LGTM — I traced the routing semantics against the merged middlewareChain.ts and they're accurate: host+path > host > path precedence with a longer-path tiebreak, both-must-match, prefix stripping, and the name default.
Two clarifications worth folding in so readers don't trip on edge cases:
urlPathmatching is segment-boundary-aware —/apimatches/apiand/api/xbut not/apinews— it's not a raw string prefix.- Host matching ignores the port.
Kris's host case-sensitivity thread is worth resolving too. Approving; these are non-blocking.
sent with Claude Opus 4.8
… matching Addresses cb1kenobi's review comment: matchesRoute() compares Host case-sensitively, so the doc should say so. Also folds in Ethan-Arrowood's non-blocking review clarifications: host matching ignores the port, and urlPath matching is segment-boundary-aware rather than a raw string prefix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
🚀 Preview DeploymentYour preview deployment is ready! 🔗 Preview URL:https://preview.harper-documentation.harperfabric.com/pr-595 This preview will update automatically when you push new commits. |
Uh oh!
There was an error while loading. Please reload this page.
🧹 Preview CleanupThe preview deployment for this PR has been removed. |
kriszyp
commented
Jul 27, 2026
I actually didn't want this merged yet, was making some edits. |
Follow-up to #595. That PR documented `host`/`urlPath` as a component-`config.yaml` setting, which described the mechanism but put the routing in the wrong place: where an application is served is a deployment concern, and a value checked into the application cannot be remapped per environment (the env-config overlay is root-config-only). Harper now treats the application's root-config entry as authoritative (HarperFast/harper PR pending), so lead with that placement: - `reference/http/overview.md` — mount an application from the root `harper-config.yaml`; a plugin's own `urlPath` positions it within the app and the mount is prefixed onto it, while a root-config `host` overrides one the app shipped. - `reference/components/plugin-api.md` — scope the plugin-level options to "within the application" and point at the root-config mount. - `reference/operations-api/operations.md` — document `host` on `deploy_component` and note that both it and `urlPath` are persisted to the root-config entry. - 5.2 release notes — lead with the application mount.
Where an application is served is a deployment concern, not an application concern, but `host`/`urlPath` were only readable from the config file that declared a plugin. For an application that is its own `config.yaml`, so the hostname and mount point had to be checked into the app — unoverridable from outside it (the env-config overlay is root-config-only). Worse, `host`/`urlPath` on a root-config *application* entry were silently inert. An application's plugin scopes read the application's own config.yaml and nothing carried the root entry's routing down to them, so `deploy_component urlPath=/api` (#1113) persisted a value that changed nothing. The flow described in #1113 only ever held for a root-declared *plugin*, whose scope does read the root config. The root config is now authoritative for where an application is served: my-app: host: api.example.com urlPath: /v1 - `scopeMount.ts` — pure mount model. `host` is replaced outright (an operator remapping a hostname must win over a value the app shipped). `urlPath` is composed rather than replaced, because a plugin's `urlPath` doubles as its app-internal base path (static's asset root, fastify's route prefix); replacing it would silently relocate app-internal URLs and collapse distinct plugins onto one path. Mount `/v1` + `static: { urlPath: assets }` → `/v1/assets/`. The composed value is a fixed point of `resolveBaseURLPath`, so downstream consumers keep resolving it without compounding the prefix. - Overlaid in `OptionsWatcher`, not at each call site, so `scope.options.getAll()` is the one effective view of a plugin's config. static's redirects and external paths, the EntryHandler's entry URLs, and fastify's route prefix are all correct with no changes of their own. Composed from the freshly-parsed file on every read, so live reload cannot compound the prefix. - Applied on both load paths: the root-config `package` recursion and the components-root directory scan. The scan is the path that matters most — it loads apps with no root entry at all, so a mount works for a payload-deployed app, not just an installed one. - `deploy_component` accepts and persists `host` alongside `urlPath`, rejecting a host that carries a port or path (it would never match the router's host compare). Also fixes the Scope `server` proxy passing a raw config `urlPath` straight to the router: a plugin that spreads its whole config section into these options (REST does) handed the router the literal './', which normalized to the unmatchable route '/.'. The proxy now resolves whichever source supplied the value. Documented in HarperFast/documentation#595.
Where an application is served is a deployment concern, not an application concern, but `host`/`urlPath` were only readable from the config file that declared a plugin. For an application that is its own `config.yaml`, so the hostname and mount point had to be checked into the app — unoverridable from outside it (the env-config overlay is root-config-only). Worse, `host`/`urlPath` on a root-config *application* entry were silently inert. An application's plugin scopes read the application's own config.yaml and nothing carried the root entry's routing down to them, so `deploy_component urlPath=/api` (#1113) persisted a value that changed nothing. The flow described in #1113 only ever held for a root-declared *plugin*, whose scope does read the root config. The root config is now authoritative for where an application is served: my-app: host: api.example.com urlPath: /v1 - `scopeMount.ts` — pure mount model. `host` is replaced outright (an operator remapping a hostname must win over a value the app shipped). `urlPath` is composed rather than replaced, because a plugin's `urlPath` doubles as its app-internal base path (static's asset root, fastify's route prefix); replacing it would silently relocate app-internal URLs and collapse distinct plugins onto one path. Mount `/v1` + `static: { urlPath: assets }` → `/v1/assets/`. The composed value is a fixed point of `resolveBaseURLPath`, so downstream consumers keep resolving it without compounding the prefix. - Overlaid in `OptionsWatcher`, not at each call site, so `scope.options.getAll()` is the one effective view of a plugin's config. static's redirects and external paths, the EntryHandler's entry URLs, and fastify's route prefix are all correct with no changes of their own. Composed from the freshly-parsed file on every read, so live reload cannot compound the prefix. - Applied on both load paths: the root-config `package` recursion and the components-root directory scan. The scan is the path that matters most — it loads apps with no root entry at all, so a mount works for a payload-deployed app, not just an installed one. - `deploy_component` accepts and persists `host` alongside `urlPath`, rejecting a host that carries a port or path (it would never match the router's host compare). Also fixes the Scope `server` proxy passing a raw config `urlPath` straight to the router: a plugin that spreads its whole config section into these options (REST does) handed the router the literal './', which normalized to the unmatchable route '/.'. The proxy now resolves whichever source supplied the value. Documented in HarperFast/documentation#595.
Follow-up to #595. That PR documented `host`/`urlPath` as a component-`config.yaml` setting, which described the mechanism but put the routing in the wrong place: where an application is served is a deployment concern, and a value checked into the application cannot be remapped per environment (the env-config overlay is root-config-only). Harper now treats the application's root-config entry as authoritative (HarperFast/harper PR pending), so lead with that placement: - `reference/http/overview.md` — mount an application from the root `harper-config.yaml`; a plugin's own `urlPath` positions it within the app and the mount is prefixed onto it, while a root-config `host` overrides one the app shipped. - `reference/components/plugin-api.md` — scope the plugin-level options to "within the application" and point at the root-config mount. - `reference/operations-api/operations.md` — document `host` on `deploy_component` and note that both it and `urlPath` are persisted to the root-config entry. - 5.2 release notes — lead with the application mount.
Summary
host,urlPath, and middleware ordering fields to the HTTP API referenceDocuments Middleware ordering and routing (HarperFast/harper#397).
Verification
npm run format:writenpm run format:checknpm run typechecknpm run build(passes with one pre-existing broken-anchor warning in the 5.1 release notes)