@pgpmjs/transform 0.21.0 → 0.22.0

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/esm/index.js CHANGED
@@ -11,7 +11,7 @@ export { PartitionCycleError, partitionUnits, RESIDUAL_UNIT_PATH, } from './part
11
11
  export { emitSqlProgram, parseSqlProgram } from './program';
12
12
  export { classifyScript, isStubScript, regenerateScripts, } from './regen';
13
13
  export { EXPORT_GRANULARITIES, isExportGranularity, restructureExportRows } from './restructure';
14
- export { diffChangeSets, diffSchemas, } from './semantic-diff-driver';
14
+ export { coverChanges, diffChangeSets, diffSchemas, } from './semantic-diff-driver';
15
15
  export { nameUnnamedConstraints, subObjectIdentityOf } from './sub-object';
16
16
  export * from '@pgsql/transform';
17
17
  export { loadModule } from 'plpgsql-parser';
@@ -1,10 +1,4 @@
1
- /**
2
- * Module ingestion for the dials pipeline: load an on-disk pgpm module into
3
- * the flattened, classified change list the transform/partition drivers
4
- * consume. `pgpm transform` uses this today; a future `pgpm import` (dumps)
5
- * and `pgpm diff` are expected to reuse the same seam.
6
- */
7
- import { parsePgpmHeader, parsePlanFile, readScript } from '@pgpmjs/ast';
1
+ import { parsePgpmHeader, parsePlanFile, readScript, resolveReference } from '@pgpmjs/ast';
8
2
  import * as path from 'path';
9
3
  const TX_LINE = /^\s*(BEGIN|COMMIT|ROLLBACK)\s*;\s*$/i;
10
4
  /**
@@ -17,6 +11,31 @@ export const stripTransactionWrapper = (sql) => sql
17
11
  .filter(line => !TX_LINE.test(line))
18
12
  .join('\n')
19
13
  .trim();
14
+ /**
15
+ * Resolve a plan dependency token to a concrete change name.
16
+ *
17
+ * Non-tag tokens (a bare change name, or a `pkg:change` cross-package name)
18
+ * pass through unchanged — they are already the identity the ledger and
19
+ * generated plans record. A tag token (`@tag`) is resolved to its change via
20
+ * the plan that defines it, so a dependency written against a tag is not
21
+ * re-emitted verbatim into a generated plan or a ledger row. Cross-package
22
+ * tags (`pkg:@tag`) cannot be resolved from a single module's plan and are
23
+ * left as-is with a warning; the resolver returns them unchanged. Anything
24
+ * that fails to resolve is kept verbatim (never dropped) with a warning.
25
+ */
26
+ const resolveDependencyToken = (token, plan, warnings, changeName) => {
27
+ if (!token.includes('@'))
28
+ return token;
29
+ const resolved = resolveReference(token, plan, plan.package);
30
+ if (resolved.error || !resolved.change) {
31
+ warnings.push(`${changeName}: could not resolve dependency tag "${token}" (${resolved.error ?? 'no matching change'}); left as-is`);
32
+ return token;
33
+ }
34
+ if (resolved.change === token) {
35
+ warnings.push(`${changeName}: dependency tag "${token}" is cross-package; left unresolved (resolve it in workspace context)`);
36
+ }
37
+ return resolved.change;
38
+ };
20
39
  /**
21
40
  * Load a pgpm module from disk: parse pgpm.plan and read each change's
22
41
  * deploy script, stripping pgpm headers and transaction wrappers so the
@@ -30,9 +49,10 @@ export const loadModuleSource = (moduleDir) => {
30
49
  const reasons = planResult.errors.map(e => e.message).join(', ');
31
50
  throw new Error(`Failed to parse pgpm.plan in ${modulePath}: ${reasons}`);
32
51
  }
52
+ const plan = planResult.data;
33
53
  const warnings = [];
34
54
  const changes = [];
35
- for (const change of planResult.data.changes) {
55
+ for (const change of plan.changes) {
36
56
  const raw = readScript(modulePath, 'deploy', change.name);
37
57
  if (!raw) {
38
58
  warnings.push(`${change.name}: no deploy script found, skipping`);
@@ -41,7 +61,7 @@ export const loadModuleSource = (moduleDir) => {
41
61
  const { body } = parsePgpmHeader(raw);
42
62
  changes.push({
43
63
  name: change.name,
44
- dependencies: change.dependencies ?? [],
64
+ dependencies: (change.dependencies ?? []).map(dep => resolveDependencyToken(dep, plan, warnings, change.name)),
45
65
  deploy: stripTransactionWrapper(body)
46
66
  });
47
67
  }
@@ -502,3 +502,38 @@ export function diffChangeSets(from, to, options = {}) {
502
502
  const flatten = (changes) => changes.map(c => c.deploy.trim()).filter(Boolean).join('\n\n');
503
503
  return diffSchemas(flatten(from), flatten(to), options);
504
504
  }
505
+ /**
506
+ * Cover each `to`-side change against a semantic diff's per-object delta:
507
+ * which of the objects the change touches are already present and identical
508
+ * on the `from` side. A `satisfied` change deploys nothing new — its ledger
509
+ * entry can be backfilled instead of executing it; an `unsatisfied` change is
510
+ * the genuine delta. Identity keying is shared with the diff itself, so
511
+ * regrouped/renamed/reordered authorship of the same objects covers the same.
512
+ *
513
+ * `diff` must come from diffing the same `from` side against these changes
514
+ * (`diffChangeSets(fromChanges, toChanges)`).
515
+ */
516
+ export function coverChanges(changes, diff, options = {}) {
517
+ const style = options.style ?? 'directory';
518
+ const deltaByKey = new Map();
519
+ for (const obj of diff.objects) {
520
+ deltaByKey.set(identityKey(obj.identity), obj.delta);
521
+ }
522
+ return changes.map(change => {
523
+ const sideWarnings = [];
524
+ const side = readSide(change.deploy, sideWarnings, change.name);
525
+ const objects = side.order.map(key => {
526
+ const unit = side.units.get(key);
527
+ return {
528
+ path: pathFor(unit.identity, { style }),
529
+ delta: deltaByKey.get(key) ?? 'unchanged'
530
+ };
531
+ });
532
+ const touched = objects.filter(o => o.delta !== 'unchanged').length;
533
+ const status = objects.length === 0 ? 'inert'
534
+ : touched === 0 ? 'satisfied'
535
+ : touched === objects.length ? 'unsatisfied'
536
+ : 'partial';
537
+ return { name: change.name, status, objects };
538
+ });
539
+ }
package/index.d.ts CHANGED
@@ -24,8 +24,8 @@ export type { GeneratedScript, RegeneratedScripts, } from './regen';
24
24
  export { classifyScript, isStubScript, regenerateScripts, } from './regen';
25
25
  export type { ExportGranularity, RestructureExportRowsOptions, RestructureExportRowsResult } from './restructure';
26
26
  export { EXPORT_GRANULARITIES, isExportGranularity, restructureExportRows } from './restructure';
27
- export type { DiffInputChange, ObjectDelta, SemanticDeltaChange, SemanticDiffOptions, SemanticDiffResult, SemanticObjectDiff, } from './semantic-diff-driver';
28
- export { diffChangeSets, diffSchemas, } from './semantic-diff-driver';
27
+ export type { ChangeCoverage, ChangeObjectCoverage, ChangeObjectDelta, ChangeSatisfaction, DiffInputChange, ObjectDelta, SemanticDeltaChange, SemanticDiffOptions, SemanticDiffResult, SemanticObjectDiff, } from './semantic-diff-driver';
28
+ export { coverChanges, diffChangeSets, diffSchemas, } from './semantic-diff-driver';
29
29
  export { nameUnnamedConstraints, subObjectIdentityOf } from './sub-object';
30
30
  export * from '@pgsql/transform';
31
31
  export { loadModule } from 'plpgsql-parser';
package/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.loadModule = exports.subObjectIdentityOf = exports.nameUnnamedConstraints = exports.diffSchemas = exports.diffChangeSets = exports.restructureExportRows = exports.isExportGranularity = exports.EXPORT_GRANULARITIES = exports.regenerateScripts = exports.isStubScript = exports.classifyScript = exports.parseSqlProgram = exports.emitSqlProgram = exports.RESIDUAL_UNIT_PATH = exports.partitionUnits = exports.PartitionCycleError = exports.partitionExportRows = exports.parsePartitionConfig = exports.stripTransactionWrapper = exports.loadModuleSource = exports.writeModule = exports.writeControlFile = exports.checkOverwrite = exports.appendModule = exports.restructureChanges = exports.isChangeGranularity = exports.defaultChangeName = exports.CHANGE_GRANULARITIES = exports.resolveFixtureClosure = exports.firstColumnRef = exports.defaultConstraintName = exports.TIER_PROFILE = exports.categorizeChange = exports.buildCategoryOf = exports.withoutColumnOrder = exports.snapshotCatalog = exports.diffCatalogSnapshots = exports.makeSchemaTranspiler = exports.makeNamespaceValidator = exports.buildSchemaRouter = exports.buildExtensionRouter = void 0;
17
+ exports.loadModule = exports.subObjectIdentityOf = exports.nameUnnamedConstraints = exports.diffSchemas = exports.diffChangeSets = exports.coverChanges = exports.restructureExportRows = exports.isExportGranularity = exports.EXPORT_GRANULARITIES = exports.regenerateScripts = exports.isStubScript = exports.classifyScript = exports.parseSqlProgram = exports.emitSqlProgram = exports.RESIDUAL_UNIT_PATH = exports.partitionUnits = exports.PartitionCycleError = exports.partitionExportRows = exports.parsePartitionConfig = exports.stripTransactionWrapper = exports.loadModuleSource = exports.writeModule = exports.writeControlFile = exports.checkOverwrite = exports.appendModule = exports.restructureChanges = exports.isChangeGranularity = exports.defaultChangeName = exports.CHANGE_GRANULARITIES = exports.resolveFixtureClosure = exports.firstColumnRef = exports.defaultConstraintName = exports.TIER_PROFILE = exports.categorizeChange = exports.buildCategoryOf = exports.withoutColumnOrder = exports.snapshotCatalog = exports.diffCatalogSnapshots = exports.makeSchemaTranspiler = exports.makeNamespaceValidator = exports.buildSchemaRouter = exports.buildExtensionRouter = void 0;
18
18
  var bundle_driver_1 = require("./bundle-driver");
19
19
  Object.defineProperty(exports, "buildExtensionRouter", { enumerable: true, get: function () { return bundle_driver_1.buildExtensionRouter; } });
20
20
  Object.defineProperty(exports, "buildSchemaRouter", { enumerable: true, get: function () { return bundle_driver_1.buildSchemaRouter; } });
@@ -65,6 +65,7 @@ Object.defineProperty(exports, "EXPORT_GRANULARITIES", { enumerable: true, get:
65
65
  Object.defineProperty(exports, "isExportGranularity", { enumerable: true, get: function () { return restructure_1.isExportGranularity; } });
66
66
  Object.defineProperty(exports, "restructureExportRows", { enumerable: true, get: function () { return restructure_1.restructureExportRows; } });
67
67
  var semantic_diff_driver_1 = require("./semantic-diff-driver");
68
+ Object.defineProperty(exports, "coverChanges", { enumerable: true, get: function () { return semantic_diff_driver_1.coverChanges; } });
68
69
  Object.defineProperty(exports, "diffChangeSets", { enumerable: true, get: function () { return semantic_diff_driver_1.diffChangeSets; } });
69
70
  Object.defineProperty(exports, "diffSchemas", { enumerable: true, get: function () { return semantic_diff_driver_1.diffSchemas; } });
70
71
  var sub_object_1 = require("./sub-object");
package/module-source.js CHANGED
@@ -34,12 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.loadModuleSource = exports.stripTransactionWrapper = void 0;
37
- /**
38
- * Module ingestion for the dials pipeline: load an on-disk pgpm module into
39
- * the flattened, classified change list the transform/partition drivers
40
- * consume. `pgpm transform` uses this today; a future `pgpm import` (dumps)
41
- * and `pgpm diff` are expected to reuse the same seam.
42
- */
43
37
  const ast_1 = require("@pgpmjs/ast");
44
38
  const path = __importStar(require("path"));
45
39
  const TX_LINE = /^\s*(BEGIN|COMMIT|ROLLBACK)\s*;\s*$/i;
@@ -54,6 +48,31 @@ const stripTransactionWrapper = (sql) => sql
54
48
  .join('\n')
55
49
  .trim();
56
50
  exports.stripTransactionWrapper = stripTransactionWrapper;
51
+ /**
52
+ * Resolve a plan dependency token to a concrete change name.
53
+ *
54
+ * Non-tag tokens (a bare change name, or a `pkg:change` cross-package name)
55
+ * pass through unchanged — they are already the identity the ledger and
56
+ * generated plans record. A tag token (`@tag`) is resolved to its change via
57
+ * the plan that defines it, so a dependency written against a tag is not
58
+ * re-emitted verbatim into a generated plan or a ledger row. Cross-package
59
+ * tags (`pkg:@tag`) cannot be resolved from a single module's plan and are
60
+ * left as-is with a warning; the resolver returns them unchanged. Anything
61
+ * that fails to resolve is kept verbatim (never dropped) with a warning.
62
+ */
63
+ const resolveDependencyToken = (token, plan, warnings, changeName) => {
64
+ if (!token.includes('@'))
65
+ return token;
66
+ const resolved = (0, ast_1.resolveReference)(token, plan, plan.package);
67
+ if (resolved.error || !resolved.change) {
68
+ warnings.push(`${changeName}: could not resolve dependency tag "${token}" (${resolved.error ?? 'no matching change'}); left as-is`);
69
+ return token;
70
+ }
71
+ if (resolved.change === token) {
72
+ warnings.push(`${changeName}: dependency tag "${token}" is cross-package; left unresolved (resolve it in workspace context)`);
73
+ }
74
+ return resolved.change;
75
+ };
57
76
  /**
58
77
  * Load a pgpm module from disk: parse pgpm.plan and read each change's
59
78
  * deploy script, stripping pgpm headers and transaction wrappers so the
@@ -67,9 +86,10 @@ const loadModuleSource = (moduleDir) => {
67
86
  const reasons = planResult.errors.map(e => e.message).join(', ');
68
87
  throw new Error(`Failed to parse pgpm.plan in ${modulePath}: ${reasons}`);
69
88
  }
89
+ const plan = planResult.data;
70
90
  const warnings = [];
71
91
  const changes = [];
72
- for (const change of planResult.data.changes) {
92
+ for (const change of plan.changes) {
73
93
  const raw = (0, ast_1.readScript)(modulePath, 'deploy', change.name);
74
94
  if (!raw) {
75
95
  warnings.push(`${change.name}: no deploy script found, skipping`);
@@ -78,7 +98,7 @@ const loadModuleSource = (moduleDir) => {
78
98
  const { body } = (0, ast_1.parsePgpmHeader)(raw);
79
99
  changes.push({
80
100
  name: change.name,
81
- dependencies: change.dependencies ?? [],
101
+ dependencies: (change.dependencies ?? []).map(dep => resolveDependencyToken(dep, plan, warnings, change.name)),
82
102
  deploy: (0, exports.stripTransactionWrapper)(body)
83
103
  });
84
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/transform",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "Change-aware SQL transformation for PGPM — superset of @pgsql/transform plus change categorization, fixture closure, and bundle transpile/apply drivers",
6
6
  "main": "index.js",
@@ -39,16 +39,16 @@
39
39
  "plpgsql"
40
40
  ],
41
41
  "devDependencies": {
42
- "@pgpmjs/bundle": "^0.14.0",
42
+ "@pgpmjs/bundle": "^0.14.1",
43
43
  "makage": "^0.3.0"
44
44
  },
45
45
  "dependencies": {
46
- "@pgpmjs/ast": "^0.11.0",
46
+ "@pgpmjs/ast": "^0.11.1",
47
47
  "@pgpmjs/naming-spec": "^0.3.0",
48
- "@pgsql/scripts": "^18.3.2",
49
- "@pgsql/semantics": "^18.1.0",
50
- "@pgsql/transform": "^18.15.0",
51
- "plpgsql-parser": "^18.5.1"
48
+ "@pgsql/scripts": "^18.3.4",
49
+ "@pgsql/semantics": "^18.1.2",
50
+ "@pgsql/transform": "^18.15.2",
51
+ "plpgsql-parser": "^18.5.3"
52
52
  },
53
- "gitHead": "d508453a32a2f1d70ff6916871046615226e6f33"
53
+ "gitHead": "6da0705f8aa49d62dc2defb8b135b71674611483"
54
54
  }
@@ -90,3 +90,38 @@ export interface DiffInputChange {
90
90
  * schema yields an identical diff.
91
91
  */
92
92
  export declare function diffChangeSets(from: DiffInputChange[], to: DiffInputChange[], options?: SemanticDiffOptions): SemanticDiffResult;
93
+ /** How one object a change touches relates to the diff's delta. */
94
+ export type ChangeObjectDelta = ObjectDelta | 'unchanged';
95
+ /**
96
+ * Whether a `to`-side change is already satisfied by the `from` side:
97
+ * - `satisfied`: every object it touches is unchanged in the diff
98
+ * - `unsatisfied`: every object it touches is added/modified in the diff
99
+ * - `partial`: a mix of unchanged and delta objects
100
+ * - `inert`: no identifiable objects (seed data, grants-only, dynamic SQL)
101
+ */
102
+ export type ChangeSatisfaction = 'satisfied' | 'unsatisfied' | 'partial' | 'inert';
103
+ export interface ChangeObjectCoverage {
104
+ /** Canonical naming-spec path for the object. */
105
+ path: string;
106
+ delta: ChangeObjectDelta;
107
+ }
108
+ export interface ChangeCoverage {
109
+ /** The input change's name (as given, e.g. `pkg:change`). */
110
+ name: string;
111
+ status: ChangeSatisfaction;
112
+ objects: ChangeObjectCoverage[];
113
+ }
114
+ /**
115
+ * Cover each `to`-side change against a semantic diff's per-object delta:
116
+ * which of the objects the change touches are already present and identical
117
+ * on the `from` side. A `satisfied` change deploys nothing new — its ledger
118
+ * entry can be backfilled instead of executing it; an `unsatisfied` change is
119
+ * the genuine delta. Identity keying is shared with the diff itself, so
120
+ * regrouped/renamed/reordered authorship of the same objects covers the same.
121
+ *
122
+ * `diff` must come from diffing the same `from` side against these changes
123
+ * (`diffChangeSets(fromChanges, toChanges)`).
124
+ */
125
+ export declare function coverChanges(changes: DiffInputChange[], diff: SemanticDiffResult, options?: {
126
+ style?: PathStyle;
127
+ }): ChangeCoverage[];
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.diffSchemas = diffSchemas;
4
4
  exports.diffChangeSets = diffChangeSets;
5
+ exports.coverChanges = coverChanges;
5
6
  /**
6
7
  * Semantic diff driver: identity-keyed schema delta between two deploy
7
8
  * surfaces.
@@ -506,3 +507,38 @@ function diffChangeSets(from, to, options = {}) {
506
507
  const flatten = (changes) => changes.map(c => c.deploy.trim()).filter(Boolean).join('\n\n');
507
508
  return diffSchemas(flatten(from), flatten(to), options);
508
509
  }
510
+ /**
511
+ * Cover each `to`-side change against a semantic diff's per-object delta:
512
+ * which of the objects the change touches are already present and identical
513
+ * on the `from` side. A `satisfied` change deploys nothing new — its ledger
514
+ * entry can be backfilled instead of executing it; an `unsatisfied` change is
515
+ * the genuine delta. Identity keying is shared with the diff itself, so
516
+ * regrouped/renamed/reordered authorship of the same objects covers the same.
517
+ *
518
+ * `diff` must come from diffing the same `from` side against these changes
519
+ * (`diffChangeSets(fromChanges, toChanges)`).
520
+ */
521
+ function coverChanges(changes, diff, options = {}) {
522
+ const style = options.style ?? 'directory';
523
+ const deltaByKey = new Map();
524
+ for (const obj of diff.objects) {
525
+ deltaByKey.set(identityKey(obj.identity), obj.delta);
526
+ }
527
+ return changes.map(change => {
528
+ const sideWarnings = [];
529
+ const side = readSide(change.deploy, sideWarnings, change.name);
530
+ const objects = side.order.map(key => {
531
+ const unit = side.units.get(key);
532
+ return {
533
+ path: (0, naming_spec_1.pathFor)(unit.identity, { style }),
534
+ delta: deltaByKey.get(key) ?? 'unchanged'
535
+ };
536
+ });
537
+ const touched = objects.filter(o => o.delta !== 'unchanged').length;
538
+ const status = objects.length === 0 ? 'inert'
539
+ : touched === 0 ? 'satisfied'
540
+ : touched === objects.length ? 'unsatisfied'
541
+ : 'partial';
542
+ return { name: change.name, status, objects };
543
+ });
544
+ }