Skip to content

feat(server): expose not-yet-validated input fields to middleware between input schemas - #1960

Merged
dinwwwh merged 2 commits into
middleapi:mainfrom
dinwwwh:claude/cache-middleware-multiple-schemas-2308ab
Aug 27, 2026
Merged

feat(server): expose not-yet-validated input fields to middleware between input schemas#1960
dinwwwh merged 2 commits into
middleapi:mainfrom
dinwwwh:claude/cache-middleware-multiple-schemas-2308ab

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Aug 27, 2026

Copy link
Copy Markdown
Member

A middleware placed between multiple .input() schemas previously received only the fields validated by the schemas before it, so whole-input middlewares like caching built keys from a partial input and returned wrong cached results for distinct requests. Such middlewares now receive the validated fields merged over the raw rest of the input, so they see the complete input anywhere in the chain.

Fixes

  • A cache middleware between input schemas no longer collides distinct inputs into one cache key; each request keys on the full input.
  • Validated fields always win over raw ones in the merged view, and the merge applies only between schemas: middlewares before the first .input() still get the raw input untouched (same reference, no clone), and middlewares after the last one plus the handler still see only fully validated input, with fields no schema defines dropped.
  • The raw view is presentation-only: the chain continues from the validated value, so remaining schemas still validate those fields before the handler runs.

Docs

  • docs/procedure (Multiple Schemas) and docs/middleware (Middleware Input) document the new behavior, with a danger note: only trust fields validated before the middleware and treat the rest as untrusted client input.

Testing

  • New test covers all three positions (before/between/after schemas), including raw pre-coercion values, unknown fields at top level and nested, and pass-through identity for leading middlewares; full suite (3271 tests), type check, and lint pass.

…ween input schemas

A middleware placed between multiple input schemas now receives the fields
validated so far merged over the raw rest of the input, so whole-input
middlewares like caching key on the complete input instead of a partial
one. Only the middleware sees this view: the chain continues from the
validated value, so remaining schemas still validate those fields and the
handler never receives a field no schema validated.
@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@1960

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@1960

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@1960

@orpc/client

npm i https://pkg.pr.new/@orpc/client@1960

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@1960

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@1960

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@1960

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@1960

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@1960

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1960

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@1960

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1960

@orpc/next

npm i https://pkg.pr.new/@orpc/next@1960

@orpc/node

npm i https://pkg.pr.new/@orpc/node@1960

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@1960

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@1960

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@1960

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@1960

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@1960

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@1960

@orpc/server

npm i https://pkg.pr.new/@orpc/server@1960

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@1960

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@1960

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1960

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1960

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@1960

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@1960

commit: ac7023f

Copilot AI 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.

Pull request overview

Updates server middleware input handling so middleware between schemas sees validated fields merged over remaining raw input.

Changes:

  • Merges partial validation results with raw input for intermediate middleware.
  • Adds coverage for middleware positions, coercion, unknown fields, and identity.
  • Documents validation and trust boundaries.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
packages/server/src/procedure-client.ts Builds the intermediate middleware input view.
packages/server/src/procedure-client.test.ts Tests input behavior throughout the schema chain.
apps/content/docs/procedure.mdx Explains multiple-schema middleware behavior.
apps/content/docs/middleware.mdx Warns that unvalidated fields remain untrusted.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 30 untouched benchmarks


Comparing dinwwwh:claude/cache-middleware-multiple-schemas-2308ab (ac7023f) with main (6cd7143)

Open in CodSpeed

@pullfrog pullfrog Bot 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.

ℹ️ No critical issues — one rough edge and a small doc thought worth a look.

Reviewed changes

  • packages/server/src/procedure-client.ts — introduced a middlewareInput alongside currentInput in executeProcedureInternal's next(). For a middleware seated between input schemas (endInputIndex > 0 && < inputSchemas.length) it passes mergeTwoLevels(options.input, currentInput) (validated fields win over raw via the merge's "second wins" semantics), so whole-input middlewares like caching now see the complete input at any point in the chain. Leading middlewares keep the raw input by reference (no clone); middlewares after the last schema and the handler keep the fully validated input.
  • packages/server/src/procedure-client.test.ts — new test covers all three positions (before/between/after), proving raw pre-coercion values (page: '2'), nested/unknown raw rest, validated-wins (id__ID from the transform vs raw ID), and leading identity; plus the updated existing test at line 626 now asserts parentMid sees the raw rest.
  • apps/content/docs/procedure.mdx / apps/content/docs/middleware.mdx — updated the multiple-schemas description and added a :::danger note that only fields validated before the middleware are trustworthy.

The guard for the three positions is correct, and the test's between-middleware assertion is the discriminating one: it would fail if the feature were absent (the three remaining schemas' raw rest would be stripped) or if the merge direction were reversed.

ℹ️ Nitpicks

  • The merged "rest" is not a defensive copy: mergeTwoLevels deep-merges only one level, so a raw-only nested sub-object survives into the merged view by reference to the original options.input. A between-schemas middleware that mutates such a value writes through to the raw input that later schemas validate (procedure-client.ts:233). Fine for the read-only caching use case, just worth noting it's a shared view, not a prison.
  • The before/after position assertions in the new test don't by themselves discriminate this feature (they'd pass unchanged on the old code); all the discriminating weight sits on the betweenMid assertion — which does hold up. No change needed, just noting the coverage is effectively single-assertion.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

}

middlewareInput = endInputIndex > 0 && endInputIndex < inputSchemas.length
? mergeTwoLevels(options.input, currentInput)

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.

Rough edge on the merged view's aliasing: mergeTwoLevels merges only one level, so a raw-only nested/primitive object (a field under params-level that the validated view doesn't redefine) survives into this merged value by reference to options.input. A between-schemas middleware that patches its received input would silently drop mutations to already-validated fields but write through raw-nested mutations into options.input, which later schemas validate directly (line 235). Since the intended use (caching) is read-only it's harmless, but a one-line addition to the middleware.mdx danger note — "the raw rest is shared with the original input, not a defensive copy" — would keep readers out of that foot-gun. The alternative (deep-cloning the merged view) would defeat the same-reference guarantee for the leading-middleware position, so I'd prefer just documenting it.

@dinwwwh
dinwwwh merged commit d63ea85 into middleapi:main Aug 27, 2026
9 checks passed
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