@pgpmjs/core 4.13.1 → 4.13.3

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.
@@ -1,5 +1,82 @@
1
1
  import { Parser } from 'csv-to-pg';
2
2
  import { getPgPool } from 'pg-cache';
3
+ /**
4
+ * Map PostgreSQL data types to FieldType values.
5
+ * Uses udt_name from information_schema which gives the base type name.
6
+ */
7
+ const mapPgTypeToFieldType = (udtName) => {
8
+ switch (udtName) {
9
+ case 'uuid':
10
+ return 'uuid';
11
+ case '_uuid':
12
+ return 'uuid[]';
13
+ case 'text':
14
+ case 'varchar':
15
+ case 'bpchar':
16
+ case 'name':
17
+ return 'text';
18
+ case '_text':
19
+ case '_varchar':
20
+ return 'text[]';
21
+ case 'bool':
22
+ return 'boolean';
23
+ case 'jsonb':
24
+ case 'json':
25
+ return 'jsonb';
26
+ case 'int4':
27
+ case 'int8':
28
+ case 'int2':
29
+ case 'numeric':
30
+ return 'int';
31
+ case 'interval':
32
+ return 'interval';
33
+ case 'timestamptz':
34
+ case 'timestamp':
35
+ return 'timestamptz';
36
+ default:
37
+ return 'text';
38
+ }
39
+ };
40
+ /**
41
+ * Query actual columns from information_schema for a given table.
42
+ * Returns a map of column_name -> udt_name (PostgreSQL type).
43
+ */
44
+ const getTableColumns = async (pool, schemaName, tableName) => {
45
+ const result = await pool.query(`
46
+ SELECT column_name, udt_name
47
+ FROM information_schema.columns
48
+ WHERE table_schema = $1 AND table_name = $2
49
+ ORDER BY ordinal_position
50
+ `, [schemaName, tableName]);
51
+ const columns = new Map();
52
+ for (const row of result.rows) {
53
+ columns.set(row.column_name, row.udt_name);
54
+ }
55
+ return columns;
56
+ };
57
+ /**
58
+ * Build dynamic fields config by intersecting the hardcoded config with actual database columns.
59
+ * - Only includes columns that exist in the database
60
+ * - Preserves special type hints from config (image, upload, url) for columns that exist
61
+ * - Infers types from PostgreSQL for columns not in config
62
+ */
63
+ const buildDynamicFields = async (pool, tableConfig) => {
64
+ const actualColumns = await getTableColumns(pool, tableConfig.schema, tableConfig.table);
65
+ if (actualColumns.size === 0) {
66
+ // Table doesn't exist, return empty fields
67
+ return {};
68
+ }
69
+ const dynamicFields = {};
70
+ // For each column in the hardcoded config, check if it exists in the database
71
+ for (const [fieldName, fieldType] of Object.entries(tableConfig.fields)) {
72
+ if (actualColumns.has(fieldName)) {
73
+ // Column exists - use the config's type hint (preserves special types like 'image', 'upload', 'url')
74
+ dynamicFields[fieldName] = fieldType;
75
+ }
76
+ // If column doesn't exist in database, skip it (this fixes the bug)
77
+ }
78
+ return dynamicFields;
79
+ };
3
80
  const config = {
4
81
  // =============================================================================
5
82
  // metaschema_public tables
@@ -110,8 +187,8 @@ const config = {
110
187
  fields: {
111
188
  id: 'uuid',
112
189
  database_id: 'uuid',
113
- schema_id: 'uuid',
114
- name: 'text'
190
+ name: 'text',
191
+ code: 'text'
115
192
  }
116
193
  },
117
194
  rls_function: {
@@ -120,8 +197,13 @@ const config = {
120
197
  fields: {
121
198
  id: 'uuid',
122
199
  database_id: 'uuid',
123
- schema_id: 'uuid',
124
- name: 'text'
200
+ table_id: 'uuid',
201
+ name: 'text',
202
+ label: 'text',
203
+ description: 'text',
204
+ data: 'jsonb',
205
+ inline: 'boolean',
206
+ security: 'int'
125
207
  }
126
208
  },
127
209
  limit_function: {
@@ -130,8 +212,12 @@ const config = {
130
212
  fields: {
131
213
  id: 'uuid',
132
214
  database_id: 'uuid',
133
- schema_id: 'uuid',
134
- name: 'text'
215
+ table_id: 'uuid',
216
+ name: 'text',
217
+ label: 'text',
218
+ description: 'text',
219
+ data: 'jsonb',
220
+ security: 'int'
135
221
  }
136
222
  },
137
223
  procedure: {
@@ -140,8 +226,12 @@ const config = {
140
226
  fields: {
141
227
  id: 'uuid',
142
228
  database_id: 'uuid',
143
- schema_id: 'uuid',
144
- name: 'text'
229
+ name: 'text',
230
+ argnames: 'text[]',
231
+ argtypes: 'text[]',
232
+ argdefaults: 'text[]',
233
+ lang_name: 'text',
234
+ definition: 'text'
145
235
  }
146
236
  },
147
237
  foreign_key_constraint: {
@@ -152,11 +242,14 @@ const config = {
152
242
  database_id: 'uuid',
153
243
  table_id: 'uuid',
154
244
  name: 'text',
245
+ description: 'text',
246
+ smart_tags: 'jsonb',
247
+ type: 'text',
155
248
  field_ids: 'uuid[]',
156
249
  ref_table_id: 'uuid',
157
250
  ref_field_ids: 'uuid[]',
158
- on_delete: 'text',
159
- on_update: 'text'
251
+ delete_action: 'text',
252
+ update_action: 'text'
160
253
  }
161
254
  },
162
255
  primary_key_constraint: {
@@ -167,6 +260,7 @@ const config = {
167
260
  database_id: 'uuid',
168
261
  table_id: 'uuid',
169
262
  name: 'text',
263
+ type: 'text',
170
264
  field_ids: 'uuid[]'
171
265
  }
172
266
  },
@@ -178,6 +272,9 @@ const config = {
178
272
  database_id: 'uuid',
179
273
  table_id: 'uuid',
180
274
  name: 'text',
275
+ description: 'text',
276
+ smart_tags: 'jsonb',
277
+ type: 'text',
181
278
  field_ids: 'uuid[]'
182
279
  }
183
280
  },
@@ -189,7 +286,9 @@ const config = {
189
286
  database_id: 'uuid',
190
287
  table_id: 'uuid',
191
288
  name: 'text',
192
- expression: 'text'
289
+ type: 'text',
290
+ field_ids: 'uuid[]',
291
+ expr: 'jsonb'
193
292
  }
194
293
  },
195
294
  full_text_search: {
@@ -199,9 +298,10 @@ const config = {
199
298
  id: 'uuid',
200
299
  database_id: 'uuid',
201
300
  table_id: 'uuid',
202
- name: 'text',
301
+ field_id: 'uuid',
203
302
  field_ids: 'uuid[]',
204
- weights: 'text[]'
303
+ weights: 'text[]',
304
+ langs: 'text[]'
205
305
  }
206
306
  },
207
307
  schema_grant: {
@@ -211,8 +311,7 @@ const config = {
211
311
  id: 'uuid',
212
312
  database_id: 'uuid',
213
313
  schema_id: 'uuid',
214
- role_name: 'text',
215
- privilege: 'text'
314
+ grantee_name: 'text'
216
315
  }
217
316
  },
218
317
  table_grant: {
@@ -222,8 +321,9 @@ const config = {
222
321
  id: 'uuid',
223
322
  database_id: 'uuid',
224
323
  table_id: 'uuid',
324
+ privilege: 'text',
225
325
  role_name: 'text',
226
- privilege: 'text'
326
+ field_ids: 'uuid[]'
227
327
  }
228
328
  },
229
329
  // =============================================================================
@@ -771,15 +871,41 @@ export const exportMeta = async ({ opts, dbname, database_id }) => {
771
871
  database: dbname
772
872
  });
773
873
  const sql = {};
774
- const parsers = Object.entries(config).reduce((m, [name, config]) => {
775
- m[name] = new Parser(config);
776
- return m;
777
- }, {});
874
+ // Cache for dynamically built parsers
875
+ const parsers = {};
876
+ // Build parser dynamically by querying actual columns from the database
877
+ const getParser = async (key) => {
878
+ if (parsers[key]) {
879
+ return parsers[key];
880
+ }
881
+ const tableConfig = config[key];
882
+ if (!tableConfig) {
883
+ return null;
884
+ }
885
+ // Build fields dynamically based on actual database columns
886
+ const dynamicFields = await buildDynamicFields(pool, tableConfig);
887
+ if (Object.keys(dynamicFields).length === 0) {
888
+ // No columns found (table doesn't exist or no matching columns)
889
+ return null;
890
+ }
891
+ const parser = new Parser({
892
+ schema: tableConfig.schema,
893
+ table: tableConfig.table,
894
+ conflictDoNothing: tableConfig.conflictDoNothing,
895
+ fields: dynamicFields
896
+ });
897
+ parsers[key] = parser;
898
+ return parser;
899
+ };
778
900
  const queryAndParse = async (key, query) => {
779
901
  try {
902
+ const parser = await getParser(key);
903
+ if (!parser) {
904
+ return;
905
+ }
780
906
  const result = await pool.query(query, [database_id]);
781
907
  if (result.rows.length) {
782
- const parsed = await parsers[key].parse(result.rows);
908
+ const parsed = await parser.parse(result.rows);
783
909
  if (parsed) {
784
910
  sql[key] = parsed;
785
911
  }
@@ -3,6 +3,83 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.exportMeta = void 0;
4
4
  const csv_to_pg_1 = require("csv-to-pg");
5
5
  const pg_cache_1 = require("pg-cache");
6
+ /**
7
+ * Map PostgreSQL data types to FieldType values.
8
+ * Uses udt_name from information_schema which gives the base type name.
9
+ */
10
+ const mapPgTypeToFieldType = (udtName) => {
11
+ switch (udtName) {
12
+ case 'uuid':
13
+ return 'uuid';
14
+ case '_uuid':
15
+ return 'uuid[]';
16
+ case 'text':
17
+ case 'varchar':
18
+ case 'bpchar':
19
+ case 'name':
20
+ return 'text';
21
+ case '_text':
22
+ case '_varchar':
23
+ return 'text[]';
24
+ case 'bool':
25
+ return 'boolean';
26
+ case 'jsonb':
27
+ case 'json':
28
+ return 'jsonb';
29
+ case 'int4':
30
+ case 'int8':
31
+ case 'int2':
32
+ case 'numeric':
33
+ return 'int';
34
+ case 'interval':
35
+ return 'interval';
36
+ case 'timestamptz':
37
+ case 'timestamp':
38
+ return 'timestamptz';
39
+ default:
40
+ return 'text';
41
+ }
42
+ };
43
+ /**
44
+ * Query actual columns from information_schema for a given table.
45
+ * Returns a map of column_name -> udt_name (PostgreSQL type).
46
+ */
47
+ const getTableColumns = async (pool, schemaName, tableName) => {
48
+ const result = await pool.query(`
49
+ SELECT column_name, udt_name
50
+ FROM information_schema.columns
51
+ WHERE table_schema = $1 AND table_name = $2
52
+ ORDER BY ordinal_position
53
+ `, [schemaName, tableName]);
54
+ const columns = new Map();
55
+ for (const row of result.rows) {
56
+ columns.set(row.column_name, row.udt_name);
57
+ }
58
+ return columns;
59
+ };
60
+ /**
61
+ * Build dynamic fields config by intersecting the hardcoded config with actual database columns.
62
+ * - Only includes columns that exist in the database
63
+ * - Preserves special type hints from config (image, upload, url) for columns that exist
64
+ * - Infers types from PostgreSQL for columns not in config
65
+ */
66
+ const buildDynamicFields = async (pool, tableConfig) => {
67
+ const actualColumns = await getTableColumns(pool, tableConfig.schema, tableConfig.table);
68
+ if (actualColumns.size === 0) {
69
+ // Table doesn't exist, return empty fields
70
+ return {};
71
+ }
72
+ const dynamicFields = {};
73
+ // For each column in the hardcoded config, check if it exists in the database
74
+ for (const [fieldName, fieldType] of Object.entries(tableConfig.fields)) {
75
+ if (actualColumns.has(fieldName)) {
76
+ // Column exists - use the config's type hint (preserves special types like 'image', 'upload', 'url')
77
+ dynamicFields[fieldName] = fieldType;
78
+ }
79
+ // If column doesn't exist in database, skip it (this fixes the bug)
80
+ }
81
+ return dynamicFields;
82
+ };
6
83
  const config = {
7
84
  // =============================================================================
8
85
  // metaschema_public tables
@@ -113,8 +190,8 @@ const config = {
113
190
  fields: {
114
191
  id: 'uuid',
115
192
  database_id: 'uuid',
116
- schema_id: 'uuid',
117
- name: 'text'
193
+ name: 'text',
194
+ code: 'text'
118
195
  }
119
196
  },
120
197
  rls_function: {
@@ -123,8 +200,13 @@ const config = {
123
200
  fields: {
124
201
  id: 'uuid',
125
202
  database_id: 'uuid',
126
- schema_id: 'uuid',
127
- name: 'text'
203
+ table_id: 'uuid',
204
+ name: 'text',
205
+ label: 'text',
206
+ description: 'text',
207
+ data: 'jsonb',
208
+ inline: 'boolean',
209
+ security: 'int'
128
210
  }
129
211
  },
130
212
  limit_function: {
@@ -133,8 +215,12 @@ const config = {
133
215
  fields: {
134
216
  id: 'uuid',
135
217
  database_id: 'uuid',
136
- schema_id: 'uuid',
137
- name: 'text'
218
+ table_id: 'uuid',
219
+ name: 'text',
220
+ label: 'text',
221
+ description: 'text',
222
+ data: 'jsonb',
223
+ security: 'int'
138
224
  }
139
225
  },
140
226
  procedure: {
@@ -143,8 +229,12 @@ const config = {
143
229
  fields: {
144
230
  id: 'uuid',
145
231
  database_id: 'uuid',
146
- schema_id: 'uuid',
147
- name: 'text'
232
+ name: 'text',
233
+ argnames: 'text[]',
234
+ argtypes: 'text[]',
235
+ argdefaults: 'text[]',
236
+ lang_name: 'text',
237
+ definition: 'text'
148
238
  }
149
239
  },
150
240
  foreign_key_constraint: {
@@ -155,11 +245,14 @@ const config = {
155
245
  database_id: 'uuid',
156
246
  table_id: 'uuid',
157
247
  name: 'text',
248
+ description: 'text',
249
+ smart_tags: 'jsonb',
250
+ type: 'text',
158
251
  field_ids: 'uuid[]',
159
252
  ref_table_id: 'uuid',
160
253
  ref_field_ids: 'uuid[]',
161
- on_delete: 'text',
162
- on_update: 'text'
254
+ delete_action: 'text',
255
+ update_action: 'text'
163
256
  }
164
257
  },
165
258
  primary_key_constraint: {
@@ -170,6 +263,7 @@ const config = {
170
263
  database_id: 'uuid',
171
264
  table_id: 'uuid',
172
265
  name: 'text',
266
+ type: 'text',
173
267
  field_ids: 'uuid[]'
174
268
  }
175
269
  },
@@ -181,6 +275,9 @@ const config = {
181
275
  database_id: 'uuid',
182
276
  table_id: 'uuid',
183
277
  name: 'text',
278
+ description: 'text',
279
+ smart_tags: 'jsonb',
280
+ type: 'text',
184
281
  field_ids: 'uuid[]'
185
282
  }
186
283
  },
@@ -192,7 +289,9 @@ const config = {
192
289
  database_id: 'uuid',
193
290
  table_id: 'uuid',
194
291
  name: 'text',
195
- expression: 'text'
292
+ type: 'text',
293
+ field_ids: 'uuid[]',
294
+ expr: 'jsonb'
196
295
  }
197
296
  },
198
297
  full_text_search: {
@@ -202,9 +301,10 @@ const config = {
202
301
  id: 'uuid',
203
302
  database_id: 'uuid',
204
303
  table_id: 'uuid',
205
- name: 'text',
304
+ field_id: 'uuid',
206
305
  field_ids: 'uuid[]',
207
- weights: 'text[]'
306
+ weights: 'text[]',
307
+ langs: 'text[]'
208
308
  }
209
309
  },
210
310
  schema_grant: {
@@ -214,8 +314,7 @@ const config = {
214
314
  id: 'uuid',
215
315
  database_id: 'uuid',
216
316
  schema_id: 'uuid',
217
- role_name: 'text',
218
- privilege: 'text'
317
+ grantee_name: 'text'
219
318
  }
220
319
  },
221
320
  table_grant: {
@@ -225,8 +324,9 @@ const config = {
225
324
  id: 'uuid',
226
325
  database_id: 'uuid',
227
326
  table_id: 'uuid',
327
+ privilege: 'text',
228
328
  role_name: 'text',
229
- privilege: 'text'
329
+ field_ids: 'uuid[]'
230
330
  }
231
331
  },
232
332
  // =============================================================================
@@ -774,15 +874,41 @@ const exportMeta = async ({ opts, dbname, database_id }) => {
774
874
  database: dbname
775
875
  });
776
876
  const sql = {};
777
- const parsers = Object.entries(config).reduce((m, [name, config]) => {
778
- m[name] = new csv_to_pg_1.Parser(config);
779
- return m;
780
- }, {});
877
+ // Cache for dynamically built parsers
878
+ const parsers = {};
879
+ // Build parser dynamically by querying actual columns from the database
880
+ const getParser = async (key) => {
881
+ if (parsers[key]) {
882
+ return parsers[key];
883
+ }
884
+ const tableConfig = config[key];
885
+ if (!tableConfig) {
886
+ return null;
887
+ }
888
+ // Build fields dynamically based on actual database columns
889
+ const dynamicFields = await buildDynamicFields(pool, tableConfig);
890
+ if (Object.keys(dynamicFields).length === 0) {
891
+ // No columns found (table doesn't exist or no matching columns)
892
+ return null;
893
+ }
894
+ const parser = new csv_to_pg_1.Parser({
895
+ schema: tableConfig.schema,
896
+ table: tableConfig.table,
897
+ conflictDoNothing: tableConfig.conflictDoNothing,
898
+ fields: dynamicFields
899
+ });
900
+ parsers[key] = parser;
901
+ return parser;
902
+ };
781
903
  const queryAndParse = async (key, query) => {
782
904
  try {
905
+ const parser = await getParser(key);
906
+ if (!parser) {
907
+ return;
908
+ }
783
909
  const result = await pool.query(query, [database_id]);
784
910
  if (result.rows.length) {
785
- const parsed = await parsers[key].parse(result.rows);
911
+ const parsed = await parser.parse(result.rows);
786
912
  if (parsed) {
787
913
  sql[key] = parsed;
788
914
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/core",
3
- "version": "4.13.1",
3
+ "version": "4.13.3",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM Package and Migration Tools",
6
6
  "main": "index.js",
@@ -64,5 +64,5 @@
64
64
  "pgsql-parser": "^17.9.11",
65
65
  "yanse": "^0.1.11"
66
66
  },
67
- "gitHead": "cf0d7de6f89b52e1e3dcd2cda068e7be27296615"
67
+ "gitHead": "c1708066d429d86bbc1ca3aed778ff51779c8efc"
68
68
  }