Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
26e21f8
TML-3690: RuntimeMode is declared once, in framework-components
wmadden-electric Oct 23, 2026
2a8447a
TML-3689: A stack with a driver always has one
wmadden-electric Oct 23, 2026
dd0b29f
TML-3689: Record upgrade instructions for the required stack driver
wmadden-electric Oct 23, 2026
9449d14
TML-3686: The published mongoQuery validates its contract once
wmadden-electric Oct 23, 2026
2480add
TML-3686: Register the MongoDB facade query-builder entry in the arch…
wmadden-electric Oct 23, 2026
d96b2ea
TML-3689: SqlExecutionStackWithDriver is SqlExecutionStack with a req…
wmadden-electric Oct 23, 2026
8fda81f
TML-3689: Docs show the runtime path the clients use, not instantiate…
wmadden-electric Oct 23, 2026
5e05ed3
TML-3686: The only mongoQuery a user can import is the validating one
wmadden-electric Oct 23, 2026
a021cac
Merge branch 'main' of https://github.com/prisma/prisma into tml-3689…
wmadden-electric Oct 23, 2026
626282c
TML-3686: Examples import from published packages, and the upgrade de…
wmadden-electric Oct 23, 2026
25f554a
TML-3689: The upgrade detection finds driver destructured from a stack
wmadden-electric Oct 23, 2026
9ee860d
TML-3689: Stack driver detection matches driver only as the destructu…
wmadden-electric Oct 23, 2026
e422b7b
TML-3686: mongoQuery takes an execution context, as the SQL builder does
wmadden-electric Oct 27, 2026
cc9da80
TML-3686: MongoExecutionContext lives in query-ast without a stack, a…
wmadden-electric Oct 27, 2026
5e67745
Merge branch 'main' of https://github.com/prisma/prisma into tml-3689…
wmadden-electric Oct 27, 2026
1225f36
TML-3689: createSqlExecutionStack builds the stack through createExec…
wmadden-electric Oct 27, 2026
bfecc75
Merge origin/main into tml-3689-runtime-type-cleanups
wmadden-electric Oct 28, 2026
09f121e
TML-3686: the Mongo runtime takes an adapter, and mongoOrm and mongoR…
wmadden-electric Oct 28, 2026
6acd291
TML-3686: mongoOrm takes a context and a runtime, as the SQL orm() does
wmadden-electric Oct 28, 2026
680e68b
Merge branch 'main' of https://github.com/prisma/prisma into tml-3689…
wmadden-electric Oct 28, 2026
61e3475
TML-3686: the Mongo context builds enum accessors once with buildName…
wmadden-electric Oct 28, 2026
375428b
TML-3686: db.enums is built by the static context, and the ORM builds…
wmadden-electric Oct 28, 2026
0b3daac
TML-3686: one codec lookup builds Mongo enum accessors
wmadden-electric Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The chain has three qualitatively different vocabularies, so it gets three class
- **`FilteredCollection<TContract, ModelName>`** — reached by `.match(...)` on a `CollectionHandle` (or chained match). Filtered writes (`updateMany`, `updateOne`, `deleteMany`, `deleteOne`), `upsertOne(updater)`, `findOneAndUpdate`, `findOneAndDelete`. Further `.match(...)` calls stay on `FilteredCollection` (AND-folded); any pipeline-stage method moves to `PipelineChain`.
- **`PipelineChain<TContract, Shape, U, F, L>`** — reached by any pipeline-stage method on either earlier state. Pipeline stages (`.sort`, `.limit`, `.group`, `.lookup`, …), `$merge`/`$out` write terminals, and read terminals (`.build()`, `.aggregate()`). Pipeline-style writes and find-and-modify are *conditional* — available only when the capability markers `U` and `F` are still "enabled"; `L` is a prefix tracker that conditions what a further `.match(...)` does to `U`. See § 2.

The user-facing entry point `mongoQuery({ contract }).from(name)` returns a `CollectionHandle`. Everything else is reached from there.
The user-facing entry point `db.query.from(name)` (or `mongoStatic({ contract }).query.from(name)`) returns a `CollectionHandle`. Everything else is reached from there.

> Throughout this ADR a **terminal** is a method that ends the chain by returning a `MongoQueryPlan` (e.g. `.build()`, `.updateMany()`, `.findOneAndUpdate(...)`). A **stage** is a method that extends the chain and returns another builder class.

Expand Down
7 changes: 5 additions & 2 deletions docs/architecture docs/subsystems/10. MongoDB Family.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,13 @@ Row type inference, custom collection subclasses, and the `include` interface al

Below the ORM and above the raw command AST sits `@internal/mongo-query-builder`: a contract-bound, typed CRUD surface that produces the same `MongoQueryPlan` values the ORM emits. The ORM is model-centric and multi-collection (roots, includes, repositories); the query builder is collection-centric and single-command (reads, writes, find-and-modify, `$merge` / `$out`). Migrations, raw tooling, and other non-ORM callers author plans through the query builder rather than hand-building AST nodes.

`mongoQuery({ context })` takes an execution context, as the SQL builder `sql({ context })` does; it reads the contract from the context. A context comes only from `mongoStatic({ contract })` or `mongo({ contract })`, which validate the contract once, so applications use `mongoStatic({ contract }).query` or `db.query`. `@prisma/orm-mongo/query-builder` republishes the builder unchanged.

```typescript
import { acc, mongoQuery } from '@internal/mongo-query-builder';
import { acc } from '@prisma/orm-mongo/query-builder';
import mongoStatic from '@prisma/orm-mongo/static';

const q = mongoQuery({ contract }); // from contractFromJson<Contract>(json) or defineContract
const q = mongoStatic({ contract }).query; // or db.query from mongo({ contract, url })

// Read — aggregation pipeline
const analytics = q
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -632,12 +632,12 @@ TPlan

The runtime does not own pooling or sockets; it delegates to a target driver. This thin-core approach keeps the runtime simple and stable while adapters and packs carry dialect and capability logic.

**Lifecycle:** instantiate stack with driver → connect at boundary → create runtime ([ADR 159](../adrs/ADR%20159%20-%20Driver%20Terminology%20and%20Lifecycle.md)).
**Lifecycle:** create the adapter and driver from the stack's descriptors → connect at boundary → create runtime ([ADR 159](../adrs/ADR%20159%20-%20Driver%20Terminology%20and%20Lifecycle.md)).

1. Create
- `createSqlExecutionStack({ target, adapter, driver, extensions })` - Creates SQL descriptor stack.
- `createSqlExecutionStack({ target, adapter, driver, extensions })` - Creates SQL descriptor stack: a `SqlExecutionStackWithDriver` when given a driver, for clients that connect, and a `SqlExecutionStack` without one, for the query surface and `toSql`.
- `createExecutionContext({ contract, stack })` - Creates context from contract and descriptors-only stack. Context creation uses descriptor `SqlStaticContributions` (codecs, operation signatures, parameterized codecs) — no instantiation required.
- `instantiateExecutionStack(stack)` - Instantiates all components including driver (unbound).
- `stack.adapter.create(stack)` and `stack.driver.create(options)` - The client creates the adapter and the unbound driver from the stack's descriptors. `instantiateExecutionStack(stack)` from the framework does the same for every component, and tests use it.
- Caller resolves binding from options and calls `driver.connect(binding)` at the boundary where env is available.
- Target factory (e.g. `postgres()`) constructs `new PostgresRuntimeImpl({ context, adapter, driver, verifyMarker, middleware })` — a concrete subclass of `SqlRuntimeBase` / `RuntimeCore`. There is no family-level `createRuntime` factory.
- Validates `contract.json` and caches `storageHash` / `profileHash` (contract is already validated before being passed to context).
Expand Down
14 changes: 7 additions & 7 deletions docs/reference/Mongo Pipeline Builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ This page is a working reference: a concrete example to ground the mental model,
Imagine an `orders` collection where each document has `customerId`, `amount`, and `status`. We want the top ten customers by total spend on active orders:

```ts
import { contractFromJson } from '@internal/mongo/static';
import { mongoQuery } from '@internal/mongo-query-builder';
import mongoStatic, { contractFromJson } from '@prisma/orm-mongo/static';
import { acc } from '@prisma/orm-mongo/query-builder';
import type { Contract } from './contract';
import contractJson from './contract.json' with { type: 'json' };

const query = mongoQuery({ contract: contractFromJson<Contract>(contractJson) });
const { query } = mongoStatic({ contract: contractFromJson<Contract>(contractJson) });

const plan = query
.from('orders')
Expand Down Expand Up @@ -73,7 +73,7 @@ Examples in the rest of this page assume:

```ts
const orders = query.from('orders');
import { acc, fn } from '@internal/mongo-query-builder';
import { acc, fn } from '@prisma/orm-mongo/query-builder';
```

`fn` is a namespace of typed expression helpers (`fn.toUpper(...)`, `fn.concat(...)`, `fn.literal(...)`); `acc` is the parallel namespace of accumulators (`acc.sum(...)`, `acc.count(...)`, …). Both produce `MongoAggExpr` AST nodes under the hood, but you rarely need to think about that.
Expand Down Expand Up @@ -144,7 +144,7 @@ orders.sortByCount((f) => f.status).build();
For bucketing the typed surface gives you the chain entry point but the bucket boundaries / grouping expression flow as raw AST nodes (most commonly `MongoAggFieldRef.of('field')` — see [the primitives reference](./mongodb-primitives-reference.md)):

```ts
import { MongoAggFieldRef } from '@internal/mongo-query-ast/execution';
import { MongoAggFieldRef } from '@prisma/orm-mongo/query-ast/execution';

orders
.bucket({ groupBy: MongoAggFieldRef.of('amount'), boundaries: [0, 100, 1000] })
Expand All @@ -170,7 +170,7 @@ orders
`graphLookup` is the recursive form. `unionWith` appends another collection's rows to the current pipeline. Both expose option objects rather than fluent chains:

```ts
import { MongoAggFieldRef } from '@internal/mongo-query-ast/execution';
import { MongoAggFieldRef } from '@prisma/orm-mongo/query-ast/execution';

orders
.graphLookup({
Expand Down Expand Up @@ -299,7 +299,7 @@ What is *not* statically guaranteed:
Two levels of escape hatch are available when the typed surface does not reach a use case:

```ts
import { MongoLimitStage } from '@internal/mongo-query-ast/execution';
import { MongoLimitStage } from '@prisma/orm-mongo/query-ast/execution';

// Append a raw stage; preserves the chain but collapses the shape unless
// you supply a NewShape to pipe<NewShape>().
Expand Down
2 changes: 1 addition & 1 deletion examples/mongo-demo/test/cache-middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('mongo-demo cache middleware integration', {
executor: runtime,
enums: buildMongoEnums(contract, context.codecs),
});
const query = mongoQuery({ contract });
const query = mongoQuery({ context });
return { runtime, orm, query, driver, driverExecuteSpy };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface ExecutionStack<
> {
readonly target: RuntimeTargetDescriptor<TFamilyId, TTargetId>;
readonly adapter: RuntimeAdapterDescriptor<TFamilyId, TTargetId, TAdapterInstance>;
readonly driver:
readonly driver?:
| RuntimeDriverDescriptor<TFamilyId, TTargetId, unknown, TDriverInstance>
| undefined;
readonly extensions: readonly RuntimeExtensionDescriptor<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,15 @@ export interface StartActivity<K extends ClientActivityKind = ClientActivityKind
readonly annotations?: ReadonlyMap<string, AnnotationValue<unknown, OperationKind>>;
}

/** How middleware treats a guardrail violation. `'strict'`, the default, makes `budgets` throw on every violation; `'permissive'` lets a violation whose severity is `'warn'` go to `log.warn`. */
export type RuntimeMode = 'strict' | 'permissive';

/**
* The context of one activity. Every hook of the activity receives the same object.
*/
export interface ActivityContext {
readonly contract: unknown;
readonly mode: 'strict' | 'permissive';
readonly mode: RuntimeMode;
readonly now: () => number;
readonly log: RuntimeLog;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type {
RuntimeExecutor,
RuntimeLog,
RuntimeMiddleware,
RuntimeMode,
RuntimeStatementStats,
StartActivity,
StatementActivity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Internal architecture notes for contributors.

| File | Responsibility |
|------|---------------|
| `query.ts` | Entry point (`mongoQuery`) — validates root, creates initial builder |
| `query.ts` | Entry point (`mongoQuery({ context })`) — reads the contract from the execution context, creates initial builder |
| `builder.ts` | `PipelineBuilder` class — immutable; each stage method returns a new instance with the appended stage |
| `types.ts` | Core type machinery: `DocField`, `DocShape`, `ModelToDocShape`, `ResolveRow`, shape transformers (`ProjectedShape`, `GroupedDocShape`, `UnwoundShape`, etc.) |
| `field-proxy.ts` | `Proxy`-based `FieldProxy<Shape>` — intercepts property access to produce `TypedAggExpr` nodes |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ A typed CRUD query builder for MongoDB contracts. Reads, writes, pipeline aggreg

## Quick start

`mongoQuery({ context })` takes an execution context, as `sql({ context })` does. Applications get the builder as `mongoStatic({ contract }).query` or `db.query`, whose context holds the contract `mongoStatic()` or `mongo()` validated.

```typescript
import { mongoQuery } from '@internal/mongo-query-builder';
import { acc } from '@prisma/orm-mongo/query-builder';
import mongoStatic from '@prisma/orm-mongo/static';

const q = mongoQuery({ contract });
const q = mongoStatic({ contract }).query;

// Read — aggregation pipeline
const analytics = q
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export interface LookupBuilder<
/**
* Type of the `from` callable passed to `PipelineChain.lookup`'s outer
* callback. The generic argument is inferred from a string-literal
* argument (the same pattern as `mongoQuery({ contract }).from('orders')`),
* argument (the same pattern as `mongoQuery({ context }).from('orders')`),
* which grounds `RootName` into the returned `LookupBuilder` *before*
* the inner `on(...)` callback is type-checked. This sequential
* inference is what makes `foreign` resolve narrowly to the foreign
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ export interface QueryRoot<
rawCommand<C extends AnyMongoCommand>(command: C): MongoQueryPlan<unknown, C>;
}

/** Builds queries for the contract of `context`, which `mongoStatic({ contract })` and `mongo({ contract })` create after validating the contract. */
export function mongoQuery<
TContract extends MongoContractWithTypeMaps<MongoContract, AnyMongoTypeMaps>,
>(options: { readonly contract: TContract }): QueryRoot<TContract> {
const { contract } = options;
>(options: { readonly context: { readonly contract: TContract } }): QueryRoot<TContract> {
const { contract } = options.context;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
return {
from<K extends keyof TContract['roots'] & string>(rootName: K) {
return createCollectionHandle(contract, rootName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { mongoQuery } from '../src/query';
import { testContract } from './fixtures/test-contract';

function createOrdersBuilder() {
return mongoQuery({ contract: testContract }).from('orders');
return mongoQuery({ context: { contract: testContract } }).from('orders');
}

describe('new stage builder methods', () => {
Expand Down
Loading
Loading