TML-2964: Mongo aggregation helpers source their codecs from the adapter (context-bound fn)#909
TML-2964: Mongo aggregation helpers source their codecs from the adapter (context-bound fn)#909wmadden-electric wants to merge 3 commits into
Conversation
Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… literals
The adapter declares an operation-to-output-codec table (value + type, the
Mongo analog of SQL queryOperationTypes), threaded through the execution
context into mongoQuery({ contractJson, operationCodecs }) and the pipeline
chain. fn is minted from that table and delivered context-bound: exposed on
the query root and passed as the second parameter to the stage callbacks
(project/addFields/replaceRoot/sortByCount/redact/match). The free-floating
fn export is deleted.
Computed outputs are table-sourced at the value and type level; input params
are constrained by decoded output type, so a contract date field passes to
fn.dateToString/dateDiff uncast. The result-shape reifier resolves in-table
operators to leaf codecs, so computed scalars decode at runtime. Array and
document helpers stamp a structural unresolved marker (resolves to unknown,
as before); acc is unchanged for now.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Examples and integration tests move off the deleted free fn import to the stage-callback parameter or the query root; direct mongoQuery calls supply operationCodecs. New integration tests prove computed scalars decode (fn.toDate returns a real Date) and that a contract date field passes to fn.dateToString uncast (the old Parameters<> cast is deleted). Upgrade instructions document the breaking change for app and extension authors. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
📝 WalkthroughWalkthroughThis PR replaces the Mongo query builder's fixed, imported ChangesOperation-codec typing for Mongo query builder
Estimated code review effort: 4 (Complex) | ~75 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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/extension-supabase
@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/config-loader
@prisma-next/emitter
@prisma-next/language-server
@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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts (1)
368-413: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winBare
ascast introduced inproject()'s callback branch.Line 395 casts
args[0]with a bareas, which is disallowed by the coding guideline.castAsis already imported and used elsewhere in this file (see thebuild()method), so it can be reused here.🛠️ Proposed fix using `castAs`
if (args.length === 1 && typeof args[0] === 'function') { - const fn = args[0] as ( - fields: FieldAccessor<Shape, N>, - helpers: StageFn<TContract, TOps>, - ) => Record<string, 1 | TypedAggExpr<DocField>>; + const fn = castAs< + ( + fields: FieldAccessor<Shape, N>, + helpers: StageFn<TContract, TOps>, + ) => Record<string, 1 | TypedAggExpr<DocField>> + >(args[0]); const accessor = createFieldAccessor<Shape, N>(); const spec = fn(accessor, this.fn);As per coding guidelines: "No bare
asin production code. UseblindCast<T, "Reason">orcastAs<T>from@prisma-next/utils/casts...as constand test files are exempt."🤖 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/5-query-builders/query-builder/src/builder.ts` around lines 368 - 413, The callback branch in `project()` uses a bare type cast on `args[0]`, which violates the no-`as` coding guideline. Update the `project` method in `PipelineChain` to use the existing `castAs` helper instead of the inline cast when treating `args[0]` as the callback, matching the pattern already used in `build()`.Source: Coding guidelines
🧹 Nitpick comments (1)
packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts (1)
137-187: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
fnis rebuilt on every chain instance instead of being reused.Each
#withStagecall produces a freshPipelineChainwith#fnuninitialized. SinceTOps/TContractnever change across a chain, the samecreateFn(operationCodecs)object (≈80 closures) gets rebuilt every time a different chain instance's.fnis accessed (e.g..match(fn1).project(fn2)triggers two separatecreateFncalls for logically identical helpers). The same duplication also occurs wheneverCollectionHandle/FilteredCollectionare re-instantiated instate-classes.tsmatch().Since the helper object doesn't depend on
Shape/markers, it's safe to thread the already-built instance forward through#withStage(and analogously throughstate-classes.tsconstructors) instead of rebuilding.⚡ Proposed fix to reuse the minted `fn` across chain transitions
readonly `#operationCodecs`: TOps; `#fn`: StageFn<TContract, TOps> | undefined; - constructor(contract: TContract, state: PipelineChainState, operationCodecs: TOps) { + constructor( + contract: TContract, + state: PipelineChainState, + operationCodecs: TOps, + fn?: StageFn<TContract, TOps>, + ) { this.#contract = contract; this.#state = state; this.#operationCodecs = operationCodecs; + this.#fn = fn; }>(stage: MongoPipelineStage): PipelineChain<TContract, NewShape, NewU, NewF, NewL, NewN, TOps> { return new PipelineChain<TContract, NewShape, NewU, NewF, NewL, NewN, TOps>( this.#contract, { ...this.#state, stages: [...this.#state.stages, stage], }, this.#operationCodecs, + this.#fn, ); }🤖 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/5-query-builders/query-builder/src/builder.ts` around lines 137 - 187, The StageFn helper in PipelineChain is being recreated for every new chain instance, even though it only depends on the shared operation codecs and contract. Thread the already-minted `fn` through `PipelineChain.#withStage` so new chains reuse the existing instance instead of leaving `#fn` undefined, and apply the same reuse pattern in the `state-classes.ts` constructors used by `CollectionHandle` and `FilteredCollection` so `createFn` is not called again for identical helpers.
🤖 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/5-query-builders/query-builder/src/expression-helpers.ts`:
- Around line 349-358: The computed() helper currently falls back to an empty
codecId when lookup[op] is missing, which hides missing adapter mappings; update
computed() in expression-helpers.ts to fail fast instead of minting a
ComputedField with an empty codec id. Use the lookup[op] result inside
computed<Op> and throw or otherwise surface an explicit error when no codec
mapping exists before constructing the _field and MongoAggOperator.of(op, args)
result.
In `@packages/2-mongo-family/5-query-builders/query-builder/src/types.ts`:
- Around line 31-51: The computed-expression typing is too narrow because
ComputedField currently hardcodes nullable: false for every table-backed
operator. Update the operator metadata/type flow in types.ts and the shape
builder used by computePipelineResultShape() so each op can carry its own
nullability, or route null-producing ops through a separate nullable path. Make
sure the resulting row shape matches decodeMongoRow() behavior by preserving
null where the operator can legitimately return it.
---
Outside diff comments:
In `@packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts`:
- Around line 368-413: The callback branch in `project()` uses a bare type cast
on `args[0]`, which violates the no-`as` coding guideline. Update the `project`
method in `PipelineChain` to use the existing `castAs` helper instead of the
inline cast when treating `args[0]` as the callback, matching the pattern
already used in `build()`.
---
Nitpick comments:
In `@packages/2-mongo-family/5-query-builders/query-builder/src/builder.ts`:
- Around line 137-187: The StageFn helper in PipelineChain is being recreated
for every new chain instance, even though it only depends on the shared
operation codecs and contract. Thread the already-minted `fn` through
`PipelineChain.#withStage` so new chains reuse the existing instance instead of
leaving `#fn` undefined, and apply the same reuse pattern in the
`state-classes.ts` constructors used by `CollectionHandle` and
`FilteredCollection` so `createFn` is not called again for identical helpers.
🪄 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: 3aabb192-36b7-417e-832b-9305cc9c970c
⛔ Files ignored due to path filters (2)
projects/mongo-agg-codec-source/plan.mdis excluded by!projects/**projects/mongo-agg-codec-source/spec.mdis excluded by!projects/**
📒 Files selected for processing (46)
examples/mongo-demo/src/server.tsexamples/mongo-demo/test/cache-middleware.test.tsexamples/mongo-demo/test/query-builder-writes.test.tspackages/2-mongo-family/5-query-builders/query-builder/src/accumulator-helpers.tspackages/2-mongo-family/5-query-builders/query-builder/src/builder.tspackages/2-mongo-family/5-query-builders/query-builder/src/exports/index.tspackages/2-mongo-family/5-query-builders/query-builder/src/expression-helpers.tspackages/2-mongo-family/5-query-builders/query-builder/src/pipeline-result-shape.tspackages/2-mongo-family/5-query-builders/query-builder/src/query.tspackages/2-mongo-family/5-query-builders/query-builder/src/resolve-path.tspackages/2-mongo-family/5-query-builders/query-builder/src/state-classes.tspackages/2-mongo-family/5-query-builders/query-builder/src/types.tspackages/2-mongo-family/5-query-builders/query-builder/test/accumulator-helpers.test-d.tspackages/2-mongo-family/5-query-builders/query-builder/test/accumulator-helpers.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/builder-new-stages.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/builder.test-d.tspackages/2-mongo-family/5-query-builders/query-builder/test/builder.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/contract-free-collection.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/expression-helpers.test-d.tspackages/2-mongo-family/5-query-builders/query-builder/test/expression-helpers.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/field-accessor.test-d.tspackages/2-mongo-family/5-query-builders/query-builder/test/find-and-modify.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/fixtures/test-contract.tspackages/2-mongo-family/5-query-builders/query-builder/test/pipeline-result-shape.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/pipeline-updates.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/raw-command.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/result-shape.test.tspackages/2-mongo-family/5-query-builders/query-builder/test/state-machine-surface.test-d.tspackages/2-mongo-family/5-query-builders/query-builder/test/state-machine.test-d.tspackages/2-mongo-family/5-query-builders/query-builder/test/types.test-d.tspackages/2-mongo-family/5-query-builders/query-builder/test/writes.test.tspackages/2-mongo-family/7-runtime/src/mongo-execution-stack.tspackages/2-mongo-family/7-runtime/test/decode-via-query-builder.test.tspackages/2-mongo-family/7-runtime/test/decode.integration.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/runtime-types.test-d.tspackages/3-extensions/mongo/src/static/mongo-static.tspackages/3-mongo-target/2-mongo-adapter/src/core/mongo-control-adapter.tspackages/3-mongo-target/2-mongo-adapter/src/core/operation-output-codecs.tspackages/3-mongo-target/2-mongo-adapter/src/exports/runtime.tsskills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.mdskills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.mdtest/integration/test/cross-package/cross-family-middleware.test.tstest/integration/test/mongo-runtime/query-builder.test.tstest/integration/test/mongo/query-builder.test.ts
| function computed<Op extends string>( | ||
| op: Op, | ||
| args: MongoAggExpr | ReadonlyArray<MongoAggExpr> | Readonly<Record<string, MongoAggExpr>>, | ||
| ): Computed<TOps, Op> { | ||
| return { | ||
| _field: { codecId: DOC.codecId, nullable: true }, | ||
| node: MongoAggOperator.of('$first', a.node), | ||
| _field: blindCast< | ||
| ComputedField<TOps, Op>, | ||
| 'codecId is read from the adapter table entry for this operator' | ||
| >({ codecId: lookup[op] ?? '', nullable: false }), | ||
| node: MongoAggOperator.of(op, args), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Fail fast when an operator has no codec mapping.
lookup[op] ?? '' silently mints a computed field with an empty codec id when the adapter table omits an operator. That hides adapter/config mistakes and can produce plans whose type-level field says “computed” without a real runtime codec source.
Proposed fix
function computed<Op extends string>(
op: Op,
args: MongoAggExpr | ReadonlyArray<MongoAggExpr> | Readonly<Record<string, MongoAggExpr>>,
): Computed<TOps, Op> {
+ const codecId = lookup[op];
+ if (codecId === undefined) {
+ throw new Error(`Missing Mongo output codec for aggregation operator "${op}".`);
+ }
return {
_field: blindCast<
ComputedField<TOps, Op>,
'codecId is read from the adapter table entry for this operator'
- >({ codecId: lookup[op] ?? '', nullable: false }),
+ >({ codecId, nullable: false }),
node: MongoAggOperator.of(op, args),
};
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function computed<Op extends string>( | |
| op: Op, | |
| args: MongoAggExpr | ReadonlyArray<MongoAggExpr> | Readonly<Record<string, MongoAggExpr>>, | |
| ): Computed<TOps, Op> { | |
| return { | |
| _field: { codecId: DOC.codecId, nullable: true }, | |
| node: MongoAggOperator.of('$first', a.node), | |
| _field: blindCast< | |
| ComputedField<TOps, Op>, | |
| 'codecId is read from the adapter table entry for this operator' | |
| >({ codecId: lookup[op] ?? '', nullable: false }), | |
| node: MongoAggOperator.of(op, args), | |
| function computed<Op extends string>( | |
| op: Op, | |
| args: MongoAggExpr | ReadonlyArray<MongoAggExpr> | Readonly<Record<string, MongoAggExpr>>, | |
| ): Computed<TOps, Op> { | |
| const codecId = lookup[op]; | |
| if (codecId === undefined) { | |
| throw new Error(`Missing Mongo output codec for aggregation operator "${op}".`); | |
| } | |
| return { | |
| _field: blindCast< | |
| ComputedField<TOps, Op>, | |
| 'codecId is read from the adapter table entry for this operator' | |
| >({ codecId, nullable: false }), | |
| node: MongoAggOperator.of(op, args), |
🤖 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/5-query-builders/query-builder/src/expression-helpers.ts`
around lines 349 - 358, The computed() helper currently falls back to an empty
codecId when lookup[op] is missing, which hides missing adapter mappings; update
computed() in expression-helpers.ts to fail fast instead of minting a
ComputedField with an empty codec id. Use the lookup[op] result inside
computed<Op> and throw or otherwise surface an explicit error when no codec
mapping exists before constructing the _field and MongoAggOperator.of(op, args)
result.
| export type MongoOperationCodecTable = Readonly<Record<string, string>>; | ||
|
|
||
| export type CodecTypesBase = Record<string, { readonly output: unknown }>; | ||
|
|
||
| /** | ||
| * Codec ids from the contract's codec-type map whose decoded output extends | ||
| * `TOutput` — the Mongo analog of SQL's `CodecIdsWithTrait`, keyed on | ||
| * decoded output type instead of traits. | ||
| */ | ||
| export type CodecIdsWithOutput<CT extends CodecTypesBase, TOutput> = { | ||
| [K in keyof CT & string]: CT[K]['output'] extends TOutput ? K : never; | ||
| }[keyof CT & string]; | ||
|
|
||
| /** | ||
| * Field stamped on a computed expression whose output codec is declared by | ||
| * the adapter table for `Op`. | ||
| */ | ||
| export type ComputedField<TOps extends MongoOperationCodecTable, Op extends string> = { | ||
| readonly codecId: TOps[Op & keyof TOps]; | ||
| readonly nullable: false; | ||
| }; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## types.ts\n'
sed -n '1,220p' packages/2-mongo-family/5-query-builders/query-builder/src/types.ts
printf '\n## pipeline-result-shape.ts\n'
sed -n '1,260p' packages/2-mongo-family/5-query-builders/query-builder/src/pipeline-result-shape.ts
printf '\n## search for ComputedField and nullable usage\n'
rg -n "ComputedField|nullable: false|nullable: true|MongoOperationCodecTable|CodecIdsWithOutput|pipeline-result-shape" packages/2-mongo-family/5-query-builders/query-builder/srcRepository: prisma/prisma-next
Length of output: 19973
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## expression-helpers.ts (computed-related sections)\n'
sed -n '1,260p' packages/2-mongo-family/5-query-builders/query-builder/src/expression-helpers.ts
printf '\n---\n'
sed -n '260,420p' packages/2-mongo-family/5-query-builders/query-builder/src/expression-helpers.ts
printf '\n## result-shape.ts\n'
sed -n '1,220p' packages/2-mongo-family/5-query-builders/query-builder/src/result-shape.ts
printf '\n## accumulator-helpers.ts\n'
sed -n '1,140p' packages/2-mongo-family/5-query-builders/query-builder/src/accumulator-helpers.tsRepository: prisma/prisma-next
Length of output: 22339
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "nullable" packages/2-mongo-family -g '*.ts' -g '*.tsx' | sed -n '1,240p'Repository: prisma/prisma-next
Length of output: 40444
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## references to MongoFieldShape.nullable and shape consumption\n'
rg -n "MongoFieldShape|freezeMongoFieldShape|freezeMongoResultShape|nullable" packages -g '*.ts' -g '*.tsx' | sed -n '1,260p'
printf '\n## search for result-shape decoders / readers\n'
rg -n "kind: 'leaf'|kind: 'array'|kind: 'document'|kind: 'unknown'" packages/2-mongo-family -g '*.ts' -g '*.tsx' | sed -n '1,260p'Repository: prisma/prisma-next
Length of output: 50374
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n## decoder implementation references\n'
rg -n "decodeMongoRow|nullable|kind: 'leaf'|kind: 'array'|kind: 'document'" packages/2-mongo-family/7-runtime/src packages/2-mongo-family/7-runtime/test -g '*.ts' -g '*.tsx' | sed -n '1,240p'
printf '\n## result-shape tests\n'
sed -n '1,240p' packages/2-mongo-family/5-query-builders/query-builder/test/pipeline-result-shape.test.ts
printf '\n---\n'
sed -n '1,220p' packages/2-mongo-family/5-query-builders/query-builder/test/result-shape.test.tsRepository: prisma/prisma-next
Length of output: 26870
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,240p' packages/2-mongo-family/7-runtime/src/codecs/decoding.tsRepository: prisma/prisma-next
Length of output: 5380
Thread nullability through computed-expression shapes
ComputedField and computePipelineResultShape() still stamp every table-backed operator as nullable: false, but several of these operators can return null when their inputs/options allow it. That makes the projected row type too narrow even though decodeMongoRow() will pass null through unchanged. Carry nullable in the op metadata, or split null-producing ops into a nullable path.
🤖 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/5-query-builders/query-builder/src/types.ts` around
lines 31 - 51, The computed-expression typing is too narrow because
ComputedField currently hardcodes nullable: false for every table-backed
operator. Update the operator metadata/type flow in types.ts and the shape
builder used by computePipelineResultShape() so each op can carry its own
nullability, or route null-producing ops through a separate nullable path. Make
sure the resulting row shape matches decodeMongoRow() behavior by preserving
null where the operator can legitimately return it.
At a glance
The decision
The family layer must not know specific codec ids — that knowledge belongs to the adapter. So the Mongo adapter now declares an operation→output-codec table (
mongoOperationOutputCodecs, 55 operators — the Mongo analog of SQL'squeryOperationTypes), and the query builder consumes it instead of hardcodingmongo/double@1/mongo/string@1/… literals. The table is exported as a value and a type, so one declaration drives runtime codec stamping, compile-time output types, and result-shape decoding.This is the first PR of TML-2964.
acc(accumulators) and structural array/document outputs follow in the next two PRs;accstill carries its literals for now (tracked, unchanged behavior).How it works
MongoExecutionContext→mongoQuery({ contractJson, operationCodecs })(both required, no default — a builder without codec knowledge cannot mint the helpers) →PipelineChain. The query-builder package defines the minimal table type it consumes and imports nothing from the runtime or target layers (lint:depsgreen).fn: minted bycreateFn(table), exposed on the query root (for standalone construction — the control adapter buildsfn.setUnion(...)outside any stage) and passed as the second parameter toproject/addFields/replaceRoot/sortByCount/redact/matchcallbacks. Existing one-param callbacks keep compiling. The free-floatingfnexport is deleted — a detached helper has no codec source, so no context-free form exists.fn.concat→ the table's$concatcodec →string), so the family's emitted.d.tscarries no codec-id literal either. Helper input params (dateToString'sdate,trim'sinput, …) are constrained by decoded output type — a type-level filter over the contract's codec-type map — so a contract date field and a computedfn.toDate(...)both satisfy a date param, uncast.$mapbodies, …) stayunknown— pass-through, as before.$toDecimal→ double codec;$addover a date) is pass-through, not corruption — the numeric codec's decode is identity, and the decode walk returnsnull/undefinedbefore invoking any codec (onNullpaths safe).Breaking change
Consumer-facing: the free
fnimport is gone andmongoQueryrequiresoperationCodecs. Migration paths (stage-callback param / query-rootfn/ adapter table for directmongoQuerycalls) are documented in the 0.14→0.15 upgrade instructions for both app authors and extension authors (extensions declaring aMongoRuntimeAdapterDescriptormust add the requiredoperationOutputCodecsfield).Verification
alt/*@9table proves types come from the table, not a residual hardcode); grep-guard: nomongo/*@1literal in the package source or emitted.d.ts(onlyaccumulator-helpers.ts, the trackedaccremainder).fn.toDate(...)returnsinstanceof Datewith no_bsontypeleakage, plus row-type assertions;fn.dateToString({ date: f.createdAt })uncast returns the formatted string.lint:deps+lint:casts(delta −15),fixtures:check,test:packages,test:integration(195 files),test:e2e(109 tests), retail-storenext build.expectTypeOfchecks added).Alternatives considered
string/number/… beside codecs): rejected — codecs are the single type vocabulary; a parallel classification also split computed and contract fields into non-interoperable representations.numeric/textual/… → codec): lighter than per-operation declarations, but reintroduces scalar-category labels into the family and can't express per-operation facts (which numeric codec$countreturns; date-arithmetic signatures).date/objectIdcarry no trait, andnumericis ambiguous (double and int32 both carry it).operationCodecs: a default requires a family-level fallback codec table — the thing this PR removes.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes