@prisma-next/sql-contract-ts 0.3.0-dev.133 → 0.3.0-dev.135

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,1490 @@
1
+ import type {
2
+ AuthoringFieldPresetDescriptor,
3
+ ExtensionPackRef,
4
+ FamilyPackRef,
5
+ TargetPackRef,
6
+ } from '@prisma-next/contract/framework-components';
7
+ import { instantiateAuthoringFieldPreset } from '@prisma-next/contract/framework-components';
8
+ import type {
9
+ ColumnDefault,
10
+ ColumnDefaultLiteralInputValue,
11
+ ExecutionMutationDefaultValue,
12
+ } from '@prisma-next/contract/types';
13
+ import type {
14
+ ColumnTypeDescriptor,
15
+ ForeignKeyDefaultsState,
16
+ } from '@prisma-next/contract-authoring';
17
+ import type { StorageTypeInstance } from '@prisma-next/sql-contract/types';
18
+ import { ifDefined } from '@prisma-next/utils/defined';
19
+ import type { NamedConstraintSpec } from './authoring-type-utils';
20
+
21
+ export type NamingStrategy = 'identity' | 'snake_case';
22
+
23
+ export type NamingConfig = {
24
+ readonly tables?: NamingStrategy;
25
+ readonly columns?: NamingStrategy;
26
+ };
27
+
28
+ type NamedStorageTypeRef = string | StorageTypeInstance;
29
+
30
+ type NamedConstraintNameSpec<Name extends string = string> = {
31
+ readonly name: Name;
32
+ };
33
+
34
+ export type ScalarFieldState<
35
+ CodecId extends string = string,
36
+ TypeRef extends NamedStorageTypeRef | undefined = undefined,
37
+ Nullable extends boolean = boolean,
38
+ ColumnName extends string | undefined = string | undefined,
39
+ IdSpec extends NamedConstraintSpec | undefined = undefined,
40
+ UniqueSpec extends NamedConstraintSpec | undefined = undefined,
41
+ > = {
42
+ readonly kind: 'scalar';
43
+ readonly descriptor?: (ColumnTypeDescriptor & { readonly codecId: CodecId }) | undefined;
44
+ readonly typeRef?: TypeRef | undefined;
45
+ readonly nullable: Nullable;
46
+ readonly columnName?: ColumnName | undefined;
47
+ readonly default?: ColumnDefault | undefined;
48
+ readonly executionDefault?: ExecutionMutationDefaultValue | undefined;
49
+ } & (IdSpec extends NamedConstraintSpec ? { readonly id: IdSpec } : { readonly id?: undefined }) &
50
+ (UniqueSpec extends NamedConstraintSpec
51
+ ? { readonly unique: UniqueSpec }
52
+ : { readonly unique?: undefined });
53
+
54
+ type AnyScalarFieldState = {
55
+ readonly kind: 'scalar';
56
+ readonly descriptor?: (ColumnTypeDescriptor & { readonly codecId: string }) | undefined;
57
+ readonly typeRef?: NamedStorageTypeRef | undefined;
58
+ readonly nullable: boolean;
59
+ readonly columnName?: string | undefined;
60
+ readonly default?: ColumnDefault | undefined;
61
+ readonly executionDefault?: ExecutionMutationDefaultValue | undefined;
62
+ readonly id?: NamedConstraintSpec | undefined;
63
+ readonly unique?: NamedConstraintSpec | undefined;
64
+ };
65
+
66
+ type HasNamedConstraintId<State extends AnyScalarFieldState> =
67
+ State extends ScalarFieldState<
68
+ string,
69
+ NamedStorageTypeRef | undefined,
70
+ boolean,
71
+ string | undefined,
72
+ infer IdSpec,
73
+ NamedConstraintSpec | undefined
74
+ >
75
+ ? IdSpec extends NamedConstraintSpec
76
+ ? true
77
+ : false
78
+ : false;
79
+
80
+ type HasNamedConstraintUnique<State extends AnyScalarFieldState> =
81
+ State extends ScalarFieldState<
82
+ string,
83
+ NamedStorageTypeRef | undefined,
84
+ boolean,
85
+ string | undefined,
86
+ NamedConstraintSpec | undefined,
87
+ infer UniqueSpec
88
+ >
89
+ ? UniqueSpec extends NamedConstraintSpec
90
+ ? true
91
+ : false
92
+ : false;
93
+
94
+ type FieldSqlSpecForState<State extends AnyScalarFieldState> = {
95
+ readonly column?: string;
96
+ } & (HasNamedConstraintId<State> extends true
97
+ ? { readonly id?: NamedConstraintNameSpec }
98
+ : Record<never, never>) &
99
+ (HasNamedConstraintUnique<State> extends true
100
+ ? { readonly unique?: NamedConstraintNameSpec }
101
+ : Record<never, never>);
102
+
103
+ type ApplyFieldSqlSpec<
104
+ State extends AnyScalarFieldState,
105
+ Spec extends FieldSqlSpecForState<State>,
106
+ > = State extends ScalarFieldState<
107
+ infer CodecId,
108
+ infer TypeRef,
109
+ infer Nullable,
110
+ infer ColumnName,
111
+ infer IdSpec,
112
+ infer UniqueSpec
113
+ >
114
+ ? ScalarFieldState<
115
+ CodecId,
116
+ TypeRef,
117
+ Nullable,
118
+ Spec extends { readonly column: infer NextColumn extends string } ? NextColumn : ColumnName,
119
+ Spec extends { readonly id: { readonly name: infer IdName extends string } }
120
+ ? IdSpec extends NamedConstraintSpec
121
+ ? NamedConstraintSpec<IdName>
122
+ : IdSpec
123
+ : IdSpec,
124
+ Spec extends { readonly unique: { readonly name: infer UniqueName extends string } }
125
+ ? UniqueSpec extends NamedConstraintSpec
126
+ ? NamedConstraintSpec<UniqueName>
127
+ : UniqueSpec
128
+ : UniqueSpec
129
+ >
130
+ : never;
131
+
132
+ export type GeneratedFieldSpec = {
133
+ readonly type: ColumnTypeDescriptor;
134
+ readonly typeParams?: Record<string, unknown>;
135
+ readonly generated: ExecutionMutationDefaultValue;
136
+ };
137
+
138
+ function isColumnDefault(value: unknown): value is ColumnDefault {
139
+ if (typeof value !== 'object' || value === null) return false;
140
+ const kind = (value as { kind?: unknown }).kind;
141
+ return kind === 'literal' || kind === 'function';
142
+ }
143
+
144
+ function toColumnDefault(value: ColumnDefaultLiteralInputValue | ColumnDefault): ColumnDefault {
145
+ if (isColumnDefault(value)) {
146
+ return value;
147
+ }
148
+ return { kind: 'literal', value };
149
+ }
150
+
151
+ // Chaining methods use `as unknown as <ConditionalType>` because TypeScript cannot
152
+ // narrow generic conditional return types through object spread. The runtime values
153
+ // are correct — the casts bridge the gap between the spread result and the
154
+ // compile-time conditional type that encodes the state transition.
155
+ export class ScalarFieldBuilder<State extends AnyScalarFieldState = AnyScalarFieldState> {
156
+ declare readonly __state: State;
157
+
158
+ constructor(private readonly state: State) {}
159
+
160
+ optional(): ScalarFieldBuilder<
161
+ State extends ScalarFieldState<
162
+ infer CodecId,
163
+ infer TypeRef,
164
+ boolean,
165
+ infer ColumnName,
166
+ infer IdSpec,
167
+ infer UniqueSpec
168
+ >
169
+ ? ScalarFieldState<CodecId, TypeRef, true, ColumnName, IdSpec, UniqueSpec>
170
+ : never
171
+ > {
172
+ return new ScalarFieldBuilder({
173
+ ...this.state,
174
+ nullable: true,
175
+ } as unknown as State extends ScalarFieldState<
176
+ infer CodecId,
177
+ infer TypeRef,
178
+ boolean,
179
+ infer ColumnName,
180
+ infer IdSpec,
181
+ infer UniqueSpec
182
+ >
183
+ ? ScalarFieldState<CodecId, TypeRef, true, ColumnName, IdSpec, UniqueSpec>
184
+ : never);
185
+ }
186
+
187
+ column<ColumnName extends string>(
188
+ name: ColumnName,
189
+ ): ScalarFieldBuilder<
190
+ State extends ScalarFieldState<
191
+ infer CodecId,
192
+ infer TypeRef,
193
+ infer Nullable,
194
+ string | undefined,
195
+ infer IdSpec,
196
+ infer UniqueSpec
197
+ >
198
+ ? ScalarFieldState<CodecId, TypeRef, Nullable, ColumnName, IdSpec, UniqueSpec>
199
+ : never
200
+ > {
201
+ return new ScalarFieldBuilder({
202
+ ...this.state,
203
+ columnName: name,
204
+ } as unknown as State extends ScalarFieldState<
205
+ infer CodecId,
206
+ infer TypeRef,
207
+ infer Nullable,
208
+ string | undefined,
209
+ infer IdSpec,
210
+ infer UniqueSpec
211
+ >
212
+ ? ScalarFieldState<CodecId, TypeRef, Nullable, ColumnName, IdSpec, UniqueSpec>
213
+ : never);
214
+ }
215
+
216
+ default(value: ColumnDefaultLiteralInputValue | ColumnDefault): ScalarFieldBuilder<State> {
217
+ return new ScalarFieldBuilder({
218
+ ...this.state,
219
+ default: toColumnDefault(value),
220
+ }) as ScalarFieldBuilder<State>;
221
+ }
222
+
223
+ defaultSql(expression: string): ScalarFieldBuilder<State> {
224
+ return new ScalarFieldBuilder({
225
+ ...this.state,
226
+ default: { kind: 'function', expression },
227
+ }) as ScalarFieldBuilder<State>;
228
+ }
229
+
230
+ id<const Name extends string | undefined = undefined>(
231
+ options?: NamedConstraintSpec<Name>,
232
+ ): ScalarFieldBuilder<
233
+ State extends ScalarFieldState<
234
+ infer CodecId,
235
+ infer TypeRef,
236
+ infer Nullable,
237
+ infer ColumnName,
238
+ NamedConstraintSpec | undefined,
239
+ infer UniqueSpec
240
+ >
241
+ ? ScalarFieldState<
242
+ CodecId,
243
+ TypeRef,
244
+ Nullable,
245
+ ColumnName,
246
+ NamedConstraintSpec<Name>,
247
+ UniqueSpec
248
+ >
249
+ : never
250
+ > {
251
+ return new ScalarFieldBuilder({
252
+ ...this.state,
253
+ id: options?.name ? { name: options.name } : {},
254
+ } as unknown as State extends ScalarFieldState<
255
+ infer CodecId,
256
+ infer TypeRef,
257
+ infer Nullable,
258
+ infer ColumnName,
259
+ NamedConstraintSpec | undefined,
260
+ infer UniqueSpec
261
+ >
262
+ ? ScalarFieldState<
263
+ CodecId,
264
+ TypeRef,
265
+ Nullable,
266
+ ColumnName,
267
+ NamedConstraintSpec<Name>,
268
+ UniqueSpec
269
+ >
270
+ : never);
271
+ }
272
+
273
+ unique<const Name extends string | undefined = undefined>(
274
+ options?: NamedConstraintSpec<Name>,
275
+ ): ScalarFieldBuilder<
276
+ State extends ScalarFieldState<
277
+ infer CodecId,
278
+ infer TypeRef,
279
+ infer Nullable,
280
+ infer ColumnName,
281
+ infer IdSpec,
282
+ NamedConstraintSpec | undefined
283
+ >
284
+ ? ScalarFieldState<CodecId, TypeRef, Nullable, ColumnName, IdSpec, NamedConstraintSpec<Name>>
285
+ : never
286
+ > {
287
+ return new ScalarFieldBuilder({
288
+ ...this.state,
289
+ unique: options?.name ? { name: options.name } : {},
290
+ } as unknown as State extends ScalarFieldState<
291
+ infer CodecId,
292
+ infer TypeRef,
293
+ infer Nullable,
294
+ infer ColumnName,
295
+ infer IdSpec,
296
+ NamedConstraintSpec | undefined
297
+ >
298
+ ? ScalarFieldState<CodecId, TypeRef, Nullable, ColumnName, IdSpec, NamedConstraintSpec<Name>>
299
+ : never);
300
+ }
301
+
302
+ sql<const Spec extends FieldSqlSpecForState<State>>(
303
+ spec: Spec,
304
+ ): ScalarFieldBuilder<ApplyFieldSqlSpec<State, Spec>> {
305
+ const idSpec = 'id' in spec ? spec.id : undefined;
306
+ const uniqueSpec = 'unique' in spec ? spec.unique : undefined;
307
+
308
+ if (idSpec && !this.state.id) {
309
+ throw new Error('field.sql({ id }) requires an existing inline .id(...) declaration.');
310
+ }
311
+ if (uniqueSpec && !this.state.unique) {
312
+ throw new Error(
313
+ 'field.sql({ unique }) requires an existing inline .unique(...) declaration.',
314
+ );
315
+ }
316
+
317
+ return new ScalarFieldBuilder({
318
+ ...this.state,
319
+ ...(spec.column ? { columnName: spec.column } : {}),
320
+ ...(idSpec ? { id: { name: idSpec.name } } : {}),
321
+ ...(uniqueSpec ? { unique: { name: uniqueSpec.name } } : {}),
322
+ } as unknown as ApplyFieldSqlSpec<State, Spec>);
323
+ }
324
+
325
+ build(): State {
326
+ return this.state;
327
+ }
328
+ }
329
+
330
+ function columnField<Descriptor extends ColumnTypeDescriptor>(
331
+ descriptor: Descriptor,
332
+ ): ScalarFieldBuilder<ScalarFieldState<Descriptor['codecId'], undefined, false, undefined>> {
333
+ return new ScalarFieldBuilder({
334
+ kind: 'scalar',
335
+ descriptor,
336
+ nullable: false,
337
+ });
338
+ }
339
+
340
+ function generatedField<Descriptor extends ColumnTypeDescriptor>(
341
+ spec: GeneratedFieldSpec & { readonly type: Descriptor },
342
+ ): ScalarFieldBuilder<ScalarFieldState<Descriptor['codecId'], undefined, false, undefined>> {
343
+ return new ScalarFieldBuilder({
344
+ kind: 'scalar',
345
+ descriptor: {
346
+ ...spec.type,
347
+ ...(spec.typeParams ? { typeParams: spec.typeParams } : {}),
348
+ },
349
+ nullable: false,
350
+ executionDefault: spec.generated,
351
+ });
352
+ }
353
+
354
+ function namedTypeField<TypeRef extends string>(
355
+ typeRef: TypeRef,
356
+ ): ScalarFieldBuilder<ScalarFieldState<string, TypeRef, false, undefined>>;
357
+ function namedTypeField<TypeRef extends StorageTypeInstance>(
358
+ typeRef: TypeRef,
359
+ ): ScalarFieldBuilder<ScalarFieldState<TypeRef['codecId'], TypeRef, false, undefined>>;
360
+ function namedTypeField(
361
+ typeRef: NamedStorageTypeRef,
362
+ ): ScalarFieldBuilder<ScalarFieldState<string, NamedStorageTypeRef, false, undefined>> {
363
+ return new ScalarFieldBuilder({
364
+ kind: 'scalar',
365
+ typeRef,
366
+ nullable: false,
367
+ });
368
+ }
369
+
370
+ export function buildFieldPreset(
371
+ descriptor: AuthoringFieldPresetDescriptor,
372
+ args: readonly unknown[],
373
+ namedConstraintOptions?: NamedConstraintSpec,
374
+ ): ScalarFieldBuilder {
375
+ const preset = instantiateAuthoringFieldPreset(descriptor, args);
376
+
377
+ return new ScalarFieldBuilder({
378
+ kind: 'scalar',
379
+ descriptor: preset.descriptor,
380
+ nullable: preset.nullable,
381
+ ...ifDefined('default', preset.default as ColumnDefault | undefined),
382
+ ...ifDefined(
383
+ 'executionDefault',
384
+ preset.executionDefault as ExecutionMutationDefaultValue | undefined,
385
+ ),
386
+ ...(preset.id
387
+ ? {
388
+ id: namedConstraintOptions?.name ? { name: namedConstraintOptions.name } : {},
389
+ }
390
+ : {}),
391
+ ...(preset.unique
392
+ ? {
393
+ unique: namedConstraintOptions?.name ? { name: namedConstraintOptions.name } : {},
394
+ }
395
+ : {}),
396
+ });
397
+ }
398
+
399
+ type RelationModelRefSource = 'string' | 'token' | 'lazyToken';
400
+ type TargetFieldRefSource = 'string' | 'token';
401
+
402
+ type EagerRelationModelName<
403
+ ModelName extends string = string,
404
+ Source extends Exclude<RelationModelRefSource, 'lazyToken'> = Exclude<
405
+ RelationModelRefSource,
406
+ 'lazyToken'
407
+ >,
408
+ > = {
409
+ readonly kind: 'relationModelName';
410
+ readonly source: Source;
411
+ readonly modelName: ModelName;
412
+ };
413
+
414
+ type LazyRelationModelName<ModelName extends string = string> = {
415
+ readonly kind: 'lazyRelationModelName';
416
+ readonly source: 'lazyToken';
417
+ readonly resolve: () => ModelName;
418
+ };
419
+
420
+ type RelationModelSource<ModelName extends string = string> =
421
+ | EagerRelationModelName<ModelName>
422
+ | LazyRelationModelName<ModelName>;
423
+
424
+ type BelongsToRelation<
425
+ ToModel extends string = string,
426
+ FromField extends string | readonly string[] = string | readonly string[],
427
+ ToField extends string | readonly string[] = string | readonly string[],
428
+ SqlSpec extends BelongsToRelationSqlSpec | undefined = undefined,
429
+ > = {
430
+ readonly kind: 'belongsTo';
431
+ readonly toModel: RelationModelSource<ToModel>;
432
+ readonly from: FromField;
433
+ readonly to: ToField;
434
+ readonly sql?: SqlSpec;
435
+ };
436
+
437
+ type HasManyRelation<
438
+ ToModel extends string = string,
439
+ ByField extends string | readonly string[] = string | readonly string[],
440
+ > = {
441
+ readonly kind: 'hasMany';
442
+ readonly toModel: RelationModelSource<ToModel>;
443
+ readonly by: ByField;
444
+ };
445
+
446
+ type HasOneRelation<
447
+ ToModel extends string = string,
448
+ ByField extends string | readonly string[] = string | readonly string[],
449
+ > = {
450
+ readonly kind: 'hasOne';
451
+ readonly toModel: RelationModelSource<ToModel>;
452
+ readonly by: ByField;
453
+ };
454
+
455
+ type ManyToManyRelation<
456
+ ToModel extends string = string,
457
+ ThroughModel extends string = string,
458
+ FromField extends string | readonly string[] = string | readonly string[],
459
+ ToField extends string | readonly string[] = string | readonly string[],
460
+ > = {
461
+ readonly kind: 'manyToMany';
462
+ readonly toModel: RelationModelSource<ToModel>;
463
+ readonly through: RelationModelSource<ThroughModel>;
464
+ readonly from: FromField;
465
+ readonly to: ToField;
466
+ };
467
+
468
+ export type RelationState =
469
+ | BelongsToRelation<
470
+ string,
471
+ string | readonly string[],
472
+ string | readonly string[],
473
+ BelongsToRelationSqlSpec | undefined
474
+ >
475
+ | HasManyRelation
476
+ | HasOneRelation
477
+ | ManyToManyRelation;
478
+
479
+ type AnyRelationState = RelationState;
480
+ type AnyRelationBuilder = RelationBuilder<AnyRelationState>;
481
+
482
+ type ApplyBelongsToRelationSqlSpec<
483
+ State extends RelationState,
484
+ SqlSpec extends BelongsToRelationSqlSpec,
485
+ > = State extends BelongsToRelation<
486
+ infer ToModel,
487
+ infer FromField,
488
+ infer ToField,
489
+ BelongsToRelationSqlSpec | undefined
490
+ >
491
+ ? BelongsToRelation<ToModel, FromField, ToField, SqlSpec>
492
+ : never;
493
+
494
+ export class RelationBuilder<State extends RelationState = AnyRelationState> {
495
+ declare readonly __state: State;
496
+
497
+ constructor(private readonly state: State) {}
498
+
499
+ sql<const SqlSpec extends BelongsToRelationSqlSpec>(
500
+ this: State extends BelongsToRelation<
501
+ string,
502
+ string | readonly string[],
503
+ string | readonly string[],
504
+ BelongsToRelationSqlSpec | undefined
505
+ >
506
+ ? RelationBuilder<State>
507
+ : never,
508
+ spec: SqlSpec,
509
+ ): RelationBuilder<ApplyBelongsToRelationSqlSpec<State, SqlSpec>> {
510
+ if (this.state.kind !== 'belongsTo') {
511
+ throw new Error('relation.sql(...) is only supported for belongsTo relations.');
512
+ }
513
+
514
+ return new RelationBuilder({
515
+ ...this.state,
516
+ sql: spec,
517
+ } as ApplyBelongsToRelationSqlSpec<State, SqlSpec>);
518
+ }
519
+
520
+ build(): State {
521
+ return this.state;
522
+ }
523
+ }
524
+
525
+ export type ColumnRef<FieldName extends string = string> = {
526
+ readonly kind: 'columnRef';
527
+ readonly fieldName: FieldName;
528
+ };
529
+
530
+ export type TargetFieldRef<
531
+ ModelName extends string = string,
532
+ FieldName extends string = string,
533
+ Source extends TargetFieldRefSource = TargetFieldRefSource,
534
+ > = {
535
+ readonly kind: 'targetFieldRef';
536
+ readonly source: Source;
537
+ readonly modelName: ModelName;
538
+ readonly fieldName: FieldName;
539
+ };
540
+
541
+ export type ModelTokenRefs<
542
+ ModelName extends string,
543
+ Fields extends Record<string, ScalarFieldBuilder>,
544
+ > = {
545
+ readonly [K in keyof Fields]: TargetFieldRef<ModelName, K & string>;
546
+ };
547
+
548
+ type ConstraintOptions<Name extends string | undefined = string | undefined> = {
549
+ readonly name?: Name;
550
+ };
551
+
552
+ type IndexOptions<Name extends string | undefined = string | undefined> =
553
+ ConstraintOptions<Name> & {
554
+ readonly using?: string;
555
+ readonly config?: Record<string, unknown>;
556
+ };
557
+
558
+ type ForeignKeyOptions<Name extends string | undefined = string | undefined> =
559
+ ConstraintOptions<Name> & {
560
+ readonly onDelete?: 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';
561
+ readonly onUpdate?: 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';
562
+ readonly constraint?: boolean;
563
+ readonly index?: boolean;
564
+ };
565
+
566
+ type BelongsToRelationSqlSpec<Name extends string | undefined = string | undefined> = {
567
+ readonly fk?: ForeignKeyOptions<Name>;
568
+ };
569
+
570
+ export type IdConstraint<
571
+ FieldNames extends readonly string[] = readonly string[],
572
+ Name extends string | undefined = string | undefined,
573
+ > = {
574
+ readonly kind: 'id';
575
+ readonly fields: FieldNames;
576
+ readonly name?: Name;
577
+ };
578
+
579
+ export type UniqueConstraint<FieldNames extends readonly string[] = readonly string[]> = {
580
+ readonly kind: 'unique';
581
+ readonly fields: FieldNames;
582
+ readonly name?: string;
583
+ };
584
+
585
+ export type IndexConstraint<
586
+ FieldNames extends readonly string[] = readonly string[],
587
+ Name extends string | undefined = string | undefined,
588
+ > = {
589
+ readonly kind: 'index';
590
+ readonly fields: FieldNames;
591
+ readonly name?: Name;
592
+ readonly using?: string;
593
+ readonly config?: Record<string, unknown>;
594
+ };
595
+
596
+ export type ForeignKeyConstraint<
597
+ SourceFieldNames extends readonly string[] = readonly string[],
598
+ TargetModelName extends string = string,
599
+ TargetFieldNames extends readonly string[] = readonly string[],
600
+ Name extends string | undefined = string | undefined,
601
+ > = {
602
+ readonly kind: 'fk';
603
+ readonly fields: SourceFieldNames;
604
+ readonly targetModel: TargetModelName;
605
+ readonly targetFields: TargetFieldNames;
606
+ readonly targetSource?: TargetFieldRefSource;
607
+ readonly name?: Name;
608
+ readonly onDelete?: 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';
609
+ readonly onUpdate?: 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';
610
+ readonly constraint?: boolean;
611
+ readonly index?: boolean;
612
+ };
613
+
614
+ function normalizeFieldRefInput(input: ColumnRef | readonly ColumnRef[]): readonly string[] {
615
+ return (Array.isArray(input) ? input : [input]).map((ref) => ref.fieldName);
616
+ }
617
+
618
+ function normalizeTargetFieldRefInput(input: TargetFieldRef | readonly TargetFieldRef[]): {
619
+ readonly modelName: string;
620
+ readonly fieldNames: readonly string[];
621
+ readonly source: TargetFieldRefSource;
622
+ } {
623
+ const refs = Array.isArray(input) ? input : [input];
624
+ const [first] = refs;
625
+ if (!first) {
626
+ throw new Error('Expected at least one target ref');
627
+ }
628
+ if (refs.some((ref) => ref.modelName !== first.modelName)) {
629
+ throw new Error('All target refs in a foreign key must point to the same model');
630
+ }
631
+ return {
632
+ modelName: first.modelName,
633
+ fieldNames: refs.map((ref) => ref.fieldName),
634
+ source: refs.some((ref) => ref.source === 'string') ? 'string' : 'token',
635
+ };
636
+ }
637
+
638
+ function createConstraintsDsl() {
639
+ function ref<ModelName extends string, FieldName extends string>(
640
+ modelName: ModelName,
641
+ fieldName: FieldName,
642
+ ): TargetFieldRef<ModelName, FieldName> {
643
+ return {
644
+ kind: 'targetFieldRef',
645
+ source: 'string',
646
+ modelName,
647
+ fieldName,
648
+ };
649
+ }
650
+
651
+ function id<FieldName extends string, Name extends string | undefined = undefined>(
652
+ field: ColumnRef<FieldName>,
653
+ options?: NamedConstraintSpec<Name>,
654
+ ): IdConstraint<readonly [FieldName], Name>;
655
+ function id<FieldNames extends readonly string[], Name extends string | undefined = undefined>(
656
+ fields: { readonly [K in keyof FieldNames]: ColumnRef<FieldNames[K] & string> },
657
+ options?: NamedConstraintSpec<Name>,
658
+ ): IdConstraint<FieldNames, Name>;
659
+ function id(
660
+ fieldOrFields: ColumnRef | readonly ColumnRef[],
661
+ options?: NamedConstraintSpec,
662
+ ): IdConstraint {
663
+ return {
664
+ kind: 'id',
665
+ fields: normalizeFieldRefInput(fieldOrFields),
666
+ ...(options?.name ? { name: options.name } : {}),
667
+ };
668
+ }
669
+
670
+ function unique<FieldName extends string>(
671
+ field: ColumnRef<FieldName>,
672
+ options?: ConstraintOptions,
673
+ ): UniqueConstraint<readonly [FieldName]>;
674
+ function unique<FieldNames extends readonly string[]>(
675
+ fields: { readonly [K in keyof FieldNames]: ColumnRef<FieldNames[K] & string> },
676
+ options?: ConstraintOptions,
677
+ ): UniqueConstraint<FieldNames>;
678
+ function unique(
679
+ fieldOrFields: ColumnRef | readonly ColumnRef[],
680
+ options?: ConstraintOptions,
681
+ ): UniqueConstraint {
682
+ return {
683
+ kind: 'unique',
684
+ fields: normalizeFieldRefInput(fieldOrFields),
685
+ ...(options?.name ? { name: options.name } : {}),
686
+ };
687
+ }
688
+
689
+ function index<FieldName extends string, Name extends string | undefined = undefined>(
690
+ field: ColumnRef<FieldName>,
691
+ options?: IndexOptions<Name>,
692
+ ): IndexConstraint<readonly [FieldName], Name>;
693
+ function index<FieldNames extends readonly string[], Name extends string | undefined = undefined>(
694
+ fields: { readonly [K in keyof FieldNames]: ColumnRef<FieldNames[K] & string> },
695
+ options?: IndexOptions<Name>,
696
+ ): IndexConstraint<FieldNames, Name>;
697
+ function index(
698
+ fieldOrFields: ColumnRef | readonly ColumnRef[],
699
+ options?: IndexOptions,
700
+ ): IndexConstraint {
701
+ return {
702
+ kind: 'index',
703
+ fields: normalizeFieldRefInput(fieldOrFields),
704
+ ...(options?.name ? { name: options.name } : {}),
705
+ ...(options?.using ? { using: options.using } : {}),
706
+ ...(options?.config ? { config: options.config } : {}),
707
+ };
708
+ }
709
+
710
+ function foreignKey<
711
+ SourceFieldName extends string,
712
+ TargetModelName extends string,
713
+ TargetFieldName extends string,
714
+ Name extends string | undefined = undefined,
715
+ >(
716
+ field: ColumnRef<SourceFieldName>,
717
+ target: TargetFieldRef<TargetModelName, TargetFieldName>,
718
+ options?: ForeignKeyOptions<Name>,
719
+ ): ForeignKeyConstraint<
720
+ readonly [SourceFieldName],
721
+ TargetModelName,
722
+ readonly [TargetFieldName],
723
+ Name
724
+ >;
725
+ function foreignKey<
726
+ SourceFieldNames extends readonly string[],
727
+ TargetModelName extends string,
728
+ TargetFieldNames extends readonly string[],
729
+ Name extends string | undefined = undefined,
730
+ >(
731
+ fields: { readonly [K in keyof SourceFieldNames]: ColumnRef<SourceFieldNames[K] & string> },
732
+ target: {
733
+ readonly [K in keyof TargetFieldNames]: TargetFieldRef<
734
+ TargetModelName,
735
+ TargetFieldNames[K] & string
736
+ >;
737
+ },
738
+ options?: ForeignKeyOptions<Name>,
739
+ ): ForeignKeyConstraint<SourceFieldNames, TargetModelName, TargetFieldNames, Name>;
740
+ function foreignKey(
741
+ fieldOrFields: ColumnRef | readonly ColumnRef[],
742
+ target: TargetFieldRef | readonly TargetFieldRef[],
743
+ options?: ForeignKeyOptions,
744
+ ): ForeignKeyConstraint {
745
+ const normalizedTarget = normalizeTargetFieldRefInput(target);
746
+ return {
747
+ kind: 'fk',
748
+ fields: normalizeFieldRefInput(fieldOrFields),
749
+ targetModel: normalizedTarget.modelName,
750
+ targetFields: normalizedTarget.fieldNames,
751
+ targetSource: normalizedTarget.source,
752
+ ...(options?.name ? { name: options.name } : {}),
753
+ ...(options?.onDelete ? { onDelete: options.onDelete } : {}),
754
+ ...(options?.onUpdate ? { onUpdate: options.onUpdate } : {}),
755
+ ...(options?.constraint !== undefined ? { constraint: options.constraint } : {}),
756
+ ...(options?.index !== undefined ? { index: options.index } : {}),
757
+ };
758
+ }
759
+
760
+ return {
761
+ ref,
762
+ id,
763
+ unique,
764
+ index,
765
+ foreignKey,
766
+ };
767
+ }
768
+
769
+ export type ConstraintsDsl = ReturnType<typeof createConstraintsDsl>;
770
+
771
+ export type ModelAttributesSpec = {
772
+ readonly id?: IdConstraint;
773
+ readonly uniques?: readonly UniqueConstraint[];
774
+ };
775
+
776
+ export type SqlStageSpec = {
777
+ readonly table?: string;
778
+ readonly indexes?: readonly IndexConstraint[];
779
+ readonly foreignKeys?: readonly ForeignKeyConstraint[];
780
+ };
781
+
782
+ type FieldRefs<Fields extends Record<string, ScalarFieldBuilder>> = {
783
+ readonly [K in keyof Fields]: ColumnRef<K & string>;
784
+ };
785
+
786
+ type AttributeContext<Fields extends Record<string, ScalarFieldBuilder>> = {
787
+ readonly fields: FieldRefs<Fields>;
788
+ readonly constraints: Pick<ConstraintsDsl, 'id' | 'unique'>;
789
+ };
790
+
791
+ type SqlContext<Fields extends Record<string, ScalarFieldBuilder>> = {
792
+ readonly cols: FieldRefs<Fields>;
793
+ readonly constraints: Pick<ConstraintsDsl, 'index' | 'foreignKey' | 'ref'>;
794
+ };
795
+
796
+ function createFieldRefs<Fields extends Record<string, ScalarFieldBuilder>>(
797
+ fields: Fields,
798
+ ): FieldRefs<Fields> {
799
+ const refs = {} as Record<string, ColumnRef>;
800
+ for (const fieldName of Object.keys(fields)) {
801
+ refs[fieldName] = { kind: 'columnRef', fieldName };
802
+ }
803
+ return refs as FieldRefs<Fields>;
804
+ }
805
+
806
+ function createModelTokenRefs<
807
+ ModelName extends string,
808
+ Fields extends Record<string, ScalarFieldBuilder>,
809
+ >(modelName: ModelName, fields: Fields): ModelTokenRefs<ModelName, Fields> {
810
+ const refs = {} as Record<string, TargetFieldRef>;
811
+ for (const fieldName of Object.keys(fields)) {
812
+ refs[fieldName] = {
813
+ kind: 'targetFieldRef',
814
+ source: 'token',
815
+ modelName,
816
+ fieldName,
817
+ };
818
+ }
819
+ return refs as ModelTokenRefs<ModelName, Fields>;
820
+ }
821
+
822
+ type StageInput<Context, Spec> = Spec | ((context: Context) => Spec);
823
+
824
+ function buildStageSpec<Context, Spec>(
825
+ stageInput: StageInput<Context, Spec>,
826
+ context: Context,
827
+ ): Spec {
828
+ if (typeof stageInput === 'function') {
829
+ return (stageInput as (context: Context) => Spec)(context);
830
+ }
831
+ return stageInput;
832
+ }
833
+
834
+ function createAttributeConstraintsDsl(): AttributeContext<
835
+ Record<string, ScalarFieldBuilder>
836
+ >['constraints'] {
837
+ const constraints = createConstraintsDsl();
838
+ return {
839
+ id: constraints.id,
840
+ unique: constraints.unique,
841
+ };
842
+ }
843
+
844
+ function createSqlConstraintsDsl(): SqlContext<Record<string, ScalarFieldBuilder>>['constraints'] {
845
+ const constraints = createConstraintsDsl();
846
+ return {
847
+ index: constraints.index,
848
+ foreignKey: constraints.foreignKey,
849
+ ref: constraints.ref,
850
+ };
851
+ }
852
+
853
+ function createColumnRefs<Fields extends Record<string, ScalarFieldBuilder>>(
854
+ fields: Fields,
855
+ ): SqlContext<Fields>['cols'] {
856
+ return createFieldRefs(fields);
857
+ }
858
+
859
+ type StaticLiteralName<Name> = Name extends string ? (string extends Name ? never : Name) : never;
860
+
861
+ type NamedConstraintLiteralName<Constraint> = Constraint extends { readonly name?: infer Name }
862
+ ? StaticLiteralName<Name>
863
+ : never;
864
+
865
+ type DuplicateLiteralNames<
866
+ Items extends readonly unknown[],
867
+ Seen extends string = never,
868
+ Duplicates extends string = never,
869
+ > = Items extends readonly [infer First, ...infer Rest extends readonly unknown[]]
870
+ ? NamedConstraintLiteralName<First> extends infer Name extends string
871
+ ? Name extends Seen
872
+ ? DuplicateLiteralNames<Rest, Seen, Duplicates | Name>
873
+ : DuplicateLiteralNames<Rest, Seen | Name, Duplicates>
874
+ : DuplicateLiteralNames<Rest, Seen, Duplicates>
875
+ : Duplicates;
876
+
877
+ type InlineIdLiteralName<Fields extends Record<string, ScalarFieldBuilder>> = {
878
+ readonly [FieldName in keyof Fields]: FieldStateOf<Fields[FieldName]> extends {
879
+ readonly id: { readonly name?: infer Name };
880
+ }
881
+ ? StaticLiteralName<Name>
882
+ : never;
883
+ }[keyof Fields];
884
+
885
+ type AttributeIdLiteralName<AttributesSpec extends ModelAttributesSpec | undefined> =
886
+ AttributesSpec extends {
887
+ readonly id?: { readonly name?: infer Name };
888
+ }
889
+ ? StaticLiteralName<Name>
890
+ : never;
891
+
892
+ type ModelIdLiteralName<
893
+ Fields extends Record<string, ScalarFieldBuilder>,
894
+ AttributesSpec extends ModelAttributesSpec | undefined,
895
+ > = [AttributeIdLiteralName<AttributesSpec>] extends [never]
896
+ ? InlineIdLiteralName<Fields>
897
+ : AttributeIdLiteralName<AttributesSpec>;
898
+
899
+ type SqlIndexes<SqlSpec extends SqlStageSpec> = SqlSpec extends {
900
+ readonly indexes?: infer Indexes extends readonly unknown[];
901
+ }
902
+ ? Indexes
903
+ : readonly [];
904
+
905
+ type SqlForeignKeys<SqlSpec extends SqlStageSpec> = SqlSpec extends {
906
+ readonly foreignKeys?: infer ForeignKeys extends readonly unknown[];
907
+ }
908
+ ? ForeignKeys
909
+ : readonly [];
910
+
911
+ type SqlNamedObjects<SqlSpec extends SqlStageSpec> = [
912
+ ...SqlIndexes<SqlSpec>,
913
+ ...SqlForeignKeys<SqlSpec>,
914
+ ];
915
+
916
+ type ValidateSqlStageSpec<
917
+ Fields extends Record<string, ScalarFieldBuilder>,
918
+ AttributesSpec extends ModelAttributesSpec | undefined,
919
+ SqlSpec extends SqlStageSpec,
920
+ > = [DuplicateLiteralNames<SqlNamedObjects<SqlSpec>>] extends [never]
921
+ ? [
922
+ Extract<
923
+ ModelIdLiteralName<Fields, AttributesSpec>,
924
+ NamedConstraintLiteralName<SqlNamedObjects<SqlSpec>[number]>
925
+ >,
926
+ ] extends [never]
927
+ ? SqlSpec
928
+ : never
929
+ : never;
930
+
931
+ type ValidateAttributesStageSpec<
932
+ Fields extends Record<string, ScalarFieldBuilder>,
933
+ SqlSpec extends SqlStageSpec | undefined,
934
+ AttributesSpec extends ModelAttributesSpec,
935
+ > = SqlSpec extends SqlStageSpec
936
+ ? [
937
+ Extract<
938
+ ModelIdLiteralName<Fields, AttributesSpec>,
939
+ NamedConstraintLiteralName<SqlNamedObjects<SqlSpec>[number]>
940
+ >,
941
+ ] extends [never]
942
+ ? AttributesSpec
943
+ : never
944
+ : AttributesSpec;
945
+
946
+ function findDuplicateRelationName(
947
+ existingRelations: Record<string, AnyRelationBuilder>,
948
+ nextRelations: Record<string, AnyRelationBuilder>,
949
+ ): string | undefined {
950
+ return Object.keys(nextRelations).find((relationName) =>
951
+ Object.hasOwn(existingRelations, relationName),
952
+ );
953
+ }
954
+
955
+ export class StagedModelBuilder<
956
+ ModelName extends string | undefined,
957
+ Fields extends Record<string, ScalarFieldBuilder>,
958
+ Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
959
+ AttributesSpec extends ModelAttributesSpec | undefined = undefined,
960
+ SqlSpec extends SqlStageSpec | undefined = undefined,
961
+ > {
962
+ declare readonly __name: ModelName;
963
+ declare readonly __fields: Fields;
964
+ declare readonly __relations: Relations;
965
+ declare readonly __attributes: AttributesSpec;
966
+ declare readonly __sql: SqlSpec;
967
+ readonly refs: ModelName extends string ? ModelTokenRefs<ModelName, Fields> : never;
968
+
969
+ constructor(
970
+ readonly stageOne: {
971
+ readonly modelName?: ModelName;
972
+ readonly fields: Fields;
973
+ readonly relations: Relations;
974
+ },
975
+ readonly attributesFactory?: StageInput<AttributeContext<Fields>, AttributesSpec>,
976
+ readonly sqlFactory?: StageInput<SqlContext<Fields>, SqlSpec>,
977
+ ) {
978
+ this.refs = (
979
+ stageOne.modelName ? createModelTokenRefs(stageOne.modelName, stageOne.fields) : undefined
980
+ ) as ModelName extends string ? ModelTokenRefs<ModelName, Fields> : never;
981
+ }
982
+
983
+ ref<FieldName extends keyof Fields & string>(
984
+ this: ModelName extends string
985
+ ? StagedModelBuilder<ModelName, Fields, Relations, AttributesSpec, SqlSpec>
986
+ : never,
987
+ fieldName: FieldName,
988
+ ): TargetFieldRef<ModelName & string, FieldName> {
989
+ const modelName = this.stageOne.modelName;
990
+ if (!modelName) {
991
+ throw new Error('Model tokens require model("ModelName", ...) before calling .ref(...)');
992
+ }
993
+
994
+ return {
995
+ kind: 'targetFieldRef',
996
+ source: 'token',
997
+ modelName,
998
+ fieldName,
999
+ } as TargetFieldRef<ModelName & string, FieldName>;
1000
+ }
1001
+
1002
+ relations<const NextRelations extends Record<string, AnyRelationBuilder>>(
1003
+ relations: NextRelations,
1004
+ ): StagedModelBuilder<ModelName, Fields, Relations & NextRelations, AttributesSpec, SqlSpec> {
1005
+ const duplicateRelationName = findDuplicateRelationName(this.stageOne.relations, relations);
1006
+ if (duplicateRelationName) {
1007
+ throw new Error(
1008
+ `Model "${this.stageOne.modelName ?? '<anonymous>'}" already defines relation "${duplicateRelationName}".`,
1009
+ );
1010
+ }
1011
+
1012
+ return new StagedModelBuilder(
1013
+ {
1014
+ ...this.stageOne,
1015
+ relations: {
1016
+ ...this.stageOne.relations,
1017
+ ...relations,
1018
+ } as Relations & NextRelations,
1019
+ },
1020
+ this.attributesFactory,
1021
+ this.sqlFactory,
1022
+ );
1023
+ }
1024
+
1025
+ attributes<const NextAttributesSpec extends ModelAttributesSpec>(
1026
+ specOrFactory: StageInput<
1027
+ AttributeContext<Fields>,
1028
+ ValidateAttributesStageSpec<Fields, SqlSpec, NextAttributesSpec>
1029
+ >,
1030
+ ): StagedModelBuilder<ModelName, Fields, Relations, NextAttributesSpec, SqlSpec> {
1031
+ return new StagedModelBuilder(this.stageOne, specOrFactory, this.sqlFactory);
1032
+ }
1033
+
1034
+ sql<const NextSqlSpec extends SqlStageSpec>(
1035
+ specOrFactory: StageInput<SqlContext<Fields>, NextSqlSpec>,
1036
+ ): [ValidateSqlStageSpec<Fields, AttributesSpec, NextSqlSpec>] extends [never]
1037
+ ? StagedModelBuilder<ModelName, Fields, Relations, AttributesSpec, never>
1038
+ : StagedModelBuilder<ModelName, Fields, Relations, AttributesSpec, NextSqlSpec> {
1039
+ // Conditional return type cannot be verified by the implementation; the
1040
+ // runtime value is always a valid StagedModelBuilder regardless of the
1041
+ // validation outcome (validation is type-level only).
1042
+ return new StagedModelBuilder(this.stageOne, this.attributesFactory, specOrFactory) as never;
1043
+ }
1044
+
1045
+ buildAttributesSpec(): AttributesSpec {
1046
+ if (!this.attributesFactory) {
1047
+ return undefined as AttributesSpec;
1048
+ }
1049
+
1050
+ return buildStageSpec(this.attributesFactory, {
1051
+ fields: createFieldRefs(this.stageOne.fields),
1052
+ constraints: createAttributeConstraintsDsl() as AttributeContext<Fields>['constraints'],
1053
+ });
1054
+ }
1055
+
1056
+ buildSqlSpec(): SqlSpec {
1057
+ if (!this.sqlFactory) {
1058
+ return undefined as SqlSpec;
1059
+ }
1060
+ return buildStageSpec(this.sqlFactory, {
1061
+ cols: createColumnRefs(this.stageOne.fields),
1062
+ constraints: createSqlConstraintsDsl() as SqlContext<Fields>['constraints'],
1063
+ });
1064
+ }
1065
+ }
1066
+
1067
+ type NamedModelTokenShape<
1068
+ ModelName extends string = string,
1069
+ Fields extends Record<string, ScalarFieldBuilder> = Record<string, ScalarFieldBuilder>,
1070
+ > = {
1071
+ readonly stageOne: {
1072
+ readonly modelName?: ModelName;
1073
+ readonly fields: Fields;
1074
+ };
1075
+ };
1076
+
1077
+ type AnyNamedModelToken = NamedModelTokenShape<string, Record<string, ScalarFieldBuilder>>;
1078
+
1079
+ type LazyNamedModelToken<Token extends AnyNamedModelToken = AnyNamedModelToken> = () => Token;
1080
+
1081
+ type RelationFieldSelection<FieldName extends string> = FieldName | readonly FieldName[];
1082
+
1083
+ type RelationModelName<Target> =
1084
+ Target extends NamedModelTokenShape<
1085
+ infer ModelName extends string,
1086
+ Record<string, ScalarFieldBuilder>
1087
+ >
1088
+ ? ModelName
1089
+ : Target extends () => infer Token
1090
+ ? RelationModelName<Token>
1091
+ : never;
1092
+
1093
+ type RelationModelFieldNames<Target> =
1094
+ Target extends NamedModelTokenShape<string, infer Fields>
1095
+ ? keyof Fields & string
1096
+ : Target extends () => infer Token
1097
+ ? RelationModelFieldNames<Token>
1098
+ : never;
1099
+
1100
+ function isLazyRelationModelName(value: unknown): value is LazyRelationModelName<string> {
1101
+ return (
1102
+ typeof value === 'object' &&
1103
+ value !== null &&
1104
+ 'kind' in value &&
1105
+ (value as { kind?: unknown }).kind === 'lazyRelationModelName' &&
1106
+ 'resolve' in value &&
1107
+ typeof (value as { resolve?: unknown }).resolve === 'function'
1108
+ );
1109
+ }
1110
+
1111
+ function resolveNamedModelTokenName(token: {
1112
+ readonly stageOne: {
1113
+ readonly modelName?: string | undefined;
1114
+ };
1115
+ }): string {
1116
+ const modelName = token.stageOne.modelName;
1117
+ if (!modelName) {
1118
+ throw new Error(
1119
+ 'Relation targets require named model tokens. Use model("ModelName", ...) before passing a token to rel.*(...).',
1120
+ );
1121
+ }
1122
+ return modelName;
1123
+ }
1124
+
1125
+ function normalizeRelationModelSource<Token extends AnyNamedModelToken>(
1126
+ target: Token | LazyNamedModelToken<Token>,
1127
+ ): RelationModelSource<RelationModelName<Token>>;
1128
+ function normalizeRelationModelSource<ToModel extends string>(
1129
+ target: ToModel,
1130
+ ): RelationModelSource<ToModel>;
1131
+ function normalizeRelationModelSource(
1132
+ target: string | AnyNamedModelToken | LazyNamedModelToken,
1133
+ ): RelationModelSource<string>;
1134
+ function normalizeRelationModelSource(
1135
+ target: string | AnyNamedModelToken | LazyNamedModelToken,
1136
+ ): RelationModelSource<string> {
1137
+ if (typeof target === 'string') {
1138
+ return {
1139
+ kind: 'relationModelName',
1140
+ source: 'string',
1141
+ modelName: target,
1142
+ };
1143
+ }
1144
+
1145
+ if (typeof target === 'function') {
1146
+ return {
1147
+ kind: 'lazyRelationModelName',
1148
+ source: 'lazyToken',
1149
+ resolve: () => resolveNamedModelTokenName(target()),
1150
+ };
1151
+ }
1152
+
1153
+ return {
1154
+ kind: 'relationModelName',
1155
+ source: 'token',
1156
+ modelName: resolveNamedModelTokenName(target),
1157
+ };
1158
+ }
1159
+
1160
+ export type StagedContractInput<
1161
+ Family extends FamilyPackRef<string> = FamilyPackRef<string>,
1162
+ Target extends TargetPackRef<'sql', string> = TargetPackRef<'sql', string>,
1163
+ Types extends Record<string, StorageTypeInstance> = Record<never, never>,
1164
+ Models extends Record<
1165
+ string,
1166
+ StagedModelBuilder<
1167
+ string | undefined,
1168
+ Record<string, ScalarFieldBuilder>,
1169
+ Record<string, AnyRelationBuilder>,
1170
+ ModelAttributesSpec | undefined,
1171
+ SqlStageSpec | undefined
1172
+ >
1173
+ > = Record<never, never>,
1174
+ ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined = undefined,
1175
+ Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined,
1176
+ > = {
1177
+ readonly family: Family;
1178
+ readonly target: Target;
1179
+ readonly extensionPacks?: ExtensionPacks;
1180
+ readonly naming?: NamingConfig;
1181
+ readonly storageHash?: string;
1182
+ readonly foreignKeyDefaults?: ForeignKeyDefaultsState;
1183
+ readonly capabilities?: Capabilities;
1184
+ readonly types?: Types;
1185
+ readonly models?: Models;
1186
+ };
1187
+
1188
+ export function model<
1189
+ const ModelName extends string,
1190
+ Fields extends Record<string, ScalarFieldBuilder>,
1191
+ Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
1192
+ >(
1193
+ modelName: ModelName,
1194
+ input: {
1195
+ readonly fields: Fields;
1196
+ readonly relations?: Relations;
1197
+ },
1198
+ ): StagedModelBuilder<ModelName, Fields, Relations>;
1199
+
1200
+ export function model<
1201
+ Fields extends Record<string, ScalarFieldBuilder>,
1202
+ Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
1203
+ >(input: {
1204
+ readonly fields: Fields;
1205
+ readonly relations?: Relations;
1206
+ }): StagedModelBuilder<undefined, Fields, Relations>;
1207
+
1208
+ export function model<
1209
+ const ModelName extends string,
1210
+ Fields extends Record<string, ScalarFieldBuilder>,
1211
+ Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
1212
+ >(
1213
+ modelNameOrInput:
1214
+ | ModelName
1215
+ | {
1216
+ readonly fields: Fields;
1217
+ readonly relations?: Relations;
1218
+ },
1219
+ maybeInput?: {
1220
+ readonly fields: Fields;
1221
+ readonly relations?: Relations;
1222
+ },
1223
+ ): StagedModelBuilder<ModelName | undefined, Fields, Relations> {
1224
+ const input = typeof modelNameOrInput === 'string' ? maybeInput : modelNameOrInput;
1225
+
1226
+ if (!input) {
1227
+ throw new Error('model("ModelName", ...) requires a model definition.');
1228
+ }
1229
+
1230
+ return new StagedModelBuilder({
1231
+ ...(typeof modelNameOrInput === 'string' ? { modelName: modelNameOrInput } : {}),
1232
+ fields: input.fields,
1233
+ relations: (input.relations ?? {}) as Relations,
1234
+ });
1235
+ }
1236
+
1237
+ function belongsTo<
1238
+ Token extends AnyNamedModelToken,
1239
+ FromField extends string | readonly string[],
1240
+ ToField extends RelationFieldSelection<RelationModelFieldNames<Token>>,
1241
+ >(
1242
+ toModel: Token | LazyNamedModelToken<Token>,
1243
+ options: { readonly from: FromField; readonly to: ToField },
1244
+ ): RelationBuilder<BelongsToRelation<RelationModelName<Token>, FromField, ToField>>;
1245
+ function belongsTo<
1246
+ ToModel extends string,
1247
+ FromField extends string | readonly string[],
1248
+ ToField extends string | readonly string[],
1249
+ >(
1250
+ toModel: ToModel,
1251
+ options: { readonly from: FromField; readonly to: ToField },
1252
+ ): RelationBuilder<BelongsToRelation<ToModel, FromField, ToField>>;
1253
+ function belongsTo(
1254
+ toModel: string | AnyNamedModelToken | LazyNamedModelToken,
1255
+ options: {
1256
+ readonly from: string | readonly string[];
1257
+ readonly to: string | readonly string[];
1258
+ },
1259
+ ): RelationBuilder<BelongsToRelation> {
1260
+ return new RelationBuilder({
1261
+ kind: 'belongsTo',
1262
+ toModel: normalizeRelationModelSource(toModel),
1263
+ from: options.from,
1264
+ to: options.to,
1265
+ });
1266
+ }
1267
+
1268
+ function hasMany<
1269
+ Token extends AnyNamedModelToken,
1270
+ ByField extends RelationFieldSelection<RelationModelFieldNames<Token>>,
1271
+ >(
1272
+ toModel: Token | LazyNamedModelToken<Token>,
1273
+ options: { readonly by: ByField },
1274
+ ): RelationBuilder<HasManyRelation<RelationModelName<Token>, ByField>>;
1275
+ function hasMany<ToModel extends string, ByField extends string | readonly string[]>(
1276
+ toModel: ToModel,
1277
+ options: { readonly by: ByField },
1278
+ ): RelationBuilder<HasManyRelation<ToModel, ByField>>;
1279
+ function hasMany(
1280
+ toModel: string | AnyNamedModelToken | LazyNamedModelToken,
1281
+ options: { readonly by: string | readonly string[] },
1282
+ ): RelationBuilder<HasManyRelation> {
1283
+ return new RelationBuilder({
1284
+ kind: 'hasMany',
1285
+ toModel: normalizeRelationModelSource(toModel),
1286
+ by: options.by,
1287
+ });
1288
+ }
1289
+
1290
+ function hasOne<
1291
+ Token extends AnyNamedModelToken,
1292
+ ByField extends RelationFieldSelection<RelationModelFieldNames<Token>>,
1293
+ >(
1294
+ toModel: Token | LazyNamedModelToken<Token>,
1295
+ options: { readonly by: ByField },
1296
+ ): RelationBuilder<HasOneRelation<RelationModelName<Token>, ByField>>;
1297
+ function hasOne<ToModel extends string, ByField extends string | readonly string[]>(
1298
+ toModel: ToModel,
1299
+ options: { readonly by: ByField },
1300
+ ): RelationBuilder<HasOneRelation<ToModel, ByField>>;
1301
+ function hasOne(
1302
+ toModel: string | AnyNamedModelToken | LazyNamedModelToken,
1303
+ options: { readonly by: string | readonly string[] },
1304
+ ): RelationBuilder<HasOneRelation> {
1305
+ return new RelationBuilder({
1306
+ kind: 'hasOne',
1307
+ toModel: normalizeRelationModelSource(toModel),
1308
+ by: options.by,
1309
+ });
1310
+ }
1311
+
1312
+ function manyToMany<
1313
+ ToToken extends AnyNamedModelToken,
1314
+ ThroughToken extends AnyNamedModelToken,
1315
+ FromField extends RelationFieldSelection<RelationModelFieldNames<ThroughToken>>,
1316
+ ToField extends RelationFieldSelection<RelationModelFieldNames<ThroughToken>>,
1317
+ >(
1318
+ toModel: ToToken | LazyNamedModelToken<ToToken>,
1319
+ options: {
1320
+ readonly through: ThroughToken | LazyNamedModelToken<ThroughToken>;
1321
+ readonly from: FromField;
1322
+ readonly to: ToField;
1323
+ },
1324
+ ): RelationBuilder<
1325
+ ManyToManyRelation<
1326
+ RelationModelName<ToToken>,
1327
+ RelationModelName<ThroughToken>,
1328
+ FromField,
1329
+ ToField
1330
+ >
1331
+ >;
1332
+ function manyToMany<
1333
+ ToModel extends string,
1334
+ ThroughModel extends string,
1335
+ FromField extends string | readonly string[],
1336
+ ToField extends string | readonly string[],
1337
+ >(
1338
+ toModel: ToModel,
1339
+ options: {
1340
+ readonly through: ThroughModel;
1341
+ readonly from: FromField;
1342
+ readonly to: ToField;
1343
+ },
1344
+ ): RelationBuilder<ManyToManyRelation<ToModel, ThroughModel, FromField, ToField>>;
1345
+ function manyToMany(
1346
+ toModel: string | AnyNamedModelToken | LazyNamedModelToken,
1347
+ options: {
1348
+ readonly through: string | AnyNamedModelToken | LazyNamedModelToken;
1349
+ readonly from: string | readonly string[];
1350
+ readonly to: string | readonly string[];
1351
+ },
1352
+ ): RelationBuilder<ManyToManyRelation> {
1353
+ return new RelationBuilder({
1354
+ kind: 'manyToMany',
1355
+ toModel: normalizeRelationModelSource(toModel),
1356
+ through: normalizeRelationModelSource(options.through),
1357
+ from: options.from,
1358
+ to: options.to,
1359
+ });
1360
+ }
1361
+
1362
+ export const rel = {
1363
+ belongsTo,
1364
+ hasMany,
1365
+ hasOne,
1366
+ manyToMany,
1367
+ };
1368
+
1369
+ export const field = {
1370
+ column: columnField,
1371
+ generated: generatedField,
1372
+ namedType: namedTypeField,
1373
+ };
1374
+
1375
+ export function isStagedContractInput(value: unknown): value is StagedContractInput {
1376
+ if (typeof value !== 'object' || value === null || !('target' in value) || !('family' in value)) {
1377
+ return false;
1378
+ }
1379
+ const target = (value as { target: unknown }).target;
1380
+ const family = (value as { family: unknown }).family;
1381
+ return (
1382
+ typeof target === 'object' &&
1383
+ target !== null &&
1384
+ 'kind' in target &&
1385
+ target.kind === 'target' &&
1386
+ typeof family === 'object' &&
1387
+ family !== null &&
1388
+ 'kind' in family &&
1389
+ family.kind === 'family'
1390
+ );
1391
+ }
1392
+
1393
+ function isRelationFieldArray(value: string | readonly string[]): value is readonly string[] {
1394
+ return Array.isArray(value);
1395
+ }
1396
+
1397
+ export function normalizeRelationFieldNames(value: string | readonly string[]): readonly string[] {
1398
+ if (isRelationFieldArray(value)) {
1399
+ return value;
1400
+ }
1401
+ return [value];
1402
+ }
1403
+
1404
+ export function resolveRelationModelName(value: RelationModelSource<string>): string {
1405
+ if (isLazyRelationModelName(value)) {
1406
+ return value.resolve();
1407
+ }
1408
+ return value.modelName;
1409
+ }
1410
+
1411
+ export function applyNaming(name: string, strategy: NamingStrategy | undefined): string {
1412
+ if (!strategy || strategy === 'identity') {
1413
+ return name;
1414
+ }
1415
+
1416
+ let result = '';
1417
+ for (let index = 0; index < name.length; index += 1) {
1418
+ const char = name[index];
1419
+ if (!char) continue;
1420
+ const lower = char.toLowerCase();
1421
+ const isUpper = char !== lower;
1422
+ if (isUpper && index > 0) {
1423
+ const prev = name[index - 1];
1424
+ const next = name[index + 1];
1425
+ const prevIsLower = !!prev && prev === prev.toLowerCase();
1426
+ const nextIsLower = !!next && next === next.toLowerCase();
1427
+ if (prevIsLower || nextIsLower) {
1428
+ result += '_';
1429
+ }
1430
+ }
1431
+ result += lower;
1432
+ }
1433
+ return result;
1434
+ }
1435
+
1436
+ export type FieldStateOf<T> = T extends ScalarFieldBuilder<infer State> ? State : never;
1437
+ export type RelationStateOf<T> = T extends RelationBuilder<infer State> ? State : never;
1438
+
1439
+ export type ModelFieldsOf<T> =
1440
+ T extends StagedModelBuilder<
1441
+ string | undefined,
1442
+ infer Fields,
1443
+ Record<string, AnyRelationBuilder>,
1444
+ ModelAttributesSpec | undefined,
1445
+ SqlStageSpec | undefined
1446
+ >
1447
+ ? Fields
1448
+ : never;
1449
+
1450
+ export type ModelRelationsOf<T> =
1451
+ T extends StagedModelBuilder<
1452
+ string | undefined,
1453
+ Record<string, ScalarFieldBuilder>,
1454
+ infer Relations,
1455
+ ModelAttributesSpec | undefined,
1456
+ SqlStageSpec | undefined
1457
+ >
1458
+ ? Relations
1459
+ : never;
1460
+
1461
+ export type ModelAttributesOf<T> =
1462
+ T extends StagedModelBuilder<
1463
+ string | undefined,
1464
+ Record<string, ScalarFieldBuilder>,
1465
+ Record<string, AnyRelationBuilder>,
1466
+ infer AttributesSpec,
1467
+ SqlStageSpec | undefined
1468
+ >
1469
+ ? AttributesSpec
1470
+ : undefined;
1471
+
1472
+ export type ModelSqlOf<T> =
1473
+ T extends StagedModelBuilder<
1474
+ string | undefined,
1475
+ Record<string, ScalarFieldBuilder>,
1476
+ Record<string, AnyRelationBuilder>,
1477
+ ModelAttributesSpec | undefined,
1478
+ infer SqlSpec
1479
+ >
1480
+ ? SqlSpec
1481
+ : undefined;
1482
+
1483
+ export type IdFieldNames<T> =
1484
+ T extends IdConstraint<infer FieldNames> ? FieldNames : readonly string[];
1485
+
1486
+ export type AttributeStageIdFieldNames<T> = T extends { readonly id?: infer I }
1487
+ ? I extends IdConstraint
1488
+ ? IdFieldNames<I>
1489
+ : undefined
1490
+ : undefined;