@prisma-next/target-postgres 0.8.0 → 0.9.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{codec-ids-CplrEfmx.d.mts → codec-ids-RvYfmUmi.d.mts} +1 -1
- package/dist/{codec-ids-CplrEfmx.d.mts.map → codec-ids-RvYfmUmi.d.mts.map} +1 -1
- package/dist/codec-ids.d.mts +1 -1
- package/dist/{codec-types-lrsb3N07.d.mts → codec-types-667FxIW8.d.mts} +2 -2
- package/dist/{codec-types-lrsb3N07.d.mts.map → codec-types-667FxIW8.d.mts.map} +1 -1
- package/dist/codec-types.d.mts +1 -1
- package/dist/{codecs-Cue97Xqf.d.mts → codecs-DXeDABSO.d.mts} +2 -2
- package/dist/{codecs-Cue97Xqf.d.mts.map → codecs-DXeDABSO.d.mts.map} +1 -1
- package/dist/codecs.d.mts +1 -1
- package/dist/control.d.mts +2 -2
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +38 -7
- package/dist/control.mjs.map +1 -1
- package/dist/{data-transform-bIeAcZIJ.d.mts → data-transform-B6p02mFJ.d.mts} +3 -3
- package/dist/{data-transform-bIeAcZIJ.d.mts.map → data-transform-B6p02mFJ.d.mts.map} +1 -1
- package/dist/{data-transform-DKWXdHuZ.mjs → data-transform-aF9az88u.mjs} +1 -1
- package/dist/{data-transform-DKWXdHuZ.mjs.map → data-transform-aF9az88u.mjs.map} +1 -1
- package/dist/data-transform.d.mts +1 -1
- package/dist/data-transform.mjs +1 -1
- package/dist/{default-normalizer-C8XyZj85.mjs → default-normalizer-DHCsbfjc.mjs} +1 -1
- package/dist/{default-normalizer-C8XyZj85.mjs.map → default-normalizer-DHCsbfjc.mjs.map} +1 -1
- package/dist/default-normalizer.mjs +1 -1
- package/dist/descriptor-meta-DFUCClk_.mjs +124 -0
- package/dist/descriptor-meta-DFUCClk_.mjs.map +1 -0
- package/dist/enum-planning-Bqp96iIw.mjs +63 -0
- package/dist/enum-planning-Bqp96iIw.mjs.map +1 -0
- package/dist/enum-planning.d.mts +48 -0
- package/dist/enum-planning.d.mts.map +1 -0
- package/dist/enum-planning.mjs +2 -0
- package/dist/{errors-Chm2bKcS.mjs → errors-BiOloWUh.mjs} +1 -1
- package/dist/{errors-Chm2bKcS.mjs.map → errors-BiOloWUh.mjs.map} +1 -1
- package/dist/errors.mjs +1 -1
- package/dist/{issue-planner-DQ6WJkad.mjs → issue-planner-BhWVYyE1.mjs} +114 -49
- package/dist/issue-planner-BhWVYyE1.mjs.map +1 -0
- package/dist/issue-planner.d.mts +11 -9
- package/dist/issue-planner.d.mts.map +1 -1
- package/dist/issue-planner.mjs +1 -1
- package/dist/migration.d.mts +4 -4
- package/dist/migration.d.mts.map +1 -1
- package/dist/migration.mjs +3 -3
- package/dist/{native-type-normalizer-Cry4QoLf.mjs → native-type-normalizer-DMikJJ1V.mjs} +1 -1
- package/dist/{native-type-normalizer-Cry4QoLf.mjs.map → native-type-normalizer-DMikJJ1V.mjs.map} +1 -1
- package/dist/native-type-normalizer.mjs +1 -1
- package/dist/{op-factory-call-DeaFxa8_.mjs → op-factory-call-DerP9BoT.mjs} +8 -5
- package/dist/{op-factory-call-DeaFxa8_.mjs.map → op-factory-call-DerP9BoT.mjs.map} +1 -1
- package/dist/{op-factory-call-UFpUPJL6.d.mts → op-factory-call-c1zELk3U.d.mts} +5 -4
- package/dist/{op-factory-call-UFpUPJL6.d.mts.map → op-factory-call-c1zELk3U.d.mts.map} +1 -1
- package/dist/op-factory-call.d.mts +1 -1
- package/dist/op-factory-call.mjs +1 -1
- package/dist/pack.d.mts +13 -24
- package/dist/pack.d.mts.map +1 -1
- package/dist/pack.mjs +1 -1
- package/dist/{planner-CYtKhLYa.mjs → planner-DSDXUbQ4.mjs} +8 -6
- package/dist/planner-DSDXUbQ4.mjs.map +1 -0
- package/dist/{planner-ddl-builders-CLB7Umhh.mjs → planner-ddl-builders-5QIyhBUF.mjs} +3 -3
- package/dist/planner-ddl-builders-5QIyhBUF.mjs.map +1 -0
- package/dist/planner-ddl-builders.d.mts +5 -5
- package/dist/planner-ddl-builders.d.mts.map +1 -1
- package/dist/planner-ddl-builders.mjs +1 -1
- package/dist/{planner-identity-values-DTx0gePL.mjs → planner-identity-values-BUYNOCwb.mjs} +9 -3
- package/dist/planner-identity-values-BUYNOCwb.mjs.map +1 -0
- package/dist/planner-identity-values.d.mts +1 -1
- package/dist/planner-identity-values.d.mts.map +1 -1
- package/dist/planner-identity-values.mjs +1 -1
- package/dist/{planner-produced-postgres-migration-CjxWIVgh.d.mts → planner-produced-postgres-migration-D34ftfEK.d.mts} +3 -3
- package/dist/{planner-produced-postgres-migration-CjxWIVgh.d.mts.map → planner-produced-postgres-migration-D34ftfEK.d.mts.map} +1 -1
- package/dist/{planner-produced-postgres-migration-DphktB2N.mjs → planner-produced-postgres-migration-D8OCSSLM.mjs} +4 -4
- package/dist/{planner-produced-postgres-migration-DphktB2N.mjs.map → planner-produced-postgres-migration-D8OCSSLM.mjs.map} +1 -1
- package/dist/planner-produced-postgres-migration.d.mts +1 -1
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/{planner-schema-lookup-B1ags8ys.mjs → planner-schema-lookup--u9whY_Y.mjs} +1 -1
- package/dist/{planner-schema-lookup-B1ags8ys.mjs.map → planner-schema-lookup--u9whY_Y.mjs.map} +1 -1
- package/dist/planner-schema-lookup.mjs +1 -1
- package/dist/{planner-sql-checks-DwZvGlV4.mjs → planner-sql-checks-Cd016Ycs.mjs} +7 -2
- package/dist/planner-sql-checks-Cd016Ycs.mjs.map +1 -0
- package/dist/planner-sql-checks.d.mts +2 -2
- package/dist/planner-sql-checks.d.mts.map +1 -1
- package/dist/planner-sql-checks.mjs +1 -1
- package/dist/{planner-target-details-bVVcanWh.d.mts → planner-target-details-iYJwzFHP.d.mts} +1 -1
- package/dist/{planner-target-details-bVVcanWh.d.mts.map → planner-target-details-iYJwzFHP.d.mts.map} +1 -1
- package/dist/planner-target-details.d.mts +1 -1
- package/dist/planner.d.mts +1 -1
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +1 -1
- package/dist/postgres-contract-serializer-D5VJk6lo.mjs +61 -0
- package/dist/postgres-contract-serializer-D5VJk6lo.mjs.map +1 -0
- package/dist/postgres-enum-type-CrKq8au9.d.mts +69 -0
- package/dist/postgres-enum-type-CrKq8au9.d.mts.map +1 -0
- package/dist/postgres-enum-type-DS-KLVRH.mjs +61 -0
- package/dist/postgres-enum-type-DS-KLVRH.mjs.map +1 -0
- package/dist/{postgres-migration-UkcHfZAA.d.mts → postgres-migration-CiQzhcMe.d.mts} +4 -4
- package/dist/{postgres-migration-UkcHfZAA.d.mts.map → postgres-migration-CiQzhcMe.d.mts.map} +1 -1
- package/dist/{postgres-migration-Bkv140RW.mjs → postgres-migration-Fdxzo6l2.mjs} +3 -3
- package/dist/{postgres-migration-Bkv140RW.mjs.map → postgres-migration-Fdxzo6l2.mjs.map} +1 -1
- package/dist/{render-ops--1nnfNus.mjs → render-ops-CkiuHSNj.mjs} +1 -1
- package/dist/{render-ops--1nnfNus.mjs.map → render-ops-CkiuHSNj.mjs.map} +1 -1
- package/dist/render-ops.d.mts +1 -1
- package/dist/render-ops.mjs +1 -1
- package/dist/{render-typescript-D3doH-vX.mjs → render-typescript-C9XWI8Ld.mjs} +1 -1
- package/dist/{render-typescript-D3doH-vX.mjs.map → render-typescript-C9XWI8Ld.mjs.map} +1 -1
- package/dist/render-typescript.mjs +1 -1
- package/dist/runtime.d.mts +25 -1
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +3 -2
- package/dist/runtime.mjs.map +1 -1
- package/dist/{shared-MpwjwAjM.d.mts → shared-DLYdmYo-.d.mts} +2 -2
- package/dist/{shared-MpwjwAjM.d.mts.map → shared-DLYdmYo-.d.mts.map} +1 -1
- package/dist/{sql-utils-CggjWNij.mjs → sql-utils-BewXAnsG.mjs} +1 -1
- package/dist/{sql-utils-CggjWNij.mjs.map → sql-utils-BewXAnsG.mjs.map} +1 -1
- package/dist/sql-utils.mjs +1 -1
- package/dist/{statement-builders-BT889jV0.mjs → statement-builders-BSIQMClE.mjs} +1 -1
- package/dist/{statement-builders-BT889jV0.mjs.map → statement-builders-BSIQMClE.mjs.map} +1 -1
- package/dist/statement-builders.mjs +1 -1
- package/dist/{tables-DgYIXjUt.mjs → tables-Ce_Q0I8B.mjs} +7 -7
- package/dist/{tables-DgYIXjUt.mjs.map → tables-Ce_Q0I8B.mjs.map} +1 -1
- package/dist/{types-CTqpysRY.d.mts → types-Dq74Z3eu.d.mts} +1 -1
- package/dist/types-Dq74Z3eu.d.mts.map +1 -0
- package/dist/types.d.mts +3 -2
- package/dist/types.mjs +2 -1
- package/package.json +18 -17
- package/src/core/authoring.ts +41 -9
- package/src/core/descriptor-meta.ts +6 -1
- package/src/core/migrations/enum-planning.ts +93 -0
- package/src/core/migrations/issue-planner.ts +25 -13
- package/src/core/migrations/op-factory-call.ts +12 -3
- package/src/core/migrations/operations/enums.ts +5 -4
- package/src/core/migrations/planner-ddl-builders.ts +4 -3
- package/src/core/migrations/planner-identity-values.ts +28 -4
- package/src/core/migrations/planner-recipes.ts +6 -2
- package/src/core/migrations/planner-sql-checks.ts +6 -2
- package/src/core/migrations/planner-strategies.ts +187 -74
- package/src/core/migrations/planner-type-resolution.ts +20 -2
- package/src/core/migrations/planner.ts +3 -0
- package/src/core/migrations/runner.ts +3 -0
- package/src/core/postgres-contract-serializer.ts +70 -0
- package/src/core/postgres-enum-type.ts +85 -0
- package/src/core/postgres-schema-verifier.ts +37 -0
- package/src/exports/control.ts +13 -1
- package/src/exports/enum-planning.ts +6 -0
- package/src/exports/runtime.ts +2 -0
- package/src/exports/types.ts +1 -0
- package/dist/descriptor-meta-Dde_BS3K.mjs +0 -99
- package/dist/descriptor-meta-Dde_BS3K.mjs.map +0 -1
- package/dist/issue-planner-DQ6WJkad.mjs.map +0 -1
- package/dist/planner-CYtKhLYa.mjs.map +0 -1
- package/dist/planner-ddl-builders-CLB7Umhh.mjs.map +0 -1
- package/dist/planner-identity-values-DTx0gePL.mjs.map +0 -1
- package/dist/planner-sql-checks-DwZvGlV4.mjs.map +0 -1
- package/dist/types-CTqpysRY.d.mts.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CodecControlHooks } from '@prisma-next/family-sql/control';
|
|
2
2
|
import type {
|
|
3
3
|
ForeignKey,
|
|
4
|
+
PostgresEnumStorageEntry,
|
|
4
5
|
ReferentialAction,
|
|
5
6
|
StorageColumn,
|
|
6
7
|
StorageTable,
|
|
@@ -15,7 +16,7 @@ export function buildCreateTableSql(
|
|
|
15
16
|
qualifiedTableName: string,
|
|
16
17
|
table: StorageTable,
|
|
17
18
|
codecHooks: Map<string, CodecControlHooks>,
|
|
18
|
-
storageTypes: Record<string, StorageTypeInstance> = {},
|
|
19
|
+
storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = {},
|
|
19
20
|
): string {
|
|
20
21
|
const columnDefinitions = Object.entries(table.columns).map(
|
|
21
22
|
([columnName, column]: [string, StorageColumn]) => {
|
|
@@ -80,7 +81,7 @@ function assertSafeDefaultExpression(expression: string): void {
|
|
|
80
81
|
export function buildColumnTypeSql(
|
|
81
82
|
column: StorageColumn,
|
|
82
83
|
codecHooks: Map<string, CodecControlHooks>,
|
|
83
|
-
storageTypes: Record<string, StorageTypeInstance> = {},
|
|
84
|
+
storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = {},
|
|
84
85
|
allowPseudoTypes = true,
|
|
85
86
|
): string {
|
|
86
87
|
const resolved = resolveColumnTypeMetadata(column, storageTypes);
|
|
@@ -201,7 +202,7 @@ export function buildAddColumnSql(
|
|
|
201
202
|
column: StorageColumn,
|
|
202
203
|
codecHooks: Map<string, CodecControlHooks>,
|
|
203
204
|
temporaryDefault?: string | null,
|
|
204
|
-
storageTypes: Record<string, StorageTypeInstance> = {},
|
|
205
|
+
storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = {},
|
|
205
206
|
): string {
|
|
206
207
|
const typeSql = buildColumnTypeSql(column, codecHooks, storageTypes);
|
|
207
208
|
const defaultSql =
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { CodecControlHooks } from '@prisma-next/family-sql/control';
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
isPostgresEnumStorageEntry,
|
|
4
|
+
type PostgresEnumStorageEntry,
|
|
5
|
+
type StorageColumn,
|
|
6
|
+
type StorageTypeInstance,
|
|
7
|
+
} from '@prisma-next/sql-contract/types';
|
|
3
8
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
9
|
+
import type { PostgresEnumType } from '../postgres-enum-type';
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Resolves the identity value (monoid neutral element) as a SQL literal for a column's type.
|
|
@@ -10,12 +16,30 @@ import { ifDefined } from '@prisma-next/utils/defined';
|
|
|
10
16
|
export function resolveIdentityValue(
|
|
11
17
|
column: StorageColumn,
|
|
12
18
|
codecHooks: Map<string, CodecControlHooks>,
|
|
13
|
-
storageTypes: Record<string, StorageTypeInstance> = {},
|
|
19
|
+
storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = {},
|
|
14
20
|
): string | null {
|
|
15
21
|
const referencedType = column.typeRef ? storageTypes[column.typeRef] : undefined;
|
|
16
|
-
const
|
|
22
|
+
const referencedIsEnum =
|
|
23
|
+
referencedType !== undefined && isPostgresEnumStorageEntry(referencedType);
|
|
24
|
+
const referencedBinding = referencedIsEnum
|
|
25
|
+
? ((referencedType as PostgresEnumType).codecBinding ?? {
|
|
26
|
+
codecId: (referencedType as PostgresEnumStorageEntry).codecId,
|
|
27
|
+
typeParams: { values: (referencedType as PostgresEnumStorageEntry).values },
|
|
28
|
+
})
|
|
29
|
+
: undefined;
|
|
30
|
+
const codecId =
|
|
31
|
+
referencedBinding?.codecId ??
|
|
32
|
+
(referencedType && !referencedIsEnum
|
|
33
|
+
? (referencedType as StorageTypeInstance).codecId
|
|
34
|
+
: undefined) ??
|
|
35
|
+
column.codecId;
|
|
17
36
|
const nativeType = referencedType?.nativeType ?? column.nativeType;
|
|
18
|
-
const typeParams =
|
|
37
|
+
const typeParams =
|
|
38
|
+
(referencedBinding?.typeParams as Record<string, unknown> | undefined) ??
|
|
39
|
+
(referencedType && !referencedIsEnum
|
|
40
|
+
? (referencedType as StorageTypeInstance).typeParams
|
|
41
|
+
: undefined) ??
|
|
42
|
+
column.typeParams;
|
|
19
43
|
|
|
20
44
|
if (codecId) {
|
|
21
45
|
const hookDefault = codecHooks.get(codecId)?.resolveIdentityValue?.({
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { CodecControlHooks, SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
PostgresEnumStorageEntry,
|
|
4
|
+
StorageColumn,
|
|
5
|
+
StorageTypeInstance,
|
|
6
|
+
} from '@prisma-next/sql-contract/types';
|
|
3
7
|
import { quoteIdentifier } from '../sql-utils';
|
|
4
8
|
import { buildAddColumnSql } from './planner-ddl-builders';
|
|
5
9
|
import {
|
|
@@ -35,7 +39,7 @@ export function buildAddNotNullColumnWithTemporaryDefaultOperation(options: {
|
|
|
35
39
|
readonly columnName: string;
|
|
36
40
|
readonly column: StorageColumn;
|
|
37
41
|
readonly codecHooks: Map<string, CodecControlHooks>;
|
|
38
|
-
readonly storageTypes: Record<string, StorageTypeInstance>;
|
|
42
|
+
readonly storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
|
|
39
43
|
readonly temporaryDefault: string;
|
|
40
44
|
}): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
|
|
41
45
|
const { schema, tableName, columnName, column, codecHooks, storageTypes, temporaryDefault } =
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { CodecControlHooks } from '@prisma-next/family-sql/control';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
PostgresEnumStorageEntry,
|
|
4
|
+
StorageColumn,
|
|
5
|
+
StorageTypeInstance,
|
|
6
|
+
} from '@prisma-next/sql-contract/types';
|
|
3
7
|
import { escapeLiteral, quoteIdentifier } from '../sql-utils';
|
|
4
8
|
import { resolveColumnTypeMetadata } from './planner-type-resolution';
|
|
5
9
|
|
|
@@ -230,7 +234,7 @@ function formatUserDefinedTypeName(identifier: string): string {
|
|
|
230
234
|
export function buildExpectedFormatType(
|
|
231
235
|
column: StorageColumn,
|
|
232
236
|
codecHooks: Map<string, CodecControlHooks>,
|
|
233
|
-
storageTypes: Record<string, StorageTypeInstance> = {},
|
|
237
|
+
storageTypes: Record<string, StorageTypeInstance | PostgresEnumStorageEntry> = {},
|
|
234
238
|
): string {
|
|
235
239
|
const resolved = resolveColumnTypeMetadata(column, storageTypes);
|
|
236
240
|
|
|
@@ -26,8 +26,15 @@ import type {
|
|
|
26
26
|
} from '@prisma-next/family-sql/control';
|
|
27
27
|
import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
|
|
28
28
|
import type { SchemaIssue } from '@prisma-next/framework-components/control';
|
|
29
|
-
import
|
|
29
|
+
import {
|
|
30
|
+
isPostgresEnumStorageEntry,
|
|
31
|
+
type PostgresEnumStorageEntry,
|
|
32
|
+
type SqlStorage,
|
|
33
|
+
type StorageTypeInstance,
|
|
34
|
+
} from '@prisma-next/sql-contract/types';
|
|
30
35
|
import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
|
|
36
|
+
import { PostgresEnumType } from '../postgres-enum-type';
|
|
37
|
+
import { determineEnumDiff, readExistingEnumValues } from './enum-planning';
|
|
31
38
|
import {
|
|
32
39
|
AddColumnCall,
|
|
33
40
|
AddEnumValuesCall,
|
|
@@ -80,7 +87,7 @@ export interface StrategyContext {
|
|
|
80
87
|
readonly fromContract: Contract<SqlStorage> | null;
|
|
81
88
|
readonly schemaName: string;
|
|
82
89
|
readonly codecHooks: ReadonlyMap<string, CodecControlHooks>;
|
|
83
|
-
readonly storageTypes: Readonly<Record<string, StorageTypeInstance>>;
|
|
90
|
+
readonly storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
|
|
84
91
|
readonly schema: SqlSchemaIR;
|
|
85
92
|
readonly policy: MigrationOperationPolicy;
|
|
86
93
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
@@ -101,10 +108,12 @@ export type CallMigrationStrategy = (
|
|
|
101
108
|
/**
|
|
102
109
|
* `true` for strategies that emit cohesive sequential recipes whose
|
|
103
110
|
* calls must stay contiguous and in the returned order — e.g.
|
|
104
|
-
* `
|
|
105
|
-
* dropEnumType
|
|
106
|
-
*
|
|
107
|
-
*
|
|
111
|
+
* `nativeEnumPlanCallStrategy` (createEnumType → alterColumnType →
|
|
112
|
+
* dropEnumType → renameType, optionally prefixed by a
|
|
113
|
+
* `DataTransformCall` placeholder), `notNullBackfillCallStrategy`
|
|
114
|
+
* (addColumn → dataTransform → setNotNull). Defaults to `false`,
|
|
115
|
+
* which lets `planIssues` hoist individual calls into their DDL
|
|
116
|
+
* sequencing bucket.
|
|
108
117
|
*/
|
|
109
118
|
recipe?: boolean;
|
|
110
119
|
}
|
|
@@ -119,7 +128,10 @@ function buildColumnSpec(
|
|
|
119
128
|
const col = ctx.toContract.storage.tables[table]?.columns[column];
|
|
120
129
|
if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
|
|
121
130
|
const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
|
|
122
|
-
const mutableTypes = ctx.storageTypes as Record<
|
|
131
|
+
const mutableTypes = ctx.storageTypes as Record<
|
|
132
|
+
string,
|
|
133
|
+
StorageTypeInstance | PostgresEnumStorageEntry
|
|
134
|
+
>;
|
|
123
135
|
return {
|
|
124
136
|
name: column,
|
|
125
137
|
typeSql: buildColumnTypeSql(col, mutableHooks, mutableTypes),
|
|
@@ -137,7 +149,10 @@ function buildAlterTypeOptions(
|
|
|
137
149
|
const col = ctx.toContract.storage.tables[table]?.columns[column];
|
|
138
150
|
if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
|
|
139
151
|
const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
|
|
140
|
-
const mutableTypes = ctx.storageTypes as Record<
|
|
152
|
+
const mutableTypes = ctx.storageTypes as Record<
|
|
153
|
+
string,
|
|
154
|
+
StorageTypeInstance | PostgresEnumStorageEntry
|
|
155
|
+
>;
|
|
141
156
|
const qualifiedTargetType = buildColumnTypeSql(col, mutableHooks, mutableTypes, false);
|
|
142
157
|
const formatTypeExpected = buildExpectedFormatType(col, mutableHooks, mutableTypes);
|
|
143
158
|
return {
|
|
@@ -276,8 +291,11 @@ function enumRebuildCallRecipe(
|
|
|
276
291
|
): readonly PostgresOpFactoryCall[] {
|
|
277
292
|
const toType = ctx.toContract.storage.types?.[typeName];
|
|
278
293
|
if (!toType) return [];
|
|
294
|
+
const isEnum = isPostgresEnumStorageEntry(toType);
|
|
279
295
|
const nativeType = toType.nativeType;
|
|
280
|
-
const desiredValues
|
|
296
|
+
const desiredValues: readonly string[] = isEnum
|
|
297
|
+
? toType.values
|
|
298
|
+
: (((toType as StorageTypeInstance).typeParams['values'] ?? []) as readonly string[]);
|
|
281
299
|
const tempName = `${nativeType}${REBUILD_SUFFIX}`;
|
|
282
300
|
|
|
283
301
|
const columnRefs: { table: string; column: string }[] = [];
|
|
@@ -305,70 +323,149 @@ function enumRebuildCallRecipe(
|
|
|
305
323
|
];
|
|
306
324
|
}
|
|
307
325
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// excludes `'data'` (`db update` / `db init`), skip the entire strategy so
|
|
312
|
-
// `storageTypePlanCallStrategy` (codec-hook driven) takes over with the
|
|
313
|
-
// dev-push enum behavior.
|
|
314
|
-
if (!ctx.policy.allowedOperationClasses.includes('data')) return { kind: 'no_match' };
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Native enum planner strategy
|
|
328
|
+
// ============================================================================
|
|
315
329
|
|
|
316
|
-
|
|
317
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Single planner strategy for `PostgresEnumType` instances. Walks
|
|
332
|
+
* `toContract.storage.types` directly (no codec-hook dispatch) and
|
|
333
|
+
* resolves existing values via `readExistingEnumValues`, the same
|
|
334
|
+
* Postgres bridging adapter the verifier uses.
|
|
335
|
+
*
|
|
336
|
+
* Per-enum dispatch:
|
|
337
|
+
*
|
|
338
|
+
* - No existing type → `CreateEnumTypeCall` with the contract's desired
|
|
339
|
+
* values.
|
|
340
|
+
* - Diff is `unchanged` → no calls emitted (consumes the matching
|
|
341
|
+
* `enum_values_changed` issue if present).
|
|
342
|
+
* - Diff is `add_values` → `AddEnumValuesCall` with the new labels.
|
|
343
|
+
* - Diff is `rebuild` → the create-temp / migrate-columns /
|
|
344
|
+
* drop-original / rename rebuild recipe. When
|
|
345
|
+
* `policy.allowedOperationClasses` includes `'data'` and the rebuild
|
|
346
|
+
* removes labels (`removedValues.length > 0`), prepend a
|
|
347
|
+
* `DataTransformCall` placeholder so the user can author the value
|
|
348
|
+
* remap before the destructive recipe runs. Without `'data'` in the
|
|
349
|
+
* policy (`db update` / `db init`), the rebuild's PG `USING ::text`
|
|
350
|
+
* cast surfaces any value-removal data loss as a runtime error rather
|
|
351
|
+
* than silent loss.
|
|
352
|
+
*
|
|
353
|
+
* Returns `recipe: true` only when a rebuild recipe was emitted (its
|
|
354
|
+
* `createEnumType(temp) → alterColumnType → dropEnumType(orig) →
|
|
355
|
+
* renameType` sequence mixes `dep`-class and `alter`-class calls that
|
|
356
|
+
* would mis-order if the planner hoisted them into its DDL sequencing
|
|
357
|
+
* buckets). For the create-only and add-values paths the strategy
|
|
358
|
+
* returns `recipe: false` so the planner hoists `CreateEnumTypeCall`
|
|
359
|
+
* into the `dep` bucket — i.e. `CREATE TYPE` runs before any
|
|
360
|
+
* `CreateTableCall` that references the new enum.
|
|
361
|
+
*/
|
|
362
|
+
export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
363
|
+
const enumTypes = collectPostgresEnumTypes(ctx.toContract.storage.types);
|
|
364
|
+
if (enumTypes.size === 0) return { kind: 'no_match' };
|
|
318
365
|
|
|
319
|
-
|
|
320
|
-
if (issue.kind !== 'enum_values_changed') continue;
|
|
321
|
-
matched.push(issue);
|
|
366
|
+
const dataAllowed = ctx.policy.allowedOperationClasses.includes('data');
|
|
322
367
|
|
|
323
|
-
|
|
368
|
+
const calls: PostgresOpFactoryCall[] = [];
|
|
369
|
+
const handledTypeNames = new Set<string>();
|
|
370
|
+
const introducedTypeNames = new Set<string>();
|
|
371
|
+
const rebuiltTypeNames = new Set<string>();
|
|
372
|
+
let emittedRebuildRecipe = false;
|
|
373
|
+
|
|
374
|
+
for (const [typeName, enumType] of enumTypes) {
|
|
375
|
+
const desired = enumType.values;
|
|
376
|
+
const existing = readExistingEnumValues(ctx.schema, enumType.nativeType);
|
|
377
|
+
if (!existing) {
|
|
378
|
+
calls.push(new CreateEnumTypeCall(ctx.schemaName, typeName, desired, enumType.nativeType));
|
|
379
|
+
handledTypeNames.add(typeName);
|
|
380
|
+
introducedTypeNames.add(typeName);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const diff = determineEnumDiff(existing, desired);
|
|
384
|
+
if (diff.kind === 'unchanged') {
|
|
385
|
+
handledTypeNames.add(typeName);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (diff.kind === 'add_values') {
|
|
389
|
+
calls.push(new AddEnumValuesCall(ctx.schemaName, typeName, enumType.nativeType, diff.values));
|
|
390
|
+
handledTypeNames.add(typeName);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (dataAllowed && diff.removedValues.length > 0) {
|
|
324
394
|
calls.push(
|
|
325
395
|
new DataTransformCall(
|
|
326
|
-
`migrate-${
|
|
327
|
-
`migrate-${
|
|
328
|
-
`migrate-${
|
|
396
|
+
`migrate-${typeName}-values`,
|
|
397
|
+
`migrate-${typeName}-values:check`,
|
|
398
|
+
`migrate-${typeName}-values:run`,
|
|
329
399
|
),
|
|
330
|
-
...enumRebuildCallRecipe(issue.typeName, ctx),
|
|
331
400
|
);
|
|
332
|
-
} else if (issue.addedValues.length === 0) {
|
|
333
|
-
calls.push(...enumRebuildCallRecipe(issue.typeName, ctx));
|
|
334
|
-
} else {
|
|
335
|
-
const toType = ctx.toContract.storage.types?.[issue.typeName];
|
|
336
|
-
if (toType) {
|
|
337
|
-
calls.push(
|
|
338
|
-
new AddEnumValuesCall(
|
|
339
|
-
ctx.schemaName,
|
|
340
|
-
issue.typeName,
|
|
341
|
-
toType.nativeType,
|
|
342
|
-
issue.addedValues,
|
|
343
|
-
),
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
401
|
}
|
|
402
|
+
calls.push(...enumRebuildCallRecipe(typeName, ctx));
|
|
403
|
+
emittedRebuildRecipe = true;
|
|
404
|
+
handledTypeNames.add(typeName);
|
|
405
|
+
rebuiltTypeNames.add(typeName);
|
|
347
406
|
}
|
|
348
407
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
408
|
+
// The strategy emits a single `recipe` flag for the entire pass,
|
|
409
|
+
// which routes every emitted call to either the contiguous recipe
|
|
410
|
+
// slot (rebuild path) or the `dep` bucket (introduce / add-values
|
|
411
|
+
// path). A plan that needs both shapes simultaneously cannot be
|
|
412
|
+
// expressed today — the introduced `CreateEnumTypeCall` would land
|
|
413
|
+
// in the recipe slot and any `CreateTableCall` referencing the new
|
|
414
|
+
// enum would fail at runtime with a confusing `type "X" does not
|
|
415
|
+
// exist` error. Surface the unrepresentable case here as a
|
|
416
|
+
// planner-time error so the failure mode is loud, not silent.
|
|
417
|
+
if (introducedTypeNames.size > 0 && rebuiltTypeNames.size > 0) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`nativeEnumPlanCallStrategy: cannot emit both a brand-new enum and a rebuild on a different enum in the same plan; the single recipe flag cannot route them to different buckets. Introduced: [${[...introducedTypeNames].sort().join(', ')}]; rebuilt: [${[...rebuiltTypeNames].sort().join(', ')}]. Split the strategy or grow the \`match\` return type before this case lands.`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const remaining = issues.filter(
|
|
424
|
+
(issue) =>
|
|
425
|
+
!(
|
|
426
|
+
(issue.kind === 'type_missing' || issue.kind === 'enum_values_changed') &&
|
|
427
|
+
issue.typeName &&
|
|
428
|
+
handledTypeNames.has(issue.typeName)
|
|
429
|
+
),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
if (calls.length === 0 && remaining.length === issues.length) {
|
|
433
|
+
return { kind: 'no_match' };
|
|
434
|
+
}
|
|
435
|
+
// `recipe: true` is required for the rebuild path — its
|
|
436
|
+
// `createEnumType(temp) → alterColumnType → dropEnumType(orig) →
|
|
437
|
+
// renameType` mixes `dep`-class and `alter`-class calls that would
|
|
438
|
+
// mis-order if the planner hoisted them into its DDL sequencing
|
|
439
|
+
// buckets. For the type_missing / add_values paths we want the
|
|
440
|
+
// opposite: hoisted into the `dep` bucket so a brand-new
|
|
441
|
+
// `CreateEnumTypeCall` runs *before* the `CreateTableCall` that
|
|
442
|
+
// references it. The two cases never co-occur in the same plan
|
|
443
|
+
// (introducing a new enum type and rebuilding an existing one in
|
|
444
|
+
// one shot would require both buckets — a shape today's interface
|
|
445
|
+
// does not surface; if that combination ever needs to land we'd
|
|
446
|
+
// split this strategy or grow the `match` return type).
|
|
447
|
+
return { kind: 'match', issues: remaining, calls, recipe: emittedRebuildRecipe };
|
|
356
448
|
};
|
|
357
449
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
450
|
+
function collectPostgresEnumTypes(
|
|
451
|
+
storageTypes: SqlStorage['types'],
|
|
452
|
+
): ReadonlyMap<string, PostgresEnumType> {
|
|
453
|
+
const result = new Map<string, PostgresEnumType>();
|
|
454
|
+
for (const [name, instance] of Object.entries(storageTypes ?? {}).sort(([a], [b]) =>
|
|
455
|
+
a.localeCompare(b),
|
|
456
|
+
)) {
|
|
457
|
+
if (instance instanceof PostgresEnumType) {
|
|
458
|
+
result.set(name, instance);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
361
463
|
|
|
362
464
|
/**
|
|
363
|
-
* Dispatches storage types through their codec's
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
* Runs after `enumChangeCallStrategy` so the structured enum path (value
|
|
369
|
-
* add, rebuild recipe) gets first pick at `enum_values_changed` issues;
|
|
370
|
-
* this strategy then handles remaining `type_missing` / `enum_values_changed`
|
|
371
|
-
* issues for types whose hook produced at least one op.
|
|
465
|
+
* Dispatches non-enum codec-typed storage types through their codec's
|
|
466
|
+
* `planTypeOperations` hook (the authoritative source for codec-driven DDL
|
|
467
|
+
* such as custom type creation). Enum dispatch lives in
|
|
468
|
+
* `nativeEnumPlanCallStrategy` and no longer relies on codec hooks.
|
|
372
469
|
*/
|
|
373
470
|
export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
|
|
374
471
|
const storageTypes = ctx.toContract.storage.types ?? {};
|
|
@@ -380,11 +477,16 @@ export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
380
477
|
for (const [typeName, typeInstance] of Object.entries(storageTypes).sort(([a], [b]) =>
|
|
381
478
|
a.localeCompare(b),
|
|
382
479
|
)) {
|
|
383
|
-
|
|
480
|
+
// Enums walk natively in `nativeEnumPlanCallStrategy`; codec-hook
|
|
481
|
+
// dispatch here is reserved for genuinely codec-typed entries
|
|
482
|
+
// (decimal, varchar, pgvector, …).
|
|
483
|
+
if (isPostgresEnumStorageEntry(typeInstance)) continue;
|
|
484
|
+
const codecInstance = typeInstance as StorageTypeInstance;
|
|
485
|
+
const hook = ctx.codecHooks.get(codecInstance.codecId);
|
|
384
486
|
if (!hook?.planTypeOperations) continue;
|
|
385
487
|
const planResult = hook.planTypeOperations({
|
|
386
488
|
typeName,
|
|
387
|
-
typeInstance,
|
|
489
|
+
typeInstance: codecInstance,
|
|
388
490
|
contract: ctx.toContract,
|
|
389
491
|
schema: ctx.schema,
|
|
390
492
|
schemaName: ctx.schemaName,
|
|
@@ -446,7 +548,10 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
|
|
|
446
548
|
const schemaLookups = buildSchemaLookupMap(ctx.schema);
|
|
447
549
|
|
|
448
550
|
const mutableCodecHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
|
|
449
|
-
const mutableStorageTypes = ctx.storageTypes as Record<
|
|
551
|
+
const mutableStorageTypes = ctx.storageTypes as Record<
|
|
552
|
+
string,
|
|
553
|
+
StorageTypeInstance | PostgresEnumType
|
|
554
|
+
>;
|
|
450
555
|
|
|
451
556
|
for (const issue of issues) {
|
|
452
557
|
if (issue.kind !== 'missing_column' || !issue.table || !issue.column) continue;
|
|
@@ -597,25 +702,33 @@ function canUseSharedTemporaryDefaultStrategy(options: {
|
|
|
597
702
|
* `policy.allowedOperationClasses`:
|
|
598
703
|
*
|
|
599
704
|
* - When `'data'` is allowed (`migration plan`), the data-safe strategies
|
|
600
|
-
* (`
|
|
601
|
-
* `
|
|
602
|
-
* matching issues and emit
|
|
705
|
+
* (`notNullBackfillCallStrategy`, `typeChangeCallStrategy`,
|
|
706
|
+
* `nullableTighteningCallStrategy`) and the enum walk
|
|
707
|
+
* (`nativeEnumPlanCallStrategy`) consume their matching issues and emit
|
|
708
|
+
* `DataTransformCall` placeholders or recipe ops.
|
|
603
709
|
*
|
|
604
|
-
* - When `'data'` is not allowed (`db update` / `db init`),
|
|
605
|
-
*
|
|
606
|
-
* downstream walk-schema strategies
|
|
607
|
-
* `notNullAddColumnCallStrategy`) or the
|
|
608
|
-
* with direct DDL.
|
|
710
|
+
* - When `'data'` is not allowed (`db update` / `db init`), the
|
|
711
|
+
* placeholder-emitting strategies short-circuit to `no_match`, leaving
|
|
712
|
+
* the issue for the downstream walk-schema strategies
|
|
713
|
+
* (`storageTypePlanCallStrategy`, `notNullAddColumnCallStrategy`) or the
|
|
714
|
+
* `mapIssueToCall` default to handle with direct DDL.
|
|
715
|
+
* `nativeEnumPlanCallStrategy` runs in both modes; under `db update` /
|
|
716
|
+
* `db init` it emits the rebuild recipe without the data-transform
|
|
717
|
+
* placeholder so value-removal data loss surfaces as a runtime cast
|
|
718
|
+
* error rather than silent loss.
|
|
609
719
|
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
* `
|
|
720
|
+
* Enum dispatch is unified into a single strategy: the
|
|
721
|
+
* `nativeEnumPlanCallStrategy` decides per-emission whether to emit a
|
|
722
|
+
* rebuild recipe (`recipe: true`, contiguous slot) or hoist the call
|
|
723
|
+
* into the `dep` bucket (`recipe: false`, so a brand-new
|
|
724
|
+
* `CreateEnumTypeCall` runs before any `CreateTableCall` referencing
|
|
725
|
+
* it). Codec-typed entries continue through `storageTypePlanCallStrategy`.
|
|
613
726
|
*/
|
|
614
727
|
export const postgresPlannerStrategies: readonly CallMigrationStrategy[] = [
|
|
615
|
-
enumChangeCallStrategy,
|
|
616
728
|
notNullBackfillCallStrategy,
|
|
617
729
|
typeChangeCallStrategy,
|
|
618
730
|
nullableTighteningCallStrategy,
|
|
731
|
+
nativeEnumPlanCallStrategy,
|
|
619
732
|
storageTypePlanCallStrategy,
|
|
620
733
|
notNullAddColumnCallStrategy,
|
|
621
734
|
];
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
isPostgresEnumStorageEntry,
|
|
3
|
+
type PostgresEnumStorageEntry,
|
|
4
|
+
type StorageColumn,
|
|
5
|
+
type StorageTypeInstance,
|
|
6
|
+
} from '@prisma-next/sql-contract/types';
|
|
2
7
|
|
|
3
8
|
export type ResolvedColumnTypeMetadata = Pick<
|
|
4
9
|
StorageColumn,
|
|
@@ -7,7 +12,7 @@ export type ResolvedColumnTypeMetadata = Pick<
|
|
|
7
12
|
|
|
8
13
|
export function resolveColumnTypeMetadata(
|
|
9
14
|
column: StorageColumn,
|
|
10
|
-
storageTypes: Record<string, StorageTypeInstance
|
|
15
|
+
storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>,
|
|
11
16
|
): ResolvedColumnTypeMetadata {
|
|
12
17
|
if (!column.typeRef) {
|
|
13
18
|
return column;
|
|
@@ -18,6 +23,19 @@ export function resolveColumnTypeMetadata(
|
|
|
18
23
|
return column;
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
if (isPostgresEnumStorageEntry(referencedType)) {
|
|
27
|
+
// Enum types are referenced by name (`quoteIdentifier(nativeType)`),
|
|
28
|
+
// not via parameterised codec expansion. The structural shape
|
|
29
|
+
// carries `codecId` as an enumerable property (mirroring the
|
|
30
|
+
// codec-typed view); `typeParams` is intentionally omitted here so
|
|
31
|
+
// `expandParameterizedTypeSql` does not try to look up a
|
|
32
|
+
// (deliberately absent) `expandNativeType` hook for `pg/enum@*`.
|
|
33
|
+
return {
|
|
34
|
+
codecId: referencedType.codecId,
|
|
35
|
+
nativeType: referencedType.nativeType,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
return {
|
|
22
40
|
codecId: referencedType.codecId,
|
|
23
41
|
nativeType: referencedType.nativeType,
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
} from '@prisma-next/framework-components/control';
|
|
20
20
|
import { parsePostgresDefault } from '../default-normalizer';
|
|
21
21
|
import { normalizeSchemaNativeType } from '../native-type-normalizer';
|
|
22
|
+
import { readExistingEnumValues } from './enum-planning';
|
|
22
23
|
import { planIssues } from './issue-planner';
|
|
23
24
|
import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
|
|
24
25
|
import { postgresPlannerStrategies } from './planner-strategies';
|
|
@@ -217,6 +218,8 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
217
218
|
frameworkComponents: options.frameworkComponents,
|
|
218
219
|
normalizeDefault: parsePostgresDefault,
|
|
219
220
|
normalizeNativeType: normalizeSchemaNativeType,
|
|
221
|
+
resolveExistingEnumValues: (schema, enumType) =>
|
|
222
|
+
readExistingEnumValues(schema, enumType.nativeType),
|
|
220
223
|
};
|
|
221
224
|
const verifyResult = verifySqlSchema(verifyOptions);
|
|
222
225
|
return verifyResult.schema.issues;
|
|
@@ -22,6 +22,7 @@ import type { Result } from '@prisma-next/utils/result';
|
|
|
22
22
|
import { notOk, ok, okVoid } from '@prisma-next/utils/result';
|
|
23
23
|
import { parsePostgresDefault } from '../default-normalizer';
|
|
24
24
|
import { normalizeSchemaNativeType } from '../native-type-normalizer';
|
|
25
|
+
import { readExistingEnumValues } from './enum-planning';
|
|
25
26
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
26
27
|
import {
|
|
27
28
|
buildLedgerInsertStatement,
|
|
@@ -187,6 +188,8 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
187
188
|
frameworkComponents: options.frameworkComponents,
|
|
188
189
|
normalizeDefault: parsePostgresDefault,
|
|
189
190
|
normalizeNativeType: normalizeSchemaNativeType,
|
|
191
|
+
resolveExistingEnumValues: (schema, enumType) =>
|
|
192
|
+
readExistingEnumValues(schema, enumType.nativeType),
|
|
190
193
|
});
|
|
191
194
|
if (!schemaVerifyResult.ok) {
|
|
192
195
|
return runnerFailure('SCHEMA_VERIFY_FAILED', schemaVerifyResult.summary, {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
2
|
+
import {
|
|
3
|
+
SqlContractSerializerBase,
|
|
4
|
+
type SqlEntityHydrationFactory,
|
|
5
|
+
} from '@prisma-next/family-sql/ir';
|
|
6
|
+
import type { AuthoringEntityContext } from '@prisma-next/framework-components/authoring';
|
|
7
|
+
import type { SqlStorage, SqlStorageTypeEntry } from '@prisma-next/sql-contract/types';
|
|
8
|
+
import { postgresAuthoringEntityTypes } from './authoring';
|
|
9
|
+
import { PostgresEnumType } from './postgres-enum-type';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build the hydration registry from this target pack's literal
|
|
13
|
+
* `postgresAuthoringEntityTypes`. Extension-pack-contributed entity
|
|
14
|
+
* types do not reach this registry today; the surface is honest for
|
|
15
|
+
* in-tree consumers (Postgres pack only) and the slot stays
|
|
16
|
+
* deserializable because the family-layer validator's
|
|
17
|
+
* `StorageTypeEntrySchema` only admits kinds whose factory the
|
|
18
|
+
* Postgres pack already ships.
|
|
19
|
+
*
|
|
20
|
+
* Future open (F14): lift the registry build to descriptor-composition
|
|
21
|
+
* time, threading the composed `AuthoringContributions.entityTypes`
|
|
22
|
+
* from extension packs, so a real extension pack shipping a
|
|
23
|
+
* round-trip-needing entity type can be deserialized end-to-end.
|
|
24
|
+
* Earned by the first such extension pack in tree.
|
|
25
|
+
*/
|
|
26
|
+
function buildPostgresEntityTypeRegistry(): ReadonlyMap<string, SqlEntityHydrationFactory> {
|
|
27
|
+
const ctx: AuthoringEntityContext = { family: 'sql', target: 'postgres' };
|
|
28
|
+
const registry = new Map<string, SqlEntityHydrationFactory>();
|
|
29
|
+
for (const descriptor of Object.values(postgresAuthoringEntityTypes)) {
|
|
30
|
+
if (descriptor.kind !== 'entity') {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (!('factory' in descriptor.output)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const factory = descriptor.output.factory as (
|
|
37
|
+
input: never,
|
|
38
|
+
ctx: AuthoringEntityContext,
|
|
39
|
+
) => SqlStorageTypeEntry;
|
|
40
|
+
registry.set(descriptor.discriminator, (entry) => {
|
|
41
|
+
if (entry instanceof PostgresEnumType) {
|
|
42
|
+
return entry;
|
|
43
|
+
}
|
|
44
|
+
return factory(entry as never, ctx);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return registry;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Postgres target `ContractSerializer` concretion. Inherits the full
|
|
52
|
+
* SQL-family deserialization pipeline (structural validation +
|
|
53
|
+
* hydration walker that materialises the SQL Contract IR class
|
|
54
|
+
* hierarchy from the validated JSON envelope). Polymorphic
|
|
55
|
+
* `storage.types` entries hydrate through the pack contribution registry
|
|
56
|
+
* keyed by each entity type's declared `discriminator` (matching the
|
|
57
|
+
* enumerable `kind` on the persisted JSON envelope).
|
|
58
|
+
*
|
|
59
|
+
* `serializeContract` falls through to the family-base default —
|
|
60
|
+
* Postgres' contract is JSON-clean today (`PostgresEnumType`
|
|
61
|
+
* instances are frozen with enumerable own properties, so
|
|
62
|
+
* `JSON.stringify` produces the canonical envelope shape). Once
|
|
63
|
+
* target-only fields land (e.g. per-target derived storage fields)
|
|
64
|
+
* this is the home for stripping them from the persisted envelope.
|
|
65
|
+
*/
|
|
66
|
+
export class PostgresContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {
|
|
67
|
+
constructor() {
|
|
68
|
+
super(buildPostgresEntityTypeRegistry());
|
|
69
|
+
}
|
|
70
|
+
}
|