import type { AnyMongoTypeMaps, ExtractMongoTypeMaps, InferModelRow, MongoContract, MongoContractWithTypeMaps, MongoModelsMap, } from '@prisma-next/mongo-contract'; import type { MongoAggAccumulator, MongoAggExpr } from '@prisma-next/mongo-query-ast/execution'; import type { ModelArrayField, ModelOriginBrand, ModelOriginBranded } from './resolve-path'; export interface DocField { readonly codecId: string; readonly nullable: boolean; } export type NumericField = { readonly codecId: 'mongo/double@1'; readonly nullable: false }; export type NullableNumericField = { readonly codecId: 'mongo/double@1'; readonly nullable: true }; export type StringField = { readonly codecId: 'mongo/string@1'; readonly nullable: false }; export type ArrayField = { readonly codecId: 'mongo/array@1'; readonly nullable: false }; export type BooleanField = { readonly codecId: 'mongo/bool@1'; readonly nullable: false }; export type DateField = { readonly codecId: 'mongo/date@1'; readonly nullable: false }; export type NullableDocField = { readonly codecId: string; readonly nullable: true }; export type LiteralValue = F extends StringField ? string : F extends NumericField ? number : F extends BooleanField ? boolean : F extends DateField ? Date : unknown; export type DocShape = Record; type ExtractCodecId = F extends { type: { kind: 'scalar'; codecId: infer C } } ? C : F extends { codecId: infer C extends string } ? C : string; export type ModelToDocShape< TContract extends MongoContract, ModelName extends string & keyof MongoModelsMap, > = { [K in keyof MongoModelsMap[ModelName]['fields'] & string]: { readonly codecId: ExtractCodecId[ModelName]['fields'][K]>; readonly nullable: MongoModelsMap[ModelName]['fields'][K]['nullable']; }; } & ModelOriginBranded; /** * Per-field resolver. Walks `Shape`'s string keys, routing * `ModelArrayField` (the lookup marker) through `InferModelRow` and * everything else through the codec-lookup branch. * * Internal helper — public callers should use `ResolveRow`, which adds * the model-origin brand detection on top. */ type ResolveFields< Shape extends DocShape, CodecTypes extends Record, TContract extends MongoContract, > = { -readonly [K in keyof Shape & string]: Shape[K] extends ModelArrayField ? IsConcreteContract extends true ? TContract extends MongoContractWithTypeMaps ? ModelName extends string & keyof MongoModelsMap ? Array> : unknown[] : unknown[] : unknown[] : Shape[K]['codecId'] extends keyof CodecTypes ? Shape[K]['nullable'] extends true ? CodecTypes[Shape[K]['codecId']]['output'] | null : CodecTypes[Shape[K]['codecId']]['output'] : unknown; }; /** * Resolve a `DocShape` to a concrete row object type. * * The optional `TContract` parameter exists so the resolver can: * * 1. Detect the `ModelOriginBrand` on `Shape` — the phantom symbol * placed by `ModelToDocShape`. When present (and the contract has * type maps), the row is resolved via `InferModelRow` from * `@prisma-next/mongo-contract`, which walks scalar / valueObject / * union field kinds (handling nested value-objects and `many: true`). * This makes entry-point reads (`q.from('users').build()`) and * shape-extending stages (`match`, `addFields`) resolve value-object * fields to their concrete nested types instead of `unknown`. * * 2. Detect the per-field `ModelArrayField` marker produced * by `lookup()` and resolve it to `Array>` so * lookup rows carry the same fully-typed foreign rows. * * When the contract is not threaded through (or lacks the type-map * phantom), both branches fall back to `unknown` / `unknown[]` — * preserving the legacy resolver shape for call sites that do not need * model-row resolution. */ /** * Flatten an intersection `A & B` into a single object literal so callers * (and `expectTypeOf().toEqualTypeOf<…>()`) see one homogeneous record * rather than the intersection form. Vitest's strict equality check * treats `A & B` as distinct from the structurally-equivalent flat * record, even when assignability is bidirectional, so the * `ResolveRow` brand-positive branch normalises its result through this. */ type Flatten = T extends infer U ? { [K in keyof U]: U[K] } : never; /** * Decide whether to route a brand-positive `ResolveRow` through * `InferModelRow`. The default `MongoContract` (no concrete models) * still satisfies `MongoContractWithTypeMaps` * because the phantom key is optional, but `InferModelRow` * collapses to an empty/unknown row. Gate on the presence of the * type-maps phantom: a concrete contract attaches concrete `TestTypeMaps`- * shaped maps, while the default `MongoContract` has no phantom and * `ExtractMongoTypeMaps` resolves to `never`. */ type IsConcreteContract = [ExtractMongoTypeMaps] extends [never] ? false : true; export type ResolveRow< Shape extends DocShape, CodecTypes extends Record, TContract extends MongoContract = MongoContract, > = Shape extends { readonly [ModelOriginBrand]?: infer ModelName extends string } ? IsConcreteContract extends true ? TContract extends MongoContractWithTypeMaps ? ModelName extends string & keyof MongoModelsMap ? Flatten< InferModelRow & Omit< ResolveFields, keyof InferModelRow > > : ResolveFields : ResolveFields : ResolveFields : ResolveFields; export interface TypedAggExpr { readonly _field: F; readonly node: MongoAggExpr; } export interface TypedAccumulatorExpr { readonly _field: F; readonly node: MongoAggAccumulator; } export type ExtractDocShape>> = { [K in keyof T & string]: T[K]['_field']; }; export type SortSpec = Partial>; export type ProjectedShape< Shape extends DocShape, Spec extends Record>, > = { [K in keyof Spec & string]: Spec[K] extends 1 ? K extends keyof Shape ? Shape[K] : DocField : Spec[K] extends TypedAggExpr ? F : DocField; } & ('_id' extends keyof Shape ? '_id' extends keyof Spec ? Record : Pick : Record); export type GroupSpec = { _id: TypedAggExpr | null; [key: string]: TypedAggExpr | TypedAccumulatorExpr | null; }; export type GroupedDocShape = { [K in keyof Spec & string]: Spec[K] extends TypedAggExpr ? F : Spec[K] extends TypedAccumulatorExpr ? F : Spec[K] extends null ? { readonly codecId: 'mongo/null@1'; readonly nullable: true } : DocField; }; /** * Intentionally identity — full array element type extraction is deferred. * Used by `UnwoundShape` so the unwind result shape can be refined later * without changing the public API. */ type UnwrapArrayDocField = F; /** * `$unwind` reshapes the array slot but leaves the rest of the document * structurally intact. The mapped iteration is keyed on `keyof S & string`, * which discards the symbol-keyed `ModelOriginBrand` carried by * model-rooted shapes. Preserve the brand explicitly so post-unwind * `ResolveRow` still routes through `InferModelRow` and value-object * fields keep their concrete nested types. */ export type UnwoundShape = { [P in keyof S & string]: P extends K ? UnwrapArrayDocField : S[P]; } & (S extends ModelOriginBranded ? ModelOriginBranded : unknown);