Skip to content

docs: document middleware host and path routing - #595

Merged
Ethan-Arrowood merged 3 commits into
mainfrom
kris/document-host-routing-reference
Jul 27, 2026
Merged

docs: document middleware host and path routing#595
Ethan-Arrowood merged 3 commits into
mainfrom
kris/document-host-routing-reference

Conversation

@kriszyp

Copy link
Copy Markdown
Member

Summary

  • document built-in virtual-host and URL-prefix middleware routing
  • add host, urlPath, and middleware ordering fields to the HTTP API reference
  • clarify that scoped plugin configuration automatically applies routing to HTTP, WebSocket, and upgrade handlers
  • add the feature to the 5.2 release notes

Documents Middleware ordering and routing (HarperFast/harper#397).

Verification

  • npm run format:write
  • npm run format:check
  • npm run typecheck
  • npm run build (passes with one pre-existing broken-anchor warning in the 5.1 release notes)

@gemini-code-assistgemini-code-assistBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment threadreference/http/api.md Outdated
Comment threadreference/http/api.md Outdated
@github-actions
github-actionsBot temporarily deployed to pr-595 July 17, 2026 20:26 Inactive
@github-actions

Copy link
Copy Markdown

🚀 Preview Deployment

Your 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>
@github-actions
github-actionsBot temporarily deployed to pr-595 July 21, 2026 02:12 Inactive
@github-actions

Copy link
Copy Markdown

🚀 Preview Deployment

Your preview deployment is ready!

🔗 Preview URL:https://preview.harper-documentation.harperfabric.com/pr-595

This preview will update automatically when you push new commits.

Comment threadreference/http/api.md Outdated

@Ethan-ArrowoodEthan-Arrowood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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:

  • urlPath matching is segment-boundary-aware — /api matches /api and /api/x but 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>
@github-actions
github-actionsBot temporarily deployed to pr-595 July 27, 2026 13:53 Inactive
@github-actions

Copy link
Copy Markdown

🚀 Preview Deployment

Your preview deployment is ready!

🔗 Preview URL:https://preview.harper-documentation.harperfabric.com/pr-595

This preview will update automatically when you push new commits.

@Ethan-Arrowood
Ethan-Arrowood merged commit 03000d3 into mainJul 27, 2026
7 checks passed
@Ethan-Arrowood
Ethan-Arrowood deleted the kris/document-host-routing-reference branch July 27, 2026 14:39
@github-actions

Copy link
Copy Markdown

🧹 Preview Cleanup

The preview deployment for this PR has been removed.

@kriszyp

Copy link
Copy Markdown
MemberAuthor

I actually didn't want this merged yet, was making some edits.

kriszyp added a commit that referenced this pull request Jul 29, 2026
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.
kriszyp added a commit to HarperFast/harper that referenced this pull request Jul 31, 2026
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.
kriszyp added a commit to HarperFast/harper that referenced this pull request Jul 31, 2026
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.
kriszyp added a commit that referenced this pull request Aug 2, 2026
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.
Sign up for freeto 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.

3 participants

@kriszyp@cb1kenobi@Ethan-Arrowood