@prisma-next/mongo-query-builder 0.5.0-dev.9 → 0.6.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -71,7 +71,6 @@ function writeMeta(storageHash: string): PlanMeta {
71
71
  target: 'mongo',
72
72
  storageHash,
73
73
  lane: 'mongo-query',
74
- paramDescriptors: [],
75
74
  };
76
75
  }
77
76
 
@@ -110,6 +109,7 @@ export class CollectionHandle<
110
109
  collection: ctx.collection,
111
110
  stages: [],
112
111
  storageHash: ctx.storageHash,
112
+ modelName: modelName as string,
113
113
  });
114
114
  this.#ctx = ctx;
115
115
  this.#modelName = modelName;
@@ -346,6 +346,7 @@ export class FilteredCollection<
346
346
  collection: ctx.collection,
347
347
  stages: [new MongoMatchStage(leading)],
348
348
  storageHash: ctx.storageHash,
349
+ modelName: modelName as string,
349
350
  });
350
351
  this.#ctx = ctx;
351
352
  this.#modelName = modelName;
@@ -545,7 +546,11 @@ export class FilteredCollection<
545
546
  ) => UpdaterResult,
546
547
  opts: { readonly upsert?: boolean; readonly returnDocument?: 'before' | 'after' } = {},
547
548
  ): MongoQueryPlan<
548
- ResolveRow<ModelToDocShape<TContract, ModelName>, ExtractMongoCodecTypes<TContract>> | null,
549
+ ResolveRow<
550
+ ModelToDocShape<TContract, ModelName>,
551
+ ExtractMongoCodecTypes<TContract>,
552
+ TContract
553
+ > | null,
549
554
  FindOneAndUpdateCommand
550
555
  > {
551
556
  const update = resolveUpdaterCallback<
@@ -572,7 +577,11 @@ export class FilteredCollection<
572
577
  * document via the row stream.
573
578
  */
574
579
  override findOneAndDelete(): MongoQueryPlan<
575
- ResolveRow<ModelToDocShape<TContract, ModelName>, ExtractMongoCodecTypes<TContract>> | null,
580
+ ResolveRow<
581
+ ModelToDocShape<TContract, ModelName>,
582
+ ExtractMongoCodecTypes<TContract>,
583
+ TContract
584
+ > | null,
576
585
  FindOneAndDeleteCommand
577
586
  > {
578
587
  const command = new FindOneAndDeleteCommand(this.#ctx.collection, this.#foldedFilter());
package/src/types.ts CHANGED
@@ -1,5 +1,12 @@
1
- import type { MongoContract } from '@prisma-next/mongo-contract';
1
+ import type {
2
+ ExtractMongoTypeMaps,
3
+ InferModelRow,
4
+ MongoContract,
5
+ MongoContractWithTypeMaps,
6
+ MongoTypeMaps,
7
+ } from '@prisma-next/mongo-contract';
2
8
  import type { MongoAggAccumulator, MongoAggExpr } from '@prisma-next/mongo-query-ast/execution';
9
+ import type { ModelArrayField, ModelOriginBrand, ModelOriginBranded } from './resolve-path';
3
10
 
4
11
  export interface DocField {
5
12
  readonly codecId: string;
@@ -40,19 +47,103 @@ export type ModelToDocShape<
40
47
  readonly codecId: ExtractCodecId<TContract['models'][ModelName]['fields'][K]>;
41
48
  readonly nullable: TContract['models'][ModelName]['fields'][K]['nullable'];
42
49
  };
43
- };
50
+ } & ModelOriginBranded<ModelName>;
44
51
 
45
- export type ResolveRow<
52
+ /**
53
+ * Per-field resolver. Walks `Shape`'s string keys, routing
54
+ * `ModelArrayField` (the lookup marker) through `InferModelRow` and
55
+ * everything else through the codec-lookup branch.
56
+ *
57
+ * Internal helper — public callers should use `ResolveRow`, which adds
58
+ * the model-origin brand detection on top.
59
+ */
60
+ type ResolveFields<
46
61
  Shape extends DocShape,
47
62
  CodecTypes extends Record<string, { readonly output: unknown }>,
63
+ TContract extends MongoContract,
48
64
  > = {
49
- -readonly [K in keyof Shape & string]: Shape[K]['codecId'] extends keyof CodecTypes
50
- ? Shape[K]['nullable'] extends true
51
- ? CodecTypes[Shape[K]['codecId']]['output'] | null
52
- : CodecTypes[Shape[K]['codecId']]['output']
53
- : unknown;
65
+ -readonly [K in keyof Shape & string]: Shape[K] extends ModelArrayField<infer ModelName>
66
+ ? IsConcreteContract<TContract> extends true
67
+ ? TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>
68
+ ? ModelName extends string & keyof TContract['models']
69
+ ? Array<InferModelRow<TContract, ModelName>>
70
+ : unknown[]
71
+ : unknown[]
72
+ : unknown[]
73
+ : Shape[K]['codecId'] extends keyof CodecTypes
74
+ ? Shape[K]['nullable'] extends true
75
+ ? CodecTypes[Shape[K]['codecId']]['output'] | null
76
+ : CodecTypes[Shape[K]['codecId']]['output']
77
+ : unknown;
54
78
  };
55
79
 
80
+ /**
81
+ * Resolve a `DocShape` to a concrete row object type.
82
+ *
83
+ * The optional `TContract` parameter exists so the resolver can:
84
+ *
85
+ * 1. Detect the `ModelOriginBrand` on `Shape` — the phantom symbol
86
+ * placed by `ModelToDocShape`. When present (and the contract has
87
+ * type maps), the row is resolved via `InferModelRow<TC, M>` from
88
+ * `@prisma-next/mongo-contract`, which walks scalar / valueObject /
89
+ * union field kinds (handling nested value-objects and `many: true`).
90
+ * This makes entry-point reads (`q.from('users').build()`) and
91
+ * shape-extending stages (`match`, `addFields`) resolve value-object
92
+ * fields to their concrete nested types instead of `unknown`.
93
+ *
94
+ * 2. Detect the per-field `ModelArrayField<ModelName>` marker produced
95
+ * by `lookup()` and resolve it to `Array<InferModelRow<TC, M>>` so
96
+ * lookup rows carry the same fully-typed foreign rows.
97
+ *
98
+ * When the contract is not threaded through (or lacks the type-map
99
+ * phantom), both branches fall back to `unknown` / `unknown[]` —
100
+ * preserving the legacy resolver shape for call sites that do not need
101
+ * model-row resolution.
102
+ */
103
+ /**
104
+ * Flatten an intersection `A & B` into a single object literal so callers
105
+ * (and `expectTypeOf().toEqualTypeOf<…>()`) see one homogeneous record
106
+ * rather than the intersection form. Vitest's strict equality check
107
+ * treats `A & B` as distinct from the structurally-equivalent flat
108
+ * record, even when assignability is bidirectional, so the
109
+ * `ResolveRow` brand-positive branch normalises its result through this.
110
+ */
111
+ type Flatten<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
112
+
113
+ /**
114
+ * Decide whether to route a brand-positive `ResolveRow` through
115
+ * `InferModelRow`. The default `MongoContract` (no concrete models)
116
+ * still satisfies `MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>`
117
+ * because the phantom key is optional, but `InferModelRow<MongoContract, …>`
118
+ * collapses to an empty/unknown row. Gate on the presence of the
119
+ * type-maps phantom: a concrete contract attaches concrete `TestTypeMaps`-
120
+ * shaped maps, while the default `MongoContract` has no phantom and
121
+ * `ExtractMongoTypeMaps` resolves to `never`.
122
+ */
123
+ type IsConcreteContract<TContract> = [ExtractMongoTypeMaps<TContract>] extends [never]
124
+ ? false
125
+ : true;
126
+
127
+ export type ResolveRow<
128
+ Shape extends DocShape,
129
+ CodecTypes extends Record<string, { readonly output: unknown }>,
130
+ TContract extends MongoContract = MongoContract,
131
+ > = Shape extends { readonly [ModelOriginBrand]?: infer ModelName extends string }
132
+ ? IsConcreteContract<TContract> extends true
133
+ ? TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>
134
+ ? ModelName extends string & keyof TContract['models']
135
+ ? Flatten<
136
+ InferModelRow<TContract, ModelName> &
137
+ Omit<
138
+ ResolveFields<Shape, CodecTypes, TContract>,
139
+ keyof InferModelRow<TContract, ModelName>
140
+ >
141
+ >
142
+ : ResolveFields<Shape, CodecTypes, TContract>
143
+ : ResolveFields<Shape, CodecTypes, TContract>
144
+ : ResolveFields<Shape, CodecTypes, TContract>
145
+ : ResolveFields<Shape, CodecTypes, TContract>;
146
+
56
147
  export interface TypedAggExpr<F extends DocField> {
57
148
  readonly _field: F;
58
149
  readonly node: MongoAggExpr;
@@ -108,6 +199,16 @@ export type GroupedDocShape<Spec extends GroupSpec> = {
108
199
  */
109
200
  type UnwrapArrayDocField<F extends DocField> = F;
110
201
 
202
+ /**
203
+ * `$unwind` reshapes the array slot but leaves the rest of the document
204
+ * structurally intact. The mapped iteration is keyed on `keyof S & string`,
205
+ * which discards the symbol-keyed `ModelOriginBrand` carried by
206
+ * model-rooted shapes. Preserve the brand explicitly so post-unwind
207
+ * `ResolveRow` still routes through `InferModelRow` and value-object
208
+ * fields keep their concrete nested types.
209
+ */
111
210
  export type UnwoundShape<S extends DocShape, K extends keyof S & string> = {
112
211
  [P in keyof S & string]: P extends K ? UnwrapArrayDocField<S[P]> : S[P];
113
- };
212
+ } & (S extends ModelOriginBranded<infer ModelName extends string>
213
+ ? ModelOriginBranded<ModelName>
214
+ : unknown);