@prisma-next/sql-contract-ts 0.3.0-dev.34 → 0.3.0-dev.37

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.
@@ -1,6 +1,9 @@
1
1
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/contract/framework-components';
2
+ import type { ExecutionMutationDefault } from '@prisma-next/contract/types';
2
3
  import type {
3
4
  ColumnBuilderState,
5
+ ContractBuilderState,
6
+ ForeignKeyDefaultsState,
4
7
  ModelBuilderState,
5
8
  RelationDefinition,
6
9
  TableBuilderState,
@@ -17,13 +20,16 @@ import {
17
20
  type Mutable,
18
21
  TableBuilder,
19
22
  } from '@prisma-next/contract-authoring';
20
- import type {
21
- ModelDefinition,
22
- ModelField,
23
- SqlContract,
24
- SqlMappings,
25
- SqlStorage,
23
+ import {
24
+ applyFkDefaults,
25
+ type ModelDefinition,
26
+ type ModelField,
27
+ type SqlContract,
28
+ type SqlMappings,
29
+ type SqlStorage,
30
+ type StorageTypeInstance,
26
31
  } from '@prisma-next/sql-contract/types';
32
+ import { ifDefined } from '@prisma-next/utils/defined';
27
33
  import { computeMappings } from './contract';
28
34
 
29
35
  /**
@@ -33,8 +39,8 @@ import { computeMappings } from './contract';
33
39
  * produces. `codecTypes` uses the generic `CodecTypes` parameter; `operationTypes` is always
34
40
  * empty since operations are added via extensions at runtime.
35
41
  *
36
- * **Difference from RuntimeContext**: This is a compile-time type for contract construction.
37
- * `RuntimeContext` is a runtime object with populated registries for query execution.
42
+ * **Difference from ExecutionContext**: This is a compile-time type for contract construction.
43
+ * `ExecutionContext` is a runtime object with populated registries for query execution.
38
44
  *
39
45
  * @template C - The `CodecTypes` generic parameter passed to `defineContract<CodecTypes>()`
40
46
  */
@@ -66,6 +72,8 @@ type BuildStorageTable<
66
72
  readonly columns: readonly string[];
67
73
  readonly references: { readonly table: string; readonly columns: readonly string[] };
68
74
  readonly name?: string;
75
+ readonly constraint: boolean;
76
+ readonly index: boolean;
69
77
  }>;
70
78
  } & (PK extends readonly string[]
71
79
  ? { readonly primaryKey: { readonly columns: PK; readonly name?: string } }
@@ -80,6 +88,7 @@ type BuildStorage<
80
88
  readonly string[] | undefined
81
89
  >
82
90
  >,
91
+ Types extends Record<string, StorageTypeInstance>,
83
92
  > = {
84
93
  readonly tables: {
85
94
  readonly [K in keyof Tables]: BuildStorageTable<
@@ -88,6 +97,7 @@ type BuildStorage<
88
97
  ExtractPrimaryKey<Tables[K]>
89
98
  >;
90
99
  };
100
+ readonly types: Types;
91
101
  };
92
102
 
93
103
  type BuildStorageTables<
@@ -128,10 +138,21 @@ class SqlContractBuilder<
128
138
  string,
129
139
  ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>
130
140
  > = Record<never, never>,
131
- CoreHash extends string | undefined = undefined,
141
+ Types extends Record<string, StorageTypeInstance> = Record<never, never>,
142
+ StorageHash extends string | undefined = undefined,
132
143
  ExtensionPacks extends Record<string, unknown> | undefined = undefined,
133
144
  Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined,
134
- > extends ContractBuilder<Target, Tables, Models, CoreHash, ExtensionPacks, Capabilities> {
145
+ > extends ContractBuilder<Target, Tables, Models, StorageHash, ExtensionPacks, Capabilities> {
146
+ protected declare readonly state: ContractBuilderState<
147
+ Target,
148
+ Tables,
149
+ Models,
150
+ StorageHash,
151
+ ExtensionPacks,
152
+ Capabilities
153
+ > & {
154
+ readonly storageTypes?: Types;
155
+ };
135
156
  /**
136
157
  * This method is responsible for normalizing the contract IR by setting default values
137
158
  * for all required fields:
@@ -152,7 +173,7 @@ class SqlContractBuilder<
152
173
  */
153
174
  build(): Target extends string
154
175
  ? SqlContract<
155
- BuildStorage<Tables>,
176
+ BuildStorage<Tables, Types>,
156
177
  BuildModels<Models>,
157
178
  BuildRelations<Models>,
158
179
  ContractBuilderMappings<CodecTypes>
@@ -160,7 +181,7 @@ class SqlContractBuilder<
160
181
  readonly schemaVersion: '1';
161
182
  readonly target: Target;
162
183
  readonly targetFamily: 'sql';
163
- readonly coreHash: CoreHash extends string ? CoreHash : string;
184
+ readonly storageHash: StorageHash extends string ? StorageHash : string;
164
185
  } & (ExtensionPacks extends Record<string, unknown>
165
186
  ? { readonly extensionPacks: ExtensionPacks }
166
187
  : Record<string, never>) &
@@ -171,7 +192,7 @@ class SqlContractBuilder<
171
192
  // Type helper to ensure literal types are preserved in return type
172
193
  type BuiltContract = Target extends string
173
194
  ? SqlContract<
174
- BuildStorage<Tables>,
195
+ BuildStorage<Tables, Types>,
175
196
  BuildModels<Models>,
176
197
  BuildRelations<Models>,
177
198
  ContractBuilderMappings<CodecTypes>
@@ -179,7 +200,7 @@ class SqlContractBuilder<
179
200
  readonly schemaVersion: '1';
180
201
  readonly target: Target;
181
202
  readonly targetFamily: 'sql';
182
- readonly coreHash: CoreHash extends string ? CoreHash : string;
203
+ readonly storageHash: StorageHash extends string ? StorageHash : string;
183
204
  } & (ExtensionPacks extends Record<string, unknown>
184
205
  ? { readonly extensionPacks: ExtensionPacks }
185
206
  : Record<string, never>) &
@@ -194,6 +215,7 @@ class SqlContractBuilder<
194
215
  const target = this.state.target as Target & string;
195
216
 
196
217
  const storageTables = {} as Partial<Mutable<BuildStorageTables<Tables>>>;
218
+ const executionDefaults: ExecutionMutationDefault[] = [];
197
219
 
198
220
  for (const tableName of Object.keys(this.state.tables) as Array<keyof Tables & string>) {
199
221
  const tableState = this.state.tables[tableName];
@@ -215,17 +237,27 @@ class SqlContractBuilder<
215
237
  if (!columnState) continue;
216
238
  const codecId = columnState.type;
217
239
  const nativeType = columnState.nativeType;
240
+ const typeRef = columnState.typeRef;
218
241
 
219
242
  columns[columnName as keyof ColumnDefs] = {
220
243
  nativeType,
221
244
  codecId,
222
245
  nullable: (columnState.nullable ?? false) as ColumnDefs[keyof ColumnDefs]['nullable'] &
223
246
  boolean,
224
- ...(columnState.typeParams ? { typeParams: columnState.typeParams } : {}),
247
+ ...ifDefined('typeParams', columnState.typeParams),
248
+ ...ifDefined('default', columnState.default),
249
+ ...ifDefined('typeRef', typeRef),
225
250
  } as BuildStorageColumn<
226
251
  ColumnDefs[keyof ColumnDefs]['nullable'] & boolean,
227
252
  ColumnDefs[keyof ColumnDefs]['type']
228
253
  >;
254
+
255
+ if ('executionDefault' in columnState && columnState.executionDefault) {
256
+ executionDefaults.push({
257
+ ref: { table: tableName, column: columnName },
258
+ onCreate: columnState.executionDefault,
259
+ });
260
+ }
229
261
  }
230
262
 
231
263
  // Build uniques from table state
@@ -240,10 +272,11 @@ class SqlContractBuilder<
240
272
  ...(i.name ? { name: i.name } : {}),
241
273
  }));
242
274
 
243
- // Build foreign keys from table state
275
+ // Build foreign keys from table state, materializing defaults
244
276
  const foreignKeys = (tableState.foreignKeys ?? []).map((fk) => ({
245
277
  columns: fk.columns,
246
278
  references: fk.references,
279
+ ...applyFkDefaults(fk, this.state.foreignKeyDefaults),
247
280
  ...(fk.name ? { name: fk.name } : {}),
248
281
  }));
249
282
 
@@ -270,7 +303,26 @@ class SqlContractBuilder<
270
303
  (storageTables as Mutable<BuildStorageTables<Tables>>)[tableName] = table;
271
304
  }
272
305
 
273
- const storage = { tables: storageTables as BuildStorageTables<Tables> } as BuildStorage<Tables>;
306
+ const storageTypes = (this.state.storageTypes ?? {}) as Types;
307
+ const storage: BuildStorage<Tables, Types> = {
308
+ tables: storageTables as BuildStorageTables<Tables>,
309
+ types: storageTypes,
310
+ };
311
+
312
+ const execution =
313
+ executionDefaults.length > 0
314
+ ? {
315
+ mutations: {
316
+ defaults: executionDefaults.sort((a, b) => {
317
+ const tableCompare = a.ref.table.localeCompare(b.ref.table);
318
+ if (tableCompare !== 0) {
319
+ return tableCompare;
320
+ }
321
+ return a.ref.column.localeCompare(b.ref.column);
322
+ }),
323
+ },
324
+ }
325
+ : undefined;
274
326
 
275
327
  // Build models - construct as partial first, then assert full type
276
328
  const modelsPartial: Partial<BuildModels<Models>> = {};
@@ -374,11 +426,12 @@ class SqlContractBuilder<
374
426
  schemaVersion: '1' as const,
375
427
  target,
376
428
  targetFamily: 'sql' as const,
377
- coreHash: this.state.coreHash || 'sha256:ts-builder-placeholder',
429
+ storageHash: this.state.storageHash || 'sha256:ts-builder-placeholder',
378
430
  models,
379
431
  relations: relationsPartial,
380
432
  storage,
381
433
  mappings,
434
+ ...(execution ? { execution } : {}),
382
435
  extensionPacks,
383
436
  capabilities: this.state.capabilities || {},
384
437
  meta: {},
@@ -391,7 +444,8 @@ class SqlContractBuilder<
391
444
  Target,
392
445
  Tables,
393
446
  Models,
394
- CoreHash,
447
+ Types,
448
+ StorageHash,
395
449
  ExtensionPacks,
396
450
  Capabilities
397
451
  >['build']
@@ -400,13 +454,23 @@ class SqlContractBuilder<
400
454
 
401
455
  override target<T extends string>(
402
456
  packRef: TargetPackRef<'sql', T>,
403
- ): SqlContractBuilder<CodecTypes, T, Tables, Models, CoreHash, ExtensionPacks, Capabilities> {
457
+ ): SqlContractBuilder<
458
+ CodecTypes,
459
+ T,
460
+ Tables,
461
+ Models,
462
+ Types,
463
+ StorageHash,
464
+ ExtensionPacks,
465
+ Capabilities
466
+ > {
404
467
  return new SqlContractBuilder<
405
468
  CodecTypes,
406
469
  T,
407
470
  Tables,
408
471
  Models,
409
- CoreHash,
472
+ Types,
473
+ StorageHash,
410
474
  ExtensionPacks,
411
475
  Capabilities
412
476
  >({
@@ -422,7 +486,8 @@ class SqlContractBuilder<
422
486
  Target,
423
487
  Tables,
424
488
  Models,
425
- CoreHash,
489
+ Types,
490
+ StorageHash,
426
491
  ExtensionPacks,
427
492
  Capabilities
428
493
  > {
@@ -461,7 +526,8 @@ class SqlContractBuilder<
461
526
  Target,
462
527
  Tables,
463
528
  Models,
464
- CoreHash,
529
+ Types,
530
+ StorageHash,
465
531
  ExtensionPacks,
466
532
  Capabilities
467
533
  >({
@@ -472,27 +538,46 @@ class SqlContractBuilder<
472
538
 
473
539
  override capabilities<C extends Record<string, Record<string, boolean>>>(
474
540
  capabilities: C,
475
- ): SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, ExtensionPacks, C> {
476
- return new SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, ExtensionPacks, C>({
541
+ ): SqlContractBuilder<CodecTypes, Target, Tables, Models, Types, StorageHash, ExtensionPacks, C> {
542
+ return new SqlContractBuilder<
543
+ CodecTypes,
544
+ Target,
545
+ Tables,
546
+ Models,
547
+ Types,
548
+ StorageHash,
549
+ ExtensionPacks,
550
+ C
551
+ >({
477
552
  ...this.state,
478
553
  capabilities,
479
554
  });
480
555
  }
481
556
 
482
- override coreHash<H extends string>(
557
+ override storageHash<H extends string>(
483
558
  hash: H,
484
- ): SqlContractBuilder<CodecTypes, Target, Tables, Models, H, ExtensionPacks, Capabilities> {
559
+ ): SqlContractBuilder<
560
+ CodecTypes,
561
+ Target,
562
+ Tables,
563
+ Models,
564
+ Types,
565
+ H,
566
+ ExtensionPacks,
567
+ Capabilities
568
+ > {
485
569
  return new SqlContractBuilder<
486
570
  CodecTypes,
487
571
  Target,
488
572
  Tables,
489
573
  Models,
574
+ Types,
490
575
  H,
491
576
  ExtensionPacks,
492
577
  Capabilities
493
578
  >({
494
579
  ...this.state,
495
- coreHash: hash,
580
+ storageHash: hash,
496
581
  });
497
582
  }
498
583
 
@@ -511,7 +596,8 @@ class SqlContractBuilder<
511
596
  Target,
512
597
  Tables & Record<TableName, ReturnType<T['build']>>,
513
598
  Models,
514
- CoreHash,
599
+ Types,
600
+ StorageHash,
515
601
  ExtensionPacks,
516
602
  Capabilities
517
603
  > {
@@ -525,7 +611,8 @@ class SqlContractBuilder<
525
611
  Target,
526
612
  Tables & Record<TableName, ReturnType<T['build']>>,
527
613
  Models,
528
- CoreHash,
614
+ Types,
615
+ StorageHash,
529
616
  ExtensionPacks,
530
617
  Capabilities
531
618
  >({
@@ -555,7 +642,8 @@ class SqlContractBuilder<
555
642
  Target,
556
643
  Tables,
557
644
  Models & Record<ModelName, ReturnType<M['build']>>,
558
- CoreHash,
645
+ Types,
646
+ StorageHash,
559
647
  ExtensionPacks,
560
648
  Capabilities
561
649
  > {
@@ -569,7 +657,8 @@ class SqlContractBuilder<
569
657
  Target,
570
658
  Tables,
571
659
  Models & Record<ModelName, ReturnType<M['build']>>,
572
- CoreHash,
660
+ Types,
661
+ StorageHash,
573
662
  ExtensionPacks,
574
663
  Capabilities
575
664
  >({
@@ -578,6 +667,64 @@ class SqlContractBuilder<
578
667
  Record<ModelName, ReturnType<M['build']>>,
579
668
  });
580
669
  }
670
+
671
+ override foreignKeyDefaults(
672
+ config: ForeignKeyDefaultsState,
673
+ ): SqlContractBuilder<
674
+ CodecTypes,
675
+ Target,
676
+ Tables,
677
+ Models,
678
+ Types,
679
+ StorageHash,
680
+ ExtensionPacks,
681
+ Capabilities
682
+ > {
683
+ return new SqlContractBuilder<
684
+ CodecTypes,
685
+ Target,
686
+ Tables,
687
+ Models,
688
+ Types,
689
+ StorageHash,
690
+ ExtensionPacks,
691
+ Capabilities
692
+ >({
693
+ ...this.state,
694
+ foreignKeyDefaults: config,
695
+ });
696
+ }
697
+
698
+ storageType<Name extends string, Type extends StorageTypeInstance>(
699
+ name: Name,
700
+ typeInstance: Type,
701
+ ): SqlContractBuilder<
702
+ CodecTypes,
703
+ Target,
704
+ Tables,
705
+ Models,
706
+ Types & Record<Name, Type>,
707
+ StorageHash,
708
+ ExtensionPacks,
709
+ Capabilities
710
+ > {
711
+ return new SqlContractBuilder<
712
+ CodecTypes,
713
+ Target,
714
+ Tables,
715
+ Models,
716
+ Types & Record<Name, Type>,
717
+ StorageHash,
718
+ ExtensionPacks,
719
+ Capabilities
720
+ >({
721
+ ...this.state,
722
+ storageTypes: {
723
+ ...(this.state.storageTypes ?? {}),
724
+ [name]: typeInstance,
725
+ },
726
+ });
727
+ }
581
728
  }
582
729
 
583
730
  export function defineContract<
package/src/contract.ts CHANGED
@@ -14,6 +14,7 @@ import type {
14
14
  StorageTypeInstance,
15
15
  UniqueConstraint,
16
16
  } from '@prisma-next/sql-contract/types';
17
+ import { ColumnDefaultSchema } from '@prisma-next/sql-contract/validators';
17
18
  import { type } from 'arktype';
18
19
  import type { O } from 'ts-toolbelt';
19
20
 
@@ -21,12 +22,14 @@ import type { O } from 'ts-toolbelt';
21
22
  * Structural validation schema for SqlContract using Arktype.
22
23
  * This validates the shape and types of the contract structure.
23
24
  */
25
+
24
26
  const StorageColumnSchema = type.declare<StorageColumn>().type({
25
27
  nativeType: 'string',
26
28
  codecId: 'string',
27
29
  nullable: 'boolean',
28
30
  'typeParams?': 'Record<string, unknown>',
29
31
  'typeRef?': 'string',
32
+ 'default?': ColumnDefaultSchema,
30
33
  });
31
34
 
32
35
  const StorageTypeInstanceSchema = type.declare<StorageTypeInstance>().type({
@@ -59,6 +62,8 @@ const ForeignKeySchema = type.declare<ForeignKey>().type({
59
62
  columns: type.string.array().readonly(),
60
63
  references: ForeignKeyReferencesSchema,
61
64
  'name?': 'string',
65
+ constraint: 'boolean',
66
+ index: 'boolean',
62
67
  });
63
68
 
64
69
  const StorageTableSchema = type.declare<StorageTable>().type({
@@ -88,6 +93,27 @@ const ModelSchema = type.declare<ModelDefinition>().type({
88
93
  relations: type({ '[string]': 'unknown' }),
89
94
  });
90
95
 
96
+ const ExecutionMutationDefaultValueSchema = type({
97
+ kind: "'generator'",
98
+ id: "'ulid' | 'nanoid' | 'uuidv7' | 'uuidv4' | 'cuid2' | 'ksuid'",
99
+ 'params?': 'Record<string, unknown>',
100
+ });
101
+
102
+ const ExecutionMutationDefaultSchema = type({
103
+ ref: {
104
+ table: 'string',
105
+ column: 'string',
106
+ },
107
+ 'onCreate?': ExecutionMutationDefaultValueSchema,
108
+ 'onUpdate?': ExecutionMutationDefaultValueSchema,
109
+ });
110
+
111
+ const ExecutionSchema = type({
112
+ mutations: {
113
+ defaults: ExecutionMutationDefaultSchema.array().readonly(),
114
+ },
115
+ });
116
+
91
117
  /**
92
118
  * Complete SqlContract schema for structural validation.
93
119
  * This validates the entire contract structure at once.
@@ -96,7 +122,8 @@ const SqlContractSchema = type({
96
122
  'schemaVersion?': "'1'",
97
123
  target: 'string',
98
124
  targetFamily: "'sql'",
99
- coreHash: 'string',
125
+ storageHash: 'string',
126
+ 'executionHash?': 'string',
100
127
  'profileHash?': 'string',
101
128
  'capabilities?': 'Record<string, Record<string, boolean>>',
102
129
  'extensionPacks?': 'Record<string, unknown>',
@@ -104,6 +131,7 @@ const SqlContractSchema = type({
104
131
  'sources?': 'Record<string, unknown>',
105
132
  models: type({ '[string]': ModelSchema }),
106
133
  storage: StorageSchema,
134
+ 'execution?': ExecutionSchema,
107
135
  });
108
136
 
109
137
  /**
@@ -198,13 +226,15 @@ export function computeMappings(
198
226
  * This checks that references (e.g., foreign keys, primary keys, uniques) point to storage objects that already exist.
199
227
  * Structural validation is expected to have already completed before this helper runs.
200
228
  *
229
+ * Rule: keep this focused on structural consistency only; capability/feature
230
+ * gating (e.g., defaults.*) belongs in migration/runtime verification, not here.
231
+ *
201
232
  * @param structurallyValidatedContract - The contract whose structure has already been validated
202
233
  * @throws Error if logical validation fails
203
234
  */
204
235
  function validateContractLogic(structurallyValidatedContract: SqlContract<SqlStorage>): void {
205
236
  const { storage, models } = structurallyValidatedContract;
206
237
  const tableNames = new Set(Object.keys(storage.tables));
207
- const typeInstanceNames = new Set(Object.keys(storage.types ?? {}));
208
238
 
209
239
  // Validate storage.types if present
210
240
  if (storage.types) {
@@ -235,11 +265,26 @@ function validateContractLogic(structurallyValidatedContract: SqlContract<SqlSto
235
265
  );
236
266
  }
237
267
 
238
- // Validate typeRef points to an existing storage.types key
239
- if (column.typeRef !== undefined && !typeInstanceNames.has(column.typeRef)) {
240
- throw new Error(
241
- `Column "${columnName}" in table "${tableName}" references non-existent type instance "${column.typeRef}" (not found in storage.types)`,
242
- );
268
+ // Validate typeRef points to an existing storage.types key and matches codecId/nativeType
269
+ if (column.typeRef !== undefined) {
270
+ const referencedType = storage.types?.[column.typeRef];
271
+ if (!referencedType) {
272
+ throw new Error(
273
+ `Column "${columnName}" in table "${tableName}" references non-existent type instance "${column.typeRef}" (not found in storage.types)`,
274
+ );
275
+ }
276
+
277
+ if (column.codecId !== referencedType.codecId) {
278
+ throw new Error(
279
+ `Column "${columnName}" in table "${tableName}" has codecId "${column.codecId}" but references type instance "${column.typeRef}" with codecId "${referencedType.codecId}"`,
280
+ );
281
+ }
282
+
283
+ if (column.nativeType !== referencedType.nativeType) {
284
+ throw new Error(
285
+ `Column "${columnName}" in table "${tableName}" has nativeType "${column.nativeType}" but references type instance "${column.typeRef}" with nativeType "${referencedType.nativeType}"`,
286
+ );
287
+ }
243
288
  }
244
289
  }
245
290
  }
@@ -431,82 +476,8 @@ function validateContractLogic(structurallyValidatedContract: SqlContract<SqlSto
431
476
  }
432
477
  }
433
478
 
434
- export function normalizeContract(contract: unknown): SqlContract<SqlStorage> {
435
- const contractObj = contract as Record<string, unknown>;
436
-
437
- // Only normalize if storage exists (validation will catch if it's missing)
438
- let normalizedStorage = contractObj['storage'];
439
- if (normalizedStorage && typeof normalizedStorage === 'object' && normalizedStorage !== null) {
440
- const storage = normalizedStorage as Record<string, unknown>;
441
- const tables = storage['tables'] as Record<string, unknown> | undefined;
442
-
443
- if (tables) {
444
- // Normalize storage tables
445
- const normalizedTables: Record<string, unknown> = {};
446
- for (const [tableName, table] of Object.entries(tables)) {
447
- const tableObj = table as Record<string, unknown>;
448
- const columns = tableObj['columns'] as Record<string, unknown> | undefined;
449
-
450
- if (columns) {
451
- // Normalize columns: add nullable: false if missing
452
- const normalizedColumns: Record<string, unknown> = {};
453
- for (const [columnName, column] of Object.entries(columns)) {
454
- const columnObj = column as Record<string, unknown>;
455
- const normalizedColumn: Record<string, unknown> = {
456
- ...columnObj,
457
- nullable: columnObj['nullable'] ?? false,
458
- };
459
-
460
- normalizedColumns[columnName] = normalizedColumn;
461
- }
462
-
463
- // Normalize table arrays: add empty arrays if missing
464
- normalizedTables[tableName] = {
465
- ...tableObj,
466
- columns: normalizedColumns,
467
- uniques: tableObj['uniques'] ?? [],
468
- indexes: tableObj['indexes'] ?? [],
469
- foreignKeys: tableObj['foreignKeys'] ?? [],
470
- };
471
- } else {
472
- normalizedTables[tableName] = tableObj;
473
- }
474
- }
475
-
476
- normalizedStorage = {
477
- ...storage,
478
- tables: normalizedTables,
479
- };
480
- }
481
- }
482
-
483
- // Only normalize if models exists (validation will catch if it's missing)
484
- let normalizedModels = contractObj['models'];
485
- if (normalizedModels && typeof normalizedModels === 'object' && normalizedModels !== null) {
486
- const models = normalizedModels as Record<string, unknown>;
487
- const normalizedModelsObj: Record<string, unknown> = {};
488
- for (const [modelName, model] of Object.entries(models)) {
489
- const modelObj = model as Record<string, unknown>;
490
- normalizedModelsObj[modelName] = {
491
- ...modelObj,
492
- relations: modelObj['relations'] ?? {},
493
- };
494
- }
495
- normalizedModels = normalizedModelsObj;
496
- }
497
-
498
- // Normalize top-level fields: add empty objects if missing
499
- return {
500
- ...contractObj,
501
- models: normalizedModels,
502
- relations: contractObj['relations'] ?? {},
503
- storage: normalizedStorage,
504
- extensionPacks: contractObj['extensionPacks'] ?? {},
505
- capabilities: contractObj['capabilities'] ?? {},
506
- meta: contractObj['meta'] ?? {},
507
- sources: contractObj['sources'] ?? {},
508
- } as SqlContract<SqlStorage>;
509
- }
479
+ import { normalizeContract } from '@prisma-next/sql-contract/validate';
480
+ export { normalizeContract };
510
481
 
511
482
  /**
512
483
  * Validates that a JSON import conforms to the SqlContract structure