@prisma-next/target-postgres 0.14.0-dev.3 → 0.14.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.
Files changed (166) hide show
  1. package/dist/canonicalize-Bn3BM1Cn.mjs +46 -0
  2. package/dist/canonicalize-Bn3BM1Cn.mjs.map +1 -0
  3. package/dist/canonicalize-CAbXtVtB.d.mts +37 -0
  4. package/dist/canonicalize-CAbXtVtB.d.mts.map +1 -0
  5. package/dist/{codec-types-DHCkwPKE.d.mts → codec-types-CueCoGD0.d.mts} +2 -2
  6. package/dist/{codec-types-DHCkwPKE.d.mts.map → codec-types-CueCoGD0.d.mts.map} +1 -1
  7. package/dist/codec-types.d.mts +1 -1
  8. package/dist/{codecs--0A5_4Bq.d.mts → codecs-Ca8upop6.d.mts} +12 -1
  9. package/dist/codecs-Ca8upop6.d.mts.map +1 -0
  10. package/dist/codecs.d.mts +1 -1
  11. package/dist/codecs.mjs +34 -1
  12. package/dist/codecs.mjs.map +1 -1
  13. package/dist/contract-free.d.mts +8 -3
  14. package/dist/contract-free.d.mts.map +1 -1
  15. package/dist/contract-free.mjs +3 -3
  16. package/dist/control.d.mts +1 -1
  17. package/dist/control.d.mts.map +1 -1
  18. package/dist/control.mjs +11 -10
  19. package/dist/control.mjs.map +1 -1
  20. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  21. package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-qKy1U5V9.d.mts} +2 -2
  22. package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-qKy1U5V9.d.mts.map} +1 -1
  23. package/dist/data-transform.d.mts +1 -1
  24. package/dist/{ddl-QDyOSeLc.mjs → ddl-CGf6UABz.mjs} +62 -5
  25. package/dist/ddl-CGf6UABz.mjs.map +1 -0
  26. package/dist/ddl.d.mts +3 -2
  27. package/dist/ddl.mjs +2 -2
  28. package/dist/{default-normalizer-DyyCHQWs.mjs → default-normalizer-CzV8-QSu.mjs} +58 -2
  29. package/dist/default-normalizer-CzV8-QSu.mjs.map +1 -0
  30. package/dist/default-normalizer.d.mts +1 -1
  31. package/dist/default-normalizer.d.mts.map +1 -1
  32. package/dist/default-normalizer.mjs +1 -1
  33. package/dist/descriptor-meta-Dvvpu_jU.mjs +17 -0
  34. package/dist/descriptor-meta-Dvvpu_jU.mjs.map +1 -0
  35. package/dist/descriptor-meta-runtime-D5zHFKUz.mjs +228 -0
  36. package/dist/descriptor-meta-runtime-D5zHFKUz.mjs.map +1 -0
  37. package/dist/{issue-planner-DL6g3CmE.mjs → issue-planner-D5H1VuVP.mjs} +11 -9
  38. package/dist/issue-planner-D5H1VuVP.mjs.map +1 -0
  39. package/dist/issue-planner.d.mts +3 -3
  40. package/dist/issue-planner.d.mts.map +1 -1
  41. package/dist/issue-planner.mjs +1 -1
  42. package/dist/migration.d.mts +3 -3
  43. package/dist/migration.mjs +2 -2
  44. package/dist/nodes-CSkPX3rI.d.mts +120 -0
  45. package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
  46. package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
  47. package/dist/nodes-Dh__NS58.mjs.map +1 -0
  48. package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-DIdBDwmO.mjs} +199 -28
  49. package/dist/op-factory-call-DIdBDwmO.mjs.map +1 -0
  50. package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-DPJR7tJc.d.mts} +44 -13
  51. package/dist/op-factory-call-DPJR7tJc.d.mts.map +1 -0
  52. package/dist/op-factory-call.d.mts +1 -1
  53. package/dist/op-factory-call.mjs +1 -1
  54. package/dist/pack.d.mts +74 -2
  55. package/dist/pack.d.mts.map +1 -1
  56. package/dist/pack.mjs +1 -1
  57. package/dist/{planner-Bs_baQax.mjs → planner-BnNEtGYM.mjs} +156 -24
  58. package/dist/planner-BnNEtGYM.mjs.map +1 -0
  59. package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-B8Nn6nHN.mjs} +12 -27
  60. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -0
  61. package/dist/planner-ddl-builders.d.mts +2 -4
  62. package/dist/planner-ddl-builders.d.mts.map +1 -1
  63. package/dist/planner-ddl-builders.mjs +2 -2
  64. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-BO-frvvN.d.mts} +3 -3
  65. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-BO-frvvN.d.mts.map} +1 -1
  66. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-DzvdQ-7K.mjs} +2 -2
  67. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-DzvdQ-7K.mjs.map} +1 -1
  68. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  69. package/dist/planner-produced-postgres-migration.mjs +1 -1
  70. package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-CyjaoKhm.mjs} +2 -2
  71. package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-CyjaoKhm.mjs.map} +1 -1
  72. package/dist/planner-sql-checks.mjs +1 -1
  73. package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
  74. package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
  75. package/dist/planner-target-details.d.mts +1 -1
  76. package/dist/planner.d.mts +31 -4
  77. package/dist/planner.d.mts.map +1 -1
  78. package/dist/planner.mjs +2 -2
  79. package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-serializer-tGMiJcl4.mjs} +30 -21
  80. package/dist/postgres-contract-serializer-tGMiJcl4.mjs.map +1 -0
  81. package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-C8JZyTSJ.mjs} +2 -2
  82. package/dist/{postgres-migration-B5jKrXv3.mjs.map → postgres-migration-C8JZyTSJ.mjs.map} +1 -1
  83. package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-Cs7zMOe1.d.mts} +5 -5
  84. package/dist/{postgres-migration-Y4YBJqkS.d.mts.map → postgres-migration-Cs7zMOe1.d.mts.map} +1 -1
  85. package/dist/postgres-rls-policy-12wAEQhs.d.mts +60 -0
  86. package/dist/postgres-rls-policy-12wAEQhs.d.mts.map +1 -0
  87. package/dist/postgres-schema-DJUEkWg-.d.mts +190 -0
  88. package/dist/postgres-schema-DJUEkWg-.d.mts.map +1 -0
  89. package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-DLcc3W2-.mjs} +138 -5
  90. package/dist/postgres-schema-DLcc3W2-.mjs.map +1 -0
  91. package/dist/postgres-schema-ir-BJl_a3J7.d.mts +102 -0
  92. package/dist/postgres-schema-ir-BJl_a3J7.d.mts.map +1 -0
  93. package/dist/postgres-schema-ir-I_FiUndw.mjs +133 -0
  94. package/dist/postgres-schema-ir-I_FiUndw.mjs.map +1 -0
  95. package/dist/postgres-schema-ir-annotations-BbDSiwx2.mjs +14 -0
  96. package/dist/postgres-schema-ir-annotations-BbDSiwx2.mjs.map +1 -0
  97. package/dist/render-ops.d.mts +1 -1
  98. package/dist/rls-canonicalize.d.mts +2 -0
  99. package/dist/rls-canonicalize.mjs +2 -0
  100. package/dist/runtime.d.mts +5 -5
  101. package/dist/runtime.d.mts.map +1 -1
  102. package/dist/runtime.mjs +2 -2
  103. package/dist/schema-ir-annotations.d.mts +8 -0
  104. package/dist/schema-ir-annotations.d.mts.map +1 -0
  105. package/dist/schema-ir-annotations.mjs +2 -0
  106. package/dist/types.d.mts +4 -140
  107. package/dist/types.mjs +3 -2
  108. package/package.json +21 -17
  109. package/src/contract-free/checks.ts +73 -0
  110. package/src/contract-free/ddl.ts +45 -1
  111. package/src/core/authoring.ts +115 -1
  112. package/src/core/codecs.ts +35 -1
  113. package/src/core/ddl/nodes.ts +94 -2
  114. package/src/core/default-normalizer.ts +60 -2
  115. package/src/core/descriptor-meta.ts +4 -0
  116. package/src/core/entity-kinds.ts +16 -0
  117. package/src/core/migrations/bound-schema.ts +10 -0
  118. package/src/core/migrations/contract-to-postgres-schema-ir.ts +91 -0
  119. package/src/core/migrations/control-policy.ts +3 -0
  120. package/src/core/migrations/diff-postgres-schema.ts +55 -0
  121. package/src/core/migrations/issue-planner.ts +8 -0
  122. package/src/core/migrations/op-factory-call.ts +106 -21
  123. package/src/core/migrations/operations/columns.ts +24 -12
  124. package/src/core/migrations/operations/data-transform.ts +6 -0
  125. package/src/core/migrations/operations/rls.ts +106 -0
  126. package/src/core/migrations/operations/shared.ts +0 -30
  127. package/src/core/migrations/planner-ddl-builders.ts +15 -56
  128. package/src/core/migrations/planner-recipes.ts +31 -13
  129. package/src/core/migrations/planner-strategies.ts +3 -11
  130. package/src/core/migrations/planner-target-details.ts +3 -1
  131. package/src/core/migrations/planner.ts +84 -1
  132. package/src/core/migrations/verify-postgres-namespaces.ts +12 -15
  133. package/src/core/postgres-contract-serializer.ts +48 -33
  134. package/src/core/postgres-schema.ts +37 -13
  135. package/src/core/postgres-validators.ts +20 -0
  136. package/src/core/rls/canonicalize.ts +48 -0
  137. package/src/core/schema-ir/postgres-rls-policy.ts +101 -0
  138. package/src/core/schema-ir/postgres-role.ts +52 -0
  139. package/src/core/schema-ir/postgres-schema-ir-annotations.ts +22 -0
  140. package/src/core/schema-ir/postgres-schema-ir.ts +138 -0
  141. package/src/core/schema-ir/postgres-table-ir.ts +96 -0
  142. package/src/exports/contract-free.ts +7 -1
  143. package/src/exports/control.ts +11 -12
  144. package/src/exports/ddl.ts +4 -0
  145. package/src/exports/planner-ddl-builders.ts +0 -2
  146. package/src/exports/planner.ts +5 -0
  147. package/src/exports/rls-canonicalize.ts +6 -0
  148. package/src/exports/schema-ir-annotations.ts +1 -0
  149. package/src/exports/types.ts +20 -0
  150. package/dist/codecs--0A5_4Bq.d.mts.map +0 -1
  151. package/dist/ddl-QDyOSeLc.mjs.map +0 -1
  152. package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
  153. package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
  154. package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
  155. package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
  156. package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
  157. package/dist/nodes-pLeLgdis.d.mts +0 -67
  158. package/dist/nodes-pLeLgdis.d.mts.map +0 -1
  159. package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
  160. package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
  161. package/dist/planner-Bs_baQax.mjs.map +0 -1
  162. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
  163. package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
  164. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
  165. package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
  166. package/dist/types.d.mts.map +0 -1
@@ -3,6 +3,7 @@ import {
3
3
  DdlNode,
4
4
  type DdlTableConstraint,
5
5
  } from '@prisma-next/sql-relational-core/ast';
6
+ import type { RlsPolicyOperation } from '../rls/canonicalize';
6
7
 
7
8
  // ---------------------------------------------------------------------------
8
9
  // AlterTableAction — nested polymorphic hierarchy
@@ -10,6 +11,7 @@ import {
10
11
 
11
12
  export interface AlterTableActionVisitor<R> {
12
13
  addColumn(action: AddColumnAction): R;
14
+ dropDefault(action: DropDefaultAction): R;
13
15
  }
14
16
 
15
17
  export abstract class AlterTableAction {
@@ -32,7 +34,31 @@ export class AddColumnAction extends AlterTableAction {
32
34
  }
33
35
  }
34
36
 
35
- export type AnyAlterTableAction = AddColumnAction;
37
+ export class DropDefaultAction extends AlterTableAction {
38
+ readonly kind = 'drop-default' as const;
39
+ readonly columnName: string;
40
+
41
+ constructor(columnName: string) {
42
+ super();
43
+ this.columnName = columnName;
44
+ Object.freeze(this);
45
+ }
46
+
47
+ override accept<R>(visitor: AlterTableActionVisitor<R>): R {
48
+ return visitor.dropDefault(this);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * The set of ALTER TABLE subactions currently expressible as typed DDL.
54
+ *
55
+ * The remaining actions — SetDefault, SetNotNull, DropNotNull,
56
+ * AlterColumnType — are still emitted as raw SQL by `operations/columns.ts`
57
+ * and join this union as they are converted to typed DDL. Until then it is
58
+ * intentionally partial: only the ALTER subactions used by the
59
+ * already-converted ops (AddColumn, DropDefault) appear here.
60
+ */
61
+ export type AnyAlterTableAction = AddColumnAction | DropDefaultAction;
36
62
 
37
63
  // ---------------------------------------------------------------------------
38
64
  // Top-level DDL visitor
@@ -42,6 +68,8 @@ export interface PostgresDdlVisitor<R> {
42
68
  createTable(node: PostgresCreateTable): R;
43
69
  createSchema(node: PostgresCreateSchema): R;
44
70
  alterTable(node: PostgresAlterTable): R;
71
+ createPolicy(node: PostgresCreatePolicy): R;
72
+ dropPolicy(node: PostgresDropPolicy): R;
45
73
  }
46
74
 
47
75
  export abstract class PostgresDdlNode extends DdlNode {
@@ -127,4 +155,68 @@ export class PostgresAlterTable extends PostgresDdlNode {
127
155
  }
128
156
  }
129
157
 
130
- export type AnyPostgresDdlNode = PostgresCreateTable | PostgresCreateSchema | PostgresAlterTable;
158
+ export type { RlsPolicyOperation };
159
+
160
+ export class PostgresCreatePolicy extends PostgresDdlNode {
161
+ readonly kind = 'create-policy' as const;
162
+ readonly schema: string;
163
+ readonly table: string;
164
+ readonly name: string;
165
+ readonly permissive: boolean;
166
+ readonly operation: RlsPolicyOperation;
167
+ readonly roles: ReadonlyArray<string>;
168
+ readonly using: string | undefined;
169
+ readonly withCheck: string | undefined;
170
+
171
+ constructor(options: {
172
+ readonly schema: string;
173
+ readonly table: string;
174
+ readonly name: string;
175
+ readonly permissive: boolean;
176
+ readonly operation: RlsPolicyOperation;
177
+ readonly roles: readonly string[];
178
+ readonly using?: string;
179
+ readonly withCheck?: string;
180
+ }) {
181
+ super();
182
+ this.schema = options.schema;
183
+ this.table = options.table;
184
+ this.name = options.name;
185
+ this.permissive = options.permissive;
186
+ this.operation = options.operation;
187
+ this.roles = Object.freeze([...options.roles]);
188
+ this.using = options.using;
189
+ this.withCheck = options.withCheck;
190
+ this.freeze();
191
+ }
192
+
193
+ override accept<R>(visitor: PostgresDdlVisitor<R>): R {
194
+ return visitor.createPolicy(this);
195
+ }
196
+ }
197
+
198
+ export class PostgresDropPolicy extends PostgresDdlNode {
199
+ readonly kind = 'drop-policy' as const;
200
+ readonly schema: string;
201
+ readonly table: string;
202
+ readonly name: string;
203
+
204
+ constructor(options: { readonly schema: string; readonly table: string; readonly name: string }) {
205
+ super();
206
+ this.schema = options.schema;
207
+ this.table = options.table;
208
+ this.name = options.name;
209
+ this.freeze();
210
+ }
211
+
212
+ override accept<R>(visitor: PostgresDdlVisitor<R>): R {
213
+ return visitor.dropPolicy(this);
214
+ }
215
+ }
216
+
217
+ export type AnyPostgresDdlNode =
218
+ | PostgresCreateTable
219
+ | PostgresCreateSchema
220
+ | PostgresAlterTable
221
+ | PostgresCreatePolicy
222
+ | PostgresDropPolicy;
@@ -1,4 +1,4 @@
1
- import type { ColumnDefault } from '@prisma-next/contract/types';
1
+ import type { ColumnDefault, JsonValue } from '@prisma-next/contract/types';
2
2
 
3
3
  /**
4
4
  * Pre-compiled regex patterns for performance.
@@ -18,6 +18,13 @@ const FALSE_PATTERN = /^false$/i;
18
18
  const NUMERIC_PATTERN = /^-?\d+(\.\d+)?$/;
19
19
  const STRING_LITERAL_PATTERN = /^'((?:[^']|'')*)'(?:::(?:"[^"]+"|[\w\s]+)(?:\(\d+\))?)?$/;
20
20
 
21
+ /**
22
+ * Matches a Postgres array literal default of the form `'{...}'::elemtype[]`.
23
+ * The literal body is captured in group 1; the cast (including `[]`) is optional.
24
+ * Examples: `'{}'::text[]`, `'{1,2}'::integer[]`, `'{}'`
25
+ */
26
+ const ARRAY_LITERAL_PATTERN = /^'(\{.*\})'(?:::.+\[\])?$/;
27
+
21
28
  /**
22
29
  * Returns the canonical expression for a timestamp default function, or undefined
23
30
  * if the expression is not a recognized timestamp default.
@@ -51,6 +58,46 @@ function canonicalizeTimestampDefault(expr: string): string | undefined {
51
58
  return undefined;
52
59
  }
53
60
 
61
+ /**
62
+ * Parses a Postgres array literal body (`{...}`) into a JS array of primitives.
63
+ * Returns undefined if the body cannot be reliably parsed.
64
+ *
65
+ * Handles:
66
+ * - `{}` → `[]`
67
+ * - `{elem1,elem2,...}` → `[elem1, elem2, ...]` with numeric and string element coercion
68
+ */
69
+ function parseArrayLiteralBody(body: string): readonly JsonValue[] | undefined {
70
+ const inner = body.slice(1, -1).trim();
71
+ if (inner === '') return [];
72
+ const elements = inner.split(',');
73
+ const result: JsonValue[] = [];
74
+ for (const rawEl of elements) {
75
+ const el = rawEl.trim();
76
+ if (el.toUpperCase() === 'NULL') {
77
+ result.push(null);
78
+ continue;
79
+ }
80
+ if (el === 'true') {
81
+ result.push(true);
82
+ continue;
83
+ }
84
+ if (el === 'false') {
85
+ result.push(false);
86
+ continue;
87
+ }
88
+ if (NUMERIC_PATTERN.test(el)) {
89
+ result.push(Number(el));
90
+ continue;
91
+ }
92
+ if (el.startsWith('"') && el.endsWith('"')) {
93
+ result.push(el.slice(1, -1).replace(/""/g, '"'));
94
+ continue;
95
+ }
96
+ return undefined;
97
+ }
98
+ return result;
99
+ }
100
+
54
101
  /**
55
102
  * Parses a raw Postgres column default expression into a normalized ColumnDefault.
56
103
  * This enables semantic comparison between contract defaults and introspected schema defaults.
@@ -59,7 +106,7 @@ function canonicalizeTimestampDefault(expr: string): string | undefined {
59
106
  * keeping the introspection layer focused on faithful data capture.
60
107
  *
61
108
  * @param rawDefault - Raw default expression from information_schema.columns.column_default
62
- * @param nativeType - Native column type, used for type-aware parsing (bigint tagging, JSON detection)
109
+ * @param nativeType - Native column type, used for type-aware parsing (array, bigint, JSON)
63
110
  * @returns Normalized ColumnDefault or undefined if the expression cannot be parsed
64
111
  */
65
112
  export function parsePostgresDefault(
@@ -69,11 +116,22 @@ export function parsePostgresDefault(
69
116
  const trimmed = rawDefault.trim();
70
117
  const normalizedType = nativeType?.toLowerCase();
71
118
  const isBigInt = normalizedType === 'bigint' || normalizedType === 'int8';
119
+ const isArrayType = normalizedType?.endsWith('[]') ?? false;
72
120
 
73
121
  if (NEXTVAL_PATTERN.test(trimmed)) {
74
122
  return { kind: 'function', expression: 'autoincrement()' };
75
123
  }
76
124
 
125
+ if (isArrayType) {
126
+ const arrayMatch = trimmed.match(ARRAY_LITERAL_PATTERN);
127
+ if (arrayMatch?.[1] !== undefined) {
128
+ const parsed = parseArrayLiteralBody(arrayMatch[1]);
129
+ if (parsed !== undefined) {
130
+ return { kind: 'literal', value: parsed };
131
+ }
132
+ }
133
+ }
134
+
77
135
  const canonicalTimestamp = canonicalizeTimestampDefault(trimmed);
78
136
  if (canonicalTimestamp) {
79
137
  return { kind: 'function', expression: canonicalTimestamp };
@@ -2,9 +2,11 @@ import type { CodecTypes } from '../exports/codec-types';
2
2
  import {
3
3
  postgresAuthoringEntityTypes,
4
4
  postgresAuthoringFieldPresets,
5
+ postgresAuthoringPslBlockDescriptors,
5
6
  postgresAuthoringTypes,
6
7
  } from './authoring';
7
8
  import { postgresTargetDescriptorMetaRuntime } from './descriptor-meta-runtime';
9
+ import { postgresCreateNamespace } from './postgres-schema';
8
10
 
9
11
  const postgresTargetDescriptorMetaBase = {
10
12
  ...postgresTargetDescriptorMetaRuntime,
@@ -13,6 +15,8 @@ const postgresTargetDescriptorMetaBase = {
13
15
  type: postgresAuthoringTypes,
14
16
  field: postgresAuthoringFieldPresets,
15
17
  entityTypes: postgresAuthoringEntityTypes,
18
+ pslBlockDescriptors: postgresAuthoringPslBlockDescriptors,
19
+ createNamespace: postgresCreateNamespace,
16
20
  },
17
21
  } as const;
18
22
 
@@ -0,0 +1,16 @@
1
+ import type { EntityKindDescriptor } from '@prisma-next/framework-components/ir';
2
+ import { PostgresRlsPolicySchema, PostgresRoleSchema } from './postgres-validators';
3
+ import { PostgresRlsPolicy, type PostgresRlsPolicyInput } from './schema-ir/postgres-rls-policy';
4
+ import { PostgresRole, type PostgresRoleInput } from './schema-ir/postgres-role';
5
+
6
+ export const policyEntityKind: EntityKindDescriptor<PostgresRlsPolicyInput, PostgresRlsPolicy> = {
7
+ kind: 'policy',
8
+ schema: PostgresRlsPolicySchema,
9
+ construct: (input) => new PostgresRlsPolicy(input),
10
+ };
11
+
12
+ export const roleEntityKind: EntityKindDescriptor<PostgresRoleInput, PostgresRole> = {
13
+ kind: 'role',
14
+ schema: PostgresRoleSchema,
15
+ construct: (input) => new PostgresRole(input),
16
+ };
@@ -0,0 +1,10 @@
1
+ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
2
+
3
+ /**
4
+ * Maps a planner schema name to the contract-free DDL builder's optional
5
+ * `schema` field: the unbound sentinel becomes `undefined` (the builder omits
6
+ * the schema qualifier), every other name passes through.
7
+ */
8
+ export function boundSchema(schemaName: string): string | undefined {
9
+ return schemaName === UNBOUND_NAMESPACE_ID ? undefined : schemaName;
10
+ }
@@ -0,0 +1,91 @@
1
+ import type { ContractToSchemaIROptions } from '@prisma-next/family-sql/control';
2
+ import { contractToSchemaIR } from '@prisma-next/family-sql/control';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
4
+ import type { PostgresContract } from '../postgres-schema';
5
+ import { isPostgresSchema } from '../postgres-schema';
6
+ import { PostgresRlsPolicy } from '../schema-ir/postgres-rls-policy';
7
+ import { PostgresSchemaIR } from '../schema-ir/postgres-schema-ir';
8
+ import { resolveDdlSchemaForNamespaceStorage } from '../schema-ir/postgres-schema-ir-annotations';
9
+ import { PostgresTableIR } from '../schema-ir/postgres-table-ir';
10
+
11
+ export function contractToPostgresSchemaIR(
12
+ contract: PostgresContract | null,
13
+ options: ContractToSchemaIROptions,
14
+ ): PostgresSchemaIR {
15
+ const sqlIr = contractToSchemaIR(contract, options);
16
+ const ownedSchemas =
17
+ contract === null
18
+ ? []
19
+ : Object.values(contract.storage.namespaces)
20
+ .filter((ns) => isPostgresSchema(ns))
21
+ .map((ns) => resolveDdlSchemaForNamespaceStorage(contract.storage, ns.id));
22
+
23
+ // Build a map of tableName → PostgresRlsPolicy[], resolving the DDL schema
24
+ // name once per namespace (not per policy).
25
+ const policiesByTable = new Map<string, PostgresRlsPolicy[]>();
26
+ if (contract !== null) {
27
+ for (const ns of Object.values(contract.storage.namespaces)) {
28
+ if (!isPostgresSchema(ns)) continue;
29
+ const resolvedSchema = resolveDdlSchemaForNamespaceStorage(contract.storage, ns.id);
30
+ for (const policy of Object.values(ns.policy)) {
31
+ const resolved =
32
+ resolvedSchema === policy.namespaceId
33
+ ? policy
34
+ : new PostgresRlsPolicy({
35
+ name: policy.name,
36
+ prefix: policy.prefix,
37
+ tableName: policy.tableName,
38
+ namespaceId: resolvedSchema,
39
+ operation: policy.operation,
40
+ roles: [...policy.roles],
41
+ ...ifDefined('using', policy.using),
42
+ ...ifDefined('withCheck', policy.withCheck),
43
+ permissive: policy.permissive,
44
+ });
45
+ const list = policiesByTable.get(policy.tableName) ?? [];
46
+ list.push(resolved);
47
+ policiesByTable.set(policy.tableName, list);
48
+ }
49
+ }
50
+ }
51
+
52
+ // Attach policies to each table from the relational projection. A policy that
53
+ // references a table absent from the schema IR is a malformed contract — the
54
+ // loop below throws rather than fabricating a stub table.
55
+ const tables: Record<string, PostgresTableIR> = {};
56
+ for (const [tableName, sqlTable] of Object.entries(sqlIr.tables)) {
57
+ tables[tableName] = new PostgresTableIR({
58
+ name: sqlTable.name,
59
+ columns: sqlTable.columns,
60
+ foreignKeys: sqlTable.foreignKeys,
61
+ uniques: sqlTable.uniques,
62
+ indexes: sqlTable.indexes,
63
+ ...(sqlTable.primaryKey !== undefined ? { primaryKey: sqlTable.primaryKey } : {}),
64
+ ...(sqlTable.annotations !== undefined ? { annotations: sqlTable.annotations } : {}),
65
+ ...(sqlTable.checks !== undefined ? { checks: sqlTable.checks } : {}),
66
+ rlsPolicies: policiesByTable.get(tableName) ?? [],
67
+ });
68
+ }
69
+ for (const [tableName, policies] of policiesByTable) {
70
+ if (!(tableName in tables)) {
71
+ const policyName = policies[0]?.name ?? '(unknown)';
72
+ throw new Error(
73
+ `contract-to-postgres-schema-ir: policy "${policyName}" references table "${tableName}" not present in the schema`,
74
+ );
75
+ }
76
+ }
77
+
78
+ return new PostgresSchemaIR({
79
+ tables,
80
+ roles:
81
+ contract === null
82
+ ? []
83
+ : Object.values(contract.storage.namespaces).flatMap((ns) =>
84
+ isPostgresSchema(ns) ? Object.values(ns.role) : [],
85
+ ),
86
+ pgSchemaName: 'public',
87
+ pgVersion: '',
88
+ existingSchemas: ownedSchemas,
89
+ nativeEnumTypeNames: [],
90
+ });
91
+ }
@@ -21,6 +21,8 @@ import type { PostgresOpFactoryCall } from './op-factory-call';
21
21
  const OBJECT_CREATION_FACTORIES: ReadonlySet<string> = new Set<string>([
22
22
  'createTable',
23
23
  'createSchema',
24
+ 'createRlsPolicy',
25
+ 'enableRowLevelSecurity',
24
26
  ]);
25
27
 
26
28
  function createsNewTopLevelObject(call: PostgresOpFactoryCall): boolean {
@@ -151,6 +153,7 @@ export function resolvePostgresCallControlPolicySubject(
151
153
  const POSTGRES_ISSUE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.freeze({
152
154
  missing_schema: 'createSchema',
153
155
  missing_table: 'createTable',
156
+ missing_rls_policy: 'createRlsPolicy',
154
157
  });
155
158
 
156
159
  export function resolvePostgresIssueCreationFactoryName(issue: SchemaIssue): string | undefined {
@@ -0,0 +1,55 @@
1
+ import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
2
+ import { diffSchemas } from '@prisma-next/framework-components/control';
3
+ import { isPostgresRlsPolicy, type PostgresRlsPolicy } from '../schema-ir/postgres-rls-policy';
4
+ import { ensurePostgresSchemaIR, type PostgresSchemaIR } from '../schema-ir/postgres-schema-ir';
5
+
6
+ // Renders a display-only reference string for the diff message. If policy
7
+ // rendering grows, route it through the adapter's SQL renderer so the message
8
+ // can't diverge from the emitted policy SQL.
9
+ function renderPostgresPolicyReference(policy: PostgresRlsPolicy): string {
10
+ return `policy "${policy.name}" on "${policy.namespaceId}"."${policy.tableName}"`;
11
+ }
12
+
13
+ /**
14
+ * Computes schema drift between two derived schema IRs.
15
+ *
16
+ * 1. Runs the framework total diff.
17
+ * 2. Filters to policy-subject issues only — this is transitional: the generic
18
+ * differ walks the whole tree, but the legacy relational verifier still owns
19
+ * table/column drift, so non-policy issues are dropped here.
20
+ * 3. Remaps the message to a human-readable policy reference.
21
+ *
22
+ * Ownership filtering (dropping `extra` issues in namespaces a contract doesn't
23
+ * own) is the caller's responsibility — use `filterIssuesByOwnership`.
24
+ */
25
+ export function diffPostgresSchema(
26
+ expected: PostgresSchemaIR,
27
+ actual: PostgresSchemaIR,
28
+ ): readonly SchemaDiffIssue[] {
29
+ const safeActual = ensurePostgresSchemaIR(actual);
30
+ const issues = diffSchemas(expected, safeActual);
31
+
32
+ return issues
33
+ .filter((i) => isPostgresRlsPolicy(i.expected ?? i.actual))
34
+ .map((i) => {
35
+ const policy = i.expected ?? i.actual;
36
+ if (!isPostgresRlsPolicy(policy)) return i;
37
+ return { ...i, message: `${i.outcome}: ${renderPostgresPolicyReference(policy)}` };
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Filters `extra` policy issues to those in owned namespaces. Call after
43
+ * `diffPostgresSchema` with the union of namespace ids from the expected IR's
44
+ * policies and its `existingSchemas`.
45
+ */
46
+ export function filterIssuesByOwnership(
47
+ issues: readonly SchemaDiffIssue[],
48
+ ownedSchemaNames: ReadonlySet<string>,
49
+ ): readonly SchemaDiffIssue[] {
50
+ return issues.filter(
51
+ (i) =>
52
+ i.outcome !== 'extra' ||
53
+ (isPostgresRlsPolicy(i.actual) && ownedSchemaNames.has(i.actual.namespaceId)),
54
+ );
55
+ }
@@ -695,6 +695,8 @@ type CallCategory =
695
695
  | 'dep'
696
696
  | 'drop'
697
697
  | 'table'
698
+ | 'rlsEnable'
699
+ | 'rlsPolicy'
698
700
  | 'column'
699
701
  | 'alter'
700
702
  | 'primaryKey'
@@ -724,6 +726,12 @@ function classifyCall(call: PostgresOpFactoryCall): CallCategory {
724
726
  return 'unique'; // after uniques, before indexes
725
727
  case 'createTable':
726
728
  return 'table';
729
+ case 'enableRowLevelSecurity':
730
+ return 'rlsEnable';
731
+ case 'createRlsPolicy':
732
+ return 'rlsPolicy';
733
+ case 'dropRlsPolicy':
734
+ return 'drop';
727
735
  case 'addColumn':
728
736
  return 'column';
729
737
  case 'alterColumnType':
@@ -40,8 +40,10 @@ import { blindCast } from '@prisma-next/utils/casts';
40
40
  import { ifDefined } from '@prisma-next/utils/defined';
41
41
  import { columnExistsAst, tableExistsAst } from '../../contract-free/checks';
42
42
  import * as contractFreeDdl from '../../contract-free/ddl';
43
+ import type { PostgresRlsPolicy } from '../schema-ir/postgres-rls-policy';
43
44
  import { escapeLiteral, quoteIdentifier } from '../sql-utils';
44
45
  import type { PostgresColumnDefault } from '../types';
46
+ import { boundSchema } from './bound-schema';
45
47
  import {
46
48
  addNotNullColumnDirect,
47
49
  alterColumnType,
@@ -61,6 +63,7 @@ import {
61
63
  } from './operations/constraints';
62
64
  import { createExtension } from './operations/dependencies';
63
65
  import { createIndex, dropIndex } from './operations/indexes';
66
+ import { createRlsPolicy, dropRlsPolicy, enableRowLevelSecurity } from './operations/rls';
64
67
  import type { ForeignKeySpec } from './operations/shared';
65
68
  import { step, targetDetails } from './operations/shared';
66
69
  import { dropTable } from './operations/tables';
@@ -83,10 +86,6 @@ type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
83
86
  // transitive package on disk.
84
87
  const POSTGRES_MIGRATION_FACADE = '@prisma-next/postgres/migration';
85
88
 
86
- function boundSchema(schemaName: string): string | undefined {
87
- return schemaName === UNBOUND_NAMESPACE_ID ? undefined : schemaName;
88
- }
89
-
90
89
  abstract class PostgresOpFactoryCallNode extends TsExpression implements FrameworkOpFactoryCall {
91
90
  abstract readonly factoryName: string;
92
91
  abstract readonly operationClass: MigrationOperationClass;
@@ -684,10 +683,8 @@ export class DropDefaultCall extends PostgresOpFactoryCallNode {
684
683
 
685
684
  /**
686
685
  * Planner-internal call for adding a NOT NULL column (no contract default) to
687
- * a table that must be empty at migration time. Carries the raw ADD COLUMN SQL
688
- * (produced by `buildAddColumnSql` at plan time deferred from slice-7 typed
689
- * DDL) plus the parameters needed to lower the typed pre/postchecks at render
690
- * time.
686
+ * a table that must be empty at migration time. Carries the typed `DdlColumn`
687
+ * and lowers it to an ADD COLUMN execute step via the adapter at `toOp` time.
691
688
  *
692
689
  * No authored `PostgresMigration` method: this call is only emitted by the
693
690
  * planner, never hand-written by migration authors.
@@ -698,15 +695,15 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
698
695
  readonly schemaName: string;
699
696
  readonly tableName: string;
700
697
  readonly columnName: string;
701
- readonly executeStepSql: string;
698
+ readonly column: DdlColumn;
702
699
  readonly label: string;
703
700
 
704
- constructor(schemaName: string, tableName: string, columnName: string, executeStepSql: string) {
701
+ constructor(schemaName: string, tableName: string, columnName: string, column: DdlColumn) {
705
702
  super();
706
703
  this.schemaName = schemaName;
707
704
  this.tableName = tableName;
708
705
  this.columnName = columnName;
709
- this.executeStepSql = executeStepSql;
706
+ this.column = column;
710
707
  this.label = `Add column ${columnName} to ${tableName}`;
711
708
  this.freeze();
712
709
  }
@@ -717,13 +714,7 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
717
714
  `AddNotNullColumnDirectCall.toOp: a lowerer is required on the Postgres planner path (column "${this.columnName}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
718
715
  );
719
716
  }
720
- return addNotNullColumnDirect(
721
- this.schemaName,
722
- this.tableName,
723
- this.columnName,
724
- this.executeStepSql,
725
- lowerer,
726
- );
717
+ return addNotNullColumnDirect(this.schemaName, this.tableName, this.column, lowerer);
727
718
  }
728
719
 
729
720
  renderTypeScript(): string {
@@ -738,9 +729,9 @@ export class AddNotNullColumnDirectCall extends PostgresOpFactoryCallNode {
738
729
  /**
739
730
  * Planner-internal call for adding a NOT NULL column (no contract default)
740
731
  * using a temporary default value for non-empty tables. Carries all parameters
741
- * needed for `buildAddNotNullColumnWithTemporaryDefaultOperation`; the typed
742
- * pre/postchecks are lowered at render time via `toOp(lowerer)`. The execute
743
- * SQL (`buildAddColumnSql`) is still raw per slice-7 deferral.
732
+ * needed for `buildAddNotNullColumnWithTemporaryDefaultOperation`; both the
733
+ * typed ADD COLUMN execute step and the pre/postchecks are lowered via the
734
+ * adapter at `toOp` time.
744
735
  *
745
736
  * No authored `PostgresMigration` method: this call is only emitted by the
746
737
  * planner, never hand-written by migration authors.
@@ -1376,6 +1367,97 @@ export class DataTransformCall extends PostgresOpFactoryCallNode {
1376
1367
  }
1377
1368
  }
1378
1369
 
1370
+ export class CreatePostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
1371
+ readonly factoryName = 'createRlsPolicy' as const;
1372
+ readonly operationClass = 'additive' as const;
1373
+ readonly schemaName: string;
1374
+ readonly tableName: string;
1375
+ readonly policy: PostgresRlsPolicy;
1376
+ readonly label: string;
1377
+
1378
+ constructor(schemaName: string, tableName: string, policy: PostgresRlsPolicy) {
1379
+ super();
1380
+ this.schemaName = schemaName;
1381
+ this.tableName = tableName;
1382
+ this.policy = policy;
1383
+ this.label = `Create RLS policy "${policy.name}" on "${tableName}"`;
1384
+ this.freeze();
1385
+ }
1386
+
1387
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1388
+ if (lowerer === undefined) {
1389
+ throw new Error(
1390
+ `CreatePostgresRlsPolicyCall.toOp: a lowerer is required on the Postgres planner path (policy "${this.policy.name}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1391
+ );
1392
+ }
1393
+ return createRlsPolicy(this.schemaName, this.tableName, this.policy, lowerer);
1394
+ }
1395
+
1396
+ renderTypeScript(): string {
1397
+ return `createRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.policy)})`;
1398
+ }
1399
+ }
1400
+
1401
+ export class DropPostgresRlsPolicyCall extends PostgresOpFactoryCallNode {
1402
+ readonly factoryName = 'dropRlsPolicy' as const;
1403
+ readonly operationClass = 'destructive' as const;
1404
+ readonly schemaName: string;
1405
+ readonly tableName: string;
1406
+ readonly policyName: string;
1407
+ readonly label: string;
1408
+
1409
+ constructor(schemaName: string, tableName: string, policyName: string) {
1410
+ super();
1411
+ this.schemaName = schemaName;
1412
+ this.tableName = tableName;
1413
+ this.policyName = policyName;
1414
+ this.label = `Drop RLS policy "${policyName}" on "${tableName}"`;
1415
+ this.freeze();
1416
+ }
1417
+
1418
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1419
+ if (lowerer === undefined) {
1420
+ throw new Error(
1421
+ `DropPostgresRlsPolicyCall.toOp: a lowerer is required on the Postgres planner path (policy "${this.policyName}" on table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1422
+ );
1423
+ }
1424
+ return dropRlsPolicy(this.schemaName, this.tableName, this.policyName, lowerer);
1425
+ }
1426
+
1427
+ renderTypeScript(): string {
1428
+ return `dropRlsPolicy(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.policyName)})`;
1429
+ }
1430
+ }
1431
+
1432
+ export class EnableRowLevelSecurityCall extends PostgresOpFactoryCallNode {
1433
+ readonly factoryName = 'enableRowLevelSecurity' as const;
1434
+ readonly operationClass = 'additive' as const;
1435
+ readonly schemaName: string;
1436
+ readonly tableName: string;
1437
+ readonly label: string;
1438
+
1439
+ constructor(schemaName: string, tableName: string) {
1440
+ super();
1441
+ this.schemaName = schemaName;
1442
+ this.tableName = tableName;
1443
+ this.label = `Enable row-level security on "${tableName}"`;
1444
+ this.freeze();
1445
+ }
1446
+
1447
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
1448
+ if (lowerer === undefined) {
1449
+ throw new Error(
1450
+ `EnableRowLevelSecurityCall.toOp: a lowerer is required on the Postgres planner path (table "${this.tableName}"). Pass the control adapter to createPostgresMigrationPlanner.`,
1451
+ );
1452
+ }
1453
+ return enableRowLevelSecurity(this.schemaName, this.tableName, lowerer);
1454
+ }
1455
+
1456
+ renderTypeScript(): string {
1457
+ return `enableRowLevelSecurity(${jsonToTsSource(this.schemaName)}, ${jsonToTsSource(this.tableName)})`;
1458
+ }
1459
+ }
1460
+
1379
1461
  export type PostgresOpFactoryCall =
1380
1462
  | CreateTableCall
1381
1463
  | DropTableCall
@@ -1399,4 +1481,7 @@ export type PostgresOpFactoryCall =
1399
1481
  | RawSqlCall
1400
1482
  | CreateExtensionCall
1401
1483
  | CreateSchemaCall
1484
+ | CreatePostgresRlsPolicyCall
1485
+ | DropPostgresRlsPolicyCall
1486
+ | EnableRowLevelSecurityCall
1402
1487
  | DataTransformCall;