@pgpmjs/core 3.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 (140) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +99 -0
  3. package/core/boilerplate-scanner.d.ts +41 -0
  4. package/core/boilerplate-scanner.js +106 -0
  5. package/core/boilerplate-types.d.ts +52 -0
  6. package/core/boilerplate-types.js +6 -0
  7. package/core/class/pgpm.d.ts +150 -0
  8. package/core/class/pgpm.js +1470 -0
  9. package/core/template-scaffold.d.ts +29 -0
  10. package/core/template-scaffold.js +168 -0
  11. package/esm/core/boilerplate-scanner.js +96 -0
  12. package/esm/core/boilerplate-types.js +5 -0
  13. package/esm/core/class/pgpm.js +1430 -0
  14. package/esm/core/template-scaffold.js +161 -0
  15. package/esm/export/export-meta.js +240 -0
  16. package/esm/export/export-migrations.js +180 -0
  17. package/esm/extensions/extensions.js +31 -0
  18. package/esm/files/extension/index.js +3 -0
  19. package/esm/files/extension/reader.js +79 -0
  20. package/esm/files/extension/writer.js +63 -0
  21. package/esm/files/index.js +6 -0
  22. package/esm/files/plan/generator.js +49 -0
  23. package/esm/files/plan/index.js +5 -0
  24. package/esm/files/plan/parser.js +296 -0
  25. package/esm/files/plan/validators.js +181 -0
  26. package/esm/files/plan/writer.js +114 -0
  27. package/esm/files/sql/index.js +1 -0
  28. package/esm/files/sql/writer.js +107 -0
  29. package/esm/files/sql-scripts/index.js +2 -0
  30. package/esm/files/sql-scripts/reader.js +19 -0
  31. package/esm/files/types/index.js +1 -0
  32. package/esm/files/types/package.js +1 -0
  33. package/esm/index.js +21 -0
  34. package/esm/init/client.js +144 -0
  35. package/esm/init/sql/bootstrap-roles.sql +55 -0
  36. package/esm/init/sql/bootstrap-test-roles.sql +72 -0
  37. package/esm/migrate/clean.js +23 -0
  38. package/esm/migrate/client.js +551 -0
  39. package/esm/migrate/index.js +5 -0
  40. package/esm/migrate/sql/procedures.sql +258 -0
  41. package/esm/migrate/sql/schema.sql +37 -0
  42. package/esm/migrate/types.js +1 -0
  43. package/esm/migrate/utils/event-logger.js +28 -0
  44. package/esm/migrate/utils/hash.js +27 -0
  45. package/esm/migrate/utils/transaction.js +125 -0
  46. package/esm/modules/modules.js +49 -0
  47. package/esm/packaging/package.js +96 -0
  48. package/esm/packaging/transform.js +70 -0
  49. package/esm/projects/deploy.js +123 -0
  50. package/esm/projects/revert.js +75 -0
  51. package/esm/projects/verify.js +61 -0
  52. package/esm/resolution/deps.js +526 -0
  53. package/esm/resolution/resolve.js +101 -0
  54. package/esm/utils/debug.js +147 -0
  55. package/esm/utils/target-utils.js +37 -0
  56. package/esm/workspace/paths.js +43 -0
  57. package/esm/workspace/utils.js +31 -0
  58. package/export/export-meta.d.ts +8 -0
  59. package/export/export-meta.js +244 -0
  60. package/export/export-migrations.d.ts +17 -0
  61. package/export/export-migrations.js +187 -0
  62. package/extensions/extensions.d.ts +5 -0
  63. package/extensions/extensions.js +35 -0
  64. package/files/extension/index.d.ts +2 -0
  65. package/files/extension/index.js +19 -0
  66. package/files/extension/reader.d.ts +24 -0
  67. package/files/extension/reader.js +86 -0
  68. package/files/extension/writer.d.ts +39 -0
  69. package/files/extension/writer.js +70 -0
  70. package/files/index.d.ts +5 -0
  71. package/files/index.js +22 -0
  72. package/files/plan/generator.d.ts +22 -0
  73. package/files/plan/generator.js +57 -0
  74. package/files/plan/index.d.ts +4 -0
  75. package/files/plan/index.js +21 -0
  76. package/files/plan/parser.d.ts +27 -0
  77. package/files/plan/parser.js +303 -0
  78. package/files/plan/validators.d.ts +52 -0
  79. package/files/plan/validators.js +187 -0
  80. package/files/plan/writer.d.ts +27 -0
  81. package/files/plan/writer.js +124 -0
  82. package/files/sql/index.d.ts +1 -0
  83. package/files/sql/index.js +17 -0
  84. package/files/sql/writer.d.ts +12 -0
  85. package/files/sql/writer.js +114 -0
  86. package/files/sql-scripts/index.d.ts +1 -0
  87. package/files/sql-scripts/index.js +18 -0
  88. package/files/sql-scripts/reader.d.ts +8 -0
  89. package/files/sql-scripts/reader.js +23 -0
  90. package/files/types/index.d.ts +46 -0
  91. package/files/types/index.js +17 -0
  92. package/files/types/package.d.ts +20 -0
  93. package/files/types/package.js +2 -0
  94. package/index.d.ts +21 -0
  95. package/index.js +45 -0
  96. package/init/client.d.ts +26 -0
  97. package/init/client.js +148 -0
  98. package/init/sql/bootstrap-roles.sql +55 -0
  99. package/init/sql/bootstrap-test-roles.sql +72 -0
  100. package/migrate/clean.d.ts +1 -0
  101. package/migrate/clean.js +27 -0
  102. package/migrate/client.d.ts +80 -0
  103. package/migrate/client.js +555 -0
  104. package/migrate/index.d.ts +5 -0
  105. package/migrate/index.js +21 -0
  106. package/migrate/sql/procedures.sql +258 -0
  107. package/migrate/sql/schema.sql +37 -0
  108. package/migrate/types.d.ts +67 -0
  109. package/migrate/types.js +2 -0
  110. package/migrate/utils/event-logger.d.ts +13 -0
  111. package/migrate/utils/event-logger.js +32 -0
  112. package/migrate/utils/hash.d.ts +12 -0
  113. package/migrate/utils/hash.js +32 -0
  114. package/migrate/utils/transaction.d.ts +27 -0
  115. package/migrate/utils/transaction.js +129 -0
  116. package/modules/modules.d.ts +31 -0
  117. package/modules/modules.js +56 -0
  118. package/package.json +70 -0
  119. package/packaging/package.d.ts +19 -0
  120. package/packaging/package.js +102 -0
  121. package/packaging/transform.d.ts +22 -0
  122. package/packaging/transform.js +75 -0
  123. package/projects/deploy.d.ts +8 -0
  124. package/projects/deploy.js +160 -0
  125. package/projects/revert.d.ts +15 -0
  126. package/projects/revert.js +112 -0
  127. package/projects/verify.d.ts +8 -0
  128. package/projects/verify.js +98 -0
  129. package/resolution/deps.d.ts +57 -0
  130. package/resolution/deps.js +531 -0
  131. package/resolution/resolve.d.ts +37 -0
  132. package/resolution/resolve.js +107 -0
  133. package/utils/debug.d.ts +21 -0
  134. package/utils/debug.js +153 -0
  135. package/utils/target-utils.d.ts +5 -0
  136. package/utils/target-utils.js +40 -0
  137. package/workspace/paths.d.ts +14 -0
  138. package/workspace/paths.js +50 -0
  139. package/workspace/utils.d.ts +8 -0
  140. package/workspace/utils.js +36 -0
@@ -0,0 +1,258 @@
1
+ -- Register a package (auto-called by deploy if needed)
2
+ CREATE PROCEDURE pgpm_migrate.register_package(p_package TEXT)
3
+ LANGUAGE plpgsql AS $$
4
+ BEGIN
5
+ INSERT INTO pgpm_migrate.packages (package)
6
+ VALUES (p_package)
7
+ ON CONFLICT (package) DO NOTHING;
8
+ END;
9
+ $$;
10
+
11
+ -- Check if a change is deployed (handles both local and cross-package dependencies)
12
+ CREATE FUNCTION pgpm_migrate.is_deployed(
13
+ p_package TEXT,
14
+ p_change_name TEXT
15
+ )
16
+ RETURNS BOOLEAN
17
+ LANGUAGE plpgsql STABLE AS $$
18
+ DECLARE
19
+ v_actual_package TEXT;
20
+ v_actual_change TEXT;
21
+ v_colon_pos INT;
22
+ BEGIN
23
+ -- Check if change_name contains a package prefix (cross-package dependency)
24
+ v_colon_pos := position(':' in p_change_name);
25
+
26
+ IF v_colon_pos > 0 THEN
27
+ -- Split into package and change name
28
+ v_actual_package := substring(p_change_name from 1 for v_colon_pos - 1);
29
+ v_actual_change := substring(p_change_name from v_colon_pos + 1);
30
+ ELSE
31
+ -- Use provided package as default
32
+ v_actual_package := p_package;
33
+ v_actual_change := p_change_name;
34
+ END IF;
35
+
36
+ RETURN EXISTS (
37
+ SELECT 1 FROM pgpm_migrate.changes
38
+ WHERE package = v_actual_package
39
+ AND change_name = v_actual_change
40
+ );
41
+ END;
42
+ $$;
43
+
44
+ -- Deploy a change
45
+ CREATE PROCEDURE pgpm_migrate.deploy(
46
+ p_package TEXT,
47
+ p_change_name TEXT,
48
+ p_script_hash TEXT,
49
+ p_requires TEXT[],
50
+ p_deploy_sql TEXT,
51
+ p_log_only BOOLEAN DEFAULT FALSE
52
+ )
53
+ LANGUAGE plpgsql AS $$
54
+ DECLARE
55
+ v_change_id TEXT;
56
+ BEGIN
57
+ -- Ensure package exists
58
+ CALL pgpm_migrate.register_package(p_package);
59
+
60
+ -- Generate simple ID
61
+ v_change_id := encode(sha256((p_package || p_change_name || p_script_hash)::bytea), 'hex');
62
+
63
+ -- Check if already deployed
64
+ IF pgpm_migrate.is_deployed(p_package, p_change_name) THEN
65
+ -- Check if it's the same script (by hash)
66
+ IF EXISTS (
67
+ SELECT 1 FROM pgpm_migrate.changes
68
+ WHERE package = p_package
69
+ AND change_name = p_change_name
70
+ AND script_hash = p_script_hash
71
+ ) THEN
72
+ -- Same change with same content, skip silently
73
+ RETURN;
74
+ ELSE
75
+ -- Different content, this is an error
76
+ RAISE EXCEPTION 'Change % already deployed in package % with different content', p_change_name, p_package;
77
+ END IF;
78
+ END IF;
79
+
80
+ -- Check dependencies
81
+ IF p_requires IS NOT NULL THEN
82
+ DECLARE
83
+ missing_changes TEXT[];
84
+ BEGIN
85
+ SELECT array_agg(req) INTO missing_changes
86
+ FROM unnest(p_requires) AS req
87
+ WHERE NOT pgpm_migrate.is_deployed(p_package, req);
88
+
89
+ IF array_length(missing_changes, 1) > 0 THEN
90
+ RAISE EXCEPTION 'Missing required changes for %: %', p_change_name, array_to_string(missing_changes, ', ');
91
+ END IF;
92
+ END;
93
+ END IF;
94
+
95
+ -- Execute deploy (skip if log-only mode)
96
+ IF NOT p_log_only THEN
97
+ BEGIN
98
+ EXECUTE p_deploy_sql;
99
+ EXCEPTION WHEN OTHERS THEN
100
+ RAISE;
101
+ END;
102
+ END IF;
103
+
104
+ -- Record deployment
105
+ INSERT INTO pgpm_migrate.changes (change_id, change_name, package, script_hash)
106
+ VALUES (v_change_id, p_change_name, p_package, p_script_hash);
107
+
108
+ -- Record dependencies (INSERTED AFTER SUCCESSFUL DEPLOYMENT)
109
+ IF p_requires IS NOT NULL THEN
110
+ INSERT INTO pgpm_migrate.dependencies (change_id, requires)
111
+ SELECT v_change_id, req FROM unnest(p_requires) AS req;
112
+ END IF;
113
+
114
+ -- Log success
115
+ INSERT INTO pgpm_migrate.events (event_type, change_name, package)
116
+ VALUES ('deploy', p_change_name, p_package);
117
+ END;
118
+ $$;
119
+
120
+ -- Revert a change
121
+ CREATE PROCEDURE pgpm_migrate.revert(
122
+ p_package TEXT,
123
+ p_change_name TEXT,
124
+ p_revert_sql TEXT
125
+ )
126
+ LANGUAGE plpgsql AS $$
127
+ BEGIN
128
+ -- Check if deployed
129
+ IF NOT pgpm_migrate.is_deployed(p_package, p_change_name) THEN
130
+ RAISE EXCEPTION 'Change % not deployed in package %', p_change_name, p_package;
131
+ END IF;
132
+
133
+ -- Check if other changes depend on this (including cross-package dependencies)
134
+ IF EXISTS (
135
+ SELECT 1 FROM pgpm_migrate.dependencies d
136
+ JOIN pgpm_migrate.changes c ON c.change_id = d.change_id
137
+ WHERE (
138
+ -- Local dependency within same package
139
+ (d.requires = p_change_name AND c.package = p_package)
140
+ OR
141
+ -- Cross-package dependency
142
+ (d.requires = p_package || ':' || p_change_name)
143
+ )
144
+ ) THEN
145
+ -- Get list of dependent changes for better error message
146
+ DECLARE
147
+ dependent_changes TEXT;
148
+ BEGIN
149
+ SELECT string_agg(
150
+ CASE
151
+ WHEN d.requires = p_change_name THEN c.change_name
152
+ ELSE c.package || ':' || c.change_name
153
+ END,
154
+ ', '
155
+ ) INTO dependent_changes
156
+ FROM pgpm_migrate.dependencies d
157
+ JOIN pgpm_migrate.changes c ON c.change_id = d.change_id
158
+ WHERE (
159
+ (d.requires = p_change_name AND c.package = p_package)
160
+ OR
161
+ (d.requires = p_package || ':' || p_change_name)
162
+ );
163
+
164
+ RAISE EXCEPTION 'Cannot revert %: required by %', p_change_name, dependent_changes;
165
+ END;
166
+ END IF;
167
+
168
+ -- Execute revert
169
+ EXECUTE p_revert_sql;
170
+
171
+ -- Remove from deployed
172
+ DELETE FROM pgpm_migrate.changes
173
+ WHERE package = p_package AND change_name = p_change_name;
174
+
175
+ -- Log revert
176
+ INSERT INTO pgpm_migrate.events (event_type, change_name, package)
177
+ VALUES ('revert', p_change_name, p_package);
178
+ END;
179
+ $$;
180
+
181
+ -- Verify a change
182
+ CREATE FUNCTION pgpm_migrate.verify(
183
+ p_package TEXT,
184
+ p_change_name TEXT,
185
+ p_verify_sql TEXT
186
+ )
187
+ RETURNS BOOLEAN
188
+ LANGUAGE plpgsql AS $$
189
+ BEGIN
190
+ EXECUTE p_verify_sql;
191
+ RETURN TRUE;
192
+ EXCEPTION WHEN OTHERS THEN
193
+ RETURN FALSE;
194
+ END;
195
+ $$;
196
+
197
+ -- List deployed changes
198
+ CREATE FUNCTION pgpm_migrate.deployed_changes(
199
+ p_package TEXT DEFAULT NULL
200
+ )
201
+ RETURNS TABLE(package TEXT, change_name TEXT, deployed_at TIMESTAMPTZ)
202
+ LANGUAGE sql STABLE AS $$
203
+ SELECT package, change_name, deployed_at
204
+ FROM pgpm_migrate.changes
205
+ WHERE p_package IS NULL OR package = p_package
206
+ ORDER BY deployed_at;
207
+ $$;
208
+
209
+ -- Get changes that depend on a given change
210
+ CREATE FUNCTION pgpm_migrate.get_dependents(
211
+ p_package TEXT,
212
+ p_change_name TEXT
213
+ )
214
+ RETURNS TABLE(package TEXT, change_name TEXT, dependency TEXT)
215
+ LANGUAGE sql STABLE AS $$
216
+ SELECT c.package, c.change_name, d.requires as dependency
217
+ FROM pgpm_migrate.dependencies d
218
+ JOIN pgpm_migrate.changes c ON c.change_id = d.change_id
219
+ WHERE (
220
+ -- Local dependency within same package
221
+ (d.requires = p_change_name AND c.package = p_package)
222
+ OR
223
+ -- Cross-package dependency
224
+ (d.requires = p_package || ':' || p_change_name)
225
+ )
226
+ ORDER BY c.package, c.change_name;
227
+ $$;
228
+
229
+ -- Get deployment status
230
+ CREATE FUNCTION pgpm_migrate.status(
231
+ p_package TEXT DEFAULT NULL
232
+ )
233
+ RETURNS TABLE(
234
+ package TEXT,
235
+ total_deployed INTEGER,
236
+ last_change TEXT,
237
+ last_deployed TIMESTAMPTZ
238
+ )
239
+ LANGUAGE sql STABLE AS $$
240
+ WITH latest AS (
241
+ SELECT DISTINCT ON (package)
242
+ package,
243
+ change_name,
244
+ deployed_at
245
+ FROM pgpm_migrate.changes
246
+ WHERE p_package IS NULL OR package = p_package
247
+ ORDER BY package, deployed_at DESC
248
+ )
249
+ SELECT
250
+ c.package,
251
+ COUNT(*)::INTEGER AS total_deployed,
252
+ l.change_name AS last_change,
253
+ l.deployed_at AS last_deployed
254
+ FROM pgpm_migrate.changes c
255
+ JOIN latest l ON l.package = c.package
256
+ WHERE p_package IS NULL OR c.package = p_package
257
+ GROUP BY c.package, l.change_name, l.deployed_at;
258
+ $$;
@@ -0,0 +1,37 @@
1
+ -- Create schema
2
+ CREATE SCHEMA pgpm_migrate;
3
+
4
+ -- 1. Packages (minimal - just name and timestamp)
5
+ CREATE TABLE pgpm_migrate.packages (
6
+ package TEXT PRIMARY KEY,
7
+ created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
8
+ );
9
+
10
+ -- 2. Deployed changes (what's currently deployed)
11
+ CREATE TABLE pgpm_migrate.changes (
12
+ change_id TEXT PRIMARY KEY,
13
+ change_name TEXT NOT NULL,
14
+ package TEXT NOT NULL REFERENCES pgpm_migrate.packages(package),
15
+ script_hash TEXT NOT NULL,
16
+ deployed_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
17
+ UNIQUE(package, change_name),
18
+ UNIQUE(package, script_hash)
19
+ );
20
+
21
+ -- 3. Dependencies (what depends on what)
22
+ CREATE TABLE pgpm_migrate.dependencies (
23
+ change_id TEXT NOT NULL REFERENCES pgpm_migrate.changes(change_id) ON DELETE CASCADE,
24
+ requires TEXT NOT NULL,
25
+ PRIMARY KEY (change_id, requires)
26
+ );
27
+
28
+ -- 4. Event log (minimal history for rollback)
29
+ CREATE TABLE pgpm_migrate.events (
30
+ event_id SERIAL PRIMARY KEY,
31
+ event_type TEXT NOT NULL CHECK (event_type IN ('deploy', 'revert', 'verify')),
32
+ change_name TEXT NOT NULL,
33
+ package TEXT NOT NULL,
34
+ occurred_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
35
+ error_message TEXT,
36
+ error_code TEXT
37
+ );
@@ -0,0 +1,67 @@
1
+ export interface MigrateChange {
2
+ name: string;
3
+ dependencies: string[];
4
+ timestamp?: string;
5
+ planner?: string;
6
+ email?: string;
7
+ comment?: string;
8
+ }
9
+ export interface MigratePlanFile {
10
+ package: string;
11
+ uri?: string;
12
+ changes: MigrateChange[];
13
+ }
14
+ export interface DeployOptions {
15
+ modulePath: string;
16
+ /**
17
+ * Target change name or tag (e.g., "changeName" or "@tagName").
18
+ * Note: Project name is already resolved upstream by LaunchQLPackage.
19
+ */
20
+ toChange?: string;
21
+ useTransaction?: boolean;
22
+ debug?: boolean;
23
+ logOnly?: boolean;
24
+ /**
25
+ * When true, construct dependency resolution purely from plan files
26
+ * rather than scanning deploy/*.sql files.
27
+ */
28
+ usePlan?: boolean;
29
+ }
30
+ export interface RevertOptions {
31
+ modulePath: string;
32
+ /**
33
+ * Target change name or tag (e.g., "changeName" or "@tagName").
34
+ * Note: Project name is already resolved upstream by LaunchQLPackage.
35
+ */
36
+ toChange?: string;
37
+ useTransaction?: boolean;
38
+ debug?: boolean;
39
+ }
40
+ export interface VerifyOptions {
41
+ modulePath: string;
42
+ /**
43
+ * Target change name or tag (e.g., "changeName" or "@tagName").
44
+ * Note: Project name is already resolved upstream by LaunchQLPackage.
45
+ */
46
+ toChange?: string;
47
+ }
48
+ export interface DeployResult {
49
+ deployed: string[];
50
+ skipped: string[];
51
+ failed?: string;
52
+ }
53
+ export interface RevertResult {
54
+ reverted: string[];
55
+ skipped: string[];
56
+ failed?: string;
57
+ }
58
+ export interface VerifyResult {
59
+ verified: string[];
60
+ failed: string[];
61
+ }
62
+ export interface StatusResult {
63
+ package: string;
64
+ totalDeployed: number;
65
+ lastChange: string;
66
+ lastDeployed: Date;
67
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ import { PgConfig } from 'pg-env';
2
+ export interface EventLogEntry {
3
+ eventType: 'deploy' | 'revert' | 'verify';
4
+ changeName: string;
5
+ package: string;
6
+ errorMessage?: string;
7
+ errorCode?: string;
8
+ }
9
+ export declare class EventLogger {
10
+ private pool;
11
+ constructor(config: PgConfig);
12
+ logEvent(entry: EventLogEntry): Promise<void>;
13
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventLogger = void 0;
4
+ const logger_1 = require("@pgpmjs/logger");
5
+ const pg_cache_1 = require("pg-cache");
6
+ const log = new logger_1.Logger('migrate:event-logger');
7
+ class EventLogger {
8
+ pool;
9
+ constructor(config) {
10
+ this.pool = (0, pg_cache_1.getPgPool)(config);
11
+ }
12
+ async logEvent(entry) {
13
+ try {
14
+ await this.pool.query(`
15
+ INSERT INTO pgpm_migrate.events
16
+ (event_type, change_name, package, error_message, error_code)
17
+ VALUES ($1::TEXT, $2::TEXT, $3::TEXT, $4::TEXT, $5::TEXT)
18
+ `, [
19
+ entry.eventType,
20
+ entry.changeName,
21
+ entry.package,
22
+ entry.errorMessage || null,
23
+ entry.errorCode || null
24
+ ]);
25
+ log.debug(`Logged ${entry.eventType} event for ${entry.package}:${entry.changeName}`);
26
+ }
27
+ catch (error) {
28
+ log.error(`Failed to log event: ${error.message}`);
29
+ }
30
+ }
31
+ }
32
+ exports.EventLogger = EventLogger;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Generate SHA256 hash of a file's contents
3
+ */
4
+ export declare function hashFile(filePath: string): Promise<string>;
5
+ /**
6
+ * Generate SHA256 hash of a string
7
+ */
8
+ export declare function hashString(content: string): string;
9
+ /**
10
+ * Generate SHA256 hash of a SQL file's parsed and cleaned AST
11
+ */
12
+ export declare function hashSqlFile(filePath: string): Promise<string>;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hashFile = hashFile;
4
+ exports.hashString = hashString;
5
+ exports.hashSqlFile = hashSqlFile;
6
+ const crypto_1 = require("crypto");
7
+ const promises_1 = require("fs/promises");
8
+ const pgsql_parser_1 = require("pgsql-parser");
9
+ const package_1 = require("../../packaging/package");
10
+ /**
11
+ * Generate SHA256 hash of a file's contents
12
+ */
13
+ async function hashFile(filePath) {
14
+ const content = await (0, promises_1.readFile)(filePath, 'utf-8');
15
+ return (0, crypto_1.createHash)('sha256').update(content).digest('hex');
16
+ }
17
+ /**
18
+ * Generate SHA256 hash of a string
19
+ */
20
+ function hashString(content) {
21
+ return (0, crypto_1.createHash)('sha256').update(content).digest('hex');
22
+ }
23
+ /**
24
+ * Generate SHA256 hash of a SQL file's parsed and cleaned AST
25
+ */
26
+ async function hashSqlFile(filePath) {
27
+ const content = await (0, promises_1.readFile)(filePath, 'utf-8');
28
+ const parsed = await (0, pgsql_parser_1.parse)(content);
29
+ const cleaned = (0, package_1.cleanTree)(parsed);
30
+ const astString = JSON.stringify(cleaned);
31
+ return hashString(astString);
32
+ }
@@ -0,0 +1,27 @@
1
+ import { Pool, PoolClient } from 'pg';
2
+ export interface TransactionOptions {
3
+ useTransaction: boolean;
4
+ }
5
+ export interface TransactionContext {
6
+ client: PoolClient | Pool;
7
+ isTransaction: boolean;
8
+ queryHistory: QueryHistoryEntry[];
9
+ addQuery: (query: string, params?: any[], startTime?: number) => void;
10
+ }
11
+ export interface QueryHistoryEntry {
12
+ query: string;
13
+ params?: any[];
14
+ timestamp: number;
15
+ duration?: number;
16
+ error?: any;
17
+ }
18
+ /**
19
+ * Execute a function within a transaction context
20
+ * If useTransaction is true, wraps the execution in a transaction
21
+ * If false, uses the pool directly without transaction
22
+ */
23
+ export declare function withTransaction<T>(pool: Pool, options: TransactionOptions, fn: (context: TransactionContext) => Promise<T>): Promise<T>;
24
+ /**
25
+ * Helper to execute a query within a transaction context with enhanced logging
26
+ */
27
+ export declare function executeQuery(context: TransactionContext, query: string, params?: any[]): Promise<any>;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withTransaction = withTransaction;
4
+ exports.executeQuery = executeQuery;
5
+ const logger_1 = require("@pgpmjs/logger");
6
+ const log = new logger_1.Logger('migrate:transaction');
7
+ /**
8
+ * Execute a function within a transaction context
9
+ * If useTransaction is true, wraps the execution in a transaction
10
+ * If false, uses the pool directly without transaction
11
+ */
12
+ async function withTransaction(pool, options, fn) {
13
+ const queryHistory = [];
14
+ const addQuery = (query, params, startTime) => {
15
+ queryHistory.push({
16
+ query,
17
+ params,
18
+ timestamp: Date.now(),
19
+ duration: startTime ? Date.now() - startTime : undefined
20
+ });
21
+ };
22
+ if (!options.useTransaction) {
23
+ // No transaction - use pool directly
24
+ log.debug('Executing without transaction');
25
+ return fn({ client: pool, isTransaction: false, queryHistory, addQuery });
26
+ }
27
+ // Use transaction
28
+ const client = await pool.connect();
29
+ const transactionStartTime = Date.now();
30
+ log.debug('Starting transaction');
31
+ try {
32
+ const beginTime = Date.now();
33
+ await client.query('BEGIN');
34
+ addQuery('BEGIN', [], beginTime);
35
+ const result = await fn({ client, isTransaction: true, queryHistory, addQuery });
36
+ const commitTime = Date.now();
37
+ await client.query('COMMIT');
38
+ addQuery('COMMIT', [], commitTime);
39
+ const transactionDuration = Date.now() - transactionStartTime;
40
+ log.debug(`Transaction committed successfully in ${transactionDuration}ms`);
41
+ return result;
42
+ }
43
+ catch (error) {
44
+ const rollbackTime = Date.now();
45
+ try {
46
+ await client.query('ROLLBACK');
47
+ addQuery('ROLLBACK', [], rollbackTime);
48
+ }
49
+ catch (rollbackError) {
50
+ log.error('Failed to rollback transaction:', rollbackError);
51
+ }
52
+ const transactionDuration = Date.now() - transactionStartTime;
53
+ // Enhanced error logging with context
54
+ const errorLines = [];
55
+ errorLines.push(`Transaction rolled back due to error after ${transactionDuration}ms:`);
56
+ errorLines.push(`Error Code: ${error.code || 'N/A'}`);
57
+ errorLines.push(`Error Message: ${error.message || 'N/A'}`);
58
+ // Log query history for debugging
59
+ if (queryHistory.length > 0) {
60
+ errorLines.push('Query history for this transaction:');
61
+ queryHistory.forEach((entry, index) => {
62
+ const duration = entry.duration ? ` (${entry.duration}ms)` : '';
63
+ const params = entry.params && entry.params.length > 0
64
+ ? ` with params: ${JSON.stringify(entry.params.slice(0, 2))}${entry.params.length > 2 ? '...' : ''}`
65
+ : '';
66
+ errorLines.push(` ${index + 1}. ${entry.query.split('\n')[0].trim()}${params}${duration}`);
67
+ });
68
+ }
69
+ // For transaction aborted errors, provide additional context
70
+ if (error.code === '25P02') {
71
+ errorLines.push('🔍 Debug Info: Transaction was aborted due to a previous error.');
72
+ errorLines.push(' This usually means a previous command in the transaction failed.');
73
+ errorLines.push(' Check the query history above to identify the failing command.');
74
+ }
75
+ // Log the consolidated error message
76
+ log.error(errorLines.join('\n'));
77
+ throw error;
78
+ }
79
+ finally {
80
+ client.release();
81
+ }
82
+ }
83
+ /**
84
+ * Helper to execute a query within a transaction context with enhanced logging
85
+ */
86
+ async function executeQuery(context, query, params) {
87
+ const startTime = Date.now();
88
+ try {
89
+ const result = await context.client.query(query, params);
90
+ const duration = Date.now() - startTime;
91
+ // Add to query history
92
+ context.addQuery(query, params, startTime);
93
+ // Log slow queries for debugging
94
+ if (duration > 1000) {
95
+ log.warn(`Slow query detected (${duration}ms): ${query.split('\n')[0].trim()}`);
96
+ }
97
+ return result;
98
+ }
99
+ catch (error) {
100
+ const duration = Date.now() - startTime;
101
+ // Add failed query to history
102
+ context.addQuery(query, params, startTime);
103
+ // Enhanced error logging
104
+ const errorLines = [];
105
+ errorLines.push(`Query failed after ${duration}ms:`);
106
+ errorLines.push(` Query: ${query.split('\n')[0].trim()}`);
107
+ if (params && params.length > 0) {
108
+ errorLines.push(` Params: ${JSON.stringify(params.slice(0, 3))}${params.length > 3 ? '...' : ''}`);
109
+ }
110
+ errorLines.push(` Error Code: ${error.code || 'N/A'}`);
111
+ errorLines.push(` Error Message: ${error.message || 'N/A'}`);
112
+ // Provide debugging hints for common errors
113
+ if (error.code === '42P01') {
114
+ errorLines.push('💡 Hint: Relation (table/view) does not exist. Check if migrations are applied in correct order.');
115
+ }
116
+ else if (error.code === '42883') {
117
+ errorLines.push('💡 Hint: Function does not exist. Check if required extensions or functions are installed.');
118
+ }
119
+ else if (error.code === '23505') {
120
+ errorLines.push('💡 Hint: Unique constraint violation. Check for duplicate data.');
121
+ }
122
+ else if (error.code === '23503') {
123
+ errorLines.push('💡 Hint: Foreign key constraint violation. Check referential integrity.');
124
+ }
125
+ // Log the consolidated error message
126
+ log.error(errorLines.join('\n'));
127
+ throw error;
128
+ }
129
+ }
@@ -0,0 +1,31 @@
1
+ import { Module } from '../files';
2
+ export type ModuleMap = Record<string, Module>;
3
+ /**
4
+ * Get the latest change from the pgpm.plan file for a specific module.
5
+ */
6
+ export declare const latestChange: (sqlmodule: string, modules: ModuleMap, basePath: string) => string;
7
+ /**
8
+ * Get the latest change and version for a specific module.
9
+ */
10
+ export declare const latestChangeAndVersion: (sqlmodule: string, modules: ModuleMap, basePath: string) => {
11
+ change: string;
12
+ version: string;
13
+ };
14
+ /**
15
+ * Get extensions and modules required by a specific module.
16
+ */
17
+ export declare const getExtensionsAndModules: (sqlmodule: string, modules: ModuleMap) => {
18
+ native: string[];
19
+ sqitch: string[];
20
+ };
21
+ /**
22
+ * Get extensions and modules with their latest changes and versions.
23
+ */
24
+ export declare const getExtensionsAndModulesChanges: (sqlmodule: string, modules: ModuleMap, basePath: string) => {
25
+ native: string[];
26
+ sqitch: {
27
+ name: string;
28
+ latest: string;
29
+ version: string;
30
+ }[];
31
+ };