@prisma-next/sql-contract-ts 0.3.0-dev.33 → 0.3.0-dev.36

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,8 @@
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,
4
6
  ModelBuilderState,
5
7
  RelationDefinition,
6
8
  TableBuilderState,
@@ -23,7 +25,9 @@ import type {
23
25
  SqlContract,
24
26
  SqlMappings,
25
27
  SqlStorage,
28
+ StorageTypeInstance,
26
29
  } from '@prisma-next/sql-contract/types';
30
+ import { ifDefined } from '@prisma-next/utils/defined';
27
31
  import { computeMappings } from './contract';
28
32
 
29
33
  /**
@@ -33,8 +37,8 @@ import { computeMappings } from './contract';
33
37
  * produces. `codecTypes` uses the generic `CodecTypes` parameter; `operationTypes` is always
34
38
  * empty since operations are added via extensions at runtime.
35
39
  *
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.
40
+ * **Difference from ExecutionContext**: This is a compile-time type for contract construction.
41
+ * `ExecutionContext` is a runtime object with populated registries for query execution.
38
42
  *
39
43
  * @template C - The `CodecTypes` generic parameter passed to `defineContract<CodecTypes>()`
40
44
  */
@@ -80,6 +84,7 @@ type BuildStorage<
80
84
  readonly string[] | undefined
81
85
  >
82
86
  >,
87
+ Types extends Record<string, StorageTypeInstance>,
83
88
  > = {
84
89
  readonly tables: {
85
90
  readonly [K in keyof Tables]: BuildStorageTable<
@@ -88,6 +93,7 @@ type BuildStorage<
88
93
  ExtractPrimaryKey<Tables[K]>
89
94
  >;
90
95
  };
96
+ readonly types: Types;
91
97
  };
92
98
 
93
99
  type BuildStorageTables<
@@ -128,10 +134,21 @@ class SqlContractBuilder<
128
134
  string,
129
135
  ModelBuilderState<string, string, Record<string, string>, Record<string, RelationDefinition>>
130
136
  > = Record<never, never>,
131
- CoreHash extends string | undefined = undefined,
137
+ Types extends Record<string, StorageTypeInstance> = Record<never, never>,
138
+ StorageHash extends string | undefined = undefined,
132
139
  ExtensionPacks extends Record<string, unknown> | undefined = undefined,
133
140
  Capabilities extends Record<string, Record<string, boolean>> | undefined = undefined,
134
- > extends ContractBuilder<Target, Tables, Models, CoreHash, ExtensionPacks, Capabilities> {
141
+ > extends ContractBuilder<Target, Tables, Models, StorageHash, ExtensionPacks, Capabilities> {
142
+ protected declare readonly state: ContractBuilderState<
143
+ Target,
144
+ Tables,
145
+ Models,
146
+ StorageHash,
147
+ ExtensionPacks,
148
+ Capabilities
149
+ > & {
150
+ readonly storageTypes?: Types;
151
+ };
135
152
  /**
136
153
  * This method is responsible for normalizing the contract IR by setting default values
137
154
  * for all required fields:
@@ -152,7 +169,7 @@ class SqlContractBuilder<
152
169
  */
153
170
  build(): Target extends string
154
171
  ? SqlContract<
155
- BuildStorage<Tables>,
172
+ BuildStorage<Tables, Types>,
156
173
  BuildModels<Models>,
157
174
  BuildRelations<Models>,
158
175
  ContractBuilderMappings<CodecTypes>
@@ -160,7 +177,7 @@ class SqlContractBuilder<
160
177
  readonly schemaVersion: '1';
161
178
  readonly target: Target;
162
179
  readonly targetFamily: 'sql';
163
- readonly coreHash: CoreHash extends string ? CoreHash : string;
180
+ readonly storageHash: StorageHash extends string ? StorageHash : string;
164
181
  } & (ExtensionPacks extends Record<string, unknown>
165
182
  ? { readonly extensionPacks: ExtensionPacks }
166
183
  : Record<string, never>) &
@@ -171,7 +188,7 @@ class SqlContractBuilder<
171
188
  // Type helper to ensure literal types are preserved in return type
172
189
  type BuiltContract = Target extends string
173
190
  ? SqlContract<
174
- BuildStorage<Tables>,
191
+ BuildStorage<Tables, Types>,
175
192
  BuildModels<Models>,
176
193
  BuildRelations<Models>,
177
194
  ContractBuilderMappings<CodecTypes>
@@ -179,7 +196,7 @@ class SqlContractBuilder<
179
196
  readonly schemaVersion: '1';
180
197
  readonly target: Target;
181
198
  readonly targetFamily: 'sql';
182
- readonly coreHash: CoreHash extends string ? CoreHash : string;
199
+ readonly storageHash: StorageHash extends string ? StorageHash : string;
183
200
  } & (ExtensionPacks extends Record<string, unknown>
184
201
  ? { readonly extensionPacks: ExtensionPacks }
185
202
  : Record<string, never>) &
@@ -194,6 +211,7 @@ class SqlContractBuilder<
194
211
  const target = this.state.target as Target & string;
195
212
 
196
213
  const storageTables = {} as Partial<Mutable<BuildStorageTables<Tables>>>;
214
+ const executionDefaults: ExecutionMutationDefault[] = [];
197
215
 
198
216
  for (const tableName of Object.keys(this.state.tables) as Array<keyof Tables & string>) {
199
217
  const tableState = this.state.tables[tableName];
@@ -215,17 +233,27 @@ class SqlContractBuilder<
215
233
  if (!columnState) continue;
216
234
  const codecId = columnState.type;
217
235
  const nativeType = columnState.nativeType;
236
+ const typeRef = columnState.typeRef;
218
237
 
219
238
  columns[columnName as keyof ColumnDefs] = {
220
239
  nativeType,
221
240
  codecId,
222
241
  nullable: (columnState.nullable ?? false) as ColumnDefs[keyof ColumnDefs]['nullable'] &
223
242
  boolean,
224
- ...(columnState.typeParams ? { typeParams: columnState.typeParams } : {}),
243
+ ...ifDefined('typeParams', columnState.typeParams),
244
+ ...ifDefined('default', columnState.default),
245
+ ...ifDefined('typeRef', typeRef),
225
246
  } as BuildStorageColumn<
226
247
  ColumnDefs[keyof ColumnDefs]['nullable'] & boolean,
227
248
  ColumnDefs[keyof ColumnDefs]['type']
228
249
  >;
250
+
251
+ if ('executionDefault' in columnState && columnState.executionDefault) {
252
+ executionDefaults.push({
253
+ ref: { table: tableName, column: columnName },
254
+ onCreate: columnState.executionDefault,
255
+ });
256
+ }
229
257
  }
230
258
 
231
259
  // Build uniques from table state
@@ -270,7 +298,26 @@ class SqlContractBuilder<
270
298
  (storageTables as Mutable<BuildStorageTables<Tables>>)[tableName] = table;
271
299
  }
272
300
 
273
- const storage = { tables: storageTables as BuildStorageTables<Tables> } as BuildStorage<Tables>;
301
+ const storageTypes = (this.state.storageTypes ?? {}) as Types;
302
+ const storage: BuildStorage<Tables, Types> = {
303
+ tables: storageTables as BuildStorageTables<Tables>,
304
+ types: storageTypes,
305
+ };
306
+
307
+ const execution =
308
+ executionDefaults.length > 0
309
+ ? {
310
+ mutations: {
311
+ defaults: executionDefaults.sort((a, b) => {
312
+ const tableCompare = a.ref.table.localeCompare(b.ref.table);
313
+ if (tableCompare !== 0) {
314
+ return tableCompare;
315
+ }
316
+ return a.ref.column.localeCompare(b.ref.column);
317
+ }),
318
+ },
319
+ }
320
+ : undefined;
274
321
 
275
322
  // Build models - construct as partial first, then assert full type
276
323
  const modelsPartial: Partial<BuildModels<Models>> = {};
@@ -374,11 +421,12 @@ class SqlContractBuilder<
374
421
  schemaVersion: '1' as const,
375
422
  target,
376
423
  targetFamily: 'sql' as const,
377
- coreHash: this.state.coreHash || 'sha256:ts-builder-placeholder',
424
+ storageHash: this.state.storageHash || 'sha256:ts-builder-placeholder',
378
425
  models,
379
426
  relations: relationsPartial,
380
427
  storage,
381
428
  mappings,
429
+ ...(execution ? { execution } : {}),
382
430
  extensionPacks,
383
431
  capabilities: this.state.capabilities || {},
384
432
  meta: {},
@@ -391,7 +439,8 @@ class SqlContractBuilder<
391
439
  Target,
392
440
  Tables,
393
441
  Models,
394
- CoreHash,
442
+ Types,
443
+ StorageHash,
395
444
  ExtensionPacks,
396
445
  Capabilities
397
446
  >['build']
@@ -400,13 +449,23 @@ class SqlContractBuilder<
400
449
 
401
450
  override target<T extends string>(
402
451
  packRef: TargetPackRef<'sql', T>,
403
- ): SqlContractBuilder<CodecTypes, T, Tables, Models, CoreHash, ExtensionPacks, Capabilities> {
452
+ ): SqlContractBuilder<
453
+ CodecTypes,
454
+ T,
455
+ Tables,
456
+ Models,
457
+ Types,
458
+ StorageHash,
459
+ ExtensionPacks,
460
+ Capabilities
461
+ > {
404
462
  return new SqlContractBuilder<
405
463
  CodecTypes,
406
464
  T,
407
465
  Tables,
408
466
  Models,
409
- CoreHash,
467
+ Types,
468
+ StorageHash,
410
469
  ExtensionPacks,
411
470
  Capabilities
412
471
  >({
@@ -422,7 +481,8 @@ class SqlContractBuilder<
422
481
  Target,
423
482
  Tables,
424
483
  Models,
425
- CoreHash,
484
+ Types,
485
+ StorageHash,
426
486
  ExtensionPacks,
427
487
  Capabilities
428
488
  > {
@@ -461,7 +521,8 @@ class SqlContractBuilder<
461
521
  Target,
462
522
  Tables,
463
523
  Models,
464
- CoreHash,
524
+ Types,
525
+ StorageHash,
465
526
  ExtensionPacks,
466
527
  Capabilities
467
528
  >({
@@ -472,27 +533,46 @@ class SqlContractBuilder<
472
533
 
473
534
  override capabilities<C extends Record<string, Record<string, boolean>>>(
474
535
  capabilities: C,
475
- ): SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, ExtensionPacks, C> {
476
- return new SqlContractBuilder<CodecTypes, Target, Tables, Models, CoreHash, ExtensionPacks, C>({
536
+ ): SqlContractBuilder<CodecTypes, Target, Tables, Models, Types, StorageHash, ExtensionPacks, C> {
537
+ return new SqlContractBuilder<
538
+ CodecTypes,
539
+ Target,
540
+ Tables,
541
+ Models,
542
+ Types,
543
+ StorageHash,
544
+ ExtensionPacks,
545
+ C
546
+ >({
477
547
  ...this.state,
478
548
  capabilities,
479
549
  });
480
550
  }
481
551
 
482
- override coreHash<H extends string>(
552
+ override storageHash<H extends string>(
483
553
  hash: H,
484
- ): SqlContractBuilder<CodecTypes, Target, Tables, Models, H, ExtensionPacks, Capabilities> {
554
+ ): SqlContractBuilder<
555
+ CodecTypes,
556
+ Target,
557
+ Tables,
558
+ Models,
559
+ Types,
560
+ H,
561
+ ExtensionPacks,
562
+ Capabilities
563
+ > {
485
564
  return new SqlContractBuilder<
486
565
  CodecTypes,
487
566
  Target,
488
567
  Tables,
489
568
  Models,
569
+ Types,
490
570
  H,
491
571
  ExtensionPacks,
492
572
  Capabilities
493
573
  >({
494
574
  ...this.state,
495
- coreHash: hash,
575
+ storageHash: hash,
496
576
  });
497
577
  }
498
578
 
@@ -511,7 +591,8 @@ class SqlContractBuilder<
511
591
  Target,
512
592
  Tables & Record<TableName, ReturnType<T['build']>>,
513
593
  Models,
514
- CoreHash,
594
+ Types,
595
+ StorageHash,
515
596
  ExtensionPacks,
516
597
  Capabilities
517
598
  > {
@@ -525,7 +606,8 @@ class SqlContractBuilder<
525
606
  Target,
526
607
  Tables & Record<TableName, ReturnType<T['build']>>,
527
608
  Models,
528
- CoreHash,
609
+ Types,
610
+ StorageHash,
529
611
  ExtensionPacks,
530
612
  Capabilities
531
613
  >({
@@ -555,7 +637,8 @@ class SqlContractBuilder<
555
637
  Target,
556
638
  Tables,
557
639
  Models & Record<ModelName, ReturnType<M['build']>>,
558
- CoreHash,
640
+ Types,
641
+ StorageHash,
559
642
  ExtensionPacks,
560
643
  Capabilities
561
644
  > {
@@ -569,7 +652,8 @@ class SqlContractBuilder<
569
652
  Target,
570
653
  Tables,
571
654
  Models & Record<ModelName, ReturnType<M['build']>>,
572
- CoreHash,
655
+ Types,
656
+ StorageHash,
573
657
  ExtensionPacks,
574
658
  Capabilities
575
659
  >({
@@ -578,6 +662,37 @@ class SqlContractBuilder<
578
662
  Record<ModelName, ReturnType<M['build']>>,
579
663
  });
580
664
  }
665
+
666
+ storageType<Name extends string, Type extends StorageTypeInstance>(
667
+ name: Name,
668
+ typeInstance: Type,
669
+ ): SqlContractBuilder<
670
+ CodecTypes,
671
+ Target,
672
+ Tables,
673
+ Models,
674
+ Types & Record<Name, Type>,
675
+ StorageHash,
676
+ ExtensionPacks,
677
+ Capabilities
678
+ > {
679
+ return new SqlContractBuilder<
680
+ CodecTypes,
681
+ Target,
682
+ Tables,
683
+ Models,
684
+ Types & Record<Name, Type>,
685
+ StorageHash,
686
+ ExtensionPacks,
687
+ Capabilities
688
+ >({
689
+ ...this.state,
690
+ storageTypes: {
691
+ ...(this.state.storageTypes ?? {}),
692
+ [name]: typeInstance,
693
+ },
694
+ });
695
+ }
581
696
  }
582
697
 
583
698
  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({
@@ -88,6 +91,27 @@ const ModelSchema = type.declare<ModelDefinition>().type({
88
91
  relations: type({ '[string]': 'unknown' }),
89
92
  });
90
93
 
94
+ const ExecutionMutationDefaultValueSchema = type({
95
+ kind: "'generator'",
96
+ id: "'ulid' | 'nanoid' | 'uuidv7' | 'uuidv4' | 'cuid2' | 'ksuid'",
97
+ 'params?': 'Record<string, unknown>',
98
+ });
99
+
100
+ const ExecutionMutationDefaultSchema = type({
101
+ ref: {
102
+ table: 'string',
103
+ column: 'string',
104
+ },
105
+ 'onCreate?': ExecutionMutationDefaultValueSchema,
106
+ 'onUpdate?': ExecutionMutationDefaultValueSchema,
107
+ });
108
+
109
+ const ExecutionSchema = type({
110
+ mutations: {
111
+ defaults: ExecutionMutationDefaultSchema.array().readonly(),
112
+ },
113
+ });
114
+
91
115
  /**
92
116
  * Complete SqlContract schema for structural validation.
93
117
  * This validates the entire contract structure at once.
@@ -96,7 +120,8 @@ const SqlContractSchema = type({
96
120
  'schemaVersion?': "'1'",
97
121
  target: 'string',
98
122
  targetFamily: "'sql'",
99
- coreHash: 'string',
123
+ storageHash: 'string',
124
+ 'executionHash?': 'string',
100
125
  'profileHash?': 'string',
101
126
  'capabilities?': 'Record<string, Record<string, boolean>>',
102
127
  'extensionPacks?': 'Record<string, unknown>',
@@ -104,6 +129,7 @@ const SqlContractSchema = type({
104
129
  'sources?': 'Record<string, unknown>',
105
130
  models: type({ '[string]': ModelSchema }),
106
131
  storage: StorageSchema,
132
+ 'execution?': ExecutionSchema,
107
133
  });
108
134
 
109
135
  /**
@@ -198,13 +224,15 @@ export function computeMappings(
198
224
  * This checks that references (e.g., foreign keys, primary keys, uniques) point to storage objects that already exist.
199
225
  * Structural validation is expected to have already completed before this helper runs.
200
226
  *
227
+ * Rule: keep this focused on structural consistency only; capability/feature
228
+ * gating (e.g., defaults.*) belongs in migration/runtime verification, not here.
229
+ *
201
230
  * @param structurallyValidatedContract - The contract whose structure has already been validated
202
231
  * @throws Error if logical validation fails
203
232
  */
204
233
  function validateContractLogic(structurallyValidatedContract: SqlContract<SqlStorage>): void {
205
234
  const { storage, models } = structurallyValidatedContract;
206
235
  const tableNames = new Set(Object.keys(storage.tables));
207
- const typeInstanceNames = new Set(Object.keys(storage.types ?? {}));
208
236
 
209
237
  // Validate storage.types if present
210
238
  if (storage.types) {
@@ -235,11 +263,26 @@ function validateContractLogic(structurallyValidatedContract: SqlContract<SqlSto
235
263
  );
236
264
  }
237
265
 
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
- );
266
+ // Validate typeRef points to an existing storage.types key and matches codecId/nativeType
267
+ if (column.typeRef !== undefined) {
268
+ const referencedType = storage.types?.[column.typeRef];
269
+ if (!referencedType) {
270
+ throw new Error(
271
+ `Column "${columnName}" in table "${tableName}" references non-existent type instance "${column.typeRef}" (not found in storage.types)`,
272
+ );
273
+ }
274
+
275
+ if (column.codecId !== referencedType.codecId) {
276
+ throw new Error(
277
+ `Column "${columnName}" in table "${tableName}" has codecId "${column.codecId}" but references type instance "${column.typeRef}" with codecId "${referencedType.codecId}"`,
278
+ );
279
+ }
280
+
281
+ if (column.nativeType !== referencedType.nativeType) {
282
+ throw new Error(
283
+ `Column "${columnName}" in table "${tableName}" has nativeType "${column.nativeType}" but references type instance "${column.typeRef}" with nativeType "${referencedType.nativeType}"`,
284
+ );
285
+ }
243
286
  }
244
287
  }
245
288
  }