@prisma-next/target-postgres 0.12.0-dev.5 → 0.12.0-dev.51

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 (150) hide show
  1. package/dist/{codec-ids-C5qzBqus.mjs → codec-ids-DTqscwN9.mjs} +3 -2
  2. package/dist/codec-ids-DTqscwN9.mjs.map +1 -0
  3. package/dist/{codec-ids-DliyCWPY.d.mts → codec-ids-FtNuF4-j.d.mts} +3 -2
  4. package/dist/codec-ids-FtNuF4-j.d.mts.map +1 -0
  5. package/dist/codec-ids.d.mts +2 -2
  6. package/dist/codec-ids.mjs +2 -2
  7. package/dist/{codec-types-BF1DXTPs.d.mts → codec-types-dvHkxz5r.d.mts} +2 -2
  8. package/dist/{codec-types-BF1DXTPs.d.mts.map → codec-types-dvHkxz5r.d.mts.map} +1 -1
  9. package/dist/codec-types.d.mts +1 -1
  10. package/dist/{codecs-DuP3d9Au.d.mts → codecs-DTYQIuS9.d.mts} +2 -2
  11. package/dist/codecs-DTYQIuS9.d.mts.map +1 -0
  12. package/dist/codecs.d.mts +1 -1
  13. package/dist/codecs.mjs +37 -2
  14. package/dist/codecs.mjs.map +1 -1
  15. package/dist/contract-free.d.mts +75 -0
  16. package/dist/contract-free.d.mts.map +1 -0
  17. package/dist/contract-free.mjs +139 -0
  18. package/dist/contract-free.mjs.map +1 -0
  19. package/dist/control.d.mts +1 -1
  20. package/dist/control.mjs +60 -34
  21. package/dist/control.mjs.map +1 -1
  22. package/dist/{data-transform-CRkv2T_U.mjs → data-transform-4QL41_vQ.mjs} +1 -1
  23. package/dist/{data-transform-CRkv2T_U.mjs.map → data-transform-4QL41_vQ.mjs.map} +1 -1
  24. package/dist/{data-transform-CAPmAdxS.d.mts → data-transform-DI2h0oin.d.mts} +2 -2
  25. package/dist/{data-transform-CAPmAdxS.d.mts.map → data-transform-DI2h0oin.d.mts.map} +1 -1
  26. package/dist/data-transform.d.mts +1 -1
  27. package/dist/data-transform.mjs +1 -1
  28. package/dist/ddl.d.mts +2 -0
  29. package/dist/ddl.mjs +2 -0
  30. package/dist/{descriptor-meta-Uu8QaClr.mjs → descriptor-meta-C_03xPpF.mjs} +2 -1
  31. package/dist/descriptor-meta-C_03xPpF.mjs.map +1 -0
  32. package/dist/{enum-planning-DRA9LaMU.mjs → enum-planning-BiM4ayjv.mjs} +0 -0
  33. package/dist/{enum-planning-DRA9LaMU.mjs.map → enum-planning-BiM4ayjv.mjs.map} +1 -1
  34. package/dist/enum-planning.d.mts +1 -1
  35. package/dist/enum-planning.mjs +1 -1
  36. package/dist/{errors-BbnITmAD.mjs → errors-BhiZYTWZ.mjs} +1 -1
  37. package/dist/{errors-BbnITmAD.mjs.map → errors-BhiZYTWZ.mjs.map} +1 -1
  38. package/dist/errors.mjs +1 -1
  39. package/dist/{issue-planner-BtaL6OfW.mjs → issue-planner-CSI6tqIH.mjs} +20 -21
  40. package/dist/issue-planner-CSI6tqIH.mjs.map +1 -0
  41. package/dist/issue-planner.d.mts +1 -1
  42. package/dist/issue-planner.d.mts.map +1 -1
  43. package/dist/issue-planner.mjs +1 -1
  44. package/dist/migration.d.mts +3 -3
  45. package/dist/migration.mjs +3 -3
  46. package/dist/nodes-Bh9HwoPz.mjs +42 -0
  47. package/dist/nodes-Bh9HwoPz.mjs.map +1 -0
  48. package/dist/nodes-CpRAsb10.d.mts +38 -0
  49. package/dist/nodes-CpRAsb10.d.mts.map +1 -0
  50. package/dist/{op-factory-call-CDlImOF_.d.mts → op-factory-call-CPgAPA7b.d.mts} +3 -3
  51. package/dist/{op-factory-call-CDlImOF_.d.mts.map → op-factory-call-CPgAPA7b.d.mts.map} +1 -1
  52. package/dist/{op-factory-call-ewOd5q6L.mjs → op-factory-call-iwM_BC7t.mjs} +2 -2
  53. package/dist/{op-factory-call-ewOd5q6L.mjs.map → op-factory-call-iwM_BC7t.mjs.map} +1 -1
  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 +3 -2
  57. package/dist/pack.d.mts.map +1 -1
  58. package/dist/pack.mjs +1 -1
  59. package/dist/planner-DJy7-gh_.mjs +340 -0
  60. package/dist/planner-DJy7-gh_.mjs.map +1 -0
  61. package/dist/{planner-ddl-builders-BNMfSE9r.mjs → planner-ddl-builders-CI0BrI_f.mjs} +2 -2
  62. package/dist/{planner-ddl-builders-BNMfSE9r.mjs.map → planner-ddl-builders-CI0BrI_f.mjs.map} +1 -1
  63. package/dist/planner-ddl-builders.d.mts +1 -1
  64. package/dist/planner-ddl-builders.mjs +1 -1
  65. package/dist/{planner-identity-values-nhVj0hp-.mjs → planner-identity-values-DfiMb2C9.mjs} +1 -1
  66. package/dist/{planner-identity-values-nhVj0hp-.mjs.map → planner-identity-values-DfiMb2C9.mjs.map} +1 -1
  67. package/dist/planner-identity-values.mjs +1 -1
  68. package/dist/{planner-produced-postgres-migration-D02NOhVQ.d.mts → planner-produced-postgres-migration-BRH1Dj_Q.d.mts} +3 -3
  69. package/dist/{planner-produced-postgres-migration-D02NOhVQ.d.mts.map → planner-produced-postgres-migration-BRH1Dj_Q.d.mts.map} +1 -1
  70. package/dist/{planner-produced-postgres-migration-D_nsXbhl.mjs → planner-produced-postgres-migration-x3hafaGC.mjs} +4 -4
  71. package/dist/{planner-produced-postgres-migration-D_nsXbhl.mjs.map → planner-produced-postgres-migration-x3hafaGC.mjs.map} +1 -1
  72. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  73. package/dist/planner-produced-postgres-migration.mjs +1 -1
  74. package/dist/{planner-schema-lookup-CGxxYfnD.mjs → planner-schema-lookup-DLJbkU7-.mjs} +1 -1
  75. package/dist/{planner-schema-lookup-CGxxYfnD.mjs.map → planner-schema-lookup-DLJbkU7-.mjs.map} +1 -1
  76. package/dist/planner-schema-lookup.mjs +1 -1
  77. package/dist/{planner-sql-checks-CfEiTXoQ.mjs → planner-sql-checks-QkUTY_jl.mjs} +38 -11
  78. package/dist/planner-sql-checks-QkUTY_jl.mjs.map +1 -0
  79. package/dist/planner-sql-checks.mjs +1 -1
  80. package/dist/{planner-target-details-a_wuOiYf.d.mts → planner-target-details-C9s0m35x.d.mts} +1 -1
  81. package/dist/{planner-target-details-a_wuOiYf.d.mts.map → planner-target-details-C9s0m35x.d.mts.map} +1 -1
  82. package/dist/planner-target-details.d.mts +1 -1
  83. package/dist/planner.d.mts +3 -2
  84. package/dist/planner.d.mts.map +1 -1
  85. package/dist/planner.mjs +1 -1
  86. package/dist/{postgres-contract-serializer-BnOboPWs.mjs → postgres-contract-serializer-BqsjtSGT.mjs} +32 -23
  87. package/dist/postgres-contract-serializer-BqsjtSGT.mjs.map +1 -0
  88. package/dist/{postgres-enum-type-CSzsvXrO.d.mts → postgres-enum-type-DrjytBxL.d.mts} +1 -1
  89. package/dist/{postgres-enum-type-CSzsvXrO.d.mts.map → postgres-enum-type-DrjytBxL.d.mts.map} +1 -1
  90. package/dist/{postgres-migration-BatbEvU6.mjs → postgres-migration-BDaWGYDu.mjs} +3 -3
  91. package/dist/{postgres-migration-BatbEvU6.mjs.map → postgres-migration-BDaWGYDu.mjs.map} +1 -1
  92. package/dist/{postgres-migration-DRY8V-bQ.d.mts → postgres-migration-CTtX2IXX.d.mts} +3 -3
  93. package/dist/{postgres-migration-DRY8V-bQ.d.mts.map → postgres-migration-CTtX2IXX.d.mts.map} +1 -1
  94. package/dist/{postgres-schema-BxAuNFX0.mjs → postgres-schema-CJH-1qeX.mjs} +27 -12
  95. package/dist/postgres-schema-CJH-1qeX.mjs.map +1 -0
  96. package/dist/{render-ops-XhICjX_P.mjs → render-ops-Da7bQcyv.mjs} +1 -1
  97. package/dist/{render-ops-XhICjX_P.mjs.map → render-ops-Da7bQcyv.mjs.map} +1 -1
  98. package/dist/render-ops.d.mts +1 -1
  99. package/dist/render-ops.mjs +1 -1
  100. package/dist/{render-typescript-K125n-RZ.mjs → render-typescript-DTpTcoNZ.mjs} +1 -1
  101. package/dist/{render-typescript-K125n-RZ.mjs.map → render-typescript-DTpTcoNZ.mjs.map} +1 -1
  102. package/dist/render-typescript.mjs +1 -1
  103. package/dist/runtime.d.mts.map +1 -1
  104. package/dist/runtime.mjs +1 -1
  105. package/dist/{shared-Do_a5ymU.d.mts → shared-CHtU3piZ.d.mts} +2 -2
  106. package/dist/{shared-Do_a5ymU.d.mts.map → shared-CHtU3piZ.d.mts.map} +1 -1
  107. package/dist/{tables-DoA39Yqo.mjs → tables-DhBnvtOj.mjs} +2 -2
  108. package/dist/{tables-DoA39Yqo.mjs.map → tables-DhBnvtOj.mjs.map} +1 -1
  109. package/dist/{types-O40IcFV9.d.mts → types-BFF73W84.d.mts} +1 -1
  110. package/dist/types-BFF73W84.d.mts.map +1 -0
  111. package/dist/types.d.mts +16 -11
  112. package/dist/types.d.mts.map +1 -1
  113. package/dist/types.mjs +1 -1
  114. package/package.json +19 -18
  115. package/src/contract-free/columns.ts +49 -0
  116. package/src/contract-free/control-bootstrap.ts +55 -0
  117. package/src/contract-free/ddl.ts +32 -0
  118. package/src/core/ast/table-source.ts +23 -0
  119. package/src/core/codec-ids.ts +1 -0
  120. package/src/core/codecs.ts +44 -0
  121. package/src/core/ddl/nodes.ts +59 -0
  122. package/src/core/descriptor-meta.ts +1 -0
  123. package/src/core/migrations/control-policy.ts +234 -0
  124. package/src/core/migrations/issue-planner.ts +3 -3
  125. package/src/core/migrations/planner-sql-checks.ts +9 -9
  126. package/src/core/migrations/planner-strategies.ts +16 -10
  127. package/src/core/migrations/planner.ts +55 -6
  128. package/src/core/migrations/runner.ts +78 -50
  129. package/src/core/postgres-contract-serializer.ts +52 -46
  130. package/src/core/postgres-schema.ts +43 -25
  131. package/src/exports/contract-free.ts +7 -0
  132. package/src/exports/ddl.ts +7 -0
  133. package/dist/codec-ids-C5qzBqus.mjs.map +0 -1
  134. package/dist/codec-ids-DliyCWPY.d.mts.map +0 -1
  135. package/dist/codecs-DuP3d9Au.d.mts.map +0 -1
  136. package/dist/descriptor-meta-Uu8QaClr.mjs.map +0 -1
  137. package/dist/issue-planner-BtaL6OfW.mjs.map +0 -1
  138. package/dist/planner-Bjz5pnLa.mjs +0 -177
  139. package/dist/planner-Bjz5pnLa.mjs.map +0 -1
  140. package/dist/planner-sql-checks-CfEiTXoQ.mjs.map +0 -1
  141. package/dist/postgres-contract-serializer-BnOboPWs.mjs.map +0 -1
  142. package/dist/postgres-schema-BxAuNFX0.mjs.map +0 -1
  143. package/dist/statement-builders-DVI5IVAa.mjs +0 -131
  144. package/dist/statement-builders-DVI5IVAa.mjs.map +0 -1
  145. package/dist/statement-builders.d.mts +0 -51
  146. package/dist/statement-builders.d.mts.map +0 -1
  147. package/dist/statement-builders.mjs +0 -2
  148. package/dist/types-O40IcFV9.d.mts.map +0 -1
  149. package/src/core/migrations/statement-builders.ts +0 -183
  150. package/src/exports/statement-builders.ts +0 -8
@@ -71,6 +71,7 @@ import {
71
71
  PG_JSON_CODEC_ID,
72
72
  PG_JSONB_CODEC_ID,
73
73
  PG_NUMERIC_CODEC_ID,
74
+ PG_TEXT_ARRAY_CODEC_ID,
74
75
  PG_TEXT_CODEC_ID,
75
76
  PG_TIME_CODEC_ID,
76
77
  PG_TIMESTAMP_CODEC_ID,
@@ -99,6 +100,7 @@ const precisionParamsSchema = arktype({
99
100
  }) satisfies StandardSchemaV1<PrecisionParams>;
100
101
 
101
102
  const PG_TEXT_META = { db: { sql: { postgres: { nativeType: 'text' } } } } as const;
103
+ const PG_TEXT_ARRAY_META = { db: { sql: { postgres: { nativeType: 'text[]' } } } } as const;
102
104
  const PG_INT4_META = { db: { sql: { postgres: { nativeType: 'integer' } } } } as const;
103
105
  const PG_INT2_META = { db: { sql: { postgres: { nativeType: 'smallint' } } } } as const;
104
106
  const PG_INT8_META = { db: { sql: { postgres: { nativeType: 'bigint' } } } } as const;
@@ -160,6 +162,47 @@ export const pgTextColumn = () =>
160
162
  pgTextColumn satisfies ColumnHelperFor<PgTextDescriptor>;
161
163
  pgTextColumn satisfies ColumnHelperForStrict<PgTextDescriptor>;
162
164
 
165
+ /**
166
+ * Postgres `text[]` codec. Encode is an identity pass-through: the pg wire
167
+ * driver serialises a JS `string[]` to a Postgres array literal under the
168
+ * `$N::text[]` cast the renderer emits from this codec's `text[]` native type,
169
+ * and decode reads it back as a JS array. Used by the control plane to write
170
+ * the marker's `invariants` column. Not a user-facing scalar — it is not part
171
+ * of the authorable `CodecTypes` surface, only the runtime codec registry.
172
+ */
173
+ export class PgTextArrayCodec extends CodecImpl<
174
+ typeof PG_TEXT_ARRAY_CODEC_ID,
175
+ readonly ['equality'],
176
+ readonly string[],
177
+ readonly string[]
178
+ > {
179
+ async encode(value: readonly string[], _ctx: CodecCallContext): Promise<readonly string[]> {
180
+ return value;
181
+ }
182
+ async decode(wire: readonly string[], _ctx: CodecCallContext): Promise<readonly string[]> {
183
+ return wire;
184
+ }
185
+ encodeJson(value: readonly string[]): JsonValue {
186
+ return [...value];
187
+ }
188
+ decodeJson(json: JsonValue): readonly string[] {
189
+ return Array.isArray(json) ? json.map((entry) => String(entry)) : [];
190
+ }
191
+ }
192
+
193
+ export class PgTextArrayDescriptor extends CodecDescriptorImpl<void> {
194
+ override readonly codecId = PG_TEXT_ARRAY_CODEC_ID;
195
+ override readonly traits = ['equality'] as const;
196
+ override readonly targetTypes = ['text[]'] as const;
197
+ override readonly meta = PG_TEXT_ARRAY_META;
198
+ override readonly paramsSchema: StandardSchemaV1<void> = voidParamsSchema;
199
+ override factory(): (ctx: CodecInstanceContext) => PgTextArrayCodec {
200
+ return () => new PgTextArrayCodec(this);
201
+ }
202
+ }
203
+
204
+ export const pgTextArrayDescriptor = new PgTextArrayDescriptor();
205
+
163
206
  export class PgInt4Codec extends CodecImpl<
164
207
  typeof PG_INT4_CODEC_ID,
165
208
  readonly ['equality', 'order', 'numeric'],
@@ -1036,4 +1079,5 @@ export const codecDescriptors: readonly AnyCodecDescriptor[] = [
1036
1079
  pgEnumDescriptor,
1037
1080
  pgJsonDescriptor,
1038
1081
  pgJsonbDescriptor,
1082
+ pgTextArrayDescriptor,
1039
1083
  ];
@@ -0,0 +1,59 @@
1
+ import { type DdlColumn, DdlNode } from '@prisma-next/sql-relational-core/ast';
2
+
3
+ export interface PostgresDdlVisitor<R> {
4
+ createTable(node: PostgresCreateTable): R;
5
+ createSchema(node: PostgresCreateSchema): R;
6
+ }
7
+
8
+ export abstract class PostgresDdlNode extends DdlNode {
9
+ abstract accept<R>(visitor: PostgresDdlVisitor<R>): R;
10
+ }
11
+
12
+ function freezeDdlColumns(columns: readonly DdlColumn[]): ReadonlyArray<DdlColumn> {
13
+ return Object.freeze([...columns]);
14
+ }
15
+
16
+ export class PostgresCreateTable extends PostgresDdlNode {
17
+ readonly kind = 'create-table' as const;
18
+ readonly table: string;
19
+ readonly schema: string | undefined;
20
+ readonly ifNotExists: boolean | undefined;
21
+ readonly columns: ReadonlyArray<DdlColumn>;
22
+
23
+ constructor(options: {
24
+ readonly table: string;
25
+ readonly schema?: string;
26
+ readonly ifNotExists?: boolean;
27
+ readonly columns: readonly DdlColumn[];
28
+ }) {
29
+ super();
30
+ this.table = options.table;
31
+ this.schema = options.schema;
32
+ this.ifNotExists = options.ifNotExists;
33
+ this.columns = freezeDdlColumns(options.columns);
34
+ this.freeze();
35
+ }
36
+
37
+ override accept<R>(visitor: PostgresDdlVisitor<R>): R {
38
+ return visitor.createTable(this);
39
+ }
40
+ }
41
+
42
+ export class PostgresCreateSchema extends PostgresDdlNode {
43
+ readonly kind = 'create-schema' as const;
44
+ readonly schema: string;
45
+ readonly ifNotExists: boolean | undefined;
46
+
47
+ constructor(options: { readonly schema: string; readonly ifNotExists?: boolean }) {
48
+ super();
49
+ this.schema = options.schema;
50
+ this.ifNotExists = options.ifNotExists;
51
+ this.freeze();
52
+ }
53
+
54
+ override accept<R>(visitor: PostgresDdlVisitor<R>): R {
55
+ return visitor.createSchema(this);
56
+ }
57
+ }
58
+
59
+ export type AnyPostgresDdlNode = PostgresCreateTable | PostgresCreateSchema;
@@ -8,6 +8,7 @@ import { postgresTargetDescriptorMetaRuntime } from './descriptor-meta-runtime';
8
8
 
9
9
  const postgresTargetDescriptorMetaBase = {
10
10
  ...postgresTargetDescriptorMetaRuntime,
11
+ defaultNamespaceId: 'public',
11
12
  authoring: {
12
13
  type: postgresAuthoringTypes,
13
14
  field: postgresAuthoringFieldPresets,
@@ -0,0 +1,234 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
2
+ import type { ControlPolicySubject } from '@prisma-next/family-sql/control';
3
+ import type { SchemaIssue } from '@prisma-next/framework-components/control';
4
+ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
5
+ import {
6
+ isPostgresEnumStorageEntry,
7
+ type SqlStorage,
8
+ storageTableAt,
9
+ } from '@prisma-next/sql-contract/types';
10
+ import { ifDefined } from '@prisma-next/utils/defined';
11
+ import { isPostgresSchema } from '../postgres-schema';
12
+ import type { PostgresOpFactoryCall } from './op-factory-call';
13
+
14
+ /**
15
+ * Factory calls that create a whole, previously-absent top-level storage
16
+ * object. Used to decide whether `tolerated` permits a call (it only allows
17
+ * creating absent objects, never modifying existing ones).
18
+ *
19
+ * Deliberately an explicit, closed set rather than a `factoryName`
20
+ * create/alter/drop classification: it answers exactly one yes/no question
21
+ * and is fail-closed. Any call not listed here — including future or
22
+ * extension-contributed factories — is treated as NOT object-creation, so it
23
+ * is suppressed under `tolerated` rather than permissively emitted.
24
+ */
25
+ const OBJECT_CREATION_FACTORIES: ReadonlySet<string> = new Set<string>([
26
+ 'createTable',
27
+ 'createEnumType',
28
+ 'createSchema',
29
+ ]);
30
+
31
+ function createsNewTopLevelObject(call: PostgresOpFactoryCall): boolean {
32
+ return OBJECT_CREATION_FACTORIES.has(call.factoryName);
33
+ }
34
+
35
+ function ddlSchemaNameForNamespace(contract: Contract<SqlStorage>, namespaceId: string): string {
36
+ const namespace = contract.storage.namespaces[namespaceId];
37
+ return isPostgresSchema(namespace) ? namespace.ddlSchemaName(contract.storage) : namespaceId;
38
+ }
39
+
40
+ function resolveNamespaceIdForTable(
41
+ contract: Contract<SqlStorage>,
42
+ tableName: string,
43
+ ddlSchemaName: string | undefined,
44
+ ): string {
45
+ for (const namespaceId of Object.keys(contract.storage.namespaces)) {
46
+ const table = storageTableAt(contract.storage, namespaceId, tableName);
47
+ if (!table) continue;
48
+ if (
49
+ ddlSchemaName === undefined ||
50
+ ddlSchemaNameForNamespace(contract, namespaceId) === ddlSchemaName
51
+ ) {
52
+ return namespaceId;
53
+ }
54
+ }
55
+ return UNBOUND_NAMESPACE_ID;
56
+ }
57
+
58
+ function resolveNamespaceIdForDdlSchema(
59
+ contract: Contract<SqlStorage>,
60
+ ddlSchemaName: string,
61
+ ): string {
62
+ for (const namespaceId of Object.keys(contract.storage.namespaces)) {
63
+ const ns = contract.storage.namespaces[namespaceId];
64
+ if (isPostgresSchema(ns) && ns.ddlSchemaName(contract.storage) === ddlSchemaName) {
65
+ return namespaceId;
66
+ }
67
+ if (namespaceId === ddlSchemaName) {
68
+ return namespaceId;
69
+ }
70
+ }
71
+ return UNBOUND_NAMESPACE_ID;
72
+ }
73
+
74
+ interface PostgresCallFields {
75
+ readonly schemaName?: string;
76
+ readonly tableName?: string;
77
+ readonly columnName?: string;
78
+ readonly typeName?: string;
79
+ }
80
+
81
+ function postgresCallFields(call: PostgresOpFactoryCall): PostgresCallFields {
82
+ return {
83
+ ...ifDefined('schemaName', 'schemaName' in call ? call.schemaName : undefined),
84
+ ...ifDefined('tableName', 'tableName' in call ? call.tableName : undefined),
85
+ ...ifDefined('columnName', 'columnName' in call ? call.columnName : undefined),
86
+ ...ifDefined('typeName', 'typeName' in call ? call.typeName : undefined),
87
+ };
88
+ }
89
+
90
+ export function formatPostgresControlPolicySubjectLabel(
91
+ factoryName: string,
92
+ subject: ControlPolicySubject | undefined,
93
+ contract: Contract<SqlStorage>,
94
+ ): string {
95
+ if (subject?.table) {
96
+ const ddlSchema = ddlSchemaNameForNamespace(contract, subject.namespaceId);
97
+ return `${factoryName}(${ddlSchema}.${subject.table})`;
98
+ }
99
+ if (subject?.typeName) {
100
+ const ddlSchema = ddlSchemaNameForNamespace(contract, subject.namespaceId);
101
+ return `${factoryName}(${ddlSchema}.${subject.typeName})`;
102
+ }
103
+ return factoryName;
104
+ }
105
+
106
+ export function resolvePostgresCallControlPolicySubject(
107
+ call: PostgresOpFactoryCall,
108
+ contract: Contract<SqlStorage>,
109
+ ): ControlPolicySubject | undefined {
110
+ const callFields = postgresCallFields(call);
111
+ const createsNewObject = createsNewTopLevelObject(call);
112
+
113
+ if (call.factoryName === 'createSchema' && callFields.schemaName) {
114
+ return {
115
+ namespaceId: resolveNamespaceIdForDdlSchema(contract, callFields.schemaName),
116
+ createsNewObject,
117
+ };
118
+ }
119
+
120
+ if (callFields.typeName && call.factoryName !== 'addColumn') {
121
+ const namespaceId = callFields.schemaName
122
+ ? resolveNamespaceIdForDdlSchema(contract, callFields.schemaName)
123
+ : UNBOUND_NAMESPACE_ID;
124
+ const ns = contract.storage.namespaces[namespaceId];
125
+ const rawEnum = isPostgresSchema(ns) ? ns.entries.type[callFields.typeName] : undefined;
126
+ const controlPolicy = isPostgresEnumStorageEntry(rawEnum) ? rawEnum.control : undefined;
127
+ return {
128
+ namespaceId,
129
+ ...ifDefined('explicitNodeControlPolicy', controlPolicy),
130
+ typeName: callFields.typeName,
131
+ createsNewObject,
132
+ };
133
+ }
134
+
135
+ if (callFields.tableName) {
136
+ const namespaceId = resolveNamespaceIdForTable(
137
+ contract,
138
+ callFields.tableName,
139
+ callFields.schemaName,
140
+ );
141
+ const table = storageTableAt(contract.storage, namespaceId, callFields.tableName);
142
+ const tableControlPolicy = table?.control;
143
+ return {
144
+ namespaceId,
145
+ ...ifDefined('explicitNodeControlPolicy', tableControlPolicy),
146
+ table: callFields.tableName,
147
+ ...ifDefined('column', callFields.columnName),
148
+ createsNewObject,
149
+ };
150
+ }
151
+
152
+ if (callFields.schemaName) {
153
+ return {
154
+ namespaceId: resolveNamespaceIdForDdlSchema(contract, callFields.schemaName),
155
+ createsNewObject,
156
+ };
157
+ }
158
+
159
+ return undefined;
160
+ }
161
+
162
+ /**
163
+ * Issue kinds that describe the absence of a whole, top-level Postgres
164
+ * object — the same kinds `createsNewTopLevelObject` recognises for calls.
165
+ * Used by {@link resolvePostgresIssueCreationFactoryName} to decide whether
166
+ * a `tolerated` subject permits the issue to flow into the planner
167
+ * (create-if-absent) and to seed the suppressed-subject warning's
168
+ * `factoryName` when the planner is skipped.
169
+ */
170
+ const POSTGRES_ISSUE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.freeze({
171
+ missing_schema: 'createSchema',
172
+ missing_table: 'createTable',
173
+ type_missing: 'createEnumType',
174
+ });
175
+
176
+ export function resolvePostgresIssueCreationFactoryName(issue: SchemaIssue): string | undefined {
177
+ return POSTGRES_ISSUE_CREATION_FACTORY[issue.kind];
178
+ }
179
+
180
+ /**
181
+ * Resolve the control-policy subject coordinate for a single
182
+ * {@link SchemaIssue}. Mirrors the resolution `resolvePostgresCallControlPolicySubject`
183
+ * performs for a generated DDL call, but works *off the issue* — so the
184
+ * planner can partition issues by effective policy before the diff engine
185
+ * runs. `createsNewObject` is derived from the issue's kind: schema/table/
186
+ * type-missing issues describe a brand-new top-level object; everything else
187
+ * touches an existing object.
188
+ *
189
+ * An `extra_table` issue carries no contract namespace coordinate (the table
190
+ * isn't in any contract namespace), so the subject's `namespaceId` falls
191
+ * back to {@link UNBOUND_NAMESPACE_ID}; the call-side resolver does the same
192
+ * for the `DropTableCall` it produces.
193
+ */
194
+ export function resolvePostgresIssueControlPolicySubject(
195
+ issue: SchemaIssue,
196
+ contract: Contract<SqlStorage>,
197
+ ): ControlPolicySubject | undefined {
198
+ const createsNewObject = POSTGRES_ISSUE_CREATION_FACTORY[issue.kind] !== undefined;
199
+
200
+ if (issue.kind === 'missing_schema' && issue.namespaceId) {
201
+ return { namespaceId: issue.namespaceId, createsNewObject };
202
+ }
203
+
204
+ if ('typeName' in issue && issue.typeName) {
205
+ const namespaceId =
206
+ 'namespaceId' in issue && issue.namespaceId ? issue.namespaceId : UNBOUND_NAMESPACE_ID;
207
+ const ns = contract.storage.namespaces[namespaceId];
208
+ const rawEnum = isPostgresSchema(ns) ? ns.entries.type[issue.typeName] : undefined;
209
+ const controlPolicy = isPostgresEnumStorageEntry(rawEnum) ? rawEnum.control : undefined;
210
+ return {
211
+ namespaceId,
212
+ ...ifDefined('explicitNodeControlPolicy', controlPolicy),
213
+ typeName: issue.typeName,
214
+ createsNewObject,
215
+ };
216
+ }
217
+
218
+ if ('table' in issue && issue.table) {
219
+ const namespaceId =
220
+ 'namespaceId' in issue && issue.namespaceId
221
+ ? issue.namespaceId
222
+ : resolveNamespaceIdForTable(contract, issue.table, undefined);
223
+ const table = storageTableAt(contract.storage, namespaceId, issue.table);
224
+ return {
225
+ namespaceId,
226
+ ...ifDefined('explicitNodeControlPolicy', table?.control),
227
+ table: issue.table,
228
+ ...ifDefined('column', 'column' in issue ? issue.column : undefined),
229
+ createsNewObject,
230
+ };
231
+ }
232
+
233
+ return undefined;
234
+ }
@@ -29,6 +29,7 @@ import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
29
29
  import type { Result } from '@prisma-next/utils/result';
30
30
  import { notOk, ok } from '@prisma-next/utils/result';
31
31
  import { PostgresEnumType } from '../postgres-enum-type';
32
+ import { isPostgresSchema } from '../postgres-schema';
32
33
  import {
33
34
  AddColumnCall,
34
35
  AddForeignKeyCall,
@@ -73,8 +74,7 @@ function locateNamespaceTypeInStorage(
73
74
  typeName: string,
74
75
  ): unknown {
75
76
  const ns = storage.namespaces[namespaceId];
76
- if (!ns || !('enum' in ns) || ns.enum == null) return undefined;
77
- return (ns.enum as Record<string, unknown>)[typeName];
77
+ return isPostgresSchema(ns) ? ns.entries.type[typeName] : undefined;
78
78
  }
79
79
 
80
80
  // ============================================================================
@@ -601,7 +601,7 @@ function mapIssueToCall(
601
601
  case 'type_missing': {
602
602
  if (!issue.typeName)
603
603
  return notOk(issueConflict('unsupportedOperation', 'Type missing issue has no typeName'));
604
- // Codec aliases live in storage.types; enum types live in namespace.enum.
604
+ // Codec aliases live in storage.types; enum types live in namespace.entries.type.
605
605
  // Check types first; fall back to the namespace-keyed enum slot using the
606
606
  // issue's namespace coordinate (populated by the verifier for enum-related
607
607
  // issues per the BaseSchemaIssue.namespaceId contract).
@@ -20,11 +20,11 @@ import { resolveColumnTypeMetadata } from './planner-type-resolution';
20
20
  * site.
21
21
  */
22
22
  export function qualifyTableName(schema: string, table: string): string {
23
- return postgresCreateNamespace({ id: schema }).qualifyTable(table);
23
+ return postgresCreateNamespace({ id: schema, entries: { table: {} } }).qualifyTable(table);
24
24
  }
25
25
 
26
26
  export function toRegclassLiteral(schema: string, name: string): string {
27
- return postgresCreateNamespace({ id: schema }).regclassLiteral(name);
27
+ return postgresCreateNamespace({ id: schema, entries: { table: {} } }).regclassLiteral(name);
28
28
  }
29
29
 
30
30
  /**
@@ -43,7 +43,7 @@ export function constraintExistsCheck({
43
43
  table?: string;
44
44
  exists?: boolean;
45
45
  }): string {
46
- const namespace = postgresCreateNamespace({ id: schema });
46
+ const namespace = postgresCreateNamespace({ id: schema, entries: { table: {} } });
47
47
  const existsClause = exists ? 'EXISTS' : 'NOT EXISTS';
48
48
  const tableFilter = table
49
49
  ? `AND c.conrelid = to_regclass(${namespace.regclassLiteral(table)})`
@@ -68,7 +68,7 @@ export function columnExistsCheck({
68
68
  column: string;
69
69
  exists?: boolean;
70
70
  }): string {
71
- const namespace = postgresCreateNamespace({ id: schema });
71
+ const namespace = postgresCreateNamespace({ id: schema, entries: { table: {} } });
72
72
  const existsClause = exists ? '' : 'NOT ';
73
73
  return `SELECT ${existsClause}EXISTS (
74
74
  SELECT 1
@@ -90,7 +90,7 @@ export function columnNullabilityCheck({
90
90
  column: string;
91
91
  nullable: boolean;
92
92
  }): string {
93
- const namespace = postgresCreateNamespace({ id: schema });
93
+ const namespace = postgresCreateNamespace({ id: schema, entries: { table: {} } });
94
94
  const expected = nullable ? 'YES' : 'NO';
95
95
  return `SELECT EXISTS (
96
96
  SELECT 1
@@ -111,7 +111,7 @@ export function columnHasNoDefaultCheck(opts: {
111
111
  table: string;
112
112
  column: string;
113
113
  }): string {
114
- const namespace = postgresCreateNamespace({ id: opts.schema });
114
+ const namespace = postgresCreateNamespace({ id: opts.schema, entries: { table: {} } });
115
115
  return `SELECT NOT EXISTS (
116
116
  SELECT 1
117
117
  FROM information_schema.columns
@@ -282,7 +282,7 @@ export function columnTypeCheck({
282
282
  column: string;
283
283
  expectedType: string;
284
284
  }): string {
285
- const namespace = postgresCreateNamespace({ id: schema });
285
+ const namespace = postgresCreateNamespace({ id: schema, entries: { table: {} } });
286
286
  return `SELECT EXISTS (
287
287
  SELECT 1
288
288
  FROM pg_attribute a
@@ -307,7 +307,7 @@ export function columnDefaultExistsCheck({
307
307
  column: string;
308
308
  exists?: boolean;
309
309
  }): string {
310
- const namespace = postgresCreateNamespace({ id: schema });
310
+ const namespace = postgresCreateNamespace({ id: schema, entries: { table: {} } });
311
311
  const nullCheck = exists ? 'IS NOT NULL' : 'IS NULL';
312
312
  return `SELECT EXISTS (
313
313
  SELECT 1
@@ -325,7 +325,7 @@ export function tableHasPrimaryKeyCheck(
325
325
  exists: boolean,
326
326
  constraintName?: string,
327
327
  ): string {
328
- const namespace = postgresCreateNamespace({ id: schema });
328
+ const namespace = postgresCreateNamespace({ id: schema, entries: { table: {} } });
329
329
  const comparison = exists ? '' : 'NOT ';
330
330
  const constraintFilter = constraintName
331
331
  ? `AND c2.relname = '${escapeLiteral(constraintName)}'`
@@ -35,6 +35,7 @@ import {
35
35
  type StorageTypeInstance,
36
36
  } from '@prisma-next/sql-contract/types';
37
37
  import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
38
+ import { blindCast } from '@prisma-next/utils/casts';
38
39
  import { PostgresEnumType } from '../postgres-enum-type';
39
40
  import { isPostgresSchema } from '../postgres-schema';
40
41
  import {
@@ -93,7 +94,7 @@ export function tableAt(
93
94
  ): StorageTable | undefined {
94
95
  // Namespace.tables is typed as Record<string, IRNode> at the interface level;
95
96
  // SQL family namespaces always hold StorageTable instances.
96
- return storage.namespaces[namespaceId]?.tables[tableName] as StorageTable | undefined;
97
+ return storage.namespaces[namespaceId]?.entries.table[tableName] as StorageTable | undefined;
97
98
  }
98
99
 
99
100
  /**
@@ -134,8 +135,8 @@ const DEFAULT_ENUM_NAMESPACE_ID = 'public';
134
135
 
135
136
  function namespaceHasEnum(storage: SqlStorage, namespaceId: string, typeName: string): boolean {
136
137
  const ns = storage.namespaces[namespaceId];
137
- if (!ns || !('enum' in ns) || ns.enum == null) return false;
138
- return (ns.enum as Record<string, PostgresEnumStorageEntry>)[typeName] !== undefined;
138
+ if (!isPostgresSchema(ns)) return false;
139
+ return ns.entries.type[typeName] !== undefined;
139
140
  }
140
141
 
141
142
  /**
@@ -166,7 +167,7 @@ function resolveColumnEnumNamespace(
166
167
 
167
168
  /**
168
169
  * Finds a type entry by explicit namespace coordinate. Namespace types (e.g.
169
- * Postgres enums) live under `storage.namespaces[nsId].enum`. Returns the
170
+ * Postgres enums) live under `storage.namespaces[nsId].entries.type`. Returns the
170
171
  * entry from the named namespace only — never scans other namespaces, so two
171
172
  * namespaces that hold an enum with the same name resolve independently.
172
173
  */
@@ -176,8 +177,12 @@ function locateNamespaceType(
176
177
  typeName: string,
177
178
  ): PostgresEnumStorageEntry | undefined {
178
179
  const ns = storage.namespaces[namespaceId];
179
- if (!ns || !('enum' in ns) || ns.enum == null) return undefined;
180
- return (ns.enum as Record<string, PostgresEnumStorageEntry>)[typeName];
180
+ const raw = ns?.entries['type']?.[typeName];
181
+ if (raw === undefined) return undefined;
182
+ return blindCast<
183
+ PostgresEnumStorageEntry,
184
+ 'postgres type slot carries PostgresEnumStorageEntry at the postgres target layer'
185
+ >(raw);
181
186
  }
182
187
 
183
188
  // ============================================================================
@@ -436,7 +441,7 @@ function enumRebuildCallRecipe(
436
441
  // same-named enums in distinct namespaces keep their columns disjoint.
437
442
  const columnRefs: { namespaceId: string; table: string; column: string }[] = [];
438
443
  for (const [nsId, ns] of Object.entries(ctx.toContract.storage.namespaces)) {
439
- for (const [tableName, tableNode] of Object.entries(ns.tables)) {
444
+ for (const [tableName, tableNode] of Object.entries(ns.entries.table)) {
440
445
  const table = tableNode as StorageTable;
441
446
  for (const [columnName, column] of Object.entries(table.columns)) {
442
447
  if (
@@ -639,9 +644,10 @@ export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) =
639
644
  function collectPostgresEnumTypes(storage: SqlStorage): ReadonlyMap<string, PostgresEnumType> {
640
645
  const result = new Map<string, PostgresEnumType>();
641
646
  for (const [nsId, ns] of Object.entries(storage.namespaces)) {
642
- if (!('enum' in ns) || ns.enum == null) continue;
643
- const nsEnums = ns.enum as Record<string, unknown>;
644
- for (const [name, instance] of Object.entries(nsEnums).sort(([a], [b]) => a.localeCompare(b))) {
647
+ if (!isPostgresSchema(ns)) continue;
648
+ for (const [name, instance] of Object.entries(ns.entries.type).sort(([a], [b]) =>
649
+ a.localeCompare(b),
650
+ )) {
645
651
  if (instance instanceof PostgresEnumType) {
646
652
  result.set(enumCompoundKey(nsId, name), instance);
647
653
  }
@@ -2,10 +2,13 @@ import type { Contract } from '@prisma-next/contract/types';
2
2
  import type {
3
3
  MigrationOperationPolicy,
4
4
  SqlMigrationPlannerPlanOptions,
5
+ SqlPlannerConflict,
5
6
  SqlPlannerFailureResult,
6
7
  } from '@prisma-next/family-sql/control';
7
8
  import {
8
9
  extractCodecControlHooks,
10
+ partitionCallsByControlPolicy,
11
+ partitionIssuesByControlPolicy,
9
12
  planFieldEventOperations,
10
13
  plannerFailure,
11
14
  } from '@prisma-next/family-sql/control';
@@ -18,10 +21,18 @@ import type {
18
21
  SchemaIssue,
19
22
  } from '@prisma-next/framework-components/control';
20
23
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
24
+ import { blindCast } from '@prisma-next/utils/casts';
21
25
  import { parsePostgresDefault } from '../default-normalizer';
22
26
  import { normalizeSchemaNativeType } from '../native-type-normalizer';
27
+ import {
28
+ formatPostgresControlPolicySubjectLabel,
29
+ resolvePostgresCallControlPolicySubject,
30
+ resolvePostgresIssueControlPolicySubject,
31
+ resolvePostgresIssueCreationFactoryName,
32
+ } from './control-policy';
23
33
  import { createResolveExistingEnumValues } from './enum-planning';
24
34
  import { planIssues } from './issue-planner';
35
+ import type { PostgresOpFactoryCall } from './op-factory-call';
25
36
  import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
26
37
  import { postgresPlannerStrategies } from './planner-strategies';
27
38
  import { verifyPostgresNamespacePresence } from './verify-postgres-namespaces';
@@ -52,7 +63,11 @@ export function createPostgresMigrationPlanner(): PostgresMigrationPlanner {
52
63
  * uniformly.
53
64
  */
54
65
  export type PostgresPlanResult =
55
- | { readonly kind: 'success'; readonly plan: TypeScriptRenderablePostgresMigration }
66
+ | {
67
+ readonly kind: 'success';
68
+ readonly plan: TypeScriptRenderablePostgresMigration;
69
+ readonly warnings?: readonly SqlPlannerConflict[];
70
+ }
56
71
  | SqlPlannerFailureResult;
57
72
 
58
73
  /**
@@ -131,8 +146,25 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
131
146
  const codecHooks = extractCodecControlHooks(options.frameworkComponents);
132
147
  const storageTypes = options.contract.storage.types ?? {};
133
148
 
134
- const result = planIssues({
149
+ // Input-side control-policy partition. `external` / `observed` subjects
150
+ // — and non-creation issues for `tolerated` subjects — are dropped from
151
+ // the planner's input entirely; the planner never observes them, never
152
+ // diffs them, never generates DDL for them. Suppression warnings are
153
+ // built directly from the suppressed partition (one per subject), so the
154
+ // user-visible message survives even when the planner would have failed
155
+ // to model the subject's live shape.
156
+ const issuePartition = partitionIssuesByControlPolicy({
135
157
  issues: schemaIssues,
158
+ contract: options.contract,
159
+ resolveControlPolicySubject: (issue) =>
160
+ resolvePostgresIssueControlPolicySubject(issue, options.contract),
161
+ resolveCreationFactoryName: resolvePostgresIssueCreationFactoryName,
162
+ formatSubjectLabel: (factoryName, subject) =>
163
+ formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
164
+ });
165
+
166
+ const result = planIssues({
167
+ issues: issuePartition.plannable,
136
168
  toContract: options.contract,
137
169
  // `fromContract` is only supplied by `migration plan`. It is `null` for
138
170
  // `db update` / `db init`, which means data-safety strategies needing
@@ -163,10 +195,26 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
163
195
  newContract: options.contract,
164
196
  codecHooks,
165
197
  });
166
- // Codec-emitted calls already conform to `OpFactoryCall` render +
167
- // toOp + importRequirements ride directly through the same emit path
168
- // as structural ops, no `RawSqlCall` wrap.
169
- const calls = [...result.value.calls, ...fieldEventOps];
198
+ // Codec hook ops are target-agnostic `OpFactoryCall`; Postgres planning
199
+ // lifts them at this integration boundary (see field-event-planner JSDoc).
200
+ const fieldEventPostgresCalls = blindCast<
201
+ readonly PostgresOpFactoryCall[],
202
+ 'Codec hook ops conform to PostgresOpFactoryCall at the app emitter boundary'
203
+ >(fieldEventOps);
204
+ const fieldEventPartition = partitionCallsByControlPolicy({
205
+ calls: fieldEventPostgresCalls,
206
+ contract: options.contract,
207
+ resolveControlPolicySubject: (call) =>
208
+ resolvePostgresCallControlPolicySubject(call, options.contract),
209
+ resolveFactoryName: (call) => call.factoryName,
210
+ formatSubjectLabel: (factoryName, subject) =>
211
+ formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
212
+ });
213
+ const calls = [...result.value.calls, ...fieldEventPartition.kept];
214
+ const warnings: SqlPlannerConflict[] = [
215
+ ...issuePartition.warnings,
216
+ ...fieldEventPartition.warnings,
217
+ ];
170
218
 
171
219
  return Object.freeze({
172
220
  kind: 'success' as const,
@@ -178,6 +226,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
178
226
  },
179
227
  options.spaceId,
180
228
  ),
229
+ ...(warnings.length > 0 ? { warnings: Object.freeze(warnings) } : {}),
181
230
  });
182
231
  }
183
232