@prisma-next/target-postgres 0.5.0-dev.4 → 0.5.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.
- package/dist/codec-types.d.mts +1 -1
- package/dist/codec-types.mjs +1 -1
- package/dist/{codecs-D-F2KJqt.d.mts → codecs-CE5EUsNM.d.mts} +54 -30
- package/dist/codecs-CE5EUsNM.d.mts.map +1 -0
- package/dist/{codecs-BoahtY_Q.mjs → codecs-dzZ_dMpK.mjs} +7 -102
- package/dist/codecs-dzZ_dMpK.mjs.map +1 -0
- package/dist/codecs.d.mts +1 -1
- package/dist/codecs.mjs +1 -1
- package/dist/control.mjs +17 -9
- package/dist/control.mjs.map +1 -1
- package/dist/{data-transform-VfEGzXWt.mjs → data-transform-C83dy0vk.mjs} +3 -1
- package/dist/data-transform-C83dy0vk.mjs.map +1 -0
- package/dist/{data-transform-CxFRBIUp.d.mts → data-transform-D8x5m1YV.d.mts} +7 -1
- package/dist/data-transform-D8x5m1YV.d.mts.map +1 -0
- package/dist/data-transform.d.mts +1 -1
- package/dist/data-transform.mjs +1 -1
- package/dist/migration.d.mts +2 -2
- package/dist/migration.mjs +2 -2
- package/dist/op-factory-call-C3bWXKSP.d.mts.map +1 -1
- package/dist/pack.d.mts +1 -1
- package/dist/{planner-CLUvVhUN.mjs → planner-B4ZSLHRI.mjs} +6 -6
- package/dist/planner-B4ZSLHRI.mjs.map +1 -0
- package/dist/{planner-produced-postgres-migration-DSSPq8QS.mjs → planner-produced-postgres-migration-C0GNhHGw.mjs} +3 -4
- package/dist/{planner-produced-postgres-migration-DSSPq8QS.mjs.map → planner-produced-postgres-migration-C0GNhHGw.mjs.map} +1 -1
- package/dist/{planner-produced-postgres-migration-CRRTno6Z.d.mts → planner-produced-postgres-migration-Dw_mPMKt.d.mts} +2 -2
- package/dist/planner-produced-postgres-migration-Dw_mPMKt.d.mts.map +1 -0
- package/dist/planner-produced-postgres-migration.d.mts +2 -2
- package/dist/planner-produced-postgres-migration.mjs +1 -1
- package/dist/planner.d.mts +15 -9
- package/dist/planner.d.mts.map +1 -1
- package/dist/planner.mjs +1 -1
- package/dist/{postgres-migration-BjA3Zmts.d.mts → postgres-migration-DcfWGqhe.d.mts} +2 -2
- package/dist/{postgres-migration-BjA3Zmts.d.mts.map → postgres-migration-DcfWGqhe.d.mts.map} +1 -1
- package/dist/{postgres-migration-qtmtbONe.mjs → postgres-migration-EGSlO4jO.mjs} +2 -2
- package/dist/{postgres-migration-qtmtbONe.mjs.map → postgres-migration-EGSlO4jO.mjs.map} +1 -1
- package/dist/{render-typescript-1rF_SB4g.mjs → render-typescript-Co3Emwgz.mjs} +1 -2
- package/dist/render-typescript-Co3Emwgz.mjs.map +1 -0
- package/dist/render-typescript.d.mts +1 -2
- package/dist/render-typescript.d.mts.map +1 -1
- package/dist/render-typescript.mjs +1 -1
- package/dist/{statement-builders-BPnmt6wx.mjs → statement-builders-CHqCtSfe.mjs} +13 -8
- package/dist/statement-builders-CHqCtSfe.mjs.map +1 -0
- package/dist/statement-builders.d.mts +10 -3
- package/dist/statement-builders.d.mts.map +1 -1
- package/dist/statement-builders.mjs +2 -2
- package/package.json +18 -15
- package/src/core/codecs.ts +17 -40
- package/src/core/migrations/operations/data-transform.ts +8 -0
- package/src/core/migrations/planner-produced-postgres-migration.ts +0 -1
- package/src/core/migrations/planner.ts +17 -11
- package/src/core/migrations/render-typescript.ts +1 -5
- package/src/core/migrations/runner.ts +45 -9
- package/src/core/migrations/statement-builders.ts +22 -6
- package/src/exports/statement-builders.ts +1 -1
- package/dist/codecs-BoahtY_Q.mjs.map +0 -1
- package/dist/codecs-D-F2KJqt.d.mts.map +0 -1
- package/dist/data-transform-CxFRBIUp.d.mts.map +0 -1
- package/dist/data-transform-VfEGzXWt.mjs.map +0 -1
- package/dist/planner-CLUvVhUN.mjs.map +0 -1
- package/dist/planner-produced-postgres-migration-CRRTno6Z.d.mts.map +0 -1
- package/dist/render-typescript-1rF_SB4g.mjs.map +0 -1
- package/dist/statement-builders-BPnmt6wx.mjs.map +0 -1
- package/src/core/json-schema-type-expression.ts +0 -131
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-typescript-Co3Emwgz.mjs","names":["BASE_IMPORTS: readonly ImportRequirement[]","requirements: ImportRequirement[]","lines: string[]"],"sources":["../src/core/migrations/render-typescript.ts"],"sourcesContent":["/**\n * Polymorphic TypeScript emitter for the Postgres migration IR.\n *\n * Each `PostgresOpFactoryCall` renders itself via `renderTypeScript()` and\n * declares its own `importRequirements()`; this file just composes the module\n * source around those contributions. The design mirrors the Mongo target's\n * `render-typescript.ts` deliberately — byte-for-byte alignment isn't required\n * (different factory module specifiers, different base-class name) but the\n * shape is, so future consolidation to a framework-level helper is mechanical.\n */\n\nimport { detectScaffoldRuntime, shebangLineFor } from '@prisma-next/migration-tools/migration-ts';\nimport { type ImportRequirement, jsonToTsSource, renderImports } from '@prisma-next/ts-render';\nimport type { PostgresOpFactoryCall } from './op-factory-call';\n\nexport interface RenderMigrationMeta {\n readonly from: string | null;\n readonly to: string;\n readonly labels?: readonly string[];\n}\n\n/**\n * Always-present base imports for the rendered scaffold. Both come from\n * `@prisma-next/target-postgres/migration` so an authored Postgres\n * `migration.ts` only needs a single dependency for its base class and\n * its CLI entrypoint:\n *\n * - `Migration` — the target-owned re-export fixes the `SqlMigration`\n * generic to `PostgresPlanTargetDetails` and the abstract `targetId`\n * to `'postgres'`, so user-authored migrations don't need to thread\n * target-details or redeclare `targetId`.\n * - `MigrationCLI` — the migration-file CLI entrypoint, re-exported from\n * `@prisma-next/cli/migration-cli`. Loads `prisma-next.config.ts`,\n * assembles a `ControlStack`, and instantiates the migration class.\n * The migration file owns this dependency directly: pulling CLI\n * machinery in at script run time is acceptable because the script's\n * whole purpose is to be invoked from the project that owns the\n * config.\n */\nconst BASE_IMPORTS: readonly ImportRequirement[] = [\n { moduleSpecifier: '@prisma-next/target-postgres/migration', symbol: 'Migration' },\n { moduleSpecifier: '@prisma-next/target-postgres/migration', symbol: 'MigrationCLI' },\n];\n\nexport function renderCallsToTypeScript(\n calls: ReadonlyArray<PostgresOpFactoryCall>,\n meta: RenderMigrationMeta,\n): string {\n const imports = buildImports(calls);\n const operationsBody = calls.map((c) => c.renderTypeScript()).join(',\\n');\n\n return [\n shebangLineFor(detectScaffoldRuntime()),\n imports,\n '',\n 'export default class M extends Migration {',\n buildDescribeMethod(meta),\n ' override get operations() {',\n ' return [',\n indent(operationsBody, 6),\n ' ];',\n ' }',\n '}',\n '',\n 'MigrationCLI.run(import.meta.url, M);',\n '',\n ].join('\\n');\n}\n\nfunction buildImports(calls: ReadonlyArray<PostgresOpFactoryCall>): string {\n const requirements: ImportRequirement[] = [...BASE_IMPORTS];\n for (const call of calls) {\n for (const req of call.importRequirements()) {\n requirements.push(req);\n }\n }\n return renderImports(requirements);\n}\n\nfunction buildDescribeMethod(meta: RenderMigrationMeta): string {\n const lines: string[] = [];\n lines.push(' override describe() {');\n lines.push(' return {');\n lines.push(` from: ${JSON.stringify(meta.from)},`);\n lines.push(` to: ${JSON.stringify(meta.to)},`);\n if (meta.labels && meta.labels.length > 0) {\n lines.push(` labels: ${jsonToTsSource(meta.labels)},`);\n }\n lines.push(' };');\n lines.push(' }');\n lines.push('');\n return lines.join('\\n');\n}\n\nfunction indent(text: string, spaces: number): string {\n const pad = ' '.repeat(spaces);\n return text\n .split('\\n')\n .map((line) => (line.trim() ? `${pad}${line}` : line))\n .join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAMA,eAA6C,CACjD;CAAE,iBAAiB;CAA0C,QAAQ;CAAa,EAClF;CAAE,iBAAiB;CAA0C,QAAQ;CAAgB,CACtF;AAED,SAAgB,wBACd,OACA,MACQ;CACR,MAAM,UAAU,aAAa,MAAM;CACnC,MAAM,iBAAiB,MAAM,KAAK,MAAM,EAAE,kBAAkB,CAAC,CAAC,KAAK,MAAM;AAEzE,QAAO;EACL,eAAe,uBAAuB,CAAC;EACvC;EACA;EACA;EACA,oBAAoB,KAAK;EACzB;EACA;EACA,OAAO,gBAAgB,EAAE;EACzB;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;AAGd,SAAS,aAAa,OAAqD;CACzE,MAAMC,eAAoC,CAAC,GAAG,aAAa;AAC3D,MAAK,MAAM,QAAQ,MACjB,MAAK,MAAM,OAAO,KAAK,oBAAoB,CACzC,cAAa,KAAK,IAAI;AAG1B,QAAO,cAAc,aAAa;;AAGpC,SAAS,oBAAoB,MAAmC;CAC9D,MAAMC,QAAkB,EAAE;AAC1B,OAAM,KAAK,0BAA0B;AACrC,OAAM,KAAK,eAAe;AAC1B,OAAM,KAAK,eAAe,KAAK,UAAU,KAAK,KAAK,CAAC,GAAG;AACvD,OAAM,KAAK,aAAa,KAAK,UAAU,KAAK,GAAG,CAAC,GAAG;AACnD,KAAI,KAAK,UAAU,KAAK,OAAO,SAAS,EACtC,OAAM,KAAK,iBAAiB,eAAe,KAAK,OAAO,CAAC,GAAG;AAE7D,OAAM,KAAK,SAAS;AACpB,OAAM,KAAK,MAAM;AACjB,OAAM,KAAK,GAAG;AACd,QAAO,MAAM,KAAK,KAAK;;AAGzB,SAAS,OAAO,MAAc,QAAwB;CACpD,MAAM,MAAM,IAAI,OAAO,OAAO;AAC9B,QAAO,KACJ,MAAM,KAAK,CACX,KAAK,SAAU,KAAK,MAAM,GAAG,GAAG,MAAM,SAAS,KAAM,CACrD,KAAK,KAAK"}
|
|
@@ -4,9 +4,8 @@ import { b as PostgresOpFactoryCall } from "./op-factory-call-C3bWXKSP.mjs";
|
|
|
4
4
|
//#region src/core/migrations/render-typescript.d.ts
|
|
5
5
|
|
|
6
6
|
interface RenderMigrationMeta {
|
|
7
|
-
readonly from: string;
|
|
7
|
+
readonly from: string | null;
|
|
8
8
|
readonly to: string;
|
|
9
|
-
readonly kind?: string;
|
|
10
9
|
readonly labels?: readonly string[];
|
|
11
10
|
}
|
|
12
11
|
declare function renderCallsToTypeScript(calls: ReadonlyArray<PostgresOpFactoryCall>, meta: RenderMigrationMeta): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-typescript.d.mts","names":[],"sources":["../src/core/migrations/render-typescript.ts"],"sourcesContent":[],"mappings":";;;;;UAeiB,mBAAA
|
|
1
|
+
{"version":3,"file":"render-typescript.d.mts","names":[],"sources":["../src/core/migrations/render-typescript.ts"],"sourcesContent":[],"mappings":";;;;;UAeiB,mBAAA;;;;;iBA6BD,uBAAA,QACP,cAAc,8BACf"}
|
|
@@ -12,7 +12,8 @@ const ensureMarkerTableStatement = {
|
|
|
12
12
|
canonical_version int,
|
|
13
13
|
updated_at timestamptz not null default now(),
|
|
14
14
|
app_tag text,
|
|
15
|
-
meta jsonb not null default '{}'
|
|
15
|
+
meta jsonb not null default '{}',
|
|
16
|
+
invariants text[] not null default '{}'
|
|
16
17
|
)`,
|
|
17
18
|
params: []
|
|
18
19
|
};
|
|
@@ -30,7 +31,7 @@ const ensureLedgerTableStatement = {
|
|
|
30
31
|
)`,
|
|
31
32
|
params: []
|
|
32
33
|
};
|
|
33
|
-
function
|
|
34
|
+
function buildMergeMarkerStatements(input) {
|
|
34
35
|
const params = [
|
|
35
36
|
1,
|
|
36
37
|
input.storageHash,
|
|
@@ -38,7 +39,8 @@ function buildWriteMarkerStatements(input) {
|
|
|
38
39
|
jsonParam(input.contractJson),
|
|
39
40
|
input.canonicalVersion ?? null,
|
|
40
41
|
input.appTag ?? null,
|
|
41
|
-
jsonParam(input.meta ?? {})
|
|
42
|
+
jsonParam(input.meta ?? {}),
|
|
43
|
+
input.invariants
|
|
42
44
|
];
|
|
43
45
|
return {
|
|
44
46
|
insert: {
|
|
@@ -50,7 +52,8 @@ function buildWriteMarkerStatements(input) {
|
|
|
50
52
|
canonical_version,
|
|
51
53
|
updated_at,
|
|
52
54
|
app_tag,
|
|
53
|
-
meta
|
|
55
|
+
meta,
|
|
56
|
+
invariants
|
|
54
57
|
) values (
|
|
55
58
|
$1,
|
|
56
59
|
$2,
|
|
@@ -59,7 +62,8 @@ function buildWriteMarkerStatements(input) {
|
|
|
59
62
|
$5,
|
|
60
63
|
now(),
|
|
61
64
|
$6,
|
|
62
|
-
$7::jsonb
|
|
65
|
+
$7::jsonb,
|
|
66
|
+
$8::text[]
|
|
63
67
|
)`,
|
|
64
68
|
params
|
|
65
69
|
},
|
|
@@ -71,7 +75,8 @@ function buildWriteMarkerStatements(input) {
|
|
|
71
75
|
canonical_version = $5,
|
|
72
76
|
updated_at = now(),
|
|
73
77
|
app_tag = $6,
|
|
74
|
-
meta = $7::jsonb
|
|
78
|
+
meta = $7::jsonb,
|
|
79
|
+
invariants = array(select distinct unnest(invariants || $8::text[]) order by 1)
|
|
75
80
|
where id = $1`,
|
|
76
81
|
params
|
|
77
82
|
}
|
|
@@ -112,5 +117,5 @@ function jsonParam(value) {
|
|
|
112
117
|
}
|
|
113
118
|
|
|
114
119
|
//#endregion
|
|
115
|
-
export { ensurePrismaContractSchemaStatement as a, ensureMarkerTableStatement as i,
|
|
116
|
-
//# sourceMappingURL=statement-builders-
|
|
120
|
+
export { ensurePrismaContractSchemaStatement as a, ensureMarkerTableStatement as i, buildMergeMarkerStatements as n, ensureLedgerTableStatement as r, buildLedgerInsertStatement as t };
|
|
121
|
+
//# sourceMappingURL=statement-builders-CHqCtSfe.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statement-builders-CHqCtSfe.mjs","names":["ensurePrismaContractSchemaStatement: SqlStatement","ensureMarkerTableStatement: SqlStatement","ensureLedgerTableStatement: SqlStatement","params: readonly unknown[]"],"sources":["../src/core/migrations/statement-builders.ts"],"sourcesContent":["export interface SqlStatement {\n readonly sql: string;\n readonly params: readonly unknown[];\n}\n\nexport const ensurePrismaContractSchemaStatement: SqlStatement = {\n sql: 'create schema if not exists prisma_contract',\n params: [],\n};\n\nexport const ensureMarkerTableStatement: SqlStatement = {\n sql: `create table if not exists prisma_contract.marker (\n id smallint primary key default 1,\n core_hash text not null,\n profile_hash text not null,\n contract_json jsonb,\n canonical_version int,\n updated_at timestamptz not null default now(),\n app_tag text,\n meta jsonb not null default '{}',\n invariants text[] not null default '{}'\n )`,\n params: [],\n};\n\nexport const ensureLedgerTableStatement: SqlStatement = {\n sql: `create table if not exists prisma_contract.ledger (\n id bigserial primary key,\n created_at timestamptz not null default now(),\n origin_core_hash text,\n origin_profile_hash text,\n destination_core_hash text not null,\n destination_profile_hash text,\n contract_json_before jsonb,\n contract_json_after jsonb,\n operations jsonb not null\n )`,\n params: [],\n};\n\nexport interface MergeMarkerInput {\n readonly storageHash: string;\n readonly profileHash: string;\n readonly contractJson?: unknown;\n readonly canonicalVersion?: number | null;\n readonly appTag?: string | null;\n readonly meta?: Record<string, unknown>;\n /**\n * Invariants to merge into `marker.invariants`. INSERT writes them as\n * the initial value (callers are expected to pass a sorted, deduped\n * array). UPDATE merges them with the existing column server-side via\n * a single atomic SQL expression.\n */\n readonly invariants: readonly string[];\n}\n\nexport function buildMergeMarkerStatements(input: MergeMarkerInput): {\n readonly insert: SqlStatement;\n readonly update: SqlStatement;\n} {\n const params: readonly unknown[] = [\n 1,\n input.storageHash,\n input.profileHash,\n jsonParam(input.contractJson),\n input.canonicalVersion ?? null,\n input.appTag ?? null,\n jsonParam(input.meta ?? {}),\n input.invariants,\n ];\n\n return {\n insert: {\n sql: `insert into prisma_contract.marker (\n id,\n core_hash,\n profile_hash,\n contract_json,\n canonical_version,\n updated_at,\n app_tag,\n meta,\n invariants\n ) values (\n $1,\n $2,\n $3,\n $4::jsonb,\n $5,\n now(),\n $6,\n $7::jsonb,\n $8::text[]\n )`,\n params,\n },\n update: {\n // `invariants = array(select distinct unnest(invariants || $8::text[]) order by 1)`\n // reads the current column value under the UPDATE's row lock, unions\n // with the incoming array, dedupes, and sorts ascending — single\n // statement, atomic, no read-then-write window.\n sql: `update prisma_contract.marker set\n core_hash = $2,\n profile_hash = $3,\n contract_json = $4::jsonb,\n canonical_version = $5,\n updated_at = now(),\n app_tag = $6,\n meta = $7::jsonb,\n invariants = array(select distinct unnest(invariants || $8::text[]) order by 1)\n where id = $1`,\n params,\n },\n };\n}\n\nexport interface LedgerInsertInput {\n readonly originStorageHash?: string | null;\n readonly originProfileHash?: string | null;\n readonly destinationStorageHash: string;\n readonly destinationProfileHash?: string | null;\n readonly contractJsonBefore?: unknown;\n readonly contractJsonAfter?: unknown;\n readonly operations: unknown;\n}\n\nexport function buildLedgerInsertStatement(input: LedgerInsertInput): SqlStatement {\n return {\n sql: `insert into prisma_contract.ledger (\n origin_core_hash,\n origin_profile_hash,\n destination_core_hash,\n destination_profile_hash,\n contract_json_before,\n contract_json_after,\n operations\n ) values (\n $1,\n $2,\n $3,\n $4,\n $5::jsonb,\n $6::jsonb,\n $7::jsonb\n )`,\n params: [\n input.originStorageHash ?? null,\n input.originProfileHash ?? null,\n input.destinationStorageHash,\n input.destinationProfileHash ?? null,\n jsonParam(input.contractJsonBefore),\n jsonParam(input.contractJsonAfter),\n jsonParam(input.operations),\n ],\n };\n}\n\nfunction jsonParam(value: unknown): string {\n return JSON.stringify(value ?? null);\n}\n"],"mappings":";AAKA,MAAaA,sCAAoD;CAC/D,KAAK;CACL,QAAQ,EAAE;CACX;AAED,MAAaC,6BAA2C;CACtD,KAAK;;;;;;;;;;;CAWL,QAAQ,EAAE;CACX;AAED,MAAaC,6BAA2C;CACtD,KAAK;;;;;;;;;;;CAWL,QAAQ,EAAE;CACX;AAkBD,SAAgB,2BAA2B,OAGzC;CACA,MAAMC,SAA6B;EACjC;EACA,MAAM;EACN,MAAM;EACN,UAAU,MAAM,aAAa;EAC7B,MAAM,oBAAoB;EAC1B,MAAM,UAAU;EAChB,UAAU,MAAM,QAAQ,EAAE,CAAC;EAC3B,MAAM;EACP;AAED,QAAO;EACL,QAAQ;GACN,KAAK;;;;;;;;;;;;;;;;;;;;;GAqBL;GACD;EACD,QAAQ;GAKN,KAAK;;;;;;;;;;GAUL;GACD;EACF;;AAaH,SAAgB,2BAA2B,OAAwC;AACjF,QAAO;EACL,KAAK;;;;;;;;;;;;;;;;;EAiBL,QAAQ;GACN,MAAM,qBAAqB;GAC3B,MAAM,qBAAqB;GAC3B,MAAM;GACN,MAAM,0BAA0B;GAChC,UAAU,MAAM,mBAAmB;GACnC,UAAU,MAAM,kBAAkB;GAClC,UAAU,MAAM,WAAW;GAC5B;EACF;;AAGH,SAAS,UAAU,OAAwB;AACzC,QAAO,KAAK,UAAU,SAAS,KAAK"}
|
|
@@ -6,18 +6,25 @@ interface SqlStatement {
|
|
|
6
6
|
declare const ensurePrismaContractSchemaStatement: SqlStatement;
|
|
7
7
|
declare const ensureMarkerTableStatement: SqlStatement;
|
|
8
8
|
declare const ensureLedgerTableStatement: SqlStatement;
|
|
9
|
-
interface
|
|
9
|
+
interface MergeMarkerInput {
|
|
10
10
|
readonly storageHash: string;
|
|
11
11
|
readonly profileHash: string;
|
|
12
12
|
readonly contractJson?: unknown;
|
|
13
13
|
readonly canonicalVersion?: number | null;
|
|
14
14
|
readonly appTag?: string | null;
|
|
15
15
|
readonly meta?: Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Invariants to merge into `marker.invariants`. INSERT writes them as
|
|
18
|
+
* the initial value (callers are expected to pass a sorted, deduped
|
|
19
|
+
* array). UPDATE merges them with the existing column server-side via
|
|
20
|
+
* a single atomic SQL expression.
|
|
21
|
+
*/
|
|
22
|
+
readonly invariants: readonly string[];
|
|
16
23
|
}
|
|
17
|
-
declare function
|
|
24
|
+
declare function buildMergeMarkerStatements(input: MergeMarkerInput): {
|
|
18
25
|
readonly insert: SqlStatement;
|
|
19
26
|
readonly update: SqlStatement;
|
|
20
27
|
};
|
|
21
28
|
//#endregion
|
|
22
|
-
export { type SqlStatement,
|
|
29
|
+
export { type SqlStatement, buildMergeMarkerStatements, ensureLedgerTableStatement, ensureMarkerTableStatement, ensurePrismaContractSchemaStatement };
|
|
23
30
|
//# sourceMappingURL=statement-builders.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statement-builders.d.mts","names":[],"sources":["../src/core/migrations/statement-builders.ts"],"sourcesContent":[],"mappings":";UAAiB,YAAA;EAAA,SAAA,GAAA,EAAA,MAAY;EAKhB,SAAA,MAAA,EAAA,SAAA,OAAA,EAAA;AAKb;
|
|
1
|
+
{"version":3,"file":"statement-builders.d.mts","names":[],"sources":["../src/core/migrations/statement-builders.ts"],"sourcesContent":[],"mappings":";UAAiB,YAAA;EAAA,SAAA,GAAA,EAAA,MAAY;EAKhB,SAAA,MAAA,EAAA,SAAA,OAAA,EAAA;AAKb;AAea,cApBA,mCAoB4B,EApBS,YAiCjD;AAEgB,cA9BJ,0BAoCW,EApCiB,YAoCjB;AAUR,cA/BH,0BA+B6B,EA/BD,YA+BC;AAAQ,UAhBjC,gBAAA,CAgBiC;EAC/B,SAAA,WAAA,EAAA,MAAA;EACA,SAAA,WAAA,EAAA,MAAA;EAAY,SAAA,YAAA,CAAA,EAAA,OAAA;;;kBAZb;;;;;;;;;iBAUF,0BAAA,QAAkC;mBAC/B;mBACA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as ensurePrismaContractSchemaStatement, i as ensureMarkerTableStatement, n as
|
|
1
|
+
import { a as ensurePrismaContractSchemaStatement, i as ensureMarkerTableStatement, n as buildMergeMarkerStatements, r as ensureLedgerTableStatement } from "./statement-builders-CHqCtSfe.mjs";
|
|
2
2
|
|
|
3
|
-
export {
|
|
3
|
+
export { buildMergeMarkerStatements, ensureLedgerTableStatement, ensureMarkerTableStatement, ensurePrismaContractSchemaStatement };
|
package/package.json
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/target-postgres",
|
|
3
|
-
"version": "0.5.0-dev.
|
|
3
|
+
"version": "0.5.0-dev.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Postgres target pack for Prisma Next",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"arktype": "^2.0.0",
|
|
9
9
|
"pathe": "^2.0.3",
|
|
10
|
-
"@prisma-next/cli": "0.5.0-dev.
|
|
11
|
-
"@prisma-next/contract": "0.5.0-dev.
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/
|
|
14
|
-
"@prisma-next/
|
|
15
|
-
"@prisma-next/migration-tools": "0.5.0-dev.
|
|
16
|
-
"@prisma-next/
|
|
17
|
-
"@prisma-next/sql-
|
|
18
|
-
"@prisma-next/sql-
|
|
19
|
-
"@prisma-next/
|
|
20
|
-
"@prisma-next/sql-
|
|
21
|
-
"@prisma-next/sql-
|
|
22
|
-
"@prisma-next/utils": "0.5.0-dev.
|
|
10
|
+
"@prisma-next/cli": "0.5.0-dev.41",
|
|
11
|
+
"@prisma-next/contract": "0.5.0-dev.41",
|
|
12
|
+
"@prisma-next/family-sql": "0.5.0-dev.41",
|
|
13
|
+
"@prisma-next/framework-components": "0.5.0-dev.41",
|
|
14
|
+
"@prisma-next/errors": "0.5.0-dev.41",
|
|
15
|
+
"@prisma-next/migration-tools": "0.5.0-dev.41",
|
|
16
|
+
"@prisma-next/ts-render": "0.5.0-dev.41",
|
|
17
|
+
"@prisma-next/sql-operations": "0.5.0-dev.41",
|
|
18
|
+
"@prisma-next/sql-contract": "0.5.0-dev.41",
|
|
19
|
+
"@prisma-next/sql-relational-core": "0.5.0-dev.41",
|
|
20
|
+
"@prisma-next/sql-schema-ir": "0.5.0-dev.41",
|
|
21
|
+
"@prisma-next/sql-errors": "0.5.0-dev.41",
|
|
22
|
+
"@prisma-next/utils": "0.5.0-dev.41"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"tsdown": "0.18.4",
|
|
26
26
|
"typescript": "5.9.3",
|
|
27
27
|
"vitest": "4.0.17",
|
|
28
|
-
"@prisma-next/tsconfig": "0.0.0",
|
|
29
28
|
"@prisma-next/test-utils": "0.0.1",
|
|
29
|
+
"@prisma-next/tsconfig": "0.0.0",
|
|
30
30
|
"@prisma-next/tsdown": "0.0.0"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
@@ -67,6 +67,9 @@
|
|
|
67
67
|
"url": "https://github.com/prisma/prisma-next.git",
|
|
68
68
|
"directory": "packages/3-targets/3-targets/postgres"
|
|
69
69
|
},
|
|
70
|
+
"prismaNext": {
|
|
71
|
+
"minServerVersion": "14"
|
|
72
|
+
},
|
|
70
73
|
"scripts": {
|
|
71
74
|
"build": "tsdown",
|
|
72
75
|
"test": "vitest run --passWithNoTests",
|
package/src/core/codecs.ts
CHANGED
|
@@ -40,7 +40,6 @@ import {
|
|
|
40
40
|
PG_VARBIT_CODEC_ID,
|
|
41
41
|
PG_VARCHAR_CODEC_ID,
|
|
42
42
|
} from './codec-ids';
|
|
43
|
-
import { renderTypeScriptTypeFromJsonSchema } from './json-schema-type-expression';
|
|
44
43
|
|
|
45
44
|
const lengthParamsSchema = arktype({
|
|
46
45
|
length: 'number.integer > 0',
|
|
@@ -85,19 +84,13 @@ function renderPrecision(typeName: string, typeParams: Record<string, unknown>):
|
|
|
85
84
|
return `${typeName}<${precision}>`;
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return renderTypeScriptTypeFromJsonSchema(schema);
|
|
96
|
-
}
|
|
97
|
-
throw new Error(
|
|
98
|
-
`renderOutputType: JSON codec typeParams must contain "type" (string) or "schemaJson" (object), got keys: ${Object.keys(typeParams).join(', ')}`,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
87
|
+
// Phase C: postgres' raw json/jsonb codecs no longer carry a
|
|
88
|
+
// `renderOutputType` slot — the schema-typed JSON surface that drove
|
|
89
|
+
// `typeParams: { schemaJson, type? }` retired in favor of the per-library
|
|
90
|
+
// extension package (`@prisma-next/extension-arktype-json`). Untyped
|
|
91
|
+
// json/jsonb columns have no typeParams; the framework emit path falls
|
|
92
|
+
// through to the generic `CodecTypes['pg/jsonb@1']['output']` accessor
|
|
93
|
+
// (which resolves to `JsonValue` via the codec-types map).
|
|
101
94
|
|
|
102
95
|
function aliasCodec<
|
|
103
96
|
Id extends string,
|
|
@@ -339,22 +332,15 @@ const pgFloat8Codec = codec({
|
|
|
339
332
|
const pgTimestampCodec = codec<
|
|
340
333
|
typeof PG_TIMESTAMP_CODEC_ID,
|
|
341
334
|
readonly ['equality', 'order'],
|
|
342
|
-
|
|
343
|
-
|
|
335
|
+
Date,
|
|
336
|
+
Date
|
|
344
337
|
>({
|
|
345
338
|
typeId: PG_TIMESTAMP_CODEC_ID,
|
|
346
339
|
targetTypes: ['timestamp'],
|
|
347
340
|
traits: ['equality', 'order'],
|
|
348
|
-
encode: (value:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return String(value);
|
|
352
|
-
},
|
|
353
|
-
decode: (wire: string | Date): string => {
|
|
354
|
-
if (wire instanceof Date) return wire.toISOString();
|
|
355
|
-
return wire;
|
|
356
|
-
},
|
|
357
|
-
encodeJson: (value: string | Date) => (value instanceof Date ? value.toISOString() : value),
|
|
341
|
+
encode: (value: Date): Date => value,
|
|
342
|
+
decode: (wire: Date): Date => wire,
|
|
343
|
+
encodeJson: (value: Date) => value.toISOString(),
|
|
358
344
|
decodeJson: (json) => {
|
|
359
345
|
if (typeof json !== 'string') {
|
|
360
346
|
throw new Error(`Expected ISO date string for pg/timestamp@1, got ${typeof json}`);
|
|
@@ -381,22 +367,15 @@ const pgTimestampCodec = codec<
|
|
|
381
367
|
const pgTimestamptzCodec = codec<
|
|
382
368
|
typeof PG_TIMESTAMPTZ_CODEC_ID,
|
|
383
369
|
readonly ['equality', 'order'],
|
|
384
|
-
|
|
385
|
-
|
|
370
|
+
Date,
|
|
371
|
+
Date
|
|
386
372
|
>({
|
|
387
373
|
typeId: PG_TIMESTAMPTZ_CODEC_ID,
|
|
388
374
|
targetTypes: ['timestamptz'],
|
|
389
375
|
traits: ['equality', 'order'],
|
|
390
|
-
encode: (value:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return String(value);
|
|
394
|
-
},
|
|
395
|
-
decode: (wire: string | Date): string => {
|
|
396
|
-
if (wire instanceof Date) return wire.toISOString();
|
|
397
|
-
return wire;
|
|
398
|
-
},
|
|
399
|
-
encodeJson: (value: string | Date) => (value instanceof Date ? value.toISOString() : value),
|
|
376
|
+
encode: (value: Date): Date => value,
|
|
377
|
+
decode: (wire: Date): Date => wire,
|
|
378
|
+
encodeJson: (value: Date) => value.toISOString(),
|
|
400
379
|
decodeJson: (json) => {
|
|
401
380
|
if (typeof json !== 'string') {
|
|
402
381
|
throw new Error(`Expected ISO date string for pg/timestamptz@1, got ${typeof json}`);
|
|
@@ -576,7 +555,6 @@ const pgJsonCodec = codec({
|
|
|
576
555
|
encode: (value: string | JsonValue): string => JSON.stringify(value),
|
|
577
556
|
decode: (wire: string | JsonValue): JsonValue =>
|
|
578
557
|
typeof wire === 'string' ? JSON.parse(wire) : wire,
|
|
579
|
-
renderOutputType: renderJsonOutputType,
|
|
580
558
|
meta: {
|
|
581
559
|
db: {
|
|
582
560
|
sql: {
|
|
@@ -595,7 +573,6 @@ const pgJsonbCodec = codec({
|
|
|
595
573
|
encode: (value: string | JsonValue): string => JSON.stringify(value),
|
|
596
574
|
decode: (wire: string | JsonValue): JsonValue =>
|
|
597
575
|
typeof wire === 'string' ? JSON.parse(wire) : wire,
|
|
598
|
-
renderOutputType: renderJsonOutputType,
|
|
599
576
|
meta: {
|
|
600
577
|
db: {
|
|
601
578
|
sql: {
|
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
} from '@prisma-next/framework-components/control';
|
|
38
38
|
import type { SqlStorage } from '@prisma-next/sql-contract/types';
|
|
39
39
|
import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
|
|
40
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
40
41
|
|
|
41
42
|
interface Buildable<R = unknown> {
|
|
42
43
|
build(): SqlQueryPlan<R>;
|
|
@@ -49,6 +50,12 @@ interface Buildable<R = unknown> {
|
|
|
49
50
|
export type DataTransformClosure = () => SqlQueryPlan | Buildable;
|
|
50
51
|
|
|
51
52
|
export interface DataTransformOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Optional opt-in routing identity. Presence opts the transform into
|
|
55
|
+
* invariant-aware routing; absence means it is path-dependent and
|
|
56
|
+
* not referenceable from refs.
|
|
57
|
+
*/
|
|
58
|
+
readonly invariantId?: string;
|
|
52
59
|
/** Optional pre-flight query. `undefined` means "no check". */
|
|
53
60
|
readonly check?: DataTransformClosure;
|
|
54
61
|
/** One or more mutation queries to execute. */
|
|
@@ -76,6 +83,7 @@ export function dataTransform<TContract extends Contract<SqlStorage>>(
|
|
|
76
83
|
label: `Data transform: ${name}`,
|
|
77
84
|
operationClass: 'data',
|
|
78
85
|
name,
|
|
86
|
+
...ifDefined('invariantId', options.invariantId),
|
|
79
87
|
source: 'migration.ts',
|
|
80
88
|
check: options.check ? invokeAndLower(options.check, contract, adapter, name) : null,
|
|
81
89
|
run: runClosures.map((closure) => invokeAndLower(closure, contract, adapter, name)),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Contract } from '@prisma-next/contract/types';
|
|
1
2
|
import type {
|
|
2
3
|
MigrationOperationPolicy,
|
|
3
4
|
SqlMigrationPlannerPlanOptions,
|
|
@@ -83,20 +84,25 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
83
84
|
readonly contract: unknown;
|
|
84
85
|
readonly schema: unknown;
|
|
85
86
|
readonly policy: MigrationOperationPolicy;
|
|
86
|
-
readonly fromHash?: string;
|
|
87
87
|
/**
|
|
88
88
|
* The "from" contract (state the planner assumes the database starts
|
|
89
|
-
* at)
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* activate.
|
|
89
|
+
* at), or `null` for reconciliation flows. Only `migration plan` ever
|
|
90
|
+
* supplies a non-null value; `db update` / `db init` reconcile against
|
|
91
|
+
* the live schema and pass `null`. When present alongside the
|
|
92
|
+
* `'data'` operation class, strategies that need from/to column-shape
|
|
93
|
+
* comparisons (unsafe type change, nullability tightening) activate.
|
|
94
|
+
*
|
|
95
|
+
* Typed as the framework `Contract | null` to satisfy the
|
|
96
|
+
* `MigrationPlanner` interface contract; `planSql` narrows to the SQL
|
|
97
|
+
* shape via `SqlMigrationPlannerPlanOptions`. Used to populate
|
|
98
|
+
* `describe().from` on the produced plan as
|
|
99
|
+
* `fromContract?.storage.storageHash ?? null`.
|
|
94
100
|
*/
|
|
95
|
-
readonly fromContract
|
|
101
|
+
readonly fromContract: Contract | null;
|
|
96
102
|
readonly schemaName?: string;
|
|
97
103
|
readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
|
|
98
104
|
}): PostgresPlanResult {
|
|
99
|
-
return this.planSql(options as SqlMigrationPlannerPlanOptions
|
|
105
|
+
return this.planSql(options as SqlMigrationPlannerPlanOptions);
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
emptyMigration(context: MigrationScaffoldContext): MigrationPlanWithAuthoringSurface {
|
|
@@ -106,7 +112,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
106
112
|
});
|
|
107
113
|
}
|
|
108
114
|
|
|
109
|
-
private planSql(options: SqlMigrationPlannerPlanOptions
|
|
115
|
+
private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {
|
|
110
116
|
const schemaName = options.schemaName ?? this.config.defaultSchema;
|
|
111
117
|
const policyResult = this.ensureAdditivePolicy(options.policy);
|
|
112
118
|
if (policyResult) {
|
|
@@ -125,7 +131,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
125
131
|
// from/to comparisons (unsafe type change, nullable tightening) are
|
|
126
132
|
// inapplicable there — reconciliation falls through to
|
|
127
133
|
// `mapIssueToCall`'s direct destructive handlers.
|
|
128
|
-
fromContract: options.fromContract
|
|
134
|
+
fromContract: options.fromContract,
|
|
129
135
|
schemaName,
|
|
130
136
|
codecHooks,
|
|
131
137
|
storageTypes,
|
|
@@ -142,7 +148,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
|
|
|
142
148
|
return Object.freeze({
|
|
143
149
|
kind: 'success' as const,
|
|
144
150
|
plan: new TypeScriptRenderablePostgresMigration(result.value.calls, {
|
|
145
|
-
from:
|
|
151
|
+
from: options.fromContract?.storage.storageHash ?? null,
|
|
146
152
|
to: options.contract.storage.storageHash,
|
|
147
153
|
}),
|
|
148
154
|
});
|
|
@@ -14,9 +14,8 @@ import { type ImportRequirement, jsonToTsSource, renderImports } from '@prisma-n
|
|
|
14
14
|
import type { PostgresOpFactoryCall } from './op-factory-call';
|
|
15
15
|
|
|
16
16
|
export interface RenderMigrationMeta {
|
|
17
|
-
readonly from: string;
|
|
17
|
+
readonly from: string | null;
|
|
18
18
|
readonly to: string;
|
|
19
|
-
readonly kind?: string;
|
|
20
19
|
readonly labels?: readonly string[];
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -84,9 +83,6 @@ function buildDescribeMethod(meta: RenderMigrationMeta): string {
|
|
|
84
83
|
lines.push(' return {');
|
|
85
84
|
lines.push(` from: ${JSON.stringify(meta.from)},`);
|
|
86
85
|
lines.push(` to: ${JSON.stringify(meta.to)},`);
|
|
87
|
-
if (meta.kind) {
|
|
88
|
-
lines.push(` kind: ${JSON.stringify(meta.kind)},`);
|
|
89
|
-
}
|
|
90
86
|
if (meta.labels && meta.labels.length > 0) {
|
|
91
87
|
lines.push(` labels: ${jsonToTsSource(meta.labels)},`);
|
|
92
88
|
}
|
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
} from '@prisma-next/family-sql/control';
|
|
13
13
|
import { runnerFailure, runnerSuccess } from '@prisma-next/family-sql/control';
|
|
14
14
|
import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
|
|
15
|
-
import { readMarker } from '@prisma-next/family-sql/verify';
|
|
16
15
|
import type { DataTransformOperation } from '@prisma-next/framework-components/control';
|
|
17
16
|
import { SqlQueryError } from '@prisma-next/sql-errors';
|
|
18
17
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
@@ -23,7 +22,7 @@ import { normalizeSchemaNativeType } from '../native-type-normalizer';
|
|
|
23
22
|
import type { PostgresPlanTargetDetails } from './planner-target-details';
|
|
24
23
|
import {
|
|
25
24
|
buildLedgerInsertStatement,
|
|
26
|
-
|
|
25
|
+
buildMergeMarkerStatements,
|
|
27
26
|
ensureLedgerTableStatement,
|
|
28
27
|
ensureMarkerTableStatement,
|
|
29
28
|
ensurePrismaContractSchemaStatement,
|
|
@@ -120,7 +119,7 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
120
119
|
try {
|
|
121
120
|
await this.acquireLock(driver, lockKey);
|
|
122
121
|
await this.ensureControlTables(driver);
|
|
123
|
-
const existingMarker = await readMarker(driver);
|
|
122
|
+
const existingMarker = await this.family.readMarker({ driver });
|
|
124
123
|
|
|
125
124
|
// Validate plan origin matches existing marker (needs marker from DB)
|
|
126
125
|
const markerCheck = this.ensureMarkerCompatibility(existingMarker, options.plan);
|
|
@@ -128,9 +127,14 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
128
127
|
return markerCheck;
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
// db update (origin: null) always applies; migration-apply (origin set
|
|
130
|
+
// db update (origin: null) always applies; migration-apply (origin set,
|
|
131
|
+
// origin !== destination) skips if marker already matches destination.
|
|
132
|
+
// Self-edges (origin === destination) intentionally bypass the skip:
|
|
133
|
+
// the migration is data-only, and the data transform's own check
|
|
134
|
+
// decides whether `run` fires.
|
|
132
135
|
const markerAtDestination = this.markerMatchesDestination(existingMarker, options.plan);
|
|
133
|
-
const
|
|
136
|
+
const isSelfEdge = options.plan.origin?.storageHash === options.plan.destination.storageHash;
|
|
137
|
+
const skipOperations = markerAtDestination && options.plan.origin != null && !isSelfEdge;
|
|
134
138
|
let applyValue: ApplyPlanSuccessValue;
|
|
135
139
|
|
|
136
140
|
if (skipOperations) {
|
|
@@ -170,9 +174,39 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
170
174
|
});
|
|
171
175
|
}
|
|
172
176
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
177
|
+
// Self-edge no-op detection: a self-edge migration with zero ops in
|
|
178
|
+
// the plan that brings no new invariants produced no observable
|
|
179
|
+
// change. Skip the marker + ledger writes so an idempotent re-apply
|
|
180
|
+
// of a self-edge data transform doesn't churn updatedAt or pile up
|
|
181
|
+
// empty ledger entries. db update no-ops still write a ledger entry
|
|
182
|
+
// as audit trail.
|
|
183
|
+
//
|
|
184
|
+
// TODO(invariant-routing follow-up): `executeDataTransform` always
|
|
185
|
+
// counts every op it visits (including self-skips via `check === true`
|
|
186
|
+
// or empty idempotency probe), so `operationsExecuted === 0` here
|
|
187
|
+
// means "the plan had zero ops" rather than "every op self-skipped".
|
|
188
|
+
// The CLI is unaffected today because `migration-apply.ts` marker-
|
|
189
|
+
// subtraction empties `effectiveRequired` first and short-circuits
|
|
190
|
+
// before we run; the non-CLI re-apply path needs a per-op `executed`
|
|
191
|
+
// flag threaded through `executeDataTransform` to recover the
|
|
192
|
+
// intended check. See review thread A13 / future M5 ADR draft.
|
|
193
|
+
const incomingInvariants = options.plan.providedInvariants;
|
|
194
|
+
const existingInvariants = new Set(existingMarker?.invariants ?? []);
|
|
195
|
+
const incomingIsSubsetOfExisting = incomingInvariants.every((id) =>
|
|
196
|
+
existingInvariants.has(id),
|
|
197
|
+
);
|
|
198
|
+
const isSelfEdgeNoOp =
|
|
199
|
+
isSelfEdge && applyValue.operationsExecuted === 0 && incomingIsSubsetOfExisting;
|
|
200
|
+
|
|
201
|
+
if (!isSelfEdgeNoOp) {
|
|
202
|
+
await this.upsertMarker(driver, options, existingMarker);
|
|
203
|
+
await this.recordLedgerEntry(
|
|
204
|
+
driver,
|
|
205
|
+
options,
|
|
206
|
+
existingMarker,
|
|
207
|
+
applyValue.executedOperations,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
176
210
|
|
|
177
211
|
await this.commitTransaction(driver);
|
|
178
212
|
committed = true;
|
|
@@ -617,7 +651,8 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
617
651
|
options: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>,
|
|
618
652
|
existingMarker: ContractMarkerRecord | null,
|
|
619
653
|
): Promise<void> {
|
|
620
|
-
const
|
|
654
|
+
const incomingInvariants = options.plan.providedInvariants;
|
|
655
|
+
const writeStatements = buildMergeMarkerStatements({
|
|
621
656
|
storageHash: options.plan.destination.storageHash,
|
|
622
657
|
profileHash:
|
|
623
658
|
options.plan.destination.profileHash ??
|
|
@@ -626,6 +661,7 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
|
|
|
626
661
|
contractJson: options.destinationContract,
|
|
627
662
|
canonicalVersion: null,
|
|
628
663
|
meta: {},
|
|
664
|
+
invariants: incomingInvariants,
|
|
629
665
|
});
|
|
630
666
|
const statement = existingMarker ? writeStatements.update : writeStatements.insert;
|
|
631
667
|
await this.executeStatement(driver, statement);
|
|
@@ -17,7 +17,8 @@ export const ensureMarkerTableStatement: SqlStatement = {
|
|
|
17
17
|
canonical_version int,
|
|
18
18
|
updated_at timestamptz not null default now(),
|
|
19
19
|
app_tag text,
|
|
20
|
-
meta jsonb not null default '{}'
|
|
20
|
+
meta jsonb not null default '{}',
|
|
21
|
+
invariants text[] not null default '{}'
|
|
21
22
|
)`,
|
|
22
23
|
params: [],
|
|
23
24
|
};
|
|
@@ -37,16 +38,23 @@ export const ensureLedgerTableStatement: SqlStatement = {
|
|
|
37
38
|
params: [],
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
export interface
|
|
41
|
+
export interface MergeMarkerInput {
|
|
41
42
|
readonly storageHash: string;
|
|
42
43
|
readonly profileHash: string;
|
|
43
44
|
readonly contractJson?: unknown;
|
|
44
45
|
readonly canonicalVersion?: number | null;
|
|
45
46
|
readonly appTag?: string | null;
|
|
46
47
|
readonly meta?: Record<string, unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* Invariants to merge into `marker.invariants`. INSERT writes them as
|
|
50
|
+
* the initial value (callers are expected to pass a sorted, deduped
|
|
51
|
+
* array). UPDATE merges them with the existing column server-side via
|
|
52
|
+
* a single atomic SQL expression.
|
|
53
|
+
*/
|
|
54
|
+
readonly invariants: readonly string[];
|
|
47
55
|
}
|
|
48
56
|
|
|
49
|
-
export function
|
|
57
|
+
export function buildMergeMarkerStatements(input: MergeMarkerInput): {
|
|
50
58
|
readonly insert: SqlStatement;
|
|
51
59
|
readonly update: SqlStatement;
|
|
52
60
|
} {
|
|
@@ -58,6 +66,7 @@ export function buildWriteMarkerStatements(input: WriteMarkerInput): {
|
|
|
58
66
|
input.canonicalVersion ?? null,
|
|
59
67
|
input.appTag ?? null,
|
|
60
68
|
jsonParam(input.meta ?? {}),
|
|
69
|
+
input.invariants,
|
|
61
70
|
];
|
|
62
71
|
|
|
63
72
|
return {
|
|
@@ -70,7 +79,8 @@ export function buildWriteMarkerStatements(input: WriteMarkerInput): {
|
|
|
70
79
|
canonical_version,
|
|
71
80
|
updated_at,
|
|
72
81
|
app_tag,
|
|
73
|
-
meta
|
|
82
|
+
meta,
|
|
83
|
+
invariants
|
|
74
84
|
) values (
|
|
75
85
|
$1,
|
|
76
86
|
$2,
|
|
@@ -79,11 +89,16 @@ export function buildWriteMarkerStatements(input: WriteMarkerInput): {
|
|
|
79
89
|
$5,
|
|
80
90
|
now(),
|
|
81
91
|
$6,
|
|
82
|
-
$7::jsonb
|
|
92
|
+
$7::jsonb,
|
|
93
|
+
$8::text[]
|
|
83
94
|
)`,
|
|
84
95
|
params,
|
|
85
96
|
},
|
|
86
97
|
update: {
|
|
98
|
+
// `invariants = array(select distinct unnest(invariants || $8::text[]) order by 1)`
|
|
99
|
+
// reads the current column value under the UPDATE's row lock, unions
|
|
100
|
+
// with the incoming array, dedupes, and sorts ascending — single
|
|
101
|
+
// statement, atomic, no read-then-write window.
|
|
87
102
|
sql: `update prisma_contract.marker set
|
|
88
103
|
core_hash = $2,
|
|
89
104
|
profile_hash = $3,
|
|
@@ -91,7 +106,8 @@ export function buildWriteMarkerStatements(input: WriteMarkerInput): {
|
|
|
91
106
|
canonical_version = $5,
|
|
92
107
|
updated_at = now(),
|
|
93
108
|
app_tag = $6,
|
|
94
|
-
meta = $7::jsonb
|
|
109
|
+
meta = $7::jsonb,
|
|
110
|
+
invariants = array(select distinct unnest(invariants || $8::text[]) order by 1)
|
|
95
111
|
where id = $1`,
|
|
96
112
|
params,
|
|
97
113
|
},
|