@pgpmjs/transform 0.20.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/bundle-driver.js CHANGED
@@ -13,8 +13,8 @@ exports.buildExtensionRouter = buildExtensionRouter;
13
13
  exports.buildSchemaRouter = buildSchemaRouter;
14
14
  exports.makeSchemaTranspiler = makeSchemaTranspiler;
15
15
  exports.makeNamespaceValidator = makeNamespaceValidator;
16
+ const semantics_1 = require("@pgsql/semantics");
16
17
  const transform_1 = require("@pgsql/transform");
17
- const transform_2 = require("@pgsql/transform");
18
18
  /**
19
19
  * Build an {@link ExtensionRouter} from the declarative {@link ExtensionRoutingInput}:
20
20
  * an explicit `routes` spec when given, otherwise the "move everything matched
@@ -27,8 +27,8 @@ function buildExtensionRouter(input) {
27
27
  if (input.inventory)
28
28
  opts.inventory = input.inventory;
29
29
  if (input.routes)
30
- return new transform_2.ExtensionRouter(input.routes, opts);
31
- return transform_2.ExtensionRouter.toSchema(input.toSchema ?? null, {
30
+ return new transform_1.ExtensionRouter(input.routes, opts);
31
+ return transform_1.ExtensionRouter.toSchema(input.toSchema ?? null, {
32
32
  ...opts,
33
33
  extensions: input.only,
34
34
  from: input.from
@@ -75,7 +75,7 @@ function buildSchemaRouter(options) {
75
75
  ...(route.toName !== undefined ? { name: route.toName } : {})
76
76
  };
77
77
  }
78
- return new transform_2.SchemaRouter(spec);
78
+ return new transform_1.SchemaRouter(spec);
79
79
  }
80
80
  /**
81
81
  * Rewrite the schema segment of a pgpm change path to the object's routed
@@ -125,14 +125,14 @@ function makeSchemaTranspiler(options) {
125
125
  // after schema routing on each script. Each is a self-contained parse →
126
126
  // rewrite → deparse pass, so they compose by threading the SQL through.
127
127
  const extRouter = options.extensions ? buildExtensionRouter(options.extensions) : undefined;
128
- const roleRouter = options.roles ? transform_2.RoleRouter.from(options.roles) : undefined;
129
- const extensionResult = extRouter ? (0, transform_2.createExtensionResult)() : undefined;
130
- const roleResult = roleRouter ? (0, transform_2.createRoleResult)() : undefined;
128
+ const roleRouter = options.roles ? transform_1.RoleRouter.from(options.roles) : undefined;
129
+ const extensionResult = extRouter ? (0, transform_1.createExtensionResult)() : undefined;
130
+ const roleResult = roleRouter ? (0, transform_1.createRoleResult)() : undefined;
131
131
  const renameChange = (name) => renameChangePath(name, router);
132
132
  const transformScript = (sql, _ctx) => {
133
- let out = (0, transform_2.transformSql)(sql, router, options.transform, result).content;
133
+ let out = (0, transform_1.transformSql)(sql, router, options.transform, result).content;
134
134
  if (extRouter) {
135
- const r = (0, transform_2.transformExtensions)(out, extRouter);
135
+ const r = (0, transform_1.transformExtensions)(out, extRouter);
136
136
  out = r.sql;
137
137
  for (const [name, schema] of r.result.installsMoved) {
138
138
  extensionResult.installsMoved.set(name, schema);
@@ -142,7 +142,7 @@ function makeSchemaTranspiler(options) {
142
142
  }
143
143
  }
144
144
  if (roleRouter) {
145
- const r = (0, transform_2.transformRoles)(out, roleRouter);
145
+ const r = (0, transform_1.transformRoles)(out, roleRouter);
146
146
  out = r.sql;
147
147
  for (const [name, count] of r.result.rolesRenamed) {
148
148
  roleResult.rolesRenamed.set(name, (roleResult.rolesRenamed.get(name) ?? 0) + count);
@@ -162,7 +162,7 @@ function makeNamespaceValidator(options) {
162
162
  const allowed = new Set(options.allowedSchemas);
163
163
  return (sql, _ctx) => {
164
164
  const violations = new Set();
165
- for (const facts of (0, transform_1.classifyStatements)(sql)) {
165
+ for (const facts of (0, semantics_1.classifyStatements)(sql)) {
166
166
  for (const ref of [...facts.creates, ...facts.references, ...facts.fkTargets]) {
167
167
  if (ref.schema && !allowed.has(ref.schema)) {
168
168
  violations.add(`${facts.nodeTag}: ${ref.schema}.${ref.name}`);
package/categorize.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { StatementFacts } from '@pgsql/transform';
1
+ import { StatementFacts } from '@pgsql/semantics';
2
2
  /**
3
3
  * A category is a chunk-seam key a change is assigned to. Free-form string; the
4
4
  * built-in {@link TIER_PROFILE} uses `'security' | 'functionality' | 'fixtures'
package/categorize.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TIER_PROFILE = void 0;
4
4
  exports.categorizeChange = categorizeChange;
5
5
  exports.buildCategoryOf = buildCategoryOf;
6
- const transform_1 = require("@pgsql/transform");
6
+ const semantics_1 = require("@pgsql/semantics");
7
7
  /**
8
8
  * Default tier profile: isolates the security surface and procedural code out
9
9
  * of the schema base. Precedence, highest first — a change is placed in the
@@ -37,7 +37,7 @@ exports.TIER_PROFILE = {
37
37
  * (defaults to {@link TIER_PROFILE}).
38
38
  */
39
39
  function categorizeChange(sql, changeName, profile = exports.TIER_PROFILE) {
40
- return profile.categorize((0, transform_1.classifyStatements)(sql), changeName);
40
+ return profile.categorize((0, semantics_1.classifyStatements)(sql), changeName);
41
41
  }
42
42
  /**
43
43
  * Build a `categoryOf(changeName)` seam function from a map of change name to
@@ -7,7 +7,7 @@
7
7
  * purpose — no dependency on `@pgpmjs/bundle`/`@pgpmjs/core` — so the driver
8
8
  * stays a pure function factory over `transformSql` and `classifyStatements`.
9
9
  */
10
- import { classifyStatements } from '@pgsql/transform';
10
+ import { classifyStatements } from '@pgsql/semantics';
11
11
  import { createExtensionResult, createRoleResult, ExtensionRouter, RoleRouter, SchemaRouter, transformExtensions, transformRoles, transformSql } from '@pgsql/transform';
12
12
  /**
13
13
  * Build an {@link ExtensionRouter} from the declarative {@link ExtensionRoutingInput}:
package/esm/categorize.js CHANGED
@@ -1,4 +1,4 @@
1
- import { classifyStatements } from '@pgsql/transform';
1
+ import { classifyStatements } from '@pgsql/semantics';
2
2
  /**
3
3
  * Default tier profile: isolates the security surface and procedural code out
4
4
  * of the schema base. Precedence, highest first — a change is placed in the
@@ -1,4 +1,4 @@
1
- import { classifyStatements } from '@pgsql/transform';
1
+ import { classifyStatements } from '@pgsql/semantics';
2
2
  import { TIER_PROFILE } from './categorize';
3
3
  function objectKey(schema, name) {
4
4
  return `${schema ?? ''}\u0000${name}`;
@@ -1,6 +1,7 @@
1
1
  import { pathFor } from '@pgpmjs/naming-spec';
2
2
  import { revertFor, verifyFor } from '@pgsql/scripts';
3
- import { buildStatementGraph, classifyStatements, identityOf, restructureSql } from '@pgsql/transform';
3
+ import { classifyStatements } from '@pgsql/semantics';
4
+ import { buildStatementGraph, identityOf, restructureSql } from '@pgsql/transform';
4
5
  import { nameUnnamedConstraints, subObjectIdentityOf } from './sub-object';
5
6
  export const CHANGE_GRANULARITIES = ['alteration', 'object', 'single'];
6
7
  export const isChangeGranularity = (value) => CHANGE_GRANULARITIES.includes(value);
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
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Partition dial: project one deploy surface into a set of pgpm packages.
3
3
  *
4
- * The object graph (statements classified by `@pgsql/transform`, grouped
4
+ * The object graph (statements classified by `@pgsql/semantics`, grouped
5
5
  * into identity-keyed units) is the source of truth; which package a unit
6
6
  * lives in is a *derived projection* of a declarative partition config.
7
7
  * Units can be assigned by schema, by object kind, by explicit cherry-pick
@@ -21,7 +21,8 @@
21
21
  * pgpm.plan + deploy trees, feed a bundle, ...).
22
22
  */
23
23
  import { pathFor } from '@pgpmjs/naming-spec';
24
- import { buildStatementGraph, classifyStatements, identityOf } from '@pgsql/transform';
24
+ import { classifyStatements } from '@pgsql/semantics';
25
+ import { buildStatementGraph, identityOf } from '@pgsql/transform';
25
26
  /** A partition whose packages require each other in a cycle — unshippable. */
26
27
  export class PartitionCycleError extends Error {
27
28
  packages;
package/esm/program.js CHANGED
@@ -1,4 +1,4 @@
1
- import { classifyStatements } from '@pgsql/transform';
1
+ import { classifyStatements } from '@pgsql/semantics';
2
2
  import { Deparser, parseSql } from 'plpgsql-parser';
3
3
  /**
4
4
  * Parse a SQL script into a statement program. Requires `loadModule()` from
package/esm/regen.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { revertFor, verifyFor } from '@pgsql/scripts';
2
- import { classifyStatements } from '@pgsql/transform';
2
+ import { classifyStatements } from '@pgsql/semantics';
3
3
  /**
4
4
  * Classify a pgpm script's substantive statements: every top-level statement
5
5
  * except transaction control (`BEGIN`/`COMMIT`/`ROLLBACK` wrappers).
@@ -28,7 +28,8 @@
28
28
  */
29
29
  import { pathFor } from '@pgpmjs/naming-spec';
30
30
  import { existenceCheck, invertStatement, revertFor, verifyFor } from '@pgsql/scripts';
31
- import { buildStatementGraph, classifyStatements, cleanTree, identityOf } from '@pgsql/transform';
31
+ import { classifyStatements } from '@pgsql/semantics';
32
+ import { buildStatementGraph, cleanTree, identityOf } from '@pgsql/transform';
32
33
  import { Deparser, parseSync } from 'plpgsql-parser';
33
34
  import { defaultConstraintName } from './constraint-names';
34
35
  import { restructureChanges } from './granularity-driver';
@@ -501,3 +502,38 @@ export function diffChangeSets(from, to, options = {}) {
501
502
  const flatten = (changes) => changes.map(c => c.deploy.trim()).filter(Boolean).join('\n\n');
502
503
  return diffSchemas(flatten(from), flatten(to), options);
503
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
+ }
@@ -1,4 +1,4 @@
1
- import { QualifiedName } from '@pgsql/transform';
1
+ import { QualifiedName } from '@pgsql/semantics';
2
2
  import { CategoryProfile, ChangeCategory } from './categorize';
3
3
  /**
4
4
  * A change plus its deploy SQL. Optional plan `dependencies` (from `pgpm.plan`)
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveFixtureClosure = resolveFixtureClosure;
4
- const transform_1 = require("@pgsql/transform");
4
+ const semantics_1 = require("@pgsql/semantics");
5
5
  const categorize_1 = require("./categorize");
6
6
  function objectKey(schema, name) {
7
7
  return `${schema ?? ''}\u0000${name}`;
8
8
  }
9
9
  function analyze(change, profile) {
10
- const facts = (0, transform_1.classifyStatements)(change.sql);
10
+ const facts = (0, semantics_1.classifyStatements)(change.sql);
11
11
  const category = profile.categorize(facts, change.name);
12
12
  const creates = [];
13
13
  const createsSchemas = [];
@@ -18,7 +18,8 @@
18
18
  * - `consolidated` — additionally inlines FKs proven safe by the graph.
19
19
  */
20
20
  import type { ObjectIdentity as NamingIdentity } from '@pgpmjs/naming-spec';
21
- import type { Granularity, StatementFacts } from '@pgsql/transform';
21
+ import type { StatementFacts } from '@pgsql/semantics';
22
+ import type { Granularity } from '@pgsql/transform';
22
23
  export type { Granularity } from '@pgsql/transform';
23
24
  /**
24
25
  * How statements are distributed across pgpm changes — orthogonal to
@@ -5,6 +5,7 @@ exports.defaultChangeName = defaultChangeName;
5
5
  exports.restructureChanges = restructureChanges;
6
6
  const naming_spec_1 = require("@pgpmjs/naming-spec");
7
7
  const scripts_1 = require("@pgsql/scripts");
8
+ const semantics_1 = require("@pgsql/semantics");
8
9
  const transform_1 = require("@pgsql/transform");
9
10
  const sub_object_1 = require("./sub-object");
10
11
  exports.CHANGE_GRANULARITIES = ['alteration', 'object', 'single'];
@@ -57,7 +58,7 @@ function restructureChanges(changes, options) {
57
58
  // Re-classify the emitted script; group statements by the object they
58
59
  // target (creates[0]), so a table's CREATE and its remaining ALTERs land
59
60
  // in the same change regardless of statement kind.
60
- const facts = (0, transform_1.classifyStatements)(sql);
61
+ const facts = (0, semantics_1.classifyStatements)(sql);
61
62
  if (options.changeGranularity === 'single') {
62
63
  const name = options.singleChangeName ?? 'module/init';
63
64
  const inputNames = new Set(changes.map(c => c.name));
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.20.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,15 +39,16 @@
39
39
  "plpgsql"
40
40
  ],
41
41
  "devDependencies": {
42
- "@pgpmjs/bundle": "^0.13.3",
42
+ "@pgpmjs/bundle": "^0.14.1",
43
43
  "makage": "^0.3.0"
44
44
  },
45
45
  "dependencies": {
46
- "@pgpmjs/ast": "^0.10.3",
47
- "@pgpmjs/naming-spec": "^0.2.0",
48
- "@pgsql/scripts": "^18.3.1",
49
- "@pgsql/transform": "^18.14.1",
50
- "plpgsql-parser": "^18.5.1"
46
+ "@pgpmjs/ast": "^0.11.1",
47
+ "@pgpmjs/naming-spec": "^0.3.0",
48
+ "@pgsql/scripts": "^18.3.4",
49
+ "@pgsql/semantics": "^18.1.2",
50
+ "@pgsql/transform": "^18.15.2",
51
+ "plpgsql-parser": "^18.5.3"
51
52
  },
52
- "gitHead": "e6a7a57f88c9deb2a4acecaba2451320d1be8203"
53
+ "gitHead": "6da0705f8aa49d62dc2defb8b135b71674611483"
53
54
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Partition dial: project one deploy surface into a set of pgpm packages.
3
3
  *
4
- * The object graph (statements classified by `@pgsql/transform`, grouped
4
+ * The object graph (statements classified by `@pgsql/semantics`, grouped
5
5
  * into identity-keyed units) is the source of truth; which package a unit
6
6
  * lives in is a *derived projection* of a declarative partition config.
7
7
  * Units can be assigned by schema, by object kind, by explicit cherry-pick
@@ -21,7 +21,8 @@
21
21
  * pgpm.plan + deploy trees, feed a bundle, ...).
22
22
  */
23
23
  import { PathStyle } from '@pgpmjs/naming-spec';
24
- import { ObjectIdentity, ObjectIdentityKind, StatementKind } from '@pgsql/transform';
24
+ import { StatementKind } from '@pgsql/semantics';
25
+ import { ObjectIdentity, ObjectIdentityKind } from '@pgsql/transform';
25
26
  /** One deployable unit: an object (by identity) and the statements that build it. */
26
27
  export interface PartitionUnit {
27
28
  /** The object's identity, or `null` for split riders and the residual unit. */
@@ -5,7 +5,7 @@ exports.partitionUnits = partitionUnits;
5
5
  /**
6
6
  * Partition dial: project one deploy surface into a set of pgpm packages.
7
7
  *
8
- * The object graph (statements classified by `@pgsql/transform`, grouped
8
+ * The object graph (statements classified by `@pgsql/semantics`, grouped
9
9
  * into identity-keyed units) is the source of truth; which package a unit
10
10
  * lives in is a *derived projection* of a declarative partition config.
11
11
  * Units can be assigned by schema, by object kind, by explicit cherry-pick
@@ -25,6 +25,7 @@ exports.partitionUnits = partitionUnits;
25
25
  * pgpm.plan + deploy trees, feed a bundle, ...).
26
26
  */
27
27
  const naming_spec_1 = require("@pgpmjs/naming-spec");
28
+ const semantics_1 = require("@pgsql/semantics");
28
29
  const transform_1 = require("@pgsql/transform");
29
30
  /** A partition whose packages require each other in a cycle — unshippable. */
30
31
  class PartitionCycleError extends Error {
@@ -84,7 +85,7 @@ exports.RESIDUAL_UNIT_PATH = 'misc/statements';
84
85
  * otherwise) that depend on their host unit.
85
86
  */
86
87
  function buildUnitGraph(sql, style, splitRiders) {
87
- const facts = (0, transform_1.classifyStatements)(sql);
88
+ const facts = (0, semantics_1.classifyStatements)(sql);
88
89
  const graph = (0, transform_1.buildStatementGraph)(facts);
89
90
  const unitOf = new Array(facts.length).fill(-1);
90
91
  const units = [];
package/program.d.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * untouched statements). A program with no dirty statements emits its source
10
10
  * byte-identically, so content-addressed hashes are preserved.
11
11
  */
12
- import type { StatementFacts } from '@pgsql/transform';
12
+ import type { StatementFacts } from '@pgsql/semantics';
13
13
  /** A statement's location in the source script (byte offsets). */
14
14
  export type SqlStatementSpan = StatementFacts['span'];
15
15
  /** One statement of a parsed SQL script. */
package/program.js CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseSqlProgram = parseSqlProgram;
4
4
  exports.emitSqlProgram = emitSqlProgram;
5
- const transform_1 = require("@pgsql/transform");
5
+ const semantics_1 = require("@pgsql/semantics");
6
6
  const plpgsql_parser_1 = require("plpgsql-parser");
7
7
  /**
8
8
  * Parse a SQL script into a statement program. Requires `loadModule()` from
@@ -11,7 +11,7 @@ const plpgsql_parser_1 = require("plpgsql-parser");
11
11
  function parseSqlProgram(source) {
12
12
  const parseResult = (0, plpgsql_parser_1.parseSql)(source);
13
13
  const stmts = parseResult?.stmts ?? [];
14
- const facts = (0, transform_1.classifyStatements)(source);
14
+ const facts = (0, semantics_1.classifyStatements)(source);
15
15
  const statements = [];
16
16
  for (let i = 0; i < stmts.length; i++) {
17
17
  const rawStmt = stmts[i];
package/regen.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * Requires `loadModule()` from `plpgsql-parser` to have completed.
13
13
  */
14
14
  import type { GeneratedScript } from '@pgsql/scripts';
15
- import type { StatementFacts } from '@pgsql/transform';
15
+ import type { StatementFacts } from '@pgsql/semantics';
16
16
  export type { GeneratedScript } from '@pgsql/scripts';
17
17
  /** Generated revert and verify scripts for one change's deploy SQL. */
18
18
  export interface RegeneratedScripts {
package/regen.js CHANGED
@@ -4,13 +4,13 @@ exports.classifyScript = classifyScript;
4
4
  exports.isStubScript = isStubScript;
5
5
  exports.regenerateScripts = regenerateScripts;
6
6
  const scripts_1 = require("@pgsql/scripts");
7
- const transform_1 = require("@pgsql/transform");
7
+ const semantics_1 = require("@pgsql/semantics");
8
8
  /**
9
9
  * Classify a pgpm script's substantive statements: every top-level statement
10
10
  * except transaction control (`BEGIN`/`COMMIT`/`ROLLBACK` wrappers).
11
11
  */
12
12
  function classifyScript(sql) {
13
- return (0, transform_1.classifyStatements)(sql).filter(f => f.nodeTag !== 'TransactionStmt');
13
+ return (0, semantics_1.classifyStatements)(sql).filter(f => f.nodeTag !== 'TransactionStmt');
14
14
  }
15
15
  /**
16
16
  * Whether a script is an empty stub: nothing but comments, whitespace, and
@@ -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.
@@ -32,6 +33,7 @@ exports.diffChangeSets = diffChangeSets;
32
33
  */
33
34
  const naming_spec_1 = require("@pgpmjs/naming-spec");
34
35
  const scripts_1 = require("@pgsql/scripts");
36
+ const semantics_1 = require("@pgsql/semantics");
35
37
  const transform_1 = require("@pgsql/transform");
36
38
  const plpgsql_parser_1 = require("plpgsql-parser");
37
39
  const constraint_names_1 = require("./constraint-names");
@@ -44,7 +46,7 @@ const identityKey = (id) => `${id.kind}\u0000${id.schema ?? ''}\u0000${id.table
44
46
  * statements are reported via `warnings`.
45
47
  */
46
48
  function readSide(sql, warnings, label) {
47
- const facts = (0, transform_1.classifyStatements)(sql);
49
+ const facts = (0, semantics_1.classifyStatements)(sql);
48
50
  const graph = (0, transform_1.buildStatementGraph)(facts);
49
51
  const units = new Map();
50
52
  const order = [];
@@ -367,7 +369,7 @@ function diffSchemas(from, to, options = {}) {
367
369
  * statement to surface its not-derivable comment and warning.
368
370
  */
369
371
  const inverseOf = (deploy, context) => {
370
- const facts = (0, transform_1.classifyStatements)(deploy);
372
+ const facts = (0, semantics_1.classifyStatements)(deploy);
371
373
  const graph = (0, transform_1.buildStatementGraph)(facts);
372
374
  const pieces = [];
373
375
  for (const i of [...graph.order].reverse()) {
@@ -390,7 +392,7 @@ function diffSchemas(from, to, options = {}) {
390
392
  */
391
393
  const verifyOf = (deploy, context) => {
392
394
  const pieces = [];
393
- for (const fact of (0, transform_1.classifyStatements)(deploy)) {
395
+ for (const fact of (0, semantics_1.classifyStatements)(deploy)) {
394
396
  const nodes = (0, scripts_1.existenceCheck)(fact);
395
397
  if (nodes === null) {
396
398
  const { warnings: w } = (0, scripts_1.verifyFor)([fact]);
@@ -505,3 +507,38 @@ function diffChangeSets(from, to, options = {}) {
505
507
  const flatten = (changes) => changes.map(c => c.deploy.trim()).filter(Boolean).join('\n\n');
506
508
  return diffSchemas(flatten(from), flatten(to), options);
507
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
+ }
package/sub-object.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * every alteration.
11
11
  */
12
12
  import type { ObjectIdentity } from '@pgpmjs/naming-spec';
13
- import type { StatementFacts } from '@pgsql/transform';
13
+ import type { StatementFacts } from '@pgsql/semantics';
14
14
  /**
15
15
  * The column or constraint a one-command `ALTER TABLE .. ADD COLUMN` /
16
16
  * `ADD CONSTRAINT` statement adds, as a naming-spec identity