@prisma-next/target-postgres 0.14.0-dev.2 → 0.14.0-dev.20

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 (153) 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/codecs.mjs.map +1 -1
  6. package/dist/contract-free.d.mts +8 -3
  7. package/dist/contract-free.d.mts.map +1 -1
  8. package/dist/contract-free.mjs +3 -3
  9. package/dist/control.d.mts +1 -1
  10. package/dist/control.mjs +6 -6
  11. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  12. package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-qKy1U5V9.d.mts} +2 -2
  13. package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-qKy1U5V9.d.mts.map} +1 -1
  14. package/dist/data-transform.d.mts +1 -1
  15. package/dist/{ddl-QDyOSeLc.mjs → ddl-D1K9soOg.mjs} +62 -5
  16. package/dist/ddl-D1K9soOg.mjs.map +1 -0
  17. package/dist/ddl.d.mts +3 -2
  18. package/dist/ddl.mjs +2 -2
  19. package/dist/{default-normalizer-DyyCHQWs.mjs → default-normalizer-CzV8-QSu.mjs} +58 -2
  20. package/dist/default-normalizer-CzV8-QSu.mjs.map +1 -0
  21. package/dist/default-normalizer.d.mts +1 -1
  22. package/dist/default-normalizer.d.mts.map +1 -1
  23. package/dist/default-normalizer.mjs +1 -1
  24. package/dist/descriptor-meta-b_SzXwen.mjs +239 -0
  25. package/dist/descriptor-meta-b_SzXwen.mjs.map +1 -0
  26. package/dist/{issue-planner-DL6g3CmE.mjs → issue-planner-CUeQ7D83.mjs} +11 -9
  27. package/dist/issue-planner-CUeQ7D83.mjs.map +1 -0
  28. package/dist/issue-planner.d.mts +3 -3
  29. package/dist/issue-planner.d.mts.map +1 -1
  30. package/dist/issue-planner.mjs +1 -1
  31. package/dist/migration.d.mts +3 -3
  32. package/dist/migration.mjs +2 -2
  33. package/dist/nodes-CSkPX3rI.d.mts +120 -0
  34. package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
  35. package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
  36. package/dist/nodes-Dh__NS58.mjs.map +1 -0
  37. package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-CQ4P40QC.d.mts} +44 -13
  38. package/dist/op-factory-call-CQ4P40QC.d.mts.map +1 -0
  39. package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-CyqPjAwf.mjs} +199 -28
  40. package/dist/op-factory-call-CyqPjAwf.mjs.map +1 -0
  41. package/dist/op-factory-call.d.mts +1 -1
  42. package/dist/op-factory-call.mjs +1 -1
  43. package/dist/pack.d.mts +74 -1
  44. package/dist/pack.d.mts.map +1 -1
  45. package/dist/pack.mjs +1 -1
  46. package/dist/{planner-Bs_baQax.mjs → planner-D5zY__R3.mjs} +115 -23
  47. package/dist/planner-D5zY__R3.mjs.map +1 -0
  48. package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-B8Nn6nHN.mjs} +12 -27
  49. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -0
  50. package/dist/planner-ddl-builders.d.mts +2 -4
  51. package/dist/planner-ddl-builders.d.mts.map +1 -1
  52. package/dist/planner-ddl-builders.mjs +2 -2
  53. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-CNTSCMl7.d.mts} +3 -3
  54. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-CNTSCMl7.d.mts.map} +1 -1
  55. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-D8JHnu5j.mjs} +2 -2
  56. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-D8JHnu5j.mjs.map} +1 -1
  57. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  58. package/dist/planner-produced-postgres-migration.mjs +1 -1
  59. package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-CI6Z21mm.mjs} +2 -2
  60. package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-CI6Z21mm.mjs.map} +1 -1
  61. package/dist/planner-sql-checks.mjs +1 -1
  62. package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
  63. package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
  64. package/dist/planner-target-details.d.mts +1 -1
  65. package/dist/planner.d.mts +35 -3
  66. package/dist/planner.d.mts.map +1 -1
  67. package/dist/planner.mjs +2 -2
  68. package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-serializer-k5eg5ZUZ.mjs} +29 -16
  69. package/dist/postgres-contract-serializer-k5eg5ZUZ.mjs.map +1 -0
  70. package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-BoqzWSkj.mjs} +2 -2
  71. package/dist/{postgres-migration-B5jKrXv3.mjs.map → postgres-migration-BoqzWSkj.mjs.map} +1 -1
  72. package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-DJNJHcdP.d.mts} +4 -4
  73. package/dist/{postgres-migration-Y4YBJqkS.d.mts.map → postgres-migration-DJNJHcdP.d.mts.map} +1 -1
  74. package/dist/postgres-rls-policy-D_z1osEq.d.mts +60 -0
  75. package/dist/postgres-rls-policy-D_z1osEq.d.mts.map +1 -0
  76. package/dist/postgres-role-_CCuyPCQ.d.mts +33 -0
  77. package/dist/postgres-role-_CCuyPCQ.d.mts.map +1 -0
  78. package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-BVDr_61a.mjs} +139 -3
  79. package/dist/postgres-schema-BVDr_61a.mjs.map +1 -0
  80. package/dist/postgres-schema-D0h4lsw2.d.mts +158 -0
  81. package/dist/postgres-schema-D0h4lsw2.d.mts.map +1 -0
  82. package/dist/postgres-schema-ir-BF0X8jit.mjs +66 -0
  83. package/dist/postgres-schema-ir-BF0X8jit.mjs.map +1 -0
  84. package/dist/postgres-schema-ir-BQ3RpZMz.d.mts +60 -0
  85. package/dist/postgres-schema-ir-BQ3RpZMz.d.mts.map +1 -0
  86. package/dist/postgres-schema-ir-annotations-El62Sywa.mjs +14 -0
  87. package/dist/postgres-schema-ir-annotations-El62Sywa.mjs.map +1 -0
  88. package/dist/render-ops.d.mts +1 -1
  89. package/dist/rls-canonicalize.d.mts +2 -0
  90. package/dist/rls-canonicalize.mjs +2 -0
  91. package/dist/runtime.d.mts +3 -2
  92. package/dist/runtime.d.mts.map +1 -1
  93. package/dist/runtime.mjs +2 -2
  94. package/dist/schema-ir-annotations.d.mts +8 -0
  95. package/dist/schema-ir-annotations.d.mts.map +1 -0
  96. package/dist/schema-ir-annotations.mjs +2 -0
  97. package/dist/types.d.mts +5 -140
  98. package/dist/types.mjs +3 -2
  99. package/package.json +21 -17
  100. package/src/contract-free/checks.ts +73 -0
  101. package/src/contract-free/ddl.ts +45 -1
  102. package/src/core/authoring.ts +115 -1
  103. package/src/core/codecs.ts +1 -1
  104. package/src/core/ddl/nodes.ts +94 -2
  105. package/src/core/default-normalizer.ts +60 -2
  106. package/src/core/descriptor-meta.ts +4 -0
  107. package/src/core/entity-kinds.ts +16 -0
  108. package/src/core/migrations/bound-schema.ts +10 -0
  109. package/src/core/migrations/control-policy.ts +3 -0
  110. package/src/core/migrations/issue-planner.ts +8 -0
  111. package/src/core/migrations/op-factory-call.ts +106 -21
  112. package/src/core/migrations/operations/columns.ts +24 -12
  113. package/src/core/migrations/operations/data-transform.ts +6 -0
  114. package/src/core/migrations/operations/rls.ts +106 -0
  115. package/src/core/migrations/operations/shared.ts +0 -30
  116. package/src/core/migrations/planner-ddl-builders.ts +15 -56
  117. package/src/core/migrations/planner-recipes.ts +31 -13
  118. package/src/core/migrations/planner-strategies.ts +3 -11
  119. package/src/core/migrations/planner-target-details.ts +3 -1
  120. package/src/core/migrations/planner.ts +99 -1
  121. package/src/core/migrations/verify-postgres-namespaces.ts +12 -15
  122. package/src/core/migrations/verify-postgres-rls-policies.ts +62 -0
  123. package/src/core/postgres-contract-serializer.ts +43 -19
  124. package/src/core/postgres-rls-policy.ts +103 -0
  125. package/src/core/postgres-role.ts +53 -0
  126. package/src/core/postgres-schema-ir-annotations.ts +22 -0
  127. package/src/core/postgres-schema-ir.ts +101 -0
  128. package/src/core/postgres-schema.ts +24 -4
  129. package/src/core/postgres-validators.ts +20 -0
  130. package/src/core/rls/canonicalize.ts +48 -0
  131. package/src/exports/contract-free.ts +7 -1
  132. package/src/exports/ddl.ts +4 -0
  133. package/src/exports/planner-ddl-builders.ts +0 -2
  134. package/src/exports/planner.ts +1 -0
  135. package/src/exports/rls-canonicalize.ts +6 -0
  136. package/src/exports/schema-ir-annotations.ts +1 -0
  137. package/src/exports/types.ts +12 -0
  138. package/dist/ddl-QDyOSeLc.mjs.map +0 -1
  139. package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
  140. package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
  141. package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
  142. package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
  143. package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
  144. package/dist/nodes-pLeLgdis.d.mts +0 -67
  145. package/dist/nodes-pLeLgdis.d.mts.map +0 -1
  146. package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
  147. package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
  148. package/dist/planner-Bs_baQax.mjs.map +0 -1
  149. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
  150. package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
  151. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
  152. package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
  153. package/dist/types.d.mts.map +0 -1
package/package.json CHANGED
@@ -1,32 +1,34 @@
1
1
  {
2
2
  "name": "@prisma-next/target-postgres",
3
- "version": "0.14.0-dev.2",
3
+ "version": "0.14.0-dev.20",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "Postgres target pack for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/cli": "0.14.0-dev.2",
10
- "@prisma-next/contract": "0.14.0-dev.2",
11
- "@prisma-next/errors": "0.14.0-dev.2",
12
- "@prisma-next/family-sql": "0.14.0-dev.2",
13
- "@prisma-next/framework-components": "0.14.0-dev.2",
14
- "@prisma-next/migration-tools": "0.14.0-dev.2",
15
- "@prisma-next/ts-render": "0.14.0-dev.2",
16
- "@prisma-next/sql-contract": "0.14.0-dev.2",
17
- "@prisma-next/sql-errors": "0.14.0-dev.2",
18
- "@prisma-next/sql-operations": "0.14.0-dev.2",
19
- "@prisma-next/sql-relational-core": "0.14.0-dev.2",
20
- "@prisma-next/sql-schema-ir": "0.14.0-dev.2",
21
- "@prisma-next/utils": "0.14.0-dev.2",
9
+ "@prisma-next/cli": "0.14.0-dev.20",
10
+ "@prisma-next/contract": "0.14.0-dev.20",
11
+ "@prisma-next/errors": "0.14.0-dev.20",
12
+ "@prisma-next/family-sql": "0.14.0-dev.20",
13
+ "@prisma-next/framework-components": "0.14.0-dev.20",
14
+ "@prisma-next/migration-tools": "0.14.0-dev.20",
15
+ "@prisma-next/ts-render": "0.14.0-dev.20",
16
+ "@prisma-next/sql-contract": "0.14.0-dev.20",
17
+ "@prisma-next/sql-errors": "0.14.0-dev.20",
18
+ "@prisma-next/sql-operations": "0.14.0-dev.20",
19
+ "@prisma-next/sql-relational-core": "0.14.0-dev.20",
20
+ "@prisma-next/sql-schema-ir": "0.14.0-dev.20",
21
+ "@prisma-next/utils": "0.14.0-dev.20",
22
22
  "@standard-schema/spec": "^1.1.0",
23
23
  "arktype": "^2.2.0",
24
24
  "pathe": "^2.0.3"
25
25
  },
26
26
  "devDependencies": {
27
- "@prisma-next/test-utils": "0.14.0-dev.2",
28
- "@prisma-next/tsconfig": "0.14.0-dev.2",
29
- "@prisma-next/tsdown": "0.14.0-dev.2",
27
+ "@prisma-next/psl-parser": "0.14.0-dev.20",
28
+ "@prisma-next/sql-contract-psl": "0.14.0-dev.20",
29
+ "@prisma-next/test-utils": "0.14.0-dev.20",
30
+ "@prisma-next/tsconfig": "0.14.0-dev.20",
31
+ "@prisma-next/tsdown": "0.14.0-dev.20",
30
32
  "tsdown": "0.22.1",
31
33
  "typescript": "5.9.3",
32
34
  "vitest": "4.1.8"
@@ -68,7 +70,9 @@
68
70
  "./planner-target-details": "./dist/planner-target-details.mjs",
69
71
  "./render-ops": "./dist/render-ops.mjs",
70
72
  "./render-typescript": "./dist/render-typescript.mjs",
73
+ "./rls-canonicalize": "./dist/rls-canonicalize.mjs",
71
74
  "./runtime": "./dist/runtime.mjs",
75
+ "./schema-ir-annotations": "./dist/schema-ir-annotations.mjs",
72
76
  "./sql-utils": "./dist/sql-utils.mjs",
73
77
  "./types": "./dist/types.mjs",
74
78
  "./package.json": "./package.json"
@@ -345,6 +345,79 @@ export function extensionExistsAst(extensionName: string): ExtensionExistsCheckB
345
345
  };
346
346
  }
347
347
 
348
+ export interface RlsPolicyExistsCheckBuilder {
349
+ policyPresent(): SelectAst;
350
+ policyAbsent(): SelectAst;
351
+ }
352
+
353
+ /**
354
+ * Typed RLS-policy existence check over `pg_policies`, with schema, table,
355
+ * and policy names bound as text parameters (never inlined). The `schema`
356
+ * coordinate is the resolved live-database schema name (e.g. `'public'`),
357
+ * compared against `pg_policies.schemaname` as a bound parameter.
358
+ */
359
+ export function rlsPolicyExistsAst(options: {
360
+ readonly schema: string;
361
+ readonly table: string;
362
+ readonly policyName: string;
363
+ }): RlsPolicyExistsCheckBuilder {
364
+ const inner = () =>
365
+ exprSelect()
366
+ .from(cfTable('pg_policies'))
367
+ .project('one', cfExpr.lit(1))
368
+ .where(
369
+ cfExpr.allOf([
370
+ cfExpr.identifierRef('schemaname').eqParam(options.schema, PG_TEXT_CODEC_ID),
371
+ cfExpr.identifierRef('tablename').eqParam(options.table, PG_TEXT_CODEC_ID),
372
+ cfExpr.identifierRef('policyname').eqParam(options.policyName, PG_TEXT_CODEC_ID),
373
+ ]),
374
+ );
375
+ return {
376
+ policyPresent: () => exprSelect().project('result', cfExpr.exists(inner())).build(),
377
+ policyAbsent: () => exprSelect().project('result', cfExpr.notExists(inner())).build(),
378
+ };
379
+ }
380
+
381
+ export interface RlsEnabledCheckBuilder {
382
+ rlsEnabled(): SelectAst;
383
+ rlsDisabled(): SelectAst;
384
+ }
385
+
386
+ /**
387
+ * Typed row-level-security enabled check over `pg_class` joined to
388
+ * `pg_namespace`, comparing `pg_class.relrowsecurity` against the expected
389
+ * boolean. Schema and table names are bound as text parameters.
390
+ */
391
+ export function rlsEnabledAst(schema: string, table: string): RlsEnabledCheckBuilder {
392
+ const inner = (enabled: boolean) =>
393
+ exprSelect()
394
+ .from(cfTable('pg_class', 'c'))
395
+ .join(
396
+ cfTable('pg_namespace', 'n'),
397
+ cfExpr.columnRef('n', 'oid').eqExpr(cfExpr.columnRef('c', 'relnamespace')),
398
+ )
399
+ .project('one', cfExpr.lit(1))
400
+ .where(
401
+ cfExpr.allOf([
402
+ cfExpr.columnRef('n', 'nspname').eqParam(schema, PG_TEXT_CODEC_ID),
403
+ cfExpr.columnRef('c', 'relname').eqParam(table, PG_TEXT_CODEC_ID),
404
+ enabled
405
+ ? cfExpr.columnRef('c', 'relrowsecurity')
406
+ : cfExpr.columnRef('c', 'relrowsecurity').not(),
407
+ ]),
408
+ );
409
+ return {
410
+ rlsEnabled: () =>
411
+ exprSelect()
412
+ .project('result', cfExpr.exists(inner(true)))
413
+ .build(),
414
+ rlsDisabled: () =>
415
+ exprSelect()
416
+ .project('result', cfExpr.exists(inner(false)))
417
+ .build(),
418
+ };
419
+ }
420
+
348
421
  export interface IndexExistsCheckBuilder {
349
422
  indexPresent(): SelectAst;
350
423
  indexAbsent(): SelectAst;
@@ -2,9 +2,13 @@ import type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-
2
2
  import {
3
3
  AddColumnAction,
4
4
  type AnyAlterTableAction,
5
+ DropDefaultAction,
5
6
  PostgresAlterTable,
7
+ PostgresCreatePolicy,
6
8
  PostgresCreateSchema,
7
9
  PostgresCreateTable,
10
+ PostgresDropPolicy,
11
+ type RlsPolicyOperation,
8
12
  } from '../core/ddl/nodes';
9
13
 
10
14
  /**
@@ -51,9 +55,17 @@ export function addColumnAction(column: DdlColumn): AddColumnAction {
51
55
  return new AddColumnAction(column);
52
56
  }
53
57
 
58
+ /**
59
+ * Build a `DROP DEFAULT` action (`ALTER COLUMN "<name>" DROP DEFAULT`) for
60
+ * use inside {@link alterTable}. The renderer quotes the column name.
61
+ */
62
+ export function dropDefaultAction(columnName: string): DropDefaultAction {
63
+ return new DropDefaultAction(columnName);
64
+ }
65
+
54
66
  /**
55
67
  * Build a Postgres `ALTER TABLE` query node carrying one or more actions.
56
- * See {@link addColumnAction} for building actions.
68
+ * See {@link addColumnAction} / {@link dropDefaultAction} for building actions.
57
69
  */
58
70
  export function alterTable(options: {
59
71
  readonly table: string;
@@ -62,3 +74,35 @@ export function alterTable(options: {
62
74
  }): PostgresAlterTable {
63
75
  return new PostgresAlterTable(options);
64
76
  }
77
+
78
+ /**
79
+ * Build a Postgres `CREATE POLICY` DDL node.
80
+ *
81
+ * Identifiers (`schema`, `table`, `name`) are quoted by the renderer.
82
+ * `using` and `withCheck` are emitted verbatim as predicate SQL — callers
83
+ * must supply safe, pre-validated SQL expressions.
84
+ */
85
+ export function createPolicy(options: {
86
+ readonly schema: string;
87
+ readonly table: string;
88
+ readonly name: string;
89
+ readonly permissive: boolean;
90
+ readonly operation: RlsPolicyOperation;
91
+ readonly roles: readonly string[];
92
+ readonly using?: string;
93
+ readonly withCheck?: string;
94
+ }): PostgresCreatePolicy {
95
+ return new PostgresCreatePolicy(options);
96
+ }
97
+
98
+ /**
99
+ * Build a Postgres `DROP POLICY` DDL node.
100
+ * Identifiers (`schema`, `table`, `name`) are quoted by the renderer.
101
+ */
102
+ export function dropPolicy(options: {
103
+ readonly schema: string;
104
+ readonly table: string;
105
+ readonly name: string;
106
+ }): PostgresDropPolicy {
107
+ return new PostgresDropPolicy(options);
108
+ }
@@ -1,13 +1,94 @@
1
1
  import { temporalAuthoringPresets } from '@prisma-next/family-sql/control';
2
2
  import type {
3
+ AuthoringEntityContext,
3
4
  AuthoringEntityTypeNamespace,
4
5
  AuthoringFieldNamespace,
6
+ AuthoringPslBlockDescriptorNamespace,
5
7
  AuthoringTypeNamespace,
8
+ PslExtensionBlock,
6
9
  } from '@prisma-next/framework-components/authoring';
10
+ import { PostgresRlsPolicy } from './postgres-rls-policy';
11
+ import { PostgresRole, type PostgresRoleInput } from './postgres-role';
12
+ import { PostgresRlsPolicySchema, PostgresRoleSchema } from './postgres-validators';
13
+ import { computeContentHash, normalizePredicate } from './rls/canonicalize';
7
14
 
8
15
  export const postgresAuthoringTypes = {} as const satisfies AuthoringTypeNamespace;
9
16
 
10
- export const postgresAuthoringEntityTypes = {} as const satisfies AuthoringEntityTypeNamespace;
17
+ export interface RlsPolicyExtensionBlock extends PslExtensionBlock {
18
+ readonly namespaceId: string;
19
+ }
20
+
21
+ function readRefParam(block: PslExtensionBlock, key: string): string | undefined {
22
+ const param = block.parameters[key];
23
+ return param?.kind === 'ref' ? param.identifier : undefined;
24
+ }
25
+
26
+ function readValueParam(block: PslExtensionBlock, key: string): string | undefined {
27
+ const param = block.parameters[key];
28
+ return param?.kind === 'value' ? param.raw : undefined;
29
+ }
30
+
31
+ function readListRefParams(block: PslExtensionBlock, key: string): string[] {
32
+ const param = block.parameters[key];
33
+ if (param?.kind !== 'list') return [];
34
+ return param.items.flatMap((item) => (item.kind === 'ref' ? [item.identifier] : []));
35
+ }
36
+
37
+ function unwrapQuotedString(raw: string): string {
38
+ if (raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) {
39
+ return raw.slice(1, -1).replace(/\\"/g, '"');
40
+ }
41
+ return raw;
42
+ }
43
+
44
+ function lowerRlsPolicyFromBlock(
45
+ block: RlsPolicyExtensionBlock,
46
+ _ctx: AuthoringEntityContext,
47
+ ): PostgresRlsPolicy {
48
+ const prefix = block.name;
49
+ const targetModelName = readRefParam(block, 'target') ?? '';
50
+ const tableName = targetModelName.charAt(0).toLowerCase() + targetModelName.slice(1);
51
+ const roles = [...readListRefParams(block, 'roles')].sort();
52
+ const using = unwrapQuotedString(readValueParam(block, 'using') ?? '');
53
+
54
+ const wireHash = computeContentHash({
55
+ using: normalizePredicate(using),
56
+ roles,
57
+ operation: 'select',
58
+ permissive: true,
59
+ });
60
+ const wireName = `${prefix}_${wireHash}`;
61
+
62
+ return new PostgresRlsPolicy({
63
+ name: wireName,
64
+ prefix,
65
+ tableName,
66
+ namespaceId: block.namespaceId,
67
+ operation: 'select',
68
+ roles,
69
+ using,
70
+ permissive: true,
71
+ });
72
+ }
73
+
74
+ export const postgresAuthoringEntityTypes = {
75
+ role: {
76
+ kind: 'entity',
77
+ discriminator: 'role',
78
+ validatorSchema: PostgresRoleSchema,
79
+ output: {
80
+ factory: (input: PostgresRoleInput): PostgresRole => new PostgresRole(input),
81
+ },
82
+ },
83
+ policy: {
84
+ kind: 'entity',
85
+ discriminator: 'policy',
86
+ validatorSchema: PostgresRlsPolicySchema,
87
+ output: {
88
+ factory: lowerRlsPolicyFromBlock,
89
+ },
90
+ },
91
+ } as const satisfies AuthoringEntityTypeNamespace;
11
92
 
12
93
  /**
13
94
  * Field presets contributed by the Postgres target pack.
@@ -22,6 +103,39 @@ export const postgresAuthoringEntityTypes = {} as const satisfies AuthoringEntit
22
103
  * portability use `uuidString` / `id.uuidv4String` / `id.uuidv7String` from
23
104
  * the family pack instead.
24
105
  */
106
+ /**
107
+ * PSL block descriptor for `policy_select`.
108
+ *
109
+ * The parser learns the block shape from this descriptor; lowering from
110
+ * `PslExtensionBlock` to `PostgresRlsPolicy` is wired in the PSL
111
+ * interpreter (a later dispatch). The `discriminator` matches
112
+ * `PostgresRlsPolicy.kind` so the parsed block node carries the same
113
+ * discriminant as the IR class it will lower to.
114
+ *
115
+ * The `roles` list uses `scope:'cross-space'` because same-namespace
116
+ * role ref resolution requires PSL namespace entries keyed by `refKind`
117
+ * (i.e. `'role'`), which in turn requires the role block discriminator to
118
+ * equal `'role'`. Aligning discriminator with refKind is tracked for
119
+ * slice 4 (cross-space roles). Until then cross-space passes validation
120
+ * unconditionally and the authored role names flow through unchanged.
121
+ */
122
+ export const postgresAuthoringPslBlockDescriptors = {
123
+ policy_select: {
124
+ kind: 'pslBlock',
125
+ keyword: 'policy_select',
126
+ discriminator: 'policy',
127
+ name: { required: true },
128
+ parameters: {
129
+ target: { kind: 'ref', refKind: 'model', scope: 'same-namespace', required: true },
130
+ roles: {
131
+ kind: 'list',
132
+ of: { kind: 'ref', refKind: 'role', scope: 'cross-space' },
133
+ },
134
+ using: { kind: 'value', codecId: 'pg/text@1', required: true },
135
+ },
136
+ },
137
+ } as const satisfies AuthoringPslBlockDescriptorNamespace;
138
+
25
139
  export const postgresAuthoringFieldPresets = {
26
140
  text: {
27
141
  kind: 'fieldPreset',
@@ -4,7 +4,7 @@
4
4
  * Each codec ships as three artifacts:
5
5
  *
6
6
  * 1. A `PgXCodec` class extending {@link CodecImpl} that wraps the module-level encode/decode/encodeJson/decodeJson constants exported from `codec-helpers.ts` (the single source of truth for non-trivial runtime conversions; trivial identity passthroughs are inlined). 2. A `PgXDescriptor` class extending {@link CodecDescriptorImpl} declaring the codec id, traits, target types, params schema, meta, and (where applicable)
7
- * the emit-path `renderOutputType`. 3. A per-codec column helper (`pgXColumn`) that calls `descriptor.factory(...)` directly and packages the result into a {@link ColumnSpec} via the framework {@link column} packager. The helper is tied to its descriptor with `satisfies ColumnHelperFor` (and `ColumnHelperForStrict` where the resolved codec type is well-defined).
7
+ * the emit-path `renderOutputType`. 3. A per-codec column helper (`pgXColumn`) that calls `descriptor.factory(...)` directly and packages the result into a framework `ColumnSpec` via the framework {@link column} packager. The helper is tied to its descriptor with `satisfies ColumnHelperFor` (and `ColumnHelperForStrict` where the resolved codec type is well-defined).
8
8
  *
9
9
  * After TML-2357 this is the canonical source of Postgres codec metadata and runtime behaviour — the legacy `mkCodec` / `defineCodec` carriers (and the parallel `byScalar`/`codecDescriptorDefinitions`/ `codecDescriptorList` collection exports) retired with the deletion sweep.
10
10
  *
@@ -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 { PostgresRlsPolicy, type PostgresRlsPolicyInput } from './postgres-rls-policy';
3
+ import { PostgresRole, type PostgresRoleInput } from './postgres-role';
4
+ import { PostgresRlsPolicySchema, PostgresRoleSchema } from './postgres-validators';
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
+ }
@@ -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 {
@@ -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':