@prisma-next/target-postgres 0.16.0-dev.22 → 0.16.0-dev.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{authoring-34L18kA1.mjs → authoring-BCzcFRay.mjs} +27 -9
- package/dist/authoring-BCzcFRay.mjs.map +1 -0
- package/dist/codecs-CGE5dh94.d.mts.map +1 -1
- package/dist/{codecs-DOgD_C1A.mjs → codecs-Deand7sy.mjs} +75 -19
- package/dist/codecs-Deand7sy.mjs.map +1 -0
- package/dist/codecs.mjs +1 -1
- package/dist/contract-free.mjs +2 -2
- package/dist/{control-policy-BrcqeM82.mjs → control-policy-B-_34g05.mjs} +8 -7
- package/dist/control-policy-B-_34g05.mjs.map +1 -0
- package/dist/control.mjs +28 -17
- package/dist/control.mjs.map +1 -1
- package/dist/{ddl-DHXKzQ99.mjs → ddl-DYEob9xQ.mjs} +2 -2
- package/dist/{ddl-DHXKzQ99.mjs.map → ddl-DYEob9xQ.mjs.map} +1 -1
- package/dist/{descriptor-meta-CiYV9DzH.mjs → descriptor-meta-D_0ZKcj-.mjs} +4 -4
- package/dist/{descriptor-meta-CiYV9DzH.mjs.map → descriptor-meta-D_0ZKcj-.mjs.map} +1 -1
- package/dist/{diff-database-schema-CK0FM2Z-.mjs → diff-database-schema-C9H09OD3.mjs} +17 -6
- package/dist/diff-database-schema-C9H09OD3.mjs.map +1 -0
- package/dist/diff-database-schema.mjs +1 -1
- package/dist/{errors-ByDS0xJL.mjs → errors-DBSNx7EJ.mjs} +6 -2
- package/dist/errors-DBSNx7EJ.mjs.map +1 -0
- package/dist/errors.d.mts +1 -0
- package/dist/errors.d.mts.map +1 -1
- package/dist/errors.mjs +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/migration.mjs +2 -2
- package/dist/{op-factory-call-PBYfcv8h.mjs → op-factory-call-BVYLIojV.mjs} +49 -38
- package/dist/op-factory-call-BVYLIojV.mjs.map +1 -0
- package/dist/op-factory-call-DT7kdjVP.d.mts.map +1 -1
- package/dist/op-factory-call.mjs +1 -1
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-CiXCo6BT.mjs → planner-4sj5cJCY.mjs} +7 -7
- package/dist/{planner-CiXCo6BT.mjs.map → planner-4sj5cJCY.mjs.map} +1 -1
- package/dist/{planner-ddl-builders-AIpFmG1i.mjs → planner-ddl-builders-CVo3L7Nd.mjs} +11 -7
- package/dist/planner-ddl-builders-CVo3L7Nd.mjs.map +1 -0
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +1 -1
- package/dist/{planner-produced-postgres-migration-PIgjJYrY.mjs → planner-produced-postgres-migration-CcQPDWmX.mjs} +3 -3
- package/dist/{planner-produced-postgres-migration-PIgjJYrY.mjs.map → planner-produced-postgres-migration-CcQPDWmX.mjs.map} +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/{planner-sql-checks-BuB877wH.mjs → planner-sql-checks-CmiQ6Nei.mjs} +3 -3
- package/dist/{planner-sql-checks-BuB877wH.mjs.map → planner-sql-checks-CmiQ6Nei.mjs.map} +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +2 -2
- package/dist/{postgres-contract-view-DebHMqGR.mjs → postgres-contract-view-B8yXVWYe.mjs} +3 -3
- package/dist/{postgres-contract-view-DebHMqGR.mjs.map → postgres-contract-view-B8yXVWYe.mjs.map} +1 -1
- package/dist/postgres-migration-DLIvzHjX.d.mts.map +1 -1
- package/dist/{postgres-migration-BkIplcRz.mjs → postgres-migration-GIOABsAa.mjs} +5 -5
- package/dist/{postgres-migration-BkIplcRz.mjs.map → postgres-migration-GIOABsAa.mjs.map} +1 -1
- package/dist/{postgres-schema-CewLYHB7.mjs → postgres-schema-CTplQZDK.mjs} +2 -2
- package/dist/{postgres-schema-CewLYHB7.mjs.map → postgres-schema-CTplQZDK.mjs.map} +1 -1
- package/dist/{render-ops-BREh1kHe.mjs → render-ops-TcY9XEkz.mjs} +7 -2
- package/dist/render-ops-TcY9XEkz.mjs.map +1 -0
- package/dist/render-ops.d.mts.map +1 -1
- package/dist/render-ops.mjs +1 -1
- package/dist/{resolve-ddl-schema-D0wi_P9Q.mjs → resolve-ddl-schema-BAsJEKW8.mjs} +2 -2
- package/dist/{resolve-ddl-schema-D0wi_P9Q.mjs.map → resolve-ddl-schema-BAsJEKW8.mjs.map} +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/schema-ir-annotations.mjs +1 -1
- package/dist/{sql-utils-SU4FDvIV.mjs → sql-utils-B2lzkKfP.mjs} +22 -19
- package/dist/sql-utils-B2lzkKfP.mjs.map +1 -0
- package/dist/sql-utils.d.mts +4 -9
- package/dist/sql-utils.d.mts.map +1 -1
- package/dist/sql-utils.mjs +2 -2
- package/dist/types.mjs +1 -1
- package/package.json +20 -20
- package/src/core/authoring.ts +28 -7
- package/src/core/codec-helpers.ts +69 -16
- package/src/core/codecs.ts +11 -2
- package/src/core/errors.ts +30 -0
- package/src/core/migrations/column-ddl-rendering.ts +2 -1
- package/src/core/migrations/contract-to-postgres-database-schema-node.ts +14 -2
- package/src/core/migrations/op-factory-call.ts +88 -29
- package/src/core/migrations/operations/constraints.ts +3 -2
- package/src/core/migrations/operations/indexes.ts +4 -1
- package/src/core/migrations/operations/rls.ts +4 -1
- package/src/core/migrations/planner-ddl-builders.ts +13 -4
- package/src/core/migrations/render-ops.ts +4 -1
- package/src/core/migrations/runner.ts +7 -3
- package/src/core/psl-infer/infer-psl-contract.ts +22 -6
- package/src/core/sql-utils.ts +16 -27
- package/src/exports/sql-utils.ts +0 -1
- package/dist/authoring-34L18kA1.mjs.map +0 -1
- package/dist/codecs-DOgD_C1A.mjs.map +0 -1
- package/dist/control-policy-BrcqeM82.mjs.map +0 -1
- package/dist/diff-database-schema-CK0FM2Z-.mjs.map +0 -1
- package/dist/errors-ByDS0xJL.mjs.map +0 -1
- package/dist/op-factory-call-PBYfcv8h.mjs.map +0 -1
- package/dist/planner-ddl-builders-AIpFmG1i.mjs.map +0 -1
- package/dist/render-ops-BREh1kHe.mjs.map +0 -1
- package/dist/sql-utils-SU4FDvIV.mjs.map +0 -1
package/src/core/authoring.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/src/core/codecs.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
}
|
package/src/core/errors.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
}
|