frg-data-diff 2.0.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/README.md +628 -0
- package/dist/apply/apply-diff.d.ts +24 -0
- package/dist/apply/apply-diff.js +205 -0
- package/dist/apply/conflict.d.ts +20 -0
- package/dist/apply/conflict.js +14 -0
- package/dist/apply/plan.d.ts +13 -0
- package/dist/apply/plan.js +37 -0
- package/dist/cli/apply.d.ts +8 -0
- package/dist/cli/apply.js +270 -0
- package/dist/cli/generator.d.ts +9 -0
- package/dist/cli/generator.js +804 -0
- package/dist/cli/pg-triggers.d.ts +2 -0
- package/dist/cli/pg-triggers.js +185 -0
- package/dist/cli/root.d.ts +8 -0
- package/dist/cli/root.js +231 -0
- package/dist/cli/sql.d.ts +9 -0
- package/dist/cli/sql.js +158 -0
- package/dist/config/config-schema.d.ts +380 -0
- package/dist/config/config-schema.js +95 -0
- package/dist/config/load-config.d.ts +12 -0
- package/dist/config/load-config.js +87 -0
- package/dist/config/resolve-options.d.ts +95 -0
- package/dist/config/resolve-options.js +183 -0
- package/dist/config/write-config.d.ts +18 -0
- package/dist/config/write-config.js +103 -0
- package/dist/db/connection.d.ts +28 -0
- package/dist/db/connection.js +34 -0
- package/dist/db/metadata.d.ts +70 -0
- package/dist/db/metadata.js +238 -0
- package/dist/db/pg-triggers.d.ts +27 -0
- package/dist/db/pg-triggers.js +59 -0
- package/dist/db/sql.d.ts +72 -0
- package/dist/db/sql.js +250 -0
- package/dist/diff/diff-schema.d.ts +380 -0
- package/dist/diff/diff-schema.js +67 -0
- package/dist/diff/generate-diff.d.ts +20 -0
- package/dist/diff/generate-diff.js +224 -0
- package/dist/diff/pg-triggers-diff.d.ts +11 -0
- package/dist/diff/pg-triggers-diff.js +161 -0
- package/dist/diff/serialize-value.d.ts +35 -0
- package/dist/diff/serialize-value.js +242 -0
- package/dist/diff/write-diff-yaml.d.ts +3 -0
- package/dist/diff/write-diff-yaml.js +48 -0
- package/dist/schema-diff/generate-schema-diff.d.ts +12 -0
- package/dist/schema-diff/generate-schema-diff.js +355 -0
- package/dist/schema-diff/generate-schema-sql.d.ts +20 -0
- package/dist/schema-diff/generate-schema-sql.js +187 -0
- package/dist/schema-diff/schema-diff-schema.d.ts +1088 -0
- package/dist/schema-diff/schema-diff-schema.js +70 -0
- package/dist/shared/env-values.d.ts +11 -0
- package/dist/shared/env-values.js +100 -0
- package/dist/shared/generator-wizard.d.ts +10 -0
- package/dist/shared/generator-wizard.js +337 -0
- package/dist/shared/identifiers.d.ts +24 -0
- package/dist/shared/identifiers.js +45 -0
- package/dist/shared/prompts.d.ts +26 -0
- package/dist/shared/prompts.js +104 -0
- package/dist/shared/summary.d.ts +33 -0
- package/dist/shared/summary.js +41 -0
- package/dist/sql/generate-sql.d.ts +19 -0
- package/dist/sql/generate-sql.js +223 -0
- package/package.json +39 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generatePgTriggersDiffSql = generatePgTriggersDiffSql;
|
|
4
|
+
const metadata_1 = require("../db/metadata");
|
|
5
|
+
const pg_triggers_1 = require("../db/pg-triggers");
|
|
6
|
+
async function generatePgTriggersDiffSql(sourcePool, destPool, options) {
|
|
7
|
+
reportProgress(options.onProgress, "PostgreSQL triggers: resolving requested table patterns");
|
|
8
|
+
const { tables } = await (0, metadata_1.resolveTablePatterns)(sourcePool, destPool, options.schema, options.tables, options.excludeTables);
|
|
9
|
+
reportProgress(options.onProgress, `PostgreSQL triggers: comparing ${tables.length} table(s)`);
|
|
10
|
+
const sourceClient = await sourcePool.connect();
|
|
11
|
+
const destClient = await destPool.connect();
|
|
12
|
+
let sql = `-- PostgreSQL Triggers and Functions Diff\n`;
|
|
13
|
+
sql += `-- Generated automatically\n\n`;
|
|
14
|
+
try {
|
|
15
|
+
reportProgress(options.onProgress, "PostgreSQL triggers: fetching trigger and function definitions from source");
|
|
16
|
+
const sourceData = await (0, pg_triggers_1.fetchTriggersAndFunctionsForTables)(sourceClient, options.schema, tables, {
|
|
17
|
+
onTableStart: (table, tablePosition, totalTables) => {
|
|
18
|
+
reportProgress(options.onProgress, `[pg-triggers source ${tablePosition}/${totalTables}] ${options.schema}.${table}: fetching definitions`);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
reportProgress(options.onProgress, "PostgreSQL triggers: fetching trigger and function definitions from destination");
|
|
22
|
+
const destData = await (0, pg_triggers_1.fetchTriggersAndFunctionsForTables)(destClient, options.schema, tables, {
|
|
23
|
+
onTableStart: (table, tablePosition, totalTables) => {
|
|
24
|
+
reportProgress(options.onProgress, `[pg-triggers dest ${tablePosition}/${totalTables}] ${options.schema}.${table}: fetching definitions`);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const sourceTablesMap = new Map(sourceData.map((d) => [d.table, d]));
|
|
28
|
+
const destTablesMap = new Map(destData.map((d) => [d.table, d]));
|
|
29
|
+
const allAddedFunctions = [];
|
|
30
|
+
const allDroppedFunctions = [];
|
|
31
|
+
const allAddedTriggers = [];
|
|
32
|
+
const allDroppedTriggers = [];
|
|
33
|
+
const sourceFunctionsMap = new Map();
|
|
34
|
+
const destFunctionsMap = new Map();
|
|
35
|
+
reportProgress(options.onProgress, "PostgreSQL triggers: indexing function definitions");
|
|
36
|
+
for (const data of sourceData) {
|
|
37
|
+
for (const fn of data.functions) {
|
|
38
|
+
sourceFunctionsMap.set(`${fn.functionSchema}.${fn.functionName}`, fn);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const data of destData) {
|
|
42
|
+
for (const fn of data.functions) {
|
|
43
|
+
destFunctionsMap.set(`${fn.functionSchema}.${fn.functionName}`, fn);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Determine function diffs globally across the requested tables
|
|
47
|
+
for (const [key, sourceFn] of sourceFunctionsMap) {
|
|
48
|
+
const destFn = destFunctionsMap.get(key);
|
|
49
|
+
if (!destFn ||
|
|
50
|
+
destFn.functionDefinition !== sourceFn.functionDefinition) {
|
|
51
|
+
allAddedFunctions.push(sourceFn);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [key, destFn] of destFunctionsMap) {
|
|
55
|
+
const sourceFn = sourceFunctionsMap.get(key);
|
|
56
|
+
if (!sourceFn) {
|
|
57
|
+
allDroppedFunctions.push(destFn);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
reportProgress(options.onProgress, `PostgreSQL functions: ${allAddedFunctions.length} function(s) to create or replace, ${allDroppedFunctions.length} function(s) to drop`);
|
|
61
|
+
// Determine trigger diffs per table
|
|
62
|
+
for (const [tableIndex, table] of tables.entries()) {
|
|
63
|
+
const progressLabel = formatPgTriggersProgressLabel(tableIndex + 1, tables.length, options.schema, table);
|
|
64
|
+
const sourceMeta = sourceTablesMap.get(table);
|
|
65
|
+
const destMeta = destTablesMap.get(table);
|
|
66
|
+
const sourceTriggersMap = new Map(sourceMeta?.triggers.map((t) => [t.triggerName, t]) ?? []);
|
|
67
|
+
const destTriggersMap = new Map(destMeta?.triggers.map((t) => [t.triggerName, t]) ?? []);
|
|
68
|
+
let addedTriggersForTable = 0;
|
|
69
|
+
let droppedTriggersForTable = 0;
|
|
70
|
+
reportProgress(options.onProgress, `${progressLabel}: comparing triggers`);
|
|
71
|
+
for (const [triggerName, sourceTrigger] of sourceTriggersMap) {
|
|
72
|
+
const destTrigger = destTriggersMap.get(triggerName);
|
|
73
|
+
if (!destTrigger ||
|
|
74
|
+
destTrigger.triggerDefinition !== sourceTrigger.triggerDefinition) {
|
|
75
|
+
if (destTrigger) {
|
|
76
|
+
allDroppedTriggers.push({ table, triggerName });
|
|
77
|
+
droppedTriggersForTable++;
|
|
78
|
+
}
|
|
79
|
+
allAddedTriggers.push(sourceTrigger);
|
|
80
|
+
addedTriggersForTable++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const [triggerName] of destTriggersMap) {
|
|
84
|
+
if (!sourceTriggersMap.has(triggerName)) {
|
|
85
|
+
allDroppedTriggers.push({ table, triggerName });
|
|
86
|
+
droppedTriggersForTable++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
reportProgress(options.onProgress, `${progressLabel}: ${addedTriggersForTable} trigger(s) to create or replace, ${droppedTriggersForTable} trigger(s) to drop`);
|
|
90
|
+
}
|
|
91
|
+
// Generate SQL
|
|
92
|
+
if (allDroppedTriggers.length === 0 &&
|
|
93
|
+
allDroppedFunctions.length === 0 &&
|
|
94
|
+
allAddedFunctions.length === 0 &&
|
|
95
|
+
allAddedTriggers.length === 0) {
|
|
96
|
+
reportProgress(options.onProgress, "PostgreSQL triggers: no differences found");
|
|
97
|
+
sql += `-- No differences found.\n`;
|
|
98
|
+
return sql;
|
|
99
|
+
}
|
|
100
|
+
reportProgress(options.onProgress, "PostgreSQL triggers: generating SQL script");
|
|
101
|
+
// We must drop triggers before dropping functions, otherwise it's a dependency violation
|
|
102
|
+
if (allDroppedTriggers.length > 0) {
|
|
103
|
+
sql += `-- Drop removed or modified triggers\n`;
|
|
104
|
+
for (const drop of allDroppedTriggers) {
|
|
105
|
+
sql += `DROP TRIGGER IF EXISTS "${drop.triggerName}" ON "${options.schema}"."${drop.table}";\n`;
|
|
106
|
+
}
|
|
107
|
+
sql += `\n`;
|
|
108
|
+
}
|
|
109
|
+
if (allDroppedFunctions.length > 0) {
|
|
110
|
+
sql += `-- Drop removed functions\n`;
|
|
111
|
+
for (const drop of allDroppedFunctions) {
|
|
112
|
+
sql += `DROP FUNCTION IF EXISTS "${drop.functionSchema}"."${drop.functionName}"();\n`;
|
|
113
|
+
}
|
|
114
|
+
sql += `\n`;
|
|
115
|
+
}
|
|
116
|
+
if (allAddedFunctions.length > 0) {
|
|
117
|
+
sql += `-- Create or replace functions\n`;
|
|
118
|
+
for (const fn of allAddedFunctions) {
|
|
119
|
+
let def = fn.functionDefinition.trim();
|
|
120
|
+
if (def.toUpperCase().startsWith("CREATE FUNCTION")) {
|
|
121
|
+
def =
|
|
122
|
+
"CREATE OR REPLACE FUNCTION" +
|
|
123
|
+
def.substring("CREATE FUNCTION".length);
|
|
124
|
+
}
|
|
125
|
+
if (!def.endsWith(";")) {
|
|
126
|
+
def += ";";
|
|
127
|
+
}
|
|
128
|
+
sql += `${def}\n\n`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (allAddedTriggers.length > 0) {
|
|
132
|
+
sql += `-- Create new or modified triggers\n`;
|
|
133
|
+
for (const trg of allAddedTriggers) {
|
|
134
|
+
let def = trg.triggerDefinition.trim();
|
|
135
|
+
if (def.toUpperCase().startsWith("CREATE TRIGGER")) {
|
|
136
|
+
def =
|
|
137
|
+
"CREATE OR REPLACE TRIGGER" +
|
|
138
|
+
def.substring("CREATE TRIGGER".length);
|
|
139
|
+
}
|
|
140
|
+
if (!def.endsWith(";")) {
|
|
141
|
+
def += ";";
|
|
142
|
+
}
|
|
143
|
+
sql += `${def}\n\n`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
sourceClient.release();
|
|
149
|
+
destClient.release();
|
|
150
|
+
}
|
|
151
|
+
return sql;
|
|
152
|
+
}
|
|
153
|
+
function formatPgTriggersProgressLabel(tablePosition, totalTables, schema, table) {
|
|
154
|
+
return `[pg-triggers ${tablePosition}/${totalTables}] ${schema}.${table}`;
|
|
155
|
+
}
|
|
156
|
+
function reportProgress(callback, message) {
|
|
157
|
+
if (callback) {
|
|
158
|
+
callback(message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=pg-triggers-diff.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Value serialization utilities for converting PostgreSQL values to/from
|
|
3
|
+
* JSON-compatible representations in the diff file.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Serializes a PostgreSQL value for storage in the diff JSON file.
|
|
7
|
+
* - Buffer (bytea) -> {$type:'bytea', $value: base64}
|
|
8
|
+
* - pg geometric types (point, circle, lseg, line, box, path) -> string literals
|
|
9
|
+
* - Date -> ISO 8601 string
|
|
10
|
+
* - BigInt -> string (to preserve precision)
|
|
11
|
+
* - null/undefined -> null
|
|
12
|
+
* - Objects/arrays -> recursively serialized
|
|
13
|
+
* - Everything else -> as-is
|
|
14
|
+
*
|
|
15
|
+
* @param value - The value to serialize
|
|
16
|
+
* @param dataType - Optional PostgreSQL data type name to guide serialization of ambiguous types
|
|
17
|
+
*/
|
|
18
|
+
export declare function serializeValue(value: unknown, dataType?: string): unknown;
|
|
19
|
+
/**
|
|
20
|
+
* Deserializes a value from the diff JSON back to the form expected by pg.
|
|
21
|
+
* - {$type: 'bytea', $value: '<base64>'} -> Buffer
|
|
22
|
+
* - everything else -> as-is
|
|
23
|
+
*/
|
|
24
|
+
export declare function deserializeValue(value: unknown): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Compares two serialized values for equality.
|
|
27
|
+
* - null vs null is equal
|
|
28
|
+
* - JSON objects are compared structurally (key order does not matter)
|
|
29
|
+
* - Arrays are compared element-wise
|
|
30
|
+
* - Primitives use strict equality
|
|
31
|
+
*
|
|
32
|
+
* This is used for jsonb structural comparison and general value comparison.
|
|
33
|
+
*/
|
|
34
|
+
export declare function valuesAreEqual(a: unknown, b: unknown): boolean;
|
|
35
|
+
//# sourceMappingURL=serialize-value.d.ts.map
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Value serialization utilities for converting PostgreSQL values to/from
|
|
4
|
+
* JSON-compatible representations in the diff file.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.serializeValue = serializeValue;
|
|
8
|
+
exports.deserializeValue = deserializeValue;
|
|
9
|
+
exports.valuesAreEqual = valuesAreEqual;
|
|
10
|
+
/**
|
|
11
|
+
* Serializes a PostgreSQL value for storage in the diff JSON file.
|
|
12
|
+
* - Buffer (bytea) -> {$type:'bytea', $value: base64}
|
|
13
|
+
* - pg geometric types (point, circle, lseg, line, box, path) -> string literals
|
|
14
|
+
* - Date -> ISO 8601 string
|
|
15
|
+
* - BigInt -> string (to preserve precision)
|
|
16
|
+
* - null/undefined -> null
|
|
17
|
+
* - Objects/arrays -> recursively serialized
|
|
18
|
+
* - Everything else -> as-is
|
|
19
|
+
*
|
|
20
|
+
* @param value - The value to serialize
|
|
21
|
+
* @param dataType - Optional PostgreSQL data type name to guide serialization of ambiguous types
|
|
22
|
+
*/
|
|
23
|
+
function serializeValue(value, dataType) {
|
|
24
|
+
if (value === null || value === undefined) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (Buffer.isBuffer(value)) {
|
|
28
|
+
// Encode bytea as base64 with a type tag
|
|
29
|
+
return { $type: "bytea", $value: value.toString("base64") };
|
|
30
|
+
}
|
|
31
|
+
if (value instanceof Date) {
|
|
32
|
+
return serializeDateValue(value, dataType);
|
|
33
|
+
}
|
|
34
|
+
if (typeof value === "bigint") {
|
|
35
|
+
return value.toString();
|
|
36
|
+
}
|
|
37
|
+
// pg returns numeric/decimal as strings - preserve as-is
|
|
38
|
+
if (typeof value === "string" ||
|
|
39
|
+
typeof value === "number" ||
|
|
40
|
+
typeof value === "boolean") {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return value.map((v) => serializeValue(v));
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === "object") {
|
|
47
|
+
// Handle pg geometric types which are returned as plain objects.
|
|
48
|
+
// Use dataType to distinguish from jsonb objects that happen to have the same shape.
|
|
49
|
+
if (dataType) {
|
|
50
|
+
const geoStr = serializeGeometricValue(dataType, value);
|
|
51
|
+
if (geoStr !== null)
|
|
52
|
+
return geoStr;
|
|
53
|
+
// Serialize pg interval objects (returned as {years, months, days, hours, minutes, seconds, milliseconds})
|
|
54
|
+
// as PostgreSQL interval literal strings so they can be passed back as parameters.
|
|
55
|
+
if (dataType === "interval") {
|
|
56
|
+
return serializeIntervalValue(value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Serialize plain objects (jsonb, json, unknown objects)
|
|
60
|
+
return JSON.parse(JSON.stringify(value, (_k, v) => {
|
|
61
|
+
if (typeof v === "bigint")
|
|
62
|
+
return v.toString();
|
|
63
|
+
return v;
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
function serializeDateValue(value, dataType) {
|
|
69
|
+
if (dataType === "date") {
|
|
70
|
+
return [
|
|
71
|
+
value.getFullYear(),
|
|
72
|
+
pad2(value.getMonth() + 1),
|
|
73
|
+
pad2(value.getDate()),
|
|
74
|
+
].join("-");
|
|
75
|
+
}
|
|
76
|
+
if (dataType === "timestamp without time zone") {
|
|
77
|
+
const milliseconds = value.getMilliseconds();
|
|
78
|
+
const fractional = milliseconds > 0 ? `.${String(milliseconds).padStart(3, "0")}` : "";
|
|
79
|
+
return (`${value.getFullYear()}-${pad2(value.getMonth() + 1)}-${pad2(value.getDate())} ` +
|
|
80
|
+
`${pad2(value.getHours())}:${pad2(value.getMinutes())}:${pad2(value.getSeconds())}${fractional}`);
|
|
81
|
+
}
|
|
82
|
+
return value.toISOString();
|
|
83
|
+
}
|
|
84
|
+
function pad2(value) {
|
|
85
|
+
return String(value).padStart(2, "0");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Attempts to serialize a known PostgreSQL geometric type to its string literal form.
|
|
89
|
+
* Returns null if the dataType is not a known geometric type.
|
|
90
|
+
*
|
|
91
|
+
* PostgreSQL accepts these string forms as input for geometric types:
|
|
92
|
+
* point: (x,y)
|
|
93
|
+
* circle: <(x,y),r>
|
|
94
|
+
* lseg: ((x1,y1),(x2,y2))
|
|
95
|
+
* box: ((x1,y1),(x2,y2))
|
|
96
|
+
* line: {a,b,c}
|
|
97
|
+
* path: ((x1,y1),...) for closed, (x1,y1,...) for open
|
|
98
|
+
*/
|
|
99
|
+
function serializeGeometricValue(dataType, obj) {
|
|
100
|
+
switch (dataType) {
|
|
101
|
+
case "point":
|
|
102
|
+
if ("x" in obj && "y" in obj) {
|
|
103
|
+
return `(${obj["x"]},${obj["y"]})`;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
case "circle":
|
|
107
|
+
if ("x" in obj && "y" in obj && "radius" in obj) {
|
|
108
|
+
return `<(${obj["x"]},${obj["y"]}),${obj["radius"]}>`;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
case "lseg":
|
|
112
|
+
case "box":
|
|
113
|
+
if ("x1" in obj && "y1" in obj && "x2" in obj && "y2" in obj) {
|
|
114
|
+
return `((${obj["x1"]},${obj["y1"]}),(${obj["x2"]},${obj["y2"]}))`;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
case "line":
|
|
118
|
+
if ("a" in obj && "b" in obj && "c" in obj) {
|
|
119
|
+
return `{${obj["a"]},${obj["b"]},${obj["c"]}}`;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
case "path":
|
|
123
|
+
if ("points" in obj && Array.isArray(obj["points"])) {
|
|
124
|
+
const points = obj["points"]
|
|
125
|
+
.map((p) => `(${p.x},${p.y})`)
|
|
126
|
+
.join(",");
|
|
127
|
+
const closed = obj["closed"];
|
|
128
|
+
return closed ? `(${points})` : `[${points}]`;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
case "polygon":
|
|
132
|
+
if (Array.isArray(obj)) {
|
|
133
|
+
// polygon is returned as an array of {x,y} points by pg
|
|
134
|
+
return null; // handled above as Array case
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
default:
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Serializes a pg interval object to a PostgreSQL interval literal string.
|
|
143
|
+
* pg returns interval values as { years?, months?, days?, hours?, minutes?, seconds?, milliseconds? }.
|
|
144
|
+
*/
|
|
145
|
+
function serializeIntervalValue(obj) {
|
|
146
|
+
const parts = [];
|
|
147
|
+
const years = Number(obj["years"] ?? 0);
|
|
148
|
+
const months = Number(obj["months"] ?? 0);
|
|
149
|
+
const days = Number(obj["days"] ?? 0);
|
|
150
|
+
const hours = Number(obj["hours"] ?? 0);
|
|
151
|
+
const minutes = Number(obj["minutes"] ?? 0);
|
|
152
|
+
const seconds = Number(obj["seconds"] ?? 0);
|
|
153
|
+
const ms = Number(obj["milliseconds"] ?? 0);
|
|
154
|
+
if (years)
|
|
155
|
+
parts.push(`${years} year${Math.abs(years) !== 1 ? "s" : ""}`);
|
|
156
|
+
if (months)
|
|
157
|
+
parts.push(`${months} month${Math.abs(months) !== 1 ? "s" : ""}`);
|
|
158
|
+
if (days)
|
|
159
|
+
parts.push(`${days} day${Math.abs(days) !== 1 ? "s" : ""}`);
|
|
160
|
+
const totalSeconds = seconds + ms / 1000;
|
|
161
|
+
if (hours || minutes || totalSeconds) {
|
|
162
|
+
const h = String(Math.abs(hours)).padStart(2, "0");
|
|
163
|
+
const m = String(Math.abs(minutes)).padStart(2, "0");
|
|
164
|
+
const s = Math.abs(totalSeconds)
|
|
165
|
+
.toFixed(totalSeconds % 1 !== 0 ? 6 : 0)
|
|
166
|
+
.padStart(2, "0");
|
|
167
|
+
const sign = hours < 0 ||
|
|
168
|
+
(hours === 0 && minutes < 0) ||
|
|
169
|
+
(hours === 0 && minutes === 0 && totalSeconds < 0)
|
|
170
|
+
? "-"
|
|
171
|
+
: "";
|
|
172
|
+
parts.push(`${sign}${h}:${m}:${s}`);
|
|
173
|
+
}
|
|
174
|
+
return parts.length > 0 ? parts.join(" ") : "0";
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Deserializes a value from the diff JSON back to the form expected by pg.
|
|
178
|
+
* - {$type: 'bytea', $value: '<base64>'} -> Buffer
|
|
179
|
+
* - everything else -> as-is
|
|
180
|
+
*/
|
|
181
|
+
function deserializeValue(value) {
|
|
182
|
+
if (value === null || value === undefined) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
if (typeof value === "object" &&
|
|
186
|
+
!Array.isArray(value) &&
|
|
187
|
+
value["$type"] === "bytea") {
|
|
188
|
+
const b64 = value["$value"];
|
|
189
|
+
return Buffer.from(b64, "base64");
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
return value.map(deserializeValue);
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Compares two serialized values for equality.
|
|
198
|
+
* - null vs null is equal
|
|
199
|
+
* - JSON objects are compared structurally (key order does not matter)
|
|
200
|
+
* - Arrays are compared element-wise
|
|
201
|
+
* - Primitives use strict equality
|
|
202
|
+
*
|
|
203
|
+
* This is used for jsonb structural comparison and general value comparison.
|
|
204
|
+
*/
|
|
205
|
+
function valuesAreEqual(a, b) {
|
|
206
|
+
if (a === b)
|
|
207
|
+
return true;
|
|
208
|
+
if (a === null && b === null)
|
|
209
|
+
return true;
|
|
210
|
+
if (a === null || b === null)
|
|
211
|
+
return false;
|
|
212
|
+
// Compare Buffer-encoded bytea
|
|
213
|
+
if (typeof a === "object" &&
|
|
214
|
+
typeof b === "object" &&
|
|
215
|
+
a["$type"] === "bytea" &&
|
|
216
|
+
b["$type"] === "bytea") {
|
|
217
|
+
return (a["$value"] ===
|
|
218
|
+
b["$value"]);
|
|
219
|
+
}
|
|
220
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
221
|
+
if (a.length !== b.length)
|
|
222
|
+
return false;
|
|
223
|
+
return a.every((v, i) => valuesAreEqual(v, b[i]));
|
|
224
|
+
}
|
|
225
|
+
if (typeof a === "object" &&
|
|
226
|
+
typeof b === "object" &&
|
|
227
|
+
!Array.isArray(a) &&
|
|
228
|
+
!Array.isArray(b)) {
|
|
229
|
+
// Structural comparison for JSON/JSONB objects
|
|
230
|
+
const aKeys = Object.keys(a).sort();
|
|
231
|
+
const bKeys = Object.keys(b).sort();
|
|
232
|
+
if (aKeys.length !== bKeys.length)
|
|
233
|
+
return false;
|
|
234
|
+
if (!aKeys.every((k, i) => k === bKeys[i]))
|
|
235
|
+
return false;
|
|
236
|
+
return aKeys.every((k) => valuesAreEqual(a[k], b[k]));
|
|
237
|
+
}
|
|
238
|
+
// Fall back to string comparison for numeric precision
|
|
239
|
+
// pg returns numeric as strings
|
|
240
|
+
return String(a) === String(b);
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=serialize-value.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildYamlOutputPath = buildYamlOutputPath;
|
|
37
|
+
exports.writeYamlFile = writeYamlFile;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const yaml_1 = require("yaml");
|
|
40
|
+
function buildYamlOutputPath(jsonOutputPath) {
|
|
41
|
+
return jsonOutputPath.endsWith(".json")
|
|
42
|
+
? jsonOutputPath.replace(/\.json$/i, ".yaml")
|
|
43
|
+
: `${jsonOutputPath}.yaml`;
|
|
44
|
+
}
|
|
45
|
+
function writeYamlFile(filePath, data) {
|
|
46
|
+
fs.writeFileSync(filePath, (0, yaml_1.stringify)(data), "utf-8");
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=write-diff-yaml.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import { type SchemaDiffJson } from "./schema-diff-schema";
|
|
3
|
+
export interface GenerateSchemaDiffOptions {
|
|
4
|
+
schema: string;
|
|
5
|
+
tables: string[];
|
|
6
|
+
excludeTables: string[];
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
onProgress?: (message: string) => void;
|
|
9
|
+
onVerboseProgress?: (message: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function generateSchemaDiff(sourcePool: Pool, destPool: Pool, options: GenerateSchemaDiffOptions): Promise<SchemaDiffJson>;
|
|
12
|
+
//# sourceMappingURL=generate-schema-diff.d.ts.map
|