@prisma-next/sql-contract-ts 0.16.0-dev.3 → 0.16.0-dev.30

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,10 +1,11 @@
1
- import { t as buildSqlContractFromDefinition } from "./build-contract-CAX6inbk.mjs";
1
+ import { n as contractError, t as buildSqlContractFromDefinition } from "./build-contract-DbUeVkiu.mjs";
2
2
  import { blindCast } from "@prisma-next/utils/casts";
3
3
  import { ifDefined } from "@prisma-next/utils/defined";
4
4
  import { isColumnDefault } from "@prisma-next/contract/types";
5
5
  import { bindEnumType, createEntityHelpersFromNamespace, enumType, isEnumTypeHandle, member } from "@prisma-next/contract-authoring";
6
6
  import { assertNoCrossRegistryCollisions, instantiateAuthoringFieldPreset, instantiateAuthoringTypeConstructor, isAuthoringEntityTypeDescriptor, isAuthoringFieldPresetDescriptor, isAuthoringTypeConstructorDescriptor, mergeAuthoringNamespaces, validateAuthoringHelperArguments } from "@prisma-next/framework-components/authoring";
7
7
  import { toStorageTypeInstance } from "@prisma-next/sql-contract/types";
8
+ import { InternalError } from "@prisma-next/utils/internal-error";
8
9
  import { providesEntityHandleLowering } from "@prisma-next/sql-contract/entity-handle-lowering-hook";
9
10
  //#region src/authoring-helper-runtime.ts
10
11
  function isNamedConstraintOptionsLike(value) {
@@ -19,7 +20,10 @@ const blockedSegments = /* @__PURE__ */ new Set([
19
20
  "prototype"
20
21
  ]);
21
22
  function assertSafeHelperKey(key, path) {
22
- if (blockedSegments.has(key)) throw new Error(`Invalid authoring helper "${[...path, key].join(".")}". Helper path segments must not use "${key}".`);
23
+ if (blockedSegments.has(key)) throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `Invalid authoring helper "${[...path, key].join(".")}". Helper path segments must not use "${key}".`, { meta: {
24
+ helperPath: [...path, key].join("."),
25
+ segment: key
26
+ } });
23
27
  }
24
28
  function createTypeHelpersFromNamespace(namespace, path = []) {
25
29
  const helpers = {};
@@ -47,12 +51,16 @@ function createFieldPresetHelper(options) {
47
51
  return (...rawArgs) => {
48
52
  const acceptsNamedConstraintOptions = options.descriptor.output.id === true || options.descriptor.output.unique === true;
49
53
  const declaredArguments = options.descriptor.args ?? [];
50
- if (acceptsNamedConstraintOptions && rawArgs.length > declaredArguments.length + 1) throw new Error(`${options.helperPath} expects at most ${declaredArguments.length + 1} argument(s), received ${rawArgs.length}`);
54
+ if (acceptsNamedConstraintOptions && rawArgs.length > declaredArguments.length + 1) throw contractError("CONTRACT.ARGUMENT_INVALID", `${options.helperPath} expects at most ${declaredArguments.length + 1} argument(s), received ${rawArgs.length}`, { meta: {
55
+ helperPath: options.helperPath,
56
+ expected: declaredArguments.length + 1,
57
+ received: rawArgs.length
58
+ } });
51
59
  let args = rawArgs;
52
60
  let namedConstraintOptions;
53
61
  if (acceptsNamedConstraintOptions && rawArgs.length === declaredArguments.length + 1) {
54
62
  const maybeNamedConstraintOptions = rawArgs.at(-1);
55
- if (!isNamedConstraintOptionsLike(maybeNamedConstraintOptions)) throw new Error(`${options.helperPath} accepts an optional trailing { name?: string } constraint options object`);
63
+ if (!isNamedConstraintOptionsLike(maybeNamedConstraintOptions)) throw contractError("CONTRACT.ARGUMENT_INVALID", `${options.helperPath} accepts an optional trailing { name?: string } constraint options object`, { meta: { helperPath: options.helperPath } });
56
64
  namedConstraintOptions = maybeNamedConstraintOptions;
57
65
  args = rawArgs.slice(0, -1);
58
66
  }
@@ -150,8 +158,8 @@ var ScalarFieldBuilder = class ScalarFieldBuilder {
150
158
  sql(spec) {
151
159
  const idSpec = "id" in spec ? spec.id : void 0;
152
160
  const uniqueSpec = "unique" in spec ? spec.unique : void 0;
153
- if (idSpec && !this.state.id) throw new Error("field.sql({ id }) requires an existing inline .id(...) declaration.");
154
- if (uniqueSpec && !this.state.unique) throw new Error("field.sql({ unique }) requires an existing inline .unique(...) declaration.");
161
+ if (idSpec && !this.state.id) throw contractError("CONTRACT.ARGUMENT_INVALID", "field.sql({ id }) requires an existing inline .id(...) declaration.");
162
+ if (uniqueSpec && !this.state.unique) throw contractError("CONTRACT.ARGUMENT_INVALID", "field.sql({ unique }) requires an existing inline .unique(...) declaration.");
155
163
  return new ScalarFieldBuilder(blindCast({
156
164
  ...this.state,
157
165
  ...spec.column ? { columnName: spec.column } : {},
@@ -179,7 +187,7 @@ var EnumScalarFieldBuilder = class EnumScalarFieldBuilder extends ScalarFieldBui
179
187
  }, this.#handle));
180
188
  }
181
189
  defaultSql(_expression) {
182
- throw new Error("defaultSql is not available on an enum field; use .default(members.X) instead");
190
+ throw contractError("CONTRACT.DEFAULT_INVALID", "defaultSql is not available on an enum field; use .default(members.X) instead", { meta: { reason: "defaultSql-on-enum-field" } });
183
191
  }
184
192
  };
185
193
  function columnField(descriptor) {
@@ -230,7 +238,7 @@ var RelationBuilder = class RelationBuilder {
230
238
  this.state = state;
231
239
  }
232
240
  sql(spec) {
233
- if (this.state.kind !== "belongsTo") throw new Error("relation.sql(...) is only supported for belongsTo relations.");
241
+ if (this.state.kind !== "belongsTo") throw contractError("CONTRACT.RELATION_INVALID", "relation.sql(...) is only supported for belongsTo relations.", { meta: { relationKind: this.state.kind } });
234
242
  return new RelationBuilder({
235
243
  ...this.state,
236
244
  sql: spec
@@ -246,11 +254,18 @@ function normalizeFieldRefInput(input) {
246
254
  function normalizeTargetFieldRefInput(input) {
247
255
  const refs = Array.isArray(input) ? input : [input];
248
256
  const [first] = refs;
249
- if (!first) throw new Error("Expected at least one target ref");
250
- if (refs.some((ref) => ref.modelName !== first.modelName)) throw new Error("All target refs in a foreign key must point to the same model");
251
- if (refs.some((ref) => ref.spaceId !== first.spaceId)) throw new Error(`All target refs in a compound foreign key must share the same spaceId (found mismatch: "${first.spaceId ?? "<local>"}" vs "${refs.find((r) => r.spaceId !== first.spaceId)?.spaceId ?? "<local>"}")`);
252
- if (refs.some((ref) => ref.namespaceId !== first.namespaceId)) throw new Error("All target refs in a compound foreign key must share the same namespaceId (found mismatch)");
253
- if (refs.some((ref) => ref.tableName !== first.tableName)) throw new Error("All target refs in a compound foreign key must share the same tableName (found mismatch)");
257
+ if (!first) throw contractError("CONTRACT.FOREIGN_KEY_INVALID", "Expected at least one target ref", { meta: { reason: "empty-target-refs" } });
258
+ if (refs.some((ref) => ref.modelName !== first.modelName)) throw contractError("CONTRACT.FOREIGN_KEY_INVALID", "All target refs in a foreign key must point to the same model", { meta: {
259
+ mismatch: "modelName",
260
+ models: refs.map((ref) => ref.modelName)
261
+ } });
262
+ if (refs.some((ref) => ref.spaceId !== first.spaceId)) throw contractError("CONTRACT.FOREIGN_KEY_INVALID", `All target refs in a compound foreign key must share the same spaceId (found mismatch: "${first.spaceId ?? "<local>"}" vs "${refs.find((r) => r.spaceId !== first.spaceId)?.spaceId ?? "<local>"}")`, { meta: {
263
+ mismatch: "spaceId",
264
+ first: first.spaceId,
265
+ second: refs.find((r) => r.spaceId !== first.spaceId)?.spaceId
266
+ } });
267
+ if (refs.some((ref) => ref.namespaceId !== first.namespaceId)) throw contractError("CONTRACT.FOREIGN_KEY_INVALID", "All target refs in a compound foreign key must share the same namespaceId (found mismatch)", { meta: { mismatch: "namespaceId" } });
268
+ if (refs.some((ref) => ref.tableName !== first.tableName)) throw contractError("CONTRACT.FOREIGN_KEY_INVALID", "All target refs in a compound foreign key must share the same tableName (found mismatch)", { meta: { mismatch: "tableName" } });
254
269
  return {
255
270
  modelName: first.modelName,
256
271
  fieldNames: refs.map((ref) => ref.columnName ?? ref.fieldName),
@@ -392,7 +407,7 @@ var ContractModelBuilder = class ContractModelBuilder {
392
407
  }
393
408
  ref(fieldName) {
394
409
  const modelName = this.stageOne.modelName;
395
- if (!modelName) throw new Error("Model tokens require model(\"ModelName\", ...) before calling .ref(...)");
410
+ if (!modelName) throw contractError("CONTRACT.MODEL_TOKEN_INVALID", "Model tokens require model(\"ModelName\", ...) before calling .ref(...)");
396
411
  return {
397
412
  kind: "targetFieldRef",
398
413
  source: "token",
@@ -402,7 +417,11 @@ var ContractModelBuilder = class ContractModelBuilder {
402
417
  }
403
418
  relations(relations) {
404
419
  const duplicateRelationName = findDuplicateRelationName(this.stageOne.relations, relations);
405
- if (duplicateRelationName) throw new Error(`Model "${this.stageOne.modelName ?? "<anonymous>"}" already defines relation "${duplicateRelationName}".`);
420
+ if (duplicateRelationName) throw contractError("CONTRACT.NAME_DUPLICATE", `Model "${this.stageOne.modelName ?? "<anonymous>"}" already defines relation "${duplicateRelationName}".`, { meta: {
421
+ kind: "relation",
422
+ name: duplicateRelationName,
423
+ modelName: this.stageOne.modelName
424
+ } });
406
425
  return new ContractModelBuilder({
407
426
  ...this.stageOne,
408
427
  relations: {
@@ -438,7 +457,7 @@ function isLazyRelationModelName(value) {
438
457
  }
439
458
  function resolveNamedModelTokenName(token) {
440
459
  const modelName = token.stageOne.modelName;
441
- if (!modelName) throw new Error("Relation targets require named model tokens. Use model(\"ModelName\", ...) before passing a token to rel.*(...).");
460
+ if (!modelName) throw contractError("CONTRACT.MODEL_TOKEN_INVALID", "Relation targets require named model tokens. Use model(\"ModelName\", ...) before passing a token to rel.*(...).");
442
461
  return modelName;
443
462
  }
444
463
  function normalizeRelationModelSource(target) {
@@ -460,7 +479,7 @@ function normalizeRelationModelSource(target) {
460
479
  }
461
480
  function model(modelNameOrInput, maybeInput) {
462
481
  const input = typeof modelNameOrInput === "string" ? maybeInput : modelNameOrInput;
463
- if (!input) throw new Error("model(\"ModelName\", ...) requires a model definition.");
482
+ if (!input) throw contractError("CONTRACT.ARGUMENT_INVALID", "model(\"ModelName\", ...) requires a model definition.");
464
483
  return new ContractModelBuilder({
465
484
  ...typeof modelNameOrInput === "string" ? { modelName: modelNameOrInput } : {},
466
485
  ...input.namespace !== void 0 ? { namespace: input.namespace } : {},
@@ -632,7 +651,10 @@ const RESERVED_HELPER_KEYS = [
632
651
  ];
633
652
  function assertNoBuiltInEntityCollisions(namespace) {
634
653
  const collisions = Object.keys(namespace).filter((name) => RESERVED_HELPER_KEYS.includes(name));
635
- if (collisions.length > 0) throw new Error(`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(", ")}.`);
654
+ if (collisions.length > 0) throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `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(", ")}.`, { meta: {
655
+ collisions,
656
+ reservedKeys: RESERVED_HELPER_KEYS
657
+ } });
636
658
  }
637
659
  function createComposedFieldHelpers(fieldNamespace) {
638
660
  const helperNamespace = createFieldHelpersFromNamespace(fieldNamespace, ({ helperPath, descriptor }) => createFieldPresetHelper({
@@ -646,14 +668,14 @@ function createComposedFieldHelpers(fieldNamespace) {
646
668
  namedType: field.namedType
647
669
  };
648
670
  const coreHelperNames = new Set(Object.keys(coreFieldHelpers));
649
- for (const helperName of Object.keys(helperNamespace)) if (coreHelperNames.has(helperName)) throw new Error(`Duplicate authoring field helper "${helperName}". Core field helpers reserve that name.`);
671
+ for (const helperName of Object.keys(helperNamespace)) if (coreHelperNames.has(helperName)) throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `Duplicate authoring field helper "${helperName}". Core field helpers reserve that name.`, { meta: { helperName } });
650
672
  return {
651
673
  ...coreFieldHelpers,
652
674
  ...helperNamespace
653
675
  };
654
676
  }
655
677
  function createComposedAuthoringHelpers(options) {
656
- const extensionValues = Object.values(options.extensionPacks ?? {});
678
+ const extensionValues = Object.values(options.extensions ?? {});
657
679
  const components = [
658
680
  options.family,
659
681
  options.target,
@@ -785,27 +807,46 @@ function resolveFieldDescriptor(modelName, fieldName, fieldState, storageTypes,
785
807
  nativeType: fieldState.typeRef.nativeType
786
808
  };
787
809
  const typeRef = typeof fieldState.typeRef === "string" ? fieldState.typeRef : storageTypeReverseLookup.get(fieldState.typeRef);
788
- if (!typeRef) throw new Error(`Field "${modelName}.${fieldName}" references a storage type instance that is not present in definition.types`);
810
+ if (!typeRef) throw contractError("CONTRACT.TYPE_UNKNOWN", `Field "${modelName}.${fieldName}" references a storage type instance that is not present in definition.types`, { meta: {
811
+ modelName,
812
+ fieldName,
813
+ reason: "instance-not-in-definition-types"
814
+ } });
789
815
  const referencedType = storageTypes[typeRef];
790
- if (!referencedType) throw new Error(`Field "${modelName}.${fieldName}" references unknown storage type "${typeRef}"`);
816
+ if (!referencedType) throw contractError("CONTRACT.TYPE_UNKNOWN", `Field "${modelName}.${fieldName}" references unknown storage type "${typeRef}"`, { meta: {
817
+ modelName,
818
+ fieldName,
819
+ typeRef
820
+ } });
791
821
  return {
792
822
  codecId: referencedType.codecId,
793
823
  nativeType: referencedType.nativeType,
794
824
  typeRef
795
825
  };
796
826
  }
797
- throw new Error(`Field "${modelName}.${fieldName}" does not resolve to a storage descriptor`);
827
+ throw contractError("CONTRACT.TYPE_UNKNOWN", `Field "${modelName}.${fieldName}" does not resolve to a storage descriptor`, { meta: {
828
+ modelName,
829
+ fieldName,
830
+ reason: "unresolved-storage-descriptor"
831
+ } });
798
832
  }
799
833
  function mapFieldNamesToColumnNames(modelName, fieldNames, fieldToColumn) {
800
834
  return fieldNames.map((fieldName) => {
801
835
  const columnName = fieldToColumn[fieldName];
802
- if (!columnName) throw new Error(`Unknown field "${modelName}.${fieldName}" in contract definition`);
836
+ if (!columnName) throw contractError("CONTRACT.FIELD_UNKNOWN", `Unknown field "${modelName}.${fieldName}" in contract definition`, { meta: {
837
+ modelName,
838
+ fieldName
839
+ } });
803
840
  return columnName;
804
841
  });
805
842
  }
806
843
  function assertRelationFieldArity(params) {
807
844
  if (params.leftFields.length === params.rightFields.length) return;
808
- throw new Error(`Relation "${params.modelName}.${params.relationName}" maps ${params.leftFields.length} ${params.leftLabel} field(s) to ${params.rightFields.length} ${params.rightLabel} field(s).`);
845
+ throw contractError("CONTRACT.RELATION_INVALID", `Relation "${params.modelName}.${params.relationName}" maps ${params.leftFields.length} ${params.leftLabel} field(s) to ${params.rightFields.length} ${params.rightLabel} field(s).`, { meta: {
846
+ modelName: params.modelName,
847
+ relationName: params.relationName,
848
+ reason: "field-count-mismatch"
849
+ } });
809
850
  }
810
851
  function resolveInlineIdConstraint(spec) {
811
852
  const inlineIdFields = [];
@@ -817,7 +858,11 @@ function resolveInlineIdConstraint(spec) {
817
858
  if (fieldState.id.name) idName = fieldState.id.name;
818
859
  }
819
860
  if (inlineIdFields.length === 0) return;
820
- if (inlineIdFields.length > 1) throw new Error(`Model "${spec.modelName}" marks multiple fields with .id(). Use .attributes(...) for compound identities.`);
861
+ if (inlineIdFields.length > 1) throw contractError("CONTRACT.IDENTITY_INVALID", `Model "${spec.modelName}" marks multiple fields with .id(). Use .attributes(...) for compound identities.`, { meta: {
862
+ modelName: spec.modelName,
863
+ reason: "multiple-inline-ids",
864
+ fields: inlineIdFields
865
+ } });
821
866
  const [inlineIdField] = inlineIdFields;
822
867
  if (!inlineIdField) return;
823
868
  return {
@@ -842,14 +887,20 @@ function collectInlineUniqueConstraints(spec) {
842
887
  function resolveModelIdConstraint(spec) {
843
888
  const inlineId = resolveInlineIdConstraint(spec);
844
889
  const attributeId = spec.attributesSpec?.id;
845
- if (inlineId && attributeId) throw new Error(`Model "${spec.modelName}" defines identity both inline and in .attributes(...). Pick one identity style.`);
890
+ if (inlineId && attributeId) throw contractError("CONTRACT.IDENTITY_INVALID", `Model "${spec.modelName}" defines identity both inline and in .attributes(...). Pick one identity style.`, { meta: {
891
+ modelName: spec.modelName,
892
+ reason: "inline-and-attributes"
893
+ } });
846
894
  const resolvedId = attributeId ?? inlineId;
847
- if (resolvedId && resolvedId.fields.length === 0) throw new Error(`Model "${spec.modelName}" defines an empty identity. Add at least one field.`);
895
+ if (resolvedId && resolvedId.fields.length === 0) throw contractError("CONTRACT.IDENTITY_INVALID", `Model "${spec.modelName}" defines an empty identity. Add at least one field.`, { meta: {
896
+ modelName: spec.modelName,
897
+ reason: "empty-identity"
898
+ } });
848
899
  return resolvedId;
849
900
  }
850
901
  function resolveModelUniqueConstraints(spec) {
851
902
  const attributeUniques = spec.attributesSpec?.uniques ?? [];
852
- for (const unique of attributeUniques) if (unique.fields.length === 0) throw new Error(`Model "${spec.modelName}" defines an empty unique constraint. Add at least one field.`);
903
+ for (const unique of attributeUniques) if (unique.fields.length === 0) throw contractError("CONTRACT.CONSTRAINT_INVALID", `Model "${spec.modelName}" defines an empty unique constraint. Add at least one field.`, { meta: { modelName: spec.modelName } });
853
904
  return [...collectInlineUniqueConstraints(spec), ...attributeUniques];
854
905
  }
855
906
  function resolveRelationForeignKeys(spec, allSpecs) {
@@ -885,7 +936,11 @@ function resolveRelationForeignKeys(spec, allSpecs) {
885
936
  });
886
937
  continue;
887
938
  }
888
- if (!allSpecs.has(targetModelName)) throw new Error(`Relation "${spec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
939
+ if (!allSpecs.has(targetModelName)) throw contractError("CONTRACT.MODEL_UNKNOWN", `Relation "${spec.modelName}.${relationName}" references unknown model "${targetModelName}"`, { meta: {
940
+ sourceModel: spec.modelName,
941
+ relationName,
942
+ targetModel: targetModelName
943
+ } });
889
944
  const fields = normalizeRelationFieldNames(relation.from);
890
945
  const targetFields = normalizeRelationFieldNames(relation.to);
891
946
  assertRelationFieldArity({
@@ -914,9 +969,12 @@ function resolveRelationAnchorFields(spec) {
914
969
  const idFields = spec.idConstraint?.fields;
915
970
  if (idFields && idFields.length > 0) return idFields;
916
971
  if ("id" in spec.fieldToColumn) return ["id"];
917
- throw new Error(`Model "${spec.modelName}" needs an explicit id or an "id" field to anchor non-owning relations`);
972
+ throw contractError("CONTRACT.IDENTITY_INVALID", `Model "${spec.modelName}" needs an explicit id or an "id" field to anchor non-owning relations`, { meta: {
973
+ modelName: spec.modelName,
974
+ reason: "missing-anchor-id"
975
+ } });
918
976
  }
919
- function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks) {
977
+ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensions) {
920
978
  const targetModelName = resolveRelationModelName(relation.toModel);
921
979
  const fromFields = normalizeRelationFieldNames(relation.from);
922
980
  const toFields = normalizeRelationFieldNames(relation.to);
@@ -929,7 +987,7 @@ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, e
929
987
  rightFields: toFields
930
988
  });
931
989
  if (relation.spaceId !== void 0) {
932
- assertKnownExtensionPack(extensionPacks, relation.spaceId, `Relation "${currentSpec.modelName}.${relationName}"`);
990
+ assertKnownExtensionPack(extensions, relation.spaceId, `Relation "${currentSpec.modelName}.${relationName}"`);
933
991
  const targetTable = relation.tableName ?? targetModelName.toLowerCase();
934
992
  const parentColumns = mapFieldNamesToColumnNames(currentSpec.modelName, fromFields, currentSpec.fieldToColumn);
935
993
  return {
@@ -948,7 +1006,11 @@ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, e
948
1006
  };
949
1007
  }
950
1008
  const targetSpec = allSpecs.get(targetModelName);
951
- if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
1009
+ if (!targetSpec) throw contractError("CONTRACT.MODEL_UNKNOWN", `Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`, { meta: {
1010
+ sourceModel: currentSpec.modelName,
1011
+ relationName,
1012
+ targetModel: targetModelName
1013
+ } });
952
1014
  return {
953
1015
  fieldName: relationName,
954
1016
  toModel: targetModelName,
@@ -965,7 +1027,11 @@ function lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, e
965
1027
  function lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs) {
966
1028
  const targetModelName = resolveRelationModelName(relation.toModel);
967
1029
  const targetSpec = allSpecs.get(targetModelName);
968
- if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
1030
+ if (!targetSpec) throw contractError("CONTRACT.MODEL_UNKNOWN", `Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`, { meta: {
1031
+ sourceModel: currentSpec.modelName,
1032
+ relationName,
1033
+ targetModel: targetModelName
1034
+ } });
969
1035
  const parentFields = resolveRelationAnchorFields(currentSpec);
970
1036
  const childFields = normalizeRelationFieldNames(relation.by);
971
1037
  assertRelationFieldArity({
@@ -992,15 +1058,27 @@ function lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs
992
1058
  function lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs) {
993
1059
  const targetModelName = resolveRelationModelName(relation.toModel);
994
1060
  const targetSpec = allSpecs.get(targetModelName);
995
- if (!targetSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`);
1061
+ if (!targetSpec) throw contractError("CONTRACT.MODEL_UNKNOWN", `Relation "${currentSpec.modelName}.${relationName}" references unknown model "${targetModelName}"`, { meta: {
1062
+ sourceModel: currentSpec.modelName,
1063
+ relationName,
1064
+ targetModel: targetModelName
1065
+ } });
996
1066
  const throughModelName = resolveRelationModelName(relation.through);
997
1067
  const throughSpec = allSpecs.get(throughModelName);
998
- if (!throughSpec) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" references unknown through model "${throughModelName}"`);
1068
+ if (!throughSpec) throw contractError("CONTRACT.MODEL_UNKNOWN", `Relation "${currentSpec.modelName}.${relationName}" references unknown through model "${throughModelName}"`, { meta: {
1069
+ sourceModel: currentSpec.modelName,
1070
+ relationName,
1071
+ targetModel: throughModelName
1072
+ } });
999
1073
  const currentAnchorFields = resolveRelationAnchorFields(currentSpec);
1000
1074
  const targetAnchorFields = resolveRelationAnchorFields(targetSpec);
1001
1075
  const throughFromFields = normalizeRelationFieldNames(relation.from);
1002
1076
  const throughToFields = normalizeRelationFieldNames(relation.to);
1003
- if (currentAnchorFields.length !== throughFromFields.length || targetAnchorFields.length !== throughToFields.length) throw new Error(`Relation "${currentSpec.modelName}.${relationName}" has mismatched many-to-many field counts.`);
1077
+ if (currentAnchorFields.length !== throughFromFields.length || targetAnchorFields.length !== throughToFields.length) throw contractError("CONTRACT.RELATION_INVALID", `Relation "${currentSpec.modelName}.${relationName}" has mismatched many-to-many field counts.`, { meta: {
1078
+ modelName: currentSpec.modelName,
1079
+ relationName,
1080
+ reason: "many-to-many-field-count-mismatch"
1081
+ } });
1004
1082
  return {
1005
1083
  fieldName: relationName,
1006
1084
  toModel: targetModelName,
@@ -1020,8 +1098,8 @@ function lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs)
1020
1098
  }
1021
1099
  };
1022
1100
  }
1023
- function resolveRelationNode(relationName, relation, currentSpec, allSpecs, extensionPacks) {
1024
- if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensionPacks);
1101
+ function resolveRelationNode(relationName, relation, currentSpec, allSpecs, extensions) {
1102
+ if (relation.kind === "belongsTo") return lowerBelongsToRelation(relationName, relation, currentSpec, allSpecs, extensions);
1025
1103
  if (relation.kind === "hasMany" || relation.kind === "hasOne") return lowerHasOwnershipRelation(relationName, relation, currentSpec, allSpecs);
1026
1104
  return lowerManyToManyRelation(relationName, relation, currentSpec, allSpecs);
1027
1105
  }
@@ -1057,44 +1135,53 @@ function lowerCrossSpaceForeignKeyNode(spec, foreignKey) {
1057
1135
  ...foreignKey.index !== void 0 ? { index: foreignKey.index } : {}
1058
1136
  };
1059
1137
  }
1060
- function assertKnownExtensionPack(extensionPacks, spaceId, context) {
1061
- if (extensionPacks !== void 0 && Object.hasOwn(extensionPacks, spaceId)) return;
1062
- throw new Error(`${context} references contract space "${spaceId}" but "${spaceId}" is not declared in extensionPacks. Add the pack to extensionPacks.`);
1138
+ function assertKnownExtensionPack(extensions, spaceId, context) {
1139
+ if (extensions !== void 0 && Object.hasOwn(extensions, spaceId)) return;
1140
+ throw contractError("CONTRACT.PACK_MISSING", `${context} references contract space "${spaceId}" but "${spaceId}" is not declared in extensions. Add the pack to extensions.`, { meta: {
1141
+ spaceId,
1142
+ context
1143
+ } });
1063
1144
  }
1064
- function resolveForeignKeyNodes(spec, allSpecs, extensionPacks) {
1145
+ function resolveForeignKeyNodes(spec, allSpecs, extensions) {
1065
1146
  const relationForeignKeys = resolveRelationForeignKeys(spec, allSpecs).map((foreignKey) => {
1066
1147
  if (foreignKey.targetSpaceId !== void 0) {
1067
- assertKnownExtensionPack(extensionPacks, foreignKey.targetSpaceId, `Relation-derived foreign key on "${spec.modelName}"`);
1148
+ assertKnownExtensionPack(extensions, foreignKey.targetSpaceId, `Relation-derived foreign key on "${spec.modelName}"`);
1068
1149
  return lowerCrossSpaceForeignKeyNode(spec, {
1069
1150
  ...foreignKey,
1070
1151
  targetSpaceId: foreignKey.targetSpaceId
1071
1152
  });
1072
1153
  }
1073
1154
  const targetSpec = allSpecs.get(foreignKey.targetModel);
1074
- if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
1155
+ if (!targetSpec) throw contractError("CONTRACT.MODEL_UNKNOWN", `Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`, { meta: {
1156
+ sourceModel: spec.modelName,
1157
+ targetModel: foreignKey.targetModel
1158
+ } });
1075
1159
  return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
1076
1160
  });
1077
1161
  const sqlForeignKeys = (spec.sqlSpec?.foreignKeys ?? []).map((foreignKey) => {
1078
1162
  if (foreignKey.targetSpaceId !== void 0) {
1079
- assertKnownExtensionPack(extensionPacks, foreignKey.targetSpaceId, `Foreign key on "${spec.modelName}"`);
1163
+ assertKnownExtensionPack(extensions, foreignKey.targetSpaceId, `Foreign key on "${spec.modelName}"`);
1080
1164
  return lowerCrossSpaceForeignKeyNode(spec, {
1081
1165
  ...foreignKey,
1082
1166
  targetSpaceId: foreignKey.targetSpaceId
1083
1167
  });
1084
1168
  }
1085
1169
  const targetSpec = allSpecs.get(foreignKey.targetModel);
1086
- if (!targetSpec) throw new Error(`Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`);
1170
+ if (!targetSpec) throw contractError("CONTRACT.MODEL_UNKNOWN", `Foreign key on "${spec.modelName}" references unknown model "${foreignKey.targetModel}"`, { meta: {
1171
+ sourceModel: spec.modelName,
1172
+ targetModel: foreignKey.targetModel
1173
+ } });
1087
1174
  return lowerLocalForeignKeyNode(spec, targetSpec, foreignKey);
1088
1175
  });
1089
1176
  return [...relationForeignKeys, ...sqlForeignKeys];
1090
1177
  }
1091
- function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup, extensionPacks) {
1178
+ function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup, extensions) {
1092
1179
  const fields = [];
1093
1180
  for (const [fieldName, fieldBuilder] of Object.entries(spec.fieldBuilders)) {
1094
1181
  const fieldState = fieldBuilder.build();
1095
1182
  const descriptor = resolveFieldDescriptor(spec.modelName, fieldName, fieldState, storageTypes, storageTypeReverseLookup);
1096
1183
  const columnName = spec.fieldToColumn[fieldName];
1097
- if (!columnName) throw new Error(`Column name resolution failed for "${spec.modelName}.${fieldName}"`);
1184
+ if (!columnName) throw new InternalError(`Column name resolution failed for "${spec.modelName}.${fieldName}"`);
1098
1185
  const enumHandle = "typeRef" in fieldState && isEnumTypeHandle(fieldState.typeRef) ? fieldState.typeRef : void 0;
1099
1186
  fields.push({
1100
1187
  fieldName,
@@ -1118,8 +1205,8 @@ function resolveModelNode(spec, allSpecs, storageTypes, storageTypeReverseLookup
1118
1205
  ...ifDefined("type", index.type),
1119
1206
  ...ifDefined("options", index.options)
1120
1207
  }));
1121
- const foreignKeys = resolveForeignKeyNodes(spec, allSpecs, extensionPacks);
1122
- const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs, extensionPacks));
1208
+ const foreignKeys = resolveForeignKeyNodes(spec, allSpecs, extensions);
1209
+ const relations = Object.entries(spec.relations).map(([relationName, relationBuilder]) => resolveRelationNode(relationName, relationBuilder.build(), spec, allSpecs, extensions));
1123
1210
  return {
1124
1211
  modelName: spec.modelName,
1125
1212
  tableName: spec.tableName,
@@ -1144,21 +1231,34 @@ function collectRuntimeModelSpecs(definition) {
1144
1231
  const tableOwners = /* @__PURE__ */ new Map();
1145
1232
  for (const [modelName, modelDefinition] of Object.entries(models)) {
1146
1233
  const tokenModelName = modelDefinition.stageOne.modelName;
1147
- if (tokenModelName && tokenModelName !== modelName) throw new Error(`Model token "${tokenModelName}" must be assigned to models.${tokenModelName}. Received models.${modelName}.`);
1234
+ if (tokenModelName && tokenModelName !== modelName) throw contractError("CONTRACT.MODEL_TOKEN_INVALID", `Model token "${tokenModelName}" must be assigned to models.${tokenModelName}. Received models.${modelName}.`, { meta: {
1235
+ tokenModelName,
1236
+ assignedKey: modelName
1237
+ } });
1148
1238
  const attributesSpec = modelDefinition.buildAttributesSpec();
1149
1239
  const sqlSpec = modelDefinition.buildSqlSpec();
1150
1240
  const tableName = sqlSpec?.table ?? applyNaming(modelName, definition.naming?.tables);
1151
1241
  const namespaceId = modelDefinition.stageOne.namespace ?? definition.target.defaultNamespaceId;
1152
1242
  const tableKey = JSON.stringify([namespaceId, tableName]);
1153
1243
  const existingModel = tableOwners.get(tableKey);
1154
- if (existingModel) throw new Error(`Models "${existingModel}" and "${modelName}" both map to table "${tableName}".`);
1244
+ if (existingModel) throw contractError("CONTRACT.NAME_DUPLICATE", `Models "${existingModel}" and "${modelName}" both map to table "${tableName}".`, { meta: {
1245
+ kind: "table",
1246
+ name: tableName,
1247
+ first: existingModel,
1248
+ second: modelName
1249
+ } });
1155
1250
  tableOwners.set(tableKey, modelName);
1156
1251
  const fieldToColumn = {};
1157
1252
  const columnOwners = /* @__PURE__ */ new Map();
1158
1253
  for (const [fieldName, fieldBuilder] of Object.entries(modelDefinition.stageOne.fields)) {
1159
1254
  const columnName = fieldBuilder.build().columnName ?? applyNaming(fieldName, definition.naming?.columns);
1160
1255
  const existingField = columnOwners.get(columnName);
1161
- if (existingField) throw new Error(`Model "${modelName}" maps both "${existingField}" and "${fieldName}" to column "${columnName}".`);
1256
+ if (existingField) throw contractError("CONTRACT.NAME_DUPLICATE", `Model "${modelName}" maps both "${existingField}" and "${fieldName}" to column "${columnName}".`, { meta: {
1257
+ kind: "column",
1258
+ name: columnName,
1259
+ first: existingField,
1260
+ second: fieldName
1261
+ } });
1162
1262
  columnOwners.set(columnName, fieldName);
1163
1263
  fieldToColumn[fieldName] = columnName;
1164
1264
  }
@@ -1186,10 +1286,10 @@ function collectRuntimeModelSpecs(definition) {
1186
1286
  modelSpecs
1187
1287
  };
1188
1288
  }
1189
- function lowerModels(collection, extensionPacks) {
1289
+ function lowerModels(collection, extensions) {
1190
1290
  emitTypedCrossModelFallbackWarnings(collection);
1191
1291
  const storageTypeReverseLookup = buildStorageTypeReverseLookup(collection.storageTypes);
1192
- return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup, extensionPacks));
1292
+ return Array.from(collection.modelSpecs.values()).map((spec) => resolveModelNode(spec, collection.modelSpecs, collection.storageTypes, storageTypeReverseLookup, extensions));
1193
1293
  }
1194
1294
  /**
1195
1295
  * Kind-agnostic walk over the author-declared `entities` handle list:
@@ -1214,7 +1314,7 @@ function lowerModels(collection, extensionPacks) {
1214
1314
  function lowerPackEntityHandles(definition, modelSpecs) {
1215
1315
  const entities = definition.entities;
1216
1316
  if (entities === void 0 || entities.length === 0) return void 0;
1217
- const components = [definition.target, ...Object.values(definition.extensionPacks ?? {})];
1317
+ const components = [definition.target, ...Object.values(definition.extensions ?? {})];
1218
1318
  const owningComponent = /* @__PURE__ */ new Map();
1219
1319
  const walkEntityTypes = (namespace, component) => {
1220
1320
  for (const value of Object.values(namespace)) if (isAuthoringEntityTypeDescriptor(value)) owningComponent.set(value.discriminator, component);
@@ -1269,7 +1369,7 @@ function lowerPackEntityHandles(definition, modelSpecs) {
1269
1369
  const claimed = /* @__PURE__ */ new Map();
1270
1370
  for (const handle of entities) {
1271
1371
  const component = owningComponent.get(handle.entityKind);
1272
- if (component === void 0) throw new Error(`defineContract: entities contains a handle with entityKind "${handle.entityKind}", which no composed pack registers. Compose a pack whose entityTypes contribution claims "${handle.entityKind}", or remove the handle.`);
1372
+ if (component === void 0) throw contractError("CONTRACT.ENTITY_KIND_UNKNOWN", `defineContract: entities contains a handle with entityKind "${handle.entityKind}", which no composed pack registers. Compose a pack whose entityTypes contribution claims "${handle.entityKind}", or remove the handle.`, { meta: { entityKind: handle.entityKind } });
1273
1373
  const refs = {};
1274
1374
  for (const [refName, refValue] of Object.entries(handle.refs ?? {})) refs[refName] = resolveRef(refValue);
1275
1375
  const forComponent = claimed.get(component) ?? [];
@@ -1284,7 +1384,10 @@ function lowerPackEntityHandles(definition, modelSpecs) {
1284
1384
  const authoring = component.authoring;
1285
1385
  if (!providesEntityHandleLowering(authoring)) {
1286
1386
  const kinds = [...new Set(handles.map((entry) => entry.handle.entityKind))].sort();
1287
- throw new Error(`defineContract: entityKind(s) ${kinds.map((kind) => `"${kind}"`).join(", ")} are registered by a pack that does not implement entity-handle lowering (no lowerEntityHandles on its authoring contributions).`);
1387
+ throw contractError("CONTRACT.PACK_CONTRIBUTION_INVALID", `defineContract: entityKind(s) ${kinds.map((kind) => `"${kind}"`).join(", ")} are registered by a pack that does not implement entity-handle lowering (no lowerEntityHandles on its authoring contributions).`, { meta: {
1388
+ entityKinds: kinds,
1389
+ reason: "missing-lowerEntityHandles"
1390
+ } });
1288
1391
  }
1289
1392
  for (const row of authoring.lowerEntityHandles({
1290
1393
  handles,
@@ -1295,7 +1398,11 @@ function lowerPackEntityHandles(definition, modelSpecs) {
1295
1398
  const forKind = forNamespace[row.entityKind] ?? {};
1296
1399
  forNamespace[row.entityKind] = forKind;
1297
1400
  const existing = forKind[row.key];
1298
- if (existing !== void 0 && existing !== row.entity) throw new Error(`defineContract: two different "${row.entityKind}" entities named "${row.key}" in namespace "${row.namespaceId}" — pack-entity names must be unique per namespace.`);
1401
+ if (existing !== void 0 && existing !== row.entity) throw contractError("CONTRACT.NAME_DUPLICATE", `defineContract: two different "${row.entityKind}" entities named "${row.key}" in namespace "${row.namespaceId}" — pack-entity names must be unique per namespace.`, { meta: {
1402
+ kind: row.entityKind,
1403
+ name: row.key,
1404
+ namespaceId: row.namespaceId
1405
+ } });
1299
1406
  forKind[row.key] = row.entity;
1300
1407
  }
1301
1408
  }
@@ -1303,12 +1410,12 @@ function lowerPackEntityHandles(definition, modelSpecs) {
1303
1410
  }
1304
1411
  function buildContractDefinition(definition) {
1305
1412
  const collection = collectRuntimeModelSpecs(definition);
1306
- const models = lowerModels(collection, definition.extensionPacks);
1413
+ const models = lowerModels(collection, definition.extensions);
1307
1414
  const attachedEntities = lowerPackEntityHandles(definition, collection.modelSpecs);
1308
1415
  return {
1309
1416
  target: definition.target,
1310
1417
  ...ifDefined("defaultControlPolicy", definition.defaultControlPolicy),
1311
- ...definition.extensionPacks ? { extensionPacks: definition.extensionPacks } : {},
1418
+ ...definition.extensions ? { extensions: definition.extensions } : {},
1312
1419
  ...definition.storageHash ? { storageHash: definition.storageHash } : {},
1313
1420
  ...definition.foreignKeyDefaults ? { foreignKeyDefaults: definition.foreignKeyDefaults } : {},
1314
1421
  ...Object.keys(collection.storageTypes).length > 0 ? { storageTypes: collection.storageTypes } : {},
@@ -1322,8 +1429,16 @@ function buildContractDefinition(definition) {
1322
1429
  //#endregion
1323
1430
  //#region src/contract-builder.ts
1324
1431
  function validateTargetPackRef(family, target) {
1325
- if (family.familyId !== "sql") throw new Error(`defineContract only accepts SQL family packs. Received family "${family.familyId}".`);
1326
- if (target.familyId !== family.familyId) throw new Error(`target pack "${target.id}" targets family "${target.familyId}" but contract family is "${family.familyId}".`);
1432
+ if (family.familyId !== "sql") throw contractError("CONTRACT.PACK_FAMILY_MISMATCH", `defineContract only accepts SQL family packs. Received family "${family.familyId}".`, { meta: {
1433
+ packId: family.id,
1434
+ packFamilyId: family.familyId,
1435
+ contractFamilyId: "sql"
1436
+ } });
1437
+ if (target.familyId !== family.familyId) throw contractError("CONTRACT.PACK_FAMILY_MISMATCH", `target pack "${target.id}" targets family "${target.familyId}" but contract family is "${family.familyId}".`, { meta: {
1438
+ packId: target.id,
1439
+ packFamilyId: target.familyId,
1440
+ contractFamilyId: family.familyId
1441
+ } });
1327
1442
  }
1328
1443
  /**
1329
1444
  * Per-target reserved namespace names enforced by `defineContract` for
@@ -1342,14 +1457,32 @@ function validateTargetPackRef(family, target) {
1342
1457
  */
1343
1458
  function validateNamespaceDeclarations(target, namespaces) {
1344
1459
  if (!namespaces) return;
1345
- if (target.targetId === "sqlite" && namespaces.length > 0) throw new Error(`defineContract: SQLite contracts cannot declare namespaces (SQLite has no schema concept; emitted DDL is always unqualified). Received namespaces: [${namespaces.map((name) => `"${name}"`).join(", ")}].`);
1460
+ if (target.targetId === "sqlite" && namespaces.length > 0) throw contractError("CONTRACT.NAMESPACE_UNSUPPORTED", `defineContract: SQLite contracts cannot declare namespaces (SQLite has no schema concept; emitted DDL is always unqualified). Received namespaces: [${namespaces.map((name) => `"${name}"`).join(", ")}].`, { meta: {
1461
+ namespaces,
1462
+ targetId: target.targetId
1463
+ } });
1346
1464
  const seen = /* @__PURE__ */ new Set();
1347
1465
  for (const namespace of namespaces) {
1348
- if (namespace.length === 0) throw new Error("defineContract: namespace names cannot be empty.");
1349
- if (namespace.trim().length === 0) throw new Error(`defineContract: namespace name "${namespace}" cannot be whitespace-only.`);
1350
- if (namespace === "__unbound__" || namespace === "__unspecified__") throw new Error(`defineContract: namespace name "${namespace}" is a reserved IR sentinel and cannot appear in the declared namespaces list.`);
1351
- if (target.targetId === "postgres" && namespace === "unbound") throw new Error(`defineContract: namespace name "unbound" is reserved by Postgres for the late-binding opt-in (use \`namespace unbound { … }\` in PSL instead of declaring it as a regular schema).`);
1352
- if (seen.has(namespace)) throw new Error(`defineContract: namespaces list contains duplicate entry "${namespace}".`);
1466
+ if (namespace.length === 0) throw contractError("CONTRACT.NAMESPACE_INVALID", "defineContract: namespace names cannot be empty.", { meta: {
1467
+ namespace,
1468
+ reason: "empty"
1469
+ } });
1470
+ if (namespace.trim().length === 0) throw contractError("CONTRACT.NAMESPACE_INVALID", `defineContract: namespace name "${namespace}" cannot be whitespace-only.`, { meta: {
1471
+ namespace,
1472
+ reason: "whitespace-only"
1473
+ } });
1474
+ if (namespace === "__unbound__" || namespace === "__unspecified__") throw contractError("CONTRACT.NAMESPACE_INVALID", `defineContract: namespace name "${namespace}" is a reserved IR sentinel and cannot appear in the declared namespaces list.`, { meta: {
1475
+ namespace,
1476
+ reason: "reserved-ir-sentinel"
1477
+ } });
1478
+ if (target.targetId === "postgres" && namespace === "unbound") throw contractError("CONTRACT.NAMESPACE_INVALID", `defineContract: namespace name "unbound" is reserved by Postgres for the late-binding opt-in (use \`namespace unbound { … }\` in PSL instead of declaring it as a regular schema).`, { meta: {
1479
+ namespace,
1480
+ reason: "reserved-by-postgres"
1481
+ } });
1482
+ if (seen.has(namespace)) throw contractError("CONTRACT.NAME_DUPLICATE", `defineContract: namespaces list contains duplicate entry "${namespace}".`, { meta: {
1483
+ kind: "namespace",
1484
+ name: namespace
1485
+ } });
1353
1486
  seen.add(namespace);
1354
1487
  }
1355
1488
  }
@@ -1379,26 +1512,50 @@ function validatePerModelNamespaces(target, namespaces, models) {
1379
1512
  for (const [modelKey, modelBuilder] of Object.entries(models)) {
1380
1513
  const perModelNamespace = modelBuilder.stageOne.namespace;
1381
1514
  if (perModelNamespace === void 0) continue;
1382
- if (target.targetId === "sqlite") throw new Error(`defineContract: model "${modelKey}" sets \`namespace: "${perModelNamespace}"\` but the target is SQLite (SQLite has no schema concept; remove the per-model \`namespace\` field).`);
1383
- if (perModelNamespace === "__unbound__" || perModelNamespace === "__unspecified__") throw new Error(`defineContract: model "${modelKey}" sets \`namespace: "${perModelNamespace}"\` but that name is a reserved IR sentinel and cannot appear in user code.`);
1384
- if (target.targetId === "postgres" && perModelNamespace === "unbound") throw new Error(`defineContract: model "${modelKey}" sets \`namespace: "unbound"\` but that name is reserved by Postgres for the late-binding opt-in (use \`namespace unbound { … }\` in PSL instead — there is no equivalent surface in the TS builder today).`);
1385
- if (!declaredNamespaces.has(perModelNamespace)) {
1386
- const hint = declaredNamespaces.size > 0 ? ` Declared namespaces: [${[...declaredNamespaces].map((name) => `"${name}"`).join(", ")}].` : " The contract does not declare any namespaces; add `namespaces: [\"…\"]` to `defineContract` first.";
1387
- throw new Error(`defineContract: model "${modelKey}" references namespace "${perModelNamespace}" but that name does not appear in the contract's declared \`namespaces\` list.${hint}`);
1388
- }
1515
+ if (target.targetId === "sqlite") throw contractError("CONTRACT.NAMESPACE_UNSUPPORTED", `defineContract: model "${modelKey}" sets \`namespace: "${perModelNamespace}"\` but the target is SQLite (SQLite has no schema concept; remove the per-model \`namespace\` field).`, { meta: {
1516
+ modelKey,
1517
+ namespace: perModelNamespace,
1518
+ targetId: target.targetId
1519
+ } });
1520
+ if (perModelNamespace === "__unbound__" || perModelNamespace === "__unspecified__") throw contractError("CONTRACT.NAMESPACE_INVALID", `defineContract: model "${modelKey}" sets \`namespace: "${perModelNamespace}"\` but that name is a reserved IR sentinel and cannot appear in user code.`, { meta: {
1521
+ modelKey,
1522
+ namespace: perModelNamespace,
1523
+ reason: "reserved-ir-sentinel"
1524
+ } });
1525
+ if (target.targetId === "postgres" && perModelNamespace === "unbound") throw contractError("CONTRACT.NAMESPACE_INVALID", `defineContract: model "${modelKey}" sets \`namespace: "unbound"\` but that name is reserved by Postgres for the late-binding opt-in (use \`namespace unbound { … }\` in PSL instead — there is no equivalent surface in the TS builder today).`, { meta: {
1526
+ modelKey,
1527
+ namespace: perModelNamespace,
1528
+ reason: "reserved-by-postgres"
1529
+ } });
1530
+ if (!declaredNamespaces.has(perModelNamespace)) throw contractError("CONTRACT.NAMESPACE_UNKNOWN", `defineContract: model "${modelKey}" references namespace "${perModelNamespace}" but that name does not appear in the contract's declared \`namespaces\` list.${declaredNamespaces.size > 0 ? ` Declared namespaces: [${[...declaredNamespaces].map((name) => `"${name}"`).join(", ")}].` : " The contract does not declare any namespaces; add `namespaces: [\"…\"]` to `defineContract` first."}`, { meta: {
1531
+ modelKey,
1532
+ namespace: perModelNamespace,
1533
+ declared: [...declaredNamespaces]
1534
+ } });
1389
1535
  }
1390
1536
  }
1391
- function validateExtensionPackRefs(target, extensionPacks) {
1392
- if (!extensionPacks) return;
1393
- for (const packRef of Object.values(extensionPacks)) {
1394
- if (packRef.kind !== "extension") throw new Error(`defineContract only accepts extension pack refs in extensionPacks. Received kind "${packRef.kind}".`);
1395
- if (packRef.familyId !== target.familyId) throw new Error(`extension pack "${packRef.id}" targets family "${packRef.familyId}" but contract target family is "${target.familyId}".`);
1396
- if (packRef.targetId && packRef.targetId !== target.targetId) throw new Error(`extension pack "${packRef.id}" targets "${packRef.targetId}" but contract target is "${target.targetId}".`);
1537
+ function validateExtensionPackRefs(target, extensions) {
1538
+ if (!extensions) return;
1539
+ for (const packRef of Object.values(extensions)) {
1540
+ if (packRef.kind !== "extension") throw contractError("CONTRACT.PACK_REF_INVALID", `defineContract only accepts extension pack refs in extensions. Received kind "${packRef.kind}".`, { meta: {
1541
+ packId: packRef.id,
1542
+ kind: packRef.kind
1543
+ } });
1544
+ if (packRef.familyId !== target.familyId) throw contractError("CONTRACT.PACK_FAMILY_MISMATCH", `extension pack "${packRef.id}" targets family "${packRef.familyId}" but contract target family is "${target.familyId}".`, { meta: {
1545
+ packId: packRef.id,
1546
+ packFamilyId: packRef.familyId,
1547
+ contractFamilyId: target.familyId
1548
+ } });
1549
+ if (packRef.targetId && packRef.targetId !== target.targetId) throw contractError("CONTRACT.PACK_TARGET_MISMATCH", `extension pack "${packRef.id}" targets "${packRef.targetId}" but contract target is "${target.targetId}".`, { meta: {
1550
+ packId: packRef.id,
1551
+ packTargetId: packRef.targetId,
1552
+ contractTargetId: target.targetId
1553
+ } });
1397
1554
  }
1398
1555
  }
1399
1556
  function buildContractFromDsl(definition) {
1400
1557
  validateTargetPackRef(definition.family, definition.target);
1401
- validateExtensionPackRefs(definition.target, definition.extensionPacks);
1558
+ validateExtensionPackRefs(definition.target, definition.extensions);
1402
1559
  validateNamespaceDeclarations(definition.target, definition.namespaces);
1403
1560
  validatePerModelNamespaces(definition.target, definition.namespaces, definition.models ?? {});
1404
1561
  return blindCast(buildSqlContractFromDefinition(buildContractDefinition(definition), definition.codecLookup));
@@ -1414,7 +1571,7 @@ function buildBoundContract(family, target, definition, factory) {
1414
1571
  const built = factory(createComposedAuthoringHelpers({
1415
1572
  family,
1416
1573
  target,
1417
- extensionPacks: definition.extensionPacks
1574
+ extensions: definition.extensions
1418
1575
  }));
1419
1576
  const mergedEnums = {
1420
1577
  ...definition.enums ?? {},