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,380 @@
1
+ import { z } from "zod";
2
+ declare const changeValueSchema: z.ZodObject<{
3
+ from: z.ZodUnknown;
4
+ to: z.ZodUnknown;
5
+ }, "strip", z.ZodTypeAny, {
6
+ from?: unknown;
7
+ to?: unknown;
8
+ }, {
9
+ from?: unknown;
10
+ to?: unknown;
11
+ }>;
12
+ declare const updateRecordSchema: z.ZodObject<{
13
+ pk: z.ZodRecord<z.ZodString, z.ZodUnknown>;
14
+ changes: z.ZodRecord<z.ZodString, z.ZodObject<{
15
+ from: z.ZodUnknown;
16
+ to: z.ZodUnknown;
17
+ }, "strip", z.ZodTypeAny, {
18
+ from?: unknown;
19
+ to?: unknown;
20
+ }, {
21
+ from?: unknown;
22
+ to?: unknown;
23
+ }>>;
24
+ guard: z.ZodRecord<z.ZodString, z.ZodUnknown>;
25
+ }, "strip", z.ZodTypeAny, {
26
+ pk: Record<string, unknown>;
27
+ changes: Record<string, {
28
+ from?: unknown;
29
+ to?: unknown;
30
+ }>;
31
+ guard: Record<string, unknown>;
32
+ }, {
33
+ pk: Record<string, unknown>;
34
+ changes: Record<string, {
35
+ from?: unknown;
36
+ to?: unknown;
37
+ }>;
38
+ guard: Record<string, unknown>;
39
+ }>;
40
+ declare const insertRecordSchema: z.ZodObject<{
41
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
42
+ }, "strip", z.ZodTypeAny, {
43
+ row: Record<string, unknown>;
44
+ }, {
45
+ row: Record<string, unknown>;
46
+ }>;
47
+ declare const deleteRecordSchema: z.ZodObject<{
48
+ pk: z.ZodRecord<z.ZodString, z.ZodUnknown>;
49
+ guard: z.ZodRecord<z.ZodString, z.ZodUnknown>;
50
+ }, "strip", z.ZodTypeAny, {
51
+ pk: Record<string, unknown>;
52
+ guard: Record<string, unknown>;
53
+ }, {
54
+ pk: Record<string, unknown>;
55
+ guard: Record<string, unknown>;
56
+ }>;
57
+ declare const tableDiffSchema: z.ZodObject<{
58
+ schema: z.ZodString;
59
+ table: z.ZodString;
60
+ primaryKey: z.ZodArray<z.ZodString, "many">;
61
+ columnTypes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
62
+ updates: z.ZodArray<z.ZodObject<{
63
+ pk: z.ZodRecord<z.ZodString, z.ZodUnknown>;
64
+ changes: z.ZodRecord<z.ZodString, z.ZodObject<{
65
+ from: z.ZodUnknown;
66
+ to: z.ZodUnknown;
67
+ }, "strip", z.ZodTypeAny, {
68
+ from?: unknown;
69
+ to?: unknown;
70
+ }, {
71
+ from?: unknown;
72
+ to?: unknown;
73
+ }>>;
74
+ guard: z.ZodRecord<z.ZodString, z.ZodUnknown>;
75
+ }, "strip", z.ZodTypeAny, {
76
+ pk: Record<string, unknown>;
77
+ changes: Record<string, {
78
+ from?: unknown;
79
+ to?: unknown;
80
+ }>;
81
+ guard: Record<string, unknown>;
82
+ }, {
83
+ pk: Record<string, unknown>;
84
+ changes: Record<string, {
85
+ from?: unknown;
86
+ to?: unknown;
87
+ }>;
88
+ guard: Record<string, unknown>;
89
+ }>, "many">;
90
+ inserts: z.ZodArray<z.ZodObject<{
91
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
92
+ }, "strip", z.ZodTypeAny, {
93
+ row: Record<string, unknown>;
94
+ }, {
95
+ row: Record<string, unknown>;
96
+ }>, "many">;
97
+ deletes: z.ZodArray<z.ZodObject<{
98
+ pk: z.ZodRecord<z.ZodString, z.ZodUnknown>;
99
+ guard: z.ZodRecord<z.ZodString, z.ZodUnknown>;
100
+ }, "strip", z.ZodTypeAny, {
101
+ pk: Record<string, unknown>;
102
+ guard: Record<string, unknown>;
103
+ }, {
104
+ pk: Record<string, unknown>;
105
+ guard: Record<string, unknown>;
106
+ }>, "many">;
107
+ }, "strip", z.ZodTypeAny, {
108
+ schema: string;
109
+ table: string;
110
+ primaryKey: string[];
111
+ updates: {
112
+ pk: Record<string, unknown>;
113
+ changes: Record<string, {
114
+ from?: unknown;
115
+ to?: unknown;
116
+ }>;
117
+ guard: Record<string, unknown>;
118
+ }[];
119
+ inserts: {
120
+ row: Record<string, unknown>;
121
+ }[];
122
+ deletes: {
123
+ pk: Record<string, unknown>;
124
+ guard: Record<string, unknown>;
125
+ }[];
126
+ columnTypes?: Record<string, string> | undefined;
127
+ }, {
128
+ schema: string;
129
+ table: string;
130
+ primaryKey: string[];
131
+ updates: {
132
+ pk: Record<string, unknown>;
133
+ changes: Record<string, {
134
+ from?: unknown;
135
+ to?: unknown;
136
+ }>;
137
+ guard: Record<string, unknown>;
138
+ }[];
139
+ inserts: {
140
+ row: Record<string, unknown>;
141
+ }[];
142
+ deletes: {
143
+ pk: Record<string, unknown>;
144
+ guard: Record<string, unknown>;
145
+ }[];
146
+ columnTypes?: Record<string, string> | undefined;
147
+ }>;
148
+ export declare const diffJsonSchema: z.ZodObject<{
149
+ format: z.ZodLiteral<"postgres-data-diff-json/v1">;
150
+ generatedAt: z.ZodString;
151
+ source: z.ZodObject<{
152
+ schema: z.ZodString;
153
+ }, "strip", z.ZodTypeAny, {
154
+ schema: string;
155
+ }, {
156
+ schema: string;
157
+ }>;
158
+ dest: z.ZodObject<{
159
+ schema: z.ZodString;
160
+ }, "strip", z.ZodTypeAny, {
161
+ schema: string;
162
+ }, {
163
+ schema: string;
164
+ }>;
165
+ options: z.ZodObject<{
166
+ includeDeletes: z.ZodBoolean;
167
+ ignoredColumns: z.ZodArray<z.ZodString, "many">;
168
+ }, "strip", z.ZodTypeAny, {
169
+ includeDeletes: boolean;
170
+ ignoredColumns: string[];
171
+ }, {
172
+ includeDeletes: boolean;
173
+ ignoredColumns: string[];
174
+ }>;
175
+ tables: z.ZodArray<z.ZodObject<{
176
+ schema: z.ZodString;
177
+ table: z.ZodString;
178
+ primaryKey: z.ZodArray<z.ZodString, "many">;
179
+ columnTypes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
180
+ updates: z.ZodArray<z.ZodObject<{
181
+ pk: z.ZodRecord<z.ZodString, z.ZodUnknown>;
182
+ changes: z.ZodRecord<z.ZodString, z.ZodObject<{
183
+ from: z.ZodUnknown;
184
+ to: z.ZodUnknown;
185
+ }, "strip", z.ZodTypeAny, {
186
+ from?: unknown;
187
+ to?: unknown;
188
+ }, {
189
+ from?: unknown;
190
+ to?: unknown;
191
+ }>>;
192
+ guard: z.ZodRecord<z.ZodString, z.ZodUnknown>;
193
+ }, "strip", z.ZodTypeAny, {
194
+ pk: Record<string, unknown>;
195
+ changes: Record<string, {
196
+ from?: unknown;
197
+ to?: unknown;
198
+ }>;
199
+ guard: Record<string, unknown>;
200
+ }, {
201
+ pk: Record<string, unknown>;
202
+ changes: Record<string, {
203
+ from?: unknown;
204
+ to?: unknown;
205
+ }>;
206
+ guard: Record<string, unknown>;
207
+ }>, "many">;
208
+ inserts: z.ZodArray<z.ZodObject<{
209
+ row: z.ZodRecord<z.ZodString, z.ZodUnknown>;
210
+ }, "strip", z.ZodTypeAny, {
211
+ row: Record<string, unknown>;
212
+ }, {
213
+ row: Record<string, unknown>;
214
+ }>, "many">;
215
+ deletes: z.ZodArray<z.ZodObject<{
216
+ pk: z.ZodRecord<z.ZodString, z.ZodUnknown>;
217
+ guard: z.ZodRecord<z.ZodString, z.ZodUnknown>;
218
+ }, "strip", z.ZodTypeAny, {
219
+ pk: Record<string, unknown>;
220
+ guard: Record<string, unknown>;
221
+ }, {
222
+ pk: Record<string, unknown>;
223
+ guard: Record<string, unknown>;
224
+ }>, "many">;
225
+ }, "strip", z.ZodTypeAny, {
226
+ schema: string;
227
+ table: string;
228
+ primaryKey: string[];
229
+ updates: {
230
+ pk: Record<string, unknown>;
231
+ changes: Record<string, {
232
+ from?: unknown;
233
+ to?: unknown;
234
+ }>;
235
+ guard: Record<string, unknown>;
236
+ }[];
237
+ inserts: {
238
+ row: Record<string, unknown>;
239
+ }[];
240
+ deletes: {
241
+ pk: Record<string, unknown>;
242
+ guard: Record<string, unknown>;
243
+ }[];
244
+ columnTypes?: Record<string, string> | undefined;
245
+ }, {
246
+ schema: string;
247
+ table: string;
248
+ primaryKey: string[];
249
+ updates: {
250
+ pk: Record<string, unknown>;
251
+ changes: Record<string, {
252
+ from?: unknown;
253
+ to?: unknown;
254
+ }>;
255
+ guard: Record<string, unknown>;
256
+ }[];
257
+ inserts: {
258
+ row: Record<string, unknown>;
259
+ }[];
260
+ deletes: {
261
+ pk: Record<string, unknown>;
262
+ guard: Record<string, unknown>;
263
+ }[];
264
+ columnTypes?: Record<string, string> | undefined;
265
+ }>, "many">;
266
+ summary: z.ZodObject<{
267
+ tablesCompared: z.ZodNumber;
268
+ updates: z.ZodNumber;
269
+ inserts: z.ZodNumber;
270
+ deletes: z.ZodNumber;
271
+ skippedTables: z.ZodArray<z.ZodString, "many">;
272
+ }, "strip", z.ZodTypeAny, {
273
+ updates: number;
274
+ inserts: number;
275
+ deletes: number;
276
+ tablesCompared: number;
277
+ skippedTables: string[];
278
+ }, {
279
+ updates: number;
280
+ inserts: number;
281
+ deletes: number;
282
+ tablesCompared: number;
283
+ skippedTables: string[];
284
+ }>;
285
+ }, "strip", z.ZodTypeAny, {
286
+ options: {
287
+ includeDeletes: boolean;
288
+ ignoredColumns: string[];
289
+ };
290
+ format: "postgres-data-diff-json/v1";
291
+ generatedAt: string;
292
+ source: {
293
+ schema: string;
294
+ };
295
+ dest: {
296
+ schema: string;
297
+ };
298
+ tables: {
299
+ schema: string;
300
+ table: string;
301
+ primaryKey: string[];
302
+ updates: {
303
+ pk: Record<string, unknown>;
304
+ changes: Record<string, {
305
+ from?: unknown;
306
+ to?: unknown;
307
+ }>;
308
+ guard: Record<string, unknown>;
309
+ }[];
310
+ inserts: {
311
+ row: Record<string, unknown>;
312
+ }[];
313
+ deletes: {
314
+ pk: Record<string, unknown>;
315
+ guard: Record<string, unknown>;
316
+ }[];
317
+ columnTypes?: Record<string, string> | undefined;
318
+ }[];
319
+ summary: {
320
+ updates: number;
321
+ inserts: number;
322
+ deletes: number;
323
+ tablesCompared: number;
324
+ skippedTables: string[];
325
+ };
326
+ }, {
327
+ options: {
328
+ includeDeletes: boolean;
329
+ ignoredColumns: string[];
330
+ };
331
+ format: "postgres-data-diff-json/v1";
332
+ generatedAt: string;
333
+ source: {
334
+ schema: string;
335
+ };
336
+ dest: {
337
+ schema: string;
338
+ };
339
+ tables: {
340
+ schema: string;
341
+ table: string;
342
+ primaryKey: string[];
343
+ updates: {
344
+ pk: Record<string, unknown>;
345
+ changes: Record<string, {
346
+ from?: unknown;
347
+ to?: unknown;
348
+ }>;
349
+ guard: Record<string, unknown>;
350
+ }[];
351
+ inserts: {
352
+ row: Record<string, unknown>;
353
+ }[];
354
+ deletes: {
355
+ pk: Record<string, unknown>;
356
+ guard: Record<string, unknown>;
357
+ }[];
358
+ columnTypes?: Record<string, string> | undefined;
359
+ }[];
360
+ summary: {
361
+ updates: number;
362
+ inserts: number;
363
+ deletes: number;
364
+ tablesCompared: number;
365
+ skippedTables: string[];
366
+ };
367
+ }>;
368
+ export type DiffJson = z.infer<typeof diffJsonSchema>;
369
+ export type TableDiff = z.infer<typeof tableDiffSchema>;
370
+ export type UpdateRecord = z.infer<typeof updateRecordSchema>;
371
+ export type InsertRecord = z.infer<typeof insertRecordSchema>;
372
+ export type DeleteRecord = z.infer<typeof deleteRecordSchema>;
373
+ export type ChangeValue = z.infer<typeof changeValueSchema>;
374
+ /**
375
+ * Validates a diff JSON object against the schema.
376
+ * Throws if invalid.
377
+ */
378
+ export declare function validateDiffJson(data: unknown): DiffJson;
379
+ export {};
380
+ //# sourceMappingURL=diff-schema.d.ts.map
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.diffJsonSchema = void 0;
4
+ exports.validateDiffJson = validateDiffJson;
5
+ const zod_1 = require("zod");
6
+ // Individual diff change record
7
+ const changeValueSchema = zod_1.z.object({
8
+ from: zod_1.z.unknown(),
9
+ to: zod_1.z.unknown(),
10
+ });
11
+ const updateRecordSchema = zod_1.z.object({
12
+ pk: zod_1.z.record(zod_1.z.unknown()),
13
+ changes: zod_1.z.record(changeValueSchema),
14
+ guard: zod_1.z.record(zod_1.z.unknown()),
15
+ });
16
+ const insertRecordSchema = zod_1.z.object({
17
+ row: zod_1.z.record(zod_1.z.unknown()),
18
+ });
19
+ const deleteRecordSchema = zod_1.z.object({
20
+ pk: zod_1.z.record(zod_1.z.unknown()),
21
+ guard: zod_1.z.record(zod_1.z.unknown()),
22
+ });
23
+ const tableDiffSchema = zod_1.z.object({
24
+ schema: zod_1.z.string(),
25
+ table: zod_1.z.string(),
26
+ primaryKey: zod_1.z.array(zod_1.z.string()),
27
+ columnTypes: zod_1.z.record(zod_1.z.string()).optional(),
28
+ updates: zod_1.z.array(updateRecordSchema),
29
+ inserts: zod_1.z.array(insertRecordSchema),
30
+ deletes: zod_1.z.array(deleteRecordSchema),
31
+ });
32
+ const diffOptionsSchema = zod_1.z.object({
33
+ includeDeletes: zod_1.z.boolean(),
34
+ ignoredColumns: zod_1.z.array(zod_1.z.string()),
35
+ });
36
+ const diffSourceDestSchema = zod_1.z.object({
37
+ schema: zod_1.z.string(),
38
+ });
39
+ const diffSummarySchema = zod_1.z.object({
40
+ tablesCompared: zod_1.z.number().int(),
41
+ updates: zod_1.z.number().int(),
42
+ inserts: zod_1.z.number().int(),
43
+ deletes: zod_1.z.number().int(),
44
+ skippedTables: zod_1.z.array(zod_1.z.string()),
45
+ });
46
+ exports.diffJsonSchema = zod_1.z.object({
47
+ format: zod_1.z.literal("postgres-data-diff-json/v1"),
48
+ generatedAt: zod_1.z.string(),
49
+ source: diffSourceDestSchema,
50
+ dest: diffSourceDestSchema,
51
+ options: diffOptionsSchema,
52
+ tables: zod_1.z.array(tableDiffSchema),
53
+ summary: diffSummarySchema,
54
+ });
55
+ /**
56
+ * Validates a diff JSON object against the schema.
57
+ * Throws if invalid.
58
+ */
59
+ function validateDiffJson(data) {
60
+ const result = exports.diffJsonSchema.safeParse(data);
61
+ if (!result.success) {
62
+ throw new Error("Invalid diff JSON format:\n" +
63
+ JSON.stringify(result.error.format(), null, 2));
64
+ }
65
+ return result.data;
66
+ }
67
+ //# sourceMappingURL=diff-schema.js.map
@@ -0,0 +1,20 @@
1
+ import { Pool } from "pg";
2
+ import { type DiffJson } from "./diff-schema";
3
+ export interface GenerateDiffOptions {
4
+ schema: string;
5
+ tables: string[];
6
+ excludeTables: string[];
7
+ ignoreColumns: string[];
8
+ tablesWhereDataFilters?: Record<string, string>;
9
+ includeDeletes: boolean;
10
+ skipMissingPk: boolean;
11
+ verbose: boolean;
12
+ onProgress?: (message: string) => void;
13
+ onVerboseProgress?: (message: string) => void;
14
+ }
15
+ /**
16
+ * Generates a full diff between source and dest databases.
17
+ * Compares tables row by row using primary key matching.
18
+ */
19
+ export declare function generateDiff(sourcePool: Pool, destPool: Pool, options: GenerateDiffOptions): Promise<DiffJson>;
20
+ //# sourceMappingURL=generate-diff.d.ts.map
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateDiff = generateDiff;
4
+ const metadata_1 = require("../db/metadata");
5
+ const sql_1 = require("../db/sql");
6
+ const serialize_value_1 = require("./serialize-value");
7
+ const identifiers_1 = require("../shared/identifiers");
8
+ /**
9
+ * Generates a full diff between source and dest databases.
10
+ * Compares tables row by row using primary key matching.
11
+ */
12
+ async function generateDiff(sourcePool, destPool, options) {
13
+ const sourceClient = await sourcePool.connect();
14
+ const destClient = await destPool.connect();
15
+ const tableDiffs = [];
16
+ const skippedTables = [];
17
+ let totalUpdates = 0;
18
+ let totalInserts = 0;
19
+ let totalDeletes = 0;
20
+ try {
21
+ for (const [tableIndex, table] of options.tables.entries()) {
22
+ const progressLabel = formatTableProgressLabel(tableIndex + 1, options.tables.length, options.schema, table);
23
+ if (options.excludeTables.includes(table)) {
24
+ reportVerboseProgress(options, `${progressLabel}: skipping excluded table`);
25
+ continue;
26
+ }
27
+ (0, identifiers_1.validateIdentifier)(table, "table name");
28
+ reportProgress(options.onProgress, `${progressLabel}: loading table metadata`);
29
+ // Fetch metadata from both source and dest
30
+ const [sourceMeta, destMeta] = await Promise.all([
31
+ (0, metadata_1.fetchTableMetadata)(sourceClient, options.schema, table),
32
+ (0, metadata_1.fetchTableMetadata)(destClient, options.schema, table),
33
+ ]);
34
+ // Primary key check
35
+ if (!sourceMeta.hasPrimaryKey) {
36
+ if (options.skipMissingPk) {
37
+ console.warn(`Skipping table without primary key: ${options.schema}.${table}`);
38
+ skippedTables.push(table);
39
+ continue;
40
+ }
41
+ else {
42
+ throw new Error(`Table ${options.schema}.${table} has no primary key. ` +
43
+ `Use --skip-missing-pk to skip tables without primary keys.`);
44
+ }
45
+ }
46
+ // Find common columns (present in both source and dest, not generated, not ignored)
47
+ const sourceColSet = new Set(sourceMeta.normalColumns);
48
+ const destColSet = new Set(destMeta.normalColumns);
49
+ const commonColumns = sourceMeta.normalColumns.filter((c) => destColSet.has(c) && !options.ignoreColumns.includes(c));
50
+ const pkColumns = sourceMeta.primaryKey;
51
+ // Build a map of column name -> data type for type-aware serialization
52
+ const colTypeMap = buildColTypeMap(sourceMeta.columns);
53
+ // Ensure pk columns are always fetched even if ignored
54
+ const fetchColumns = [...new Set([...pkColumns, ...commonColumns])];
55
+ reportVerboseProgress(options, `${progressLabel}: using primary key ${pkColumns.join(", ")} and ${commonColumns.length} comparable column(s)`);
56
+ // Fetch all rows from source and dest
57
+ const whereDataFilter = options.tablesWhereDataFilters?.[table];
58
+ reportProgress(options.onProgress, `${progressLabel}: fetching source and destination rows`);
59
+ const sourceRowsPromise = (0, sql_1.fetchRowsBatched)(sourceClient, options.schema, table, fetchColumns, pkColumns, undefined, whereDataFilter, (progress) => {
60
+ reportVerboseProgress(options, `${progressLabel}: source batch ${progress.batchNumber} fetched ${progress.batchRows} row(s), ${progress.fetchedRows} total`);
61
+ });
62
+ const destRowsPromise = (0, sql_1.fetchRowsBatched)(destClient, options.schema, table, fetchColumns, pkColumns, undefined, whereDataFilter, (progress) => {
63
+ reportVerboseProgress(options, `${progressLabel}: destination batch ${progress.batchNumber} fetched ${progress.batchRows} row(s), ${progress.fetchedRows} total`);
64
+ });
65
+ const [sourceRows, destRows] = await Promise.all([
66
+ sourceRowsPromise,
67
+ destRowsPromise,
68
+ ]);
69
+ reportProgress(options.onProgress, `${progressLabel}: fetched ${sourceRows.length} source row(s) and ${destRows.length} destination row(s)`);
70
+ reportProgress(options.onProgress, `${progressLabel}: comparing row sets`);
71
+ // Index dest rows by PK
72
+ const destByPk = new Map();
73
+ for (const row of destRows) {
74
+ const pk = buildPkKey(pkColumns, row, colTypeMap);
75
+ destByPk.set(pk, row);
76
+ }
77
+ const updates = [];
78
+ const inserts = [];
79
+ const deletes = [];
80
+ // Compare source rows against dest
81
+ const seenPkKeys = new Set();
82
+ for (const sourceRow of sourceRows) {
83
+ const pkKey = buildPkKey(pkColumns, sourceRow, colTypeMap);
84
+ seenPkKeys.add(pkKey);
85
+ const pkValues = extractPkValues(pkColumns, sourceRow);
86
+ const destRow = destByPk.get(pkKey);
87
+ if (!destRow) {
88
+ // Row exists in source but not in dest -> INSERT
89
+ const serializedRow = {};
90
+ for (const col of fetchColumns) {
91
+ serializedRow[col] = (0, serialize_value_1.serializeValue)(sourceRow[col], colTypeMap[col]);
92
+ }
93
+ inserts.push({ row: serializedRow });
94
+ }
95
+ else {
96
+ // Row exists in both -> check for changes
97
+ const changes = {};
98
+ const compareColumns = commonColumns.filter((c) => !pkColumns.includes(c));
99
+ for (const col of compareColumns) {
100
+ const srcVal = (0, serialize_value_1.serializeValue)(sourceRow[col], colTypeMap[col]);
101
+ const dstVal = (0, serialize_value_1.serializeValue)(destRow[col], colTypeMap[col]);
102
+ if (!(0, serialize_value_1.valuesAreEqual)(srcVal, dstVal)) {
103
+ changes[col] = { from: dstVal, to: srcVal };
104
+ }
105
+ }
106
+ if (Object.keys(changes).length > 0) {
107
+ const serializedPk = {};
108
+ for (const col of pkColumns) {
109
+ serializedPk[col] = (0, serialize_value_1.serializeValue)(pkValues[col], colTypeMap[col]);
110
+ }
111
+ const guard = {};
112
+ for (const col of fetchColumns) {
113
+ guard[col] = (0, serialize_value_1.serializeValue)(destRow[col], colTypeMap[col]);
114
+ }
115
+ updates.push({ pk: serializedPk, changes, guard });
116
+ }
117
+ }
118
+ }
119
+ // Find deletes: rows in dest that are not in source
120
+ if (options.includeDeletes) {
121
+ for (const destRow of destRows) {
122
+ const pkKey = buildPkKey(pkColumns, destRow, colTypeMap);
123
+ if (!seenPkKeys.has(pkKey)) {
124
+ const pkValues = extractPkValues(pkColumns, destRow);
125
+ const serializedPk = {};
126
+ for (const col of pkColumns) {
127
+ serializedPk[col] = (0, serialize_value_1.serializeValue)(pkValues[col], colTypeMap[col]);
128
+ }
129
+ // Build guard from all non-ignored, non-generated columns
130
+ const guard = {};
131
+ for (const col of fetchColumns) {
132
+ guard[col] = (0, serialize_value_1.serializeValue)(destRow[col], colTypeMap[col]);
133
+ }
134
+ deletes.push({ pk: serializedPk, guard });
135
+ }
136
+ }
137
+ }
138
+ if (updates.length > 0 || inserts.length > 0 || deletes.length > 0) {
139
+ tableDiffs.push({
140
+ table,
141
+ schema: options.schema,
142
+ primaryKey: pkColumns,
143
+ columnTypes: colTypeMap,
144
+ updates,
145
+ inserts,
146
+ deletes,
147
+ });
148
+ }
149
+ totalUpdates += updates.length;
150
+ totalInserts += inserts.length;
151
+ totalDeletes += deletes.length;
152
+ reportProgress(options.onProgress, `${progressLabel}: completed with ${inserts.length} insert(s), ${updates.length} update(s), ${deletes.length} delete(s)`);
153
+ }
154
+ }
155
+ finally {
156
+ sourceClient.release();
157
+ destClient.release();
158
+ }
159
+ return {
160
+ format: "postgres-data-diff-json/v1",
161
+ generatedAt: new Date().toISOString(),
162
+ source: { schema: options.schema },
163
+ dest: { schema: options.schema },
164
+ options: {
165
+ includeDeletes: options.includeDeletes,
166
+ ignoredColumns: options.ignoreColumns,
167
+ },
168
+ tables: tableDiffs,
169
+ summary: {
170
+ tablesCompared: options.tables.length - skippedTables.length,
171
+ updates: totalUpdates,
172
+ inserts: totalInserts,
173
+ deletes: totalDeletes,
174
+ skippedTables,
175
+ },
176
+ };
177
+ }
178
+ /**
179
+ * Builds a string key from primary key column values.
180
+ */
181
+ function buildPkKey(pkColumns, row, colTypeMap) {
182
+ return pkColumns
183
+ .map((col) => JSON.stringify((0, serialize_value_1.serializeValue)(row[col], colTypeMap[col])))
184
+ .join("|||");
185
+ }
186
+ /**
187
+ * Builds a map from column name to data type for type-aware serialization.
188
+ */
189
+ function buildColTypeMap(columns) {
190
+ const map = {};
191
+ for (const col of columns) {
192
+ map[col.columnName] = col.dataType;
193
+ }
194
+ return map;
195
+ }
196
+ /**
197
+ * Extracts primary key values from a row.
198
+ */
199
+ function extractPkValues(pkColumns, row) {
200
+ const result = {};
201
+ for (const col of pkColumns) {
202
+ result[col] = row[col];
203
+ }
204
+ return result;
205
+ }
206
+ function formatTableProgressLabel(tablePosition, totalTables, schema, table) {
207
+ return `[data ${tablePosition}/${totalTables}] ${schema}.${table}`;
208
+ }
209
+ function reportProgress(callback, message) {
210
+ if (callback) {
211
+ callback(message);
212
+ }
213
+ }
214
+ function reportVerboseProgress(options, message) {
215
+ if (!options.verbose) {
216
+ return;
217
+ }
218
+ if (options.onVerboseProgress) {
219
+ options.onVerboseProgress(message);
220
+ return;
221
+ }
222
+ console.log(message);
223
+ }
224
+ //# sourceMappingURL=generate-diff.js.map
@@ -0,0 +1,11 @@
1
+ import { type Pool } from "pg";
2
+ export interface GeneratePgTriggersDiffOptions {
3
+ schema: string;
4
+ tables: string[];
5
+ excludeTables: string[];
6
+ verbose: boolean;
7
+ onProgress?: (message: string) => void;
8
+ onVerboseProgress?: (message: string) => void;
9
+ }
10
+ export declare function generatePgTriggersDiffSql(sourcePool: Pool, destPool: Pool, options: GeneratePgTriggersDiffOptions): Promise<string>;
11
+ //# sourceMappingURL=pg-triggers-diff.d.ts.map