fastify-auto-sqlapi 0.1.13 → 0.2.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.
- package/AGENTS_BACKEND.md +32 -5
- package/BREAKING_CHANGES.md +80 -5
- package/CHANGELOG.md +45 -0
- package/README.md +49 -7
- package/dist/bin/generate-schema.js +89 -77
- package/dist/bin/generate-tables.js +72 -62
- package/dist/bin/utils.d.ts.map +1 -1
- package/dist/bin/utils.js +47 -33
- package/dist/lib/agent/manifest.d.ts +3 -2
- package/dist/lib/agent/manifest.d.ts.map +1 -1
- package/dist/lib/agent/manifest.js +104 -80
- package/dist/lib/cli/mysql-introspect.d.ts +4 -9
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +8 -7
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +0 -1
- package/dist/lib/cli/schema-codegen.d.ts +3 -0
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +3 -0
- package/dist/lib/cli/tables-codegen.d.ts +8 -1
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
- package/dist/lib/cli/tables-codegen.js +100 -118
- package/dist/lib/condition-methods.d.ts +14 -4
- package/dist/lib/condition-methods.d.ts.map +1 -1
- package/dist/lib/condition-methods.js +18 -4
- package/dist/lib/db.d.ts +1 -1
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +16 -4
- package/dist/lib/dialect.d.ts +2 -1
- package/dist/lib/dialect.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +42 -35
- package/dist/lib/engine/rest/delete.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.d.ts.map +1 -1
- package/dist/lib/engine/rest/insert.d.ts.map +1 -1
- package/dist/lib/engine/rest/insert.js +1 -1
- package/dist/lib/engine/rest/update.d.ts.map +1 -1
- package/dist/lib/engine/rest/update.js +25 -18
- package/dist/lib/engine/search/aggregations.d.ts +33 -0
- package/dist/lib/engine/search/aggregations.d.ts.map +1 -0
- package/dist/lib/engine/search/aggregations.js +109 -0
- package/dist/lib/engine/search/conditions.d.ts +31 -0
- package/dist/lib/engine/search/conditions.d.ts.map +1 -0
- package/dist/lib/engine/search/conditions.js +131 -0
- package/dist/lib/engine/search/fields.d.ts +49 -0
- package/dist/lib/engine/search/fields.d.ts.map +1 -0
- package/dist/lib/engine/search/fields.js +142 -0
- package/dist/lib/engine/search/join-families.d.ts +35 -0
- package/dist/lib/engine/search/join-families.d.ts.map +1 -0
- package/dist/lib/engine/search/join-families.js +345 -0
- package/dist/lib/engine/search/joins.d.ts +55 -0
- package/dist/lib/engine/search/joins.d.ts.map +1 -0
- package/dist/lib/engine/search/joins.js +81 -0
- package/dist/lib/engine/search/order-by.d.ts +35 -0
- package/dist/lib/engine/search/order-by.d.ts.map +1 -0
- package/dist/lib/engine/search/order-by.js +104 -0
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +106 -749
- package/dist/lib/engine/validate.d.ts +0 -5
- package/dist/lib/engine/validate.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.d.ts +25 -17
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +86 -44
- package/dist/lib/read-access.d.ts +37 -1
- package/dist/lib/read-access.d.ts.map +1 -1
- package/dist/lib/read-access.js +64 -1
- package/dist/lib/schema/helpers.d.ts +0 -15
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +7 -4
- package/dist/lib/schema/search.d.ts.map +1 -1
- package/dist/lib/schema/search.js +120 -121
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/table-helpers.d.ts +16 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +55 -1
- package/dist/lib/tenant.d.ts +26 -0
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +46 -0
- package/dist/routes/auto/route-helpers.d.ts +0 -7
- package/dist/routes/auto/route-helpers.d.ts.map +1 -1
- package/dist/routes/auto/route-helpers.js +46 -30
- package/dist/routes/auto/search.routes.js +0 -1
- package/dist/types/join.d.ts +9 -0
- package/dist/types/join.d.ts.map +1 -1
- package/dist/types/table.d.ts +1 -1
- package/docs/GLOSSARY.md +28 -0
- package/docs/adr/0010-joins-do-not-run-route-guards.md +100 -0
- package/docs/adr/0011-join-fields-allowlist.md +95 -0
- package/docs/adr/0012-anti-duplication-tooling.md +59 -0
- package/docs/adr/README.md +5 -1
- package/package.json +29 -2
|
@@ -22,6 +22,74 @@ function printHelp() {
|
|
|
22
22
|
display('Table*.ts files are only created if they do not already exist.', CONSOLE_COLORS.magenta);
|
|
23
23
|
display('dbTables.ts is created if missing, indexing the Table*.ts files present on disk.', CONSOLE_COLORS.magenta);
|
|
24
24
|
}
|
|
25
|
+
const tableVarOf = (schema) => 'Table' + schema.schemaName.replace(/^Schema/, '');
|
|
26
|
+
/** Parse every Schema*.ts on disk — relation detection needs them all, even for a subset run. */
|
|
27
|
+
function parseAllSchemas(schemasDir, schemaFiles) {
|
|
28
|
+
const parsed = [];
|
|
29
|
+
for (const file of schemaFiles) {
|
|
30
|
+
const content = fs.readFileSync(path.join(schemasDir, file), 'utf-8');
|
|
31
|
+
const schema = parseSchemaFile(content);
|
|
32
|
+
if (schema)
|
|
33
|
+
parsed.push(schema);
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
/** The schemas to emit Table files for. Exits when a requested name has no schema. */
|
|
38
|
+
function selectTargetSchemas(allSchemas, all, requested) {
|
|
39
|
+
if (all)
|
|
40
|
+
return allSchemas;
|
|
41
|
+
const requestedSet = new Set(requested);
|
|
42
|
+
const targets = allSchemas.filter((s) => requestedSet.has(s.tableName));
|
|
43
|
+
const foundNames = new Set(targets.map((s) => s.tableName));
|
|
44
|
+
for (const name of requestedSet) {
|
|
45
|
+
if (!foundNames.has(name)) {
|
|
46
|
+
error(`Table "${name}" not found. Available: ${allSchemas.map((s) => s.tableName).join(', ')}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return targets;
|
|
51
|
+
}
|
|
52
|
+
/** Write the missing Table*.ts files. Existing ones are never overwritten. */
|
|
53
|
+
function writeTableFiles(targetSchemas, allSchemas, tablesDir) {
|
|
54
|
+
const created = [];
|
|
55
|
+
for (const schema of targetSchemas) {
|
|
56
|
+
const tableVarName = tableVarOf(schema);
|
|
57
|
+
const tableFile = path.join(tablesDir, `${tableVarName}.ts`);
|
|
58
|
+
if (fs.existsSync(tableFile)) {
|
|
59
|
+
displayAsTableRow(`${tableVarName}.ts`, 'skipped (already exists)', 60, CONSOLE_COLORS.gray);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
fs.writeFileSync(tableFile, generateSingleTableFile(schema, allSchemas));
|
|
63
|
+
created.push(tableVarName);
|
|
64
|
+
displayAsTableRow(`${tableVarName}.ts`, 'created', 60, CONSOLE_COLORS.green);
|
|
65
|
+
}
|
|
66
|
+
return created;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Write `dbTables.ts` only when missing. The index must reference only the Table*.ts files
|
|
70
|
+
* actually on disk: a subset run would otherwise emit imports for files never generated,
|
|
71
|
+
* and the consumer's project would not compile. When it already exists, the newly created
|
|
72
|
+
* tables are reported instead of being silently left out.
|
|
73
|
+
*/
|
|
74
|
+
function writeDbTablesIndex(tablesDir, allSchemas, createdTableVars) {
|
|
75
|
+
const dbTablesFile = path.join(tablesDir, 'dbTables.ts');
|
|
76
|
+
if (!fs.existsSync(dbTablesFile)) {
|
|
77
|
+
const existingTableVars = new Set(fs
|
|
78
|
+
.readdirSync(tablesDir)
|
|
79
|
+
.filter((f) => f.startsWith('Table') && f.endsWith('.ts'))
|
|
80
|
+
.map((f) => f.slice(0, -'.ts'.length)));
|
|
81
|
+
const indexSchemas = allSchemas.filter((s) => existingTableVars.has(tableVarOf(s)));
|
|
82
|
+
fs.writeFileSync(dbTablesFile, generateDbTablesIndex(indexSchemas));
|
|
83
|
+
displayAsTableRow('dbTables.ts', 'created', 60, CONSOLE_COLORS.green);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
displayAsTableRow('dbTables.ts', 'skipped (already exists)', 60, CONSOLE_COLORS.gray);
|
|
87
|
+
const indexContent = fs.readFileSync(dbTablesFile, 'utf-8');
|
|
88
|
+
const missing = createdTableVars.filter((v) => !indexContent.includes(v));
|
|
89
|
+
if (missing.length > 0) {
|
|
90
|
+
display(`Remember to add the new tables to dbTables.ts: ${missing.join(', ')}`, CONSOLE_COLORS.yellow);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
25
93
|
await runCli('fastify-auto-sqlapi: generating tables template', async () => {
|
|
26
94
|
const cliArgs = parseArgs({
|
|
27
95
|
output: { type: 'value' },
|
|
@@ -55,72 +123,14 @@ await runCli('fastify-auto-sqlapi: generating tables template', async () => {
|
|
|
55
123
|
display('Run sqlapi-generate-schema first.', CONSOLE_COLORS.magenta);
|
|
56
124
|
process.exit(1);
|
|
57
125
|
}
|
|
58
|
-
|
|
59
|
-
const allSchemas = [];
|
|
60
|
-
for (const file of schemaFiles) {
|
|
61
|
-
const content = fs.readFileSync(path.join(schemasDir, file), 'utf-8');
|
|
62
|
-
const parsed = parseSchemaFile(content);
|
|
63
|
-
if (parsed) {
|
|
64
|
-
allSchemas.push(parsed);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
126
|
+
const allSchemas = parseAllSchemas(schemasDir, schemaFiles);
|
|
67
127
|
if (allSchemas.length === 0) {
|
|
68
128
|
error('No valid schemas parsed.');
|
|
69
129
|
process.exit(1);
|
|
70
130
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
targetSchemas = allSchemas;
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
const requestedSet = new Set(cliArgs.tables);
|
|
78
|
-
targetSchemas = allSchemas.filter((s) => requestedSet.has(s.tableName));
|
|
79
|
-
const foundNames = new Set(targetSchemas.map((s) => s.tableName));
|
|
80
|
-
for (const name of requestedSet) {
|
|
81
|
-
if (!foundNames.has(name)) {
|
|
82
|
-
error(`Table "${name}" not found. Available: ${allSchemas.map((s) => s.tableName).join(', ')}`);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
const tableVarOf = (schema) => 'Table' + schema.schemaName.replace(/^Schema/, '');
|
|
88
|
-
// Generate individual Table*.ts files (skip if already exists)
|
|
89
|
-
const createdTableVars = [];
|
|
90
|
-
for (const schema of targetSchemas) {
|
|
91
|
-
const tableVarName = tableVarOf(schema);
|
|
92
|
-
const tableFile = path.join(tablesDir, `${tableVarName}.ts`);
|
|
93
|
-
if (fs.existsSync(tableFile)) {
|
|
94
|
-
displayAsTableRow(`${tableVarName}.ts`, 'skipped (already exists)', 60, CONSOLE_COLORS.gray);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
const content = generateSingleTableFile(schema, allSchemas);
|
|
98
|
-
fs.writeFileSync(tableFile, content);
|
|
99
|
-
createdTableVars.push(tableVarName);
|
|
100
|
-
displayAsTableRow(`${tableVarName}.ts`, 'created', 60, CONSOLE_COLORS.green);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// Generate dbTables.ts only if it does not exist. The index must reference only
|
|
104
|
-
// Table*.ts files actually present on disk, otherwise a subset run would emit
|
|
105
|
-
// imports for files that were never generated and the project would not compile.
|
|
106
|
-
const dbTablesFile = path.join(tablesDir, 'dbTables.ts');
|
|
107
|
-
if (fs.existsSync(dbTablesFile)) {
|
|
108
|
-
displayAsTableRow('dbTables.ts', 'skipped (already exists)', 60, CONSOLE_COLORS.gray);
|
|
109
|
-
const indexContent = fs.readFileSync(dbTablesFile, 'utf-8');
|
|
110
|
-
const missing = createdTableVars.filter((v) => !indexContent.includes(v));
|
|
111
|
-
if (missing.length > 0) {
|
|
112
|
-
display(`Remember to add the new tables to dbTables.ts: ${missing.join(', ')}`, CONSOLE_COLORS.yellow);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
const existingTableVars = new Set(fs
|
|
117
|
-
.readdirSync(tablesDir)
|
|
118
|
-
.filter((f) => f.startsWith('Table') && f.endsWith('.ts'))
|
|
119
|
-
.map((f) => f.slice(0, -'.ts'.length)));
|
|
120
|
-
const indexSchemas = allSchemas.filter((s) => existingTableVars.has(tableVarOf(s)));
|
|
121
|
-
fs.writeFileSync(dbTablesFile, generateDbTablesIndex(indexSchemas));
|
|
122
|
-
displayAsTableRow('dbTables.ts', 'created', 60, CONSOLE_COLORS.green);
|
|
123
|
-
}
|
|
131
|
+
const targetSchemas = selectTargetSchemas(allSchemas, cliArgs.all, cliArgs.tables);
|
|
132
|
+
const createdTableVars = writeTableFiles(targetSchemas, allSchemas, tablesDir);
|
|
133
|
+
writeDbTablesIndex(tablesDir, allSchemas, createdTableVars);
|
|
124
134
|
console.log('');
|
|
125
135
|
display('Edit the generated Table*.ts files to customize your table configuration.', CONSOLE_COLORS.magenta);
|
|
126
136
|
});
|
package/dist/bin/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/bin/utils.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAqBlD;AAED,eAAO,MAAM,cAAc;;;;;;;;CAQjB,CAAC;AAEX,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAU,GAChB,IAAI,CAQN;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,IAAI,CAE7D;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExC;AAED,KAAK,OAAO,GACR;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,KAAK,QAAQ,CAAC,CAAC,SAAS,OAAO,IAC7B,CAAC,CAAC,MAAM,CAAC,SAAS,MAAM,GAAG,OAAO,GAClC,CAAC,CAAC,MAAM,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,GACnC,MAAM,GAAG,SAAS,CAAC;AAErB;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzD,IAAI,EAAE,CAAC,EACP,IAAI,WAAwB,GAC3B;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG;IAAE,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/bin/utils.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAqBlD;AAED,eAAO,MAAM,cAAc;;;;;;;;CAQjB,CAAC;AAEX,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAU,GAChB,IAAI,CAQN;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,IAAI,CAE7D;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExC;AAED,KAAK,OAAO,GACR;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,KAAK,QAAQ,CAAC,CAAC,SAAS,OAAO,IAC7B,CAAC,CAAC,MAAM,CAAC,SAAS,MAAM,GAAG,OAAO,GAClC,CAAC,CAAC,MAAM,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,GACnC,MAAM,GAAG,SAAS,CAAC;AAErB;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACzD,IAAI,EAAE,CAAC,EACP,IAAI,WAAwB,GAC3B;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAAG;IAAE,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAmB/D;AAqDD;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF"}
|
package/dist/bin/utils.js
CHANGED
|
@@ -62,51 +62,65 @@ export function error(text) {
|
|
|
62
62
|
* (split on ',') into the list as well, supporting both "a b" and "a,b" CLI shapes.
|
|
63
63
|
*/
|
|
64
64
|
export function parseArgs(spec, argv = process.argv.slice(2)) {
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
65
|
const out = { positional: [] };
|
|
67
66
|
for (const [name, def] of Object.entries(spec)) {
|
|
68
67
|
out[name] = def.type === 'flag' ? false : def.type === 'list' ? [] : undefined;
|
|
69
68
|
}
|
|
70
|
-
|
|
69
|
+
let i = 0;
|
|
70
|
+
while (i < argv.length) {
|
|
71
71
|
const token = argv[i];
|
|
72
72
|
if (token.startsWith('--')) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!def)
|
|
76
|
-
continue;
|
|
77
|
-
if (def.type === 'flag') {
|
|
78
|
-
out[name] = true;
|
|
79
|
-
}
|
|
80
|
-
else if (def.type === 'value' && i + 1 < argv.length) {
|
|
81
|
-
out[name] = argv[++i];
|
|
82
|
-
}
|
|
83
|
-
else if (def.type === 'list' && i + 1 < argv.length) {
|
|
84
|
-
const next = argv[++i];
|
|
85
|
-
for (const part of next.split(',')) {
|
|
86
|
-
const t = part.trim();
|
|
87
|
-
if (t)
|
|
88
|
-
out[name].push(t);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
73
|
+
// An option may consume the following entry as its value.
|
|
74
|
+
i += 1 + readOption(out, spec, argv, i);
|
|
91
75
|
}
|
|
92
76
|
else {
|
|
93
|
-
out
|
|
94
|
-
|
|
95
|
-
for (const [n, def] of Object.entries(spec)) {
|
|
96
|
-
if (def.type === 'list') {
|
|
97
|
-
for (const part of token.split(',')) {
|
|
98
|
-
const t = part.trim();
|
|
99
|
-
if (t)
|
|
100
|
-
out[n].push(t);
|
|
101
|
-
}
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
77
|
+
readPositional(out, spec, token);
|
|
78
|
+
i += 1;
|
|
105
79
|
}
|
|
106
80
|
}
|
|
107
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
81
|
return out;
|
|
109
82
|
}
|
|
83
|
+
/** Split on ',' and append the non-empty tokens. */
|
|
84
|
+
function pushCsv(target, raw) {
|
|
85
|
+
for (const part of raw.split(',')) {
|
|
86
|
+
const t = part.trim();
|
|
87
|
+
if (t)
|
|
88
|
+
target.push(t);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Read one `--name [value]` option at `i`. Returns how many *extra* argv entries it
|
|
93
|
+
* consumed (1 when it took the next entry as its value, 0 otherwise).
|
|
94
|
+
*/
|
|
95
|
+
function readOption(out, spec, argv, i) {
|
|
96
|
+
const name = argv[i].slice(2);
|
|
97
|
+
const def = spec[name];
|
|
98
|
+
if (!def)
|
|
99
|
+
return 0;
|
|
100
|
+
const hasNext = i + 1 < argv.length;
|
|
101
|
+
if (def.type === 'flag') {
|
|
102
|
+
out[name] = true;
|
|
103
|
+
}
|
|
104
|
+
else if (def.type === 'value' && hasNext) {
|
|
105
|
+
out[name] = argv[i + 1];
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
else if (def.type === 'list' && hasNext) {
|
|
109
|
+
pushCsv(out[name], argv[i + 1]);
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
/** A positional token also feeds the (single) list spec, so `a b` and `a,b` both work. */
|
|
115
|
+
function readPositional(out, spec, token) {
|
|
116
|
+
out.positional.push(token);
|
|
117
|
+
for (const [name, def] of Object.entries(spec)) {
|
|
118
|
+
if (def.type === 'list') {
|
|
119
|
+
pushCsv(out[name], token);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
110
124
|
/**
|
|
111
125
|
* CLI entry-point wrapper: prints a header, awaits `fn`, and turns thrown errors into a red message
|
|
112
126
|
* with a non-zero exit code (without aborting the process synchronously, so unfinished stdout flushes).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DbTables, ITable, TableOperation } from '../../types.js';
|
|
2
|
-
|
|
2
|
+
interface ManifestField {
|
|
3
3
|
type: string;
|
|
4
4
|
/** Present (true) when the field must be sent on insert. */
|
|
5
5
|
required?: boolean;
|
|
@@ -8,7 +8,7 @@ export interface ManifestField {
|
|
|
8
8
|
/** Present (true) when the field is writable but never returned by reads (readExclude). */
|
|
9
9
|
writeOnly?: boolean;
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
interface ManifestJoin {
|
|
12
12
|
alias: string;
|
|
13
13
|
table: string;
|
|
14
14
|
/** '1:N' → usable in joinMustExist/joinMultiple/joinGroup; 'N:1' → usable in joinLeft. */
|
|
@@ -50,4 +50,5 @@ export declare function buildAgentManifest(dbTables: DbTables): AgentManifest;
|
|
|
50
50
|
* Notation: `!` required on insert, `?` nullable, `(writeOnly)` never returned by reads.
|
|
51
51
|
*/
|
|
52
52
|
export declare function renderAgentManifestMd(manifest: AgentManifest): string;
|
|
53
|
+
export {};
|
|
53
54
|
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/lib/agent/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/lib/agent/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAkB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAOvF,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2FAA2F;IAC3F,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,0FAA0F;IAC1F,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACtC,uFAAuF;IACvF,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,gFAAgF;IAChF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACvC;AAgCD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE,CAInE;AA0DD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,aAAa,CAMpE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAkBrE"}
|
|
@@ -40,6 +40,65 @@ export function tableOperations(tableConf) {
|
|
|
40
40
|
return [...requested];
|
|
41
41
|
return requested.filter((op) => !SINGLE_PK_OPERATIONS.has(op));
|
|
42
42
|
}
|
|
43
|
+
/** Field descriptors: type, plus the flags an agent needs to build a valid payload. */
|
|
44
|
+
function manifestFields(tableConf) {
|
|
45
|
+
const schema = tableConf.Schema;
|
|
46
|
+
const required = new Set((schema.validation.required) ?? []);
|
|
47
|
+
const readExcluded = new Set(tableConf.readExclude ?? []);
|
|
48
|
+
const fields = {};
|
|
49
|
+
for (const [name, fieldSchema] of Object.entries(schema.fields)) {
|
|
50
|
+
const f = { type: shortType(fieldSchema) };
|
|
51
|
+
if (required.has(name))
|
|
52
|
+
f.required = true;
|
|
53
|
+
if (isNullable(fieldSchema))
|
|
54
|
+
f.nullable = true;
|
|
55
|
+
if (readExcluded.has(name))
|
|
56
|
+
f.writeOnly = true;
|
|
57
|
+
fields[name] = f;
|
|
58
|
+
}
|
|
59
|
+
return fields;
|
|
60
|
+
}
|
|
61
|
+
function manifestJoins(joins) {
|
|
62
|
+
return (joins ?? []).map((j) => ({
|
|
63
|
+
alias: j.alias,
|
|
64
|
+
table: j.joinSchema.tableName,
|
|
65
|
+
kind: j.unique ? 'N:1' : '1:N',
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
function manifestExtraFilters(tableConf) {
|
|
69
|
+
const entries = Object.entries(tableConf.extraFilters ?? {});
|
|
70
|
+
if (entries.length === 0)
|
|
71
|
+
return undefined;
|
|
72
|
+
return Object.fromEntries(entries.map(([n, s]) => [n, shortType(s)]));
|
|
73
|
+
}
|
|
74
|
+
/** One table's entry. Optional keys are omitted entirely when empty, to keep the prompt small. */
|
|
75
|
+
function manifestTable(tableConf) {
|
|
76
|
+
const entry = {
|
|
77
|
+
table: tableConf.Schema.tableName,
|
|
78
|
+
primary: tableConf.primary,
|
|
79
|
+
operations: tableOperations(tableConf),
|
|
80
|
+
fields: manifestFields(tableConf),
|
|
81
|
+
};
|
|
82
|
+
const computed = computedTypes(tableConf);
|
|
83
|
+
const extraFilters = manifestExtraFilters(tableConf);
|
|
84
|
+
const readJoins = manifestJoins(tableConf.allowedReadJoins);
|
|
85
|
+
const writeJoins = manifestJoins(tableConf.allowedWriteJoins);
|
|
86
|
+
if (tableConf.excludeFromCreation?.length)
|
|
87
|
+
entry.serverGenerated = [...tableConf.excludeFromCreation];
|
|
88
|
+
if (computed)
|
|
89
|
+
entry.computed = computed;
|
|
90
|
+
if (extraFilters)
|
|
91
|
+
entry.extraFilters = extraFilters;
|
|
92
|
+
if (readJoins.length)
|
|
93
|
+
entry.readJoins = readJoins;
|
|
94
|
+
if (writeJoins.length)
|
|
95
|
+
entry.writeJoins = writeJoins;
|
|
96
|
+
if (tableConf.tenantScope)
|
|
97
|
+
entry.tenantScoped = true;
|
|
98
|
+
if (tableConf.defaultOrder)
|
|
99
|
+
entry.defaultOrder = tableConf.defaultOrder;
|
|
100
|
+
return entry;
|
|
101
|
+
}
|
|
43
102
|
/**
|
|
44
103
|
* Build the machine-readable description of every table exposed by this deployment:
|
|
45
104
|
* fields (type/required/nullable), enabled operations, join aliases, computed fields,
|
|
@@ -50,55 +109,7 @@ export function tableOperations(tableConf) {
|
|
|
50
109
|
export function buildAgentManifest(dbTables) {
|
|
51
110
|
const tables = {};
|
|
52
111
|
for (const [key, tableConf] of Object.entries(dbTables)) {
|
|
53
|
-
|
|
54
|
-
const required = new Set((schema.validation.required) ?? []);
|
|
55
|
-
const readExcluded = new Set(tableConf.readExclude ?? []);
|
|
56
|
-
const fields = {};
|
|
57
|
-
for (const [name, fieldSchema] of Object.entries(schema.fields)) {
|
|
58
|
-
const f = { type: shortType(fieldSchema) };
|
|
59
|
-
if (required.has(name))
|
|
60
|
-
f.required = true;
|
|
61
|
-
if (isNullable(fieldSchema))
|
|
62
|
-
f.nullable = true;
|
|
63
|
-
if (readExcluded.has(name))
|
|
64
|
-
f.writeOnly = true;
|
|
65
|
-
fields[name] = f;
|
|
66
|
-
}
|
|
67
|
-
const readJoins = (tableConf.allowedReadJoins ?? []).map((j) => ({
|
|
68
|
-
alias: j.alias,
|
|
69
|
-
table: j.joinSchema.tableName,
|
|
70
|
-
kind: j.unique ? 'N:1' : '1:N',
|
|
71
|
-
}));
|
|
72
|
-
const writeJoins = (tableConf.allowedWriteJoins ?? []).map((j) => ({
|
|
73
|
-
alias: j.alias,
|
|
74
|
-
table: j.joinSchema.tableName,
|
|
75
|
-
kind: j.unique ? 'N:1' : '1:N',
|
|
76
|
-
}));
|
|
77
|
-
const extraFilters = Object.keys(tableConf.extraFilters ?? {}).length
|
|
78
|
-
? Object.fromEntries(Object.entries(tableConf.extraFilters).map(([n, s]) => [n, shortType(s)]))
|
|
79
|
-
: undefined;
|
|
80
|
-
const entry = {
|
|
81
|
-
table: schema.tableName,
|
|
82
|
-
primary: tableConf.primary,
|
|
83
|
-
operations: tableOperations(tableConf),
|
|
84
|
-
fields,
|
|
85
|
-
};
|
|
86
|
-
if (tableConf.excludeFromCreation?.length)
|
|
87
|
-
entry.serverGenerated = [...tableConf.excludeFromCreation];
|
|
88
|
-
const computed = computedTypes(tableConf);
|
|
89
|
-
if (computed)
|
|
90
|
-
entry.computed = computed;
|
|
91
|
-
if (extraFilters)
|
|
92
|
-
entry.extraFilters = extraFilters;
|
|
93
|
-
if (readJoins.length)
|
|
94
|
-
entry.readJoins = readJoins;
|
|
95
|
-
if (writeJoins.length)
|
|
96
|
-
entry.writeJoins = writeJoins;
|
|
97
|
-
if (tableConf.tenantScope)
|
|
98
|
-
entry.tenantScoped = true;
|
|
99
|
-
if (tableConf.defaultOrder)
|
|
100
|
-
entry.defaultOrder = tableConf.defaultOrder;
|
|
101
|
-
tables[key] = entry;
|
|
112
|
+
tables[key] = manifestTable(tableConf);
|
|
102
113
|
}
|
|
103
114
|
return { tables };
|
|
104
115
|
}
|
|
@@ -114,40 +125,53 @@ export function renderAgentManifestMd(manifest) {
|
|
|
114
125
|
'serverGenerated fields: omit on insert (values from client are ignored).',
|
|
115
126
|
'readJoins aliases: `1:N` → joinMustExist/joinMultiple/joinGroup; `N:1` → joinLeft.',
|
|
116
127
|
'writeJoins aliases: keys for `secondaries`/`deletions` in writes.',
|
|
128
|
+
'filters: unknown keys are rejected (400), never ignored. extraFilters do not apply ' +
|
|
129
|
+
'under joinLeft — use joinMustExist on the same relation.',
|
|
117
130
|
'',
|
|
118
131
|
];
|
|
119
132
|
for (const t of Object.values(manifest.tables)) {
|
|
120
|
-
|
|
121
|
-
const flags = t.tenantScoped ? ' [tenant-scoped]' : '';
|
|
122
|
-
lines.push(`## ${t.table} PK:${pk} ops:${t.operations.join(',')}${flags}`);
|
|
123
|
-
const fieldParts = Object.entries(t.fields).map(([name, f]) => {
|
|
124
|
-
let s = `${name}:${f.type}`;
|
|
125
|
-
if (f.required)
|
|
126
|
-
s += '!';
|
|
127
|
-
if (f.nullable)
|
|
128
|
-
s += '?';
|
|
129
|
-
if (f.writeOnly)
|
|
130
|
-
s += '(writeOnly)';
|
|
131
|
-
return s;
|
|
132
|
-
});
|
|
133
|
-
lines.push(`fields: ${fieldParts.join(', ')}`);
|
|
134
|
-
if (t.serverGenerated)
|
|
135
|
-
lines.push(`serverGenerated: ${t.serverGenerated.join(', ')}`);
|
|
136
|
-
if (t.computed) {
|
|
137
|
-
lines.push(`computed: ${Object.entries(t.computed).map(([n, ty]) => `${n}:${ty}`).join(', ')}`);
|
|
138
|
-
}
|
|
139
|
-
if (t.extraFilters) {
|
|
140
|
-
lines.push(`extraFilters: ${Object.entries(t.extraFilters).map(([n, ty]) => `${n}:${ty}`).join(', ')}`);
|
|
141
|
-
}
|
|
142
|
-
if (t.readJoins) {
|
|
143
|
-
lines.push(`readJoins: ${t.readJoins.map((j) => `${j.alias}→${j.table}(${j.kind})`).join(', ')}`);
|
|
144
|
-
}
|
|
145
|
-
if (t.writeJoins) {
|
|
146
|
-
lines.push(`writeJoins: ${t.writeJoins.map((j) => `${j.alias}→${j.table}`).join(', ')}`);
|
|
147
|
-
}
|
|
148
|
-
if (t.defaultOrder)
|
|
149
|
-
lines.push(`defaultOrder: ${t.defaultOrder}`);
|
|
150
|
-
lines.push('');
|
|
133
|
+
lines.push(...renderTableMd(t));
|
|
151
134
|
}
|
|
152
135
|
return lines.join('\n');
|
|
153
136
|
}
|
|
137
|
+
/** `name:type` pairs — the compact form used throughout the manifest. */
|
|
138
|
+
function namedTypes(map) {
|
|
139
|
+
return Object.entries(map).map(([n, ty]) => `${n}:${ty}`).join(', ');
|
|
140
|
+
}
|
|
141
|
+
/** A field's type with its notation suffixes: `!` required, `?` nullable, `(writeOnly)`. */
|
|
142
|
+
function fieldNotation(f) {
|
|
143
|
+
let s = f.type;
|
|
144
|
+
if (f.required)
|
|
145
|
+
s += '!';
|
|
146
|
+
if (f.nullable)
|
|
147
|
+
s += '?';
|
|
148
|
+
if (f.writeOnly)
|
|
149
|
+
s += '(writeOnly)';
|
|
150
|
+
return s;
|
|
151
|
+
}
|
|
152
|
+
function renderTableMd(t) {
|
|
153
|
+
const pk = Array.isArray(t.primary) ? t.primary.join('+') : t.primary;
|
|
154
|
+
const flags = t.tenantScoped ? ' [tenant-scoped]' : '';
|
|
155
|
+
const ops = t.operations.join(',');
|
|
156
|
+
const lines = [`## ${t.table} PK:${pk} ops:${ops}${flags}`];
|
|
157
|
+
const fieldParts = Object.entries(t.fields).map(([name, f]) => `${name}:${fieldNotation(f)}`);
|
|
158
|
+
lines.push(`fields: ${fieldParts.join(', ')}`);
|
|
159
|
+
if (t.serverGenerated)
|
|
160
|
+
lines.push(`serverGenerated: ${t.serverGenerated.join(', ')}`);
|
|
161
|
+
if (t.computed)
|
|
162
|
+
lines.push(`computed: ${namedTypes(t.computed)}`);
|
|
163
|
+
if (t.extraFilters)
|
|
164
|
+
lines.push(`extraFilters: ${namedTypes(t.extraFilters)}`);
|
|
165
|
+
if (t.readJoins) {
|
|
166
|
+
const readJoins = t.readJoins.map((j) => `${j.alias}→${j.table}(${j.kind})`);
|
|
167
|
+
lines.push(`readJoins: ${readJoins.join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
if (t.writeJoins) {
|
|
170
|
+
const writeJoins = t.writeJoins.map((j) => `${j.alias}→${j.table}`);
|
|
171
|
+
lines.push(`writeJoins: ${writeJoins.join(', ')}`);
|
|
172
|
+
}
|
|
173
|
+
if (t.defaultOrder)
|
|
174
|
+
lines.push(`defaultOrder: ${t.defaultOrder}`);
|
|
175
|
+
lines.push('');
|
|
176
|
+
return lines;
|
|
177
|
+
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import type { ColumnInfo } from '../../types.js';
|
|
2
|
-
export
|
|
2
|
+
export interface MysqlConnectionConfig {
|
|
3
3
|
host: string;
|
|
4
4
|
port: number;
|
|
5
5
|
user: string;
|
|
6
6
|
password: string;
|
|
7
7
|
database: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function
|
|
10
|
-
|
|
11
|
-
port: number;
|
|
12
|
-
user: string;
|
|
13
|
-
password: string;
|
|
14
|
-
database: string;
|
|
15
|
-
}, schema: string): Promise<ColumnInfo[]>;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildMysqlConnectionConfig(): MysqlConnectionConfig;
|
|
10
|
+
export declare function introspectMysqlTables(connectionConfig: MysqlConnectionConfig, schema: string): Promise<ColumnInfo[]>;
|
|
16
11
|
//# sourceMappingURL=mysql-introspect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mysql-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/mysql-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,
|
|
1
|
+
{"version":3,"file":"mysql-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/mysql-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoCD,wBAAgB,0BAA0B,IAAI,qBAAqB,CAmBlE;AAkDD,wBAAsB,qBAAqB,CACzC,gBAAgB,EAAE,qBAAqB,EACvC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CA4BvB"}
|
|
@@ -66,7 +66,6 @@ function mapMysqlType(dataType) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
export async function introspectMysqlTables(connectionConfig, schema) {
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
69
|
const mysql2 = loadOptionalDependency('mysql2/promise', 'npm install mysql2');
|
|
71
70
|
const connection = await mysql2.createConnection(connectionConfig);
|
|
72
71
|
try {
|
|
@@ -74,13 +73,15 @@ export async function introspectMysqlTables(connectionConfig, schema) {
|
|
|
74
73
|
FROM information_schema.columns
|
|
75
74
|
WHERE table_schema = ?
|
|
76
75
|
ORDER BY table_name, ordinal_position`, [schema]);
|
|
77
|
-
//
|
|
76
|
+
// `||` is kept as-is from the untyped version: this change types the driver, it does
|
|
77
|
+
// not alter behaviour. The trailing fallbacks only satisfy ColumnInfo, which cannot
|
|
78
|
+
// hold `undefined`.
|
|
78
79
|
return rows.map((row) => ({
|
|
79
|
-
table_name: row.TABLE_NAME || row.table_name,
|
|
80
|
-
column_name: row.COLUMN_NAME || row.column_name,
|
|
81
|
-
udt_name: mapMysqlType(row.DATA_TYPE || row.data_type),
|
|
82
|
-
column_default: row.COLUMN_DEFAULT || row.column_default,
|
|
83
|
-
is_nullable: row.IS_NULLABLE || row.is_nullable,
|
|
80
|
+
table_name: row.TABLE_NAME || row.table_name || '',
|
|
81
|
+
column_name: row.COLUMN_NAME || row.column_name || '',
|
|
82
|
+
udt_name: mapMysqlType(row.DATA_TYPE || row.data_type || ''),
|
|
83
|
+
column_default: row.COLUMN_DEFAULT || row.column_default || null,
|
|
84
|
+
is_nullable: row.IS_NULLABLE || row.is_nullable || '',
|
|
84
85
|
is_primary: (row.COLUMN_KEY || row.column_key) === 'PRI',
|
|
85
86
|
is_auto_increment: String(row.EXTRA || row.extra || '').includes('auto_increment'),
|
|
86
87
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/pg-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"pg-introspect.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/pg-introspect.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAmBjD,wBAAgB,qBAAqB,IAAI,MAAM,CAY9C;AAED,wBAAsB,gBAAgB,CACpC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB"}
|
|
@@ -11,7 +11,6 @@ export function buildConnectionString() {
|
|
|
11
11
|
return `postgres://${user}:${password}@${host}:${port}/${db}`;
|
|
12
12
|
}
|
|
13
13
|
export async function introspectTables(connectionString, schema) {
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
14
|
const pg = loadOptionalDependency('pg', 'npm install pg');
|
|
16
15
|
const client = new pg.Client({ connectionString });
|
|
17
16
|
try {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ColumnInfo, TableMap } from '../../types.js';
|
|
2
|
+
/** Maps a DB column type to its TypeBox schema expression.
|
|
3
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
4
|
+
*/
|
|
2
5
|
export declare function convertColType(udtName: string, columnInfo: {
|
|
3
6
|
column_default: string | null;
|
|
4
7
|
is_nullable: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/schema-codegen.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE3D,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9F,MAAM,CA0DR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,QAAQ,CAqC1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAoBzF;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,MAAM,CA8CR"}
|
|
1
|
+
{"version":3,"file":"schema-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/schema-codegen.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE3D;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9F,MAAM,CA0DR;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,QAAQ,CAqC1D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAoBzF;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,MAAM,CA8CR"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { toCamelCase, toSchemaName } from '../naming.js';
|
|
2
|
+
/** Maps a DB column type to its TypeBox schema expression.
|
|
3
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
4
|
+
*/
|
|
2
5
|
export function convertColType(udtName, columnInfo) {
|
|
3
6
|
// AUTO_INCREMENT (mysql) behaves like a default: the DB generates the value.
|
|
4
7
|
const hasDefault = columnInfo.column_default || columnInfo.is_auto_increment;
|
|
@@ -6,15 +6,22 @@ export interface ParsedSchema {
|
|
|
6
6
|
/** PRIMARY KEY fields declared in the schema file (`primaryKey: [...]`), when present. */
|
|
7
7
|
primary?: string[];
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
interface DetectedRelation {
|
|
10
10
|
parentSchemaName: string;
|
|
11
11
|
parentField: string;
|
|
12
12
|
childSchemaName: string;
|
|
13
13
|
childField: string;
|
|
14
14
|
}
|
|
15
15
|
export declare function parseSchemaFile(content: string): ParsedSchema | null;
|
|
16
|
+
/** Infers FK relations between parsed schemas.
|
|
17
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
18
|
+
*/
|
|
16
19
|
export declare function detectRelations(schemas: ParsedSchema[]): DetectedRelation[];
|
|
17
20
|
export declare function generateSingleTableFile(schema: ParsedSchema, allSchemas: ParsedSchema[]): string;
|
|
18
21
|
export declare function generateDbTablesIndex(schemas: ParsedSchema[]): string;
|
|
22
|
+
/** Legacy single-file DbTables generator.
|
|
23
|
+
* @testonly Exported only so unit tests can exercise it directly.
|
|
24
|
+
*/
|
|
19
25
|
export declare function generateTablesFile(schemas: ParsedSchema[]): string;
|
|
26
|
+
export {};
|
|
20
27
|
//# sourceMappingURL=tables-codegen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tables-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/tables-codegen.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,
|
|
1
|
+
{"version":3,"file":"tables-codegen.d.ts","sourceRoot":"","sources":["../../../src/lib/cli/tables-codegen.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,UAAU,gBAAgB;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAuCpE;AAyCD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,gBAAgB,EAAE,CAwB3E;AAyED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,MAAM,CAgFhG;AAID,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE;AA8CD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAoClE"}
|