TML-2376: Mongo middleware can rewrite params before encode#652
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR splits Mongo lowering into structural and resolution phases, adds middleware-visible param-ref mutation over lowered drafts, and updates runtime execution, adapter wiring, docs, and tests to use the new flow. ChangesTwo-Phase Lowering Architecture
Sequence Diagram(s)sequenceDiagram
participant MongoRuntime as MongoRuntime.execute()
participant MongoAdapter as MongoAdapter
participant MongoParamRefMutator as MongoParamRefMutator
participant BeforeExecuteChain as beforeExecute middleware
participant MongoDriver as MongoDriver
MongoRuntime->>MongoAdapter: structuralLower(plan)
MongoAdapter-->>MongoRuntime: MongoLoweredDraft
MongoRuntime->>MongoParamRefMutator: createMongoParamRefMutator(draft)
MongoRuntime->>BeforeExecuteChain: runBeforeExecuteChain(exec, params)
BeforeExecuteChain->>MongoParamRefMutator: entries()
BeforeExecuteChain->>MongoParamRefMutator: replaceValue(...)
BeforeExecuteChain-->>MongoRuntime: middleware complete
MongoRuntime->>MongoAdapter: resolveParams(draft, ctx)
MongoAdapter-->>MongoRuntime: AnyMongoWireCommand
MongoRuntime->>MongoDriver: execute(command)
MongoDriver-->>MongoRuntime: result
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
size-limit report 📦
|
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/emitter
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts (1)
101-105: ⚡ Quick winAdd
checkAbortedguard at entry for consistency.
resolveValue(line 49) andresolveDraftDoc(line 137) both check abort status at entry, butresolveDraftSlotdoes not. While all paths eventually reach an abort check, addingcheckAborted(ctx, 'encode')here would maintain consistency and provide fail-fast behavior.🛡️ Proposed addition
async function resolveDraftSlot( value: unknown, codecs: MongoCodecRegistry, ctx: CodecCallContext, ): Promise<unknown> { + checkAborted(ctx, 'encode'); if (value instanceof MongoParamRef) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts` around lines 101 - 105, resolveDraftSlot is missing an immediate abort check for consistency with resolveValue and resolveDraftDoc; add a call to checkAborted(ctx, 'encode') at the start of the resolveDraftSlot function so it fails fast if the context is aborted. Locate the async function resolveDraftSlot(value, codecs, ctx) and insert the checkAborted(ctx, 'encode') guard as the first statement to mirror the behavior in resolveValue and resolveDraftDoc.packages/3-mongo-target/2-mongo-adapter/src/lowering.ts (1)
469-666: ⚖️ Poor tradeoffConsider extracting shared stage-lowering logic.
The
structuralLowerStagefunction duplicates ~200 lines fromlowerStage(lines 235-450), differing only in whether it calls synchronousstructuralLowerFilter/structuralLowerPipelinevs async variants. A shared helper parameterized by filter/pipeline lowering strategies could eliminate this duplication and improve maintainability.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/3-mongo-target/2-mongo-adapter/src/lowering.ts` around lines 469 - 666, structuralLowerStage duplicates most logic from lowerStage; extract a shared helper (e.g., buildLowerStage) that accepts strategy functions for lowering components (filter, pipeline, exprRecord, agg/window helpers as needed) and reuse it from both structuralLowerStage and lowerStage by passing structuralLowerFilter/structuralLowerPipeline (sync) or their async variants; update structuralLowerStage to call the helper to remove the ~200-line duplication while preserving current behavior and signatures of structuralLowerStage and lowerStage.packages/2-mongo-family/7-runtime/src/param-ref-mutator.ts (1)
170-173: ⚡ Quick winPrefer
ifDefinedfor conditional object spreads.Replace the imperative mutation pattern with the repo's idiomatic spread helper for cleaner, more declarative code.
♻️ Proposed refactor using `ifDefined`
+import { ifDefined } from '`@prisma-next/utils/defined`'; + function substituteSlot(value: unknown, overrides: ReadonlyMap<MongoParamRef, unknown>): unknown { if (value instanceof MongoParamRef) { if (overrides.has(value)) { - const opts: { name?: string; codecId?: string } = {}; - if (value.name !== undefined) opts.name = value.name; - if (value.codecId !== undefined) opts.codecId = value.codecId; - return new MongoParamRef(overrides.get(value), opts); + return new MongoParamRef(overrides.get(value), { + ...ifDefined('name', value.name), + ...ifDefined('codecId', value.codecId), + }); }Based on learnings: prefer
ifDefinedfromprisma-next/utils/definedfor conditional object spreads instead of inline ternary-based spreads or imperative mutation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/2-mongo-family/7-runtime/src/param-ref-mutator.ts` around lines 170 - 173, The code mutates an opts object to conditionally include name and codecId when constructing the MongoParamRef; replace that imperative pattern with the repo helper ifDefined from prisma-next/utils/defined to build a single immutable opts via spreads (e.g. const opts = { ...ifDefined(value.name, { name: value.name }), ...ifDefined(value.codecId, { codecId: value.codecId }) }) and then return new MongoParamRef(overrides.get(value), opts); also add the required import for ifDefined and remove the mutable opts mutation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/2-mongo-family/7-runtime/src/param-ref-mutator.ts`:
- Line 366: Replace the bare double-cast usages with the typed helper blindCast
from '`@prisma-next/utils/casts`': locate the statement assigning handle (the
expression "ref as unknown as MongoParamRefHandle<string | undefined>") and the
other similar casts around the same block (also at the sites referenced by the
reviewer), and change them to use blindCast<MongoParamRefHandle<string |
undefined>, "Explain reason here">(...ref...) so the cast is explicit and
documented; import blindCast from '`@prisma-next/utils/casts`' if not already
imported and add a short reason string for the cast.
- Line 389: Replace the bare TypeScript "as" cast used for replaceValue with the
blindCast utility to satisfy the no-bare-casts rule: import blindCast (or
castAs) from "`@prisma-next/utils/casts`" if not already present, then change the
assignment that uses replaceValue as
MongoParamRefMutator<TCodecMap>['replaceValue'] to
blindCast<MongoParamRefMutator<TCodecMap>['replaceValue'], "ReplaceValue
cast">(replaceValue) so the cast is explicit and annotated; locate the
replacement in the object where replaceValue is assigned (symbol: replaceValue
and type MongoParamRefMutator<TCodecMap>['replaceValue']) and update
accordingly.
---
Nitpick comments:
In `@packages/2-mongo-family/7-runtime/src/param-ref-mutator.ts`:
- Around line 170-173: The code mutates an opts object to conditionally include
name and codecId when constructing the MongoParamRef; replace that imperative
pattern with the repo helper ifDefined from prisma-next/utils/defined to build a
single immutable opts via spreads (e.g. const opts = { ...ifDefined(value.name,
{ name: value.name }), ...ifDefined(value.codecId, { codecId: value.codecId })
}) and then return new MongoParamRef(overrides.get(value), opts); also add the
required import for ifDefined and remove the mutable opts mutation.
In `@packages/3-mongo-target/2-mongo-adapter/src/lowering.ts`:
- Around line 469-666: structuralLowerStage duplicates most logic from
lowerStage; extract a shared helper (e.g., buildLowerStage) that accepts
strategy functions for lowering components (filter, pipeline, exprRecord,
agg/window helpers as needed) and reuse it from both structuralLowerStage and
lowerStage by passing structuralLowerFilter/structuralLowerPipeline (sync) or
their async variants; update structuralLowerStage to call the helper to remove
the ~200-line duplication while preserving current behavior and signatures of
structuralLowerStage and lowerStage.
In `@packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts`:
- Around line 101-105: resolveDraftSlot is missing an immediate abort check for
consistency with resolveValue and resolveDraftDoc; add a call to
checkAborted(ctx, 'encode') at the start of the resolveDraftSlot function so it
fails fast if the context is aborted. Locate the async function
resolveDraftSlot(value, codecs, ctx) and insert the checkAborted(ctx, 'encode')
guard as the first statement to mirror the behavior in resolveValue and
resolveDraftDoc.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: b6ff1927-584b-47f7-aa37-0f009d539d03
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (16)
packages/2-mongo-family/6-transport/mongo-lowering/src/adapter-types.tspackages/2-mongo-family/6-transport/mongo-lowering/src/exports/index.tspackages/2-mongo-family/7-runtime/package.jsonpackages/2-mongo-family/7-runtime/src/exports/index.tspackages/2-mongo-family/7-runtime/src/mongo-middleware.tspackages/2-mongo-family/7-runtime/src/mongo-runtime.tspackages/2-mongo-family/7-runtime/src/param-ref-mutator.tspackages/2-mongo-family/7-runtime/test/execute-param-mutator-wiring.test.tspackages/2-mongo-family/7-runtime/test/mongo-middleware.test.tspackages/2-mongo-family/7-runtime/test/mongo-runtime-abort.test.tspackages/2-mongo-family/7-runtime/test/param-ref-mutator.test.tspackages/3-mongo-target/2-mongo-adapter/src/exports/runtime.tspackages/3-mongo-target/2-mongo-adapter/src/lowering.tspackages/3-mongo-target/2-mongo-adapter/src/mongo-adapter.tspackages/3-mongo-target/2-mongo-adapter/src/resolve-value.tspackages/3-mongo-target/2-mongo-adapter/test/structural-lower.test.ts
|
Addressed the local review findings in two commits on this branch (
Gates: Deliberately out of scope (follow-up TML-2752): the fully type-honest fix for F01 — a distinct pre-resolve plan type for |
…ms phases (TML-2376) Introduce a two-phase lowering design in the Mongo adapter, mirroring the SQL family's lowerToDraft / encodeDraftParams split: - structuralLower(plan): synchronous; performs all structural transforms (collection, filter shape, pipeline stages, update shape) but leaves MongoParamRef nodes in place as unresolved leaves in the draft tree. - resolveParams(draft, ctx): async; walks the draft, resolves every MongoParamRef leaf via the codec registry, and returns a frozen AnyMongoWireCommand. - lower() is re-expressed as resolveParams(structuralLower(plan), ctx), preserving all existing observable behaviour. Supporting changes: - Add MongoLoweredDraft discriminated union to mongo-lowering/adapter-types.ts (and export it) so downstream phases can type the intermediate draft without depending on wire-command classes. - Add structuralLowerFilter / structuralLowerStage / structuralLowerPipeline synchronous helpers in lowering.ts; existing lowerFilter / lowerStage / lowerPipeline now compose these with resolveDraftDoc. - Add resolveDraftDoc / resolveDraftSlot to resolve-value.ts for recursive resolution of Record<string,unknown> draft structures. - Expose structuralLower and resolveParams on MongoRuntimeAdapterInstance (exports/runtime.ts) so later dispatches can wire them into the runtime without modifying this package. - New test file (test/structural-lower.test.ts) asserts MongoParamRef nodes survive in the draft for insertOne documents, field filters, $and filters, aggregate pipeline stages ($match, $addFields), updateOne, insertMany, rawInsertOne, rawAggregate, findOneAndUpdate, findOneAndDelete, and updateMany; also verifies lower() composition is unchanged. Signed-off-by: Will Madden <madden@prisma.io>
… over MongoLoweredDraft (TML-2376) Introduce the mutator surface that middleware authors will use to rewrite MongoParamRef values before encode runs, mirroring the SQL family's SqlParamRefMutator over SqlExecutionPlan.params. New module packages/2-mongo-family/7-runtime/src/param-ref-mutator.ts: - MongoParamRefHandle<TCodecId> — phantom-branded opaque token produced by entries(); callers pattern-match on entry.codecId to narrow to a typed replaceValue overload. - MongoParamRefEntry / MongoParamRefEntryUnion — entry shape exposed to middleware, carrying ref, current effective value, and codecId. - flattenMongoParamRefs(draft) — flat Generator walk over a MongoLoweredDraft yielding every MongoParamRef leaf (object values, array elements, filter predicates, update operators, pipeline stages). Raw-command variants yield zero entries. - MongoParamRefMutator (public) — extends ParamRefMutator; entries(), replaceValue (typed overloads), replaceValues. - MongoParamRefMutatorInternal — adds currentDraft() for the runtime. - createMongoParamRefMutator(draft) — factory; overrides lazily allocated; currentDraft() returns original draft by reference when nothing was replaced (reference-identity fast path), otherwise rebuilds the tree with new MongoParamRef nodes carrying the replacement values. - buildMutatedDraft / substituteSlot / substituteDoc / substituteUpdate — tree-walking reconstruction helpers, internal only. Supporting changes: - mongo-middleware.ts: type beforeExecute third arg as MongoParamRefMutator (was base ParamRefMutator marker); existing (plan) / (plan, ctx) shapes remain compatible — TypeScript permits fewer-param functions. - runtime package.json: promote @prisma-next/mongo-value from devDependency to dependency (needed for instanceof MongoParamRef in source); update lockfile accordingly. - exports/index.ts: expose mutator types and factories. - test/mongo-middleware.test.ts, test/mongo-runtime-abort.test.ts: add vi.fn() stubs for structuralLower and resolveParams on the mock MongoRuntimeAdapterInstance (required after D1 extended MongoAdapter). - test/param-ref-mutator.test.ts: 21 tests covering the flatten walk (object/array/filter/pipeline), replaceValues write-through, typed replaceValue overload (codecId-matched), preserves codecId/name on rebuilt refs, nested object and array element replacement, filter predicate and pipeline stage replacement, reference-identity fast path, and raw-command zero-entries no-op. Signed-off-by: Will Madden <madden@prisma.io>
Defer value resolution past beforeExecute: structuralLower, mutator + runBeforeExecuteChain, then resolveParams before the driver. Content hash runs on the resolved wire command. Tests cover bulk middleware, fast path, and hash discrimination after mutation. Signed-off-by: Will Madden <madden@prisma.io>
Replace new bare `as` casts with blindCast or narrowing helpers so lint:casts passes, and extend the mongo-runner stub with structuralLower and resolveParams after the adapter interface split. Signed-off-by: Will Madden <madden@prisma.io>
Use type predicates for draft update unions, replace bare casts with blindCast in PR-touched paths, and extend param-ref-mutator tests to restore 100% coverage on mongo-runtime. Signed-off-by: Will Madden <madden@prisma.io>
Add structuralLower + resolveParams to the cross-family middleware mock adapter so its create() instance satisfies MongoRuntimeAdapterInstance after the adapter interface split. Signed-off-by: Will Madden <madden@prisma.io>
…eware contract (TML-2376) Guard computeMongoContentHash when plan.command is still a structural draft, document the beforeExecute vs post-resolve plan lifecycle, and pin flatten/resolve walk parity with tests. Signed-off-by: Will Madden <madden@prisma.io>
…parity (TML-2376) Document intentional Mongo vs SQL placement for the two-phase lowering split and param mutator, and that Mongo execute now mirrors ADR 215's pre-resolve middleware lifecycle. Signed-off-by: Will Madden <madden@prisma.io>
784d55b to
b00339c
Compare
…(TML-2376) Signed-off-by: Will Madden <madden@prisma.io>
Linked issue
Refs TML-2376. Restores Mongo parity with the SQL-family param-mutator seam shipped under TML-2373 (deferred out of that project's Postgres-only scope). One follow-up is intentionally left out of scope: TML-2752 (see Reviewer notes).
At a glance
A query parameter in Mongo is a
MongoParamRef— a placeholder carrying the raw value plus an optionalcodecIdtag identifying how it should be encoded. This PR lets a middleware see those refs inbeforeExecuteand rewrite their values before they're encoded and sent to the driver:Before this change the third
paramsargument was inert: Mongo'slower()resolved everyMongoParamRefto its raw value during lowering, so by the timebeforeExecuteran the command carried only resolved primitives — there was nothing left to rewrite.Decision
Make Mongo middleware able to rewrite parameter values before encode, by deferring value resolution until after the
beforeExecutechain — the same lifecycle the SQL family already follows (ADR 215). Concretely this PR ships:lower()becomesstructuralLower(shape only, refs preserved) +resolveParams(resolve refs → frozen wire command).MongoParamRefMutator, wired through the runtime — constructed over the unresolved draft, handed tobeforeExecute, and consulted when the draft is resolved.beforeExecute, content-hashing it then is a misuse; the hash helper now throws a clear error instead of failing cryptically.How it fits together
The work builds up in four steps; the execute pipeline ends up as
structuralLower → mutator → beforeExecute → resolveParams → driver.mongo-adapter,mongo-lowering).structuralLowerdoes every structural transform — collection, filter shape, pipeline stages, update shape — but leavesMongoParamRefnodes in place. The deferredresolveParamspass produces the frozenAnyMongoWireCommand. Resolution previously lived in two places (#resolveDocumentandlowerFilter, the latter reached from many command paths plus$match/$geoNear/$lookup/$merge); all of it moved into the resolve phase together, so nothing resolves early.7-runtime).flattenMongoParamRefsyields everyMongoParamRefleaf regardless of nesting (object / array / filter / pipeline);createMongoParamRefMutatorexposes the SQL-shapedentries()/replaceValue/replaceValuessurface. It keeps a reference-identity fast path: when nothing is replaced,currentDraft()returns the original draft by reference, so the common no-mutation path allocates no second tree.mongo-runtime.ts) to construct the mutator, run the chain, then resolvecurrentDraft(). The content-hash call site moves afterresolveParams, so the digest reflects any mutation and never feeds aMongoParamRefinstance tocanonicalStringify(which rejects class instances).commandslot holds the unresolved draft — typed as the resolved wire command (a known trade-off; see Reviewer notes). The supported surface for middleware is theparamsmutator, not structural reads ofplan.command. To stop the one dangerous misuse silently,computeMongoContentHashnow throwsRUNTIME.CONTENT_HASH_REQUIRES_RESOLVED_COMMANDif handed a pre-resolve draft, and the JSDoc onbeforeExecute/MongoMiddlewareContext.contentHash/MongoExecutionPlan.commanddocuments the lifecycle honestly.Reviewer notes
plan.commandis a deliberate transitional state, not an oversight. DuringbeforeExecutethecommandslot carries the unresolvedMongoLoweredDraftwhile typed asAnyMongoWireCommand(via a narrowedblindCastinmongo-runtime.ts). Middleware is expected to use theparamsmutator, never structural reads ofplan.command. The footgun is closed two ways: the content-hash guard fails loud, and the contract is now documented on the relevant types. The fully type-honest fix — a distinct pre-resolve plan type forbeforeExecute— needs a change to the cross-family framework middleware SPI (RuntimeMiddlewareuses oneTPlanacross all hooks), so it's tracked separately as TML-2752 rather than folded in here.param-ref-mutator.ts(new, ~400 lines) in7-runtime— the mutator surface, the flatten walk, and the draft-reconstruction helper. Reconstruction rebuilds the tree from the original draft applying an identity-keyed override map (MongoParamRefis frozen, so values can't be mutated in place); replacement nodes preservecodecId/namesoresolveParamsstill routes to the right codec.flattenMongoParamRefsenumerates exactly whatresolveParamsresolves. Both walk plain objects and arrays and treat everything else (incl.Date) as a leaf; this parity is the invariant that keeps a ref from being invisible-to-middleware yet still resolved. It's documented on the walk and pinned by a parity test.7-runtime, vs SQL keeping both runtime-/lanes-private), plus Mongo's stricter distinct-draft type.Behavior changes & evidence
MongoParamRefnodes and can rewrite them before encode. Wiring inpackages/2-mongo-family/7-runtime/src/mongo-runtime.ts; mutator inpackages/2-mongo-family/7-runtime/src/param-ref-mutator.ts. Proof:packages/2-mongo-family/7-runtime/test/execute-param-mutator-wiring.test.tsasserts a bulk middleware's writeback reaches a stub driver (token: 'bulk:alpha', untaggednoteunchanged).packages/3-mongo-target/2-mongo-adapter/src/mongo-adapter.ts(two-phase split) +lowering.ts/resolve-value.ts. Proof:packages/3-mongo-target/2-mongo-adapter/test/structural-lower.test.tspinsMongoParamRefsurvival across all command shapes.packages/2-mongo-family/7-runtime/src/content-hash.ts. Proof: the hash-discrimination case inexecute-param-mutator-wiring.test.ts(mutating vs identity middleware → different hashes) andpackages/2-mongo-family/7-runtime/test/content-hash-guard.test.ts(draft input →RUNTIME.CONTENT_HASH_REQUIRES_RESOLVED_COMMAND).Lowering-contract audit
computeMongoContentHashresolveParams, post-mutation), so a mutation changes the digest; now also throws on a pre-resolve draft instead of feedingMongoParamReftocanonicalStringify.execute(command)AnyMongoWireCommandshape; only when it's built moved afterbeforeExecute.decodeMongoRow(collection read)command.collectionfrom the resolved exec, unchanged.runBeforeCompile/ plan metaresolveParamsover the unchanged draft matches the prior eagerlower().Verification
pnpm testinpackages/2-mongo-family/7-runtime— 170 passing.pnpm testinpackages/3-mongo-target/2-mongo-adapter— 284 passing.pnpm typecheck(both packages) — clean.pnpm lint:deps— clean.Alternatives considered
MongoParamRefclass instances throughcanonicalStringify(which rejects them) and would key the cache on pre-mutation values, returning stale results when middleware mutates.beforeExecutea type-honest pre-resolve plan type now. Rejected for this PR: it's a cross-family change to the framework middleware SPI; tracked as TML-2752. The transitional cast is made safe in the meantime by the content-hash guard and documented contract.Checklist
git commit -s).TML-NNNN: <sentence-case title>form.Summary by CodeRabbit
New Features
Refactor
Documentation
Tests