@prisma-next/mongo-orm 0.16.0-dev.3 → 0.16.0-dev.30
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.
- package/dist/index.d.mts +4 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +54 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -15
- package/src/collection.ts +184 -87
package/src/collection.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
} from '@prisma-next/mongo-query-ast/execution';
|
|
33
33
|
import type { MongoValue } from '@prisma-next/mongo-value';
|
|
34
34
|
import { MongoParamRef } from '@prisma-next/mongo-value';
|
|
35
|
+
import { blindCast, castAs } from '@prisma-next/utils/casts';
|
|
35
36
|
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
36
37
|
import type { MongoIncludeExpr } from './collection-state';
|
|
37
38
|
import { emptyCollectionState, type MongoCollectionState } from './collection-state';
|
|
@@ -105,7 +106,7 @@ export interface MongoCollection<
|
|
|
105
106
|
data: ReadonlyArray<ResolvedCreateInput<TContract, ModelName, TVariant>>,
|
|
106
107
|
): AsyncIterableResult<IncludedRow<TContract, ModelName, TIncludes>>;
|
|
107
108
|
/** Inserts multiple documents and returns the number inserted. */
|
|
108
|
-
|
|
109
|
+
createAndCount(
|
|
109
110
|
data: ReadonlyArray<ResolvedCreateInput<TContract, ModelName, TVariant>>,
|
|
110
111
|
): Promise<number>;
|
|
111
112
|
/** Updates one matching document via `findOneAndUpdate`. Returns the updated document or `null`. Requires `.where()`. */
|
|
@@ -125,9 +126,9 @@ export interface MongoCollection<
|
|
|
125
126
|
callback: (u: FieldAccessor<TContract, ModelName>) => FieldOperation[],
|
|
126
127
|
): AsyncIterableResult<IncludedRow<TContract, ModelName, TIncludes>>;
|
|
127
128
|
/** Updates all matching documents and returns the number modified. Requires `.where()`. */
|
|
128
|
-
|
|
129
|
+
updateAndCount(data: Partial<DefaultModelRow<TContract, ModelName>>): Promise<number>;
|
|
129
130
|
/** Updates all matching documents using field operations and returns the number modified. Requires `.where()`. */
|
|
130
|
-
|
|
131
|
+
updateAndCount(
|
|
131
132
|
callback: (u: FieldAccessor<TContract, ModelName>) => FieldOperation[],
|
|
132
133
|
): Promise<number>;
|
|
133
134
|
/** Deletes one matching document via `findOneAndDelete`. Returns the deleted document or `null`. Requires `.where()`. */
|
|
@@ -135,7 +136,7 @@ export interface MongoCollection<
|
|
|
135
136
|
/** Non-atomic: reads matching docs then deletes them. Concurrent writes may cause stale results. Requires `.where()`. */
|
|
136
137
|
deleteAll(): AsyncIterableResult<IncludedRow<TContract, ModelName, TIncludes>>;
|
|
137
138
|
/** Deletes all matching documents and returns the number deleted. Requires `.where()`. */
|
|
138
|
-
|
|
139
|
+
deleteAndCount(): Promise<number>;
|
|
139
140
|
/**
|
|
140
141
|
* On insert: `update` fields are applied via `$set`, remaining `create` fields via `$setOnInsert`.
|
|
141
142
|
* This means `update` values take precedence over `create` for overlapping fields on insert.
|
|
@@ -156,6 +157,10 @@ function resolveCollectionName(model: MongoModelDefinition, modelName: string):
|
|
|
156
157
|
return model.storage.collection ?? modelName;
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
function isUnknownRecord(value: unknown): value is Record<string, unknown> {
|
|
161
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
162
|
+
}
|
|
163
|
+
|
|
159
164
|
class MongoCollectionImpl<
|
|
160
165
|
TContract extends MongoContractWithTypeMaps<MongoContract, AnyMongoTypeMaps>,
|
|
161
166
|
ModelName extends string & keyof MongoModelsMap<TContract>,
|
|
@@ -174,9 +179,10 @@ class MongoCollectionImpl<
|
|
|
174
179
|
this.#contract = contract;
|
|
175
180
|
this.#modelName = modelName;
|
|
176
181
|
this.#executor = executor;
|
|
177
|
-
const model =
|
|
178
|
-
|
|
179
|
-
|
|
182
|
+
const model = blindCast<
|
|
183
|
+
MongoModelDefinition,
|
|
184
|
+
'modelName is constrained to Mongo contract model keys but namespace lookup erases storage type'
|
|
185
|
+
>(domainModelsAtDefaultNamespace(contract.domain)[modelName]);
|
|
180
186
|
this.#collectionName = resolveCollectionName(model, modelName);
|
|
181
187
|
this.#state = emptyCollectionState();
|
|
182
188
|
}
|
|
@@ -184,29 +190,33 @@ class MongoCollectionImpl<
|
|
|
184
190
|
variant<V extends VariantNames<TContract, ModelName>>(
|
|
185
191
|
variantName: V,
|
|
186
192
|
): MongoCollection<TContract, ModelName, TIncludes, V> {
|
|
187
|
-
const model =
|
|
188
|
-
|
|
|
189
|
-
|
|
193
|
+
const model = blindCast<
|
|
194
|
+
MongoModelDefinition | undefined,
|
|
195
|
+
'Mongo contract model lookup preserves target storage metadata erased by the namespace helper'
|
|
196
|
+
>(domainModelsAtDefaultNamespace(this.#contract.domain)[this.#modelName]);
|
|
190
197
|
if (!model?.discriminator || !model.variants) {
|
|
191
198
|
// No polymorphism metadata on this model — return unchanged. Cast required
|
|
192
199
|
// because TS cannot verify TVariant (the current variant) is assignable to V.
|
|
193
|
-
return
|
|
200
|
+
return blindCast<
|
|
201
|
+
MongoCollection<TContract, ModelName, TIncludes, V>,
|
|
202
|
+
'no-op variant refinement preserves runtime state while changing only the type-level variant'
|
|
203
|
+
>(this);
|
|
194
204
|
}
|
|
195
205
|
|
|
196
|
-
const variantEntry = model.variants[variantName
|
|
206
|
+
const variantEntry = model.variants[variantName];
|
|
197
207
|
if (!variantEntry) {
|
|
198
208
|
// Unknown variant name at runtime — return unchanged. Same cast rationale.
|
|
199
|
-
return
|
|
209
|
+
return blindCast<
|
|
210
|
+
MongoCollection<TContract, ModelName, TIncludes, V>,
|
|
211
|
+
'unknown variant fallback preserves runtime state while changing only the type-level variant'
|
|
212
|
+
>(this);
|
|
200
213
|
}
|
|
201
214
|
|
|
202
215
|
const filter = MongoFieldFilter.eq(
|
|
203
216
|
model.discriminator.field,
|
|
204
217
|
new MongoParamRef(variantEntry.value),
|
|
205
218
|
);
|
|
206
|
-
return this.#cloneWithVariant<V>(
|
|
207
|
-
{ filters: [...this.#state.filters, filter] },
|
|
208
|
-
variantName as string,
|
|
209
|
-
);
|
|
219
|
+
return this.#cloneWithVariant<V>({ filters: [...this.#state.filters, filter] }, variantName);
|
|
210
220
|
}
|
|
211
221
|
|
|
212
222
|
where(
|
|
@@ -215,7 +225,12 @@ class MongoCollectionImpl<
|
|
|
215
225
|
if (isMongoFilterExpr(filter)) {
|
|
216
226
|
return this.#clone({ filters: [...this.#state.filters, filter] });
|
|
217
227
|
}
|
|
218
|
-
const compiled = this.#compileWhereObject(
|
|
228
|
+
const compiled = this.#compileWhereObject(
|
|
229
|
+
blindCast<
|
|
230
|
+
Record<string, unknown>,
|
|
231
|
+
'typed Mongo where input is a model-field value record after filter-expression narrowing'
|
|
232
|
+
>(filter),
|
|
233
|
+
);
|
|
219
234
|
return this.#clone({ filters: [...this.#state.filters, ...compiled] });
|
|
220
235
|
}
|
|
221
236
|
|
|
@@ -228,14 +243,15 @@ class MongoCollectionImpl<
|
|
|
228
243
|
include<K extends ReferenceRelationKeys<TContract, ModelName> & string>(
|
|
229
244
|
relationName: K,
|
|
230
245
|
): MongoCollection<TContract, ModelName, TIncludes & Record<K, true>, TVariant> {
|
|
231
|
-
const model =
|
|
232
|
-
|
|
233
|
-
|
|
246
|
+
const model = blindCast<
|
|
247
|
+
MongoModelDefinition,
|
|
248
|
+
'modelName is constrained to Mongo contract model keys but namespace lookup erases storage type'
|
|
249
|
+
>(domainModelsAtDefaultNamespace(this.#contract.domain)[this.#modelName]);
|
|
234
250
|
const relation = model.relations?.[relationName];
|
|
235
251
|
if (!relation) {
|
|
236
252
|
throw ormError(
|
|
237
253
|
'ORM.RELATION_UNKNOWN',
|
|
238
|
-
`Unknown relation "${relationName}" on model "${this.#modelName
|
|
254
|
+
`Unknown relation "${relationName}" on model "${this.#modelName}"`,
|
|
239
255
|
{ meta: { model: this.#modelName, relation: relationName } },
|
|
240
256
|
);
|
|
241
257
|
}
|
|
@@ -248,7 +264,7 @@ class MongoCollectionImpl<
|
|
|
248
264
|
);
|
|
249
265
|
}
|
|
250
266
|
|
|
251
|
-
const ref = relation
|
|
267
|
+
const ref: ContractReferenceRelation = relation;
|
|
252
268
|
const localField = ref.on.localFields[0];
|
|
253
269
|
const foreignField = ref.on.targetFields[0];
|
|
254
270
|
if (
|
|
@@ -265,9 +281,9 @@ class MongoCollectionImpl<
|
|
|
265
281
|
}
|
|
266
282
|
|
|
267
283
|
const targetModelName = ref.to.model;
|
|
268
|
-
const targetModel =
|
|
269
|
-
|
|
270
|
-
|
|
284
|
+
const targetModel = castAs<MongoModelDefinition | undefined>(
|
|
285
|
+
domainModelsAtDefaultNamespace(this.#contract.domain)[targetModelName],
|
|
286
|
+
);
|
|
271
287
|
if (!targetModel) {
|
|
272
288
|
throw new InternalError(
|
|
273
289
|
`Target model "${targetModelName}" not found for relation "${relationName}"`,
|
|
@@ -282,20 +298,20 @@ class MongoCollectionImpl<
|
|
|
282
298
|
cardinality: ref.cardinality,
|
|
283
299
|
};
|
|
284
300
|
|
|
285
|
-
return
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
301
|
+
return blindCast<
|
|
302
|
+
MongoCollection<TContract, ModelName, TIncludes & Record<K, true>, TVariant>,
|
|
303
|
+
'include clone state contains the appended relation but the generic include refinement is not inferred'
|
|
304
|
+
>(
|
|
305
|
+
this.#clone({
|
|
306
|
+
includes: [...this.#state.includes, includeExpr],
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
293
309
|
}
|
|
294
310
|
|
|
295
311
|
orderBy(
|
|
296
312
|
spec: Partial<Record<ModelFieldKeys<TContract, ModelName>, 1 | -1>>,
|
|
297
313
|
): MongoCollection<TContract, ModelName, TIncludes, TVariant> {
|
|
298
|
-
const merged = { ...this.#state.orderBy, ...
|
|
314
|
+
const merged: Readonly<Record<string, 1 | -1>> = { ...this.#state.orderBy, ...spec };
|
|
299
315
|
return this.#clone({ orderBy: merged });
|
|
300
316
|
}
|
|
301
317
|
|
|
@@ -325,17 +341,24 @@ class MongoCollectionImpl<
|
|
|
325
341
|
): Promise<IncludedRow<TContract, ModelName, TIncludes>> {
|
|
326
342
|
this.#rejectIncludes('create');
|
|
327
343
|
const normalized = this.#injectDiscriminator(
|
|
328
|
-
this.#stripUndefined(
|
|
344
|
+
this.#stripUndefined(
|
|
345
|
+
blindCast<
|
|
346
|
+
Record<string, unknown>,
|
|
347
|
+
'resolved Mongo create input is a model-field value record'
|
|
348
|
+
>(data),
|
|
349
|
+
),
|
|
329
350
|
);
|
|
330
351
|
const document = this.#toDocument(normalized);
|
|
331
352
|
const command = new InsertOneCommand(this.#collectionName, document);
|
|
332
353
|
const results = await this.#drainPlan(command);
|
|
333
|
-
const insertedId =
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
354
|
+
const insertedId = blindCast<
|
|
355
|
+
{ insertedId: unknown },
|
|
356
|
+
'InsertOneCommand runtime result exposes the server-assigned insertedId'
|
|
357
|
+
>(results[0]).insertedId;
|
|
358
|
+
return blindCast<
|
|
359
|
+
IncludedRow<TContract, ModelName, TIncludes>,
|
|
360
|
+
'created row combines resolved model input with the server-assigned _id'
|
|
361
|
+
>({ _id: insertedId, ...normalized });
|
|
339
362
|
}
|
|
340
363
|
|
|
341
364
|
createAll(
|
|
@@ -345,33 +368,52 @@ class MongoCollectionImpl<
|
|
|
345
368
|
const self = this;
|
|
346
369
|
async function* gen(): AsyncGenerator<IncludedRow<TContract, ModelName, TIncludes>> {
|
|
347
370
|
const normalizedRows = data.map((d) =>
|
|
348
|
-
self.#injectDiscriminator(
|
|
371
|
+
self.#injectDiscriminator(
|
|
372
|
+
self.#stripUndefined(
|
|
373
|
+
blindCast<
|
|
374
|
+
Record<string, unknown>,
|
|
375
|
+
'resolved Mongo create-all input is a model-field value record'
|
|
376
|
+
>(d),
|
|
377
|
+
),
|
|
378
|
+
),
|
|
349
379
|
);
|
|
350
380
|
const documents = normalizedRows.map((d) => self.#toDocument(d));
|
|
351
381
|
const command = new InsertManyCommand(self.#collectionName, documents);
|
|
352
382
|
const results = await self.#drainPlan(command);
|
|
353
|
-
const insertedIds =
|
|
383
|
+
const insertedIds = blindCast<
|
|
384
|
+
{ insertedIds: readonly unknown[] },
|
|
385
|
+
'InsertManyCommand runtime result exposes insertedIds in input order'
|
|
386
|
+
>(results[0]).insertedIds;
|
|
354
387
|
for (let i = 0; i < normalizedRows.length; i++) {
|
|
355
|
-
yield
|
|
356
|
-
TContract,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
>;
|
|
388
|
+
yield blindCast<
|
|
389
|
+
IncludedRow<TContract, ModelName, TIncludes>,
|
|
390
|
+
'created row combines resolved model input with its server-assigned _id'
|
|
391
|
+
>({ _id: insertedIds[i], ...normalizedRows[i] });
|
|
360
392
|
}
|
|
361
393
|
}
|
|
362
394
|
return new AsyncIterableResult(gen());
|
|
363
395
|
}
|
|
364
396
|
|
|
365
|
-
async
|
|
397
|
+
async createAndCount(
|
|
366
398
|
data: ReadonlyArray<ResolvedCreateInput<TContract, ModelName, TVariant>>,
|
|
367
399
|
): Promise<number> {
|
|
368
|
-
this.#rejectIncludes('
|
|
400
|
+
this.#rejectIncludes('createAndCount');
|
|
369
401
|
const documents = data.map((d) =>
|
|
370
|
-
this.#toDocument(
|
|
402
|
+
this.#toDocument(
|
|
403
|
+
this.#injectDiscriminator(
|
|
404
|
+
blindCast<
|
|
405
|
+
Record<string, unknown>,
|
|
406
|
+
'resolved Mongo create-and-count input is a model-field value record'
|
|
407
|
+
>(d),
|
|
408
|
+
),
|
|
409
|
+
),
|
|
371
410
|
);
|
|
372
411
|
const command = new InsertManyCommand(this.#collectionName, documents);
|
|
373
412
|
const results = await this.#drainPlan(command);
|
|
374
|
-
return
|
|
413
|
+
return blindCast<
|
|
414
|
+
{ insertedCount: number },
|
|
415
|
+
'InsertManyCommand runtime result exposes insertedCount'
|
|
416
|
+
>(results[0]).insertedCount;
|
|
375
417
|
}
|
|
376
418
|
|
|
377
419
|
async update(
|
|
@@ -386,7 +428,13 @@ class MongoCollectionImpl<
|
|
|
386
428
|
const updateDoc = this.#resolveUpdateDoc(dataOrCallback);
|
|
387
429
|
const command = new FindOneAndUpdateCommand(this.#collectionName, filter, updateDoc, false);
|
|
388
430
|
const results = await this.#drainPlan(command);
|
|
389
|
-
|
|
431
|
+
const result = results[0];
|
|
432
|
+
return result === undefined
|
|
433
|
+
? null
|
|
434
|
+
: blindCast<
|
|
435
|
+
IncludedRow<TContract, ModelName, TIncludes>,
|
|
436
|
+
'FindOneAndUpdateCommand plan has no resultShape; collection update exposes its raw driver document as IncludedRow'
|
|
437
|
+
>(result);
|
|
390
438
|
}
|
|
391
439
|
|
|
392
440
|
updateAll(
|
|
@@ -415,19 +463,22 @@ class MongoCollectionImpl<
|
|
|
415
463
|
return new AsyncIterableResult(gen());
|
|
416
464
|
}
|
|
417
465
|
|
|
418
|
-
async
|
|
466
|
+
async updateAndCount(
|
|
419
467
|
dataOrCallback:
|
|
420
468
|
| Partial<DefaultModelRow<TContract, ModelName>>
|
|
421
469
|
| ((u: FieldAccessor<TContract, ModelName>) => FieldOperation[]),
|
|
422
470
|
): Promise<number> {
|
|
423
|
-
this.#requireFilters('
|
|
424
|
-
this.#rejectWindowing('
|
|
425
|
-
this.#rejectIncludes('
|
|
471
|
+
this.#requireFilters('updateAndCount');
|
|
472
|
+
this.#rejectWindowing('updateAndCount');
|
|
473
|
+
this.#rejectIncludes('updateAndCount');
|
|
426
474
|
const filter = this.#mergeFilters();
|
|
427
475
|
const updateDoc = this.#resolveUpdateDoc(dataOrCallback);
|
|
428
476
|
const command = new UpdateManyCommand(this.#collectionName, filter, updateDoc);
|
|
429
477
|
const results = await this.#drainPlan(command);
|
|
430
|
-
return
|
|
478
|
+
return blindCast<
|
|
479
|
+
{ modifiedCount: number },
|
|
480
|
+
'UpdateManyCommand runtime result exposes modifiedCount'
|
|
481
|
+
>(results[0]).modifiedCount;
|
|
431
482
|
}
|
|
432
483
|
|
|
433
484
|
async delete(): Promise<IncludedRow<TContract, ModelName, TIncludes> | null> {
|
|
@@ -437,7 +488,13 @@ class MongoCollectionImpl<
|
|
|
437
488
|
const filter = this.#mergeFilters();
|
|
438
489
|
const command = new FindOneAndDeleteCommand(this.#collectionName, filter);
|
|
439
490
|
const results = await this.#drainPlan(command);
|
|
440
|
-
|
|
491
|
+
const result = results[0];
|
|
492
|
+
return result === undefined
|
|
493
|
+
? null
|
|
494
|
+
: blindCast<
|
|
495
|
+
IncludedRow<TContract, ModelName, TIncludes>,
|
|
496
|
+
'FindOneAndDeleteCommand plan has no resultShape; collection delete exposes its raw driver document as IncludedRow'
|
|
497
|
+
>(result);
|
|
441
498
|
}
|
|
442
499
|
|
|
443
500
|
deleteAll(): AsyncIterableResult<IncludedRow<TContract, ModelName, TIncludes>> {
|
|
@@ -457,14 +514,17 @@ class MongoCollectionImpl<
|
|
|
457
514
|
return new AsyncIterableResult(gen());
|
|
458
515
|
}
|
|
459
516
|
|
|
460
|
-
async
|
|
461
|
-
this.#requireFilters('
|
|
462
|
-
this.#rejectWindowing('
|
|
463
|
-
this.#rejectIncludes('
|
|
517
|
+
async deleteAndCount(): Promise<number> {
|
|
518
|
+
this.#requireFilters('deleteAndCount');
|
|
519
|
+
this.#rejectWindowing('deleteAndCount');
|
|
520
|
+
this.#rejectIncludes('deleteAndCount');
|
|
464
521
|
const filter = this.#mergeFilters();
|
|
465
522
|
const command = new DeleteManyCommand(this.#collectionName, filter);
|
|
466
523
|
const results = await this.#drainPlan(command);
|
|
467
|
-
return
|
|
524
|
+
return blindCast<
|
|
525
|
+
{ deletedCount: number },
|
|
526
|
+
'DeleteManyCommand runtime result exposes deletedCount'
|
|
527
|
+
>(results[0]).deletedCount;
|
|
468
528
|
}
|
|
469
529
|
|
|
470
530
|
async upsert(input: {
|
|
@@ -479,7 +539,12 @@ class MongoCollectionImpl<
|
|
|
479
539
|
const filter = this.#mergeFilters();
|
|
480
540
|
|
|
481
541
|
const allCreateFields = this.#toDocument(
|
|
482
|
-
this.#injectDiscriminator(
|
|
542
|
+
this.#injectDiscriminator(
|
|
543
|
+
blindCast<
|
|
544
|
+
Record<string, unknown>,
|
|
545
|
+
'resolved Mongo upsert create input is a model-field value record'
|
|
546
|
+
>(input.create),
|
|
547
|
+
),
|
|
483
548
|
);
|
|
484
549
|
|
|
485
550
|
let updateDoc: Record<string, Record<string, MongoValue>>;
|
|
@@ -506,7 +571,12 @@ class MongoCollectionImpl<
|
|
|
506
571
|
this.#wrapFieldOpValue(field, value, operator),
|
|
507
572
|
);
|
|
508
573
|
} else {
|
|
509
|
-
const setFields = this.#toSetFields(
|
|
574
|
+
const setFields = this.#toSetFields(
|
|
575
|
+
blindCast<
|
|
576
|
+
Record<string, unknown>,
|
|
577
|
+
'resolved Mongo upsert update input is a partial model-field value record'
|
|
578
|
+
>(input.update),
|
|
579
|
+
);
|
|
510
580
|
updateDoc = {};
|
|
511
581
|
if (Object.keys(setFields).length > 0) {
|
|
512
582
|
updateDoc['$set'] = setFields;
|
|
@@ -531,7 +601,10 @@ class MongoCollectionImpl<
|
|
|
531
601
|
|
|
532
602
|
const command = new FindOneAndUpdateCommand(this.#collectionName, filter, updateDoc, true);
|
|
533
603
|
const results = await this.#drainPlan(command);
|
|
534
|
-
return
|
|
604
|
+
return blindCast<
|
|
605
|
+
IncludedRow<TContract, ModelName, TIncludes>,
|
|
606
|
+
'FindOneAndUpdateCommand upsert plan has no resultShape; collection upsert exposes its raw driver document as IncludedRow'
|
|
607
|
+
>(results[0]);
|
|
535
608
|
}
|
|
536
609
|
|
|
537
610
|
async #readMatchingIds(): Promise<unknown[]> {
|
|
@@ -551,7 +624,11 @@ class MongoCollectionImpl<
|
|
|
551
624
|
// re-read flow depends on it.
|
|
552
625
|
const { resultShape: _rs, ...planWithoutShape } = idQuery.#compile();
|
|
553
626
|
for await (const row of this.#executor.execute(planWithoutShape)) {
|
|
554
|
-
|
|
627
|
+
const storageRow = blindCast<
|
|
628
|
+
Record<string, unknown>,
|
|
629
|
+
'Mongo id-prefetch plan without resultShape yields a raw storage row containing _id'
|
|
630
|
+
>(row);
|
|
631
|
+
ids.push(storageRow['_id']);
|
|
555
632
|
}
|
|
556
633
|
return ids;
|
|
557
634
|
}
|
|
@@ -562,9 +639,10 @@ class MongoCollectionImpl<
|
|
|
562
639
|
}
|
|
563
640
|
|
|
564
641
|
#compile(): MongoQueryPlan<IncludedRow<TContract, ModelName, TIncludes>> {
|
|
565
|
-
const model =
|
|
566
|
-
|
|
|
567
|
-
|
|
642
|
+
const model = blindCast<
|
|
643
|
+
MongoModelDefinition | undefined,
|
|
644
|
+
'Mongo contract model lookup preserves target storage metadata erased by the namespace helper'
|
|
645
|
+
>(domainModelsAtDefaultNamespace(this.#contract.domain)[this.#modelName]);
|
|
568
646
|
if (!model) {
|
|
569
647
|
throw ormError('ORM.MODEL_UNKNOWN', `Unknown model: "${this.#modelName}".`, {
|
|
570
648
|
meta: { model: this.#modelName },
|
|
@@ -578,7 +656,7 @@ class MongoCollectionImpl<
|
|
|
578
656
|
);
|
|
579
657
|
}
|
|
580
658
|
|
|
581
|
-
#wrapCommand(command: AnyMongoCommand): MongoQueryPlan {
|
|
659
|
+
#wrapCommand(command: AnyMongoCommand): MongoQueryPlan<unknown> {
|
|
582
660
|
return { collection: this.#collectionName, command, meta: this.#planMeta() };
|
|
583
661
|
}
|
|
584
662
|
|
|
@@ -593,9 +671,10 @@ class MongoCollectionImpl<
|
|
|
593
671
|
}
|
|
594
672
|
|
|
595
673
|
#modelFields(): Record<string, ContractField> {
|
|
596
|
-
const model =
|
|
597
|
-
|
|
|
598
|
-
|
|
674
|
+
const model = blindCast<
|
|
675
|
+
MongoModelDefinition | undefined,
|
|
676
|
+
'Mongo contract model lookup preserves target storage metadata erased by the namespace helper'
|
|
677
|
+
>(domainModelsAtDefaultNamespace(this.#contract.domain)[this.#modelName]);
|
|
599
678
|
return model?.fields ?? {};
|
|
600
679
|
}
|
|
601
680
|
|
|
@@ -624,10 +703,22 @@ class MongoCollectionImpl<
|
|
|
624
703
|
|
|
625
704
|
if (field.many && Array.isArray(value)) {
|
|
626
705
|
return value.map((item) =>
|
|
627
|
-
this.#wrapValueObject(
|
|
628
|
-
|
|
706
|
+
this.#wrapValueObject(
|
|
707
|
+
blindCast<
|
|
708
|
+
Record<string, unknown>,
|
|
709
|
+
'contract-typed value-object array elements are field-value records'
|
|
710
|
+
>(item),
|
|
711
|
+
voDef,
|
|
712
|
+
),
|
|
713
|
+
);
|
|
629
714
|
}
|
|
630
|
-
return this.#wrapValueObject(
|
|
715
|
+
return this.#wrapValueObject(
|
|
716
|
+
blindCast<
|
|
717
|
+
Record<string, unknown>,
|
|
718
|
+
'contract-typed value-object input is a field-value record'
|
|
719
|
+
>(value),
|
|
720
|
+
voDef,
|
|
721
|
+
);
|
|
631
722
|
}
|
|
632
723
|
|
|
633
724
|
return new MongoParamRef(value);
|
|
@@ -708,7 +799,12 @@ class MongoCollectionImpl<
|
|
|
708
799
|
this.#wrapFieldOpValue(field, value, operator),
|
|
709
800
|
);
|
|
710
801
|
}
|
|
711
|
-
return this.#toUpdateDocument(
|
|
802
|
+
return this.#toUpdateDocument(
|
|
803
|
+
blindCast<
|
|
804
|
+
Record<string, unknown>,
|
|
805
|
+
'partial Mongo update input is a model-field value record after callback narrowing'
|
|
806
|
+
>(dataOrCallback),
|
|
807
|
+
);
|
|
712
808
|
}
|
|
713
809
|
|
|
714
810
|
#wrapFieldOpValue(field: string, value: MongoValue, operator?: string): MongoValue {
|
|
@@ -729,11 +825,11 @@ class MongoCollectionImpl<
|
|
|
729
825
|
|
|
730
826
|
if (contractField.type.kind === 'valueObject' && value instanceof MongoParamRef) {
|
|
731
827
|
const raw = value.value;
|
|
732
|
-
if (
|
|
828
|
+
if (isUnknownRecord(raw)) {
|
|
733
829
|
const voName = contractField.type.name;
|
|
734
830
|
const voDef = domainValueObjectsAtDefaultNamespace(this.#contract.domain)?.[voName];
|
|
735
831
|
if (voDef) {
|
|
736
|
-
return this.#wrapValueObject(raw
|
|
832
|
+
return this.#wrapValueObject(raw, voDef);
|
|
737
833
|
}
|
|
738
834
|
}
|
|
739
835
|
}
|
|
@@ -761,11 +857,11 @@ class MongoCollectionImpl<
|
|
|
761
857
|
|
|
762
858
|
if (currentField?.type.kind === 'valueObject' && value instanceof MongoParamRef) {
|
|
763
859
|
const raw = value.value;
|
|
764
|
-
if (
|
|
860
|
+
if (isUnknownRecord(raw)) {
|
|
765
861
|
const voName = currentField.type.name;
|
|
766
862
|
const voDef = domainValueObjectsAtDefaultNamespace(this.#contract.domain)?.[voName];
|
|
767
863
|
if (voDef) {
|
|
768
|
-
return this.#wrapValueObject(raw
|
|
864
|
+
return this.#wrapValueObject(raw, voDef);
|
|
769
865
|
}
|
|
770
866
|
}
|
|
771
867
|
}
|
|
@@ -825,9 +921,10 @@ class MongoCollectionImpl<
|
|
|
825
921
|
|
|
826
922
|
#injectDiscriminator(data: Record<string, unknown>): Record<string, unknown> {
|
|
827
923
|
if (!this.#variantName) return data;
|
|
828
|
-
const model =
|
|
829
|
-
|
|
|
830
|
-
|
|
924
|
+
const model = blindCast<
|
|
925
|
+
MongoModelDefinition | undefined,
|
|
926
|
+
'Mongo contract model lookup preserves target storage metadata erased by the namespace helper'
|
|
927
|
+
>(domainModelsAtDefaultNamespace(this.#contract.domain)[this.#modelName]);
|
|
831
928
|
if (!model?.discriminator || !model.variants) return data;
|
|
832
929
|
const variantEntry = model.variants[this.#variantName];
|
|
833
930
|
if (!variantEntry) return data;
|