@prisma-next/sql-contract-ts 0.5.0-dev.80 → 0.5.0-dev.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract-ts",
3
- "version": "0.5.0-dev.80",
3
+ "version": "0.5.0-dev.82",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -9,12 +9,12 @@
9
9
  "arktype": "^2.1.29",
10
10
  "pathe": "^2.0.3",
11
11
  "ts-toolbelt": "^9.6.0",
12
- "@prisma-next/contract": "0.5.0-dev.80",
13
- "@prisma-next/config": "0.5.0-dev.80",
14
- "@prisma-next/contract-authoring": "0.5.0-dev.80",
15
- "@prisma-next/framework-components": "0.5.0-dev.80",
16
- "@prisma-next/sql-contract": "0.5.0-dev.80",
17
- "@prisma-next/utils": "0.5.0-dev.80"
12
+ "@prisma-next/config": "0.5.0-dev.82",
13
+ "@prisma-next/contract-authoring": "0.5.0-dev.82",
14
+ "@prisma-next/framework-components": "0.5.0-dev.82",
15
+ "@prisma-next/sql-contract": "0.5.0-dev.82",
16
+ "@prisma-next/utils": "0.5.0-dev.82",
17
+ "@prisma-next/contract": "0.5.0-dev.82"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@types/pg": "8.20.0",
@@ -22,9 +22,9 @@
22
22
  "tsdown": "0.22.0",
23
23
  "typescript": "5.9.3",
24
24
  "vitest": "4.1.5",
25
+ "@prisma-next/test-utils": "0.0.1",
25
26
  "@prisma-next/tsconfig": "0.0.0",
26
- "@prisma-next/tsdown": "0.0.0",
27
- "@prisma-next/test-utils": "0.0.1"
27
+ "@prisma-next/tsdown": "0.0.0"
28
28
  },
29
29
  "files": [
30
30
  "dist",
@@ -17,6 +17,12 @@ import {
17
17
  type StorageHashBase,
18
18
  } from '@prisma-next/contract/types';
19
19
  import type { CodecLookup } from '@prisma-next/framework-components/codec';
20
+ import { validateIndexTypes } from '@prisma-next/sql-contract/index-type-validation';
21
+ import {
22
+ createIndexTypeRegistry,
23
+ type IndexTypeMap,
24
+ type IndexTypeRegistration,
25
+ } from '@prisma-next/sql-contract/index-types';
20
26
  import {
21
27
  applyFkDefaults,
22
28
  type SqlStorage,
@@ -63,11 +69,37 @@ function encodeColumnDefault(
63
69
  };
64
70
  }
65
71
 
66
- function assertStorageSemantics(storage: SqlStorage): void {
67
- const semanticErrors = validateStorageSemantics(storage);
72
+ function assertStorageSemantics(
73
+ definition: ContractDefinition,
74
+ contract: Contract<SqlStorage>,
75
+ ): void {
76
+ const semanticErrors = validateStorageSemantics(contract.storage);
68
77
  if (semanticErrors.length > 0) {
69
78
  throw new Error(`Contract semantic validation failed: ${semanticErrors.join('; ')}`);
70
79
  }
80
+
81
+ const indexTypeRegistry = createIndexTypeRegistry();
82
+ const packsToRegister: ReadonlyArray<{ readonly id?: string; readonly indexTypes?: unknown }> = [
83
+ definition.target,
84
+ ...Object.values(definition.extensionPacks ?? {}),
85
+ ];
86
+ for (const pack of packsToRegister) {
87
+ const registration = pack.indexTypes;
88
+ if (registration === undefined) continue;
89
+ if (
90
+ typeof registration !== 'object' ||
91
+ registration === null ||
92
+ !Array.isArray((registration as { entries?: unknown }).entries)
93
+ ) {
94
+ throw new Error(
95
+ `Pack "${pack.id ?? '<unknown>'}" declares "indexTypes" but its value is not an IndexTypeRegistration (expected an object with an "entries" array; got ${typeof registration}).`,
96
+ );
97
+ }
98
+ for (const entry of (registration as IndexTypeRegistration<IndexTypeMap>).entries) {
99
+ indexTypeRegistry.register(entry);
100
+ }
101
+ }
102
+ validateIndexTypes(contract, indexTypeRegistry);
71
103
  }
72
104
 
73
105
  function assertKnownTargetModel(
@@ -277,8 +309,8 @@ export function buildSqlContractFromDefinition(
277
309
  indexes: (semanticModel.indexes ?? []).map((i) => ({
278
310
  columns: i.columns,
279
311
  ...ifDefined('name', i.name),
280
- ...ifDefined('using', i.using),
281
- ...ifDefined('config', i.config),
312
+ ...ifDefined('type', i.type),
313
+ ...ifDefined('options', i.options),
282
314
  })),
283
315
  foreignKeys,
284
316
  ...(semanticModel.id
@@ -447,7 +479,7 @@ export function buildSqlContractFromDefinition(
447
479
  meta: {},
448
480
  };
449
481
 
450
- assertStorageSemantics(contract.storage);
482
+ assertStorageSemantics(definition, contract);
451
483
 
452
484
  return contract;
453
485
  }
@@ -26,7 +26,14 @@ import type {
26
26
  TupleFromArgumentDescriptors,
27
27
  UnionToIntersection,
28
28
  } from './authoring-type-utils';
29
+ import type {
30
+ AnyRelationBuilder,
31
+ ContractModelBuilder,
32
+ IndexTypeMap,
33
+ ScalarFieldBuilder,
34
+ } from './contract-dsl';
29
35
  import { buildFieldPreset, field, model, rel } from './contract-dsl';
36
+ import type { MergeExtensionIndexTypes } from './contract-types';
30
37
 
31
38
  type ExtractTypeNamespaceFromPack<Pack> = Pack extends {
32
39
  readonly authoring?: { readonly type?: infer Namespace extends AuthoringTypeNamespace };
@@ -93,6 +100,33 @@ type TypeHelpersFromNamespace<Namespace> = {
93
100
 
94
101
  type CoreFieldHelpers = Pick<typeof field, 'column' | 'generated' | 'namedType'>;
95
102
 
103
+ type MergeAllPackIndexTypes<Family, Target, ExtensionPacks> = MergeExtensionIndexTypes<
104
+ { readonly __family: Family; readonly __target: Target } & (ExtensionPacks extends Record<
105
+ string,
106
+ unknown
107
+ >
108
+ ? ExtensionPacks
109
+ : Record<never, never>)
110
+ >;
111
+
112
+ type PackAwareModel<IndexTypes extends IndexTypeMap> = {
113
+ <
114
+ const ModelName extends string,
115
+ Fields extends Record<string, ScalarFieldBuilder>,
116
+ Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
117
+ >(
118
+ modelName: ModelName,
119
+ input: { readonly fields: Fields; readonly relations?: Relations },
120
+ ): ContractModelBuilder<ModelName, Fields, Relations, undefined, undefined, IndexTypes>;
121
+ <
122
+ Fields extends Record<string, ScalarFieldBuilder>,
123
+ Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
124
+ >(input: {
125
+ readonly fields: Fields;
126
+ readonly relations?: Relations;
127
+ }): ContractModelBuilder<undefined, Fields, Relations, undefined, undefined, IndexTypes>;
128
+ };
129
+
96
130
  export type ComposedAuthoringHelpers<
97
131
  Family extends FamilyPackRef<string>,
98
132
  Target extends TargetPackRef<'sql', string>,
@@ -104,7 +138,7 @@ export type ComposedAuthoringHelpers<
104
138
  ExtractFieldNamespaceFromPack<Target> &
105
139
  MergeExtensionFieldNamespaces<ExtensionPacks>
106
140
  >;
107
- readonly model: typeof model;
141
+ readonly model: PackAwareModel<MergeAllPackIndexTypes<Family, Target, ExtensionPacks>>;
108
142
  readonly rel: typeof rel;
109
143
  readonly type: TypeHelpersFromNamespace<
110
144
  ExtractTypeNamespaceFromPack<Family> &
@@ -29,8 +29,8 @@ export interface UniqueConstraintNode {
29
29
  export interface IndexNode {
30
30
  readonly columns: readonly string[];
31
31
  readonly name?: string;
32
- readonly using?: string;
33
- readonly config?: Record<string, unknown>;
32
+ readonly type?: string;
33
+ readonly options?: Record<string, unknown>;
34
34
  }
35
35
 
36
36
  export interface ForeignKeyNode {
@@ -465,7 +465,7 @@ export type RelationState =
465
465
  | ManyToManyRelation;
466
466
 
467
467
  type AnyRelationState = RelationState;
468
- type AnyRelationBuilder = RelationBuilder<AnyRelationState>;
468
+ export type AnyRelationBuilder = RelationBuilder<AnyRelationState>;
469
469
 
470
470
  type ApplyBelongsToRelationSqlSpec<
471
471
  State extends RelationState,
@@ -537,11 +537,21 @@ type ConstraintOptions<Name extends string | undefined = string | undefined> = {
537
537
  readonly name?: Name;
538
538
  };
539
539
 
540
- type IndexOptions<Name extends string | undefined = string | undefined> =
541
- ConstraintOptions<Name> & {
542
- readonly using?: string;
543
- readonly config?: Record<string, unknown>;
544
- };
540
+ export type IndexTypeMap = Record<string, { readonly options: unknown }>;
541
+
542
+ type IndexInput<
543
+ Name extends string | undefined,
544
+ IndexTypes extends IndexTypeMap,
545
+ > = keyof IndexTypes extends never
546
+ ? ConstraintOptions<Name>
547
+ :
548
+ | (ConstraintOptions<Name> & { readonly type?: never; readonly options?: never })
549
+ | {
550
+ readonly [K in keyof IndexTypes & string]: ConstraintOptions<Name> & {
551
+ readonly type: K;
552
+ readonly options: IndexTypes[K]['options'];
553
+ };
554
+ }[keyof IndexTypes & string];
545
555
 
546
556
  type ForeignKeyOptions<Name extends string | undefined = string | undefined> =
547
557
  ConstraintOptions<Name> & {
@@ -577,8 +587,8 @@ export type IndexConstraint<
577
587
  readonly kind: 'index';
578
588
  readonly fields: FieldNames;
579
589
  readonly name?: Name;
580
- readonly using?: string;
581
- readonly config?: Record<string, unknown>;
590
+ readonly type?: string;
591
+ readonly options?: Record<string, unknown>;
582
592
  };
583
593
 
584
594
  export type ForeignKeyConstraint<
@@ -623,7 +633,7 @@ function normalizeTargetFieldRefInput(input: TargetFieldRef | readonly TargetFie
623
633
  };
624
634
  }
625
635
 
626
- function createConstraintsDsl() {
636
+ function createConstraintsDsl<IndexTypes extends IndexTypeMap = Record<never, never>>() {
627
637
  function ref<ModelName extends string, FieldName extends string>(
628
638
  modelName: ModelName,
629
639
  fieldName: FieldName,
@@ -674,24 +684,26 @@ function createConstraintsDsl() {
674
684
  };
675
685
  }
676
686
 
677
- function index<FieldName extends string, Name extends string | undefined = undefined>(
678
- field: ColumnRef<FieldName>,
679
- options?: IndexOptions<Name>,
680
- ): IndexConstraint<readonly [FieldName], Name>;
681
687
  function index<FieldNames extends readonly string[], Name extends string | undefined = undefined>(
682
688
  fields: { readonly [K in keyof FieldNames]: ColumnRef<FieldNames[K] & string> },
683
- options?: IndexOptions<Name>,
689
+ options?: IndexInput<Name, IndexTypes>,
684
690
  ): IndexConstraint<FieldNames, Name>;
685
691
  function index(
686
- fieldOrFields: ColumnRef | readonly ColumnRef[],
687
- options?: IndexOptions,
692
+ fields: readonly ColumnRef[],
693
+ options?: {
694
+ readonly name?: string;
695
+ readonly type?: string;
696
+ readonly options?: unknown;
697
+ },
688
698
  ): IndexConstraint {
689
699
  return {
690
700
  kind: 'index',
691
- fields: normalizeFieldRefInput(fieldOrFields),
692
- ...(options?.name ? { name: options.name } : {}),
693
- ...(options?.using ? { using: options.using } : {}),
694
- ...(options?.config ? { config: options.config } : {}),
701
+ fields: normalizeFieldRefInput(fields),
702
+ ...(options?.name !== undefined ? { name: options.name } : {}),
703
+ ...(options?.type !== undefined ? { type: options.type } : {}),
704
+ ...(options?.options !== undefined
705
+ ? { options: options.options as Record<string, unknown> }
706
+ : {}),
695
707
  };
696
708
  }
697
709
 
@@ -776,9 +788,26 @@ type AttributeContext<Fields extends Record<string, ScalarFieldBuilder>> = {
776
788
  readonly constraints: Pick<ConstraintsDsl, 'id' | 'unique'>;
777
789
  };
778
790
 
779
- type SqlContext<Fields extends Record<string, ScalarFieldBuilder>> = {
791
+ type PackAwareIndex<IndexTypes extends IndexTypeMap> = <
792
+ FieldNames extends readonly string[],
793
+ Name extends string | undefined = undefined,
794
+ >(
795
+ fields: { readonly [K in keyof FieldNames]: ColumnRef<FieldNames[K] & string> },
796
+ options?: IndexInput<Name, IndexTypes>,
797
+ ) => IndexConstraint<FieldNames, Name>;
798
+
799
+ type PackAwareSqlConstraints<IndexTypes extends IndexTypeMap> = {
800
+ readonly foreignKey: ConstraintsDsl['foreignKey'];
801
+ readonly ref: ConstraintsDsl['ref'];
802
+ readonly index: PackAwareIndex<IndexTypes>;
803
+ };
804
+
805
+ export type SqlContext<
806
+ Fields extends Record<string, ScalarFieldBuilder>,
807
+ IndexTypes extends IndexTypeMap = Record<never, never>,
808
+ > = {
780
809
  readonly cols: FieldRefs<Fields>;
781
- readonly constraints: Pick<ConstraintsDsl, 'index' | 'foreignKey' | 'ref'>;
810
+ readonly constraints: PackAwareSqlConstraints<IndexTypes>;
782
811
  };
783
812
 
784
813
  function createFieldRefs<Fields extends Record<string, ScalarFieldBuilder>>(
@@ -829,8 +858,10 @@ function createAttributeConstraintsDsl(): AttributeContext<
829
858
  };
830
859
  }
831
860
 
832
- function createSqlConstraintsDsl(): SqlContext<Record<string, ScalarFieldBuilder>>['constraints'] {
833
- const constraints = createConstraintsDsl();
861
+ function createSqlConstraintsDsl<
862
+ IndexTypes extends IndexTypeMap = Record<never, never>,
863
+ >(): SqlContext<Record<string, ScalarFieldBuilder>, IndexTypes>['constraints'] {
864
+ const constraints = createConstraintsDsl<IndexTypes>();
834
865
  return {
835
866
  index: constraints.index,
836
867
  foreignKey: constraints.foreignKey,
@@ -946,12 +977,14 @@ export class ContractModelBuilder<
946
977
  Relations extends Record<string, AnyRelationBuilder> = Record<never, never>,
947
978
  AttributesSpec extends ModelAttributesSpec | undefined = undefined,
948
979
  SqlSpec extends SqlStageSpec | undefined = undefined,
980
+ IndexTypes extends IndexTypeMap = Record<never, never>,
949
981
  > {
950
982
  declare readonly __name: ModelName;
951
983
  declare readonly __fields: Fields;
952
984
  declare readonly __relations: Relations;
953
985
  declare readonly __attributes: AttributesSpec;
954
986
  declare readonly __sql: SqlSpec;
987
+ declare readonly __indexTypes: IndexTypes;
955
988
  readonly refs: ModelName extends string ? ModelTokenRefs<ModelName, Fields> : never;
956
989
 
957
990
  constructor(
@@ -961,7 +994,7 @@ export class ContractModelBuilder<
961
994
  readonly relations: Relations;
962
995
  },
963
996
  readonly attributesFactory?: StageInput<AttributeContext<Fields>, AttributesSpec>,
964
- readonly sqlFactory?: StageInput<SqlContext<Fields>, SqlSpec>,
997
+ readonly sqlFactory?: StageInput<SqlContext<Fields, IndexTypes>, SqlSpec>,
965
998
  ) {
966
999
  this.refs = (
967
1000
  stageOne.modelName ? createModelTokenRefs(stageOne.modelName, stageOne.fields) : undefined
@@ -970,7 +1003,7 @@ export class ContractModelBuilder<
970
1003
 
971
1004
  ref<FieldName extends keyof Fields & string>(
972
1005
  this: ModelName extends string
973
- ? ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, SqlSpec>
1006
+ ? ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, SqlSpec, IndexTypes>
974
1007
  : never,
975
1008
  fieldName: FieldName,
976
1009
  ): TargetFieldRef<ModelName & string, FieldName> {
@@ -989,7 +1022,14 @@ export class ContractModelBuilder<
989
1022
 
990
1023
  relations<const NextRelations extends Record<string, AnyRelationBuilder>>(
991
1024
  relations: NextRelations,
992
- ): ContractModelBuilder<ModelName, Fields, Relations & NextRelations, AttributesSpec, SqlSpec> {
1025
+ ): ContractModelBuilder<
1026
+ ModelName,
1027
+ Fields,
1028
+ Relations & NextRelations,
1029
+ AttributesSpec,
1030
+ SqlSpec,
1031
+ IndexTypes
1032
+ > {
993
1033
  const duplicateRelationName = findDuplicateRelationName(this.stageOne.relations, relations);
994
1034
  if (duplicateRelationName) {
995
1035
  throw new Error(
@@ -1015,15 +1055,15 @@ export class ContractModelBuilder<
1015
1055
  AttributeContext<Fields>,
1016
1056
  ValidateAttributesStageSpec<Fields, SqlSpec, NextAttributesSpec>
1017
1057
  >,
1018
- ): ContractModelBuilder<ModelName, Fields, Relations, NextAttributesSpec, SqlSpec> {
1058
+ ): ContractModelBuilder<ModelName, Fields, Relations, NextAttributesSpec, SqlSpec, IndexTypes> {
1019
1059
  return new ContractModelBuilder(this.stageOne, specOrFactory, this.sqlFactory);
1020
1060
  }
1021
1061
 
1022
1062
  sql<const NextSqlSpec extends SqlStageSpec>(
1023
- specOrFactory: StageInput<SqlContext<Fields>, NextSqlSpec>,
1063
+ specOrFactory: StageInput<SqlContext<Fields, IndexTypes>, NextSqlSpec>,
1024
1064
  ): [ValidateSqlStageSpec<Fields, AttributesSpec, NextSqlSpec>] extends [never]
1025
- ? ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, never>
1026
- : ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, NextSqlSpec> {
1065
+ ? ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, never, IndexTypes>
1066
+ : ContractModelBuilder<ModelName, Fields, Relations, AttributesSpec, NextSqlSpec, IndexTypes> {
1027
1067
  // Conditional return type cannot be verified by the implementation; the runtime value is always a valid ContractModelBuilder regardless of the validation outcome (validation is type-level only).
1028
1068
  return new ContractModelBuilder(this.stageOne, this.attributesFactory, specOrFactory) as never;
1029
1069
  }
@@ -1045,7 +1085,7 @@ export class ContractModelBuilder<
1045
1085
  }
1046
1086
  return buildStageSpec(this.sqlFactory, {
1047
1087
  cols: createColumnRefs(this.stageOne.fields),
1048
- constraints: createSqlConstraintsDsl() as SqlContext<Fields>['constraints'],
1088
+ constraints: createSqlConstraintsDsl<IndexTypes>(),
1049
1089
  });
1050
1090
  }
1051
1091
  }
@@ -1,5 +1,6 @@
1
1
  import type { ColumnTypeDescriptor } from '@prisma-next/framework-components/codec';
2
2
  import type { StorageTypeInstance } from '@prisma-next/sql-contract/types';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
3
4
  import type {
4
5
  ContractDefinition,
5
6
  FieldNode,
@@ -573,9 +574,9 @@ function resolveModelNode(
573
574
  })) satisfies readonly UniqueConstraintNode[];
574
575
  const indexes = (spec.sqlSpec?.indexes ?? []).map((index) => ({
575
576
  columns: mapFieldNamesToColumnNames(spec.modelName, index.fields, spec.fieldToColumn),
576
- ...(index.name ? { name: index.name } : {}),
577
- ...(index.using ? { using: index.using } : {}),
578
- ...(index.config ? { config: index.config } : {}),
577
+ ...ifDefined('name', index.name),
578
+ ...ifDefined('type', index.type),
579
+ ...ifDefined('options', index.options),
579
580
  })) satisfies readonly IndexNode[];
580
581
  const foreignKeys = resolveForeignKeyNodes(spec, allSpecs);
581
582
  const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) =>
@@ -5,6 +5,7 @@ import type {
5
5
  StorageHashBase,
6
6
  } from '@prisma-next/contract/types';
7
7
  import type { ExtensionPackRef, TargetPackRef } from '@prisma-next/framework-components/components';
8
+ import type { IndexTypeRegistration } from '@prisma-next/sql-contract/index-types';
8
9
  import type {
9
10
  ContractWithTypeMaps,
10
11
  Index,
@@ -34,6 +35,28 @@ type MergeExtensionCodecTypesSafe<Packs> =
34
35
  : MergeExtensionCodecTypes<Packs>
35
36
  : Record<string, never>;
36
37
 
38
+ export type ExtractIndexTypesFromPack<P> = P extends {
39
+ readonly indexTypes: IndexTypeRegistration<infer M>;
40
+ }
41
+ ? M
42
+ : Record<never, never>;
43
+
44
+ type AllIndexTypeLiterals<Packs> =
45
+ Packs extends Record<string, unknown>
46
+ ? { [K in keyof Packs]: keyof ExtractIndexTypesFromPack<Packs[K]> }[keyof Packs] & string
47
+ : never;
48
+
49
+ export type MergeExtensionIndexTypes<Packs extends Record<string, unknown>> = {
50
+ readonly [Lit in AllIndexTypeLiterals<Packs>]: Extract<
51
+ {
52
+ [K in keyof Packs]: Lit extends keyof ExtractIndexTypesFromPack<Packs[K]>
53
+ ? ExtractIndexTypesFromPack<Packs[K]>[Lit]
54
+ : never;
55
+ }[keyof Packs],
56
+ { readonly options: unknown }
57
+ >;
58
+ };
59
+
37
60
  export type MergeExtensionPackRefs<
38
61
  Existing extends Record<string, unknown> | undefined,
39
62
  Added extends Record<string, ExtensionPackRef<'sql', string>>,
@@ -64,6 +87,16 @@ type CodecTypesFromDefinition<Definition> = ExtractCodecTypesFromPack<
64
87
  > &
65
88
  MergeExtensionCodecTypesSafe<DefinitionExtensionPacks<Definition>>;
66
89
 
90
+ type DefinitionTarget<Definition> = Definition extends { readonly target: infer Target }
91
+ ? Target
92
+ : never;
93
+
94
+ type AllPacks<Definition> = DefinitionExtensionPacks<Definition> & {
95
+ readonly __target: DefinitionTarget<Definition>;
96
+ };
97
+
98
+ export type IndexTypesFromDefinition<Definition> = MergeExtensionIndexTypes<AllPacks<Definition>>;
99
+
67
100
  type DefinitionModels<Definition> = Definition extends {
68
101
  readonly models?: unknown;
69
102
  }