supatool 0.6.2 → 0.6.4
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.
- package/dist/sync/definitionExtractor.js +35 -20
- package/package.json +1 -1
|
@@ -927,23 +927,30 @@ async function generateCreateTableDDL(client, tableName, schemaName = 'public')
|
|
|
927
927
|
ORDER BY tc.constraint_name
|
|
928
928
|
`, [schemaName, tableName]),
|
|
929
929
|
// Get FOREIGN KEY constraints
|
|
930
|
+
// Use pg_constraint directly to avoid the N² row explosion that occurs when
|
|
931
|
+
// joining kcu × ccu on constraint_name alone for composite FKs.
|
|
930
932
|
client.query(`
|
|
931
|
-
SELECT
|
|
932
|
-
|
|
933
|
-
string_agg(
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
933
|
+
SELECT
|
|
934
|
+
c.conname AS constraint_name,
|
|
935
|
+
(SELECT string_agg(a.attname, ', ' ORDER BY u.ord)
|
|
936
|
+
FROM unnest(c.conkey) WITH ORDINALITY AS u(attnum, ord)
|
|
937
|
+
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = u.attnum
|
|
938
|
+
) AS columns,
|
|
939
|
+
fn.nspname AS foreign_table_schema,
|
|
940
|
+
fc.relname AS foreign_table_name,
|
|
941
|
+
(SELECT string_agg(a.attname, ', ' ORDER BY u.ord)
|
|
942
|
+
FROM unnest(c.confkey) WITH ORDINALITY AS u(attnum, ord)
|
|
943
|
+
JOIN pg_attribute a ON a.attrelid = c.confrelid AND a.attnum = u.attnum
|
|
944
|
+
) AS foreign_columns
|
|
945
|
+
FROM pg_constraint c
|
|
946
|
+
JOIN pg_class rel ON rel.oid = c.conrelid
|
|
947
|
+
JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace
|
|
948
|
+
JOIN pg_class fc ON fc.oid = c.confrelid
|
|
949
|
+
JOIN pg_namespace fn ON fn.oid = fc.relnamespace
|
|
950
|
+
WHERE c.contype = 'f'
|
|
951
|
+
AND nsp.nspname = $1
|
|
952
|
+
AND rel.relname = $2
|
|
953
|
+
ORDER BY c.conname
|
|
947
954
|
`, [schemaName, tableName])
|
|
948
955
|
]);
|
|
949
956
|
const columnComments = new Map();
|
|
@@ -1353,7 +1360,7 @@ async function generateIndexFile(definitions, outputDir, separateDirectories = t
|
|
|
1353
1360
|
}
|
|
1354
1361
|
: undefined
|
|
1355
1362
|
};
|
|
1356
|
-
writeFileIfChanged(path.join(outputDir, 'schema_index.json'), JSON.stringify(schemaIndex, null, 2)
|
|
1363
|
+
writeFileIfChanged(path.join(outputDir, 'schema_index.json'), JSON.stringify(schemaIndex, null, 2));
|
|
1357
1364
|
// schema_summary.md (one-file overview for AI) — include RLS status per table
|
|
1358
1365
|
let summaryMd = '# Schema summary\n\n';
|
|
1359
1366
|
const tableDefs = definitions.filter(d => d.type === 'table' || d.type === 'view');
|
|
@@ -1389,6 +1396,7 @@ async function generateIndexFile(definitions, outputDir, separateDirectories = t
|
|
|
1389
1396
|
writeFileIfChanged(path.join(outputDir, 'schema_summary.md'), summaryMd);
|
|
1390
1397
|
// RLS disabled tables warning doc (tables only; RLS enabled with 0 policies is not warned)
|
|
1391
1398
|
const rlsNotEnabled = tableRlsStatus.filter(s => !s.rlsEnabled);
|
|
1399
|
+
const rlsWarningsPath = path.join(outputDir, 'rls_warnings.md');
|
|
1392
1400
|
if (rlsNotEnabled.length > 0) {
|
|
1393
1401
|
let warnMd = '# Tables with RLS disabled (warning)\n\n';
|
|
1394
1402
|
warnMd += 'The following tables do not have Row Level Security enabled.\n';
|
|
@@ -1397,7 +1405,11 @@ async function generateIndexFile(definitions, outputDir, separateDirectories = t
|
|
|
1397
1405
|
rlsNotEnabled.forEach(s => {
|
|
1398
1406
|
warnMd += `| ${s.schema} | ${s.table} |\n`;
|
|
1399
1407
|
});
|
|
1400
|
-
writeFileIfChanged(
|
|
1408
|
+
writeFileIfChanged(rlsWarningsPath, warnMd);
|
|
1409
|
+
}
|
|
1410
|
+
else if (fs.existsSync(rlsWarningsPath)) {
|
|
1411
|
+
// All RLS issues resolved — remove stale warning file
|
|
1412
|
+
fs.unlinkSync(rlsWarningsPath);
|
|
1401
1413
|
}
|
|
1402
1414
|
}
|
|
1403
1415
|
/**
|
|
@@ -1529,7 +1541,10 @@ async function extractDefinitions(options) {
|
|
|
1529
1541
|
cronJobs: { current: 0, total: 0 },
|
|
1530
1542
|
customTypes: { current: 0, total: 0 }
|
|
1531
1543
|
};
|
|
1532
|
-
|
|
1544
|
+
// --schema-only implies full extraction of all object types for the target schema,
|
|
1545
|
+
// so that --force does not incorrectly delete rpc/cron/types files.
|
|
1546
|
+
const effectiveAll = all || schemaOnly;
|
|
1547
|
+
if (effectiveAll) {
|
|
1533
1548
|
// Get total count for each object type first
|
|
1534
1549
|
spinner.text = 'Counting database objects...';
|
|
1535
1550
|
// Get total tables/views count
|
|
@@ -1591,7 +1606,7 @@ async function extractDefinitions(options) {
|
|
|
1591
1606
|
AND t.typname NOT LIKE '_%'
|
|
1592
1607
|
`);
|
|
1593
1608
|
progress.customTypes.total = parseInt(typesCountResult.rows[0].count);
|
|
1594
|
-
// When --all: fetch all objects (sequential)
|
|
1609
|
+
// When --all / --schema-only: fetch all objects (sequential)
|
|
1595
1610
|
const tables = await fetchTableDefinitions(client, spinner, progress, schemas);
|
|
1596
1611
|
const rlsPolicies = await fetchRlsPolicies(client, spinner, progress, schemas);
|
|
1597
1612
|
const functions = await fetchFunctions(client, spinner, progress, schemas);
|
package/package.json
CHANGED