Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
6 changes: 6 additions & 0 deletions architecture.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,12 @@
"layer": "adapters",
"plane": "runtime"
},
{
"glob": "packages/3-targets/mongo/9-facade/src/exports/query-builder.ts",
"domain": "extensions",
"layer": "adapters",
"plane": "runtime"
},
{
"glob": "packages/3-targets/mongo/9-facade/src/exports/target.ts",
"domain": "extensions",
Expand Down
4 changes: 3 additions & 1 deletion docs/architecture docs/subsystems/10. MongoDB Family.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,10 @@ 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.

Applications use `mongoQuery` from `@prisma/orm-mongo/query-builder`. It validates the contract first, with the same validator and error as `mongo({ contract })`, and skips a value that was already validated, such as one from `contractFromJson`. The package's own builder, `mongoQueryOnValidatedContract`, is on the internal entry `@internal/mongo-query-builder/internal/on-validated-contract` (published as `@prisma/orm-family-mongo/query-builder/internal/on-validated-contract`) and does not validate.

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

const q = mongoQuery({ contract }); // from contractFromJson<Contract>(json) or defineContract

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
12 changes: 6 additions & 6 deletions docs/reference/Mongo Pipeline Builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ 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 { contractFromJson } from '@prisma/orm-mongo/static';
import { acc, mongoQuery } from '@prisma/orm-mongo/query-builder';
import type { Contract } from './contract';
import contractJson from './contract.json' with { type: 'json' };

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
8 changes: 7 additions & 1 deletion packages/0-shared/publish-surface/src/shells.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,12 @@ const SQL_QUERY_REEXPORTS: readonly ShellReexportMapping[] = [
*/
const MONGO_QUERY_REEXPORTS: readonly ShellReexportMapping[] = [
{ package: '@internal/mongo-orm', entry: 'orm' },
{ package: '@internal/mongo-query-builder', entry: 'query-builder' },
{
package: '@internal/mongo-query-builder',
entry: 'query-builder',
root: false,
subpaths: ['contract-free'],
},
{ package: '@internal/mongo-query-ast', entry: 'query-ast' },
{ package: '@internal/mongo-value', entry: 'value' },
];
Expand Down Expand Up @@ -548,6 +553,7 @@ export const publicShells: ReadonlyMap<ShellName, ShellDefinition> = new Map<
dir: 'packages/2-mongo-family/5-query-builders/query-builder',
name: '@internal/mongo-query-builder',
entry: 'query-builder',
subpaths: ['.', 'contract-free', 'internal/on-validated-contract'],
},
{
dir: 'packages/2-mongo-family/6-transport/mongo-lowering',
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 (`mongoQueryOnValidatedContract`, internal entry; it trusts the contract it is given) — creates the 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,8 +4,10 @@ A typed CRUD query builder for MongoDB contracts. Reads, writes, pipeline aggreg

## Quick start

Applications use `mongoQuery` from `@prisma/orm-mongo/query-builder`, which validates the contract first. This package's builder, `mongoQueryOnValidatedContract`, lives on the internal entry `@internal/mongo-query-builder/internal/on-validated-contract` (published as `@prisma/orm-family-mongo/query-builder/internal/on-validated-contract`). It does not validate: pass it only a contract that is already validated, such as one from `contractFromJson`.

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

const q = mongoQuery({ contract });

Expand Down Expand Up @@ -109,7 +111,7 @@ The chain has three steps:

The result-row encoding uses a `ModelArrayField<ModelName>` marker so `ResolveRow` produces `Array<ForeignRow>` — the field is **not** `unknown[]`. Marker behaviour is unchanged from the legacy shape: clears `UpdateEnabled` and `FindAndModifyEnabled`, sets `LeadingMatch` to `'past-leading'`, preserves the nested-shape parameter `N`.

**Limitation.** Typoed root names (`from('usexxxrs')`) are currently accepted at compile time and rejected at runtime. The constraint is a pre-existing limitation of `Contract.roots: Record<string, string>` (shared with `mongoQuery.from('badname')`); compile-time rejection is tracked under [TML-2400](https://linear.app/prisma-company/issue/TML-2400).
**Limitation.** Typoed root names (`from('usexxxrs')`) are currently accepted at compile time and rejected at runtime. The constraint is a pre-existing limitation of `Contract.roots: Record<string, string>` (shared with `mongoQueryOnValidatedContract.from('badname')`); compile-time rejection is tracked under [TML-2400](https://linear.app/prisma-company/issue/TML-2400).

## Field accessor

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"exports": {
".": "./dist/index.mjs",
"./contract-free": "./dist/exports/contract-free.mjs",
"./internal/on-validated-contract": "./dist/internal/on-validated-contract.mjs",
"./package.json": "./package.json"
},
"engines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export type {
} from '../lookup-builder';
export type { FindAndModifyEnabled, UpdateEnabled } from '../markers';
export type { QueryRoot } from '../query';
export { mongoQuery } from '../query';
export type {
ModelArrayField,
ModelNestedShape,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { mongoQueryOnValidatedContract } from '../query';
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 `mongoQueryOnValidatedContract({ contract }).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 @@ -10,9 +10,7 @@ import { contractError } from './contract-errors';
import { asMongoContract, type CollectionHandle, createCollectionHandle } from './state-classes';

/**
* Public entry point of the query builder. `mongoQuery(...).from(rootName)`
* yields the root state of the three-state machine
* (`CollectionHandle` → `FilteredCollection` → `PipelineChain`).
* The query builder over a contract that is already validated; it does not validate. Applications use `mongoQuery` from `@prisma/orm-mongo/query-builder`, which validates first. `mongoQueryOnValidatedContract(...).from(rootName)` yields the root state of the three-state machine (`CollectionHandle` → `FilteredCollection` → `PipelineChain`).
*
* `rawCommand(cmd)` is the escape hatch for cases the typed surface does
* not cover (yet) — it accepts any `AnyMongoCommand` (typed CRUD or a
Expand All @@ -29,7 +27,7 @@ export interface QueryRoot<
rawCommand<C extends AnyMongoCommand>(command: C): MongoQueryPlan<unknown, C>;
}

export function mongoQuery<
export function mongoQueryOnValidatedContract<
TContract extends MongoContractWithTypeMaps<MongoContract, AnyMongoTypeMaps>,
>(options: { readonly contract: TContract }): QueryRoot<TContract> {
const { contract } = options;
Expand All @@ -43,7 +41,7 @@ export function mongoQuery<
if (!storageHash) {
throw contractError(
'CONTRACT.VALIDATION_FAILED',
'Contract is missing storage.storageHash. Pass a validated contract to mongoQuery().',
'Contract is missing storage.storageHash. Pass a validated contract to mongoQueryOnValidatedContract().',
);
}
const meta: PlanMeta = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function writeMeta(storageHash: string): PlanMeta {

/**
* Root state of the query-builder state machine. Returned from
* `mongoQuery(...).from(name)` and bound to a single collection.
* `mongoQueryOnValidatedContract(...).from(name)` and bound to a single collection.
*
* Inherits the entire pipeline-stage surface from `PipelineChain` (since an
* empty `CollectionHandle` is observably an empty pipeline). Adds:
Expand Down Expand Up @@ -634,7 +634,7 @@ export interface BindingContext<

/**
* Construct a `CollectionHandle` from a validated contract + root name.
* Used by `mongoQuery(...).from(name)` to enter the state machine.
* Used by `mongoQueryOnValidatedContract(...).from(name)` to enter the state machine.
*/
export function createCollectionHandle<
TContract extends MongoContractWithTypeMaps<MongoContract, AnyMongoTypeMaps>,
Expand Down Expand Up @@ -662,7 +662,7 @@ export function createCollectionHandle<
if (!c.storage?.storageHash) {
throw contractError(
'CONTRACT.VALIDATION_FAILED',
'Contract is missing storage.storageHash. Pass a validated contract to mongoQuery().',
'Contract is missing storage.storageHash. Pass a validated contract to mongoQueryOnValidatedContract().',
);
}
return new CollectionHandle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ import {
MongoVectorSearchStage,
} from '@internal/mongo-query-ast/execution';
import { describe, expect, it } from 'vitest';
import { mongoQuery } from '../src/query';
import { mongoQueryOnValidatedContract } from '../src/query';
import { testContract } from './fixtures/test-contract';

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

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