@prisma-next/target-postgres 0.14.0-dev.4 → 0.14.0-dev.41

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 (191) hide show
  1. package/dist/authoring-BX1WHIdN.mjs +220 -0
  2. package/dist/authoring-BX1WHIdN.mjs.map +1 -0
  3. package/dist/canonicalize-Bn3BM1Cn.mjs +46 -0
  4. package/dist/canonicalize-Bn3BM1Cn.mjs.map +1 -0
  5. package/dist/canonicalize-CAbXtVtB.d.mts +37 -0
  6. package/dist/canonicalize-CAbXtVtB.d.mts.map +1 -0
  7. package/dist/{codec-types-DHCkwPKE.d.mts → codec-types-CueCoGD0.d.mts} +2 -2
  8. package/dist/{codec-types-DHCkwPKE.d.mts.map → codec-types-CueCoGD0.d.mts.map} +1 -1
  9. package/dist/codec-types.d.mts +1 -1
  10. package/dist/{codecs--0A5_4Bq.d.mts → codecs-Ca8upop6.d.mts} +12 -1
  11. package/dist/codecs-Ca8upop6.d.mts.map +1 -0
  12. package/dist/codecs.d.mts +1 -1
  13. package/dist/codecs.mjs +34 -1
  14. package/dist/codecs.mjs.map +1 -1
  15. package/dist/contract-free.d.mts +8 -3
  16. package/dist/contract-free.d.mts.map +1 -1
  17. package/dist/contract-free.mjs +3 -3
  18. package/dist/control.d.mts +1 -1
  19. package/dist/control.d.mts.map +1 -1
  20. package/dist/control.mjs +639 -21
  21. package/dist/control.mjs.map +1 -1
  22. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  23. package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-qKy1U5V9.d.mts} +2 -2
  24. package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-qKy1U5V9.d.mts.map} +1 -1
  25. package/dist/data-transform.d.mts +1 -1
  26. package/dist/{ddl-QDyOSeLc.mjs → ddl-BmLIe1z_.mjs} +62 -5
  27. package/dist/ddl-BmLIe1z_.mjs.map +1 -0
  28. package/dist/ddl.d.mts +3 -2
  29. package/dist/ddl.mjs +2 -2
  30. package/dist/{default-normalizer-DyyCHQWs.mjs → default-normalizer-Dug8Fez9.mjs} +119 -2
  31. package/dist/default-normalizer-Dug8Fez9.mjs.map +1 -0
  32. package/dist/default-normalizer.d.mts +1 -1
  33. package/dist/default-normalizer.d.mts.map +1 -1
  34. package/dist/default-normalizer.mjs +1 -1
  35. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs +12 -0
  36. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs.map +1 -0
  37. package/dist/descriptor-meta-tjatyhtZ.mjs +18 -0
  38. package/dist/descriptor-meta-tjatyhtZ.mjs.map +1 -0
  39. package/dist/{issue-planner-DL6g3CmE.mjs → issue-planner-DbFvaAgu.mjs} +35 -10
  40. package/dist/issue-planner-DbFvaAgu.mjs.map +1 -0
  41. package/dist/issue-planner.d.mts +3 -3
  42. package/dist/issue-planner.d.mts.map +1 -1
  43. package/dist/issue-planner.mjs +1 -1
  44. package/dist/migration.d.mts +5 -5
  45. package/dist/migration.mjs +4 -4
  46. package/dist/nodes-CSkPX3rI.d.mts +120 -0
  47. package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
  48. package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
  49. package/dist/nodes-Dh__NS58.mjs.map +1 -0
  50. package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-CJWUxyUW.d.mts} +44 -13
  51. package/dist/op-factory-call-CJWUxyUW.d.mts.map +1 -0
  52. package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-t65c4jyY.mjs} +201 -28
  53. package/dist/op-factory-call-t65c4jyY.mjs.map +1 -0
  54. package/dist/op-factory-call.d.mts +1 -1
  55. package/dist/op-factory-call.mjs +1 -1
  56. package/dist/pack.d.mts +74 -2
  57. package/dist/pack.d.mts.map +1 -1
  58. package/dist/pack.mjs +1 -1
  59. package/dist/planner-CG2pLHk-.mjs +640 -0
  60. package/dist/planner-CG2pLHk-.mjs.map +1 -0
  61. package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-B8Nn6nHN.mjs} +12 -27
  62. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -0
  63. package/dist/planner-ddl-builders.d.mts +2 -4
  64. package/dist/planner-ddl-builders.d.mts.map +1 -1
  65. package/dist/planner-ddl-builders.mjs +2 -2
  66. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-BolD0Mvc.mjs} +3 -3
  67. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-BolD0Mvc.mjs.map} +1 -1
  68. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-Cz9--XOT.d.mts} +4 -4
  69. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-Cz9--XOT.d.mts.map} +1 -1
  70. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  71. package/dist/planner-produced-postgres-migration.mjs +1 -1
  72. package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-DE9c4519.mjs} +2 -2
  73. package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-DE9c4519.mjs.map} +1 -1
  74. package/dist/planner-sql-checks.mjs +1 -1
  75. package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
  76. package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
  77. package/dist/planner-target-details.d.mts +1 -1
  78. package/dist/planner.d.mts +82 -4
  79. package/dist/planner.d.mts.map +1 -1
  80. package/dist/planner.mjs +2 -2
  81. package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-view-BegduLr6.mjs} +50 -23
  82. package/dist/postgres-contract-view-BegduLr6.mjs.map +1 -0
  83. package/dist/postgres-contract-view-C9j5cqP0.d.mts +34 -0
  84. package/dist/postgres-contract-view-C9j5cqP0.d.mts.map +1 -0
  85. package/dist/postgres-database-schema-node-os8ogEap.d.mts +183 -0
  86. package/dist/postgres-database-schema-node-os8ogEap.d.mts.map +1 -0
  87. package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-BfMmK6tv.mjs} +21 -2
  88. package/dist/postgres-migration-BfMmK6tv.mjs.map +1 -0
  89. package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-D7kNVWVG.d.mts} +19 -6
  90. package/dist/postgres-migration-D7kNVWVG.d.mts.map +1 -0
  91. package/dist/postgres-rls-policy--CSddvMR.d.mts +52 -0
  92. package/dist/postgres-rls-policy--CSddvMR.d.mts.map +1 -0
  93. package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-BLIHBEfX.mjs} +108 -5
  94. package/dist/postgres-schema-BLIHBEfX.mjs.map +1 -0
  95. package/dist/postgres-schema-BywoeKlt.d.mts +189 -0
  96. package/dist/postgres-schema-BywoeKlt.d.mts.map +1 -0
  97. package/dist/postgres-table-schema-node-1nNc8XDT.mjs +248 -0
  98. package/dist/postgres-table-schema-node-1nNc8XDT.mjs.map +1 -0
  99. package/dist/render-ops.d.mts +1 -1
  100. package/dist/{render-typescript-KMgosran.mjs → render-typescript-7yqILcwr.mjs} +45 -16
  101. package/dist/render-typescript-7yqILcwr.mjs.map +1 -0
  102. package/dist/render-typescript.mjs +1 -1
  103. package/dist/resolve-ddl-schema-CL1RR1mx.mjs +20 -0
  104. package/dist/resolve-ddl-schema-CL1RR1mx.mjs.map +1 -0
  105. package/dist/rls-canonicalize.d.mts +2 -0
  106. package/dist/rls-canonicalize.mjs +2 -0
  107. package/dist/runtime.d.mts +7 -6
  108. package/dist/runtime.d.mts.map +1 -1
  109. package/dist/runtime.mjs +3 -3
  110. package/dist/runtime.mjs.map +1 -1
  111. package/dist/schema-ir-annotations.d.mts +14 -0
  112. package/dist/schema-ir-annotations.d.mts.map +1 -0
  113. package/dist/schema-ir-annotations.mjs +2 -0
  114. package/dist/types.d.mts +4 -140
  115. package/dist/types.mjs +3 -2
  116. package/package.json +22 -17
  117. package/src/contract-free/checks.ts +73 -0
  118. package/src/contract-free/ddl.ts +45 -1
  119. package/src/core/authoring.ts +115 -1
  120. package/src/core/codecs.ts +35 -1
  121. package/src/core/ddl/nodes.ts +94 -2
  122. package/src/core/default-normalizer.ts +122 -2
  123. package/src/core/descriptor-meta.ts +4 -0
  124. package/src/core/entity-kinds.ts +16 -0
  125. package/src/core/migrations/bound-schema.ts +10 -0
  126. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +125 -0
  127. package/src/core/migrations/control-policy.ts +3 -0
  128. package/src/core/migrations/diff-database-schema.ts +191 -0
  129. package/src/core/migrations/issue-planner.ts +44 -3
  130. package/src/core/migrations/op-factory-call.ts +109 -21
  131. package/src/core/migrations/operations/columns.ts +24 -12
  132. package/src/core/migrations/operations/data-transform.ts +6 -0
  133. package/src/core/migrations/operations/rls.ts +106 -0
  134. package/src/core/migrations/operations/shared.ts +0 -30
  135. package/src/core/migrations/planner-ddl-builders.ts +15 -56
  136. package/src/core/migrations/planner-recipes.ts +31 -13
  137. package/src/core/migrations/planner-strategies.ts +3 -11
  138. package/src/core/migrations/planner-target-details.ts +3 -1
  139. package/src/core/migrations/planner.ts +165 -33
  140. package/src/core/migrations/postgres-migration.ts +34 -4
  141. package/src/core/migrations/render-typescript.ts +42 -15
  142. package/src/core/migrations/resolve-ddl-schema.ts +24 -0
  143. package/src/core/migrations/runner.ts +13 -16
  144. package/src/core/migrations/verify-postgres-namespaces.ts +24 -19
  145. package/src/core/postgres-contract-serializer.ts +50 -33
  146. package/src/core/postgres-contract-view.ts +48 -0
  147. package/src/core/postgres-rls-policy.ts +64 -0
  148. package/src/core/postgres-role.ts +37 -0
  149. package/src/core/postgres-schema.ts +37 -13
  150. package/src/core/postgres-validators.ts +20 -0
  151. package/src/core/psl-infer/infer-psl-contract.ts +834 -0
  152. package/src/core/psl-infer/postgres-default-mapping.ts +16 -0
  153. package/src/core/psl-infer/postgres-type-map.ts +157 -0
  154. package/src/core/rls/canonicalize.ts +48 -0
  155. package/src/core/schema-ir/postgres-database-schema-node.ts +63 -0
  156. package/src/core/schema-ir/postgres-namespace-schema-node.ts +61 -0
  157. package/src/core/schema-ir/postgres-policy-schema-node.ts +93 -0
  158. package/src/core/schema-ir/postgres-role-schema-node.ts +66 -0
  159. package/src/core/schema-ir/postgres-table-schema-node.ts +107 -0
  160. package/src/core/schema-ir/schema-node-kinds.ts +28 -0
  161. package/src/exports/contract-free.ts +7 -1
  162. package/src/exports/control.ts +39 -12
  163. package/src/exports/ddl.ts +4 -0
  164. package/src/exports/migration.ts +1 -0
  165. package/src/exports/planner-ddl-builders.ts +0 -2
  166. package/src/exports/planner.ts +2 -0
  167. package/src/exports/rls-canonicalize.ts +6 -0
  168. package/src/exports/runtime.ts +1 -0
  169. package/src/exports/schema-ir-annotations.ts +1 -0
  170. package/src/exports/types.ts +28 -0
  171. package/dist/codecs--0A5_4Bq.d.mts.map +0 -1
  172. package/dist/ddl-QDyOSeLc.mjs.map +0 -1
  173. package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
  174. package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
  175. package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
  176. package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
  177. package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
  178. package/dist/nodes-pLeLgdis.d.mts +0 -67
  179. package/dist/nodes-pLeLgdis.d.mts.map +0 -1
  180. package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
  181. package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
  182. package/dist/planner-Bs_baQax.mjs +0 -324
  183. package/dist/planner-Bs_baQax.mjs.map +0 -1
  184. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
  185. package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
  186. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
  187. package/dist/postgres-migration-B5jKrXv3.mjs.map +0 -1
  188. package/dist/postgres-migration-Y4YBJqkS.d.mts.map +0 -1
  189. package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
  190. package/dist/render-typescript-KMgosran.mjs.map +0 -1
  191. package/dist/types.d.mts.map +0 -1
@@ -10,20 +10,22 @@ import {
10
10
  isAuthoringEntityTypeDescriptor,
11
11
  } from '@prisma-next/framework-components/authoring';
12
12
  import {
13
+ type AnyEntityKindDescriptor,
13
14
  type Namespace,
14
- NamespaceBase,
15
15
  UNBOUND_NAMESPACE_ID,
16
16
  } from '@prisma-next/framework-components/ir';
17
- import type { SqlNamespaceTablesInput, SqlStorage } from '@prisma-next/sql-contract/types';
17
+ import type { SqlNamespaceInput, SqlStorage } from '@prisma-next/sql-contract/types';
18
18
  import { blindCast } from '@prisma-next/utils/casts';
19
- import type { JsonObject } from '@prisma-next/utils/json';
19
+ import type { JsonObject, JsonValue } from '@prisma-next/utils/json';
20
20
  import { postgresAuthoringEntityTypes } from './authoring';
21
- import { postgresTargetDescriptorMeta } from './descriptor-meta';
21
+ import { PG_INT_CODEC_ID, PG_TEXT_CODEC_ID } from './codec-ids';
22
+ import { policyEntityKind, roleEntityKind } from './entity-kinds';
22
23
  import { isPostgresSchema, PostgresSchema } from './postgres-schema';
23
24
 
24
25
  const POSTGRES_AUTHORING_CTX: AuthoringEntityContext = {
25
26
  family: 'sql',
26
27
  target: 'postgres',
28
+ enumInferenceCodecs: { text: PG_TEXT_CODEC_ID, int: PG_INT_CODEC_ID },
27
29
  };
28
30
 
29
31
  function isAuthoringEntityTypeFactoryOutput(
@@ -32,7 +34,8 @@ function isAuthoringEntityTypeFactoryOutput(
32
34
  return (
33
35
  typeof output === 'object' &&
34
36
  output !== null &&
35
- typeof (output as AuthoringEntityTypeFactoryOutput).factory === 'function'
37
+ 'factory' in output &&
38
+ typeof output.factory === 'function'
36
39
  );
37
40
  }
38
41
 
@@ -65,34 +68,29 @@ function collectStorageTypesHydrators(
65
68
  }
66
69
 
67
70
  export class PostgresContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {
68
- constructor() {
71
+ constructor(extraPackEntityKinds: readonly AnyEntityKindDescriptor[] = []) {
69
72
  const storageTypesHydrators = collectStorageTypesHydrators(postgresAuthoringEntityTypes);
70
- super(storageTypesHydrators);
71
- }
72
-
73
- protected override get defaultNamespaceId(): string {
74
- return postgresTargetDescriptorMeta.defaultNamespaceId;
73
+ super(storageTypesHydrators, [policyEntityKind, roleEntityKind, ...extraPackEntityKinds]);
75
74
  }
76
75
 
77
76
  protected override hydrateSqlNamespaceEntry(
78
77
  nsId: string,
79
- raw: Namespace | Record<string, unknown>,
80
- ): Namespace | SqlNamespaceTablesInput {
81
- if (raw instanceof NamespaceBase) {
82
- return raw;
83
- }
78
+ raw: Record<string, unknown>,
79
+ ): Namespace | SqlNamespaceInput {
84
80
  const hydrated = blindCast<
85
- SqlNamespaceTablesInput,
86
- 'super.hydrateSqlNamespaceEntry returns SqlNamespaceTablesInput when raw is not a NamespaceBase'
81
+ SqlNamespaceInput,
82
+ 'raw is always plain JSON, so super.hydrateSqlNamespaceEntry returns SqlNamespaceInput'
87
83
  >(super.hydrateSqlNamespaceEntry(nsId, raw));
88
84
  const { id, entries } = hydrated;
89
85
 
90
- const valueSetSlot = entries['valueSet'];
91
- const hasValueSets = valueSetSlot !== undefined && Object.keys(valueSetSlot).length > 0;
92
- const emptyTables = Object.keys(entries['table'] ?? {}).length === 0;
93
- if (id === UNBOUND_NAMESPACE_ID && emptyTables && !hasValueSets) {
86
+ const allSlotsEmpty = Object.values(entries).every(
87
+ (slot) => slot === undefined || Object.keys(slot).length === 0,
88
+ );
89
+ if (id === UNBOUND_NAMESPACE_ID && allSlotsEmpty) {
94
90
  return PostgresSchema.unbound;
95
91
  }
92
+ const valueSetSlot = entries['valueSet'];
93
+ const hasValueSets = valueSetSlot !== undefined && Object.keys(valueSetSlot).length > 0;
96
94
  return new PostgresSchema({
97
95
  id,
98
96
  entries: {
@@ -118,7 +116,7 @@ export class PostgresContractSerializer extends SqlContractSerializerBase<Contra
118
116
  table: Object.fromEntries(
119
117
  Object.entries(ns.entries.table ?? {}).map(([tableName, table]) => [
120
118
  tableName,
121
- this.serializeJsonValue(table) as JsonObject,
119
+ this.serializeJsonObject(table),
122
120
  ]),
123
121
  ),
124
122
  },
@@ -132,42 +130,61 @@ export class PostgresContractSerializer extends SqlContractSerializerBase<Contra
132
130
  if (storage.types !== undefined) {
133
131
  const typesOut: Record<string, JsonObject> = {};
134
132
  for (const [name, entry] of Object.entries(storage.types)) {
135
- typesOut[name] = this.serializeJsonValue(entry) as JsonObject;
133
+ typesOut[name] = this.serializeJsonObject(entry);
136
134
  }
137
135
  storageOut['types'] = typesOut;
138
136
  }
139
- return {
137
+ return blindCast<
138
+ JsonObject,
139
+ 'contract minus storage plus a JSON-shaped storageOut is a JsonObject'
140
+ >({
140
141
  ...rest,
141
142
  storage: storageOut,
142
- } as unknown as JsonObject;
143
+ });
143
144
  }
144
145
 
145
146
  private serializePostgresNamespace(ns: PostgresSchema, isUnboundSlot: boolean): JsonObject {
146
147
  const tablesOut: Record<string, JsonObject> = {};
147
148
  for (const [tableName, table] of Object.entries(ns.table)) {
148
- tablesOut[tableName] = this.serializeJsonValue(table) as JsonObject;
149
+ tablesOut[tableName] = this.serializeJsonObject(table);
149
150
  }
150
151
  const valueSetEntries = ns.valueSet;
151
152
  const valueSetOut: Record<string, JsonObject> = {};
152
153
  if (valueSetEntries !== undefined) {
153
154
  for (const [valueSetName, valueSet] of Object.entries(valueSetEntries)) {
154
- valueSetOut[valueSetName] = blindCast<
155
- JsonObject,
156
- 'serializeJsonValue round-trips the value-set node through JSON, yielding a JsonObject'
157
- >(this.serializeJsonValue(valueSet));
155
+ valueSetOut[valueSetName] = this.serializeJsonObject(valueSet);
158
156
  }
159
157
  }
158
+ const roleOut: Record<string, JsonObject> = {};
159
+ for (const [roleName, role] of Object.entries(ns.role)) {
160
+ roleOut[roleName] = this.serializeJsonObject(role);
161
+ }
162
+ const policyOut: Record<string, JsonObject> = {};
163
+ for (const [policyName, policy] of Object.entries(ns.policy)) {
164
+ policyOut[policyName] = this.serializeJsonObject(policy);
165
+ }
160
166
  return {
161
167
  id: ns.id,
162
168
  kind: isUnboundSlot ? 'postgres-unbound-schema' : 'postgres-schema',
163
169
  entries: {
164
170
  table: tablesOut,
165
171
  ...(Object.keys(valueSetOut).length > 0 ? { valueSet: valueSetOut } : {}),
172
+ ...(Object.keys(roleOut).length > 0 ? { role: roleOut } : {}),
173
+ ...(Object.keys(policyOut).length > 0 ? { policy: policyOut } : {}),
166
174
  },
167
175
  };
168
176
  }
169
177
 
170
- private serializeJsonValue(value: unknown): unknown {
171
- return JSON.parse(JSON.stringify(value)) as unknown;
178
+ private serializeJsonObject(value: unknown): JsonObject {
179
+ return blindCast<
180
+ JsonObject,
181
+ 'serializeJsonValue round-trips an IR node through JSON, yielding a JsonObject'
182
+ >(this.serializeJsonValue(value));
183
+ }
184
+
185
+ private serializeJsonValue(value: unknown): JsonValue {
186
+ return blindCast<JsonValue, 'JSON.parse(JSON.stringify(x)) yields a JsonValue'>(
187
+ JSON.parse(JSON.stringify(value)),
188
+ );
172
189
  }
173
190
  }
@@ -0,0 +1,48 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import {
3
+ buildSqlSchemaQualifiedView,
4
+ type SqlSchemaQualifiedView,
5
+ } from '@prisma-next/sql-contract/contract-view';
6
+ import type { SqlStorage } from '@prisma-next/sql-contract/types';
7
+ import { PostgresContractSerializer } from './postgres-contract-serializer';
8
+
9
+ /**
10
+ * A schema-qualified Postgres contract view: the deserialized contract
11
+ * intersected with a single `namespace` member holding every schema by id. It is
12
+ * substitutable for `Contract` (carries `storage`, `domain`, …) and reaches each
13
+ * schema's entities through `view.namespace.<schema>`:
14
+ *
15
+ * ```ts
16
+ * const view = PostgresContractView.fromJson<Contract>(contractJson);
17
+ * view.namespace.public.table.users // typed table leaf in the public schema
18
+ * view.namespace.auth.table.users // the auth schema's own users table
19
+ * view.namespace.public.entries.policy.X // pack-contributed kind (RLS / #771 path)
20
+ * view.namespace.__unbound__.table.X // default schema, keyed by its raw id
21
+ * view.storage // the full contract is still present
22
+ * ```
23
+ *
24
+ * This mirrors the runtime `db.enums.<ns>` keying exactly (the default schema
25
+ * keeps its literal `__unbound__` id). Schema names are NOT promoted to the
26
+ * contract root, so there is no collision with contract envelope fields — a
27
+ * schema named `storage` is `view.namespace.storage`, while `view.storage`
28
+ * stays the contract's `storage`.
29
+ */
30
+ export type PostgresContractView<TContract extends Contract<SqlStorage> = Contract<SqlStorage>> =
31
+ SqlSchemaQualifiedView<TContract>;
32
+
33
+ export const PostgresContractView = {
34
+ /** Wrap an already-deserialized Postgres contract in a schema-qualified view. */
35
+ from<TContract extends Contract<SqlStorage>>(
36
+ contract: TContract,
37
+ ): PostgresContractView<TContract> {
38
+ return buildSqlSchemaQualifiedView(contract);
39
+ },
40
+
41
+ /** Deserialize a Postgres contract JSON envelope and wrap it in a view. */
42
+ fromJson<TContract extends Contract<SqlStorage> = Contract<SqlStorage>>(
43
+ json: unknown,
44
+ ): PostgresContractView<TContract> {
45
+ const contract = new PostgresContractSerializer().deserializeContract<TContract>(json);
46
+ return buildSqlSchemaQualifiedView(contract);
47
+ },
48
+ };
@@ -0,0 +1,64 @@
1
+ import { freezeNode } from '@prisma-next/framework-components/ir';
2
+ import { SqlNode } from '@prisma-next/sql-contract/types';
3
+
4
+ export type RlsPolicyOperation = 'select' | 'insert' | 'update' | 'delete' | 'all';
5
+
6
+ export interface PostgresRlsPolicyInput {
7
+ /** Full wire name: `<prefix>_<8hex>`. Stored as-is; hashing is not this class's job. */
8
+ readonly name: string;
9
+ /** User-supplied prefix (the part before the `_<8hex>` suffix). */
10
+ readonly prefix: string;
11
+ /** Name of the table this policy attaches to, by name within the same schema. */
12
+ readonly tableName: string;
13
+ /** Namespace coordinate (schema name). Policies are schema-scoped. */
14
+ readonly namespaceId: string;
15
+ readonly operation: RlsPolicyOperation;
16
+ /** Sorted role names rendered in `TO <roles>`. Plain strings in this slice. */
17
+ readonly roles: readonly string[];
18
+ /** USING predicate SQL string, if present. */
19
+ readonly using?: string;
20
+ /** WITH CHECK predicate SQL string, if present. */
21
+ readonly withCheck?: string;
22
+ /** `true` = `AS PERMISSIVE`, `false` = `AS RESTRICTIVE`. */
23
+ readonly permissive: boolean;
24
+ }
25
+
26
+ /**
27
+ * Postgres contract-IR class for a row-level security policy (`CREATE POLICY … ON …`).
28
+ *
29
+ * This is an authored, serialized Contract-IR entity — it is registered as an entity
30
+ * kind, extends `SqlNode`, and is stored in `contract.json`. It is NOT a DiffableNode;
31
+ * the schema-diff tree uses `PostgresPolicySchemaNode` for that role.
32
+ *
33
+ * Target-only concept — no SQL-family abstract. Extends `SqlNode` directly.
34
+ * Frozen at construction via `freezeNode(this)`. The `kind: 'policy'`
35
+ * discriminant is enumerable (overrides SqlNode's non-enumerable `'sql'`) so it
36
+ * survives JSON serialization and enables dispatch. The literal matches the
37
+ * entries key (one-string rule: node.kind === entries key === entity kind).
38
+ */
39
+ export class PostgresRlsPolicy extends SqlNode {
40
+ override readonly kind = 'policy' as const;
41
+ readonly name: string;
42
+ readonly prefix: string;
43
+ readonly tableName: string;
44
+ readonly namespaceId: string;
45
+ readonly operation: RlsPolicyOperation;
46
+ readonly roles: readonly string[];
47
+ declare readonly using?: string;
48
+ declare readonly withCheck?: string;
49
+ readonly permissive: boolean;
50
+
51
+ constructor(input: PostgresRlsPolicyInput) {
52
+ super();
53
+ this.name = input.name;
54
+ this.prefix = input.prefix;
55
+ this.tableName = input.tableName;
56
+ this.namespaceId = input.namespaceId;
57
+ this.operation = input.operation;
58
+ this.roles = Object.freeze([...input.roles]);
59
+ if (input.using !== undefined) this.using = input.using;
60
+ if (input.withCheck !== undefined) this.withCheck = input.withCheck;
61
+ this.permissive = input.permissive;
62
+ freezeNode(this);
63
+ }
64
+ }
@@ -0,0 +1,37 @@
1
+ import { freezeNode } from '@prisma-next/framework-components/ir';
2
+ import { SqlNode } from '@prisma-next/sql-contract/types';
3
+
4
+ export interface PostgresRoleInput {
5
+ readonly name: string;
6
+ /**
7
+ * Namespace coordinate. Roles are cluster-scoped; pass `UNBOUND_NAMESPACE_ID`
8
+ * from `@prisma-next/framework-components/ir`.
9
+ */
10
+ readonly namespaceId: string;
11
+ }
12
+
13
+ /**
14
+ * Postgres contract-IR class for a database role (`CREATE ROLE …`).
15
+ *
16
+ * This is an authored, serialized Contract-IR entity — it is registered as an entity
17
+ * kind, extends `SqlNode`, and is stored in `contract.json`. It is NOT a DiffableNode;
18
+ * the schema-diff tree uses `PostgresRoleSchemaNode` for that role.
19
+ *
20
+ * Roles are cluster-scoped, so their namespace coordinate is always
21
+ * `UNBOUND_NAMESPACE_ID`. Target-only concept — no SQL-family abstract.
22
+ * Extends `SqlNode` directly, frozen at construction via `freezeNode(this)`.
23
+ * The `kind: 'role'` discriminant is enumerable so it survives JSON.
24
+ * Matches the entries key (one-string rule).
25
+ */
26
+ export class PostgresRole extends SqlNode {
27
+ override readonly kind = 'role' as const;
28
+ readonly name: string;
29
+ readonly namespaceId: string;
30
+
31
+ constructor(input: PostgresRoleInput) {
32
+ super();
33
+ this.name = input.name;
34
+ this.namespaceId = input.namespaceId;
35
+ freezeNode(this);
36
+ }
37
+ }
@@ -1,24 +1,36 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
1
2
  import {
2
3
  freezeNode,
3
4
  hydrateNamespaceEntities,
4
- NamespaceBase,
5
5
  UNBOUND_NAMESPACE_ID,
6
6
  } from '@prisma-next/framework-components/ir';
7
7
  import { composeSqlEntityKinds } from '@prisma-next/sql-contract/entity-kinds';
8
- import type {
9
- SqlNamespaceEntries,
10
- SqlNamespaceTablesInput,
11
- SqlStorage,
12
- StorageTable,
13
- StorageValueSet,
8
+ import {
9
+ SqlNamespaceBase,
10
+ type SqlNamespaceEntries,
11
+ type SqlNamespaceInput,
12
+ type SqlStorage,
13
+ type StorageTable,
14
+ type StorageValueSet,
14
15
  } from '@prisma-next/sql-contract/types';
16
+
15
17
  import { type CfExpr, cfExpr } from '@prisma-next/sql-relational-core/contract-free';
16
18
  import { blindCast } from '@prisma-next/utils/casts';
17
19
  import { ifDefined } from '@prisma-next/utils/defined';
18
20
  import { PostgresTableSource } from './ast/table-source';
19
21
  import { PG_TEXT_CODEC_ID } from './codec-ids';
22
+ import { policyEntityKind, roleEntityKind } from './entity-kinds';
23
+ import type { PostgresRlsPolicy } from './postgres-rls-policy';
24
+ import type { PostgresRole } from './postgres-role';
20
25
  import { escapeLiteral } from './sql-utils';
21
26
 
27
+ export type PostgresContract = Contract<SqlStorage> & { readonly target: 'postgres' };
28
+
29
+ export type PostgresNamespaceEntries = SqlNamespaceEntries & {
30
+ readonly policy?: Readonly<Record<string, PostgresRlsPolicy>>;
31
+ readonly role?: Readonly<Record<string, PostgresRole>>;
32
+ };
33
+
22
34
  export interface PostgresSchemaInput {
23
35
  readonly id: string;
24
36
  readonly entries: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
@@ -38,7 +50,7 @@ export interface PostgresSchemaInput {
38
50
  * prefix entirely — call sites stay polymorphic and never branch on
39
51
  * `id === UNBOUND_NAMESPACE_ID`.
40
52
  */
41
- export class PostgresSchema extends NamespaceBase {
53
+ export class PostgresSchema extends SqlNamespaceBase {
42
54
  /**
43
55
  * Stable singleton reference for the late-bound slot. Materialised
44
56
  * lazily below the singleton subclass declaration so the static
@@ -50,13 +62,17 @@ export class PostgresSchema extends NamespaceBase {
50
62
 
51
63
  declare readonly kind: 'schema';
52
64
  readonly id: string;
53
- readonly entries: SqlNamespaceEntries;
65
+ readonly entries: PostgresNamespaceEntries;
54
66
 
55
67
  constructor(input: PostgresSchemaInput) {
56
68
  super();
57
69
  this.id = input.id;
58
70
 
59
- const dispatched = hydrateNamespaceEntities(input.entries, composeSqlEntityKinds(), 'carry');
71
+ const dispatched = hydrateNamespaceEntities(
72
+ input.entries,
73
+ composeSqlEntityKinds([policyEntityKind, roleEntityKind]),
74
+ 'carry',
75
+ );
60
76
 
61
77
  // Drop an empty valueSet so presence signals non-emptiness.
62
78
  const valueSetRaw = dispatched['valueSet'];
@@ -67,8 +83,8 @@ export class PostgresSchema extends NamespaceBase {
67
83
 
68
84
  this.entries = Object.freeze(
69
85
  blindCast<
70
- SqlNamespaceEntries,
71
- 'composeSqlEntityKinds() supplies table→StorageTable and valueSet→StorageValueSet descriptors, so this open-dict result holds exactly the typed members SqlNamespaceEntries declares; the descriptor Map erases those per-kind Node types from the return.'
86
+ PostgresNamespaceEntries,
87
+ 'composeSqlEntityKinds([policyEntityKind, roleEntityKind]) supplies table→StorageTable, valueSet→StorageValueSet, policy→PostgresRlsPolicy, role→PostgresRole descriptors'
72
88
  >(withPresence),
73
89
  );
74
90
  Object.defineProperty(this, 'kind', {
@@ -88,6 +104,14 @@ export class PostgresSchema extends NamespaceBase {
88
104
  return this.entries.valueSet;
89
105
  }
90
106
 
107
+ get policy(): Readonly<Record<string, PostgresRlsPolicy>> {
108
+ return this.entries.policy ?? Object.freeze({});
109
+ }
110
+
111
+ get role(): Readonly<Record<string, PostgresRole>> {
112
+ return this.entries.role ?? Object.freeze({});
113
+ }
114
+
91
115
  /**
92
116
  * The bare schema qualifier as it would appear in a rendered SQL
93
117
  * fragment (already quoted). The unbound-schema singleton overrides
@@ -247,7 +271,7 @@ export function isPostgresSchema(ns: unknown): ns is PostgresSchema {
247
271
  * by reference and trust the resulting `SqlStorage.namespaces` map to
248
272
  * be value-stable for a given input set.
249
273
  */
250
- export function postgresCreateNamespace(input: SqlNamespaceTablesInput): PostgresSchema {
274
+ export function postgresCreateNamespace(input: SqlNamespaceInput): PostgresSchema {
251
275
  const schemaInput: PostgresSchemaInput = {
252
276
  id: input.id,
253
277
  entries: {
@@ -0,0 +1,20 @@
1
+ import { type } from 'arktype';
2
+
3
+ export const PostgresRoleSchema = type({
4
+ kind: "'role'",
5
+ name: 'string',
6
+ namespaceId: 'string',
7
+ });
8
+
9
+ export const PostgresRlsPolicySchema = type({
10
+ kind: "'policy'",
11
+ name: 'string',
12
+ prefix: 'string',
13
+ tableName: 'string',
14
+ namespaceId: 'string',
15
+ operation: "'select' | 'insert' | 'update' | 'delete' | 'all'",
16
+ roles: type.string.array().readonly(),
17
+ 'using?': 'string',
18
+ 'withCheck?': 'string',
19
+ permissive: 'boolean',
20
+ });