@prisma-next/mongo-contract-ts 0.0.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.
@@ -0,0 +1,1403 @@
1
+ import { computeProfileHash, computeStorageHash } from '@prisma-next/contract/hashing';
2
+ import type {
3
+ ContractEmbedRelation,
4
+ ContractField,
5
+ ContractFieldType,
6
+ ContractReferenceRelation,
7
+ ContractValueObject,
8
+ ProfileHashBase,
9
+ StorageHashBase,
10
+ } from '@prisma-next/contract/types';
11
+ import type {
12
+ ExtensionPackRef,
13
+ FamilyPackRef,
14
+ TargetPackRef,
15
+ } from '@prisma-next/framework-components/components';
16
+ import {
17
+ type MongoCollectionOptions,
18
+ type MongoContract,
19
+ type MongoContractWithTypeMaps,
20
+ type MongoIndex,
21
+ type MongoIndexFields,
22
+ type MongoIndexOptions,
23
+ type MongoStorage,
24
+ type MongoStorageCollection,
25
+ type MongoStorageCollectionOptions,
26
+ type MongoStorageIndex,
27
+ type MongoTypeMaps,
28
+ validateMongoContract,
29
+ } from '@prisma-next/mongo-contract';
30
+
31
+ type VariantSpec = {
32
+ readonly value: string;
33
+ };
34
+
35
+ type StorageRelationSpec = {
36
+ readonly field: string;
37
+ };
38
+
39
+ type ContractCapabilities = Record<string, Record<string, boolean>>;
40
+ type StringListInput = string | readonly string[];
41
+ type Present<T> = Exclude<T, undefined>;
42
+ type EmptyObject = Record<never, never>;
43
+ type Simplify<T> = { [K in keyof T]: T[K] } & EmptyObject;
44
+ type StrictShape<Actual, Shape> = Actual &
45
+ Shape &
46
+ Record<Exclude<keyof Actual, keyof Shape>, never>;
47
+
48
+ type UnionToIntersection<Union> = (Union extends unknown ? (value: Union) => void : never) extends (
49
+ value: infer Intersection,
50
+ ) => void
51
+ ? Intersection
52
+ : never;
53
+
54
+ export type ExtractCodecTypesFromPack<P> = P extends { __codecTypes?: infer CodecTypes }
55
+ ? CodecTypes extends Record<string, { output: unknown }>
56
+ ? CodecTypes
57
+ : Record<string, never>
58
+ : Record<string, never>;
59
+
60
+ // This mirrors @prisma-next/target-mongo/codec-types because authoring must stay decoupled from
61
+ // the target layer while still exposing the built-in Mongo codec registry to type inference.
62
+ type MongoCodecTypes = {
63
+ readonly 'mongo/objectId@1': { readonly input: string; readonly output: string };
64
+ readonly 'mongo/string@1': { readonly input: string; readonly output: string };
65
+ readonly 'mongo/double@1': { readonly input: number; readonly output: number };
66
+ readonly 'mongo/int32@1': { readonly input: number; readonly output: number };
67
+ readonly 'mongo/bool@1': { readonly input: boolean; readonly output: boolean };
68
+ readonly 'mongo/date@1': { readonly input: Date; readonly output: Date };
69
+ readonly 'mongo/vector@1': {
70
+ readonly input: readonly number[];
71
+ readonly output: readonly number[];
72
+ };
73
+ };
74
+
75
+ type MergeExtensionCodecTypes<Packs extends Record<string, unknown>> = UnionToIntersection<
76
+ {
77
+ [K in keyof Packs]: ExtractCodecTypesFromPack<Packs[K]>;
78
+ }[keyof Packs]
79
+ >;
80
+
81
+ type MergeExtensionCodecTypesSafe<Packs> =
82
+ Packs extends Record<string, unknown>
83
+ ? keyof Packs extends never
84
+ ? Record<string, never>
85
+ : MergeExtensionCodecTypes<Packs>
86
+ : Record<string, never>;
87
+
88
+ export interface FieldBuilder<
89
+ Type extends ContractFieldType = ContractFieldType,
90
+ Nullable extends boolean = boolean,
91
+ Many extends boolean = boolean,
92
+ > {
93
+ readonly __kind: 'field';
94
+ readonly __type: Type;
95
+ readonly __nullable: Nullable;
96
+ readonly __many: Many;
97
+ optional(): FieldBuilder<Type, true, Many>;
98
+ many(): FieldBuilder<Type, Nullable, true>;
99
+ }
100
+
101
+ export interface ValueObjectBuilder<
102
+ Name extends string = string,
103
+ Fields extends Record<string, FieldBuilder> = Record<string, FieldBuilder>,
104
+ > {
105
+ readonly __kind: 'valueObject';
106
+ readonly __name: Name;
107
+ readonly __fields: Fields;
108
+ }
109
+
110
+ export interface FieldReference<
111
+ ModelName extends string = string,
112
+ FieldName extends string = string,
113
+ > {
114
+ readonly __kind: 'fieldRef';
115
+ readonly modelName: ModelName;
116
+ readonly fieldName: FieldName;
117
+ }
118
+
119
+ export interface RelationOn<
120
+ LocalFields extends readonly string[] = readonly string[],
121
+ TargetFields extends readonly string[] = readonly string[],
122
+ > {
123
+ readonly localFields: LocalFields;
124
+ readonly targetFields: TargetFields;
125
+ }
126
+
127
+ export interface RelationBuilder<
128
+ To extends string = string,
129
+ Cardinality extends '1:1' | '1:N' | 'N:1' = '1:1' | '1:N' | 'N:1',
130
+ On extends RelationOn | undefined = RelationOn | undefined,
131
+ > {
132
+ readonly __kind: 'relation';
133
+ readonly __to: To;
134
+ readonly __cardinality: Cardinality;
135
+ readonly __on: On;
136
+ }
137
+
138
+ export interface ModelBuilder<
139
+ Name extends string = string,
140
+ Fields extends Record<string, FieldBuilder> = Record<string, FieldBuilder>,
141
+ Relations extends Record<string, RelationBuilder> = Record<string, RelationBuilder>,
142
+ Collection extends string | undefined = string | undefined,
143
+ Owner extends string | undefined = string | undefined,
144
+ Base extends string | undefined = string | undefined,
145
+ StorageRelations extends Record<string, StorageRelationSpec> | undefined =
146
+ | Record<string, StorageRelationSpec>
147
+ | undefined,
148
+ Discriminator extends { readonly field: string } | undefined =
149
+ | { readonly field: string }
150
+ | undefined,
151
+ Variants extends Record<string, VariantSpec> | undefined =
152
+ | Record<string, VariantSpec>
153
+ | undefined,
154
+ > {
155
+ readonly __kind: 'model';
156
+ readonly __name: Name;
157
+ readonly __fields: Fields;
158
+ readonly __relations: Relations;
159
+ readonly __indexes: readonly MongoIndex[] | undefined;
160
+ readonly __collectionOptions: MongoCollectionOptions | undefined;
161
+ readonly __collection: Collection;
162
+ readonly __owner: Owner;
163
+ readonly __base: Base;
164
+ readonly __storageRelations: StorageRelations;
165
+ readonly __discriminator: Discriminator;
166
+ readonly __variants: Variants;
167
+ ref<const FieldName extends keyof Fields & string>(
168
+ fieldName: FieldName,
169
+ ): FieldReference<Name, FieldName>;
170
+ }
171
+
172
+ type AnyFieldBuilder = FieldBuilder<ContractFieldType, boolean, boolean>;
173
+ type AnyReferenceRelationBuilder = RelationBuilder<string, '1:1' | '1:N' | 'N:1', RelationOn>;
174
+ type AnyEmbedRelationBuilder = RelationBuilder<string, '1:1' | '1:N', undefined>;
175
+ type AnyRelationBuilder = AnyReferenceRelationBuilder | AnyEmbedRelationBuilder;
176
+ type AnyFieldReference = FieldReference<string, string>;
177
+ type NamedValueObjectBuilder<
178
+ Name extends string = string,
179
+ Fields extends Record<string, AnyFieldBuilder> = Record<string, AnyFieldBuilder>,
180
+ > = ValueObjectBuilder<Name, Fields>;
181
+ type AnyValueObjectBuilder = NamedValueObjectBuilder;
182
+ type NamedModelBuilder<
183
+ Name extends string = string,
184
+ Fields extends Record<string, AnyFieldBuilder> = Record<string, AnyFieldBuilder>,
185
+ Relations extends Record<string, AnyRelationBuilder> = Record<string, AnyRelationBuilder>,
186
+ Collection extends string | undefined = string | undefined,
187
+ Owner extends string | undefined = string | undefined,
188
+ Base extends string | undefined = string | undefined,
189
+ StorageRelations extends Record<string, StorageRelationSpec> | undefined =
190
+ | Record<string, StorageRelationSpec>
191
+ | undefined,
192
+ Discriminator extends { readonly field: string } | undefined =
193
+ | { readonly field: string }
194
+ | undefined,
195
+ Variants extends Record<string, VariantSpec> | undefined =
196
+ | Record<string, VariantSpec>
197
+ | undefined,
198
+ > = ModelBuilder<
199
+ Name,
200
+ Fields,
201
+ Relations,
202
+ Collection,
203
+ Owner,
204
+ Base,
205
+ StorageRelations,
206
+ Discriminator,
207
+ Variants
208
+ >;
209
+ type AnyModelBuilder = NamedModelBuilder;
210
+
211
+ type ExtractFieldReferenceName<T> =
212
+ T extends FieldReference<string, infer FieldName extends string> ? FieldName : never;
213
+ type ExtractModelName<T> = T extends NamedModelBuilder<infer Name> ? Name : never;
214
+ type ExtractValueObjectName<T> = T extends NamedValueObjectBuilder<infer Name> ? Name : never;
215
+ type ExtractModelCollection<T> =
216
+ T extends NamedModelBuilder<
217
+ string,
218
+ Record<string, AnyFieldBuilder>,
219
+ Record<string, AnyRelationBuilder>,
220
+ infer Collection
221
+ >
222
+ ? Collection
223
+ : never;
224
+ type ExtractModelOwner<T> =
225
+ T extends NamedModelBuilder<
226
+ string,
227
+ Record<string, AnyFieldBuilder>,
228
+ Record<string, AnyRelationBuilder>,
229
+ string | undefined,
230
+ infer Owner
231
+ >
232
+ ? Owner
233
+ : never;
234
+ type ExtractModelBase<T> =
235
+ T extends NamedModelBuilder<
236
+ string,
237
+ Record<string, AnyFieldBuilder>,
238
+ Record<string, AnyRelationBuilder>,
239
+ string | undefined,
240
+ string | undefined,
241
+ infer Base
242
+ >
243
+ ? Base
244
+ : never;
245
+ type ExtractModelStorageRelations<T> =
246
+ T extends NamedModelBuilder<
247
+ string,
248
+ Record<string, AnyFieldBuilder>,
249
+ Record<string, AnyRelationBuilder>,
250
+ string | undefined,
251
+ string | undefined,
252
+ string | undefined,
253
+ infer StorageRelations
254
+ >
255
+ ? StorageRelations
256
+ : never;
257
+
258
+ type ModelStorageSection<T> =
259
+ ExtractModelCollection<T> extends string
260
+ ? { readonly collection: ExtractModelCollection<T> }
261
+ : EmptyObject;
262
+ type ModelStorageRelationsSection<T> =
263
+ ExtractModelStorageRelations<T> extends Record<string, StorageRelationSpec>
264
+ ? keyof ExtractModelStorageRelations<T> extends never
265
+ ? EmptyObject
266
+ : { readonly relations: ExtractModelStorageRelations<T> }
267
+ : EmptyObject;
268
+ type RootModelCollection<T> =
269
+ ExtractModelCollection<T> extends string
270
+ ? ExtractModelOwner<T> extends undefined
271
+ ? ExtractModelBase<T> extends undefined
272
+ ? ExtractModelCollection<T>
273
+ : never
274
+ : never
275
+ : never;
276
+ type RootModelName<T> = RootModelCollection<T> extends never ? never : ExtractModelName<T>;
277
+ type CollectionName<T> =
278
+ ExtractModelCollection<T> extends string ? ExtractModelCollection<T> : never;
279
+
280
+ type ModelNameInput = string | AnyModelBuilder;
281
+ type ValueObjectNameInput = string | AnyValueObjectBuilder;
282
+ type RelationTargetFieldsInput<TargetName extends string> =
283
+ | StringListInput
284
+ | FieldReference<TargetName, string>
285
+ | readonly FieldReference<TargetName, string>[];
286
+
287
+ type NormalizeModelName<T> = T extends string ? T : ExtractModelName<T>;
288
+
289
+ type NormalizeModelNameOrUndefined<T> = [T] extends [undefined]
290
+ ? undefined
291
+ : NormalizeModelName<Present<T>>;
292
+
293
+ type NormalizeValueObjectName<T> = T extends string ? T : ExtractValueObjectName<T>;
294
+
295
+ type NormalizeStringList<T> = T extends readonly string[]
296
+ ? T
297
+ : T extends string
298
+ ? readonly [T]
299
+ : readonly string[];
300
+
301
+ type NormalizeTargetFieldList<T> = T extends readonly AnyFieldReference[]
302
+ ? {
303
+ readonly [K in keyof T]: ExtractFieldReferenceName<T[K]>;
304
+ }
305
+ : T extends AnyFieldReference
306
+ ? readonly [ExtractFieldReferenceName<T>]
307
+ : NormalizeStringList<T>;
308
+
309
+ type ContractFieldFromBuilder<TBuilder> =
310
+ TBuilder extends FieldBuilder<
311
+ infer Type extends ContractFieldType,
312
+ infer Nullable extends boolean,
313
+ infer Many extends boolean
314
+ >
315
+ ? Simplify<
316
+ {
317
+ readonly type: Type;
318
+ readonly nullable: Nullable;
319
+ } & (Many extends true ? { readonly many: true } : EmptyObject)
320
+ >
321
+ : never;
322
+
323
+ type ContractFieldsFromRecord<Fields extends Record<string, AnyFieldBuilder>> = Simplify<{
324
+ readonly [K in keyof Fields]: ContractFieldFromBuilder<Fields[K]>;
325
+ }>;
326
+
327
+ type ContractValueObjectFromBuilder<TBuilder> =
328
+ TBuilder extends ValueObjectBuilder<string, infer Fields extends Record<string, AnyFieldBuilder>>
329
+ ? Simplify<{
330
+ readonly fields: ContractFieldsFromRecord<Fields>;
331
+ }>
332
+ : never;
333
+
334
+ type ContractValueObjectsFromRecord<ValueObjects extends Record<string, AnyValueObjectBuilder>> =
335
+ Simplify<{
336
+ readonly [K in keyof ValueObjects as ExtractValueObjectName<
337
+ ValueObjects[K]
338
+ >]: ContractValueObjectFromBuilder<ValueObjects[K]>;
339
+ }>;
340
+
341
+ type ContractRelationFromBuilder<TBuilder> =
342
+ TBuilder extends RelationBuilder<
343
+ infer To extends string,
344
+ infer Cardinality extends '1:1' | '1:N' | 'N:1',
345
+ infer On extends RelationOn | undefined
346
+ >
347
+ ? On extends RelationOn
348
+ ? {
349
+ readonly to: To;
350
+ readonly cardinality: Cardinality;
351
+ readonly on: On;
352
+ }
353
+ : {
354
+ readonly to: To;
355
+ readonly cardinality: Cardinality;
356
+ }
357
+ : never;
358
+
359
+ type ContractRelationsFromRecord<Relations extends Record<string, AnyRelationBuilder>> =
360
+ keyof Relations extends never
361
+ ? Record<string, never>
362
+ : Simplify<{
363
+ readonly [K in keyof Relations]: ContractRelationFromBuilder<Relations[K]>;
364
+ }>;
365
+
366
+ type ContractModelStorageFromBuilder<TBuilder> = ModelStorageSection<TBuilder> &
367
+ ModelStorageRelationsSection<TBuilder>;
368
+
369
+ type MaybeOwner<Owner> = [Owner] extends [undefined]
370
+ ? EmptyObject
371
+ : { readonly owner: Owner & string };
372
+ type MaybeBase<Base> = [Base] extends [undefined] ? EmptyObject : { readonly base: Base & string };
373
+ type MaybeDiscriminator<Discriminator> = [Discriminator] extends [undefined]
374
+ ? EmptyObject
375
+ : { readonly discriminator: Discriminator & { readonly field: string } };
376
+ type MaybeVariants<Variants> = [Variants] extends [undefined]
377
+ ? EmptyObject
378
+ : { readonly variants: Variants };
379
+
380
+ type ContractModelFromBuilder<TBuilder> =
381
+ TBuilder extends NamedModelBuilder<
382
+ string,
383
+ infer Fields extends Record<string, AnyFieldBuilder>,
384
+ infer Relations extends Record<string, AnyRelationBuilder>,
385
+ string | undefined,
386
+ infer Owner,
387
+ infer Base,
388
+ Record<string, StorageRelationSpec> | undefined,
389
+ infer Discriminator,
390
+ infer Variants
391
+ >
392
+ ? Simplify<
393
+ {
394
+ readonly fields: ContractFieldsFromRecord<Fields>;
395
+ readonly relations: ContractRelationsFromRecord<Relations>;
396
+ readonly storage: ContractModelStorageFromBuilder<TBuilder>;
397
+ } & MaybeOwner<Owner> &
398
+ MaybeBase<Base> &
399
+ MaybeDiscriminator<Discriminator> &
400
+ MaybeVariants<Variants>
401
+ >
402
+ : never;
403
+
404
+ type ContractModelsFromRecord<Models extends Record<string, AnyModelBuilder>> = Simplify<{
405
+ readonly [K in keyof Models as ExtractModelName<Models[K]>]: ContractModelFromBuilder<Models[K]>;
406
+ }>;
407
+
408
+ type DerivedRootModels<Models extends Record<string, AnyModelBuilder>> = Simplify<{
409
+ readonly [K in keyof Models as RootModelCollection<Models[K]>]: RootModelName<Models[K]>;
410
+ }>;
411
+
412
+ type StorageCollectionsFromModels<Models extends Record<string, AnyModelBuilder>> = Simplify<{
413
+ readonly [K in keyof Models as CollectionName<Models[K]>]: MongoStorageCollection;
414
+ }>;
415
+
416
+ type NormalizeRoots<Roots extends Record<string, ModelNameInput>> = Simplify<{
417
+ readonly [K in keyof Roots]: NormalizeModelName<Roots[K]>;
418
+ }>;
419
+
420
+ type DefinitionModels<Definition> = Definition extends {
421
+ readonly models?: infer Models extends Record<string, AnyModelBuilder>;
422
+ }
423
+ ? Models
424
+ : Record<never, never>;
425
+
426
+ type DefinitionValueObjects<Definition> = Definition extends {
427
+ readonly valueObjects?: infer ValueObjects extends Record<string, AnyValueObjectBuilder>;
428
+ }
429
+ ? ValueObjects
430
+ : Record<never, never>;
431
+
432
+ type DefinitionRoots<Definition> = Definition extends {
433
+ readonly roots?: infer Roots extends Record<string, ModelNameInput>;
434
+ }
435
+ ? NormalizeRoots<Roots>
436
+ : DerivedRootModels<DefinitionModels<Definition>>;
437
+
438
+ type DefinitionCapabilities<Definition> = Definition extends {
439
+ readonly capabilities?: infer Capabilities extends ContractCapabilities;
440
+ }
441
+ ? Capabilities
442
+ : Record<never, never>;
443
+
444
+ type DefinitionExtensionPacks<Definition> = Definition extends {
445
+ readonly extensionPacks?: infer ExtensionPacks extends Record<
446
+ string,
447
+ ExtensionPackRef<string, string>
448
+ >;
449
+ }
450
+ ? ExtensionPacks
451
+ : Record<never, never>;
452
+
453
+ type DefinitionFamilyId<Definition> = Definition extends {
454
+ readonly family: FamilyPackRef<infer FamilyId>;
455
+ }
456
+ ? FamilyId
457
+ : string;
458
+
459
+ type DefinitionTargetId<Definition> = Definition extends {
460
+ readonly target: TargetPackRef<string, infer TargetId>;
461
+ }
462
+ ? TargetId
463
+ : string;
464
+
465
+ type DefinitionStorage<Definition> = Simplify<
466
+ MongoStorage & {
467
+ readonly collections: StorageCollectionsFromModels<DefinitionModels<Definition>>;
468
+ readonly storageHash: StorageHashBase<string>;
469
+ }
470
+ >;
471
+
472
+ type MaybeValueObjectsSection<ValueObjects extends Record<string, AnyValueObjectBuilder>> =
473
+ keyof ValueObjects extends never
474
+ ? EmptyObject
475
+ : {
476
+ readonly valueObjects: ContractValueObjectsFromRecord<ValueObjects>;
477
+ };
478
+
479
+ type MongoContractBaseFromDefinition<Definition> = Simplify<
480
+ {
481
+ readonly target: DefinitionTargetId<Definition>;
482
+ readonly targetFamily: DefinitionFamilyId<Definition>;
483
+ readonly roots: DefinitionRoots<Definition>;
484
+ readonly models: ContractModelsFromRecord<DefinitionModels<Definition>>;
485
+ readonly storage: DefinitionStorage<Definition>;
486
+ readonly capabilities: DefinitionCapabilities<Definition>;
487
+ readonly extensionPacks: DefinitionExtensionPacks<Definition>;
488
+ readonly profileHash: ProfileHashBase<string>;
489
+ readonly meta: Record<string, never>;
490
+ } & MaybeValueObjectsSection<DefinitionValueObjects<Definition>>
491
+ >;
492
+
493
+ type CodecTypesFromDefinition<Definition> = MongoCodecTypes &
494
+ MergeExtensionCodecTypesSafe<DefinitionExtensionPacks<Definition>>;
495
+
496
+ export type MongoContractResult<Definition> = MongoContractWithTypeMaps<
497
+ MongoContractBaseFromDefinition<Definition>,
498
+ MongoTypeMaps<CodecTypesFromDefinition<Definition>>
499
+ >;
500
+
501
+ type ContractAuthoringHelpers = {
502
+ readonly field: typeof field;
503
+ readonly index: typeof index;
504
+ readonly model: typeof model;
505
+ readonly rel: typeof rel;
506
+ readonly valueObject: typeof valueObject;
507
+ };
508
+
509
+ export type ContractScaffold<
510
+ Family extends FamilyPackRef<string>,
511
+ Target extends TargetPackRef<string, string>,
512
+ ExtensionPacks extends Record<string, ExtensionPackRef<string, string>> | undefined = undefined,
513
+ Capabilities extends ContractCapabilities | undefined = undefined,
514
+ Roots extends Record<string, ModelNameInput> | undefined = undefined,
515
+ > = {
516
+ readonly family: Family;
517
+ readonly target: Target;
518
+ readonly extensionPacks?: ExtensionPacks;
519
+ readonly capabilities?: Capabilities;
520
+ readonly roots?: Roots;
521
+ };
522
+
523
+ export type ContractDefinition<
524
+ Family extends FamilyPackRef<string>,
525
+ Target extends TargetPackRef<string, string>,
526
+ Models extends Record<string, AnyModelBuilder> = Record<never, never>,
527
+ ValueObjects extends Record<string, AnyValueObjectBuilder> = Record<never, never>,
528
+ ExtensionPacks extends Record<string, ExtensionPackRef<string, string>> | undefined = undefined,
529
+ Capabilities extends ContractCapabilities | undefined = undefined,
530
+ Roots extends Record<string, ModelNameInput> | undefined = undefined,
531
+ > = ContractScaffold<Family, Target, ExtensionPacks, Capabilities, Roots> & {
532
+ readonly models?: Models;
533
+ readonly valueObjects?: ValueObjects;
534
+ };
535
+
536
+ export type ContractFactory<
537
+ Models extends Record<string, AnyModelBuilder> = Record<never, never>,
538
+ ValueObjects extends Record<string, AnyValueObjectBuilder> = Record<never, never>,
539
+ Roots extends Record<string, ModelNameInput> | undefined = undefined,
540
+ > = (helpers: ContractAuthoringHelpers) => {
541
+ readonly models?: Models;
542
+ readonly valueObjects?: ValueObjects;
543
+ readonly roots?: Roots;
544
+ };
545
+
546
+ type FieldBuilderSpec<
547
+ Type extends ContractFieldType,
548
+ Nullable extends boolean,
549
+ Many extends boolean,
550
+ > = {
551
+ readonly type: Type;
552
+ readonly nullable: Nullable;
553
+ readonly many: Many;
554
+ };
555
+
556
+ function createFieldBuilder<
557
+ Type extends ContractFieldType,
558
+ Nullable extends boolean,
559
+ Many extends boolean,
560
+ >(spec: FieldBuilderSpec<Type, Nullable, Many>): FieldBuilder<Type, Nullable, Many> {
561
+ return {
562
+ __kind: 'field',
563
+ __type: spec.type,
564
+ __nullable: spec.nullable,
565
+ __many: spec.many,
566
+ optional() {
567
+ return createFieldBuilder<Type, true, Many>({
568
+ type: spec.type,
569
+ nullable: true,
570
+ many: spec.many,
571
+ });
572
+ },
573
+ many() {
574
+ return createFieldBuilder<Type, Nullable, true>({
575
+ type: spec.type,
576
+ nullable: spec.nullable,
577
+ many: true,
578
+ });
579
+ },
580
+ };
581
+ }
582
+
583
+ function normalizeOptionalTypeParams(
584
+ typeParams: Record<string, unknown> | undefined,
585
+ ): { readonly typeParams: Record<string, unknown> } | Record<never, never> {
586
+ if (!typeParams) {
587
+ return {};
588
+ }
589
+
590
+ return { typeParams };
591
+ }
592
+
593
+ function createScalarFieldBuilder<
594
+ CodecId extends string,
595
+ TypeParams extends Record<string, unknown> | undefined = undefined,
596
+ >(
597
+ codecId: CodecId,
598
+ options?: { readonly typeParams?: TypeParams },
599
+ ): FieldBuilder<
600
+ {
601
+ readonly kind: 'scalar';
602
+ readonly codecId: CodecId;
603
+ } & ([TypeParams] extends [undefined] ? EmptyObject : { readonly typeParams: TypeParams }),
604
+ false,
605
+ false
606
+ > {
607
+ return createFieldBuilder({
608
+ type: {
609
+ kind: 'scalar',
610
+ codecId,
611
+ ...normalizeOptionalTypeParams(options?.typeParams),
612
+ } as {
613
+ readonly kind: 'scalar';
614
+ readonly codecId: CodecId;
615
+ } & ([TypeParams] extends [undefined] ? EmptyObject : { readonly typeParams: TypeParams }),
616
+ nullable: false,
617
+ many: false,
618
+ });
619
+ }
620
+
621
+ export const field = {
622
+ scalar: createScalarFieldBuilder,
623
+ objectId() {
624
+ return createScalarFieldBuilder('mongo/objectId@1');
625
+ },
626
+ string() {
627
+ return createScalarFieldBuilder('mongo/string@1');
628
+ },
629
+ double() {
630
+ return createScalarFieldBuilder('mongo/double@1');
631
+ },
632
+ int32() {
633
+ return createScalarFieldBuilder('mongo/int32@1');
634
+ },
635
+ bool() {
636
+ return createScalarFieldBuilder('mongo/bool@1');
637
+ },
638
+ date() {
639
+ return createScalarFieldBuilder('mongo/date@1');
640
+ },
641
+ vector<const TypeParams extends Record<string, unknown> | undefined = undefined>(options?: {
642
+ readonly typeParams?: TypeParams;
643
+ }) {
644
+ return createScalarFieldBuilder('mongo/vector@1', options);
645
+ },
646
+ valueObject<const ValueObject extends ValueObjectNameInput>(valueObjectName: ValueObject) {
647
+ return createFieldBuilder({
648
+ type: {
649
+ kind: 'valueObject',
650
+ name: resolveValueObjectName(valueObjectName),
651
+ } as {
652
+ readonly kind: 'valueObject';
653
+ readonly name: NormalizeValueObjectName<ValueObject>;
654
+ },
655
+ nullable: false,
656
+ many: false,
657
+ });
658
+ },
659
+ } as const;
660
+
661
+ export function index<const Fields extends MongoIndexFields>(
662
+ fields: Fields,
663
+ ): {
664
+ readonly fields: Fields;
665
+ };
666
+ export function index<const Fields extends MongoIndexFields, const Options>(
667
+ fields: Fields,
668
+ options: StrictShape<Options, MongoIndexOptions>,
669
+ ): {
670
+ readonly fields: Fields;
671
+ readonly options: Options & MongoIndexOptions;
672
+ };
673
+ export function index(
674
+ fields: MongoIndexFields,
675
+ options?: MongoIndexOptions,
676
+ ): {
677
+ readonly fields: MongoIndexFields;
678
+ readonly options?: MongoIndexOptions;
679
+ } {
680
+ return {
681
+ fields,
682
+ ...(options ? { options } : {}),
683
+ };
684
+ }
685
+
686
+ function createFieldReference<const ModelName extends string, const FieldName extends string>(
687
+ modelName: ModelName,
688
+ fieldName: FieldName,
689
+ ): FieldReference<ModelName, FieldName> {
690
+ return {
691
+ __kind: 'fieldRef',
692
+ modelName,
693
+ fieldName,
694
+ };
695
+ }
696
+
697
+ function isFieldReference(value: unknown): value is FieldReference<string, string> {
698
+ return (
699
+ typeof value === 'object' && value !== null && '__kind' in value && value.__kind === 'fieldRef'
700
+ );
701
+ }
702
+
703
+ function resolveModelName(value: ModelNameInput): string {
704
+ return typeof value === 'string' ? value : value.__name;
705
+ }
706
+
707
+ function resolveValueObjectName(value: ValueObjectNameInput): string {
708
+ return typeof value === 'string' ? value : value.__name;
709
+ }
710
+
711
+ function normalizeStringList(value: StringListInput): readonly string[] {
712
+ return typeof value === 'string' ? [value] : [...value];
713
+ }
714
+
715
+ function normalizeTargetField(
716
+ targetModelName: string,
717
+ value: string | FieldReference<string, string>,
718
+ ): string {
719
+ if (!isFieldReference(value)) {
720
+ return value;
721
+ }
722
+
723
+ if (value.modelName !== targetModelName) {
724
+ throw new Error(
725
+ `Relation target "${targetModelName}" cannot reference field "${value.modelName}.${value.fieldName}".`,
726
+ );
727
+ }
728
+
729
+ return value.fieldName;
730
+ }
731
+
732
+ function normalizeTargetFields(
733
+ targetModelName: string,
734
+ value: RelationTargetFieldsInput<string>,
735
+ ): readonly string[] {
736
+ if (typeof value === 'string') {
737
+ return [value];
738
+ }
739
+
740
+ if (isFieldReference(value)) {
741
+ return [normalizeTargetField(targetModelName, value)];
742
+ }
743
+
744
+ return value.map((entry) => normalizeTargetField(targetModelName, entry));
745
+ }
746
+
747
+ type ReferenceOptions<
748
+ Target extends ModelNameInput,
749
+ From extends StringListInput,
750
+ To extends RelationTargetFieldsInput<NormalizeModelName<Target>>,
751
+ > = {
752
+ readonly from: From;
753
+ readonly to: To;
754
+ };
755
+
756
+ type RelationOnFromOptions<
757
+ From extends StringListInput,
758
+ To extends RelationTargetFieldsInput<string>,
759
+ > = {
760
+ readonly localFields: NormalizeStringList<From>;
761
+ readonly targetFields: NormalizeTargetFieldList<To>;
762
+ };
763
+
764
+ function createRelationBuilder<
765
+ To extends string,
766
+ Cardinality extends '1:1' | '1:N' | 'N:1',
767
+ On extends RelationOn | undefined,
768
+ >(spec: {
769
+ readonly to: To;
770
+ readonly cardinality: Cardinality;
771
+ readonly on: On;
772
+ }): RelationBuilder<To, Cardinality, On> {
773
+ return {
774
+ __kind: 'relation',
775
+ __to: spec.to,
776
+ __cardinality: spec.cardinality,
777
+ __on: spec.on,
778
+ };
779
+ }
780
+
781
+ function createReferenceRelationBuilder<
782
+ Target extends ModelNameInput,
783
+ Cardinality extends '1:1' | '1:N' | 'N:1',
784
+ From extends StringListInput,
785
+ To extends RelationTargetFieldsInput<NormalizeModelName<Target>>,
786
+ >(
787
+ target: Target,
788
+ cardinality: Cardinality,
789
+ options: ReferenceOptions<Target, From, To>,
790
+ ): RelationBuilder<NormalizeModelName<Target>, Cardinality, RelationOnFromOptions<From, To>> {
791
+ const targetModelName = resolveModelName(target);
792
+
793
+ return createRelationBuilder({
794
+ to: targetModelName as NormalizeModelName<Target>,
795
+ cardinality,
796
+ on: {
797
+ localFields: normalizeStringList(options.from) as NormalizeStringList<From>,
798
+ targetFields: normalizeTargetFields(
799
+ targetModelName,
800
+ options.to,
801
+ ) as NormalizeTargetFieldList<To>,
802
+ },
803
+ });
804
+ }
805
+
806
+ function createEmbedRelationBuilder<
807
+ Target extends ModelNameInput,
808
+ Cardinality extends '1:1' | '1:N',
809
+ >(
810
+ target: Target,
811
+ cardinality: Cardinality,
812
+ ): RelationBuilder<NormalizeModelName<Target>, Cardinality, undefined> {
813
+ return createRelationBuilder({
814
+ to: resolveModelName(target) as NormalizeModelName<Target>,
815
+ cardinality,
816
+ on: undefined,
817
+ });
818
+ }
819
+
820
+ function hasOne<const Target extends ModelNameInput>(
821
+ target: Target,
822
+ ): RelationBuilder<NormalizeModelName<Target>, '1:1', undefined>;
823
+ function hasOne<
824
+ const Target extends ModelNameInput,
825
+ const From extends StringListInput,
826
+ const To extends RelationTargetFieldsInput<NormalizeModelName<Target>>,
827
+ >(
828
+ target: Target,
829
+ options: ReferenceOptions<Target, From, To>,
830
+ ): RelationBuilder<NormalizeModelName<Target>, '1:1', RelationOnFromOptions<From, To>>;
831
+ function hasOne(
832
+ target: ModelNameInput,
833
+ options?: ReferenceOptions<ModelNameInput, StringListInput, RelationTargetFieldsInput<string>>,
834
+ ) {
835
+ if (!options) {
836
+ return createEmbedRelationBuilder(target, '1:1');
837
+ }
838
+
839
+ return createReferenceRelationBuilder(target, '1:1', options);
840
+ }
841
+
842
+ function hasMany<const Target extends ModelNameInput>(
843
+ target: Target,
844
+ ): RelationBuilder<NormalizeModelName<Target>, '1:N', undefined>;
845
+ function hasMany<
846
+ const Target extends ModelNameInput,
847
+ const From extends StringListInput,
848
+ const To extends RelationTargetFieldsInput<NormalizeModelName<Target>>,
849
+ >(
850
+ target: Target,
851
+ options: ReferenceOptions<Target, From, To>,
852
+ ): RelationBuilder<NormalizeModelName<Target>, '1:N', RelationOnFromOptions<From, To>>;
853
+ function hasMany(
854
+ target: ModelNameInput,
855
+ options?: ReferenceOptions<ModelNameInput, StringListInput, RelationTargetFieldsInput<string>>,
856
+ ) {
857
+ if (!options) {
858
+ return createEmbedRelationBuilder(target, '1:N');
859
+ }
860
+
861
+ return createReferenceRelationBuilder(target, '1:N', options);
862
+ }
863
+
864
+ function belongsTo<
865
+ const Target extends ModelNameInput,
866
+ const From extends StringListInput,
867
+ const To extends RelationTargetFieldsInput<NormalizeModelName<Target>>,
868
+ >(
869
+ target: Target,
870
+ options: ReferenceOptions<Target, From, To>,
871
+ ): RelationBuilder<NormalizeModelName<Target>, 'N:1', RelationOnFromOptions<From, To>> {
872
+ return createReferenceRelationBuilder(target, 'N:1', options);
873
+ }
874
+
875
+ export const rel = {
876
+ belongsTo,
877
+ hasMany,
878
+ hasOne,
879
+ } as const;
880
+
881
+ type ValueObjectInput<Fields extends Record<string, AnyFieldBuilder>> = {
882
+ readonly fields: Fields;
883
+ };
884
+
885
+ export function valueObject<
886
+ const Name extends string,
887
+ const Fields extends Record<string, AnyFieldBuilder>,
888
+ >(name: Name, input: ValueObjectInput<Fields>): ValueObjectBuilder<Name, Fields> {
889
+ return {
890
+ __kind: 'valueObject',
891
+ __name: name,
892
+ __fields: input.fields,
893
+ };
894
+ }
895
+
896
+ type ModelDiscriminatorInput<Variants extends Record<string, VariantSpec>> = {
897
+ readonly field: string;
898
+ readonly variants: Variants;
899
+ };
900
+
901
+ type ModelInput<
902
+ Fields extends Record<string, AnyFieldBuilder>,
903
+ Relations extends Record<string, AnyRelationBuilder> | undefined,
904
+ Collection extends string | undefined,
905
+ Indexes extends readonly MongoIndex[] | undefined,
906
+ CollectionOptions,
907
+ Owner extends ModelNameInput | undefined,
908
+ Base extends ModelNameInput | undefined,
909
+ StorageRelations extends Record<string, StorageRelationSpec> | undefined,
910
+ Discriminator extends ModelDiscriminatorInput<Record<string, VariantSpec>> | undefined,
911
+ > = {
912
+ readonly collection?: Collection;
913
+ readonly indexes?: Indexes;
914
+ readonly collectionOptions?: StrictShape<CollectionOptions, MongoCollectionOptions>;
915
+ readonly storageRelations?: StorageRelations;
916
+ readonly fields: Fields;
917
+ readonly relations?: Relations;
918
+ readonly owner?: Owner;
919
+ readonly base?: Base;
920
+ readonly discriminator?: Discriminator;
921
+ };
922
+
923
+ export function model<
924
+ const Name extends string,
925
+ const Fields extends Record<string, AnyFieldBuilder>,
926
+ const Relations extends Record<string, AnyRelationBuilder> | undefined = undefined,
927
+ const Collection extends string | undefined = undefined,
928
+ const Indexes extends readonly MongoIndex[] | undefined = undefined,
929
+ const CollectionOptions = undefined,
930
+ const Owner extends ModelNameInput | undefined = undefined,
931
+ const Base extends ModelNameInput | undefined = undefined,
932
+ const StorageRelations extends Record<string, StorageRelationSpec> | undefined = undefined,
933
+ const Discriminator extends
934
+ | ModelDiscriminatorInput<Record<string, VariantSpec>>
935
+ | undefined = undefined,
936
+ >(
937
+ name: Name,
938
+ input: ModelInput<
939
+ Fields,
940
+ Relations,
941
+ Collection,
942
+ Indexes,
943
+ CollectionOptions,
944
+ Owner,
945
+ Base,
946
+ StorageRelations,
947
+ Discriminator
948
+ >,
949
+ ): ModelBuilder<
950
+ Name,
951
+ Fields,
952
+ Relations extends Record<string, AnyRelationBuilder> ? Relations : Record<never, never>,
953
+ Collection,
954
+ NormalizeModelNameOrUndefined<Owner>,
955
+ NormalizeModelNameOrUndefined<Base>,
956
+ StorageRelations,
957
+ Discriminator extends { readonly field: infer Field extends string }
958
+ ? { readonly field: Field }
959
+ : undefined,
960
+ Discriminator extends { readonly variants: infer Variants extends Record<string, VariantSpec> }
961
+ ? Variants
962
+ : undefined
963
+ > {
964
+ return {
965
+ __kind: 'model',
966
+ __name: name,
967
+ __fields: input.fields,
968
+ __relations: (input.relations ?? {}) as Relations extends Record<string, AnyRelationBuilder>
969
+ ? Relations
970
+ : Record<never, never>,
971
+ __indexes: input.indexes,
972
+ __collectionOptions: input.collectionOptions,
973
+ __collection: input.collection as Collection,
974
+ __owner: (input.owner
975
+ ? resolveModelName(input.owner)
976
+ : undefined) as NormalizeModelNameOrUndefined<Owner>,
977
+ __base: (input.base
978
+ ? resolveModelName(input.base)
979
+ : undefined) as NormalizeModelNameOrUndefined<Base>,
980
+ __storageRelations: input.storageRelations as StorageRelations,
981
+ __discriminator: (input.discriminator
982
+ ? { field: input.discriminator.field }
983
+ : undefined) as Discriminator extends { readonly field: infer Field extends string }
984
+ ? { readonly field: Field }
985
+ : undefined,
986
+ __variants: input.discriminator?.variants as Discriminator extends {
987
+ readonly variants: infer Variants extends Record<string, VariantSpec>;
988
+ }
989
+ ? Variants
990
+ : undefined,
991
+ ref(fieldName) {
992
+ return createFieldReference(name, fieldName);
993
+ },
994
+ };
995
+ }
996
+
997
+ function validateTargetPackRef(
998
+ family: FamilyPackRef<string>,
999
+ target: TargetPackRef<string, string>,
1000
+ ): void {
1001
+ if (family.familyId !== 'mongo') {
1002
+ throw new Error(
1003
+ `defineContract only accepts Mongo family packs. Received family "${family.familyId}".`,
1004
+ );
1005
+ }
1006
+
1007
+ if (target.familyId !== family.familyId) {
1008
+ throw new Error(
1009
+ `target pack "${target.id}" targets family "${target.familyId}" but contract family is "${family.familyId}".`,
1010
+ );
1011
+ }
1012
+ }
1013
+
1014
+ function validateExtensionPackRefs(
1015
+ target: TargetPackRef<string, string>,
1016
+ extensionPacks?: Record<string, ExtensionPackRef<string, string>>,
1017
+ ): void {
1018
+ if (!extensionPacks) {
1019
+ return;
1020
+ }
1021
+
1022
+ for (const packRef of Object.values(extensionPacks)) {
1023
+ if (packRef.kind !== 'extension') {
1024
+ throw new Error(
1025
+ `defineContract only accepts extension pack refs in extensionPacks. Received kind "${packRef.kind}".`,
1026
+ );
1027
+ }
1028
+
1029
+ if (packRef.familyId !== target.familyId) {
1030
+ throw new Error(
1031
+ `extension pack "${packRef.id}" targets family "${packRef.familyId}" but contract target family is "${target.familyId}".`,
1032
+ );
1033
+ }
1034
+
1035
+ if (packRef.targetId && packRef.targetId !== target.targetId) {
1036
+ throw new Error(
1037
+ `extension pack "${packRef.id}" targets "${packRef.targetId}" but contract target is "${target.targetId}".`,
1038
+ );
1039
+ }
1040
+ }
1041
+ }
1042
+
1043
+ function isContractScaffold(
1044
+ value: unknown,
1045
+ ): value is ContractScaffold<
1046
+ FamilyPackRef<string>,
1047
+ TargetPackRef<string, string>,
1048
+ Record<string, ExtensionPackRef<string, string>> | undefined,
1049
+ ContractCapabilities | undefined,
1050
+ Record<string, ModelNameInput> | undefined
1051
+ > {
1052
+ if (typeof value !== 'object' || value === null) {
1053
+ return false;
1054
+ }
1055
+
1056
+ return 'family' in value && 'target' in value;
1057
+ }
1058
+
1059
+ function buildContractField(builder: AnyFieldBuilder): ContractField {
1060
+ return builder.__many
1061
+ ? {
1062
+ type: builder.__type,
1063
+ nullable: builder.__nullable,
1064
+ many: true,
1065
+ }
1066
+ : {
1067
+ type: builder.__type,
1068
+ nullable: builder.__nullable,
1069
+ };
1070
+ }
1071
+
1072
+ function buildFields(fields: Record<string, AnyFieldBuilder>): Record<string, ContractField> {
1073
+ const builtFields: Record<string, ContractField> = {};
1074
+
1075
+ for (const [fieldName, fieldBuilder] of Object.entries(fields)) {
1076
+ builtFields[fieldName] = buildContractField(fieldBuilder);
1077
+ }
1078
+
1079
+ return builtFields;
1080
+ }
1081
+
1082
+ function buildRelation(
1083
+ relationBuilder: AnyRelationBuilder,
1084
+ ): ContractEmbedRelation | ContractReferenceRelation {
1085
+ return relationBuilder.__on
1086
+ ? {
1087
+ to: relationBuilder.__to,
1088
+ cardinality: relationBuilder.__cardinality,
1089
+ on: relationBuilder.__on,
1090
+ }
1091
+ : {
1092
+ to: relationBuilder.__to,
1093
+ cardinality: relationBuilder.__cardinality,
1094
+ };
1095
+ }
1096
+
1097
+ function buildRelations(
1098
+ relations: Record<string, AnyRelationBuilder>,
1099
+ ): Record<string, ContractEmbedRelation | ContractReferenceRelation> {
1100
+ const builtRelations: Record<string, ContractEmbedRelation | ContractReferenceRelation> = {};
1101
+
1102
+ for (const [relationName, relationBuilder] of Object.entries(relations)) {
1103
+ builtRelations[relationName] = buildRelation(relationBuilder);
1104
+ }
1105
+
1106
+ return builtRelations;
1107
+ }
1108
+
1109
+ function buildValueObjects(
1110
+ valueObjects: Record<string, AnyValueObjectBuilder> | undefined,
1111
+ ): Record<string, ContractValueObject> {
1112
+ const builtValueObjects: Record<string, ContractValueObject> = {};
1113
+
1114
+ for (const valueObjectBuilder of Object.values(valueObjects ?? {})) {
1115
+ if (valueObjectBuilder.__name in builtValueObjects) {
1116
+ throw new Error(
1117
+ `Duplicate value object name "${valueObjectBuilder.__name}" in defineContract().`,
1118
+ );
1119
+ }
1120
+
1121
+ builtValueObjects[valueObjectBuilder.__name] = {
1122
+ fields: buildFields(valueObjectBuilder.__fields),
1123
+ };
1124
+ }
1125
+
1126
+ return builtValueObjects;
1127
+ }
1128
+
1129
+ function buildModels(
1130
+ models: Record<string, AnyModelBuilder> | undefined,
1131
+ ): Record<string, MongoContract['models'][string]> {
1132
+ const builtModels: Record<string, MongoContract['models'][string]> = {};
1133
+
1134
+ for (const modelBuilder of Object.values(models ?? {})) {
1135
+ if (modelBuilder.__name in builtModels) {
1136
+ throw new Error(`Duplicate model name "${modelBuilder.__name}" in defineContract().`);
1137
+ }
1138
+
1139
+ const storage = {
1140
+ ...(modelBuilder.__collection ? { collection: modelBuilder.__collection } : {}),
1141
+ ...(modelBuilder.__storageRelations ? { relations: modelBuilder.__storageRelations } : {}),
1142
+ };
1143
+
1144
+ builtModels[modelBuilder.__name] = {
1145
+ fields: buildFields(modelBuilder.__fields),
1146
+ relations: buildRelations(modelBuilder.__relations),
1147
+ storage,
1148
+ ...(modelBuilder.__owner ? { owner: modelBuilder.__owner } : {}),
1149
+ ...(modelBuilder.__base ? { base: modelBuilder.__base } : {}),
1150
+ ...(modelBuilder.__discriminator ? { discriminator: modelBuilder.__discriminator } : {}),
1151
+ ...(modelBuilder.__variants ? { variants: modelBuilder.__variants } : {}),
1152
+ };
1153
+ }
1154
+
1155
+ return builtModels;
1156
+ }
1157
+
1158
+ function deriveRoots(models: Record<string, AnyModelBuilder> | undefined): Record<string, string> {
1159
+ const roots: Record<string, string> = {};
1160
+
1161
+ for (const modelBuilder of Object.values(models ?? {})) {
1162
+ if (!modelBuilder.__collection || modelBuilder.__owner || modelBuilder.__base) {
1163
+ continue;
1164
+ }
1165
+
1166
+ roots[modelBuilder.__collection] = modelBuilder.__name;
1167
+ }
1168
+
1169
+ return roots;
1170
+ }
1171
+
1172
+ function normalizeRoots(roots: Record<string, ModelNameInput> | undefined): Record<string, string> {
1173
+ const normalizedRoots: Record<string, string> = {};
1174
+
1175
+ for (const [rootName, rootValue] of Object.entries(roots ?? {})) {
1176
+ normalizedRoots[rootName] = resolveModelName(rootValue);
1177
+ }
1178
+
1179
+ return normalizedRoots;
1180
+ }
1181
+
1182
+ function stableStringify(value: unknown): string {
1183
+ if (Array.isArray(value)) {
1184
+ return `[${value.map(stableStringify).join(',')}]`;
1185
+ }
1186
+
1187
+ if (value && typeof value === 'object') {
1188
+ return `{${Object.entries(value as Record<string, unknown>)
1189
+ .sort(([left], [right]) => left.localeCompare(right))
1190
+ .map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`)
1191
+ .join(',')}}`;
1192
+ }
1193
+
1194
+ return JSON.stringify(value);
1195
+ }
1196
+
1197
+ function toStorageIndex(index: MongoIndex): MongoStorageIndex {
1198
+ const keys = Object.entries(index.fields).map(([field, direction]) => ({
1199
+ field,
1200
+ direction,
1201
+ }));
1202
+ const result: Record<string, unknown> = { keys };
1203
+ if (index.options) {
1204
+ for (const [key, value] of Object.entries(index.options)) {
1205
+ if (value !== undefined) {
1206
+ result[key] = value;
1207
+ }
1208
+ }
1209
+ }
1210
+ return result as unknown as MongoStorageIndex;
1211
+ }
1212
+
1213
+ function toStorageCollectionOptions(opts: MongoCollectionOptions): MongoStorageCollectionOptions {
1214
+ const result: Record<string, unknown> = {};
1215
+ if (opts.capped) {
1216
+ result['capped'] = { size: opts.size ?? 0, ...(opts.max != null ? { max: opts.max } : {}) };
1217
+ }
1218
+ if (opts.timeseries) result['timeseries'] = opts.timeseries;
1219
+ if (opts.collation) result['collation'] = opts.collation;
1220
+ if (opts.changeStreamPreAndPostImages)
1221
+ result['changeStreamPreAndPostImages'] = opts.changeStreamPreAndPostImages;
1222
+ if (opts.clusteredIndex) result['clusteredIndex'] = { name: opts.clusteredIndex.name };
1223
+ return result as unknown as MongoStorageCollectionOptions;
1224
+ }
1225
+
1226
+ function buildCollections(
1227
+ models: Record<string, AnyModelBuilder> | undefined,
1228
+ ): Record<string, MongoStorageCollection> {
1229
+ const collections: Record<string, MongoStorageCollection> = {};
1230
+ const declaredIndexOwners = new Map<string, string>();
1231
+
1232
+ for (const modelBuilder of Object.values(models ?? {})) {
1233
+ if (!modelBuilder.__collection) {
1234
+ if (modelBuilder.__indexes && modelBuilder.__indexes.length > 0) {
1235
+ throw new Error(
1236
+ `Model "${modelBuilder.__name}" defines indexes but has no collection to attach them to.`,
1237
+ );
1238
+ }
1239
+
1240
+ if (modelBuilder.__collectionOptions) {
1241
+ throw new Error(
1242
+ `Model "${modelBuilder.__name}" defines collectionOptions but has no collection to attach them to.`,
1243
+ );
1244
+ }
1245
+
1246
+ continue;
1247
+ }
1248
+
1249
+ const existingCollection = collections[modelBuilder.__collection] ?? {};
1250
+ const existingIndexes = existingCollection.indexes ?? [];
1251
+
1252
+ if (existingCollection.options && modelBuilder.__collectionOptions) {
1253
+ throw new Error(
1254
+ `Collection "${modelBuilder.__collection}" has collectionOptions declared by multiple models. Author collectionOptions on a single model per collection.`,
1255
+ );
1256
+ }
1257
+
1258
+ for (const collectionIndex of modelBuilder.__indexes ?? []) {
1259
+ const indexSignature = stableStringify(collectionIndex);
1260
+ const collectionIndexKey = `${modelBuilder.__collection}:${indexSignature}`;
1261
+ const firstOwner = declaredIndexOwners.get(collectionIndexKey);
1262
+ if (firstOwner) {
1263
+ throw new Error(
1264
+ `Collection "${modelBuilder.__collection}" defines duplicate index ${indexSignature}. First declared on model "${firstOwner}" and duplicated on model "${modelBuilder.__name}".`,
1265
+ );
1266
+ }
1267
+ declaredIndexOwners.set(collectionIndexKey, modelBuilder.__name);
1268
+ }
1269
+
1270
+ const storageIndexes = (modelBuilder.__indexes ?? []).map(toStorageIndex);
1271
+ const storageOptions = modelBuilder.__collectionOptions
1272
+ ? toStorageCollectionOptions(modelBuilder.__collectionOptions)
1273
+ : undefined;
1274
+
1275
+ collections[modelBuilder.__collection] =
1276
+ storageIndexes.length > 0
1277
+ ? {
1278
+ ...existingCollection,
1279
+ indexes: [...existingIndexes, ...storageIndexes],
1280
+ ...(storageOptions ? { options: storageOptions } : {}),
1281
+ }
1282
+ : storageOptions
1283
+ ? {
1284
+ ...existingCollection,
1285
+ options: storageOptions,
1286
+ }
1287
+ : existingCollection;
1288
+ }
1289
+
1290
+ return collections;
1291
+ }
1292
+
1293
+ function buildContractFromDefinition<
1294
+ const Definition extends ContractDefinition<
1295
+ FamilyPackRef<string>,
1296
+ TargetPackRef<string, string>,
1297
+ Record<string, AnyModelBuilder>,
1298
+ Record<string, AnyValueObjectBuilder>,
1299
+ Record<string, ExtensionPackRef<string, string>> | undefined,
1300
+ ContractCapabilities | undefined,
1301
+ Record<string, ModelNameInput> | undefined
1302
+ >,
1303
+ >(definition: Definition): MongoContractResult<Definition> {
1304
+ validateTargetPackRef(definition.family, definition.target);
1305
+ validateExtensionPackRefs(definition.target, definition.extensionPacks);
1306
+
1307
+ const builtModels = buildModels(definition.models);
1308
+ const builtValueObjects = buildValueObjects(definition.valueObjects);
1309
+ const roots = definition.roots
1310
+ ? normalizeRoots(definition.roots)
1311
+ : deriveRoots(definition.models);
1312
+ const capabilities = definition.capabilities ?? {};
1313
+ const collections = buildCollections(definition.models);
1314
+ const storageBody = {
1315
+ collections,
1316
+ };
1317
+
1318
+ const builtContract = {
1319
+ target: definition.target.targetId,
1320
+ targetFamily: definition.family.familyId,
1321
+ roots,
1322
+ models: builtModels,
1323
+ ...(Object.keys(builtValueObjects).length > 0 ? { valueObjects: builtValueObjects } : {}),
1324
+ storage: {
1325
+ ...storageBody,
1326
+ storageHash: computeStorageHash({
1327
+ target: definition.target.targetId,
1328
+ targetFamily: definition.family.familyId,
1329
+ storage: storageBody,
1330
+ }),
1331
+ },
1332
+ capabilities,
1333
+ extensionPacks: definition.extensionPacks ?? {},
1334
+ profileHash: computeProfileHash({
1335
+ target: definition.target.targetId,
1336
+ targetFamily: definition.family.familyId,
1337
+ capabilities,
1338
+ }),
1339
+ meta: {},
1340
+ } satisfies MongoContract;
1341
+
1342
+ validateMongoContract(builtContract);
1343
+
1344
+ return builtContract as MongoContractResult<Definition>;
1345
+ }
1346
+
1347
+ export function defineContract<
1348
+ const Definition extends ContractDefinition<
1349
+ FamilyPackRef<string>,
1350
+ TargetPackRef<string, string>,
1351
+ Record<string, AnyModelBuilder>,
1352
+ Record<string, AnyValueObjectBuilder>,
1353
+ Record<string, ExtensionPackRef<string, string>> | undefined,
1354
+ ContractCapabilities | undefined,
1355
+ Record<string, ModelNameInput> | undefined
1356
+ >,
1357
+ >(definition: Definition): MongoContractResult<Definition>;
1358
+ export function defineContract<
1359
+ const Definition extends ContractScaffold<
1360
+ FamilyPackRef<string>,
1361
+ TargetPackRef<string, string>,
1362
+ Record<string, ExtensionPackRef<string, string>> | undefined,
1363
+ ContractCapabilities | undefined,
1364
+ Record<string, ModelNameInput> | undefined
1365
+ >,
1366
+ const Built extends {
1367
+ readonly models?: Record<string, AnyModelBuilder>;
1368
+ readonly valueObjects?: Record<string, AnyValueObjectBuilder>;
1369
+ readonly roots?: Record<string, ModelNameInput>;
1370
+ },
1371
+ >(
1372
+ definition: Definition,
1373
+ factory: (_helpers: ContractAuthoringHelpers) => Built,
1374
+ ): MongoContractResult<Definition & Built>;
1375
+ export function defineContract(
1376
+ definition: ContractScaffold<
1377
+ FamilyPackRef<string>,
1378
+ TargetPackRef<string, string>,
1379
+ Record<string, ExtensionPackRef<string, string>> | undefined,
1380
+ ContractCapabilities | undefined,
1381
+ Record<string, ModelNameInput> | undefined
1382
+ >,
1383
+ factory?: ContractFactory<
1384
+ Record<string, AnyModelBuilder>,
1385
+ Record<string, AnyValueObjectBuilder>,
1386
+ Record<string, ModelNameInput> | undefined
1387
+ >,
1388
+ ) {
1389
+ if (!isContractScaffold(definition)) {
1390
+ throw new TypeError(
1391
+ 'defineContract expects a contract definition object. Define your contract with defineContract({ family, target, models, ... }).',
1392
+ );
1393
+ }
1394
+
1395
+ if (!factory) {
1396
+ return buildContractFromDefinition(definition);
1397
+ }
1398
+
1399
+ return buildContractFromDefinition({
1400
+ ...definition,
1401
+ ...factory({ field, index, model, rel, valueObject }),
1402
+ });
1403
+ }