@pgpmjs/transform 0.14.0 → 0.15.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.
@@ -0,0 +1,137 @@
1
+ import { buildStatementGraph, classifyStatements, restructureSql } from '@pgsql/transform';
2
+ const KIND_DIRS = {
3
+ table: 'tables',
4
+ view: 'views',
5
+ index: 'indexes',
6
+ type: 'types',
7
+ function: 'procedures',
8
+ trigger: 'triggers',
9
+ policy: 'policies',
10
+ seed_dml: 'fixtures'
11
+ };
12
+ /**
13
+ * Default pgpm-style change name for a statement group:
14
+ * `schemas/<schema>` for schemas, `schemas/<schema>/<kind>/<name>` for
15
+ * objects, `misc/<n>` when nothing better is known.
16
+ */
17
+ export function defaultChangeName(facts) {
18
+ const created = facts.creates[0];
19
+ if (facts.kind === 'schema' && created)
20
+ return `schemas/${created.name}`;
21
+ if (created) {
22
+ const dir = KIND_DIRS[facts.kind] ?? 'objects';
23
+ const schema = created.schema ?? 'public';
24
+ // Trigger/policy names are table-qualified (`table.trigger`).
25
+ const name = created.name.replace(/\./g, '/');
26
+ return `schemas/${schema}/${dir}/${name}`;
27
+ }
28
+ return 'misc/statements';
29
+ }
30
+ /**
31
+ * Restructure a module's deploy changes to the target granularity.
32
+ *
33
+ * The flattened program is restructured as one script, then re-sliced: each
34
+ * emitted statement joins the group of the object it creates (statements
35
+ * creating nothing attach to the previous group), groups become changes named
36
+ * by `changeName`, and change dependencies are the statement-graph edges
37
+ * mapped onto owning groups. Requires `loadModule()` from `plpgsql-parser`.
38
+ */
39
+ export function restructureChanges(changes, options) {
40
+ const nameFor = options.changeName ?? defaultChangeName;
41
+ const flattened = changes
42
+ .map(c => c.deploy.trim())
43
+ .filter(Boolean)
44
+ .join('\n\n');
45
+ const { sql, warnings } = restructureSql(flattened, {
46
+ granularity: options.granularity
47
+ });
48
+ // Re-classify the emitted script; group statements by the object they
49
+ // target (creates[0]), so a table's CREATE and its remaining ALTERs land
50
+ // in the same change regardless of statement kind.
51
+ const facts = classifyStatements(sql);
52
+ const graph = buildStatementGraph(facts);
53
+ const groupOf = new Array(facts.length).fill(-1);
54
+ const groupKeys = [];
55
+ const groupFacts = [];
56
+ const groupKeyToIndex = new Map();
57
+ facts.forEach((f, i) => {
58
+ const created = f.creates[0];
59
+ if (!created) {
60
+ // Statements creating nothing (grants, comments) ride with the
61
+ // previous statement's change.
62
+ if (i > 0 && groupOf[i - 1] !== -1)
63
+ groupOf[i] = groupOf[i - 1];
64
+ return;
65
+ }
66
+ const key = `${created.schema ?? ''}.${created.name}`;
67
+ let g = groupKeyToIndex.get(key);
68
+ if (g === undefined) {
69
+ g = groupKeys.length;
70
+ groupKeys.push(key);
71
+ groupFacts.push(f);
72
+ groupKeyToIndex.set(key, g);
73
+ }
74
+ else if (!(groupFacts[g].kind in KIND_DIRS) && groupFacts[g].kind !== 'schema' && (f.kind in KIND_DIRS || f.kind === 'schema')) {
75
+ // Prefer naming the group after its creating statement over an ALTER.
76
+ groupFacts[g] = f;
77
+ }
78
+ groupOf[i] = g;
79
+ });
80
+ const groupNames = groupFacts.map(nameFor);
81
+ // Schema producers, for schema-level change dependencies.
82
+ const schemaGroup = new Map();
83
+ facts.forEach((f, i) => {
84
+ if (f.kind === 'schema' && f.creates[0] && groupOf[i] !== -1) {
85
+ schemaGroup.set(f.creates[0].name, groupOf[i]);
86
+ }
87
+ });
88
+ // Slice statement text per group, in the emitted (topological) order.
89
+ const groupSql = groupNames.map(() => []);
90
+ facts.forEach((f, i) => {
91
+ const text = sql.slice(f.span.start, f.span.start + f.span.len).trim();
92
+ const g = groupOf[i];
93
+ if (g !== -1 && text) {
94
+ groupSql[g].push(text.endsWith(';') ? text : `${text};`);
95
+ }
96
+ });
97
+ // Change dependencies = statement edges projected onto groups, plus
98
+ // schema references (an object change depends on its schema's change).
99
+ const groupDeps = groupNames.map(() => new Set());
100
+ for (const edge of graph.edges) {
101
+ if (edge.kind === 'late')
102
+ continue;
103
+ const from = groupOf[edge.from];
104
+ const to = groupOf[edge.to];
105
+ if (from !== -1 && to !== -1 && from !== to)
106
+ groupDeps[from].add(to);
107
+ }
108
+ facts.forEach((f, i) => {
109
+ const from = groupOf[i];
110
+ if (from === -1)
111
+ return;
112
+ const schemas = new Set(f.referencedSchemas);
113
+ for (const created of f.creates) {
114
+ if (created.schema)
115
+ schemas.add(created.schema);
116
+ }
117
+ for (const schema of schemas) {
118
+ const to = schemaGroup.get(schema);
119
+ if (to !== undefined && to !== from)
120
+ groupDeps[from].add(to);
121
+ }
122
+ });
123
+ // Emit groups in first-statement order (already topological).
124
+ const order = [...groupNames.keys()].sort((a, b) => {
125
+ const firstA = groupOf.indexOf(a);
126
+ const firstB = groupOf.indexOf(b);
127
+ return firstA - firstB;
128
+ });
129
+ const result = order
130
+ .filter(g => groupSql[g].length > 0)
131
+ .map(g => ({
132
+ name: groupNames[g],
133
+ dependencies: [...groupDeps[g]].map(d => groupNames[d]).sort(),
134
+ deploy: groupSql[g].join('\n\n')
135
+ }));
136
+ return { changes: result, warnings };
137
+ }
package/esm/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from '@pgsql/transform';
2
2
  export { loadModule } from 'plpgsql-parser';
3
3
  export { buildExtensionRouter, buildSchemaRouter, makeNamespaceValidator, makeSchemaTranspiler, } from './bundle-driver';
4
+ export { defaultChangeName, restructureChanges, } from './granularity-driver';
4
5
  export { buildCategoryOf, categorizeChange, TIER_PROFILE, } from './categorize';
5
6
  export { resolveFixtureClosure } from './fixture-closure';
6
7
  export { emitSqlProgram, parseSqlProgram } from './program';
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Granularity driver: restructure a pgpm module's deploy surface between the
3
+ * atomic, object, and consolidated shapes.
4
+ *
5
+ * The upstream pass (`restructureSql` in `@pgsql/transform`) rewrites one SQL
6
+ * script between equivalent shapes, guarded by the statement dependency
7
+ * graph. This driver lifts that to the pgpm change model: it flattens a
8
+ * module's deploy scripts in plan order into one program, restructures it to
9
+ * the target granularity, then re-slices the result into changes — one change
10
+ * per created object — with change dependencies recomputed from the statement
11
+ * graph. Like the other drivers in this package it is structurally typed on
12
+ * the bundle seams: no dependency on `@pgpmjs/bundle` or `@pgpmjs/core`.
13
+ *
14
+ * - `atomic` — the machine-emitted shape: bare CREATE TABLE plus one
15
+ * ALTER per column/constraint.
16
+ * - `object` — each table fully baked; cross-object statements
17
+ * (FKs, indexes, triggers, policies) stay separate.
18
+ * - `consolidated` — additionally inlines FKs proven safe by the graph.
19
+ */
20
+ import type { Granularity, StatementFacts } from '@pgsql/transform';
21
+ export type { Granularity } from '@pgsql/transform';
22
+ /** A change's deploy surface going into or out of the restructure. */
23
+ export interface GranularityChange {
24
+ /** Change name (plan token, e.g. `schemas/app/tables/users`). */
25
+ name: string;
26
+ /** Change names this change requires (within the same module). */
27
+ dependencies: string[];
28
+ /** Deploy SQL (headerless — the caller owns pgpm headers). */
29
+ deploy: string;
30
+ }
31
+ export interface RestructureModuleOptions {
32
+ granularity: Granularity;
33
+ /**
34
+ * Derive a change name for a statement group from the facts of its primary
35
+ * (creating) statement. Defaults to {@link defaultChangeName}: pgpm-style
36
+ * `schemas/<schema>` / `schemas/<schema>/tables/<name>` paths.
37
+ */
38
+ changeName?: (facts: StatementFacts) => string;
39
+ }
40
+ export interface RestructureModuleResult {
41
+ /** Restructured changes in deploy order, dependencies recomputed. */
42
+ changes: GranularityChange[];
43
+ /** Non-fatal notes (folds rejected to preserve ordering, etc.). */
44
+ warnings: string[];
45
+ }
46
+ /**
47
+ * Default pgpm-style change name for a statement group:
48
+ * `schemas/<schema>` for schemas, `schemas/<schema>/<kind>/<name>` for
49
+ * objects, `misc/<n>` when nothing better is known.
50
+ */
51
+ export declare function defaultChangeName(facts: StatementFacts): string;
52
+ /**
53
+ * Restructure a module's deploy changes to the target granularity.
54
+ *
55
+ * The flattened program is restructured as one script, then re-sliced: each
56
+ * emitted statement joins the group of the object it creates (statements
57
+ * creating nothing attach to the previous group), groups become changes named
58
+ * by `changeName`, and change dependencies are the statement-graph edges
59
+ * mapped onto owning groups. Requires `loadModule()` from `plpgsql-parser`.
60
+ */
61
+ export declare function restructureChanges(changes: GranularityChange[], options: RestructureModuleOptions): RestructureModuleResult;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultChangeName = defaultChangeName;
4
+ exports.restructureChanges = restructureChanges;
5
+ const transform_1 = require("@pgsql/transform");
6
+ const KIND_DIRS = {
7
+ table: 'tables',
8
+ view: 'views',
9
+ index: 'indexes',
10
+ type: 'types',
11
+ function: 'procedures',
12
+ trigger: 'triggers',
13
+ policy: 'policies',
14
+ seed_dml: 'fixtures'
15
+ };
16
+ /**
17
+ * Default pgpm-style change name for a statement group:
18
+ * `schemas/<schema>` for schemas, `schemas/<schema>/<kind>/<name>` for
19
+ * objects, `misc/<n>` when nothing better is known.
20
+ */
21
+ function defaultChangeName(facts) {
22
+ const created = facts.creates[0];
23
+ if (facts.kind === 'schema' && created)
24
+ return `schemas/${created.name}`;
25
+ if (created) {
26
+ const dir = KIND_DIRS[facts.kind] ?? 'objects';
27
+ const schema = created.schema ?? 'public';
28
+ // Trigger/policy names are table-qualified (`table.trigger`).
29
+ const name = created.name.replace(/\./g, '/');
30
+ return `schemas/${schema}/${dir}/${name}`;
31
+ }
32
+ return 'misc/statements';
33
+ }
34
+ /**
35
+ * Restructure a module's deploy changes to the target granularity.
36
+ *
37
+ * The flattened program is restructured as one script, then re-sliced: each
38
+ * emitted statement joins the group of the object it creates (statements
39
+ * creating nothing attach to the previous group), groups become changes named
40
+ * by `changeName`, and change dependencies are the statement-graph edges
41
+ * mapped onto owning groups. Requires `loadModule()` from `plpgsql-parser`.
42
+ */
43
+ function restructureChanges(changes, options) {
44
+ const nameFor = options.changeName ?? defaultChangeName;
45
+ const flattened = changes
46
+ .map(c => c.deploy.trim())
47
+ .filter(Boolean)
48
+ .join('\n\n');
49
+ const { sql, warnings } = (0, transform_1.restructureSql)(flattened, {
50
+ granularity: options.granularity
51
+ });
52
+ // Re-classify the emitted script; group statements by the object they
53
+ // target (creates[0]), so a table's CREATE and its remaining ALTERs land
54
+ // in the same change regardless of statement kind.
55
+ const facts = (0, transform_1.classifyStatements)(sql);
56
+ const graph = (0, transform_1.buildStatementGraph)(facts);
57
+ const groupOf = new Array(facts.length).fill(-1);
58
+ const groupKeys = [];
59
+ const groupFacts = [];
60
+ const groupKeyToIndex = new Map();
61
+ facts.forEach((f, i) => {
62
+ const created = f.creates[0];
63
+ if (!created) {
64
+ // Statements creating nothing (grants, comments) ride with the
65
+ // previous statement's change.
66
+ if (i > 0 && groupOf[i - 1] !== -1)
67
+ groupOf[i] = groupOf[i - 1];
68
+ return;
69
+ }
70
+ const key = `${created.schema ?? ''}.${created.name}`;
71
+ let g = groupKeyToIndex.get(key);
72
+ if (g === undefined) {
73
+ g = groupKeys.length;
74
+ groupKeys.push(key);
75
+ groupFacts.push(f);
76
+ groupKeyToIndex.set(key, g);
77
+ }
78
+ else if (!(groupFacts[g].kind in KIND_DIRS) && groupFacts[g].kind !== 'schema' && (f.kind in KIND_DIRS || f.kind === 'schema')) {
79
+ // Prefer naming the group after its creating statement over an ALTER.
80
+ groupFacts[g] = f;
81
+ }
82
+ groupOf[i] = g;
83
+ });
84
+ const groupNames = groupFacts.map(nameFor);
85
+ // Schema producers, for schema-level change dependencies.
86
+ const schemaGroup = new Map();
87
+ facts.forEach((f, i) => {
88
+ if (f.kind === 'schema' && f.creates[0] && groupOf[i] !== -1) {
89
+ schemaGroup.set(f.creates[0].name, groupOf[i]);
90
+ }
91
+ });
92
+ // Slice statement text per group, in the emitted (topological) order.
93
+ const groupSql = groupNames.map(() => []);
94
+ facts.forEach((f, i) => {
95
+ const text = sql.slice(f.span.start, f.span.start + f.span.len).trim();
96
+ const g = groupOf[i];
97
+ if (g !== -1 && text) {
98
+ groupSql[g].push(text.endsWith(';') ? text : `${text};`);
99
+ }
100
+ });
101
+ // Change dependencies = statement edges projected onto groups, plus
102
+ // schema references (an object change depends on its schema's change).
103
+ const groupDeps = groupNames.map(() => new Set());
104
+ for (const edge of graph.edges) {
105
+ if (edge.kind === 'late')
106
+ continue;
107
+ const from = groupOf[edge.from];
108
+ const to = groupOf[edge.to];
109
+ if (from !== -1 && to !== -1 && from !== to)
110
+ groupDeps[from].add(to);
111
+ }
112
+ facts.forEach((f, i) => {
113
+ const from = groupOf[i];
114
+ if (from === -1)
115
+ return;
116
+ const schemas = new Set(f.referencedSchemas);
117
+ for (const created of f.creates) {
118
+ if (created.schema)
119
+ schemas.add(created.schema);
120
+ }
121
+ for (const schema of schemas) {
122
+ const to = schemaGroup.get(schema);
123
+ if (to !== undefined && to !== from)
124
+ groupDeps[from].add(to);
125
+ }
126
+ });
127
+ // Emit groups in first-statement order (already topological).
128
+ const order = [...groupNames.keys()].sort((a, b) => {
129
+ const firstA = groupOf.indexOf(a);
130
+ const firstB = groupOf.indexOf(b);
131
+ return firstA - firstB;
132
+ });
133
+ const result = order
134
+ .filter(g => groupSql[g].length > 0)
135
+ .map(g => ({
136
+ name: groupNames[g],
137
+ dependencies: [...groupDeps[g]].map(d => groupNames[d]).sort(),
138
+ deploy: groupSql[g].join('\n\n')
139
+ }));
140
+ return { changes: result, warnings };
141
+ }
package/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export * from '@pgsql/transform';
2
2
  export { loadModule } from 'plpgsql-parser';
3
3
  export type { BundleScriptContext, ExtensionRoutingInput, NamespaceValidatorOptions, SchemaObjectRoute, SchemaTranspiler, SchemaTranspilerOptions, } from './bundle-driver';
4
4
  export { buildExtensionRouter, buildSchemaRouter, makeNamespaceValidator, makeSchemaTranspiler, } from './bundle-driver';
5
+ export type { GranularityChange, RestructureModuleOptions, RestructureModuleResult, } from './granularity-driver';
6
+ export { defaultChangeName, restructureChanges, } from './granularity-driver';
5
7
  export type { CategoryProfile, ChangeCategory, } from './categorize';
6
8
  export { buildCategoryOf, categorizeChange, TIER_PROFILE, } from './categorize';
7
9
  export type { ClosureChange, ClosureInputChange, ClosureReason, FixtureClosure, ResolveFixtureClosureOptions, } from './fixture-closure';
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.parseSqlProgram = exports.emitSqlProgram = exports.resolveFixtureClosure = exports.TIER_PROFILE = exports.categorizeChange = exports.buildCategoryOf = exports.makeSchemaTranspiler = exports.makeNamespaceValidator = exports.buildSchemaRouter = exports.buildExtensionRouter = exports.loadModule = void 0;
17
+ exports.parseSqlProgram = exports.emitSqlProgram = exports.resolveFixtureClosure = exports.TIER_PROFILE = exports.categorizeChange = exports.buildCategoryOf = exports.restructureChanges = exports.defaultChangeName = exports.makeSchemaTranspiler = exports.makeNamespaceValidator = exports.buildSchemaRouter = exports.buildExtensionRouter = exports.loadModule = void 0;
18
18
  __exportStar(require("@pgsql/transform"), exports);
19
19
  var plpgsql_parser_1 = require("plpgsql-parser");
20
20
  Object.defineProperty(exports, "loadModule", { enumerable: true, get: function () { return plpgsql_parser_1.loadModule; } });
@@ -23,6 +23,9 @@ Object.defineProperty(exports, "buildExtensionRouter", { enumerable: true, get:
23
23
  Object.defineProperty(exports, "buildSchemaRouter", { enumerable: true, get: function () { return bundle_driver_1.buildSchemaRouter; } });
24
24
  Object.defineProperty(exports, "makeNamespaceValidator", { enumerable: true, get: function () { return bundle_driver_1.makeNamespaceValidator; } });
25
25
  Object.defineProperty(exports, "makeSchemaTranspiler", { enumerable: true, get: function () { return bundle_driver_1.makeSchemaTranspiler; } });
26
+ var granularity_driver_1 = require("./granularity-driver");
27
+ Object.defineProperty(exports, "defaultChangeName", { enumerable: true, get: function () { return granularity_driver_1.defaultChangeName; } });
28
+ Object.defineProperty(exports, "restructureChanges", { enumerable: true, get: function () { return granularity_driver_1.restructureChanges; } });
26
29
  var categorize_1 = require("./categorize");
27
30
  Object.defineProperty(exports, "buildCategoryOf", { enumerable: true, get: function () { return categorize_1.buildCategoryOf; } });
28
31
  Object.defineProperty(exports, "categorizeChange", { enumerable: true, get: function () { return categorize_1.categorizeChange; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/transform",
3
- "version": "0.14.0",
3
+ "version": "0.15.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",
@@ -43,8 +43,8 @@
43
43
  "makage": "^0.3.0"
44
44
  },
45
45
  "dependencies": {
46
- "@pgsql/transform": "^18.8.0",
46
+ "@pgsql/transform": "^18.9.0",
47
47
  "plpgsql-parser": "^18.2.2"
48
48
  },
49
- "gitHead": "622be766e8b1d5762b6f0536699d59f745a34c0c"
49
+ "gitHead": "6c59df639625959f2db55ee1c6a3768c580045f0"
50
50
  }