@pgpmjs/transform 0.7.1 → 0.10.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.
@@ -7,23 +7,57 @@
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 { SchemaTransformResult, TransformSqlOptions } from '@pgsql/transform';
10
+ import { SchemaRouter, SchemaTransformResult, TransformSqlOptions } from '@pgsql/transform';
11
11
  /** Identity of the script being transformed/validated (matches the bundle seams). */
12
12
  export interface BundleScriptContext {
13
13
  change: string;
14
14
  kind: 'deploy' | 'revert' | 'verify';
15
15
  }
16
+ /**
17
+ * A single object-level route: send one named object out of a source schema to
18
+ * a different target schema, independent of the schema-level default. The
19
+ * object `kind` selects the routing namespace (relation / function / type) so a
20
+ * table and a function of the same name route independently.
21
+ */
22
+ export interface SchemaObjectRoute {
23
+ /** Source schema the object is defined in (e.g. `users`). */
24
+ fromSchema: string;
25
+ /** Object namespace. `table`/`view` → relation, `procedure` → function. */
26
+ kind: 'table' | 'view' | 'function' | 'procedure' | 'type';
27
+ /** Unqualified object name (e.g. `accounts`). */
28
+ name: string;
29
+ /** Target schema this object is routed to (e.g. `reporting`). */
30
+ toSchema: string;
31
+ }
16
32
  export interface SchemaTranspilerOptions {
17
- /** Old schema name → new schema name. Drives both dimensions. */
18
- schemaMap: Record<string, string>;
33
+ /**
34
+ * Whole-schema default: source schema → target schema. Optional when
35
+ * `routes` fully cover the objects being moved.
36
+ */
37
+ schemaMap?: Record<string, string>;
38
+ /**
39
+ * Object-level overrides. Each route wins over the schema-level default for
40
+ * its specific object, letting a single source schema fan out per object.
41
+ */
42
+ routes?: SchemaObjectRoute[];
19
43
  /** Forwarded to {@link transformSql} (round-trip validation, extra passes). */
20
44
  transform?: TransformSqlOptions;
21
45
  }
46
+ /**
47
+ * Build a {@link SchemaRouter} from a schema-level default map plus object
48
+ * routes. Object routes are grouped into the router's relation/function/type
49
+ * buckets; schema-level entries become each route's default target.
50
+ */
51
+ export declare function buildSchemaRouter(options: SchemaTranspilerOptions): SchemaRouter;
22
52
  export interface SchemaTranspiler {
23
53
  /**
24
- * Change-name/path rewrite (the pgpm structural dimension): renames the
25
- * segment following any `schemas` path segment, e.g.
26
- * `schemas/auth/tables/users` → `schemas/tenant_auth/tables/users`.
54
+ * Change-name/path rewrite (the pgpm structural dimension): rewrites the
55
+ * schema segment of a change path to the object's routed target. The object
56
+ * identity is read from the path itself — `schemas/<schema>/<kind>/<name>` —
57
+ * so a table and a function under the same source schema can land in
58
+ * different target schemas, e.g. `schemas/users/tables/accounts/table` →
59
+ * `schemas/tenant_a/tables/accounts/table` while
60
+ * `schemas/users/procedures/account_count` → `schemas/reporting/procedures/account_count`.
27
61
  */
28
62
  renameChange: (name: string) => string;
29
63
  /**
@@ -39,9 +73,9 @@ export interface SchemaTranspiler {
39
73
  result: SchemaTransformResult;
40
74
  }
41
75
  /**
42
- * Build the caller-supplied callbacks for `transpileBundle` from a single
43
- * schema map, so the folder/plan rename and the in-SQL rewrite stay in
44
- * lockstep.
76
+ * Build the caller-supplied callbacks for `transpileBundle` from a schema map
77
+ * and/or object routes, so the folder/plan rename (change identity) and the
78
+ * in-SQL rewrite (AST) stay in lockstep on the exact same {@link SchemaRouter}.
45
79
  */
46
80
  export declare function makeSchemaTranspiler(options: SchemaTranspilerOptions): SchemaTranspiler;
47
81
  export interface NamespaceValidatorOptions {
package/bundle-driver.js CHANGED
@@ -9,33 +9,92 @@
9
9
  * stays a pure function factory over `transformSql` and `classifyStatements`.
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.buildSchemaRouter = buildSchemaRouter;
12
13
  exports.makeSchemaTranspiler = makeSchemaTranspiler;
13
14
  exports.makeNamespaceValidator = makeNamespaceValidator;
14
15
  const transform_1 = require("@pgsql/transform");
15
16
  const transform_2 = require("@pgsql/transform");
17
+ /** pgpm change-path folder segment → transform routing namespace. */
18
+ const PATH_KIND_NS = {
19
+ tables: 'relation',
20
+ views: 'relation',
21
+ procedures: 'function',
22
+ functions: 'function',
23
+ types: 'type'
24
+ };
25
+ /** Object route `kind` → the {@link SchemaRoute} bucket it belongs in. */
26
+ const ROUTE_KIND_BUCKET = {
27
+ table: 'relations',
28
+ view: 'relations',
29
+ function: 'functions',
30
+ procedure: 'functions',
31
+ type: 'types'
32
+ };
16
33
  /**
17
- * Build the caller-supplied callbacks for `transpileBundle` from a single
18
- * schema map, so the folder/plan rename and the in-SQL rewrite stay in
19
- * lockstep.
34
+ * Build a {@link SchemaRouter} from a schema-level default map plus object
35
+ * routes. Object routes are grouped into the router's relation/function/type
36
+ * buckets; schema-level entries become each route's default target.
37
+ */
38
+ function buildSchemaRouter(options) {
39
+ const spec = {};
40
+ const ensure = (schema) => (spec[schema] ??= {});
41
+ for (const [from, to] of Object.entries(options.schemaMap ?? {})) {
42
+ ensure(from).schema = to;
43
+ }
44
+ for (const route of options.routes ?? []) {
45
+ const bucketKey = ROUTE_KIND_BUCKET[route.kind];
46
+ const target = ensure(route.fromSchema);
47
+ (target[bucketKey] ??= {})[route.name] = route.toSchema;
48
+ }
49
+ return new transform_2.SchemaRouter(spec);
50
+ }
51
+ /**
52
+ * Rewrite the schema segment of a pgpm change path to the object's routed
53
+ * target. Interprets `schemas/<schema>/<kind>/<name>...`: the bare
54
+ * `schemas/<schema>/schema` change resolves at the schema level; typed folders
55
+ * (`tables`, `procedures`, `types`, …) resolve the named object in its
56
+ * namespace, so object routes and the schema-level default both apply.
57
+ */
58
+ function renameChangePath(name, router) {
59
+ const parts = name.split('/');
60
+ for (let i = 0; i < parts.length - 1; i++) {
61
+ if (parts[i] !== 'schemas')
62
+ continue;
63
+ const schema = parts[i + 1];
64
+ if (!router.has(schema))
65
+ continue;
66
+ const folder = parts[i + 2];
67
+ let target;
68
+ if (folder === undefined || folder === 'schema') {
69
+ target = router.resolve(schema, undefined, 'schema');
70
+ }
71
+ else {
72
+ const ns = PATH_KIND_NS[folder];
73
+ const objectName = parts[i + 3];
74
+ target = ns
75
+ ? router.resolve(schema, objectName, ns)
76
+ : router.resolve(schema, undefined, 'schema');
77
+ }
78
+ if (target && target !== schema)
79
+ parts[i + 1] = target;
80
+ }
81
+ return parts.join('/');
82
+ }
83
+ /**
84
+ * Build the caller-supplied callbacks for `transpileBundle` from a schema map
85
+ * and/or object routes, so the folder/plan rename (change identity) and the
86
+ * in-SQL rewrite (AST) stay in lockstep on the exact same {@link SchemaRouter}.
20
87
  */
21
88
  function makeSchemaTranspiler(options) {
22
- const mapping = new Map(Object.entries(options.schemaMap));
89
+ const router = buildSchemaRouter(options);
23
90
  const result = {
24
91
  schemasFound: new Set(),
25
92
  schemasTransformed: new Map(),
26
93
  errors: []
27
94
  };
28
- const renameChange = (name) => {
29
- const parts = name.split('/');
30
- for (let i = 0; i < parts.length - 1; i++) {
31
- if (parts[i] === 'schemas' && mapping.has(parts[i + 1])) {
32
- parts[i + 1] = mapping.get(parts[i + 1]);
33
- }
34
- }
35
- return parts.join('/');
36
- };
95
+ const renameChange = (name) => renameChangePath(name, router);
37
96
  const transformScript = (sql, _ctx) => {
38
- return (0, transform_2.transformSql)(sql, mapping, options.transform, result).content;
97
+ return (0, transform_2.transformSql)(sql, router, options.transform, result).content;
39
98
  };
40
99
  return { renameChange, transformScript, result };
41
100
  }
@@ -8,30 +8,88 @@
8
8
  * stays a pure function factory over `transformSql` and `classifyStatements`.
9
9
  */
10
10
  import { classifyStatements } from '@pgsql/transform';
11
- import { transformSql } from '@pgsql/transform';
11
+ import { SchemaRouter, transformSql } from '@pgsql/transform';
12
+ /** pgpm change-path folder segment → transform routing namespace. */
13
+ const PATH_KIND_NS = {
14
+ tables: 'relation',
15
+ views: 'relation',
16
+ procedures: 'function',
17
+ functions: 'function',
18
+ types: 'type'
19
+ };
20
+ /** Object route `kind` → the {@link SchemaRoute} bucket it belongs in. */
21
+ const ROUTE_KIND_BUCKET = {
22
+ table: 'relations',
23
+ view: 'relations',
24
+ function: 'functions',
25
+ procedure: 'functions',
26
+ type: 'types'
27
+ };
12
28
  /**
13
- * Build the caller-supplied callbacks for `transpileBundle` from a single
14
- * schema map, so the folder/plan rename and the in-SQL rewrite stay in
15
- * lockstep.
29
+ * Build a {@link SchemaRouter} from a schema-level default map plus object
30
+ * routes. Object routes are grouped into the router's relation/function/type
31
+ * buckets; schema-level entries become each route's default target.
32
+ */
33
+ export function buildSchemaRouter(options) {
34
+ const spec = {};
35
+ const ensure = (schema) => (spec[schema] ??= {});
36
+ for (const [from, to] of Object.entries(options.schemaMap ?? {})) {
37
+ ensure(from).schema = to;
38
+ }
39
+ for (const route of options.routes ?? []) {
40
+ const bucketKey = ROUTE_KIND_BUCKET[route.kind];
41
+ const target = ensure(route.fromSchema);
42
+ (target[bucketKey] ??= {})[route.name] = route.toSchema;
43
+ }
44
+ return new SchemaRouter(spec);
45
+ }
46
+ /**
47
+ * Rewrite the schema segment of a pgpm change path to the object's routed
48
+ * target. Interprets `schemas/<schema>/<kind>/<name>...`: the bare
49
+ * `schemas/<schema>/schema` change resolves at the schema level; typed folders
50
+ * (`tables`, `procedures`, `types`, …) resolve the named object in its
51
+ * namespace, so object routes and the schema-level default both apply.
52
+ */
53
+ function renameChangePath(name, router) {
54
+ const parts = name.split('/');
55
+ for (let i = 0; i < parts.length - 1; i++) {
56
+ if (parts[i] !== 'schemas')
57
+ continue;
58
+ const schema = parts[i + 1];
59
+ if (!router.has(schema))
60
+ continue;
61
+ const folder = parts[i + 2];
62
+ let target;
63
+ if (folder === undefined || folder === 'schema') {
64
+ target = router.resolve(schema, undefined, 'schema');
65
+ }
66
+ else {
67
+ const ns = PATH_KIND_NS[folder];
68
+ const objectName = parts[i + 3];
69
+ target = ns
70
+ ? router.resolve(schema, objectName, ns)
71
+ : router.resolve(schema, undefined, 'schema');
72
+ }
73
+ if (target && target !== schema)
74
+ parts[i + 1] = target;
75
+ }
76
+ return parts.join('/');
77
+ }
78
+ /**
79
+ * Build the caller-supplied callbacks for `transpileBundle` from a schema map
80
+ * and/or object routes, so the folder/plan rename (change identity) and the
81
+ * in-SQL rewrite (AST) stay in lockstep on the exact same {@link SchemaRouter}.
16
82
  */
17
83
  export function makeSchemaTranspiler(options) {
18
- const mapping = new Map(Object.entries(options.schemaMap));
84
+ const router = buildSchemaRouter(options);
19
85
  const result = {
20
86
  schemasFound: new Set(),
21
87
  schemasTransformed: new Map(),
22
88
  errors: []
23
89
  };
24
- const renameChange = (name) => {
25
- const parts = name.split('/');
26
- for (let i = 0; i < parts.length - 1; i++) {
27
- if (parts[i] === 'schemas' && mapping.has(parts[i + 1])) {
28
- parts[i + 1] = mapping.get(parts[i + 1]);
29
- }
30
- }
31
- return parts.join('/');
32
- };
90
+ const renameChange = (name) => renameChangePath(name, router);
33
91
  const transformScript = (sql, _ctx) => {
34
- return transformSql(sql, mapping, options.transform, result).content;
92
+ return transformSql(sql, router, options.transform, result).content;
35
93
  };
36
94
  return { renameChange, transformScript, result };
37
95
  }
package/esm/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from '@pgsql/transform';
2
- export { makeNamespaceValidator, makeSchemaTranspiler, } from './bundle-driver';
2
+ export { loadModule } from 'plpgsql-parser';
3
+ export { buildSchemaRouter, makeNamespaceValidator, makeSchemaTranspiler, } from './bundle-driver';
3
4
  export { buildCategoryOf, categorizeChange, TIER_PROFILE, } from './categorize';
4
5
  export { resolveFixtureClosure } from './fixture-closure';
package/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from '@pgsql/transform';
2
- export type { BundleScriptContext, NamespaceValidatorOptions, SchemaTranspiler, SchemaTranspilerOptions, } from './bundle-driver';
3
- export { makeNamespaceValidator, makeSchemaTranspiler, } from './bundle-driver';
2
+ export { loadModule } from 'plpgsql-parser';
3
+ export type { BundleScriptContext, NamespaceValidatorOptions, SchemaObjectRoute, SchemaTranspiler, SchemaTranspilerOptions, } from './bundle-driver';
4
+ export { buildSchemaRouter, makeNamespaceValidator, makeSchemaTranspiler, } from './bundle-driver';
4
5
  export type { CategoryProfile, ChangeCategory, } from './categorize';
5
6
  export { buildCategoryOf, categorizeChange, TIER_PROFILE, } from './categorize';
6
7
  export type { ClosureChange, ClosureInputChange, ClosureReason, FixtureClosure, ResolveFixtureClosureOptions, } from './fixture-closure';
package/index.js CHANGED
@@ -14,9 +14,12 @@ 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.resolveFixtureClosure = exports.TIER_PROFILE = exports.categorizeChange = exports.buildCategoryOf = exports.makeSchemaTranspiler = exports.makeNamespaceValidator = void 0;
17
+ exports.resolveFixtureClosure = exports.TIER_PROFILE = exports.categorizeChange = exports.buildCategoryOf = exports.makeSchemaTranspiler = exports.makeNamespaceValidator = exports.buildSchemaRouter = exports.loadModule = void 0;
18
18
  __exportStar(require("@pgsql/transform"), exports);
19
+ var plpgsql_parser_1 = require("plpgsql-parser");
20
+ Object.defineProperty(exports, "loadModule", { enumerable: true, get: function () { return plpgsql_parser_1.loadModule; } });
19
21
  var bundle_driver_1 = require("./bundle-driver");
22
+ Object.defineProperty(exports, "buildSchemaRouter", { enumerable: true, get: function () { return bundle_driver_1.buildSchemaRouter; } });
20
23
  Object.defineProperty(exports, "makeNamespaceValidator", { enumerable: true, get: function () { return bundle_driver_1.makeNamespaceValidator; } });
21
24
  Object.defineProperty(exports, "makeSchemaTranspiler", { enumerable: true, get: function () { return bundle_driver_1.makeSchemaTranspiler; } });
22
25
  var categorize_1 = require("./categorize");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/transform",
3
- "version": "0.7.1",
3
+ "version": "0.10.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,12 +39,12 @@
39
39
  "plpgsql"
40
40
  ],
41
41
  "devDependencies": {
42
- "@pgpmjs/bundle": "^0.8.1",
42
+ "@pgpmjs/bundle": "^0.11.0",
43
43
  "makage": "^0.3.0"
44
44
  },
45
45
  "dependencies": {
46
- "@pgsql/transform": "^18.3.1",
46
+ "@pgsql/transform": "^18.5.0",
47
47
  "plpgsql-parser": "^18.2.1"
48
48
  },
49
- "gitHead": "37e83eb66e9d13d0d4d8ac3b36b5ebe394b84a43"
49
+ "gitHead": "d85911699e8a50a9fdfeb7506195442525c1f238"
50
50
  }