Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion .agents/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ Rules below are listed by bare filename; the canonical file is `.agents/rules/<n
## SQL & Query Patterns
- `query-patterns.mdc` — Query DSL patterns
- `postgres-lateral-patterns.mdc` — LATERAL/json_agg patterns
- `include-many-patterns.mdc` — includeMany type inference
- `sql-types-imports.mdc` — SQL types import path (use @prisma-next/sql-contract/types)

## TypeScript & Typing
Expand Down
95 changes: 0 additions & 95 deletions .agents/rules/include-many-patterns.mdc

This file was deleted.

2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pnpm fixtures:check # Use this rather than ad-hoc emit-and-diff
- **ExecutionContext**: Encapsulates contract, codecs, operations, and types. Pass to `schema()`, `sql()`, `orm()`.
- **Interface + factory pattern for stateful services**: Stateful services (registries, runtimes, adapters, drivers) are exposed through an interface plus a `createX()` factory; the implementing class stays package-private. Consumers depend on the interface, never the implementation. Pattern reference: [`docs/architecture docs/patterns/interface-plus-factory.md`](docs/architecture%20docs/patterns/interface-plus-factory.md).
- **Three-layer polymorphic IR for AST/IR class hierarchies**: AST/IR nodes (Contract IR, Schema IR, migration ops) are organised as framework interface → family abstract base → target concrete classes. Concrete classes are publicly exported as the target's IR alphabet; `freezeNode(this)` is called in the constructor. Target packs contribute new entity kinds via `AuthoringContributions.entities` (see [`docs/reference/typescript-patterns.md`](docs/reference/typescript-patterns.md) § "AST/IR class hierarchies"). Pattern references: [`three-layer-polymorphic-ir.md`](docs/architecture%20docs/patterns/three-layer-polymorphic-ir.md), [`frozen-class-ast.md`](docs/architecture%20docs/patterns/frozen-class-ast.md), [`json-canonical-class-in-memory.md`](docs/architecture%20docs/patterns/json-canonical-class-in-memory.md).
- **Capability Gating**: Features like `includeMany` and `returning()` require capabilities in the contract; gating is enforced at authoring time.
- **Capability Gating**: Features like `returning()` require capabilities in the contract; gating is enforced at authoring time.
- **Builder chaining**: Methods return new instances — always chain calls.
- **Column access**: Use `table.columns.fieldName` to avoid conflicts with table properties.

Expand Down
33 changes: 0 additions & 33 deletions docs/reference/query-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,39 +152,6 @@ const plan = db.sql
type JoinedRow = ResultType<typeof plan>;
```

### Queries with includeMany

```typescript
import { db } from '../prisma/db';
import type { ResultType } from '@prisma-next/sql-query/types';

const userTable = db.schema.tables.user;
const postTable = db.schema.tables.post;

const plan = db.sql
.from(userTable)
.includeMany(
postTable,
(on) => on.eqCol(userTable.columns.id, postTable.columns.userId),
(child) =>
child
.select({
id: postTable.columns.id,
title: postTable.columns.title,
})
.orderBy(postTable.columns.createdAt.desc()),
{ alias: 'posts' },
)
.select({
id: userTable.columns.id,
email: userTable.columns.email,
posts: true,
})
.build();

type UserWithPosts = ResultType<typeof plan>;
```

## Anti-Patterns

**❌ WRONG: Don't create extra aliases for one-off usage**
Expand Down
20 changes: 0 additions & 20 deletions packages/2-sql/4-lanes/relational-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,26 +151,6 @@ export type ComputeColumnJsType<
: never
: never;

/**
* Utility type to check if a contract has the required capabilities for includeMany.
* Requires both `lateral` and `jsonAgg` to be `true` in the contract's capabilities for the target.
* Capabilities are nested by target: `{ [target]: { lateral: true, jsonAgg: true } }`
*/
export type HasIncludeManyCapabilities<TContract extends Contract<SqlStorage>> = TContract extends {
capabilities: infer C;
target: infer T;
}
? T extends string
? C extends Record<string, Record<string, boolean>>
? C extends { [K in T]: infer TargetCaps }
? TargetCaps extends { lateral: true; jsonAgg: true }
? true
: false
: false
: false
: false
: false;

/**
* Alias for the SQL-domain executable plan, exposed under the legacy
* `SqlPlan` name for compatibility with SQL builder/utility call sites.
Expand Down
32 changes: 14 additions & 18 deletions packages/3-extensions/sql-orm-client/src/collection-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ import {
stripHiddenMappedFields,
} from './collection-runtime';
import { executeQueryPlan } from './execute-query-plan';
import { selectIncludeStrategy } from './include-strategy';
import { compileSelect, compileSelectWithIncludeStrategy } from './query-plan';
import { compileSelect, compileSelectWithIncludes } from './query-plan';
import { augmentSelectionForJoinColumns } from './selection-shaping';
import {
type CollectionContext,
Expand Down Expand Up @@ -78,11 +77,9 @@ export function dispatchCollectionRows<Row>(options: {
return dispatchWithIncludes<Row>(options);
}

// Both include builders — lateral and correlated — lower every include
// The correlated-subquery include builder lowers every include
// descriptor shape (row, scalar reducers, and combine()) at any depth
// into a single query. Dispatch picks one purely on the `lateral`
// capability flag via `selectIncludeStrategy`; the read path has no
// multi-query fallback.
// into a single query; the read path has no multi-query fallback.
function dispatchWithIncludes<Row>(options: {
contract: Contract<SqlStorage>;
runtime: CollectionContext<Contract<SqlStorage>>['runtime'];
Expand All @@ -91,21 +88,19 @@ function dispatchWithIncludes<Row>(options: {
modelName: string;
}): AsyncIterableResult<Row> {
const { contract, runtime, state, tableName, modelName } = options;
const strategy = selectIncludeStrategy(contract);
const generator = async function* (): AsyncGenerator<Row, void, unknown> {
const { scope, release } = await acquireRuntimeScope(runtime);
try {
const parentJoinColumns = state.includes.map((include) => include.localColumn);
const { selectedForQuery: parentSelectedForQuery, hiddenColumns: hiddenParentColumns } =
augmentSelectionForJoinColumns(state.selectedFields, parentJoinColumns);
const compiled = compileSelectWithIncludeStrategy(
const compiled = compileSelectWithIncludes(
contract,
tableName,
{
...state,
selectedFields: parentSelectedForQuery,
},
strategy,
modelName,
);

Expand Down Expand Up @@ -155,7 +150,7 @@ function dispatchWithIncludes<Row>(options: {
/**
* Reload the rows a mutation just wrote (create / createAll / update /
* updateAll / upsert) through the read-path dispatch, so `.include()`
* relations resolve via the exact same lateral / correlated builders,
* relations resolve via the exact same correlated-subquery builder,
* decode, hidden-column stripping, and polymorphism mapping a read
* query uses — there is no parallel mutation read-back implementation.
*
Expand Down Expand Up @@ -255,7 +250,7 @@ function buildIdentityInFilter(
* Decode a single-query include payload from a parent row's raw cell
* into the model-shaped value that downstream consumers see. Recurses
* through `include.nested.includes` so depth-2+ trees — emitted by the
* recursive lateral / correlated builders — are decoded symmetrically.
* recursive correlated-subquery builder — are decoded symmetrically.
*
* The shape produced by the SQL side is one JSON column per top-level
* include; values nested inside that JSON are already-parsed JS values
Expand Down Expand Up @@ -312,9 +307,10 @@ function decodeIncludePayload(
* - scalar branch -> unwrap the `{value: ...}` envelope via the
* standalone scalar decoder.
*
* On a parent with zero matching child rows the LATERAL still produces
* one row (aggregates collapse the empty input to a single row), so
* the combine envelope here is always present in the read path. The
* On a parent with zero matching child rows the correlated subquery
* still produces one row (aggregates collapse the empty input to a
* single row), so the combine envelope here is always present in the
* read path. The
* mutation read-back's `assignEmptyMutationIncludes` writes the empty
* per-branch shape directly to `parent.mapped[relationName]` for any
* parent absent from the read-back result and never enters the decoder,
Expand Down Expand Up @@ -350,7 +346,7 @@ function decodeCombineIncludePayload(
function parseCombineEnvelope(include: IncludeExpr, raw: unknown): Record<string, unknown> {
if (raw === null || raw === undefined) {
throw new Error(
`combine() envelope for include "${include.relationName}" is missing (got ${raw === null ? 'null' : 'undefined'}); the LATERAL / correlated subquery should always produce a JSON object — this indicates a planner or decoder bug.`,
`combine() envelope for include "${include.relationName}" is missing (got ${raw === null ? 'null' : 'undefined'}); the correlated subquery should always produce a JSON object — this indicates a planner or decoder bug.`,
);
}
const parsed = parseIncludePayload(raw);
Expand All @@ -374,7 +370,7 @@ function describeEnvelopeShape(value: unknown): string {

/**
* Pull the primitive scalar value out of the JSON envelope emitted by
* the lateral / correlated scalar builder.
* the correlated scalar builder.
*
* Contract: the envelope is always either
* - a `{ value: <primitive> }` JSON object (the SQL path), or
Expand All @@ -393,8 +389,8 @@ function describeEnvelopeShape(value: unknown): string {
* `SUM` / `AVG` / `MIN` / `MAX` over an empty input set return SQL
* `NULL`, which surfaces as `null` here. The outer `raw === null`
* fallback is defensive cover for an empty parent set; in single-query
* dispatch the LATERAL / correlated subquery always produces a row,
* so the inner envelope's `value` is always set by SQL.
* dispatch the correlated subquery always produces a row, so the inner
* envelope's `value` is always set by SQL.
*/
function decodeScalarIncludePayload(
include: IncludeExpr,
Expand Down
45 changes: 0 additions & 45 deletions packages/3-extensions/sql-orm-client/src/include-strategy.ts

This file was deleted.

Loading
Loading