@prisma-next/target-postgres 0.16.0-dev.3 → 0.16.0-dev.31

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 (166) hide show
  1. package/README.md +1 -1
  2. package/dist/{authoring-34L18kA1.mjs → authoring-DnO1B0N9.mjs} +29 -18
  3. package/dist/authoring-DnO1B0N9.mjs.map +1 -0
  4. package/dist/canonicalize-C7zKHw8m.mjs +59 -0
  5. package/dist/canonicalize-C7zKHw8m.mjs.map +1 -0
  6. package/dist/{canonicalize-D1F4vUnP.d.mts → canonicalize-CQlxSR4D.d.mts} +3 -18
  7. package/dist/canonicalize-CQlxSR4D.d.mts.map +1 -0
  8. package/dist/{codec-types-CQ07nOz-.d.mts → codec-types-DdoPc2dy.d.mts} +3 -2
  9. package/dist/codec-types-DdoPc2dy.d.mts.map +1 -0
  10. package/dist/codec-types.d.mts +1 -1
  11. package/dist/{codecs-G4DTY0F_.d.mts → codecs-CGE5dh94.d.mts} +7 -2
  12. package/dist/codecs-CGE5dh94.d.mts.map +1 -0
  13. package/dist/{codecs-TCsGgW-Y.mjs → codecs-Deand7sy.mjs} +84 -26
  14. package/dist/codecs-Deand7sy.mjs.map +1 -0
  15. package/dist/codecs.d.mts +2 -2
  16. package/dist/codecs.mjs +2 -2
  17. package/dist/contract-free.d.mts +1 -1
  18. package/dist/contract-free.d.mts.map +1 -1
  19. package/dist/contract-free.mjs +2 -2
  20. package/dist/{control-policy-CZKdMztu.mjs → control-policy-DEwdS3oK.mjs} +29 -29
  21. package/dist/control-policy-DEwdS3oK.mjs.map +1 -0
  22. package/dist/control.mjs +91 -239
  23. package/dist/control.mjs.map +1 -1
  24. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  25. package/dist/{ddl-DHXKzQ99.mjs → ddl-BQC6u-L9.mjs} +28 -4
  26. package/dist/{ddl-DHXKzQ99.mjs.map → ddl-BQC6u-L9.mjs.map} +1 -1
  27. package/dist/ddl.d.mts +3 -3
  28. package/dist/ddl.mjs +2 -2
  29. package/dist/{descriptor-meta-qubYaxQA.mjs → descriptor-meta-DGlJurgA.mjs} +4 -4
  30. package/dist/{descriptor-meta-qubYaxQA.mjs.map → descriptor-meta-DGlJurgA.mjs.map} +1 -1
  31. package/dist/{diff-database-schema-C-hnJ5mr.mjs → diff-database-schema-qRPwuYBH.mjs} +37 -15
  32. package/dist/diff-database-schema-qRPwuYBH.mjs.map +1 -0
  33. package/dist/diff-database-schema.mjs +1 -1
  34. package/dist/{errors-ByDS0xJL.mjs → errors-DBSNx7EJ.mjs} +6 -2
  35. package/dist/errors-DBSNx7EJ.mjs.map +1 -0
  36. package/dist/errors.d.mts +4 -1
  37. package/dist/errors.d.mts.map +1 -1
  38. package/dist/errors.mjs +2 -2
  39. package/dist/issue-planner.d.mts +2 -2
  40. package/dist/issue-planner.d.mts.map +1 -1
  41. package/dist/issue-planner.mjs +1 -1
  42. package/dist/migration.d.mts +2 -2
  43. package/dist/migration.mjs +2 -2
  44. package/dist/{nodes-DJC5B8Id.mjs → nodes-04EILM-z.mjs} +65 -2
  45. package/dist/nodes-04EILM-z.mjs.map +1 -0
  46. package/dist/{nodes-CQzM2hR0.d.mts → nodes-DjCsN-uv.d.mts} +69 -4
  47. package/dist/nodes-DjCsN-uv.d.mts.map +1 -0
  48. package/dist/{op-factory-call-DT7kdjVP.d.mts → op-factory-call-0TB5XNij.d.mts} +46 -8
  49. package/dist/op-factory-call-0TB5XNij.d.mts.map +1 -0
  50. package/dist/{op-factory-call-DuRj7WXX.mjs → op-factory-call-CIaPDDUc.mjs} +147 -64
  51. package/dist/op-factory-call-CIaPDDUc.mjs.map +1 -0
  52. package/dist/op-factory-call.d.mts +2 -2
  53. package/dist/op-factory-call.mjs +2 -2
  54. package/dist/pack.d.mts +2 -2
  55. package/dist/pack.d.mts.map +1 -1
  56. package/dist/pack.mjs +1 -1
  57. package/dist/{planner-DPGxFcXo.mjs → planner-BhnkIJX-.mjs} +121 -13
  58. package/dist/planner-BhnkIJX-.mjs.map +1 -0
  59. package/dist/{planner-ddl-builders-BKlmGDJl.mjs → planner-ddl-builders-CVo3L7Nd.mjs} +12 -7
  60. package/dist/planner-ddl-builders-CVo3L7Nd.mjs.map +1 -0
  61. package/dist/planner-ddl-builders.d.mts.map +1 -1
  62. package/dist/planner-ddl-builders.mjs +1 -1
  63. package/dist/{planner-produced-postgres-migration-CFDpqWWE.mjs → planner-produced-postgres-migration-BE3EJIRK.mjs} +9 -6
  64. package/dist/planner-produced-postgres-migration-BE3EJIRK.mjs.map +1 -0
  65. package/dist/{planner-produced-postgres-migration-C-dwGdb2.d.mts → planner-produced-postgres-migration-CRAHl6ak.d.mts} +3 -3
  66. package/dist/planner-produced-postgres-migration-CRAHl6ak.d.mts.map +1 -0
  67. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  68. package/dist/planner-produced-postgres-migration.mjs +1 -1
  69. package/dist/{planner-schema-lookup-CiVaAQP-.mjs → planner-schema-lookup-D3Y_OL-C.mjs} +3 -3
  70. package/dist/planner-schema-lookup-D3Y_OL-C.mjs.map +1 -0
  71. package/dist/planner-schema-lookup.mjs +1 -1
  72. package/dist/{planner-sql-checks-BuB877wH.mjs → planner-sql-checks-CmiQ6Nei.mjs} +3 -3
  73. package/dist/{planner-sql-checks-BuB877wH.mjs.map → planner-sql-checks-CmiQ6Nei.mjs.map} +1 -1
  74. package/dist/planner-sql-checks.mjs +1 -1
  75. package/dist/planner.d.mts +27 -1
  76. package/dist/planner.d.mts.map +1 -1
  77. package/dist/planner.mjs +2 -2
  78. package/dist/{postgres-contract-view-DebHMqGR.mjs → postgres-contract-view-BeIEyZfD.mjs} +3 -3
  79. package/dist/{postgres-contract-view-DebHMqGR.mjs.map → postgres-contract-view-BeIEyZfD.mjs.map} +1 -1
  80. package/dist/{postgres-migration-C_OPFhIF.mjs → postgres-migration--BzvpA92.mjs} +9 -6
  81. package/dist/postgres-migration--BzvpA92.mjs.map +1 -0
  82. package/dist/{postgres-migration-DLIvzHjX.d.mts → postgres-migration-BXelnqHO.d.mts} +15 -10
  83. package/dist/postgres-migration-BXelnqHO.d.mts.map +1 -0
  84. package/dist/{postgres-schema-CewLYHB7.mjs → postgres-schema-CTplQZDK.mjs} +2 -2
  85. package/dist/{postgres-schema-CewLYHB7.mjs.map → postgres-schema-CTplQZDK.mjs.map} +1 -1
  86. package/dist/{render-ops-BREh1kHe.mjs → render-ops-TcY9XEkz.mjs} +7 -2
  87. package/dist/render-ops-TcY9XEkz.mjs.map +1 -0
  88. package/dist/render-ops.d.mts.map +1 -1
  89. package/dist/render-ops.mjs +1 -1
  90. package/dist/{render-typescript-7yqILcwr.mjs → render-typescript-Ta7_6CGf.mjs} +20 -8
  91. package/dist/render-typescript-Ta7_6CGf.mjs.map +1 -0
  92. package/dist/render-typescript.d.mts +2 -0
  93. package/dist/render-typescript.d.mts.map +1 -1
  94. package/dist/render-typescript.mjs +1 -1
  95. package/dist/{resolve-ddl-schema-D0wi_P9Q.mjs → resolve-ddl-schema-BAsJEKW8.mjs} +2 -2
  96. package/dist/{resolve-ddl-schema-D0wi_P9Q.mjs.map → resolve-ddl-schema-BAsJEKW8.mjs.map} +1 -1
  97. package/dist/rls-canonicalize.d.mts +2 -26
  98. package/dist/rls-canonicalize.mjs +2 -2
  99. package/dist/runtime.mjs +1 -1
  100. package/dist/schema-ir-annotations.mjs +1 -1
  101. package/dist/{sql-utils-SU4FDvIV.mjs → sql-utils-B2lzkKfP.mjs} +22 -19
  102. package/dist/sql-utils-B2lzkKfP.mjs.map +1 -0
  103. package/dist/sql-utils.d.mts +4 -9
  104. package/dist/sql-utils.d.mts.map +1 -1
  105. package/dist/sql-utils.mjs +2 -2
  106. package/dist/types.mjs +1 -1
  107. package/package.json +20 -20
  108. package/src/contract-free/ddl.ts +47 -0
  109. package/src/core/authoring.ts +35 -26
  110. package/src/core/codec-helpers.ts +69 -16
  111. package/src/core/codec-type-map.ts +2 -0
  112. package/src/core/codecs.ts +29 -14
  113. package/src/core/ddl/nodes.ts +104 -0
  114. package/src/core/errors.ts +30 -0
  115. package/src/core/migrations/column-ddl-rendering.ts +2 -1
  116. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +38 -14
  117. package/src/core/migrations/issue-planner.ts +40 -13
  118. package/src/core/migrations/op-factory-call.ts +189 -44
  119. package/src/core/migrations/operations/constraints.ts +3 -2
  120. package/src/core/migrations/operations/data-transform.ts +1 -1
  121. package/src/core/migrations/operations/indexes.ts +80 -32
  122. package/src/core/migrations/operations/rls.ts +4 -1
  123. package/src/core/migrations/planner-ddl-builders.ts +16 -6
  124. package/src/core/migrations/planner-produced-postgres-migration.ts +8 -1
  125. package/src/core/migrations/planner-schema-lookup.ts +2 -2
  126. package/src/core/migrations/planner.ts +163 -5
  127. package/src/core/migrations/postgres-migration.ts +26 -8
  128. package/src/core/migrations/render-ops.ts +4 -1
  129. package/src/core/migrations/render-typescript.ts +20 -8
  130. package/src/core/migrations/runner.ts +7 -3
  131. package/src/core/psl-infer/infer-psl-contract.ts +36 -185
  132. package/src/core/psl-infer/postgres-type-map.ts +43 -65
  133. package/src/core/rls/canonicalize.ts +4 -21
  134. package/src/core/sql-utils.ts +16 -27
  135. package/src/exports/codecs.ts +2 -0
  136. package/src/exports/ddl.ts +4 -0
  137. package/src/exports/errors.ts +1 -1
  138. package/src/exports/op-factory-call.ts +1 -0
  139. package/src/exports/rls-canonicalize.ts +0 -6
  140. package/src/exports/sql-utils.ts +0 -1
  141. package/dist/authoring-34L18kA1.mjs.map +0 -1
  142. package/dist/canonicalize-D1F4vUnP.d.mts.map +0 -1
  143. package/dist/codec-types-CQ07nOz-.d.mts.map +0 -1
  144. package/dist/codecs-G4DTY0F_.d.mts.map +0 -1
  145. package/dist/codecs-TCsGgW-Y.mjs.map +0 -1
  146. package/dist/control-policy-CZKdMztu.mjs.map +0 -1
  147. package/dist/diff-database-schema-C-hnJ5mr.mjs.map +0 -1
  148. package/dist/errors-ByDS0xJL.mjs.map +0 -1
  149. package/dist/nodes-CQzM2hR0.d.mts.map +0 -1
  150. package/dist/nodes-DJC5B8Id.mjs.map +0 -1
  151. package/dist/op-factory-call-DT7kdjVP.d.mts.map +0 -1
  152. package/dist/op-factory-call-DuRj7WXX.mjs.map +0 -1
  153. package/dist/planner-DPGxFcXo.mjs.map +0 -1
  154. package/dist/planner-ddl-builders-BKlmGDJl.mjs.map +0 -1
  155. package/dist/planner-produced-postgres-migration-C-dwGdb2.d.mts.map +0 -1
  156. package/dist/planner-produced-postgres-migration-CFDpqWWE.mjs.map +0 -1
  157. package/dist/planner-schema-lookup-CiVaAQP-.mjs.map +0 -1
  158. package/dist/postgres-migration-C_OPFhIF.mjs.map +0 -1
  159. package/dist/postgres-migration-DLIvzHjX.d.mts.map +0 -1
  160. package/dist/render-ops-BREh1kHe.mjs.map +0 -1
  161. package/dist/render-typescript-7yqILcwr.mjs.map +0 -1
  162. package/dist/rls-canonicalize.d.mts.map +0 -1
  163. package/dist/sql-utils-SU4FDvIV.mjs.map +0 -1
  164. package/dist/wire-name-DbQ9-2RC.mjs +0 -104
  165. package/dist/wire-name-DbQ9-2RC.mjs.map +0 -1
  166. package/src/core/rls/wire-name.ts +0 -33
@@ -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
  }
@@ -21,6 +21,7 @@ import {
21
21
  pgBoolDescriptor,
22
22
  pgByteaDescriptor,
23
23
  pgCharDescriptor,
24
+ pgDateDescriptor,
24
25
  pgEnumDescriptor,
25
26
  pgFloat4Descriptor,
26
27
  pgFloat8Descriptor,
@@ -63,6 +64,7 @@ export const codecDescriptorMap = {
63
64
  float4: pgFloat4Descriptor,
64
65
  float8: pgFloat8Descriptor,
65
66
  numeric: pgNumericDescriptor,
67
+ date: pgDateDescriptor,
66
68
  timestamp: pgTimestampDescriptor,
67
69
  timestamptz: pgTimestamptzDescriptor,
68
70
  time: pgTimeDescriptor,
@@ -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
 
@@ -218,18 +219,26 @@ export class PgEnumCodec extends CodecImpl<
218
219
  }
219
220
  }
220
221
 
221
- type PgEnumParams = { readonly typeName: string };
222
+ export type PgEnumParams = { readonly typeName: string };
223
+
224
+ /**
225
+ * Narrows codec `typeParams` to {@link PgEnumParams} — the shape that binds a
226
+ * column to a named database type (a native enum's `CREATE TYPE` name).
227
+ */
228
+ export function isPgEnumParams(value: unknown): value is PgEnumParams {
229
+ return (
230
+ typeof value === 'object' &&
231
+ value !== null &&
232
+ !Array.isArray(value) &&
233
+ 'typeName' in value &&
234
+ typeof value.typeName === 'string'
235
+ );
236
+ }
222
237
 
223
238
  const pgEnumParamsSchema = arktype({
224
239
  typeName: 'string',
225
240
  }) satisfies StandardSchemaV1<PgEnumParams>;
226
241
 
227
- function isJsonObject(
228
- value: JsonValue | undefined,
229
- ): value is { readonly [key: string]: JsonValue } {
230
- return typeof value === 'object' && value !== null && !Array.isArray(value);
231
- }
232
-
233
242
  export class PgEnumDescriptor extends CodecDescriptorImpl<PgEnumParams> {
234
243
  override readonly codecId = PG_ENUM_CODEC_ID;
235
244
  override readonly traits = ['equality', 'order', 'textual'] as const;
@@ -240,10 +249,8 @@ export class PgEnumDescriptor extends CodecDescriptorImpl<PgEnumParams> {
240
249
  return renderTsLiteral(value);
241
250
  }
242
251
  override metaFor(typeParams: JsonValue | undefined): CodecMeta | undefined {
243
- if (!isJsonObject(typeParams)) return this.meta;
244
- const typeName = typeParams['typeName'];
245
- return typeof typeName === 'string'
246
- ? { db: { sql: { postgres: { nativeType: typeName } } } }
252
+ return isPgEnumParams(typeParams)
253
+ ? { db: { sql: { postgres: { nativeType: typeParams.typeName } } } }
247
254
  : this.meta;
248
255
  }
249
256
  override factory(_params: PgEnumParams): (ctx: CodecInstanceContext) => PgEnumCodec {
@@ -626,13 +633,21 @@ export class PgNumericCodec extends CodecImpl<
626
633
  encodeJson(value: string): JsonValue {
627
634
  const number = Number(value);
628
635
  if (!Number.isFinite(number)) {
629
- 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
+ );
630
641
  }
631
642
  return number;
632
643
  }
633
644
  decodeJson(json: JsonValue): string {
634
645
  if (typeof json !== 'number') {
635
- 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
+ );
636
651
  }
637
652
  return pgNumericDecode(json);
638
653
  }
@@ -1336,7 +1351,7 @@ export const codecDescriptors: readonly AnyCodecDescriptor[] = [
1336
1351
  pgFloat4Descriptor,
1337
1352
  pgFloat8Descriptor,
1338
1353
  pgNumericDescriptor,
1339
- // PSL `@db.Date` binding is deferred to remove-db-attributes' `Date` type; pin it via codecId, not a second targetTypes activation here.
1354
+ // PSL `Date` pins this codec by ID rather than activating a second target-type mapping.
1340
1355
  pgDateDescriptor,
1341
1356
  pgTimestampDescriptor,
1342
1357
  pgTimestamptzDescriptor,
@@ -73,6 +73,9 @@ export interface PostgresDdlVisitor<R> {
73
73
  createPolicy(node: PostgresCreatePolicy): R;
74
74
  dropPolicy(node: PostgresDropPolicy): R;
75
75
  alterPolicyRename(node: PostgresAlterPolicyRename): R;
76
+ createIndex(node: PostgresCreateIndex): R;
77
+ dropIndex(node: PostgresDropIndex): R;
78
+ alterIndexRename(node: PostgresAlterIndexRename): R;
76
79
  disableRowLevelSecurity(node: PostgresDisableRowLevelSecurity): R;
77
80
  }
78
81
 
@@ -284,6 +287,104 @@ export class PostgresAlterPolicyRename extends PostgresDdlNode {
284
287
  }
285
288
  }
286
289
 
290
+ /**
291
+ * The element list between the parens of CREATE INDEX: either a column
292
+ * tuple (each identifier quoted by the renderer) or one opaque expression
293
+ * string covering the entire list, inserted verbatim — the same opaque-SQL
294
+ * stance as RLS policy predicates (ADR 234).
295
+ */
296
+ export type DdlIndexElements =
297
+ | { readonly columns: readonly string[] }
298
+ | { readonly expression: string };
299
+
300
+ export class PostgresCreateIndex extends PostgresDdlNode {
301
+ readonly kind = 'create-index' as const;
302
+ /** Absent ⇔ unqualified (the unbound namespace — `search_path` decides). */
303
+ readonly schema: string | undefined;
304
+ readonly table: string;
305
+ readonly name: string;
306
+ readonly unique: boolean;
307
+ readonly elements: DdlIndexElements;
308
+ readonly type: string | undefined;
309
+ readonly options: Record<string, unknown> | undefined;
310
+ /**
311
+ * Partial-index predicate (WHERE body, without the keyword). Inserted
312
+ * verbatim, never quoted or escaped.
313
+ */
314
+ readonly where: string | undefined;
315
+
316
+ constructor(options: {
317
+ readonly schema: string | undefined;
318
+ readonly table: string;
319
+ readonly name: string;
320
+ readonly unique: boolean;
321
+ readonly elements: DdlIndexElements;
322
+ readonly type: string | undefined;
323
+ readonly options: Record<string, unknown> | undefined;
324
+ readonly where: string | undefined;
325
+ }) {
326
+ super();
327
+ this.schema = options.schema;
328
+ this.table = options.table;
329
+ this.name = options.name;
330
+ this.unique = options.unique;
331
+ this.elements =
332
+ 'columns' in options.elements
333
+ ? { columns: Object.freeze([...options.elements.columns]) }
334
+ : { expression: options.elements.expression };
335
+ this.type = options.type;
336
+ this.options = options.options;
337
+ this.where = options.where;
338
+ this.freeze();
339
+ }
340
+
341
+ override accept<R>(visitor: PostgresDdlVisitor<R>): R {
342
+ return visitor.createIndex(this);
343
+ }
344
+ }
345
+
346
+ export class PostgresDropIndex extends PostgresDdlNode {
347
+ readonly kind = 'drop-index' as const;
348
+ /** Absent ⇔ unqualified (the unbound namespace). */
349
+ readonly schema: string | undefined;
350
+ readonly name: string;
351
+
352
+ constructor(options: { readonly schema: string | undefined; readonly name: string }) {
353
+ super();
354
+ this.schema = options.schema;
355
+ this.name = options.name;
356
+ this.freeze();
357
+ }
358
+
359
+ override accept<R>(visitor: PostgresDdlVisitor<R>): R {
360
+ return visitor.dropIndex(this);
361
+ }
362
+ }
363
+
364
+ export class PostgresAlterIndexRename extends PostgresDdlNode {
365
+ readonly kind = 'alter-index-rename' as const;
366
+ /** Absent ⇔ unqualified (the unbound namespace). */
367
+ readonly schema: string | undefined;
368
+ readonly from: string;
369
+ readonly to: string;
370
+
371
+ constructor(options: {
372
+ readonly schema: string | undefined;
373
+ readonly from: string;
374
+ readonly to: string;
375
+ }) {
376
+ super();
377
+ this.schema = options.schema;
378
+ this.from = options.from;
379
+ this.to = options.to;
380
+ this.freeze();
381
+ }
382
+
383
+ override accept<R>(visitor: PostgresDdlVisitor<R>): R {
384
+ return visitor.alterIndexRename(this);
385
+ }
386
+ }
387
+
287
388
  export class PostgresDisableRowLevelSecurity extends PostgresDdlNode {
288
389
  readonly kind = 'disable-row-level-security' as const;
289
390
  readonly schema: string;
@@ -310,4 +411,7 @@ export type AnyPostgresDdlNode =
310
411
  | PostgresCreatePolicy
311
412
  | PostgresDropPolicy
312
413
  | PostgresAlterPolicyRename
414
+ | PostgresCreateIndex
415
+ | PostgresDropIndex
416
+ | PostgresAlterIndexRename
313
417
  | PostgresDisableRowLevelSecurity;
@@ -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';
@@ -192,18 +193,30 @@ export function contractToPostgresDatabaseSchemaNode(
192
193
  dependsOn: columnDependsOn(ddlSchema, tableName, u.columns),
193
194
  }),
194
195
  );
195
- const indexes = sqlTable.indexes.map(
196
- (i) =>
197
- new SqlIndexIR({
198
- columns: i.columns,
199
- unique: i.unique,
200
- ...ifDefined('name', i.name),
201
- ...ifDefined('type', i.type),
202
- ...ifDefined('options', i.options),
203
- ...ifDefined('annotations', i.annotations),
204
- dependsOn: columnDependsOn(ddlSchema, tableName, i.columns),
205
- }),
206
- );
196
+ const indexes = sqlTable.indexes.map((i) => {
197
+ const base = {
198
+ name: i.name,
199
+ prefix: i.prefix,
200
+ where: i.where,
201
+ unique: i.unique,
202
+ partial: i.partial,
203
+ type: i.type,
204
+ options: i.options,
205
+ annotations: i.annotations,
206
+ // Expression indexes stamp chains to every column of the table —
207
+ // the same over-approximation the family derivation uses.
208
+ dependsOn: columnDependsOn(
209
+ ddlSchema,
210
+ tableName,
211
+ i.columns ?? Object.keys(sqlTable.columns),
212
+ ),
213
+ };
214
+ return new SqlIndexIR(
215
+ i.expression !== undefined
216
+ ? { ...base, expression: i.expression }
217
+ : { ...base, columns: i.columns ?? [] },
218
+ );
219
+ });
207
220
  const primaryKey =
208
221
  sqlTable.primaryKey !== undefined
209
222
  ? new PrimaryKey({
@@ -231,14 +244,25 @@ export function contractToPostgresDatabaseSchemaNode(
231
244
  for (const [tableName, tablePolicies] of policiesByTable) {
232
245
  if (!(tableName in tables)) {
233
246
  const policyName = tablePolicies[0]?.name ?? '(unknown)';
234
- throw new Error(
247
+ throw postgresError(
248
+ 'CONTRACT.POLICY_INVALID',
235
249
  `contract-to-postgres-database-schema-node: policy "${policyName}" references table "${tableName}" not present in namespace "${ddlSchema}"`,
250
+ { meta: { policyName, tableName, namespaceId: ddlSchema, reason: 'table-missing' } },
236
251
  );
237
252
  }
238
253
  if (!Object.hasOwn(ns.rls, tableName)) {
239
254
  const policyPrefix = tablePolicies[0]?.prefix ?? '(unknown)';
240
- throw new Error(
255
+ throw postgresError(
256
+ 'CONTRACT.POLICY_INVALID',
241
257
  `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.`,
258
+ {
259
+ meta: {
260
+ policyName: policyPrefix,
261
+ tableName,
262
+ namespaceId: ddlSchema,
263
+ reason: 'table-not-rls-controlled',
264
+ },
265
+ },
242
266
  );
243
267
  }
244
268
  }
@@ -30,7 +30,6 @@ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
30
30
  import type { SqlStorage, StorageTypeInstance } from '@prisma-next/sql-contract/types';
31
31
  import type { DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
32
32
  import * as contractFree from '@prisma-next/sql-relational-core/contract-free';
33
- import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
34
33
  import {
35
34
  RelationalSchemaNodeKind,
36
35
  type SqlColumnDefaultIR,
@@ -200,6 +199,7 @@ function classifyCall(call: PostgresOpFactoryCall): CallCategory {
200
199
  case 'addUnique':
201
200
  return 'unique';
202
201
  case 'createIndex':
202
+ case 'renameIndex':
203
203
  return 'index';
204
204
  case 'addForeignKey':
205
205
  return 'foreignKey';
@@ -248,6 +248,7 @@ function conflictKindForCall(call: PostgresOpFactoryCall): SqlPlannerConflict['k
248
248
  case 'dropConstraint':
249
249
  return 'foreignKeyConflict';
250
250
  case 'createIndex':
251
+ case 'renameIndex':
251
252
  case 'dropIndex':
252
253
  return 'indexIncompatible';
253
254
  default:
@@ -263,6 +264,7 @@ function locationForCall(call: PostgresOpFactoryCall): SqlPlannerConflict['locat
263
264
  tableName?: string;
264
265
  columnName?: string;
265
266
  indexName?: string;
267
+ newIndexName?: string;
266
268
  constraintName?: string;
267
269
  typeName?: string;
268
270
  };
@@ -281,7 +283,10 @@ function locationForCall(call: PostgresOpFactoryCall): SqlPlannerConflict['locat
281
283
  location.entityName = anyCall.typeName;
282
284
  }
283
285
  if (anyCall.columnName) location.column = anyCall.columnName;
286
+ // A rename call carries old/new index names; the new name is the index's
287
+ // contract-side identity, so it is the conflict location.
284
288
  if (anyCall.indexName) location.index = anyCall.indexName;
289
+ else if (anyCall.newIndexName) location.index = anyCall.newIndexName;
285
290
  if (anyCall.constraintName) location.constraint = anyCall.constraintName;
286
291
  return Object.keys(location).length > 0 ? (location as SqlPlannerConflictLocation) : undefined;
287
292
  }
@@ -476,11 +481,7 @@ function buildCreateTableCallsFromNode(
476
481
  ),
477
482
  ];
478
483
  for (const index of table.indexes) {
479
- const indexName = index.name ?? defaultIndexName(table.name, index.columns);
480
- const extras: { type?: string; options?: Record<string, unknown> } = {};
481
- if (index.type !== undefined) extras.type = index.type;
482
- if (index.options !== undefined) extras.options = index.options;
483
- calls.push(new CreateIndexCall(schemaName, table.name, indexName, [...index.columns], extras));
484
+ calls.push(createIndexCallFromNode(index, schemaName, table.name));
484
485
  }
485
486
  for (const fk of table.foreignKeys) {
486
487
  calls.push(new AddForeignKeyCall(schemaName, table.name, fkSpecFromNode(fk, table.name)));
@@ -808,6 +809,37 @@ function mapUniqueNodeIssue(
808
809
  return notOk(nodeConflict('indexIncompatible', issue.path.join('/')));
809
810
  }
810
811
 
812
+ /**
813
+ * The CreateIndexCall for a name-identified index node: the node's own name
814
+ * verbatim, its element list (column tuple or opaque expression), and the
815
+ * unique/type/options/where attributes carried through.
816
+ */
817
+ export function createIndexCallFromNode(
818
+ index: SqlIndexIR,
819
+ schemaName: string,
820
+ tableName: string,
821
+ ): CreateIndexCall {
822
+ const extras: {
823
+ type?: string;
824
+ options?: Record<string, unknown>;
825
+ where?: string;
826
+ unique?: boolean;
827
+ } = {};
828
+ if (index.type !== undefined) extras.type = index.type;
829
+ if (index.options !== undefined) extras.options = index.options;
830
+ if (index.where !== undefined) extras.where = index.where;
831
+ if (index.unique) extras.unique = true;
832
+ return new CreateIndexCall(
833
+ schemaName,
834
+ tableName,
835
+ index.name,
836
+ index.columns !== undefined
837
+ ? { columns: [...index.columns] }
838
+ : { expression: index.expression ?? '' },
839
+ extras,
840
+ );
841
+ }
842
+
811
843
  function mapIndexNodeIssue(
812
844
  issue: SchemaDiffIssue,
813
845
  schemaName: string,
@@ -818,19 +850,14 @@ function mapIndexNodeIssue(
818
850
  SqlIndexIR,
819
851
  'a not-found index issue always carries the expected index node'
820
852
  >(issue.expected);
821
- const indexName = index.name ?? defaultIndexName(tableName, index.columns);
822
- const extras: { type?: string; options?: Record<string, unknown> } = {};
823
- if (index.type !== undefined) extras.type = index.type;
824
- if (index.options !== undefined) extras.options = index.options;
825
- return ok([new CreateIndexCall(schemaName, tableName, indexName, [...index.columns], extras)]);
853
+ return ok([createIndexCallFromNode(index, schemaName, tableName)]);
826
854
  }
827
855
  if (issueOutcome(issue) === 'not-expected') {
828
856
  const index = blindCast<
829
857
  SqlIndexIR,
830
858
  'a not-expected index issue always carries the actual index node'
831
859
  >(issue.actual);
832
- const indexName = index.name ?? defaultIndexName(tableName, index.columns);
833
- return ok([new DropIndexCall(schemaName, tableName, indexName)]);
860
+ return ok([new DropIndexCall(schemaName, tableName, index.name)]);
834
861
  }
835
862
  return notOk(nodeConflict('indexIncompatible', issue.path.join('/')));
836
863
  }