@prisma-next/target-postgres 0.3.0-dev.6 → 0.3.0-dev.64

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 (46) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +8 -1
  3. package/dist/control.d.mts +16 -0
  4. package/dist/control.d.mts.map +1 -0
  5. package/dist/control.mjs +2947 -0
  6. package/dist/control.mjs.map +1 -0
  7. package/dist/descriptor-meta-DxB8oZzB.mjs +13 -0
  8. package/dist/descriptor-meta-DxB8oZzB.mjs.map +1 -0
  9. package/dist/pack.d.mts +7 -0
  10. package/dist/pack.d.mts.map +1 -0
  11. package/dist/pack.mjs +9 -0
  12. package/dist/pack.mjs.map +1 -0
  13. package/dist/runtime.d.mts +9 -0
  14. package/dist/runtime.d.mts.map +1 -0
  15. package/dist/runtime.mjs +21 -0
  16. package/dist/runtime.mjs.map +1 -0
  17. package/package.json +32 -32
  18. package/src/core/migrations/planner-reconciliation.ts +602 -0
  19. package/src/core/migrations/planner.ts +476 -215
  20. package/src/core/migrations/runner.ts +29 -34
  21. package/src/core/migrations/statement-builders.ts +9 -7
  22. package/src/core/types.ts +5 -0
  23. package/src/exports/control.ts +9 -8
  24. package/src/exports/runtime.ts +7 -12
  25. package/dist/chunk-RKEXRSSI.js +0 -14
  26. package/dist/chunk-RKEXRSSI.js.map +0 -1
  27. package/dist/core/descriptor-meta.d.ts +0 -9
  28. package/dist/core/descriptor-meta.d.ts.map +0 -1
  29. package/dist/core/migrations/planner.d.ts +0 -14
  30. package/dist/core/migrations/planner.d.ts.map +0 -1
  31. package/dist/core/migrations/runner.d.ts +0 -8
  32. package/dist/core/migrations/runner.d.ts.map +0 -1
  33. package/dist/core/migrations/statement-builders.d.ts +0 -30
  34. package/dist/core/migrations/statement-builders.d.ts.map +0 -1
  35. package/dist/exports/control.d.ts +0 -8
  36. package/dist/exports/control.d.ts.map +0 -1
  37. package/dist/exports/control.js +0 -1255
  38. package/dist/exports/control.js.map +0 -1
  39. package/dist/exports/pack.d.ts +0 -4
  40. package/dist/exports/pack.d.ts.map +0 -1
  41. package/dist/exports/pack.js +0 -11
  42. package/dist/exports/pack.js.map +0 -1
  43. package/dist/exports/runtime.d.ts +0 -12
  44. package/dist/exports/runtime.d.ts.map +0 -1
  45. package/dist/exports/runtime.js +0 -19
  46. package/dist/exports/runtime.js.map +0 -1
@@ -1,1255 +0,0 @@
1
- import {
2
- postgresTargetDescriptorMeta
3
- } from "../chunk-RKEXRSSI.js";
4
-
5
- // src/core/migrations/planner.ts
6
- import {
7
- createMigrationPlan,
8
- plannerFailure,
9
- plannerSuccess
10
- } from "@prisma-next/family-sql/control";
11
- import { arraysEqual, verifySqlSchema } from "@prisma-next/family-sql/schema-verify";
12
- var DEFAULT_PLANNER_CONFIG = {
13
- defaultSchema: "public"
14
- };
15
- function createPostgresMigrationPlanner(config = {}) {
16
- return new PostgresMigrationPlanner({
17
- ...DEFAULT_PLANNER_CONFIG,
18
- ...config
19
- });
20
- }
21
- var PostgresMigrationPlanner = class {
22
- constructor(config) {
23
- this.config = config;
24
- }
25
- plan(options) {
26
- const schemaName = options.schemaName ?? this.config.defaultSchema;
27
- const policyResult = this.ensureAdditivePolicy(options.policy);
28
- if (policyResult) {
29
- return policyResult;
30
- }
31
- const classification = this.classifySchema(options);
32
- if (classification.kind === "conflict") {
33
- return plannerFailure(classification.conflicts);
34
- }
35
- const operations = [];
36
- operations.push(
37
- ...this.buildDatabaseDependencyOperations(options),
38
- ...this.buildTableOperations(options.contract.storage.tables, options.schema, schemaName),
39
- ...this.buildColumnOperations(options.contract.storage.tables, options.schema, schemaName),
40
- ...this.buildPrimaryKeyOperations(
41
- options.contract.storage.tables,
42
- options.schema,
43
- schemaName
44
- ),
45
- ...this.buildUniqueOperations(options.contract.storage.tables, options.schema, schemaName),
46
- ...this.buildIndexOperations(options.contract.storage.tables, options.schema, schemaName),
47
- ...this.buildForeignKeyOperations(
48
- options.contract.storage.tables,
49
- options.schema,
50
- schemaName
51
- )
52
- );
53
- const plan = createMigrationPlan({
54
- targetId: "postgres",
55
- origin: null,
56
- destination: {
57
- coreHash: options.contract.coreHash,
58
- ...options.contract.profileHash ? { profileHash: options.contract.profileHash } : {}
59
- },
60
- operations
61
- });
62
- return plannerSuccess(plan);
63
- }
64
- ensureAdditivePolicy(policy) {
65
- if (!policy.allowedOperationClasses.includes("additive")) {
66
- return plannerFailure([
67
- {
68
- kind: "unsupportedOperation",
69
- summary: "Init planner requires additive operations be allowed",
70
- why: 'The init planner only emits additive operations. Update the policy to include "additive".'
71
- }
72
- ]);
73
- }
74
- return null;
75
- }
76
- /**
77
- * Builds migration operations from component-owned database dependencies.
78
- * These operations install database-side persistence structures declared by components.
79
- */
80
- buildDatabaseDependencyOperations(options) {
81
- const dependencies = this.collectDependencies(options);
82
- const operations = [];
83
- const seenDependencyIds = /* @__PURE__ */ new Set();
84
- const seenOperationIds = /* @__PURE__ */ new Set();
85
- for (const dependency of dependencies) {
86
- if (seenDependencyIds.has(dependency.id)) {
87
- continue;
88
- }
89
- seenDependencyIds.add(dependency.id);
90
- const issues = dependency.verifyDatabaseDependencyInstalled(options.schema);
91
- if (issues.length === 0) {
92
- continue;
93
- }
94
- for (const installOp of dependency.install) {
95
- if (seenOperationIds.has(installOp.id)) {
96
- continue;
97
- }
98
- seenOperationIds.add(installOp.id);
99
- operations.push(installOp);
100
- }
101
- }
102
- return operations;
103
- }
104
- collectDependencies(options) {
105
- const components = options.frameworkComponents;
106
- if (components.length === 0) {
107
- return [];
108
- }
109
- const deps = [];
110
- for (const component of components) {
111
- if (!isSqlDependencyProvider(component)) {
112
- continue;
113
- }
114
- const initDeps = component.databaseDependencies?.init;
115
- if (initDeps && initDeps.length > 0) {
116
- deps.push(...initDeps);
117
- }
118
- }
119
- return sortDependencies(deps);
120
- }
121
- buildTableOperations(tables, schema, schemaName) {
122
- const operations = [];
123
- for (const [tableName, table] of sortedEntries(tables)) {
124
- if (schema.tables[tableName]) {
125
- continue;
126
- }
127
- const qualified = qualifyTableName(schemaName, tableName);
128
- operations.push({
129
- id: `table.${tableName}`,
130
- label: `Create table ${tableName}`,
131
- summary: `Creates table ${tableName} with required columns`,
132
- operationClass: "additive",
133
- target: {
134
- id: "postgres",
135
- details: this.buildTargetDetails("table", tableName, schemaName)
136
- },
137
- precheck: [
138
- {
139
- description: `ensure table "${tableName}" does not exist`,
140
- sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, tableName)}) IS NULL`
141
- }
142
- ],
143
- execute: [
144
- {
145
- description: `create table "${tableName}"`,
146
- sql: buildCreateTableSql(qualified, table)
147
- }
148
- ],
149
- postcheck: [
150
- {
151
- description: `verify table "${tableName}" exists`,
152
- sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, tableName)}) IS NOT NULL`
153
- }
154
- ]
155
- });
156
- }
157
- return operations;
158
- }
159
- buildColumnOperations(tables, schema, schemaName) {
160
- const operations = [];
161
- for (const [tableName, table] of sortedEntries(tables)) {
162
- const schemaTable = schema.tables[tableName];
163
- if (!schemaTable) {
164
- continue;
165
- }
166
- for (const [columnName, column] of sortedEntries(table.columns)) {
167
- if (schemaTable.columns[columnName]) {
168
- continue;
169
- }
170
- operations.push(this.buildAddColumnOperation(schemaName, tableName, columnName, column));
171
- }
172
- }
173
- return operations;
174
- }
175
- buildAddColumnOperation(schema, tableName, columnName, column) {
176
- const qualified = qualifyTableName(schema, tableName);
177
- const notNull = column.nullable === false;
178
- const precheck = [
179
- {
180
- description: `ensure column "${columnName}" is missing`,
181
- sql: columnExistsCheck({ schema, table: tableName, column: columnName, exists: false })
182
- },
183
- ...notNull ? [
184
- {
185
- description: `ensure table "${tableName}" is empty before adding NOT NULL column`,
186
- sql: tableIsEmptyCheck(qualified)
187
- }
188
- ] : []
189
- ];
190
- const execute = [
191
- {
192
- description: `add column "${columnName}"`,
193
- sql: buildAddColumnSql(qualified, columnName, column)
194
- }
195
- ];
196
- const postcheck = [
197
- {
198
- description: `verify column "${columnName}" exists`,
199
- sql: columnExistsCheck({ schema, table: tableName, column: columnName })
200
- },
201
- ...notNull ? [
202
- {
203
- description: `verify column "${columnName}" is NOT NULL`,
204
- sql: columnIsNotNullCheck({ schema, table: tableName, column: columnName })
205
- }
206
- ] : []
207
- ];
208
- return {
209
- id: `column.${tableName}.${columnName}`,
210
- label: `Add column ${columnName} to ${tableName}`,
211
- summary: `Adds column ${columnName} to table ${tableName}`,
212
- operationClass: "additive",
213
- target: {
214
- id: "postgres",
215
- details: this.buildTargetDetails("table", tableName, schema)
216
- },
217
- precheck,
218
- execute,
219
- postcheck
220
- };
221
- }
222
- buildPrimaryKeyOperations(tables, schema, schemaName) {
223
- const operations = [];
224
- for (const [tableName, table] of sortedEntries(tables)) {
225
- if (!table.primaryKey) {
226
- continue;
227
- }
228
- const schemaTable = schema.tables[tableName];
229
- if (!schemaTable || schemaTable.primaryKey) {
230
- continue;
231
- }
232
- const constraintName = table.primaryKey.name ?? `${tableName}_pkey`;
233
- operations.push({
234
- id: `primaryKey.${tableName}.${constraintName}`,
235
- label: `Add primary key ${constraintName} on ${tableName}`,
236
- summary: `Adds primary key ${constraintName} on ${tableName}`,
237
- operationClass: "additive",
238
- target: {
239
- id: "postgres",
240
- details: this.buildTargetDetails("table", tableName, schemaName)
241
- },
242
- precheck: [
243
- {
244
- description: `ensure primary key does not exist on "${tableName}"`,
245
- sql: tableHasPrimaryKeyCheck(schemaName, tableName, false)
246
- }
247
- ],
248
- execute: [
249
- {
250
- description: `add primary key "${constraintName}"`,
251
- sql: `ALTER TABLE ${qualifyTableName(schemaName, tableName)}
252
- ADD CONSTRAINT ${quoteIdentifier(constraintName)}
253
- PRIMARY KEY (${table.primaryKey.columns.map(quoteIdentifier).join(", ")})`
254
- }
255
- ],
256
- postcheck: [
257
- {
258
- description: `verify primary key "${constraintName}" exists`,
259
- sql: tableHasPrimaryKeyCheck(schemaName, tableName, true, constraintName)
260
- }
261
- ]
262
- });
263
- }
264
- return operations;
265
- }
266
- buildUniqueOperations(tables, schema, schemaName) {
267
- const operations = [];
268
- for (const [tableName, table] of sortedEntries(tables)) {
269
- const schemaTable = schema.tables[tableName];
270
- for (const unique of table.uniques) {
271
- if (schemaTable && hasUniqueConstraint(schemaTable, unique.columns)) {
272
- continue;
273
- }
274
- const constraintName = unique.name ?? `${tableName}_${unique.columns.join("_")}_key`;
275
- operations.push({
276
- id: `unique.${tableName}.${constraintName}`,
277
- label: `Add unique constraint ${constraintName} on ${tableName}`,
278
- summary: `Adds unique constraint ${constraintName} on ${tableName}`,
279
- operationClass: "additive",
280
- target: {
281
- id: "postgres",
282
- details: this.buildTargetDetails("unique", constraintName, schemaName, tableName)
283
- },
284
- precheck: [
285
- {
286
- description: `ensure unique constraint "${constraintName}" is missing`,
287
- sql: constraintExistsCheck({ constraintName, schema: schemaName, exists: false })
288
- }
289
- ],
290
- execute: [
291
- {
292
- description: `add unique constraint "${constraintName}"`,
293
- sql: `ALTER TABLE ${qualifyTableName(schemaName, tableName)}
294
- ADD CONSTRAINT ${quoteIdentifier(constraintName)}
295
- UNIQUE (${unique.columns.map(quoteIdentifier).join(", ")})`
296
- }
297
- ],
298
- postcheck: [
299
- {
300
- description: `verify unique constraint "${constraintName}" exists`,
301
- sql: constraintExistsCheck({ constraintName, schema: schemaName })
302
- }
303
- ]
304
- });
305
- }
306
- }
307
- return operations;
308
- }
309
- buildIndexOperations(tables, schema, schemaName) {
310
- const operations = [];
311
- for (const [tableName, table] of sortedEntries(tables)) {
312
- const schemaTable = schema.tables[tableName];
313
- for (const index of table.indexes) {
314
- if (schemaTable && hasIndex(schemaTable, index.columns)) {
315
- continue;
316
- }
317
- const indexName = index.name ?? `${tableName}_${index.columns.join("_")}_idx`;
318
- operations.push({
319
- id: `index.${tableName}.${indexName}`,
320
- label: `Create index ${indexName} on ${tableName}`,
321
- summary: `Creates index ${indexName} on ${tableName}`,
322
- operationClass: "additive",
323
- target: {
324
- id: "postgres",
325
- details: this.buildTargetDetails("index", indexName, schemaName, tableName)
326
- },
327
- precheck: [
328
- {
329
- description: `ensure index "${indexName}" is missing`,
330
- sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NULL`
331
- }
332
- ],
333
- execute: [
334
- {
335
- description: `create index "${indexName}"`,
336
- sql: `CREATE INDEX ${quoteIdentifier(indexName)} ON ${qualifyTableName(
337
- schemaName,
338
- tableName
339
- )} (${index.columns.map(quoteIdentifier).join(", ")})`
340
- }
341
- ],
342
- postcheck: [
343
- {
344
- description: `verify index "${indexName}" exists`,
345
- sql: `SELECT to_regclass(${toRegclassLiteral(schemaName, indexName)}) IS NOT NULL`
346
- }
347
- ]
348
- });
349
- }
350
- }
351
- return operations;
352
- }
353
- buildForeignKeyOperations(tables, schema, schemaName) {
354
- const operations = [];
355
- for (const [tableName, table] of sortedEntries(tables)) {
356
- const schemaTable = schema.tables[tableName];
357
- for (const foreignKey of table.foreignKeys) {
358
- if (schemaTable && hasForeignKey(schemaTable, foreignKey)) {
359
- continue;
360
- }
361
- const fkName = foreignKey.name ?? `${tableName}_${foreignKey.columns.join("_")}_fkey`;
362
- operations.push({
363
- id: `foreignKey.${tableName}.${fkName}`,
364
- label: `Add foreign key ${fkName} on ${tableName}`,
365
- summary: `Adds foreign key ${fkName} referencing ${foreignKey.references.table}`,
366
- operationClass: "additive",
367
- target: {
368
- id: "postgres",
369
- details: this.buildTargetDetails("foreignKey", fkName, schemaName, tableName)
370
- },
371
- precheck: [
372
- {
373
- description: `ensure foreign key "${fkName}" is missing`,
374
- sql: constraintExistsCheck({
375
- constraintName: fkName,
376
- schema: schemaName,
377
- exists: false
378
- })
379
- }
380
- ],
381
- execute: [
382
- {
383
- description: `add foreign key "${fkName}"`,
384
- sql: `ALTER TABLE ${qualifyTableName(schemaName, tableName)}
385
- ADD CONSTRAINT ${quoteIdentifier(fkName)}
386
- FOREIGN KEY (${foreignKey.columns.map(quoteIdentifier).join(", ")})
387
- REFERENCES ${qualifyTableName(schemaName, foreignKey.references.table)} (${foreignKey.references.columns.map(quoteIdentifier).join(", ")})`
388
- }
389
- ],
390
- postcheck: [
391
- {
392
- description: `verify foreign key "${fkName}" exists`,
393
- sql: constraintExistsCheck({ constraintName: fkName, schema: schemaName })
394
- }
395
- ]
396
- });
397
- }
398
- }
399
- return operations;
400
- }
401
- buildTargetDetails(objectType, name, schema, table) {
402
- return {
403
- schema,
404
- objectType,
405
- name,
406
- ...table ? { table } : {}
407
- };
408
- }
409
- classifySchema(options) {
410
- const verifyOptions = {
411
- contract: options.contract,
412
- schema: options.schema,
413
- strict: false,
414
- typeMetadataRegistry: /* @__PURE__ */ new Map(),
415
- frameworkComponents: options.frameworkComponents
416
- };
417
- const verifyResult = verifySqlSchema(verifyOptions);
418
- const conflicts = this.extractConflicts(verifyResult.schema.issues);
419
- if (conflicts.length > 0) {
420
- return { kind: "conflict", conflicts };
421
- }
422
- return { kind: "ok" };
423
- }
424
- extractConflicts(issues) {
425
- const conflicts = [];
426
- for (const issue of issues) {
427
- if (isAdditiveIssue(issue)) {
428
- continue;
429
- }
430
- const conflict = this.convertIssueToConflict(issue);
431
- if (conflict) {
432
- conflicts.push(conflict);
433
- }
434
- }
435
- return conflicts.sort(conflictComparator);
436
- }
437
- convertIssueToConflict(issue) {
438
- switch (issue.kind) {
439
- case "type_mismatch":
440
- return this.buildConflict("typeMismatch", issue);
441
- case "nullability_mismatch":
442
- return this.buildConflict("nullabilityConflict", issue);
443
- case "primary_key_mismatch":
444
- return this.buildConflict("indexIncompatible", issue);
445
- case "unique_constraint_mismatch":
446
- return this.buildConflict("indexIncompatible", issue);
447
- case "index_mismatch":
448
- return this.buildConflict("indexIncompatible", issue);
449
- case "foreign_key_mismatch":
450
- return this.buildConflict("foreignKeyConflict", issue);
451
- default:
452
- return null;
453
- }
454
- }
455
- buildConflict(kind, issue) {
456
- const location = buildConflictLocation(issue);
457
- const meta = issue.expected || issue.actual ? Object.freeze({
458
- ...issue.expected ? { expected: issue.expected } : {},
459
- ...issue.actual ? { actual: issue.actual } : {}
460
- }) : void 0;
461
- return {
462
- kind,
463
- summary: issue.message,
464
- ...location ? { location } : {},
465
- ...meta ? { meta } : {}
466
- };
467
- }
468
- };
469
- function isSqlDependencyProvider(component) {
470
- if (typeof component !== "object" || component === null) {
471
- return false;
472
- }
473
- const record = component;
474
- if (Object.hasOwn(record, "familyId") && record["familyId"] !== "sql") {
475
- return false;
476
- }
477
- if (!Object.hasOwn(record, "databaseDependencies")) {
478
- return false;
479
- }
480
- const deps = record["databaseDependencies"];
481
- return deps === void 0 || typeof deps === "object" && deps !== null;
482
- }
483
- function sortDependencies(dependencies) {
484
- if (dependencies.length <= 1) {
485
- return dependencies;
486
- }
487
- return [...dependencies].sort((a, b) => a.id.localeCompare(b.id));
488
- }
489
- function buildCreateTableSql(qualifiedTableName, table) {
490
- const columnDefinitions = Object.entries(table.columns).map(
491
- ([columnName, column]) => {
492
- const parts = [
493
- quoteIdentifier(columnName),
494
- column.nativeType,
495
- column.nullable ? "" : "NOT NULL"
496
- ].filter(Boolean);
497
- return parts.join(" ");
498
- }
499
- );
500
- const constraintDefinitions = [];
501
- if (table.primaryKey) {
502
- constraintDefinitions.push(
503
- `PRIMARY KEY (${table.primaryKey.columns.map(quoteIdentifier).join(", ")})`
504
- );
505
- }
506
- const allDefinitions = [...columnDefinitions, ...constraintDefinitions];
507
- return `CREATE TABLE ${qualifiedTableName} (
508
- ${allDefinitions.join(",\n ")}
509
- )`;
510
- }
511
- function qualifyTableName(schema, table) {
512
- return `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
513
- }
514
- function toRegclassLiteral(schema, name) {
515
- const regclass = `${quoteIdentifier(schema)}.${quoteIdentifier(name)}`;
516
- return `'${escapeLiteral(regclass)}'`;
517
- }
518
- function quoteIdentifier(identifier) {
519
- return `"${identifier.replace(/"/g, '""')}"`;
520
- }
521
- function escapeLiteral(value) {
522
- return value.replace(/'/g, "''");
523
- }
524
- function sortedEntries(record) {
525
- return Object.entries(record).sort(([a], [b]) => a.localeCompare(b));
526
- }
527
- function constraintExistsCheck({
528
- constraintName,
529
- schema,
530
- exists = true
531
- }) {
532
- const existsClause = exists ? "EXISTS" : "NOT EXISTS";
533
- return `SELECT ${existsClause} (
534
- SELECT 1 FROM pg_constraint c
535
- JOIN pg_namespace n ON c.connamespace = n.oid
536
- WHERE c.conname = '${escapeLiteral(constraintName)}'
537
- AND n.nspname = '${escapeLiteral(schema)}'
538
- )`;
539
- }
540
- function columnExistsCheck({
541
- schema,
542
- table,
543
- column,
544
- exists = true
545
- }) {
546
- const existsClause = exists ? "" : "NOT ";
547
- return `SELECT ${existsClause}EXISTS (
548
- SELECT 1
549
- FROM information_schema.columns
550
- WHERE table_schema = '${escapeLiteral(schema)}'
551
- AND table_name = '${escapeLiteral(table)}'
552
- AND column_name = '${escapeLiteral(column)}'
553
- )`;
554
- }
555
- function columnIsNotNullCheck({
556
- schema,
557
- table,
558
- column
559
- }) {
560
- return `SELECT EXISTS (
561
- SELECT 1
562
- FROM information_schema.columns
563
- WHERE table_schema = '${escapeLiteral(schema)}'
564
- AND table_name = '${escapeLiteral(table)}'
565
- AND column_name = '${escapeLiteral(column)}'
566
- AND is_nullable = 'NO'
567
- )`;
568
- }
569
- function tableIsEmptyCheck(qualifiedTableName) {
570
- return `SELECT NOT EXISTS (SELECT 1 FROM ${qualifiedTableName} LIMIT 1)`;
571
- }
572
- function buildAddColumnSql(qualifiedTableName, columnName, column) {
573
- const parts = [
574
- `ALTER TABLE ${qualifiedTableName}`,
575
- `ADD COLUMN ${quoteIdentifier(columnName)} ${column.nativeType}`,
576
- column.nullable ? "" : "NOT NULL"
577
- ].filter(Boolean);
578
- return parts.join(" ");
579
- }
580
- function tableHasPrimaryKeyCheck(schema, table, exists, constraintName) {
581
- const comparison = exists ? "" : "NOT ";
582
- const constraintFilter = constraintName ? `AND c2.relname = '${escapeLiteral(constraintName)}'` : "";
583
- return `SELECT ${comparison}EXISTS (
584
- SELECT 1
585
- FROM pg_index i
586
- JOIN pg_class c ON c.oid = i.indrelid
587
- JOIN pg_namespace n ON n.oid = c.relnamespace
588
- LEFT JOIN pg_class c2 ON c2.oid = i.indexrelid
589
- WHERE n.nspname = '${escapeLiteral(schema)}'
590
- AND c.relname = '${escapeLiteral(table)}'
591
- AND i.indisprimary
592
- ${constraintFilter}
593
- )`;
594
- }
595
- function hasUniqueConstraint(table, columns) {
596
- return table.uniques.some((unique) => arraysEqual(unique.columns, columns));
597
- }
598
- function hasIndex(table, columns) {
599
- return table.indexes.some((index) => !index.unique && arraysEqual(index.columns, columns));
600
- }
601
- function hasForeignKey(table, fk) {
602
- return table.foreignKeys.some(
603
- (candidate) => arraysEqual(candidate.columns, fk.columns) && candidate.referencedTable === fk.references.table && arraysEqual(candidate.referencedColumns, fk.references.columns)
604
- );
605
- }
606
- function isAdditiveIssue(issue) {
607
- switch (issue.kind) {
608
- case "missing_table":
609
- case "missing_column":
610
- case "extension_missing":
611
- return true;
612
- case "primary_key_mismatch":
613
- return issue.actual === void 0;
614
- case "unique_constraint_mismatch":
615
- case "index_mismatch":
616
- case "foreign_key_mismatch":
617
- return issue.indexOrConstraint === void 0;
618
- default:
619
- return false;
620
- }
621
- }
622
- function buildConflictLocation(issue) {
623
- const location = {};
624
- if (issue.table) {
625
- location.table = issue.table;
626
- }
627
- if (issue.column) {
628
- location.column = issue.column;
629
- }
630
- if (issue.indexOrConstraint) {
631
- location.constraint = issue.indexOrConstraint;
632
- }
633
- return Object.keys(location).length > 0 ? location : void 0;
634
- }
635
- function conflictComparator(a, b) {
636
- if (a.kind !== b.kind) {
637
- return a.kind < b.kind ? -1 : 1;
638
- }
639
- const aLocation = a.location ?? {};
640
- const bLocation = b.location ?? {};
641
- const tableCompare = compareStrings(aLocation.table, bLocation.table);
642
- if (tableCompare !== 0) {
643
- return tableCompare;
644
- }
645
- const columnCompare = compareStrings(aLocation.column, bLocation.column);
646
- if (columnCompare !== 0) {
647
- return columnCompare;
648
- }
649
- const constraintCompare = compareStrings(aLocation.constraint, bLocation.constraint);
650
- if (constraintCompare !== 0) {
651
- return constraintCompare;
652
- }
653
- return compareStrings(a.summary, b.summary);
654
- }
655
- function compareStrings(a, b) {
656
- if (a === b) {
657
- return 0;
658
- }
659
- if (a === void 0) {
660
- return -1;
661
- }
662
- if (b === void 0) {
663
- return 1;
664
- }
665
- return a < b ? -1 : 1;
666
- }
667
-
668
- // src/core/migrations/runner.ts
669
- import { runnerFailure, runnerSuccess } from "@prisma-next/family-sql/control";
670
- import { verifySqlSchema as verifySqlSchema2 } from "@prisma-next/family-sql/schema-verify";
671
- import { readMarker } from "@prisma-next/family-sql/verify";
672
- import { SqlQueryError } from "@prisma-next/sql-errors";
673
- import { ok, okVoid } from "@prisma-next/utils/result";
674
-
675
- // src/core/migrations/statement-builders.ts
676
- var ensurePrismaContractSchemaStatement = {
677
- sql: "create schema if not exists prisma_contract",
678
- params: []
679
- };
680
- var ensureMarkerTableStatement = {
681
- sql: `create table if not exists prisma_contract.marker (
682
- id smallint primary key default 1,
683
- core_hash text not null,
684
- profile_hash text not null,
685
- contract_json jsonb,
686
- canonical_version int,
687
- updated_at timestamptz not null default now(),
688
- app_tag text,
689
- meta jsonb not null default '{}'
690
- )`,
691
- params: []
692
- };
693
- var ensureLedgerTableStatement = {
694
- sql: `create table if not exists prisma_contract.ledger (
695
- id bigserial primary key,
696
- created_at timestamptz not null default now(),
697
- origin_core_hash text,
698
- origin_profile_hash text,
699
- destination_core_hash text not null,
700
- destination_profile_hash text,
701
- contract_json_before jsonb,
702
- contract_json_after jsonb,
703
- operations jsonb not null
704
- )`,
705
- params: []
706
- };
707
- function buildWriteMarkerStatements(input) {
708
- const params = [
709
- 1,
710
- input.coreHash,
711
- input.profileHash,
712
- jsonParam(input.contractJson),
713
- input.canonicalVersion ?? null,
714
- input.appTag ?? null,
715
- jsonParam(input.meta ?? {})
716
- ];
717
- return {
718
- insert: {
719
- sql: `insert into prisma_contract.marker (
720
- id,
721
- core_hash,
722
- profile_hash,
723
- contract_json,
724
- canonical_version,
725
- updated_at,
726
- app_tag,
727
- meta
728
- ) values (
729
- $1,
730
- $2,
731
- $3,
732
- $4::jsonb,
733
- $5,
734
- now(),
735
- $6,
736
- $7::jsonb
737
- )`,
738
- params
739
- },
740
- update: {
741
- sql: `update prisma_contract.marker set
742
- core_hash = $2,
743
- profile_hash = $3,
744
- contract_json = $4::jsonb,
745
- canonical_version = $5,
746
- updated_at = now(),
747
- app_tag = $6,
748
- meta = $7::jsonb
749
- where id = $1`,
750
- params
751
- }
752
- };
753
- }
754
- function buildLedgerInsertStatement(input) {
755
- return {
756
- sql: `insert into prisma_contract.ledger (
757
- origin_core_hash,
758
- origin_profile_hash,
759
- destination_core_hash,
760
- destination_profile_hash,
761
- contract_json_before,
762
- contract_json_after,
763
- operations
764
- ) values (
765
- $1,
766
- $2,
767
- $3,
768
- $4,
769
- $5::jsonb,
770
- $6::jsonb,
771
- $7::jsonb
772
- )`,
773
- params: [
774
- input.originCoreHash ?? null,
775
- input.originProfileHash ?? null,
776
- input.destinationCoreHash,
777
- input.destinationProfileHash ?? null,
778
- jsonParam(input.contractJsonBefore),
779
- jsonParam(input.contractJsonAfter),
780
- jsonParam(input.operations)
781
- ]
782
- };
783
- }
784
- function jsonParam(value) {
785
- return JSON.stringify(value ?? null);
786
- }
787
-
788
- // src/core/migrations/runner.ts
789
- var DEFAULT_CONFIG = {
790
- defaultSchema: "public"
791
- };
792
- var LOCK_DOMAIN = "prisma_next.contract.marker";
793
- function cloneAndFreezeRecord(value) {
794
- const cloned = {};
795
- for (const [key, val] of Object.entries(value)) {
796
- if (val === null || val === void 0) {
797
- cloned[key] = val;
798
- } else if (Array.isArray(val)) {
799
- cloned[key] = Object.freeze([...val]);
800
- } else if (typeof val === "object") {
801
- cloned[key] = cloneAndFreezeRecord(val);
802
- } else {
803
- cloned[key] = val;
804
- }
805
- }
806
- return Object.freeze(cloned);
807
- }
808
- function createPostgresMigrationRunner(family, config = {}) {
809
- return new PostgresMigrationRunner(family, { ...DEFAULT_CONFIG, ...config });
810
- }
811
- var PostgresMigrationRunner = class {
812
- constructor(family, config) {
813
- this.family = family;
814
- this.config = config;
815
- }
816
- async execute(options) {
817
- const schema = options.schemaName ?? this.config.defaultSchema;
818
- const driver = options.driver;
819
- const lockKey = `${LOCK_DOMAIN}:${schema}`;
820
- const destinationCheck = this.ensurePlanMatchesDestinationContract(
821
- options.plan.destination,
822
- options.destinationContract
823
- );
824
- if (!destinationCheck.ok) {
825
- return destinationCheck;
826
- }
827
- const policyCheck = this.enforcePolicyCompatibility(options.policy, options.plan.operations);
828
- if (!policyCheck.ok) {
829
- return policyCheck;
830
- }
831
- await this.beginTransaction(driver);
832
- let committed = false;
833
- try {
834
- await this.acquireLock(driver, lockKey);
835
- await this.ensureControlTables(driver);
836
- const existingMarker = await readMarker(driver);
837
- const markerCheck = this.ensureMarkerCompatibility(existingMarker, options.plan);
838
- if (!markerCheck.ok) {
839
- return markerCheck;
840
- }
841
- const markerAtDestination = this.markerMatchesDestination(existingMarker, options.plan);
842
- let applyValue;
843
- if (markerAtDestination) {
844
- applyValue = { operationsExecuted: 0, executedOperations: [] };
845
- } else {
846
- const applyResult = await this.applyPlan(driver, options);
847
- if (!applyResult.ok) {
848
- return applyResult;
849
- }
850
- applyValue = applyResult.value;
851
- }
852
- const schemaIR = await this.family.introspect({
853
- driver,
854
- contractIR: options.destinationContract
855
- });
856
- const schemaVerifyResult = verifySqlSchema2({
857
- contract: options.destinationContract,
858
- schema: schemaIR,
859
- strict: options.strictVerification ?? true,
860
- context: options.context ?? {},
861
- typeMetadataRegistry: this.family.typeMetadataRegistry,
862
- frameworkComponents: options.frameworkComponents
863
- });
864
- if (!schemaVerifyResult.ok) {
865
- return runnerFailure("SCHEMA_VERIFY_FAILED", schemaVerifyResult.summary, {
866
- why: "The resulting database schema does not satisfy the destination contract.",
867
- meta: {
868
- issues: schemaVerifyResult.schema.issues
869
- }
870
- });
871
- }
872
- await this.upsertMarker(driver, options, existingMarker);
873
- await this.recordLedgerEntry(driver, options, existingMarker, applyValue.executedOperations);
874
- await this.commitTransaction(driver);
875
- committed = true;
876
- return runnerSuccess({
877
- operationsPlanned: options.plan.operations.length,
878
- operationsExecuted: applyValue.operationsExecuted
879
- });
880
- } finally {
881
- if (!committed) {
882
- await this.rollbackTransaction(driver);
883
- }
884
- }
885
- }
886
- async applyPlan(driver, options) {
887
- const checks = options.executionChecks;
888
- const runPrechecks = checks?.prechecks !== false;
889
- const runPostchecks = checks?.postchecks !== false;
890
- const runIdempotency = checks?.idempotencyChecks !== false;
891
- let operationsExecuted = 0;
892
- const executedOperations = [];
893
- for (const operation of options.plan.operations) {
894
- options.callbacks?.onOperationStart?.(operation);
895
- try {
896
- if (runPostchecks && runIdempotency) {
897
- const postcheckAlreadySatisfied = await this.expectationsAreSatisfied(
898
- driver,
899
- operation.postcheck
900
- );
901
- if (postcheckAlreadySatisfied) {
902
- executedOperations.push(this.createPostcheckPreSatisfiedSkipRecord(operation));
903
- continue;
904
- }
905
- }
906
- if (runPrechecks) {
907
- const precheckResult = await this.runExpectationSteps(
908
- driver,
909
- operation.precheck,
910
- operation,
911
- "precheck"
912
- );
913
- if (!precheckResult.ok) {
914
- return precheckResult;
915
- }
916
- }
917
- const executeResult = await this.runExecuteSteps(driver, operation.execute, operation);
918
- if (!executeResult.ok) {
919
- return executeResult;
920
- }
921
- if (runPostchecks) {
922
- const postcheckResult = await this.runExpectationSteps(
923
- driver,
924
- operation.postcheck,
925
- operation,
926
- "postcheck"
927
- );
928
- if (!postcheckResult.ok) {
929
- return postcheckResult;
930
- }
931
- }
932
- executedOperations.push(operation);
933
- operationsExecuted += 1;
934
- } finally {
935
- options.callbacks?.onOperationComplete?.(operation);
936
- }
937
- }
938
- return ok({ operationsExecuted, executedOperations });
939
- }
940
- async ensureControlTables(driver) {
941
- await this.executeStatement(driver, ensurePrismaContractSchemaStatement);
942
- await this.executeStatement(driver, ensureMarkerTableStatement);
943
- await this.executeStatement(driver, ensureLedgerTableStatement);
944
- }
945
- async runExpectationSteps(driver, steps, operation, phase) {
946
- for (const step of steps) {
947
- const result = await driver.query(step.sql);
948
- if (!this.stepResultIsTrue(result.rows)) {
949
- const code = phase === "precheck" ? "PRECHECK_FAILED" : "POSTCHECK_FAILED";
950
- return runnerFailure(
951
- code,
952
- `Operation ${operation.id} failed during ${phase}: ${step.description}`,
953
- {
954
- meta: {
955
- operationId: operation.id,
956
- phase,
957
- stepDescription: step.description
958
- }
959
- }
960
- );
961
- }
962
- }
963
- return okVoid();
964
- }
965
- async runExecuteSteps(driver, steps, operation) {
966
- for (const step of steps) {
967
- try {
968
- await driver.query(step.sql);
969
- } catch (error) {
970
- if (SqlQueryError.is(error)) {
971
- return runnerFailure(
972
- "EXECUTION_FAILED",
973
- `Operation ${operation.id} failed during execution: ${step.description}`,
974
- {
975
- why: error.message,
976
- meta: {
977
- operationId: operation.id,
978
- stepDescription: step.description,
979
- sql: step.sql,
980
- sqlState: error.sqlState,
981
- constraint: error.constraint,
982
- table: error.table,
983
- column: error.column,
984
- detail: error.detail
985
- }
986
- }
987
- );
988
- }
989
- throw error;
990
- }
991
- }
992
- return okVoid();
993
- }
994
- stepResultIsTrue(rows) {
995
- if (!rows || rows.length === 0) {
996
- return false;
997
- }
998
- const firstRow = rows[0];
999
- const firstValue = firstRow ? Object.values(firstRow)[0] : void 0;
1000
- if (typeof firstValue === "boolean") {
1001
- return firstValue;
1002
- }
1003
- if (typeof firstValue === "number") {
1004
- return firstValue !== 0;
1005
- }
1006
- if (typeof firstValue === "string") {
1007
- const lower = firstValue.toLowerCase();
1008
- if (lower === "t" || lower === "true" || lower === "1") {
1009
- return true;
1010
- }
1011
- if (lower === "f" || lower === "false" || lower === "0") {
1012
- return false;
1013
- }
1014
- return firstValue.length > 0;
1015
- }
1016
- return Boolean(firstValue);
1017
- }
1018
- async expectationsAreSatisfied(driver, steps) {
1019
- if (steps.length === 0) {
1020
- return false;
1021
- }
1022
- for (const step of steps) {
1023
- const result = await driver.query(step.sql);
1024
- if (!this.stepResultIsTrue(result.rows)) {
1025
- return false;
1026
- }
1027
- }
1028
- return true;
1029
- }
1030
- createPostcheckPreSatisfiedSkipRecord(operation) {
1031
- const clonedMeta = operation.meta ? cloneAndFreezeRecord(operation.meta) : void 0;
1032
- const runnerMeta = Object.freeze({
1033
- skipped: true,
1034
- reason: "postcheck_pre_satisfied"
1035
- });
1036
- const mergedMeta = Object.freeze({
1037
- ...clonedMeta ?? {},
1038
- runner: runnerMeta
1039
- });
1040
- const frozenPostcheck = Object.freeze([...operation.postcheck]);
1041
- return Object.freeze({
1042
- id: operation.id,
1043
- label: operation.label,
1044
- ...operation.summary ? { summary: operation.summary } : {},
1045
- operationClass: operation.operationClass,
1046
- target: operation.target,
1047
- // Already frozen from plan creation
1048
- precheck: Object.freeze([]),
1049
- execute: Object.freeze([]),
1050
- postcheck: frozenPostcheck,
1051
- ...operation.meta || mergedMeta ? { meta: mergedMeta } : {}
1052
- });
1053
- }
1054
- markerMatchesDestination(marker, plan) {
1055
- if (!marker) {
1056
- return false;
1057
- }
1058
- if (marker.coreHash !== plan.destination.coreHash) {
1059
- return false;
1060
- }
1061
- if (plan.destination.profileHash && marker.profileHash !== plan.destination.profileHash) {
1062
- return false;
1063
- }
1064
- return true;
1065
- }
1066
- enforcePolicyCompatibility(policy, operations) {
1067
- const allowedClasses = new Set(policy.allowedOperationClasses);
1068
- for (const operation of operations) {
1069
- if (!allowedClasses.has(operation.operationClass)) {
1070
- return runnerFailure(
1071
- "POLICY_VIOLATION",
1072
- `Operation ${operation.id} has class "${operation.operationClass}" which is not allowed by policy.`,
1073
- {
1074
- why: `Policy only allows: ${policy.allowedOperationClasses.join(", ")}.`,
1075
- meta: {
1076
- operationId: operation.id,
1077
- operationClass: operation.operationClass,
1078
- allowedClasses: policy.allowedOperationClasses
1079
- }
1080
- }
1081
- );
1082
- }
1083
- }
1084
- return okVoid();
1085
- }
1086
- ensureMarkerCompatibility(marker, plan) {
1087
- const origin = plan.origin ?? null;
1088
- if (!origin) {
1089
- if (!marker) {
1090
- return okVoid();
1091
- }
1092
- if (this.markerMatchesDestination(marker, plan)) {
1093
- return okVoid();
1094
- }
1095
- return runnerFailure(
1096
- "MARKER_ORIGIN_MISMATCH",
1097
- `Existing contract marker (${marker.coreHash}) does not match plan origin (no marker expected).`,
1098
- {
1099
- meta: {
1100
- markerCoreHash: marker.coreHash,
1101
- expectedOrigin: null
1102
- }
1103
- }
1104
- );
1105
- }
1106
- if (!marker) {
1107
- return runnerFailure(
1108
- "MARKER_ORIGIN_MISMATCH",
1109
- `Missing contract marker: expected origin core hash ${origin.coreHash}.`,
1110
- {
1111
- meta: {
1112
- expectedOriginCoreHash: origin.coreHash
1113
- }
1114
- }
1115
- );
1116
- }
1117
- if (marker.coreHash !== origin.coreHash) {
1118
- return runnerFailure(
1119
- "MARKER_ORIGIN_MISMATCH",
1120
- `Existing contract marker (${marker.coreHash}) does not match plan origin (${origin.coreHash}).`,
1121
- {
1122
- meta: {
1123
- markerCoreHash: marker.coreHash,
1124
- expectedOriginCoreHash: origin.coreHash
1125
- }
1126
- }
1127
- );
1128
- }
1129
- if (origin.profileHash && marker.profileHash !== origin.profileHash) {
1130
- return runnerFailure(
1131
- "MARKER_ORIGIN_MISMATCH",
1132
- `Existing contract marker profile hash (${marker.profileHash}) does not match plan origin profile hash (${origin.profileHash}).`,
1133
- {
1134
- meta: {
1135
- markerProfileHash: marker.profileHash,
1136
- expectedOriginProfileHash: origin.profileHash
1137
- }
1138
- }
1139
- );
1140
- }
1141
- return okVoid();
1142
- }
1143
- ensurePlanMatchesDestinationContract(destination, contract) {
1144
- if (destination.coreHash !== contract.coreHash) {
1145
- return runnerFailure(
1146
- "DESTINATION_CONTRACT_MISMATCH",
1147
- `Plan destination core hash (${destination.coreHash}) does not match provided contract core hash (${contract.coreHash}).`,
1148
- {
1149
- meta: {
1150
- planCoreHash: destination.coreHash,
1151
- contractCoreHash: contract.coreHash
1152
- }
1153
- }
1154
- );
1155
- }
1156
- if (destination.profileHash && contract.profileHash && destination.profileHash !== contract.profileHash) {
1157
- return runnerFailure(
1158
- "DESTINATION_CONTRACT_MISMATCH",
1159
- `Plan destination profile hash (${destination.profileHash}) does not match provided contract profile hash (${contract.profileHash}).`,
1160
- {
1161
- meta: {
1162
- planProfileHash: destination.profileHash,
1163
- contractProfileHash: contract.profileHash
1164
- }
1165
- }
1166
- );
1167
- }
1168
- return okVoid();
1169
- }
1170
- async upsertMarker(driver, options, existingMarker) {
1171
- const writeStatements = buildWriteMarkerStatements({
1172
- coreHash: options.plan.destination.coreHash,
1173
- profileHash: options.plan.destination.profileHash ?? options.destinationContract.profileHash ?? options.plan.destination.coreHash,
1174
- contractJson: options.destinationContract,
1175
- canonicalVersion: null,
1176
- meta: {}
1177
- });
1178
- const statement = existingMarker ? writeStatements.update : writeStatements.insert;
1179
- await this.executeStatement(driver, statement);
1180
- }
1181
- async recordLedgerEntry(driver, options, existingMarker, executedOperations) {
1182
- const ledgerStatement = buildLedgerInsertStatement({
1183
- originCoreHash: existingMarker?.coreHash ?? null,
1184
- originProfileHash: existingMarker?.profileHash ?? null,
1185
- destinationCoreHash: options.plan.destination.coreHash,
1186
- destinationProfileHash: options.plan.destination.profileHash ?? options.destinationContract.profileHash ?? options.plan.destination.coreHash,
1187
- contractJsonBefore: existingMarker?.contractJson ?? null,
1188
- contractJsonAfter: options.destinationContract,
1189
- operations: executedOperations
1190
- });
1191
- await this.executeStatement(driver, ledgerStatement);
1192
- }
1193
- async acquireLock(driver, key) {
1194
- await driver.query("select pg_advisory_xact_lock(hashtext($1))", [key]);
1195
- }
1196
- async beginTransaction(driver) {
1197
- await driver.query("BEGIN");
1198
- }
1199
- async commitTransaction(driver) {
1200
- await driver.query("COMMIT");
1201
- }
1202
- async rollbackTransaction(driver) {
1203
- await driver.query("ROLLBACK");
1204
- }
1205
- async executeStatement(driver, statement) {
1206
- if (statement.params.length > 0) {
1207
- await driver.query(statement.sql, statement.params);
1208
- return;
1209
- }
1210
- await driver.query(statement.sql);
1211
- }
1212
- };
1213
-
1214
- // src/exports/control.ts
1215
- var postgresTargetDescriptor = {
1216
- ...postgresTargetDescriptorMeta,
1217
- /**
1218
- * Migrations capability for CLI to access planner/runner via core types.
1219
- * The SQL-specific planner/runner types are compatible with the generic
1220
- * MigrationPlanner/MigrationRunner interfaces at runtime.
1221
- */
1222
- migrations: {
1223
- createPlanner(_family) {
1224
- return createPostgresMigrationPlanner();
1225
- },
1226
- createRunner(family) {
1227
- return createPostgresMigrationRunner(family);
1228
- }
1229
- },
1230
- create() {
1231
- return {
1232
- familyId: "sql",
1233
- targetId: "postgres"
1234
- };
1235
- },
1236
- /**
1237
- * Direct method for SQL-specific usage.
1238
- * @deprecated Use migrations.createPlanner() for CLI compatibility.
1239
- */
1240
- createPlanner(_family) {
1241
- return createPostgresMigrationPlanner();
1242
- },
1243
- /**
1244
- * Direct method for SQL-specific usage.
1245
- * @deprecated Use migrations.createRunner() for CLI compatibility.
1246
- */
1247
- createRunner(family) {
1248
- return createPostgresMigrationRunner(family);
1249
- }
1250
- };
1251
- var control_default = postgresTargetDescriptor;
1252
- export {
1253
- control_default as default
1254
- };
1255
- //# sourceMappingURL=control.js.map