feat(effect): proposal — HttpApiMiddleware can declare query fields#2154
Draft
kitlangton wants to merge 1 commit into
Draft
feat(effect): proposal — HttpApiMiddleware can declare query fields#2154kitlangton wants to merge 1 commit into
kitlangton wants to merge 1 commit into
Conversation
Lets HttpApiMiddleware declare query parameters it reads off the request URL. The framework merges those field schemas into the runtime query schema of every endpoint that applies the middleware, so requests carrying those params no longer 400 just because the endpoint did not list them. Mirrors the existing `error` pattern: lazy walk over `endpoint.middlewares` in `HttpApiEndpoint.getQuerySchema`, no eager mutation at `.middleware()` time. Conflicts (same field name with non-equivalent schemas) throw at HttpApi materialisation time. This PR is intentionally narrow as a design proposal — open questions for type-level threading, `headers` symmetry, OpenAPI emission, and non-Schema.Struct.Fields endpoint queries are listed in the PR description.
🦋 Changeset detectedLatest commit: 6d318ff The changes in this PR will be included in the next version bump. This PR includes changesets to release 27 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Status
Draft proposal — looking for design feedback before fleshing out. I have a working runtime implementation plus a passing end-to-end test, but several follow-on design calls (type-level threading,
headerssymmetry, OpenAPI emission, non-Fields endpoint queries) are deliberately deferred so this PR can be a focused conversation starter rather than a fait accompli. Happy to rework or split per maintainer preference.Problem
HttpApiMiddlewarecannot declare query parameters that propagate to endpoints. Today middleware can declareprovides,requires,error,security, andrequiredForClient— those cover what middleware adds to the Effect signature (context, errors, auth) but not the request shape the middleware reads. If middleware reads?directory=…off the URL, that field is invisible to HttpApi's schema validation, so:request.url.query: Schema.Struct({…}).Concrete repro: opencode's
WorkspaceRoutingMiddlewarereads?directoryand?workspaceon every endpoint in the groups it's applied to. The fix today is to spread aWorkspaceRoutingQueryFieldsconstant into all 9 affected endpoint query schemas — and remember to keep doing it forever (anomalyco/opencode#26581).Proposed API
queryis constrained toSchema.Struct.Fields(the dictionary form), not arbitrarySchema.Top, so the merge is mechanically field-wise.Implementation
Mirrors the existing
errorpattern — lazy merge at builder time, no eager mutation at.middleware()call time:HttpApiMiddleware.Serviceaccepts aqueryoption, stored on the static class asself.query(parallel toself.error,self.security).HttpApiEndpoint.makepreserves the raw query fields when the user passesSchema.Struct.Fields(so the merge can rebuild from them later).HttpApiEndpoint.getQuerySchema(endpoint)(new) is the parallel ofgetErrorSchemas(endpoint)— walksendpoint.middlewares, merges field schemas with the endpoint's own fields, and returns the combined transformed schema. Throws on conflicting field names with non-equivalent schemas.HttpApiBuilder.handlerToHttpEffectcallsgetQuerySchema(endpoint)instead of readingendpoint.querydirectly.Total runtime change: ~80 lines net, all in three files.
What's working
?directory=fooagainst an endpoint that didn't declaredirectory) — accepted, decoded as part ofctx.query.?directory=foo&limit=10wherelimitis endpoint-declared anddirectoryis middleware-declared) — both decode correctly.packages/platform-node/test/HttpApi.test.tscovers all three.HttpApi.test.tstests pass with the change. The 1 failure is a pre-existingtoMatchSnapshotissue onmainunrelated to this work.Deferred — explicit open questions
These are intentionally unresolved here so the maintainers can shape them:
ctx.query, but the endpoint'sQuerytype parameter doesn't include them, so they show up at runtime but not in TS. Should.middleware(M)widen the endpoint's query type to a union of M's fields ∪ endpoint's fields?erroralready accumulates viaMiddleware | IplusErrors<Endpoint>— same shape would work for query, but it's invasive.headerssymmetry — the same plumbing trivially extends toheaders. Worth landing in this PR, or as a separate one once the query shape is blessed?OpenApi.fromApiwalksendpoint.middlewaresfor security but not for query. Adding a parallel walk inprocessParametersis straightforward; deferred only because it's a separate code path with its own concerns. Should be in this PR before merge?Schema.Struct.Fieldsendpoint queries — when an endpoint passes a fullSchema.Top(not the Fields dict form) and applies middleware with declared query fields,getQuerySchemacurrently throws because it can't extract raw fields to merge. Three options: keep the throw with a clear error (current), unwrap the AST to extract fields (fragile), or also store the Schema.Top alongside and intersect at the Schema layer. I went with the throw since it's cheap and pushes the user to the cleaner Fields shape.// not supported: bodycomment somewhere visible if useful.Test plan
pnpm check:tsgocleanpnpm lint-fixcleanpnpm docgen(effect package) cleanpackages/platform-node/test/HttpApi.test.tspassesHttpApi.test.tstests still pass (1 pre-existing snapshot failure onmainunrelated)minor)Why a draft PR rather than an issue
Honestly — happy to convert to an issue if that's the maintainers' preferred shape for this kind of design call. The reason I sent it as a PR is that the API surface and merge mechanism are concrete enough that running code is easier to react to than a prose proposal. If you'd rather discuss before any code lands, say the word and I'll close this and open an issue with the same content.