@prisma-next/target-postgres 0.16.0-dev.22 → 0.16.0-dev.24

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.
Files changed (92) hide show
  1. package/dist/{authoring-34L18kA1.mjs → authoring-BCzcFRay.mjs} +27 -9
  2. package/dist/authoring-BCzcFRay.mjs.map +1 -0
  3. package/dist/codecs-CGE5dh94.d.mts.map +1 -1
  4. package/dist/{codecs-DOgD_C1A.mjs → codecs-Deand7sy.mjs} +75 -19
  5. package/dist/codecs-Deand7sy.mjs.map +1 -0
  6. package/dist/codecs.mjs +1 -1
  7. package/dist/contract-free.mjs +2 -2
  8. package/dist/{control-policy-BrcqeM82.mjs → control-policy-B-_34g05.mjs} +8 -7
  9. package/dist/control-policy-B-_34g05.mjs.map +1 -0
  10. package/dist/control.mjs +28 -17
  11. package/dist/control.mjs.map +1 -1
  12. package/dist/{ddl-DHXKzQ99.mjs → ddl-DYEob9xQ.mjs} +2 -2
  13. package/dist/{ddl-DHXKzQ99.mjs.map → ddl-DYEob9xQ.mjs.map} +1 -1
  14. package/dist/{descriptor-meta-CiYV9DzH.mjs → descriptor-meta-D_0ZKcj-.mjs} +4 -4
  15. package/dist/{descriptor-meta-CiYV9DzH.mjs.map → descriptor-meta-D_0ZKcj-.mjs.map} +1 -1
  16. package/dist/{diff-database-schema-CK0FM2Z-.mjs → diff-database-schema-C9H09OD3.mjs} +17 -6
  17. package/dist/diff-database-schema-C9H09OD3.mjs.map +1 -0
  18. package/dist/diff-database-schema.mjs +1 -1
  19. package/dist/{errors-ByDS0xJL.mjs → errors-DBSNx7EJ.mjs} +6 -2
  20. package/dist/errors-DBSNx7EJ.mjs.map +1 -0
  21. package/dist/errors.d.mts +1 -0
  22. package/dist/errors.d.mts.map +1 -1
  23. package/dist/errors.mjs +1 -1
  24. package/dist/issue-planner.mjs +1 -1
  25. package/dist/migration.mjs +2 -2
  26. package/dist/{op-factory-call-PBYfcv8h.mjs → op-factory-call-BVYLIojV.mjs} +49 -38
  27. package/dist/op-factory-call-BVYLIojV.mjs.map +1 -0
  28. package/dist/op-factory-call-DT7kdjVP.d.mts.map +1 -1
  29. package/dist/op-factory-call.mjs +1 -1
  30. package/dist/pack.d.mts.map +1 -1
  31. package/dist/pack.mjs +1 -1
  32. package/dist/{planner-CiXCo6BT.mjs → planner-4sj5cJCY.mjs} +7 -7
  33. package/dist/{planner-CiXCo6BT.mjs.map → planner-4sj5cJCY.mjs.map} +1 -1
  34. package/dist/{planner-ddl-builders-AIpFmG1i.mjs → planner-ddl-builders-CVo3L7Nd.mjs} +11 -7
  35. package/dist/planner-ddl-builders-CVo3L7Nd.mjs.map +1 -0
  36. package/dist/planner-ddl-builders.d.mts.map +1 -1
  37. package/dist/planner-ddl-builders.mjs +1 -1
  38. package/dist/{planner-produced-postgres-migration-PIgjJYrY.mjs → planner-produced-postgres-migration-CcQPDWmX.mjs} +3 -3
  39. package/dist/{planner-produced-postgres-migration-PIgjJYrY.mjs.map → planner-produced-postgres-migration-CcQPDWmX.mjs.map} +1 -1
  40. package/dist/planner-produced-postgres-migration.mjs +1 -1
  41. package/dist/{planner-sql-checks-BuB877wH.mjs → planner-sql-checks-CmiQ6Nei.mjs} +3 -3
  42. package/dist/{planner-sql-checks-BuB877wH.mjs.map → planner-sql-checks-CmiQ6Nei.mjs.map} +1 -1
  43. package/dist/planner-sql-checks.mjs +1 -1
  44. package/dist/planner.d.mts.map +1 -1
  45. package/dist/planner.mjs +2 -2
  46. package/dist/{postgres-contract-view-DebHMqGR.mjs → postgres-contract-view-B8yXVWYe.mjs} +3 -3
  47. package/dist/{postgres-contract-view-DebHMqGR.mjs.map → postgres-contract-view-B8yXVWYe.mjs.map} +1 -1
  48. package/dist/postgres-migration-DLIvzHjX.d.mts.map +1 -1
  49. package/dist/{postgres-migration-BkIplcRz.mjs → postgres-migration-GIOABsAa.mjs} +5 -5
  50. package/dist/{postgres-migration-BkIplcRz.mjs.map → postgres-migration-GIOABsAa.mjs.map} +1 -1
  51. package/dist/{postgres-schema-CewLYHB7.mjs → postgres-schema-CTplQZDK.mjs} +2 -2
  52. package/dist/{postgres-schema-CewLYHB7.mjs.map → postgres-schema-CTplQZDK.mjs.map} +1 -1
  53. package/dist/{render-ops-BREh1kHe.mjs → render-ops-TcY9XEkz.mjs} +7 -2
  54. package/dist/render-ops-TcY9XEkz.mjs.map +1 -0
  55. package/dist/render-ops.d.mts.map +1 -1
  56. package/dist/render-ops.mjs +1 -1
  57. package/dist/{resolve-ddl-schema-D0wi_P9Q.mjs → resolve-ddl-schema-BAsJEKW8.mjs} +2 -2
  58. package/dist/{resolve-ddl-schema-D0wi_P9Q.mjs.map → resolve-ddl-schema-BAsJEKW8.mjs.map} +1 -1
  59. package/dist/runtime.mjs +1 -1
  60. package/dist/schema-ir-annotations.mjs +1 -1
  61. package/dist/{sql-utils-SU4FDvIV.mjs → sql-utils-B2lzkKfP.mjs} +22 -19
  62. package/dist/sql-utils-B2lzkKfP.mjs.map +1 -0
  63. package/dist/sql-utils.d.mts +4 -9
  64. package/dist/sql-utils.d.mts.map +1 -1
  65. package/dist/sql-utils.mjs +2 -2
  66. package/dist/types.mjs +1 -1
  67. package/package.json +20 -20
  68. package/src/core/authoring.ts +28 -7
  69. package/src/core/codec-helpers.ts +69 -16
  70. package/src/core/codecs.ts +11 -2
  71. package/src/core/errors.ts +30 -0
  72. package/src/core/migrations/column-ddl-rendering.ts +2 -1
  73. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +14 -2
  74. package/src/core/migrations/op-factory-call.ts +88 -29
  75. package/src/core/migrations/operations/constraints.ts +3 -2
  76. package/src/core/migrations/operations/indexes.ts +4 -1
  77. package/src/core/migrations/operations/rls.ts +4 -1
  78. package/src/core/migrations/planner-ddl-builders.ts +13 -4
  79. package/src/core/migrations/render-ops.ts +4 -1
  80. package/src/core/migrations/runner.ts +7 -3
  81. package/src/core/psl-infer/infer-psl-contract.ts +22 -6
  82. package/src/core/sql-utils.ts +16 -27
  83. package/src/exports/sql-utils.ts +0 -1
  84. package/dist/authoring-34L18kA1.mjs.map +0 -1
  85. package/dist/codecs-DOgD_C1A.mjs.map +0 -1
  86. package/dist/control-policy-BrcqeM82.mjs.map +0 -1
  87. package/dist/diff-database-schema-CK0FM2Z-.mjs.map +0 -1
  88. package/dist/errors-ByDS0xJL.mjs.map +0 -1
  89. package/dist/op-factory-call-PBYfcv8h.mjs.map +0 -1
  90. package/dist/planner-ddl-builders-AIpFmG1i.mjs.map +0 -1
  91. package/dist/render-ops-BREh1kHe.mjs.map +0 -1
  92. package/dist/sql-utils-SU4FDvIV.mjs.map +0 -1
@@ -26,6 +26,7 @@ import { assertDefined } from '@prisma-next/utils/assertions';
26
26
  import { blindCast } from '@prisma-next/utils/casts';
27
27
  import { ifDefined } from '@prisma-next/utils/defined';
28
28
  import { PG_ENUM_CODEC_ID } from './codec-ids';
29
+ import { postgresError } from './errors';
29
30
  import { PostgresNativeEnum } from './postgres-native-enum';
30
31
  import { PostgresRlsEnablement, type PostgresRlsEnablementInput } from './postgres-rls-enablement';
31
32
  import { PostgresRlsPolicy, type RlsPolicyOperation } from './postgres-rls-policy';
@@ -723,12 +724,16 @@ function requireLocalTarget(
723
724
  return { namespaceId: target.namespaceId, tableName: target.tableName };
724
725
  }
725
726
  if (target !== undefined && target.kind === 'cross-space') {
726
- throw new Error(
727
+ throw postgresError(
728
+ 'CONTRACT.POLICY_INVALID',
727
729
  `defineContract: ${subject} targets model "${target.modelName ?? target.tableName}", which lives in another contract space. Policies and rlsEnabled entries must target a model declared in this contract.`,
730
+ { meta: { model: target.modelName ?? target.tableName, reason: 'cross-space-target' } },
728
731
  );
729
732
  }
730
- throw new Error(
733
+ throw postgresError(
734
+ 'CONTRACT.MODEL_UNKNOWN',
731
735
  `defineContract: ${subject} targets model "${target?.modelName ?? '<anonymous>'}", which is not in the contract's models. Add the model to \`models\`.`,
736
+ { meta: { model: target?.modelName ?? '<anonymous>' } },
732
737
  );
733
738
  }
734
739
 
@@ -780,8 +785,10 @@ export function postgresLowerEntityHandles(
780
785
  'role handles are constructed only by the postgres contract-builder role() constructor, which enforces this shape'
781
786
  >(handle);
782
787
  if (roles.has(roleHandle.name)) {
783
- throw new Error(
788
+ throw postgresError(
789
+ 'CONTRACT.ROLE_INVALID',
784
790
  `defineContract: role "${roleHandle.name}" is declared more than once in the entities list.`,
791
+ { meta: { role: roleHandle.name, reason: 'duplicate' } },
785
792
  );
786
793
  }
787
794
  // Roles are cluster-scoped in Postgres, so they always land in the
@@ -804,8 +811,10 @@ export function postgresLowerEntityHandles(
804
811
  break;
805
812
  }
806
813
  default:
807
- throw new Error(
814
+ throw postgresError(
815
+ 'CONTRACT.ENTITY_KIND_INVALID',
808
816
  `defineContract: the postgres pack does not lower "${handle.entityKind}" handles from the entities list.`,
817
+ { meta: { entityKind: handle.entityKind } },
809
818
  );
810
819
  }
811
820
  }
@@ -816,21 +825,33 @@ export function postgresLowerEntityHandles(
816
825
  for (const { handle: policy, refs } of policies) {
817
826
  const prefix = policy.name;
818
827
  if (prefix.length > RLS_POLICY_PREFIX_MAX_LENGTH) {
819
- throw new Error(
828
+ throw postgresError(
829
+ 'CONTRACT.POLICY_INVALID',
820
830
  `defineContract: policy prefix "${prefix}" exceeds the ${RLS_POLICY_PREFIX_MAX_LENGTH}-character maximum (Postgres identifiers cap at 63 characters and the wire name appends a 9-character hash suffix).`,
831
+ { meta: { prefix, reason: 'prefix-too-long' } },
821
832
  );
822
833
  }
823
834
  const coordinate = requireLocalTarget(refs, `policy "${prefix}"`);
824
835
  if (!enablements.has(coordinateKey(coordinate))) {
825
836
  const target = refs['target'];
826
- throw new Error(
837
+ throw postgresError(
838
+ 'CONTRACT.POLICY_INVALID',
827
839
  `defineContract: policy "${prefix}" targets model "${target?.modelName ?? coordinate.tableName}", whose table is not RLS-enabled. Add rlsEnabled(<model>) to the entities list.`,
840
+ {
841
+ meta: {
842
+ prefix,
843
+ model: target?.modelName ?? coordinate.tableName,
844
+ reason: 'target-not-rls-enabled',
845
+ },
846
+ },
828
847
  );
829
848
  }
830
849
  const prefixKey = `${coordinate.namespaceId} ${prefix}`;
831
850
  if (seenPrefixes.has(prefixKey)) {
832
- throw new Error(
851
+ throw postgresError(
852
+ 'CONTRACT.POLICY_INVALID',
833
853
  `defineContract: policy prefix "${prefix}" is declared more than once in namespace "${coordinate.namespaceId}". Policy prefixes must be unique per namespace.`,
854
+ { meta: { prefix, namespaceId: coordinate.namespaceId, reason: 'duplicate-prefix' } },
834
855
  );
835
856
  }
836
857
  seenPrefixes.add(prefixKey);
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { JsonValue } from '@prisma-next/contract/types';
10
+ import { postgresError } from './errors';
10
11
 
11
12
  export function renderLength(
12
13
  typeName: string,
@@ -17,8 +18,10 @@ export function renderLength(
17
18
  return undefined;
18
19
  }
19
20
  if (typeof length !== 'number' || !Number.isFinite(length) || !Number.isInteger(length)) {
20
- throw new Error(
21
+ throw postgresError(
22
+ 'RUNTIME.TYPE_PARAMS_INVALID',
21
23
  `renderOutputType: expected integer "length" in typeParams for ${typeName}, got ${String(length)}`,
24
+ { meta: { nativeType: typeName, param: 'length', received: String(length) } },
22
25
  );
23
26
  }
24
27
  return `${typeName}<${length}>`;
@@ -34,8 +37,10 @@ export function renderPrecision(typeName: string, typeParams: Record<string, unk
34
37
  !Number.isFinite(precision) ||
35
38
  !Number.isInteger(precision)
36
39
  ) {
37
- throw new Error(
40
+ throw postgresError(
41
+ 'RUNTIME.TYPE_PARAMS_INVALID',
38
42
  `renderOutputType: expected integer "precision" in typeParams for ${typeName}, got ${String(precision)}`,
43
+ { meta: { nativeType: typeName, param: 'precision', received: String(precision) } },
39
44
  );
40
45
  }
41
46
  return `${typeName}<${precision}>`;
@@ -57,15 +62,19 @@ export const pgNumericRenderOutputType = (typeParams: {
57
62
  !Number.isFinite(precision) ||
58
63
  !Number.isInteger(precision)
59
64
  ) {
60
- throw new Error(
65
+ throw postgresError(
66
+ 'RUNTIME.TYPE_PARAMS_INVALID',
61
67
  `renderOutputType: expected integer "precision" in typeParams for Numeric, got ${String(precision)}`,
68
+ { meta: { nativeType: 'Numeric', param: 'precision', received: String(precision) } },
62
69
  );
63
70
  }
64
71
  const scale = typeParams.scale;
65
72
  if (scale === undefined) return `Numeric<${precision}>`;
66
73
  if (typeof scale !== 'number' || !Number.isFinite(scale) || !Number.isInteger(scale)) {
67
- throw new Error(
74
+ throw postgresError(
75
+ 'RUNTIME.TYPE_PARAMS_INVALID',
68
76
  `renderOutputType: expected integer "scale" in typeParams for Numeric, got ${String(scale)}`,
77
+ { meta: { nativeType: 'Numeric', param: 'scale', received: String(scale) } },
69
78
  );
70
79
  }
71
80
  return `Numeric<${precision}, ${scale}>`;
@@ -78,14 +87,26 @@ const ISO_8601_TIMESTAMPTZ =
78
87
  export const pgTimestampEncodeJson = (value: Date): JsonValue => value.toISOString().slice(0, -1);
79
88
  export const pgTimestampDecodeJson = (json: JsonValue): Date => {
80
89
  if (typeof json !== 'string') {
81
- throw new Error(`Expected ISO date string for pg/timestamp@1, got ${typeof json}`);
90
+ throw postgresError(
91
+ 'RUNTIME.DECODE_FAILED',
92
+ `Expected ISO date string for pg/timestamp@1, got ${typeof json}`,
93
+ { meta: { codecId: 'pg/timestamp@1', received: typeof json } },
94
+ );
82
95
  }
83
96
  if (!ISO_8601_TIMESTAMP.test(json)) {
84
- throw new Error(`Invalid ISO date string for pg/timestamp@1: ${json}`);
97
+ throw postgresError(
98
+ 'RUNTIME.DECODE_FAILED',
99
+ `Invalid ISO date string for pg/timestamp@1: ${json}`,
100
+ { meta: { codecId: 'pg/timestamp@1', received: json } },
101
+ );
85
102
  }
86
103
  const date = new Date(`${json}Z`);
87
104
  if (Number.isNaN(date.getTime())) {
88
- throw new Error(`Invalid ISO date string for pg/timestamp@1: ${json}`);
105
+ throw postgresError(
106
+ 'RUNTIME.DECODE_FAILED',
107
+ `Invalid ISO date string for pg/timestamp@1: ${json}`,
108
+ { meta: { codecId: 'pg/timestamp@1', received: json } },
109
+ );
89
110
  }
90
111
  return date;
91
112
  };
@@ -94,14 +115,26 @@ export const pgTimestamptzEncodeJson = (value: Date): JsonValue =>
94
115
  value.toISOString().replace(/Z$/, '+00:00');
95
116
  export const pgTimestamptzDecodeJson = (json: JsonValue): Date => {
96
117
  if (typeof json !== 'string') {
97
- throw new Error(`Expected ISO date string for pg/timestamptz@1, got ${typeof json}`);
118
+ throw postgresError(
119
+ 'RUNTIME.DECODE_FAILED',
120
+ `Expected ISO date string for pg/timestamptz@1, got ${typeof json}`,
121
+ { meta: { codecId: 'pg/timestamptz@1', received: typeof json } },
122
+ );
98
123
  }
99
124
  if (!ISO_8601_TIMESTAMPTZ.test(json)) {
100
- throw new Error(`Invalid ISO date string for pg/timestamptz@1: ${json}`);
125
+ throw postgresError(
126
+ 'RUNTIME.DECODE_FAILED',
127
+ `Invalid ISO date string for pg/timestamptz@1: ${json}`,
128
+ { meta: { codecId: 'pg/timestamptz@1', received: json } },
129
+ );
101
130
  }
102
131
  const date = new Date(json);
103
132
  if (Number.isNaN(date.getTime())) {
104
- throw new Error(`Invalid ISO date string for pg/timestamptz@1: ${json}`);
133
+ throw postgresError(
134
+ 'RUNTIME.DECODE_FAILED',
135
+ `Invalid ISO date string for pg/timestamptz@1: ${json}`,
136
+ { meta: { codecId: 'pg/timestamptz@1', received: json } },
137
+ );
105
138
  }
106
139
  return date;
107
140
  };
@@ -141,11 +174,17 @@ export const pgDateEncodeJson = (value: Date): JsonValue => pgDateEncode(value);
141
174
 
142
175
  export const pgDateDecodeJson = (json: JsonValue): Date => {
143
176
  if (typeof json !== 'string') {
144
- throw new Error(`Expected date string for pg/date@1, got ${typeof json}`);
177
+ throw postgresError(
178
+ 'RUNTIME.DECODE_FAILED',
179
+ `Expected date string for pg/date@1, got ${typeof json}`,
180
+ { meta: { codecId: 'pg/date@1', received: typeof json } },
181
+ );
145
182
  }
146
183
  const match = ISO_8601_DATE.exec(json);
147
184
  if (!match) {
148
- throw new Error(`Invalid date string for pg/date@1: ${json}`);
185
+ throw postgresError('RUNTIME.DECODE_FAILED', `Invalid date string for pg/date@1: ${json}`, {
186
+ meta: { codecId: 'pg/date@1', received: json },
187
+ });
149
188
  }
150
189
  const [, yearText, monthText, dayText] = match;
151
190
  const year = Number(yearText);
@@ -153,7 +192,9 @@ export const pgDateDecodeJson = (json: JsonValue): Date => {
153
192
  const day = Number(dayText);
154
193
  const date = new Date(Date.UTC(year, month, day));
155
194
  if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month || date.getUTCDate() !== day) {
156
- throw new Error(`Invalid date string for pg/date@1: ${json}`);
195
+ throw postgresError('RUNTIME.DECODE_FAILED', `Invalid date string for pg/date@1: ${json}`, {
196
+ meta: { codecId: 'pg/date@1', received: json },
197
+ });
157
198
  }
158
199
  return date;
159
200
  };
@@ -168,19 +209,31 @@ export const pgByteaEncodeJson = (value: Uint8Array): JsonValue =>
168
209
 
169
210
  export const pgByteaDecodeJson = (value: JsonValue): Uint8Array => {
170
211
  if (typeof value !== 'string' || !value.startsWith('\\x')) {
171
- throw new Error(`Expected Postgres bytea hex text to start with "\\x"`);
212
+ throw postgresError(
213
+ 'RUNTIME.DECODE_FAILED',
214
+ `Expected Postgres bytea hex text to start with "\\x"`,
215
+ { meta: { codecId: 'pg/bytea@1' } },
216
+ );
172
217
  }
173
218
 
174
219
  const hex = value.slice(2);
175
220
  if (hex.length % 2 !== 0) {
176
- throw new Error(`Invalid Postgres bytea hex text length: ${hex.length}`);
221
+ throw postgresError(
222
+ 'RUNTIME.DECODE_FAILED',
223
+ `Invalid Postgres bytea hex text length: ${hex.length}`,
224
+ { meta: { codecId: 'pg/bytea@1', received: hex.length } },
225
+ );
177
226
  }
178
227
 
179
228
  const bytes = new Uint8Array(hex.length / 2);
180
229
  for (let offset = 0; offset < hex.length; offset += 2) {
181
230
  const pair = hex.slice(offset, offset + 2);
182
231
  if (!/^[0-9a-fA-F]{2}$/.test(pair)) {
183
- throw new Error(`Invalid Postgres bytea hex pair "${pair}" at offset ${offset}`);
232
+ throw postgresError(
233
+ 'RUNTIME.DECODE_FAILED',
234
+ `Invalid Postgres bytea hex pair "${pair}" at offset ${offset}`,
235
+ { meta: { codecId: 'pg/bytea@1', received: pair } },
236
+ );
184
237
  }
185
238
  bytes[offset / 2] = Number.parseInt(pair, 16);
186
239
  }
@@ -92,6 +92,7 @@ import {
92
92
  PG_VARBIT_CODEC_ID,
93
93
  PG_VARCHAR_CODEC_ID,
94
94
  } from './codec-ids';
95
+ import { postgresError } from './errors';
95
96
  import { DEFAULT_NAMESPACE_ID } from './namespace-ids';
96
97
  import { PostgresNativeEnum } from './postgres-native-enum';
97
98
 
@@ -632,13 +633,21 @@ export class PgNumericCodec extends CodecImpl<
632
633
  encodeJson(value: string): JsonValue {
633
634
  const number = Number(value);
634
635
  if (!Number.isFinite(number)) {
635
- throw new TypeError('pg/numeric@1 database JSON value must be a finite number');
636
+ throw postgresError(
637
+ 'RUNTIME.ENCODE_FAILED',
638
+ 'pg/numeric@1 database JSON value must be a finite number',
639
+ { meta: { codecId: 'pg/numeric@1', received: value } },
640
+ );
636
641
  }
637
642
  return number;
638
643
  }
639
644
  decodeJson(json: JsonValue): string {
640
645
  if (typeof json !== 'number') {
641
- throw new TypeError('pg/numeric@1 database JSON value must be a number');
646
+ throw postgresError(
647
+ 'RUNTIME.DECODE_FAILED',
648
+ 'pg/numeric@1 database JSON value must be a number',
649
+ { meta: { codecId: 'pg/numeric@1', received: typeof json } },
650
+ );
642
651
  }
643
652
  return pgNumericDecode(json);
644
653
  }
@@ -1,4 +1,34 @@
1
1
  import { CliStructuredError } from '@prisma-next/errors/control';
2
+ import type { StructuredError, StructuredErrorOptions } from '@prisma-next/utils/structured-error';
3
+ import { structuredError } from '@prisma-next/utils/structured-error';
4
+
5
+ export type PostgresTargetErrorCode =
6
+ | 'CONTRACT.CODEC_DESCRIPTOR_MISSING'
7
+ | 'CONTRACT.DEFAULT_INVALID'
8
+ | 'CONTRACT.ENTITY_KIND_INVALID'
9
+ | 'CONTRACT.IDENTIFIER_INVALID'
10
+ | 'CONTRACT.INDEX_INVALID'
11
+ | 'CONTRACT.INFER_UNSUPPORTED'
12
+ | 'CONTRACT.MODEL_UNKNOWN'
13
+ | 'CONTRACT.NAME_DUPLICATE'
14
+ | 'CONTRACT.NATIVE_TYPE_INVALID'
15
+ | 'CONTRACT.PACK_CONTRIBUTION_INVALID'
16
+ | 'CONTRACT.POLICY_INVALID'
17
+ | 'CONTRACT.ROLE_INVALID'
18
+ | 'MIGRATION.CONTRACT_SPACE_VIOLATION'
19
+ | 'MIGRATION.POSTGRES_CONTROL_STACK_MISSING'
20
+ | 'MIGRATION.TARGET_MISMATCH'
21
+ | 'RUNTIME.DECODE_FAILED'
22
+ | 'RUNTIME.ENCODE_FAILED'
23
+ | 'RUNTIME.TYPE_PARAMS_INVALID';
24
+
25
+ export function postgresError(
26
+ code: PostgresTargetErrorCode,
27
+ message: string,
28
+ options?: StructuredErrorOptions,
29
+ ): StructuredError {
30
+ return structuredError(code, message, options);
31
+ }
2
32
 
3
33
  /**
4
34
  * A `PostgresMigration` operation that needs the materialized control adapter
@@ -5,6 +5,7 @@ import * as contractFree from '@prisma-next/sql-relational-core/contract-free';
5
5
  import type { SqlColumnDefaultIR, SqlColumnIR } from '@prisma-next/sql-schema-ir/types';
6
6
  import { blindCast } from '@prisma-next/utils/casts';
7
7
  import { ifDefined } from '@prisma-next/utils/defined';
8
+ import { InternalError } from '@prisma-next/utils/internal-error';
8
9
  import { postgresDefaultToDdlColumnDefault } from './op-factory-call';
9
10
  import { buildColumnDefaultSql, buildColumnTypeSql } from './planner-ddl-builders';
10
11
  import { resolveIdentityValue } from './planner-identity-values';
@@ -29,7 +30,7 @@ function columnLike(
29
30
  'nativeType' | 'codecId' | 'nullable' | 'many' | 'typeParams' | 'typeRef' | 'default'
30
31
  > {
31
32
  if (column.codecRef === undefined || column.codecBaseNativeType === undefined) {
32
- throw new Error(
33
+ throw new InternalError(
33
34
  `columnLike: expected column "${column.name}" carries no codec identity — the expected tree must be derived via contractToSchemaIR for planning`,
34
35
  );
35
36
  }
@@ -10,6 +10,7 @@ import {
10
10
  SqlUniqueIR,
11
11
  } from '@prisma-next/sql-schema-ir/types';
12
12
  import { ifDefined } from '@prisma-next/utils/defined';
13
+ import { postgresError } from '../errors';
13
14
  import type { PostgresRlsPolicy } from '../postgres-rls-policy';
14
15
  import type { PostgresContract } from '../postgres-schema';
15
16
  import { isPostgresSchema } from '../postgres-schema';
@@ -232,14 +233,25 @@ export function contractToPostgresDatabaseSchemaNode(
232
233
  for (const [tableName, tablePolicies] of policiesByTable) {
233
234
  if (!(tableName in tables)) {
234
235
  const policyName = tablePolicies[0]?.name ?? '(unknown)';
235
- throw new Error(
236
+ throw postgresError(
237
+ 'CONTRACT.POLICY_INVALID',
236
238
  `contract-to-postgres-database-schema-node: policy "${policyName}" references table "${tableName}" not present in namespace "${ddlSchema}"`,
239
+ { meta: { policyName, tableName, namespaceId: ddlSchema, reason: 'table-missing' } },
237
240
  );
238
241
  }
239
242
  if (!Object.hasOwn(ns.rls, tableName)) {
240
243
  const policyPrefix = tablePolicies[0]?.prefix ?? '(unknown)';
241
- throw new Error(
244
+ throw postgresError(
245
+ 'CONTRACT.POLICY_INVALID',
242
246
  `contract-to-postgres-database-schema-node: policy "${policyPrefix}" targets table "${tableName}" in namespace "${ddlSchema}", which is not RLS-controlled. Mark the model with @@rls (entries.rls["${tableName}"]) or remove the policy.`,
247
+ {
248
+ meta: {
249
+ policyName: policyPrefix,
250
+ tableName,
251
+ namespaceId: ddlSchema,
252
+ reason: 'table-not-rls-controlled',
253
+ },
254
+ },
243
255
  );
244
256
  }
245
257
  }