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.
Files changed (62) hide show
  1. package/README.md +628 -0
  2. package/dist/apply/apply-diff.d.ts +24 -0
  3. package/dist/apply/apply-diff.js +205 -0
  4. package/dist/apply/conflict.d.ts +20 -0
  5. package/dist/apply/conflict.js +14 -0
  6. package/dist/apply/plan.d.ts +13 -0
  7. package/dist/apply/plan.js +37 -0
  8. package/dist/cli/apply.d.ts +8 -0
  9. package/dist/cli/apply.js +270 -0
  10. package/dist/cli/generator.d.ts +9 -0
  11. package/dist/cli/generator.js +804 -0
  12. package/dist/cli/pg-triggers.d.ts +2 -0
  13. package/dist/cli/pg-triggers.js +185 -0
  14. package/dist/cli/root.d.ts +8 -0
  15. package/dist/cli/root.js +231 -0
  16. package/dist/cli/sql.d.ts +9 -0
  17. package/dist/cli/sql.js +158 -0
  18. package/dist/config/config-schema.d.ts +380 -0
  19. package/dist/config/config-schema.js +95 -0
  20. package/dist/config/load-config.d.ts +12 -0
  21. package/dist/config/load-config.js +87 -0
  22. package/dist/config/resolve-options.d.ts +95 -0
  23. package/dist/config/resolve-options.js +183 -0
  24. package/dist/config/write-config.d.ts +18 -0
  25. package/dist/config/write-config.js +103 -0
  26. package/dist/db/connection.d.ts +28 -0
  27. package/dist/db/connection.js +34 -0
  28. package/dist/db/metadata.d.ts +70 -0
  29. package/dist/db/metadata.js +238 -0
  30. package/dist/db/pg-triggers.d.ts +27 -0
  31. package/dist/db/pg-triggers.js +59 -0
  32. package/dist/db/sql.d.ts +72 -0
  33. package/dist/db/sql.js +250 -0
  34. package/dist/diff/diff-schema.d.ts +380 -0
  35. package/dist/diff/diff-schema.js +67 -0
  36. package/dist/diff/generate-diff.d.ts +20 -0
  37. package/dist/diff/generate-diff.js +224 -0
  38. package/dist/diff/pg-triggers-diff.d.ts +11 -0
  39. package/dist/diff/pg-triggers-diff.js +161 -0
  40. package/dist/diff/serialize-value.d.ts +35 -0
  41. package/dist/diff/serialize-value.js +242 -0
  42. package/dist/diff/write-diff-yaml.d.ts +3 -0
  43. package/dist/diff/write-diff-yaml.js +48 -0
  44. package/dist/schema-diff/generate-schema-diff.d.ts +12 -0
  45. package/dist/schema-diff/generate-schema-diff.js +355 -0
  46. package/dist/schema-diff/generate-schema-sql.d.ts +20 -0
  47. package/dist/schema-diff/generate-schema-sql.js +187 -0
  48. package/dist/schema-diff/schema-diff-schema.d.ts +1088 -0
  49. package/dist/schema-diff/schema-diff-schema.js +70 -0
  50. package/dist/shared/env-values.d.ts +11 -0
  51. package/dist/shared/env-values.js +100 -0
  52. package/dist/shared/generator-wizard.d.ts +10 -0
  53. package/dist/shared/generator-wizard.js +337 -0
  54. package/dist/shared/identifiers.d.ts +24 -0
  55. package/dist/shared/identifiers.js +45 -0
  56. package/dist/shared/prompts.d.ts +26 -0
  57. package/dist/shared/prompts.js +104 -0
  58. package/dist/shared/summary.d.ts +33 -0
  59. package/dist/shared/summary.js +41 -0
  60. package/dist/sql/generate-sql.d.ts +19 -0
  61. package/dist/sql/generate-sql.js +223 -0
  62. package/package.json +39 -0
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConflictError = void 0;
4
+ exports.applyTableDiff = applyTableDiff;
5
+ const serialize_value_1 = require("../diff/serialize-value");
6
+ const sql_1 = require("../db/sql");
7
+ const metadata_1 = require("../db/metadata");
8
+ /**
9
+ * Applies a single table diff to the destination database.
10
+ * Modifies summary in-place.
11
+ */
12
+ async function applyTableDiff(client, tableDiff, options, summary) {
13
+ const { schema, table, primaryKey, inserts, updates, deletes } = tableDiff;
14
+ // Fetch column type metadata for type-correct guard WHERE clauses.
15
+ // Skip if dry-run and there's nothing to process.
16
+ let columnTypes = {};
17
+ const needsTypes = !options.dryRun &&
18
+ ((options.applyUpdates && updates.length > 0) ||
19
+ (options.applyDeletes && deletes.length > 0));
20
+ if (needsTypes) {
21
+ columnTypes = await (0, metadata_1.fetchColumnTypeMap)(client, schema, table);
22
+ }
23
+ // 1. Inserts
24
+ if (options.applyInserts && inserts.length > 0) {
25
+ for (const record of inserts) {
26
+ await applyInsert(client, schema, table, primaryKey, record, options, summary);
27
+ }
28
+ }
29
+ // 2. Updates
30
+ if (options.applyUpdates && updates.length > 0) {
31
+ for (const record of updates) {
32
+ await applyUpdate(client, schema, table, primaryKey, record, options, summary, columnTypes);
33
+ }
34
+ }
35
+ // 3. Deletes (only if explicitly enabled)
36
+ if (options.applyDeletes && deletes.length > 0) {
37
+ for (const record of deletes) {
38
+ await applyDelete(client, schema, table, primaryKey, record, options, summary, columnTypes);
39
+ }
40
+ }
41
+ }
42
+ async function applyInsert(client, schema, table, pkColumns, record, options, summary) {
43
+ const columns = Object.keys(record.row);
44
+ const deserializedRow = {};
45
+ for (const col of columns) {
46
+ deserializedRow[col] = (0, serialize_value_1.deserializeValue)(record.row[col]);
47
+ }
48
+ const pkValues = {};
49
+ for (const col of pkColumns) {
50
+ pkValues[col] = deserializedRow[col];
51
+ }
52
+ if (options.verbose) {
53
+ console.log(` INSERT into ${schema}.${table} pk=${JSON.stringify(pkValues)}`);
54
+ }
55
+ if (options.dryRun) {
56
+ summary.applied.inserts++;
57
+ return;
58
+ }
59
+ if (options.conflictMode === "skip" || options.conflictMode === "overwrite") {
60
+ // Use a savepoint so a failure doesn't abort the entire transaction
61
+ const sp = `sp_insert_${Date.now()}_${Math.random().toString(36).slice(2)}`;
62
+ await client.query(`SAVEPOINT ${sp}`);
63
+ try {
64
+ if (options.insertMode === "upsert") {
65
+ await (0, sql_1.upsertRow)(client, schema, table, deserializedRow, columns, pkColumns);
66
+ }
67
+ else {
68
+ await (0, sql_1.insertRow)(client, schema, table, deserializedRow, columns);
69
+ }
70
+ await client.query(`RELEASE SAVEPOINT ${sp}`);
71
+ summary.applied.inserts++;
72
+ }
73
+ catch (err) {
74
+ await client.query(`ROLLBACK TO SAVEPOINT ${sp}`);
75
+ await client.query(`RELEASE SAVEPOINT ${sp}`);
76
+ if (options.conflictMode === "skip") {
77
+ const reason = err instanceof Error ? err.message : String(err);
78
+ summary.skipped.push({
79
+ table: `${schema}.${table}`,
80
+ operation: "insert",
81
+ pk: pkValues,
82
+ reason,
83
+ });
84
+ }
85
+ else {
86
+ // overwrite: try upsert as fallback
87
+ await (0, sql_1.upsertRow)(client, schema, table, deserializedRow, columns, pkColumns);
88
+ summary.applied.inserts++;
89
+ }
90
+ }
91
+ }
92
+ else {
93
+ // abort mode: let errors propagate directly
94
+ try {
95
+ if (options.insertMode === "upsert") {
96
+ await (0, sql_1.upsertRow)(client, schema, table, deserializedRow, columns, pkColumns);
97
+ }
98
+ else {
99
+ // strict: will throw if PK already exists
100
+ await (0, sql_1.insertRow)(client, schema, table, deserializedRow, columns);
101
+ }
102
+ summary.applied.inserts++;
103
+ }
104
+ catch (err) {
105
+ const reason = err instanceof Error ? err.message : String(err);
106
+ throw new ConflictError(`Insert conflict in ${schema}.${table} pk=${JSON.stringify(pkValues)}: ${reason}`);
107
+ }
108
+ }
109
+ }
110
+ async function applyUpdate(client, schema, table, pkColumns, record, options, summary, columnTypes = {}) {
111
+ const pkValues = {};
112
+ for (const col of pkColumns) {
113
+ pkValues[col] = (0, serialize_value_1.deserializeValue)(record.pk[col]);
114
+ }
115
+ const setColumns = Object.keys(record.changes);
116
+ const setValues = {};
117
+ for (const col of setColumns) {
118
+ setValues[col] = (0, serialize_value_1.deserializeValue)(record.changes[col].to);
119
+ }
120
+ if (options.verbose) {
121
+ console.log(` UPDATE ${schema}.${table} pk=${JSON.stringify(pkValues)} columns=[${setColumns.join(", ")}]`);
122
+ }
123
+ if (options.dryRun) {
124
+ summary.applied.updates++;
125
+ return;
126
+ }
127
+ const guardCols = [];
128
+ const guardVals = {};
129
+ if (options.conflictMode !== "overwrite") {
130
+ for (const [col, value] of Object.entries(record.guard)) {
131
+ if (!pkColumns.includes(col)) {
132
+ guardCols.push(col);
133
+ guardVals[col] = (0, serialize_value_1.deserializeValue)(value);
134
+ }
135
+ }
136
+ }
137
+ const rowsUpdated = await (0, sql_1.updateRow)(client, schema, table, pkColumns, pkValues, setColumns, setValues, guardCols, guardVals, columnTypes);
138
+ if (rowsUpdated === 0) {
139
+ // Either PK not found, or guard failed
140
+ const reason = "Row not found or guard check failed (destination row may have changed)";
141
+ if (options.conflictMode === "abort") {
142
+ throw new ConflictError(`Update conflict in ${schema}.${table} pk=${JSON.stringify(pkValues)}: ${reason}`);
143
+ }
144
+ else if (options.conflictMode === "skip") {
145
+ summary.skipped.push({
146
+ table: `${schema}.${table}`,
147
+ operation: "update",
148
+ pk: pkValues,
149
+ reason,
150
+ });
151
+ }
152
+ }
153
+ else {
154
+ summary.applied.updates++;
155
+ }
156
+ }
157
+ async function applyDelete(client, schema, table, pkColumns, record, options, summary, columnTypes = {}) {
158
+ const pkValues = {};
159
+ for (const col of pkColumns) {
160
+ pkValues[col] = (0, serialize_value_1.deserializeValue)(record.pk[col]);
161
+ }
162
+ // Build guard from all guard columns (not just pk)
163
+ const guardColumns = Object.keys(record.guard).filter((c) => !pkColumns.includes(c));
164
+ const guardVals = {};
165
+ for (const col of guardColumns) {
166
+ guardVals[col] = (0, serialize_value_1.deserializeValue)(record.guard[col]);
167
+ }
168
+ if (options.verbose) {
169
+ console.log(` DELETE from ${schema}.${table} pk=${JSON.stringify(pkValues)}`);
170
+ }
171
+ if (options.dryRun) {
172
+ summary.applied.deletes++;
173
+ return;
174
+ }
175
+ // Always use guarded delete (abort/skip modes check guards)
176
+ const rowsDeleted = await (0, sql_1.deleteRow)(client, schema, table, pkColumns, pkValues, guardColumns, guardVals, columnTypes);
177
+ if (rowsDeleted === 0) {
178
+ const reason = "Row not found or guard check failed (destination row may have changed)";
179
+ if (options.conflictMode === "abort") {
180
+ throw new ConflictError(`Delete conflict in ${schema}.${table} pk=${JSON.stringify(pkValues)}: ${reason}`);
181
+ }
182
+ else if (options.conflictMode === "skip") {
183
+ summary.skipped.push({
184
+ table: `${schema}.${table}`,
185
+ operation: "delete",
186
+ pk: pkValues,
187
+ reason,
188
+ });
189
+ }
190
+ }
191
+ else {
192
+ summary.applied.deletes++;
193
+ }
194
+ }
195
+ /**
196
+ * Thrown when a conflict is detected in abort mode.
197
+ */
198
+ class ConflictError extends Error {
199
+ constructor(message) {
200
+ super(message);
201
+ this.name = "ConflictError";
202
+ }
203
+ }
204
+ exports.ConflictError = ConflictError;
205
+ //# sourceMappingURL=apply-diff.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Conflict handling documentation and utilities.
3
+ *
4
+ * Conflict modes:
5
+ *
6
+ * abort - (default) Abort the transaction on the first conflict.
7
+ * The destination database is left unchanged (if transaction=true).
8
+ *
9
+ * skip - Skip the conflicting row and continue applying other rows.
10
+ * Skipped rows are recorded in the summary.
11
+ *
12
+ * overwrite - Ignore the "from" guard for updates and force the "to" value.
13
+ * For inserts, behaves like upsert.
14
+ * Note: overwrite does NOT disable guarded deletes.
15
+ * Deletes still check the guard even in overwrite mode
16
+ * to prevent accidental data loss.
17
+ */
18
+ export type ConflictMode = "abort" | "skip" | "overwrite";
19
+ export declare function describeConflictMode(mode: ConflictMode): string;
20
+ //# sourceMappingURL=conflict.d.ts.map
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeConflictMode = describeConflictMode;
4
+ function describeConflictMode(mode) {
5
+ switch (mode) {
6
+ case "abort":
7
+ return "abort: Roll back transaction on first conflict (default, safest)";
8
+ case "skip":
9
+ return "skip: Skip conflicting rows and continue";
10
+ case "overwrite":
11
+ return "overwrite: Force apply changes, ignoring from-value guards for updates";
12
+ }
13
+ }
14
+ //# sourceMappingURL=conflict.js.map
@@ -0,0 +1,13 @@
1
+ import { Pool } from "pg";
2
+ import { type DiffJson } from "../diff/diff-schema";
3
+ import { type ApplyTableOptions } from "./apply-diff";
4
+ import { type ApplySummary } from "../shared/summary";
5
+ export interface RunApplyOptions extends ApplyTableOptions {
6
+ transaction: boolean;
7
+ }
8
+ /**
9
+ * Applies a full diff to the destination database.
10
+ * Handles transactions, conflict modes, and dry-run behavior.
11
+ */
12
+ export declare function runApply(destPool: Pool, diff: DiffJson, options: RunApplyOptions): Promise<ApplySummary>;
13
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runApply = runApply;
4
+ const apply_diff_1 = require("./apply-diff");
5
+ const summary_1 = require("../shared/summary");
6
+ /**
7
+ * Applies a full diff to the destination database.
8
+ * Handles transactions, conflict modes, and dry-run behavior.
9
+ */
10
+ async function runApply(destPool, diff, options) {
11
+ const summary = (0, summary_1.createEmptySummary)();
12
+ const client = await destPool.connect();
13
+ try {
14
+ if (options.transaction && !options.dryRun) {
15
+ await client.query("BEGIN");
16
+ }
17
+ try {
18
+ for (const tableDiff of diff.tables) {
19
+ await (0, apply_diff_1.applyTableDiff)(client, tableDiff, options, summary);
20
+ }
21
+ if (options.transaction && !options.dryRun) {
22
+ await client.query("COMMIT");
23
+ }
24
+ }
25
+ catch (err) {
26
+ if (options.transaction && !options.dryRun) {
27
+ await client.query("ROLLBACK");
28
+ }
29
+ throw err;
30
+ }
31
+ }
32
+ finally {
33
+ client.release();
34
+ }
35
+ return summary;
36
+ }
37
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * frg-data-diff apply
4
+ *
5
+ * Reads a JSON diff file and safely applies it to a destination PostgreSQL database.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * frg-data-diff apply
5
+ *
6
+ * Reads a JSON diff file and safely applies it to a destination PostgreSQL database.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ const commander_1 = require("commander");
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const load_config_1 = require("../config/load-config");
46
+ const resolve_options_1 = require("../config/resolve-options");
47
+ const connection_1 = require("../db/connection");
48
+ const diff_schema_1 = require("../diff/diff-schema");
49
+ const plan_1 = require("../apply/plan");
50
+ const summary_1 = require("../shared/summary");
51
+ const prompts_1 = require("../shared/prompts");
52
+ const env_values_1 = require("../shared/env-values");
53
+ const program = new commander_1.Command();
54
+ program
55
+ .name("frg-data-diff apply")
56
+ .description("Reads a JSON diff file and safely applies it to a destination PostgreSQL database.")
57
+ .option("--dest-pg-host <host>", "Destination database host")
58
+ .option("--dest-pg-port <port>", "Destination database port", (v) => parseInt(v, 10))
59
+ .option("--dest-pg-database <db>", "Destination database name")
60
+ .option("--dest-pg-user <user>", "Destination database user")
61
+ .option("--dest-pg-password-env <value>", "Destination DB password or $ENV_VAR reference")
62
+ .option("--dest-pg-ssl", "Use SSL for destination database connection")
63
+ .option("--no-dest-pg-ssl", "Do not use SSL for destination database connection")
64
+ .option("--input <file>", "Input diff file path", "frg-data-diff.json")
65
+ .option("--dry-run", "Simulate the apply without making any changes (default)")
66
+ .option("--execute", "Apply changes to the destination database (required to mutate)")
67
+ .option("--apply-inserts", "Apply inserts (default: true)")
68
+ .option("--no-apply-inserts", "Do not apply inserts")
69
+ .option("--apply-updates", "Apply updates (default: true)")
70
+ .option("--no-apply-updates", "Do not apply updates")
71
+ .option("--apply-deletes", "Apply deletes (default: false, requires explicit opt-in)")
72
+ .option("--no-apply-deletes", "Do not apply deletes (default)")
73
+ .option("--conflict-mode <mode>", "Conflict mode: abort, skip, or overwrite", "abort")
74
+ .option("--insert-mode <mode>", "Insert mode: strict or upsert", "strict")
75
+ .option("--transaction", "Wrap all changes in a single transaction (default: true)")
76
+ .option("--no-transaction", "Do not wrap changes in a transaction")
77
+ .option("--verbose", "Enable verbose logging")
78
+ .option("--config <file>", "Path to config file", load_config_1.DEFAULT_CONFIG_FILENAME)
79
+ .option("--yes", "Skip interactive confirmation (for CI/CD)");
80
+ program.parse(process.argv);
81
+ const opts = program.opts();
82
+ async function main() {
83
+ // Validate conflicting flags
84
+ if (opts["dryRun"] && opts["execute"]) {
85
+ console.error("Error: --dry-run and --execute cannot both be specified.");
86
+ process.exit(1);
87
+ }
88
+ const configFilePath = path.resolve(opts["config"] || load_config_1.DEFAULT_CONFIG_FILENAME);
89
+ const configExists = fs.existsSync(configFilePath);
90
+ let applyConfig;
91
+ if (configExists) {
92
+ const config = (0, load_config_1.loadConfig)(configFilePath);
93
+ applyConfig = config.apply;
94
+ }
95
+ // Determine dryRun: --execute overrides config/default dryRun
96
+ const dryRunFromCli = resolveDryRunFromCli(opts["execute"], opts["dryRun"]);
97
+ // Build resolved options from CLI args + config
98
+ const cliArgs = {
99
+ destPgHost: opts["destPgHost"],
100
+ destPgPort: opts["destPgPort"],
101
+ destPgDatabase: opts["destPgDatabase"],
102
+ destPgUser: opts["destPgUser"],
103
+ destPgPassword: opts["destPgPasswordEnv"],
104
+ destPgSsl: normalizeOptionalBoolean(opts["destPgSsl"]),
105
+ input: opts["input"],
106
+ dryRun: dryRunFromCli,
107
+ applyInserts: normalizeOptionalBoolean(opts["applyInserts"]),
108
+ applyUpdates: normalizeOptionalBoolean(opts["applyUpdates"]),
109
+ applyDeletes: normalizeOptionalBoolean(opts["applyDeletes"]),
110
+ conflictMode: opts["conflictMode"],
111
+ insertMode: opts["insertMode"],
112
+ transaction: normalizeOptionalBoolean(opts["transaction"]),
113
+ verbose: opts["verbose"] ? true : undefined,
114
+ };
115
+ // Remove undefined values
116
+ const cleanCliArgs = Object.fromEntries(Object.entries(cliArgs).filter(([, v]) => v !== undefined));
117
+ const resolved = (0, resolve_options_1.resolveApplyOptions)(applyConfig, cleanCliArgs);
118
+ const runtimeResolved = (0, resolve_options_1.resolveRuntimeApplyOptions)(resolved);
119
+ // Validate required values
120
+ if (!resolved.destPgHost ||
121
+ !resolved.destPgDatabase ||
122
+ !resolved.destPgUser ||
123
+ !resolved.destPgPassword) {
124
+ console.error("Error: Missing required destination database connection options.");
125
+ console.error("Provide --dest-pg-host, --dest-pg-database, --dest-pg-user, --dest-pg-password-env");
126
+ console.error("or configure them in .frg-data-diff.config.json");
127
+ process.exit(1);
128
+ }
129
+ if (!runtimeResolved.input) {
130
+ console.error("Error: Missing required --input diff file path.");
131
+ process.exit(1);
132
+ }
133
+ const inputPath = path.resolve(runtimeResolved.input);
134
+ if (!fs.existsSync(inputPath)) {
135
+ console.error(`Error: Diff file not found: ${inputPath}`);
136
+ process.exit(1);
137
+ }
138
+ // Validate conflict mode
139
+ const destConnection = (0, connection_1.resolveConnectionParams)({
140
+ host: resolved.destPgHost,
141
+ port: resolved.destPgPort,
142
+ database: resolved.destPgDatabase,
143
+ user: resolved.destPgUser,
144
+ password: resolved.destPgPassword,
145
+ ssl: runtimeResolved.destPgSsl,
146
+ }, {
147
+ host: "destination host",
148
+ port: "destination port",
149
+ database: "destination database",
150
+ user: "destination user",
151
+ password: "destination password",
152
+ });
153
+ // Print resolved plan
154
+ printResolvedApplyPlan(resolved, runtimeResolved, inputPath, destConnection);
155
+ // Ask for confirmation if no --yes flag
156
+ if (!opts["yes"]) {
157
+ const proceed = await (0, prompts_1.confirmProceed)();
158
+ if (!proceed) {
159
+ console.log("Aborted.");
160
+ process.exit(0);
161
+ }
162
+ }
163
+ // Load and validate the diff file
164
+ let rawDiff;
165
+ try {
166
+ const content = fs.readFileSync(inputPath, "utf-8");
167
+ rawDiff = JSON.parse(content);
168
+ }
169
+ catch (err) {
170
+ console.error(`Error reading diff file: ${inputPath}`);
171
+ if (err instanceof Error)
172
+ console.error(err.message);
173
+ process.exit(1);
174
+ }
175
+ let diff;
176
+ try {
177
+ diff = (0, diff_schema_1.validateDiffJson)(rawDiff);
178
+ }
179
+ catch (err) {
180
+ console.error("Diff file validation failed:");
181
+ console.error(err instanceof Error ? err.message : String(err));
182
+ process.exit(1);
183
+ }
184
+ if (runtimeResolved.dryRun) {
185
+ console.log("\n[DRY RUN] No changes will be made to the destination database.");
186
+ }
187
+ else {
188
+ console.log("\nApplying changes to destination database...");
189
+ }
190
+ const destPool = (0, connection_1.createPool)(destConnection);
191
+ try {
192
+ const summary = await (0, plan_1.runApply)(destPool, diff, {
193
+ dryRun: runtimeResolved.dryRun,
194
+ applyInserts: runtimeResolved.applyInserts,
195
+ applyUpdates: runtimeResolved.applyUpdates,
196
+ applyDeletes: runtimeResolved.applyDeletes,
197
+ conflictMode: runtimeResolved.conflictMode,
198
+ insertMode: runtimeResolved.insertMode,
199
+ transaction: runtimeResolved.transaction,
200
+ verbose: resolved.verbose,
201
+ });
202
+ (0, summary_1.printSummary)(summary, runtimeResolved.dryRun);
203
+ // Output machine-readable summary
204
+ console.log("\nMachine-readable summary:");
205
+ console.log(JSON.stringify(summary, null, 2));
206
+ }
207
+ finally {
208
+ await destPool.end();
209
+ }
210
+ }
211
+ function printResolvedApplyPlan(resolved, runtimeResolved, inputPath, destConnection) {
212
+ const resolvedDest = destConnection ??
213
+ (0, connection_1.resolveConnectionParams)({
214
+ host: resolved.destPgHost,
215
+ port: resolved.destPgPort,
216
+ database: resolved.destPgDatabase,
217
+ user: resolved.destPgUser,
218
+ password: resolved.destPgPassword,
219
+ ssl: runtimeResolved.destPgSsl,
220
+ }, {
221
+ host: "destination host",
222
+ port: "destination port",
223
+ database: "destination database",
224
+ user: "destination user",
225
+ password: "destination password",
226
+ });
227
+ console.log("\ntool:");
228
+ console.log(" frg-data-diff apply");
229
+ console.log("\ndest:");
230
+ console.log(` host: ${(0, env_values_1.formatVisibleValue)(resolved.destPgHost, resolvedDest.host)}`);
231
+ console.log(` port: ${typeof resolved.destPgPort === "string" ? `${resolved.destPgPort} -> ${resolvedDest.port}` : resolvedDest.port}`);
232
+ console.log(` database: ${(0, env_values_1.formatVisibleValue)(resolved.destPgDatabase, resolvedDest.database)}`);
233
+ console.log(` user: ${(0, env_values_1.formatVisibleValue)(resolved.destPgUser, resolvedDest.user)}`);
234
+ console.log(` password: ${(0, env_values_1.formatSecretValue)(String(resolved.destPgPassword))}`);
235
+ console.log(` ssl: ${runtimeResolved.destPgSsl}`);
236
+ console.log(`\ninput: ${inputPath}`);
237
+ console.log(`dry-run: ${runtimeResolved.dryRun}`);
238
+ console.log(`apply inserts: ${runtimeResolved.applyInserts}`);
239
+ console.log(`apply updates: ${runtimeResolved.applyUpdates}`);
240
+ console.log(`apply deletes: ${runtimeResolved.applyDeletes}`);
241
+ console.log(`conflict mode: ${runtimeResolved.conflictMode}`);
242
+ console.log(`insert mode: ${runtimeResolved.insertMode}`);
243
+ console.log(`transaction: ${runtimeResolved.transaction}`);
244
+ if (runtimeResolved.applyDeletes) {
245
+ console.log("\nWarning: applyDeletes is enabled. Rows may be deleted from the destination database.");
246
+ }
247
+ }
248
+ main().catch((err) => {
249
+ console.error("Fatal error:", err instanceof Error ? err.message : String(err));
250
+ process.exit(1);
251
+ });
252
+ function normalizeOptionalBoolean(value) {
253
+ if (value === true) {
254
+ return true;
255
+ }
256
+ if (value === false) {
257
+ return false;
258
+ }
259
+ return undefined;
260
+ }
261
+ function resolveDryRunFromCli(execute, dryRun) {
262
+ if (execute === true) {
263
+ return false;
264
+ }
265
+ if (dryRun === true) {
266
+ return true;
267
+ }
268
+ return undefined;
269
+ }
270
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * frg-data-diff generate
4
+ *
5
+ * Compares a source PostgreSQL database against a destination PostgreSQL database
6
+ * and writes a JSON diff file.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=generator.d.ts.map