@pgpmjs/export 0.1.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.
@@ -0,0 +1,171 @@
1
+ import { getPgPool } from 'pg-cache';
2
+ import { writePgpmFiles, writePgpmPlan } from '@pgpmjs/core';
3
+ import { exportMeta } from './export-meta';
4
+ import { DB_REQUIRED_EXTENSIONS, SERVICE_REQUIRED_EXTENSIONS, META_COMMON_HEADER, META_COMMON_FOOTER, META_TABLE_ORDER, detectMissingModules, installMissingModules, makeReplacer, preparePackage, normalizeOutdir } from './export-utils';
5
+ const exportMigrationsToDisk = async ({ project, options, database, databaseId, databaseName, author, outdir, schema_names, extensionName, extensionDesc, metaExtensionName, metaExtensionDesc, prompter, argv, repoName, username, serviceOutdir, skipSchemaRenaming = false }) => {
6
+ const normalizedOutdir = normalizeOutdir(outdir);
7
+ // Use serviceOutdir for service module, defaulting to outdir if not provided
8
+ const svcOutdir = normalizeOutdir(serviceOutdir || outdir);
9
+ const pgPool = getPgPool({
10
+ ...options.pg,
11
+ database
12
+ });
13
+ const db = await pgPool.query(`select * from metaschema_public.database where id=$1`, [databaseId]);
14
+ const schemas = await pgPool.query(`select * from metaschema_public.schema where database_id=$1`, [databaseId]);
15
+ if (!db?.rows?.length) {
16
+ console.log('NO DATABASES.');
17
+ return;
18
+ }
19
+ if (!schemas?.rows?.length) {
20
+ console.log('NO SCHEMAS.');
21
+ return;
22
+ }
23
+ const name = extensionName || db.rows[0].name;
24
+ // When skipSchemaRenaming is true, pass empty schemas array to avoid renaming
25
+ // This is useful for self-referential introspection where you want to apply
26
+ // policies to real infrastructure schemas (metaschema_public, services_public, etc.)
27
+ const schemasForReplacement = skipSchemaRenaming
28
+ ? []
29
+ : schemas.rows.filter((schema) => schema_names.includes(schema.schema_name));
30
+ const { replace, replacer } = makeReplacer({
31
+ schemas: schemasForReplacement,
32
+ name
33
+ });
34
+ // Filter sql_actions by database_id to avoid cross-database pollution
35
+ // Previously this query had no WHERE clause, which could export actions
36
+ // from unrelated databases in a persistent database environment
37
+ const results = await pgPool.query(`select * from db_migrate.sql_actions where database_id = $1 order by id`, [databaseId]);
38
+ const opts = {
39
+ name,
40
+ replacer,
41
+ outdir: normalizedOutdir,
42
+ author
43
+ };
44
+ // Build description for the database extension package
45
+ const dbExtensionDesc = extensionDesc || `${name} database schema for ${databaseName}`;
46
+ if (results?.rows?.length > 0) {
47
+ // Detect missing modules at workspace level and prompt user
48
+ const dbMissingResult = await detectMissingModules(project, [...DB_REQUIRED_EXTENSIONS], prompter, argv);
49
+ // Create/prepare the module directory
50
+ const dbModuleDir = await preparePackage({
51
+ project,
52
+ author,
53
+ outdir: normalizedOutdir,
54
+ name,
55
+ description: dbExtensionDesc,
56
+ extensions: [...DB_REQUIRED_EXTENSIONS],
57
+ prompter,
58
+ repoName,
59
+ username
60
+ });
61
+ // Install missing modules if user confirmed (now that module exists)
62
+ if (dbMissingResult.shouldInstall) {
63
+ await installMissingModules(dbModuleDir, dbMissingResult.missingModules);
64
+ }
65
+ writePgpmPlan(results.rows, opts);
66
+ writePgpmFiles(results.rows, opts);
67
+ }
68
+ else {
69
+ console.log('No sql_actions found — skipping database module. Meta/service module will still be exported.');
70
+ }
71
+ // =========================================================================
72
+ // Meta/service module export — runs independently of sql_actions
73
+ // =========================================================================
74
+ const metaResult = await exportMeta({
75
+ opts: options,
76
+ dbname: database,
77
+ database_id: databaseId
78
+ });
79
+ // Build description for the meta/service extension package
80
+ const metaDesc = metaExtensionDesc || `${metaExtensionName} service utilities for managing domains, APIs, and services`;
81
+ // Detect missing modules at workspace level and prompt user
82
+ const svcMissingResult = await detectMissingModules(project, [...SERVICE_REQUIRED_EXTENSIONS], prompter, argv);
83
+ // Create/prepare the module directory (use serviceOutdir if provided)
84
+ const svcModuleDir = await preparePackage({
85
+ project,
86
+ author,
87
+ outdir: svcOutdir,
88
+ name: metaExtensionName,
89
+ description: metaDesc,
90
+ extensions: [...SERVICE_REQUIRED_EXTENSIONS],
91
+ prompter,
92
+ repoName,
93
+ username
94
+ });
95
+ // Install missing modules if user confirmed (now that module exists)
96
+ if (svcMissingResult.shouldInstall) {
97
+ await installMissingModules(svcModuleDir, svcMissingResult.missingModules);
98
+ }
99
+ // Use same skipSchemaRenaming logic for meta replacer
100
+ const metaSchemasForReplacement = skipSchemaRenaming
101
+ ? []
102
+ : schemas.rows.filter((schema) => schema_names.includes(schema.schema_name));
103
+ const metaReplacer = makeReplacer({
104
+ schemas: metaSchemasForReplacement,
105
+ name: metaExtensionName,
106
+ // Use extensionName for schema prefix — the services metadata references
107
+ // schemas owned by the application package (e.g. agent_db_auth_public),
108
+ // not the services package (agent_db_services_auth_public)
109
+ schemaPrefix: name
110
+ });
111
+ // Create separate files for each table type
112
+ const metaPackage = [];
113
+ // Track which tables have content for dependency resolution
114
+ const tablesWithContent = [];
115
+ // Create a file for each table type that has content
116
+ for (const tableName of META_TABLE_ORDER) {
117
+ const tableSql = metaResult[tableName];
118
+ if (tableSql) {
119
+ const replacedSql = metaReplacer.replacer(tableSql);
120
+ // Determine dependencies - each table depends on the previous tables that have content
121
+ // This ensures proper ordering during deployment
122
+ const deps = tableName === 'database'
123
+ ? []
124
+ : tablesWithContent.length > 0
125
+ ? [`migrate/${tablesWithContent[tablesWithContent.length - 1]}`]
126
+ : [];
127
+ metaPackage.push({
128
+ deps,
129
+ deploy: `migrate/${tableName}`,
130
+ content: `${META_COMMON_HEADER}
131
+
132
+ ${replacedSql}
133
+
134
+ ${META_COMMON_FOOTER}
135
+ `
136
+ });
137
+ tablesWithContent.push(tableName);
138
+ }
139
+ }
140
+ opts.replacer = metaReplacer.replacer;
141
+ opts.name = metaExtensionName;
142
+ opts.outdir = svcOutdir;
143
+ writePgpmPlan(metaPackage, opts);
144
+ writePgpmFiles(metaPackage, opts);
145
+ pgPool.end();
146
+ };
147
+ export const exportMigrations = async ({ project, options, dbInfo, author, outdir, schema_names, extensionName, extensionDesc, metaExtensionName, metaExtensionDesc, prompter, argv, repoName, username, serviceOutdir, skipSchemaRenaming }) => {
148
+ for (let v = 0; v < dbInfo.database_ids.length; v++) {
149
+ const databaseId = dbInfo.database_ids[v];
150
+ await exportMigrationsToDisk({
151
+ project,
152
+ options,
153
+ extensionName,
154
+ extensionDesc,
155
+ metaExtensionName,
156
+ metaExtensionDesc,
157
+ database: dbInfo.dbname,
158
+ databaseName: dbInfo.databaseName,
159
+ databaseId,
160
+ schema_names,
161
+ author,
162
+ outdir,
163
+ prompter,
164
+ argv,
165
+ repoName,
166
+ username,
167
+ serviceOutdir,
168
+ skipSchemaRenaming
169
+ });
170
+ }
171
+ };