@prisma-next/sql-contract-ts 0.16.0-dev.2 → 0.16.0-dev.21

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,27 +1,27 @@
1
1
  {
2
2
  "name": "@prisma-next/sql-contract-ts",
3
- "version": "0.16.0-dev.2",
3
+ "version": "0.16.0-dev.21",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "SQL-specific TypeScript contract authoring surface for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/config": "0.16.0-dev.2",
10
- "@prisma-next/contract": "0.16.0-dev.2",
11
- "@prisma-next/contract-authoring": "0.16.0-dev.2",
12
- "@prisma-next/framework-components": "0.16.0-dev.2",
13
- "@prisma-next/sql-contract": "0.16.0-dev.2",
14
- "@prisma-next/utils": "0.16.0-dev.2",
9
+ "@prisma-next/config": "0.16.0-dev.21",
10
+ "@prisma-next/contract": "0.16.0-dev.21",
11
+ "@prisma-next/contract-authoring": "0.16.0-dev.21",
12
+ "@prisma-next/framework-components": "0.16.0-dev.21",
13
+ "@prisma-next/sql-contract": "0.16.0-dev.21",
14
+ "@prisma-next/utils": "0.16.0-dev.21",
15
15
  "arktype": "^2.2.2",
16
16
  "pathe": "^2.0.3",
17
17
  "ts-toolbelt": "^9.6.0"
18
18
  },
19
19
  "devDependencies": {
20
- "@prisma-next/test-utils": "0.16.0-dev.2",
21
- "@prisma-next/tsconfig": "0.16.0-dev.2",
20
+ "@prisma-next/test-utils": "0.16.0-dev.21",
21
+ "@prisma-next/tsconfig": "0.16.0-dev.21",
22
22
  "@types/pg": "8.20.0",
23
23
  "pg": "8.22.0",
24
- "@prisma-next/tsdown": "0.16.0-dev.2",
24
+ "@prisma-next/tsdown": "0.16.0-dev.21",
25
25
  "tsdown": "0.22.8",
26
26
  "typescript": "5.9.3",
27
27
  "vitest": "4.1.10"
@@ -26,12 +26,12 @@
26
26
  },
27
27
  "storageHash": {
28
28
  "type": "string",
29
- "pattern": "^sha256:[a-f0-9]{64}$",
29
+ "pattern": "^[a-f0-9]{64}$",
30
30
  "description": "SHA-256 hash of the storage section (DB-satisfied expectations)"
31
31
  },
32
32
  "profileHash": {
33
33
  "type": "string",
34
- "pattern": "^sha256:[a-f0-9]{64}$",
34
+ "pattern": "^[a-f0-9]{64}$",
35
35
  "description": "SHA-256 hash of the pinned capability profile (capability keys/variants and adapter pins)"
36
36
  },
37
37
  "capabilities": {
@@ -44,7 +44,7 @@
44
44
  }
45
45
  }
46
46
  },
47
- "extensionPacks": {
47
+ "extensions": {
48
48
  "type": "object",
49
49
  "description": "Extension packs and their configuration",
50
50
  "additionalProperties": true
@@ -104,7 +104,7 @@
104
104
  "properties": {
105
105
  "executionHash": {
106
106
  "type": "string",
107
- "pattern": "^sha256:[a-f0-9]{64}$",
107
+ "pattern": "^[a-f0-9]{64}$",
108
108
  "description": "SHA-256 hash of the execution section (client-side behavior)"
109
109
  },
110
110
  "mutations": {
@@ -10,6 +10,7 @@ import {
10
10
  validateAuthoringHelperArguments,
11
11
  } from '@prisma-next/framework-components/authoring';
12
12
  import { type StorageTypeInstance, toStorageTypeInstance } from '@prisma-next/sql-contract/types';
13
+ import { contractError } from './contract-errors';
13
14
 
14
15
  export type RuntimeNamedConstraintSpec = {
15
16
  readonly name?: string;
@@ -33,8 +34,10 @@ const blockedSegments = new Set(['__proto__', 'constructor', 'prototype']);
33
34
 
34
35
  function assertSafeHelperKey(key: string, path: readonly string[]): void {
35
36
  if (blockedSegments.has(key)) {
36
- throw new Error(
37
+ throw contractError(
38
+ 'CONTRACT.PACK_CONTRIBUTION_INVALID',
37
39
  `Invalid authoring helper "${[...path, key].join('.')}". Helper path segments must not use "${key}".`,
40
+ { meta: { helperPath: [...path, key].join('.'), segment: key } },
38
41
  );
39
42
  }
40
43
  }
@@ -83,8 +86,16 @@ export function createFieldPresetHelper<Result>(options: {
83
86
  const declaredArguments = options.descriptor.args ?? [];
84
87
 
85
88
  if (acceptsNamedConstraintOptions && rawArgs.length > declaredArguments.length + 1) {
86
- throw new Error(
89
+ throw contractError(
90
+ 'CONTRACT.ARGUMENT_INVALID',
87
91
  `${options.helperPath} expects at most ${declaredArguments.length + 1} argument(s), received ${rawArgs.length}`,
92
+ {
93
+ meta: {
94
+ helperPath: options.helperPath,
95
+ expected: declaredArguments.length + 1,
96
+ received: rawArgs.length,
97
+ },
98
+ },
88
99
  );
89
100
  }
90
101
 
@@ -94,8 +105,10 @@ export function createFieldPresetHelper<Result>(options: {
94
105
  if (acceptsNamedConstraintOptions && rawArgs.length === declaredArguments.length + 1) {
95
106
  const maybeNamedConstraintOptions = rawArgs.at(-1);
96
107
  if (!isNamedConstraintOptionsLike(maybeNamedConstraintOptions)) {
97
- throw new Error(
108
+ throw contractError(
109
+ 'CONTRACT.ARGUMENT_INVALID',
98
110
  `${options.helperPath} accepts an optional trailing { name?: string } constraint options object`,
111
+ { meta: { helperPath: options.helperPath } },
99
112
  );
100
113
  }
101
114
  namedConstraintOptions = maybeNamedConstraintOptions;
@@ -58,6 +58,7 @@ import { validateStorageSemantics } from '@prisma-next/sql-contract/validators';
58
58
  import { deriveValueSetFromEntity } from '@prisma-next/sql-contract/value-set-derivation-hook';
59
59
  import { blindCast } from '@prisma-next/utils/casts';
60
60
  import { ifDefined } from '@prisma-next/utils/defined';
61
+ import { InternalError } from '@prisma-next/utils/internal-error';
61
62
  import type {
62
63
  ContractDefinition,
63
64
  FieldNode,
@@ -65,6 +66,7 @@ import type {
65
66
  RelationNode,
66
67
  ValueObjectFieldNode,
67
68
  } from './contract-definition';
69
+ import { contractError } from './contract-errors';
68
70
 
69
71
  type DomainFieldRef =
70
72
  | { readonly kind: 'scalar'; readonly many?: boolean }
@@ -92,7 +94,7 @@ function encodeColumnDefault(
92
94
  }
93
95
  if (many) {
94
96
  if (!Array.isArray(defaultInput.value)) {
95
- throw new Error(
97
+ throw new InternalError(
96
98
  `Literal default on a list column must be an array; received ${typeof defaultInput.value}. ` +
97
99
  'A scalar default on a list field must be rejected at the authoring surface.',
98
100
  );
@@ -114,13 +116,17 @@ function assertStorageSemantics(
114
116
  ): void {
115
117
  const semanticErrors = validateStorageSemantics(contract.storage);
116
118
  if (semanticErrors.length > 0) {
117
- throw new Error(`Contract semantic validation failed: ${semanticErrors.join('; ')}`);
119
+ throw contractError(
120
+ 'CONTRACT.VALIDATION_FAILED',
121
+ `Contract semantic validation failed: ${semanticErrors.join('; ')}`,
122
+ { meta: { errors: semanticErrors } },
123
+ );
118
124
  }
119
125
 
120
126
  const indexTypeRegistry = createIndexTypeRegistry();
121
127
  const packsToRegister: ReadonlyArray<{ readonly id?: string; readonly indexTypes?: unknown }> = [
122
128
  definition.target,
123
- ...Object.values(definition.extensionPacks ?? {}),
129
+ ...Object.values(definition.extensions ?? {}),
124
130
  ];
125
131
  for (const pack of packsToRegister) {
126
132
  const registration = pack.indexTypes;
@@ -130,8 +136,10 @@ function assertStorageSemantics(
130
136
  registration === null ||
131
137
  !Array.isArray((registration as { entries?: unknown }).entries)
132
138
  ) {
133
- throw new Error(
139
+ throw contractError(
140
+ 'CONTRACT.PACK_CONTRIBUTION_INVALID',
134
141
  `Pack "${pack.id ?? '<unknown>'}" declares "indexTypes" but its value is not an IndexTypeRegistration (expected an object with an "entries" array; got ${typeof registration}).`,
142
+ { meta: { packId: pack.id, contribution: 'indexTypes', reason: 'invalid-shape' } },
135
143
  );
136
144
  }
137
145
  for (const entry of (registration as IndexTypeRegistration<IndexTypeMap>).entries) {
@@ -158,8 +166,10 @@ function assertKnownTargetModel(
158
166
  targetNamespaceId !== undefined && targetNamespaceId.length > 0
159
167
  ? `${targetNamespaceId}.${targetModelName}`
160
168
  : targetModelName;
161
- throw new Error(
169
+ throw contractError(
170
+ 'CONTRACT.MODEL_UNKNOWN',
162
171
  `${context} on model "${sourceModelName}" references unknown model "${qualified}"`,
172
+ { meta: { sourceModel: sourceModelName, targetModel: qualified, context } },
163
173
  );
164
174
  }
165
175
  return targetModel;
@@ -172,8 +182,17 @@ function assertTargetTableMatches(
172
182
  context: string,
173
183
  ): void {
174
184
  if (targetModel.tableName !== referencedTableName) {
175
- throw new Error(
185
+ throw contractError(
186
+ 'CONTRACT.TABLE_MISMATCH',
176
187
  `${context} on model "${sourceModelName}" references table "${referencedTableName}" but model "${targetModel.modelName}" maps to "${targetModel.tableName}"`,
188
+ {
189
+ meta: {
190
+ sourceModel: sourceModelName,
191
+ referencedTable: referencedTableName,
192
+ mappedTable: targetModel.tableName,
193
+ context,
194
+ },
195
+ },
177
196
  );
178
197
  }
179
198
  }
@@ -310,8 +329,10 @@ function collectEntityFromColumn(
310
329
  const forKind = forNs[entityRef.entityKind] ?? {};
311
330
  const existing = forKind[entityRef.entityName];
312
331
  if (existing !== undefined && existing !== entityRef.entity) {
313
- throw new Error(
332
+ throw contractError(
333
+ 'CONTRACT.NAME_DUPLICATE',
314
334
  `buildSqlContractFromDefinition: two different "${entityRef.entityKind}" entities named "${entityRef.entityName}" in namespace "${namespaceId}" — pack-entity names must be unique per namespace.`,
335
+ { meta: { kind: entityRef.entityKind, name: entityRef.entityName, namespaceId } },
315
336
  );
316
337
  }
317
338
  forKind[entityRef.entityName] = entityRef.entity;
@@ -344,8 +365,10 @@ function mergeColumnAndAttachedEntities(
344
365
  for (const [name, entity] of Object.entries(columnForKind ?? {})) {
345
366
  const existing = attachedForKind?.[name];
346
367
  if (existing !== undefined && existing !== entity) {
347
- throw new Error(
368
+ throw contractError(
369
+ 'CONTRACT.NAME_DUPLICATE',
348
370
  `buildSqlContractFromDefinition: two different "${kind}" entities named "${name}" in namespace "${namespaceId}" — a column-referenced entity conflicts with an attached one; pack-entity names must be unique per namespace.`,
371
+ { meta: { kind, name, namespaceId } },
349
372
  );
350
373
  }
351
374
  }
@@ -377,8 +400,10 @@ function buildThroughDescriptor(
377
400
  defaultNamespaceId: string,
378
401
  ): ContractRelationThrough {
379
402
  if (!tableNamespaceByName.has(through.table)) {
380
- throw new Error(
403
+ throw contractError(
404
+ 'CONTRACT.MODEL_UNKNOWN',
381
405
  `buildSqlContractFromDefinition: junction table "${through.table}" for relation "${modelName}.${fieldName}" is not a declared model.`,
406
+ { meta: { sourceModel: modelName, relationName: fieldName, junctionTable: through.table } },
382
407
  );
383
408
  }
384
409
  // Junction table names are unique per namespace, not globally. Prefer the
@@ -405,8 +430,10 @@ function targetColumnsForJunction(targetModel: ModelNode, fieldName: string): re
405
430
  if (firstUnique) {
406
431
  return firstUnique.columns;
407
432
  }
408
- throw new Error(
433
+ throw contractError(
434
+ 'CONTRACT.IDENTITY_INVALID',
409
435
  `M:N target model "${targetModel.modelName}" (relation field "${fieldName}") has no primary id or unique key to derive junction targetColumns.`,
436
+ { meta: { modelName: targetModel.modelName, reason: 'no-key-for-junction-target-columns' } },
410
437
  );
411
438
  }
412
439
 
@@ -515,8 +542,10 @@ function assertNoManagedEntityKinds(
515
542
  if (entitiesForNs === undefined) return;
516
543
  for (const kind of Object.keys(entitiesForNs)) {
517
544
  if (MANAGED_ENTRY_KINDS.has(kind)) {
518
- throw new Error(
545
+ throw contractError(
546
+ 'CONTRACT.ENTITY_KIND_INVALID',
519
547
  `buildSqlContractFromDefinition: attached entity in namespace "${namespaceId}" declares entry kind "${kind}", which is managed by the framework (table/valueSet) and cannot be attached.`,
548
+ { meta: { entityKind: kind, namespaceId } },
520
549
  );
521
550
  }
522
551
  }
@@ -528,7 +557,7 @@ function assertNoManagedEntityKinds(
528
557
  * the same string a pack entity's entries-map key (`entries.<kind>`) uses.
529
558
  * Mirrors `contract-psl`'s `buildEntityTypesByDiscriminator`, recomposed here
530
559
  * from the packs `ContractDefinition` already carries (`target` +
531
- * `extensionPacks`) since the TS assembler has no single pre-merged
560
+ * `extensions`) since the TS assembler has no single pre-merged
532
561
  * `AuthoringContributions` input to read the way the PSL interpreter does.
533
562
  */
534
563
  function collectEntityTypeDescriptorsByDiscriminator(
@@ -544,7 +573,7 @@ function collectEntityTypeDescriptorsByDiscriminator(
544
573
  }
545
574
  }
546
575
  };
547
- const components = [definition.target, ...Object.values(definition.extensionPacks ?? {})];
576
+ const components = [definition.target, ...Object.values(definition.extensions ?? {})];
548
577
  for (const component of components) {
549
578
  const entityTypes = component.authoring?.entityTypes;
550
579
  if (entityTypes !== undefined) {
@@ -599,8 +628,10 @@ function mergeNamespaceValueSets(
599
628
  if (enumValueSets !== undefined && packValueSets !== undefined) {
600
629
  for (const name of Object.keys(packValueSets)) {
601
630
  if (Object.hasOwn(enumValueSets, name)) {
602
- throw new Error(
631
+ throw contractError(
632
+ 'CONTRACT.NAME_DUPLICATE',
603
633
  `buildSqlContractFromDefinition: value-set "${name}" in namespace "${namespaceId}" is derived from both an enum and a pack entity — names must be unique per namespace.`,
634
+ { meta: { kind: 'valueSet', name, namespaceId } },
604
635
  );
605
636
  }
606
637
  }
@@ -690,13 +721,29 @@ export function buildSqlContractFromDefinition(
690
721
  : undefined;
691
722
  if (executionDefaultPhases) {
692
723
  if (field.default !== undefined) {
693
- throw new Error(
724
+ throw contractError(
725
+ 'CONTRACT.DEFAULT_INVALID',
694
726
  `Field "${semanticModel.modelName}.${field.fieldName}" cannot define both default and executionDefaults.`,
727
+ {
728
+ meta: {
729
+ modelName: semanticModel.modelName,
730
+ fieldName: field.fieldName,
731
+ reason: 'default-and-executionDefaults',
732
+ },
733
+ },
695
734
  );
696
735
  }
697
736
  if (field.nullable) {
698
- throw new Error(
737
+ throw contractError(
738
+ 'CONTRACT.DEFAULT_INVALID',
699
739
  `Field "${semanticModel.modelName}.${field.fieldName}" cannot be nullable when executionDefaults are present.`,
740
+ {
741
+ meta: {
742
+ modelName: semanticModel.modelName,
743
+ fieldName: field.fieldName,
744
+ reason: 'nullable-with-executionDefaults',
745
+ },
746
+ },
700
747
  );
701
748
  }
702
749
  }
@@ -914,8 +961,10 @@ export function buildSqlContractFromDefinition(
914
961
  tablesByNamespace[namespaceId] = nsTables;
915
962
  }
916
963
  if (nsTables[tableName] !== undefined) {
917
- throw new Error(
964
+ throw contractError(
965
+ 'CONTRACT.NAME_DUPLICATE',
918
966
  `buildSqlContractFromDefinition: duplicate table "${tableName}" in namespace "${namespaceId}".`,
967
+ { meta: { kind: 'table', name: tableName, namespaceId } },
919
968
  );
920
969
  }
921
970
  nsTables[tableName] = tableInput;
@@ -978,8 +1027,16 @@ export function buildSqlContractFromDefinition(
978
1027
 
979
1028
  if (relation.cardinality === 'N:M') {
980
1029
  if (!relation.through) {
981
- throw new Error(
1030
+ throw contractError(
1031
+ 'CONTRACT.RELATION_INVALID',
982
1032
  `Relation "${semanticModel.modelName}.${relation.fieldName}" with cardinality "N:M" requires through metadata`,
1033
+ {
1034
+ meta: {
1035
+ modelName: semanticModel.modelName,
1036
+ relationName: relation.fieldName,
1037
+ reason: 'many-to-many-missing-through',
1038
+ },
1039
+ },
983
1040
  );
984
1041
  }
985
1042
  modelRelations[relation.fieldName] = {
@@ -1062,8 +1119,16 @@ export function buildSqlContractFromDefinition(
1062
1119
  const storageValueSetsByNs: Record<string, Record<string, StorageValueSetInput>> = {};
1063
1120
  for (const [enumName, handle] of Object.entries(definition.enums ?? {})) {
1064
1121
  if (enumName !== handle.enumName) {
1065
- throw new Error(
1122
+ throw contractError(
1123
+ 'CONTRACT.ENUM_INVALID',
1066
1124
  `enum declaration key "${enumName}" must match enumType name "${handle.enumName}". Aliases are not supported.`,
1125
+ {
1126
+ meta: {
1127
+ enumName: handle.enumName,
1128
+ declarationKey: enumName,
1129
+ reason: 'key-name-mismatch',
1130
+ },
1131
+ },
1067
1132
  );
1068
1133
  }
1069
1134
  const nsId = defaultNamespaceId;
@@ -1154,21 +1219,21 @@ export function buildSqlContractFromDefinition(
1154
1219
  }
1155
1220
  : undefined;
1156
1221
 
1157
- const extensionNamespaces = definition.extensionPacks
1158
- ? Object.values(definition.extensionPacks).map((pack) => pack.id)
1222
+ const extensionNamespaces = definition.extensions
1223
+ ? Object.values(definition.extensions).map((pack) => pack.id)
1159
1224
  : undefined;
1160
1225
 
1161
- const extensionPacks: Record<string, unknown> = { ...(definition.extensionPacks || {}) };
1226
+ const extensions: Record<string, unknown> = { ...(definition.extensions || {}) };
1162
1227
  if (extensionNamespaces) {
1163
1228
  for (const namespace of extensionNamespaces) {
1164
- if (!Object.hasOwn(extensionPacks, namespace)) {
1165
- extensionPacks[namespace] = {};
1229
+ if (!Object.hasOwn(extensions, namespace)) {
1230
+ extensions[namespace] = {};
1166
1231
  }
1167
1232
  }
1168
1233
  }
1169
1234
 
1170
- const extensionPackCapabilitySources = definition.extensionPacks
1171
- ? Object.values(definition.extensionPacks).map(
1235
+ const extensionPackCapabilitySources = definition.extensions
1236
+ ? Object.values(definition.extensions).map(
1172
1237
  (pack) => pack.capabilities as CapabilityMatrix | undefined,
1173
1238
  )
1174
1239
  : [];
@@ -1258,7 +1323,7 @@ export function buildSqlContractFromDefinition(
1258
1323
  roots,
1259
1324
  storage,
1260
1325
  ...(executionWithHash ? { execution: executionWithHash } : {}),
1261
- extensionPacks,
1326
+ extensions,
1262
1327
  capabilities,
1263
1328
  profileHash,
1264
1329
  meta: {},
@@ -37,6 +37,7 @@ import type {
37
37
  ScalarFieldBuilder,
38
38
  } from './contract-dsl';
39
39
  import { buildFieldPreset, field, model, rel } from './contract-dsl';
40
+ import { contractError } from './contract-errors';
40
41
  import type { MergeExtensionIndexTypes } from './contract-types';
41
42
 
42
43
  type ExtractTypeNamespaceFromPack<Pack> = ExtractAuthoringNamespaceFromPack<
@@ -55,16 +56,16 @@ type ExtractEntitiesNamespaceFromPack<Pack> = ExtractAuthoringNamespaceFromPack<
55
56
  Record<never, never>
56
57
  >;
57
58
 
58
- type MergeExtensionTypeNamespaces<ExtensionPacks> = MergeExtensionAuthoringNamespaces<
59
- ExtensionPacks,
59
+ type MergeExtensionTypeNamespaces<Extensions> = MergeExtensionAuthoringNamespaces<
60
+ Extensions,
60
61
  'type'
61
62
  >;
62
- type MergeExtensionFieldNamespaces<ExtensionPacks> = MergeExtensionAuthoringNamespaces<
63
- ExtensionPacks,
63
+ type MergeExtensionFieldNamespaces<Extensions> = MergeExtensionAuthoringNamespaces<
64
+ Extensions,
64
65
  'field'
65
66
  >;
66
- type MergeExtensionEntityNamespaces<ExtensionPacks> = MergeExtensionAuthoringNamespaces<
67
- ExtensionPacks,
67
+ type MergeExtensionEntityNamespaces<Extensions> = MergeExtensionAuthoringNamespaces<
68
+ Extensions,
68
69
  'entityTypes'
69
70
  >;
70
71
 
@@ -100,12 +101,12 @@ type TypeHelpersFromNamespace<Namespace> = {
100
101
 
101
102
  type CoreFieldHelpers = Pick<typeof field, 'column' | 'generated' | 'namedType'>;
102
103
 
103
- type MergeAllPackIndexTypes<Family, Target, ExtensionPacks> = MergeExtensionIndexTypes<
104
- { readonly __family: Family; readonly __target: Target } & (ExtensionPacks extends Record<
104
+ type MergeAllPackIndexTypes<Family, Target, Extensions> = MergeExtensionIndexTypes<
105
+ { readonly __family: Family; readonly __target: Target } & (Extensions extends Record<
105
106
  string,
106
107
  unknown
107
108
  >
108
- ? ExtensionPacks
109
+ ? Extensions
109
110
  : Record<never, never>)
110
111
  >;
111
112
 
@@ -131,24 +132,24 @@ type PackAwareModel<IndexTypes extends IndexTypeMap> = {
131
132
  export type ComposedAuthoringHelpers<
132
133
  Family extends FamilyPackRef<string>,
133
134
  Target extends TargetPackRef<'sql', string>,
134
- ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined,
135
+ Extensions extends Record<string, ExtensionPackRef<'sql', string>> | undefined,
135
136
  > = EntityHelpersFromNamespace<
136
137
  ExtractEntitiesNamespaceFromPack<Family> &
137
138
  ExtractEntitiesNamespaceFromPack<Target> &
138
- MergeExtensionEntityNamespaces<ExtensionPacks>
139
+ MergeExtensionEntityNamespaces<Extensions>
139
140
  > & {
140
141
  readonly field: CoreFieldHelpers &
141
142
  FieldHelpersFromNamespace<
142
143
  ExtractFieldNamespaceFromPack<Family> &
143
144
  ExtractFieldNamespaceFromPack<Target> &
144
- MergeExtensionFieldNamespaces<ExtensionPacks>
145
+ MergeExtensionFieldNamespaces<Extensions>
145
146
  >;
146
- readonly model: PackAwareModel<MergeAllPackIndexTypes<Family, Target, ExtensionPacks>>;
147
+ readonly model: PackAwareModel<MergeAllPackIndexTypes<Family, Target, Extensions>>;
147
148
  readonly rel: typeof rel;
148
149
  readonly type: TypeHelpersFromNamespace<
149
150
  ExtractTypeNamespaceFromPack<Family> &
150
151
  ExtractTypeNamespaceFromPack<Target> &
151
- MergeExtensionTypeNamespaces<ExtensionPacks>
152
+ MergeExtensionTypeNamespaces<Extensions>
152
153
  >;
153
154
  };
154
155
 
@@ -224,8 +225,10 @@ const RESERVED_HELPER_KEYS: readonly string[] = ['field', 'model', 'rel', 'type'
224
225
  function assertNoBuiltInEntityCollisions(namespace: AuthoringEntityTypeNamespace): void {
225
226
  const collisions = Object.keys(namespace).filter((name) => RESERVED_HELPER_KEYS.includes(name));
226
227
  if (collisions.length > 0) {
227
- throw new Error(
228
+ throw contractError(
229
+ 'CONTRACT.PACK_CONTRIBUTION_INVALID',
228
230
  `Pack-contributed entity type(s) ${collisions.map((c) => `"${c}"`).join(', ')} collide with the reserved built-in helper key(s) on the composed helpers surface. Reserved keys: ${RESERVED_HELPER_KEYS.map((k) => `"${k}"`).join(', ')}.`,
231
+ { meta: { collisions, reservedKeys: RESERVED_HELPER_KEYS } },
229
232
  );
230
233
  }
231
234
  }
@@ -252,8 +255,10 @@ function createComposedFieldHelpers(
252
255
  const coreHelperNames = new Set(Object.keys(coreFieldHelpers));
253
256
  for (const helperName of Object.keys(helperNamespace)) {
254
257
  if (coreHelperNames.has(helperName)) {
255
- throw new Error(
258
+ throw contractError(
259
+ 'CONTRACT.PACK_CONTRIBUTION_INVALID',
256
260
  `Duplicate authoring field helper "${helperName}". Core field helpers reserve that name.`,
261
+ { meta: { helperName } },
257
262
  );
258
263
  }
259
264
  }
@@ -267,14 +272,14 @@ function createComposedFieldHelpers(
267
272
  export function createComposedAuthoringHelpers<
268
273
  Family extends FamilyPackRef<string>,
269
274
  Target extends TargetPackRef<'sql', string>,
270
- ExtensionPacks extends Record<string, ExtensionPackRef<'sql', string>> | undefined,
275
+ Extensions extends Record<string, ExtensionPackRef<'sql', string>> | undefined,
271
276
  >(options: {
272
277
  readonly family: Family;
273
278
  readonly target: Target;
274
- readonly extensionPacks?: ExtensionPacks;
275
- }): ComposedAuthoringHelpers<Family, Target, ExtensionPacks> {
279
+ readonly extensions?: Extensions;
280
+ }): ComposedAuthoringHelpers<Family, Target, Extensions> {
276
281
  const extensionValues: readonly ExtensionPackRef<'sql', string>[] = Object.values(
277
- (options.extensionPacks ?? {}) as Record<string, ExtensionPackRef<'sql', string>>,
282
+ (options.extensions ?? {}) as Record<string, ExtensionPackRef<'sql', string>>,
278
283
  );
279
284
  const components: readonly AuthoringComponent[] = [
280
285
  options.family,
@@ -298,5 +303,5 @@ export function createComposedAuthoringHelpers<
298
303
  model,
299
304
  rel,
300
305
  type: createTypeHelpersFromNamespace(typeNamespace),
301
- } as ComposedAuthoringHelpers<Family, Target, ExtensionPacks>;
306
+ } as ComposedAuthoringHelpers<Family, Target, Extensions>;
302
307
  }
@@ -5,9 +5,11 @@ import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
5
5
  import type { TargetPackRef } from '@prisma-next/framework-components/components';
6
6
  import type { SqlNamespaceBase, SqlNamespaceInput } from '@prisma-next/sql-contract/types';
7
7
  import { ifDefined } from '@prisma-next/utils/defined';
8
+ import { InternalError } from '@prisma-next/utils/internal-error';
8
9
  import { ok } from '@prisma-next/utils/result';
9
10
  import { extname } from 'pathe';
10
11
  import { buildSqlContractFromDefinition } from './build-contract';
12
+ import { contractError } from './contract-errors';
11
13
 
12
14
  /**
13
15
  * Derives the emit output path from the TS contract input so artefacts land
@@ -33,7 +35,7 @@ export function emptyContract(options: {
33
35
  }): ContractConfig {
34
36
  return {
35
37
  source: {
36
- sourceFormat: 'typescript',
38
+ format: 'typescript',
37
39
  load: async () => {
38
40
  const built = buildSqlContractFromDefinition({
39
41
  target: options.target,
@@ -54,7 +56,7 @@ export function typescriptContract(
54
56
  ): ContractConfig {
55
57
  return {
56
58
  source: {
57
- sourceFormat: 'typescript',
59
+ format: 'typescript',
58
60
  load: async () =>
59
61
  ok(applySpecifierDefaultControlPolicy(contract, options?.defaultControlPolicy)),
60
62
  },
@@ -71,20 +73,22 @@ export function typescriptContractFromPath(
71
73
  ): ContractConfig {
72
74
  return {
73
75
  source: {
74
- sourceFormat: 'typescript',
76
+ format: 'typescript',
75
77
  inputs: [contractPath],
76
78
  load: async (context) => {
77
79
  const [absolutePath] = context.resolvedInputs;
78
80
  if (absolutePath === undefined) {
79
- throw new Error(
81
+ throw new InternalError(
80
82
  'typescriptContractFromPath: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.',
81
83
  );
82
84
  }
83
85
  const mod = await import(pathToFileURL(absolutePath).href);
84
86
  const contract: Contract | undefined = mod.default ?? mod.contract;
85
87
  if (contract === undefined) {
86
- throw new Error(
88
+ throw contractError(
89
+ 'CONTRACT.MODULE_EXPORT_MISSING',
87
90
  `typescriptContractFromPath: module at "${absolutePath}" has no "default" or "contract" export.`,
91
+ { meta: { path: absolutePath } },
88
92
  );
89
93
  }
90
94
  return ok(applySpecifierDefaultControlPolicy(contract, options?.defaultControlPolicy));