@sqg/sqg 0.3.0 → 0.5.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/dist/sqg.mjs +376 -254
- package/dist/templates/java-duckdb-arrow.hbs +2 -2
- package/package.json +1 -1
package/dist/sqg.mjs
CHANGED
|
@@ -21,45 +21,128 @@ import typescriptPlugin from "prettier/parser-typescript";
|
|
|
21
21
|
import estree from "prettier/plugins/estree";
|
|
22
22
|
|
|
23
23
|
//#region src/constants.ts
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"sqlite",
|
|
31
|
-
"duckdb",
|
|
32
|
-
"postgres"
|
|
33
|
-
];
|
|
34
|
-
/** Supported code generators with their descriptions */
|
|
35
|
-
const SUPPORTED_GENERATORS = {
|
|
36
|
-
"typescript/better-sqlite3": {
|
|
24
|
+
/** All supported generators with their full specification */
|
|
25
|
+
const GENERATORS = {
|
|
26
|
+
"typescript/sqlite/better-sqlite3": {
|
|
27
|
+
language: "typescript",
|
|
28
|
+
engine: "sqlite",
|
|
29
|
+
driver: "better-sqlite3",
|
|
37
30
|
description: "TypeScript with better-sqlite3 driver",
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
extension: ".ts",
|
|
32
|
+
template: "better-sqlite3.hbs"
|
|
40
33
|
},
|
|
41
|
-
"typescript/duckdb": {
|
|
34
|
+
"typescript/duckdb/node-api": {
|
|
35
|
+
language: "typescript",
|
|
36
|
+
engine: "duckdb",
|
|
37
|
+
driver: "node-api",
|
|
42
38
|
description: "TypeScript with @duckdb/node-api driver",
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
extension: ".ts",
|
|
40
|
+
template: "typescript-duckdb.hbs"
|
|
41
|
+
},
|
|
42
|
+
"java/sqlite/jdbc": {
|
|
43
|
+
language: "java",
|
|
44
|
+
engine: "sqlite",
|
|
45
|
+
driver: "jdbc",
|
|
46
|
+
description: "Java with JDBC for SQLite",
|
|
47
|
+
extension: ".java",
|
|
48
|
+
template: "java-jdbc.hbs"
|
|
45
49
|
},
|
|
46
|
-
"java/jdbc": {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
extension: ".java"
|
|
50
|
+
"java/duckdb/jdbc": {
|
|
51
|
+
language: "java",
|
|
52
|
+
engine: "duckdb",
|
|
53
|
+
driver: "jdbc",
|
|
54
|
+
description: "Java with JDBC for DuckDB",
|
|
55
|
+
extension: ".java",
|
|
56
|
+
template: "java-jdbc.hbs"
|
|
54
57
|
},
|
|
55
|
-
"java/duckdb
|
|
58
|
+
"java/duckdb/arrow": {
|
|
59
|
+
language: "java",
|
|
60
|
+
engine: "duckdb",
|
|
61
|
+
driver: "arrow",
|
|
56
62
|
description: "Java with DuckDB Arrow API",
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
extension: ".java",
|
|
64
|
+
template: "java-duckdb-arrow.hbs"
|
|
65
|
+
},
|
|
66
|
+
"java/postgres/jdbc": {
|
|
67
|
+
language: "java",
|
|
68
|
+
engine: "postgres",
|
|
69
|
+
driver: "jdbc",
|
|
70
|
+
description: "Java with JDBC for PostgreSQL",
|
|
71
|
+
extension: ".java",
|
|
72
|
+
template: "java-jdbc.hbs"
|
|
59
73
|
}
|
|
60
74
|
};
|
|
61
|
-
/**
|
|
62
|
-
const
|
|
75
|
+
/** Default drivers for language/engine combinations */
|
|
76
|
+
const DEFAULT_DRIVERS = {
|
|
77
|
+
"typescript/sqlite": "better-sqlite3",
|
|
78
|
+
"typescript/duckdb": "node-api",
|
|
79
|
+
"java/sqlite": "jdbc",
|
|
80
|
+
"java/duckdb": "jdbc",
|
|
81
|
+
"java/postgres": "jdbc"
|
|
82
|
+
};
|
|
83
|
+
/** List of all full generator names */
|
|
84
|
+
const GENERATOR_NAMES = Object.keys(GENERATORS);
|
|
85
|
+
/** List of short generator names (language/engine) */
|
|
86
|
+
const SHORT_GENERATOR_NAMES = Object.keys(DEFAULT_DRIVERS);
|
|
87
|
+
/**
|
|
88
|
+
* Resolve a generator string to its full form.
|
|
89
|
+
* Accepts both short (language/engine) and full (language/engine/driver) formats.
|
|
90
|
+
*/
|
|
91
|
+
function resolveGenerator(generator) {
|
|
92
|
+
if (generator in GENERATORS) return generator;
|
|
93
|
+
if (generator in DEFAULT_DRIVERS) return `${generator}/${DEFAULT_DRIVERS[generator]}`;
|
|
94
|
+
return generator;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Parse a generator string and return its info.
|
|
98
|
+
* Throws if the generator is invalid.
|
|
99
|
+
*/
|
|
100
|
+
function parseGenerator(generator) {
|
|
101
|
+
const info = GENERATORS[resolveGenerator(generator)];
|
|
102
|
+
if (!info) throw new Error(`Invalid generator: ${generator}`);
|
|
103
|
+
return info;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if a generator string is valid (either short or full form).
|
|
107
|
+
*/
|
|
108
|
+
function isValidGenerator(generator) {
|
|
109
|
+
return resolveGenerator(generator) in GENERATORS;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get the database engine for a generator.
|
|
113
|
+
*/
|
|
114
|
+
function getGeneratorEngine(generator) {
|
|
115
|
+
return parseGenerator(generator).engine;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Find similar generator names for typo suggestions.
|
|
119
|
+
*/
|
|
120
|
+
function findSimilarGenerators(input) {
|
|
121
|
+
const normalized = input.toLowerCase();
|
|
122
|
+
return [...GENERATOR_NAMES, ...SHORT_GENERATOR_NAMES].filter((name) => {
|
|
123
|
+
const nameLower = name.toLowerCase();
|
|
124
|
+
if (nameLower.includes(normalized) || normalized.includes(nameLower)) return true;
|
|
125
|
+
return [
|
|
126
|
+
normalized.replace(/\//g, "-"),
|
|
127
|
+
normalized.replace(/-/g, "/"),
|
|
128
|
+
normalized.replace(/_/g, "/"),
|
|
129
|
+
normalized.replace(/_/g, "-")
|
|
130
|
+
].some((v) => nameLower.includes(v) || v.includes(nameLower));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Format generators for CLI help output.
|
|
135
|
+
*/
|
|
136
|
+
function formatGeneratorsHelp() {
|
|
137
|
+
const lines = [];
|
|
138
|
+
for (const shortName of SHORT_GENERATOR_NAMES) {
|
|
139
|
+
const fullName = `${shortName}/${DEFAULT_DRIVERS[shortName]}`;
|
|
140
|
+
const info = GENERATORS[fullName];
|
|
141
|
+
lines.push(` ${shortName.padEnd(24)} ${info.description} (default)`);
|
|
142
|
+
for (const [generatorName, generatorInfo] of Object.entries(GENERATORS)) if (generatorName.startsWith(`${shortName}/`) && generatorName !== fullName) lines.push(` ${generatorName.padEnd(24)} ${generatorInfo.description}`);
|
|
143
|
+
}
|
|
144
|
+
return lines.join("\n");
|
|
145
|
+
}
|
|
63
146
|
/** SQL annotation syntax reference */
|
|
64
147
|
const SQL_SYNTAX_REFERENCE = `
|
|
65
148
|
SQL Annotation Syntax:
|
|
@@ -88,34 +171,6 @@ Example:
|
|
|
88
171
|
|
|
89
172
|
-- TABLE users :appender
|
|
90
173
|
`.trim();
|
|
91
|
-
/**
|
|
92
|
-
* Find similar generator names for typo suggestions
|
|
93
|
-
*/
|
|
94
|
-
function findSimilarGenerators(input) {
|
|
95
|
-
const normalized = input.toLowerCase();
|
|
96
|
-
return GENERATOR_NAMES.filter((name) => {
|
|
97
|
-
const nameLower = name.toLowerCase();
|
|
98
|
-
if (nameLower.includes(normalized) || normalized.includes(nameLower)) return true;
|
|
99
|
-
return [
|
|
100
|
-
normalized.replace("/", "-"),
|
|
101
|
-
normalized.replace("-", "/"),
|
|
102
|
-
normalized.replace("_", "/"),
|
|
103
|
-
normalized.replace("_", "-")
|
|
104
|
-
].some((v) => nameLower.includes(v) || v.includes(nameLower));
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Format generators for CLI help output
|
|
109
|
-
*/
|
|
110
|
-
function formatGeneratorsHelp() {
|
|
111
|
-
return Object.entries(SUPPORTED_GENERATORS).map(([name, info]) => ` ${name.padEnd(28)} ${info.description} (${info.compatibleEngines.join(", ")})`).join("\n");
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Format engines for CLI help output
|
|
115
|
-
*/
|
|
116
|
-
function formatEnginesHelp() {
|
|
117
|
-
return SUPPORTED_ENGINES.map((e) => ` ${e}`).join("\n");
|
|
118
|
-
}
|
|
119
174
|
|
|
120
175
|
//#endregion
|
|
121
176
|
//#region src/errors.ts
|
|
@@ -174,41 +229,20 @@ var ConfigError = class extends SqgError {
|
|
|
174
229
|
* Error for invalid generator names
|
|
175
230
|
*/
|
|
176
231
|
var InvalidGeneratorError = class extends SqgError {
|
|
177
|
-
constructor(generatorName, validGenerators, suggestion) {
|
|
232
|
+
constructor(generatorName, validGenerators$1, suggestion) {
|
|
178
233
|
const similarMsg = suggestion ? ` Did you mean '${suggestion}'?` : "";
|
|
179
|
-
super(`Invalid generator '${generatorName}'.${similarMsg} Valid generators: ${validGenerators.join(", ")}`, "INVALID_GENERATOR", suggestion ? `Use '${suggestion}' instead` : `Choose from: ${validGenerators.join(", ")}`, { generator: generatorName });
|
|
234
|
+
super(`Invalid generator '${generatorName}'.${similarMsg} Valid generators: ${validGenerators$1.join(", ")}`, "INVALID_GENERATOR", suggestion ? `Use '${suggestion}' instead` : `Choose from: ${validGenerators$1.join(", ")}`, { generator: generatorName });
|
|
180
235
|
this.name = "InvalidGeneratorError";
|
|
181
236
|
}
|
|
182
237
|
};
|
|
183
238
|
/**
|
|
184
|
-
* Error for invalid engine names
|
|
185
|
-
*/
|
|
186
|
-
var InvalidEngineError = class extends SqgError {
|
|
187
|
-
constructor(engineName, validEngines) {
|
|
188
|
-
super(`Invalid engine '${engineName}'. Valid engines: ${validEngines.join(", ")}`, "INVALID_ENGINE", `Choose from: ${validEngines.join(", ")}`, { engine: engineName });
|
|
189
|
-
this.name = "InvalidEngineError";
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
/**
|
|
193
|
-
* Error for generator/engine compatibility
|
|
194
|
-
*/
|
|
195
|
-
var GeneratorEngineMismatchError = class extends SqgError {
|
|
196
|
-
constructor(generator, engine, compatibleEngines) {
|
|
197
|
-
super(`Generator '${generator}' is not compatible with engine '${engine}'`, "GENERATOR_ENGINE_MISMATCH", `Generator '${generator}' works with: ${compatibleEngines.join(", ")}`, {
|
|
198
|
-
generator,
|
|
199
|
-
engine
|
|
200
|
-
});
|
|
201
|
-
this.name = "GeneratorEngineMismatchError";
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
/**
|
|
205
239
|
* Error for database initialization/connection issues
|
|
206
240
|
*/
|
|
207
241
|
var DatabaseError = class extends SqgError {
|
|
208
242
|
constructor(message, engine, suggestion, context) {
|
|
209
243
|
super(message, "DATABASE_ERROR", suggestion, {
|
|
210
|
-
|
|
211
|
-
|
|
244
|
+
...context,
|
|
245
|
+
engine
|
|
212
246
|
});
|
|
213
247
|
this.name = "DatabaseError";
|
|
214
248
|
}
|
|
@@ -406,11 +440,15 @@ function parseSQLQueries(filePath, extraVariables) {
|
|
|
406
440
|
const queries = [];
|
|
407
441
|
const tables = [];
|
|
408
442
|
const cursor = parser.parse(content).cursor();
|
|
443
|
+
function getLineNumber(position) {
|
|
444
|
+
return content.slice(0, position).split("\n").length;
|
|
445
|
+
}
|
|
409
446
|
function getStr(nodeName, optional = false) {
|
|
410
447
|
const node = cursor.node.getChild(nodeName);
|
|
411
448
|
if (!node) {
|
|
412
449
|
if (optional) return;
|
|
413
|
-
|
|
450
|
+
const lineNumber = getLineNumber(cursor.node.from);
|
|
451
|
+
throw new Error(`Node '${nodeName}' not found at line ${lineNumber}`);
|
|
414
452
|
}
|
|
415
453
|
return nodeStr(node);
|
|
416
454
|
}
|
|
@@ -1328,6 +1366,9 @@ var BaseGenerator = class {
|
|
|
1328
1366
|
isCompatibleWith(_engine) {
|
|
1329
1367
|
return true;
|
|
1330
1368
|
}
|
|
1369
|
+
supportsAppenders(_engine) {
|
|
1370
|
+
return false;
|
|
1371
|
+
}
|
|
1331
1372
|
functionReturnType(query) {
|
|
1332
1373
|
if (query.isOne) return this.rowType(query);
|
|
1333
1374
|
return this.typeMapper.formatListType(this.rowType(query));
|
|
@@ -1351,6 +1392,9 @@ var JavaGenerator = class extends BaseGenerator {
|
|
|
1351
1392
|
super(template, new JavaTypeMapper());
|
|
1352
1393
|
this.template = template;
|
|
1353
1394
|
}
|
|
1395
|
+
supportsAppenders(engine) {
|
|
1396
|
+
return engine === "duckdb";
|
|
1397
|
+
}
|
|
1354
1398
|
getFunctionName(id) {
|
|
1355
1399
|
return camelCase$1(id);
|
|
1356
1400
|
}
|
|
@@ -1447,14 +1491,17 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
|
1447
1491
|
const name = `${gen.name}-jdbc`;
|
|
1448
1492
|
writeGeneratedFile(projectDir, {
|
|
1449
1493
|
name,
|
|
1450
|
-
generator: "java/jdbc",
|
|
1494
|
+
generator: "java/duckdb/jdbc",
|
|
1451
1495
|
output: gen.output,
|
|
1452
1496
|
config: gen.config
|
|
1453
|
-
}, this.javaGenerator, name, q, tables);
|
|
1497
|
+
}, this.javaGenerator, name, q, tables, "duckdb");
|
|
1454
1498
|
}
|
|
1455
1499
|
isCompatibleWith(engine) {
|
|
1456
1500
|
return engine === "duckdb";
|
|
1457
1501
|
}
|
|
1502
|
+
supportsAppenders(_engine) {
|
|
1503
|
+
return true;
|
|
1504
|
+
}
|
|
1458
1505
|
getFilename(sqlFileName) {
|
|
1459
1506
|
return this.javaGenerator.getFilename(sqlFileName);
|
|
1460
1507
|
}
|
|
@@ -1462,25 +1509,28 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
|
1462
1509
|
return this.javaGenerator.getClassName(name);
|
|
1463
1510
|
}
|
|
1464
1511
|
mapType(column) {
|
|
1465
|
-
const { type
|
|
1512
|
+
const { type } = column;
|
|
1466
1513
|
if (typeof type === "string") {
|
|
1467
1514
|
const mappedType = {
|
|
1468
1515
|
INTEGER: "IntVector",
|
|
1516
|
+
BIGINT: "BigIntVector",
|
|
1469
1517
|
BOOLEAN: "BitVector",
|
|
1470
1518
|
DOUBLE: "Float8Vector",
|
|
1471
1519
|
FLOAT: "Float4Vector",
|
|
1472
1520
|
VARCHAR: "VarCharVector",
|
|
1473
|
-
TEXT: "VarCharVector"
|
|
1521
|
+
TEXT: "VarCharVector",
|
|
1522
|
+
TIMESTAMP: "TimeStampVector",
|
|
1523
|
+
DATE: "DateDayVector",
|
|
1524
|
+
TIME: "TimeMicroVector"
|
|
1474
1525
|
}[type.toUpperCase()];
|
|
1475
1526
|
if (!mappedType) consola.warn("(duckdb-arrow) Mapped type is unknown:", type);
|
|
1476
1527
|
return mappedType ?? "Object";
|
|
1477
1528
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
return this.typeMapper.getTypeName(mockColumn);
|
|
1529
|
+
if (type instanceof ListType) return "ListVector";
|
|
1530
|
+
if (type instanceof StructType) return "StructVector";
|
|
1531
|
+
if (type instanceof MapType) return "MapVector";
|
|
1532
|
+
consola.warn("(duckdb-arrow) Unknown complex type:", type);
|
|
1533
|
+
return "Object";
|
|
1484
1534
|
}
|
|
1485
1535
|
mapParameterType(type, nullable) {
|
|
1486
1536
|
return this.typeMapper.getTypeName({
|
|
@@ -1688,6 +1738,9 @@ var TsDuckDBGenerator = class extends TsGenerator {
|
|
|
1688
1738
|
constructor(template) {
|
|
1689
1739
|
super(template);
|
|
1690
1740
|
}
|
|
1741
|
+
supportsAppenders(_engine) {
|
|
1742
|
+
return true;
|
|
1743
|
+
}
|
|
1691
1744
|
async beforeGenerate(projectDir, gen, queries, tables) {
|
|
1692
1745
|
await super.beforeGenerate(projectDir, gen, queries, tables);
|
|
1693
1746
|
Handlebars.registerHelper("tsType", (column) => {
|
|
@@ -1735,22 +1788,31 @@ var TsDuckDBGenerator = class extends TsGenerator {
|
|
|
1735
1788
|
|
|
1736
1789
|
//#endregion
|
|
1737
1790
|
//#region src/generators/index.ts
|
|
1791
|
+
/**
|
|
1792
|
+
* Get a generator instance for the given generator.
|
|
1793
|
+
* Accepts both short (language/engine) and full (language/engine/driver) formats.
|
|
1794
|
+
*/
|
|
1738
1795
|
function getGenerator(generator) {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1796
|
+
const fullGenerator = resolveGenerator(generator);
|
|
1797
|
+
try {
|
|
1798
|
+
const info = parseGenerator(generator);
|
|
1799
|
+
const key = `${info.language}/${info.driver}`;
|
|
1800
|
+
switch (key) {
|
|
1801
|
+
case "typescript/better-sqlite3": return new TsGenerator(`templates/${info.template}`);
|
|
1802
|
+
case "typescript/node-api": return new TsDuckDBGenerator(`templates/${info.template}`);
|
|
1803
|
+
case "java/jdbc": return new JavaGenerator(`templates/${info.template}`);
|
|
1804
|
+
case "java/arrow": return new JavaDuckDBArrowGenerator(`templates/${info.template}`);
|
|
1805
|
+
default: throw new Error(`No generator class for ${key}`);
|
|
1747
1806
|
}
|
|
1807
|
+
} catch {
|
|
1808
|
+
const similar = findSimilarGenerators(generator);
|
|
1809
|
+
throw new InvalidGeneratorError(fullGenerator, [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES], similar.length > 0 ? similar[0] : void 0);
|
|
1748
1810
|
}
|
|
1749
1811
|
}
|
|
1750
1812
|
|
|
1751
1813
|
//#endregion
|
|
1752
1814
|
//#region src/sqltool.ts
|
|
1753
|
-
const GENERATED_FILE_COMMENT = "This file is generated by SQG. Do not edit manually.";
|
|
1815
|
+
const GENERATED_FILE_COMMENT = "This file is generated by SQG (https://sqg.dev). Do not edit manually.";
|
|
1754
1816
|
const configSchema = z.object({ result: z.record(z.string(), z.string()).optional() });
|
|
1755
1817
|
var Config = class Config {
|
|
1756
1818
|
constructor(result) {
|
|
@@ -1894,13 +1956,13 @@ var TableHelper = class {
|
|
|
1894
1956
|
return this.generator.typeMapper;
|
|
1895
1957
|
}
|
|
1896
1958
|
};
|
|
1897
|
-
function generateSourceFile(name, queries, tables, templatePath, generator, config) {
|
|
1959
|
+
function generateSourceFile(name, queries, tables, templatePath, generator, engine, config) {
|
|
1898
1960
|
const templateSrc = readFileSync(templatePath, "utf-8");
|
|
1899
1961
|
const template = Handlebars.compile(templateSrc);
|
|
1900
1962
|
Handlebars.registerHelper("mapType", (column) => generator.mapType(column));
|
|
1901
1963
|
Handlebars.registerHelper("plusOne", (value) => value + 1);
|
|
1902
1964
|
const migrations = queries.filter((q) => q.isMigrate).map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q)));
|
|
1903
|
-
const tableHelpers = tables.filter((t) => !t.skipGenerateFunction).map((t) => new TableHelper(t, generator));
|
|
1965
|
+
const tableHelpers = generator.supportsAppenders(engine) ? tables.filter((t) => !t.skipGenerateFunction).map((t) => new TableHelper(t, generator)) : [];
|
|
1904
1966
|
return template({
|
|
1905
1967
|
generatedComment: GENERATED_FILE_COMMENT,
|
|
1906
1968
|
migrations,
|
|
@@ -1913,6 +1975,8 @@ function generateSourceFile(name, queries, tables, templatePath, generator, conf
|
|
|
1913
1975
|
allowProtoMethodsByDefault: true
|
|
1914
1976
|
});
|
|
1915
1977
|
}
|
|
1978
|
+
/** All valid generator strings for schema validation */
|
|
1979
|
+
const validGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
|
|
1916
1980
|
/**
|
|
1917
1981
|
* Project configuration schema with descriptions for validation messages
|
|
1918
1982
|
*/
|
|
@@ -1920,10 +1984,9 @@ const ProjectSchema = z.object({
|
|
|
1920
1984
|
version: z.number().describe("Configuration version (currently 1)"),
|
|
1921
1985
|
name: z.string().min(1, "Project name is required").describe("Project name used for generated class names"),
|
|
1922
1986
|
sql: z.array(z.object({
|
|
1923
|
-
engine: z.enum(SUPPORTED_ENGINES).describe(`Database engine: ${SUPPORTED_ENGINES.join(", ")}`),
|
|
1924
1987
|
files: z.array(z.string().min(1)).min(1, "At least one SQL file is required").describe("SQL files to process"),
|
|
1925
1988
|
gen: z.array(z.object({
|
|
1926
|
-
generator: z.
|
|
1989
|
+
generator: z.string().refine((val) => isValidGenerator(val), { message: `Invalid generator. Valid generators: ${validGenerators.join(", ")}` }).describe(`Code generation generator: ${SHORT_GENERATOR_NAMES.join(", ")}`),
|
|
1927
1990
|
name: z.string().optional().describe("Override the generated class/module name"),
|
|
1928
1991
|
template: z.string().optional().describe("Custom Handlebars template path"),
|
|
1929
1992
|
output: z.string().min(1, "Output path is required").describe("Output file or directory path"),
|
|
@@ -1941,15 +2004,36 @@ var ExtraVariable = class {
|
|
|
1941
2004
|
this.value = value;
|
|
1942
2005
|
}
|
|
1943
2006
|
};
|
|
1944
|
-
function createExtraVariables(sources) {
|
|
2007
|
+
function createExtraVariables(sources, suppressLogging = false) {
|
|
1945
2008
|
return sources.map((source) => {
|
|
1946
2009
|
const path = source.path;
|
|
1947
2010
|
const resolvedPath = path.replace("$HOME", homedir());
|
|
1948
2011
|
const varName = `sources_${(source.name ?? basename(path, extname(resolvedPath))).replace(/\s+/g, "_")}`;
|
|
1949
|
-
consola.info("Extra variable:", varName, resolvedPath);
|
|
2012
|
+
if (!suppressLogging) consola.info("Extra variable:", varName, resolvedPath);
|
|
1950
2013
|
return new ExtraVariable(varName, `'${resolvedPath}'`);
|
|
1951
2014
|
});
|
|
1952
2015
|
}
|
|
2016
|
+
function buildProjectFromCliOptions(options) {
|
|
2017
|
+
if (!isValidGenerator(options.generator)) {
|
|
2018
|
+
const similar = findSimilarGenerators(options.generator);
|
|
2019
|
+
const allGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
|
|
2020
|
+
throw new InvalidGeneratorError(options.generator, allGenerators, similar.length > 0 ? similar[0] : void 0);
|
|
2021
|
+
}
|
|
2022
|
+
const generatorInfo = parseGenerator(options.generator);
|
|
2023
|
+
const genConfig = {
|
|
2024
|
+
generator: options.generator,
|
|
2025
|
+
output: options.output || "."
|
|
2026
|
+
};
|
|
2027
|
+
if (generatorInfo.language === "java") genConfig.config = { package: "generated" };
|
|
2028
|
+
return {
|
|
2029
|
+
version: 1,
|
|
2030
|
+
name: options.name || "generated",
|
|
2031
|
+
sql: [{
|
|
2032
|
+
files: options.files,
|
|
2033
|
+
gen: [genConfig]
|
|
2034
|
+
}]
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
1953
2037
|
/**
|
|
1954
2038
|
* Parse and validate project configuration with helpful error messages
|
|
1955
2039
|
*/
|
|
@@ -1971,12 +2055,12 @@ function parseProjectConfig(filePath) {
|
|
|
1971
2055
|
const obj = parsed;
|
|
1972
2056
|
if (obj.sql && Array.isArray(obj.sql)) for (let i = 0; i < obj.sql.length; i++) {
|
|
1973
2057
|
const sqlConfig = obj.sql[i];
|
|
1974
|
-
if (sqlConfig.engine && !SUPPORTED_ENGINES.includes(sqlConfig.engine)) throw new InvalidEngineError(String(sqlConfig.engine), [...SUPPORTED_ENGINES]);
|
|
1975
2058
|
if (sqlConfig.gen && Array.isArray(sqlConfig.gen)) for (let j = 0; j < sqlConfig.gen.length; j++) {
|
|
1976
2059
|
const genConfig = sqlConfig.gen[j];
|
|
1977
|
-
if (genConfig.generator && !
|
|
2060
|
+
if (genConfig.generator && !isValidGenerator(String(genConfig.generator))) {
|
|
1978
2061
|
const similar = findSimilarGenerators(String(genConfig.generator));
|
|
1979
|
-
|
|
2062
|
+
const allGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
|
|
2063
|
+
throw new InvalidGeneratorError(String(genConfig.generator), allGenerators, similar.length > 0 ? similar[0] : void 0);
|
|
1980
2064
|
}
|
|
1981
2065
|
}
|
|
1982
2066
|
}
|
|
@@ -2020,11 +2104,16 @@ function validateQueries(queries) {
|
|
|
2020
2104
|
};
|
|
2021
2105
|
}
|
|
2022
2106
|
}
|
|
2023
|
-
async function writeGeneratedFile(projectDir, gen, generator, file, queries, tables =
|
|
2107
|
+
async function writeGeneratedFile(projectDir, gen, generator, file, queries, tables, engine, writeToStdout = false) {
|
|
2024
2108
|
await generator.beforeGenerate(projectDir, gen, queries, tables);
|
|
2025
2109
|
const templatePath = join(dirname(new URL(import.meta.url).pathname), gen.template ?? generator.template);
|
|
2026
2110
|
const name = gen.name ?? basename(file, extname(file));
|
|
2027
|
-
const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, gen.config);
|
|
2111
|
+
const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, engine, gen.config);
|
|
2112
|
+
if (writeToStdout) {
|
|
2113
|
+
process.stdout.write(sourceFile);
|
|
2114
|
+
if (!sourceFile.endsWith("\n")) process.stdout.write("\n");
|
|
2115
|
+
return null;
|
|
2116
|
+
}
|
|
2028
2117
|
const outputPath = getOutputPath(projectDir, name, gen, generator);
|
|
2029
2118
|
writeFileSync(outputPath, sourceFile);
|
|
2030
2119
|
consola.success(`Generated ${outputPath}`);
|
|
@@ -2032,33 +2121,11 @@ async function writeGeneratedFile(projectDir, gen, generator, file, queries, tab
|
|
|
2032
2121
|
return outputPath;
|
|
2033
2122
|
}
|
|
2034
2123
|
/**
|
|
2035
|
-
* Validate project configuration without executing queries
|
|
2124
|
+
* Validate project configuration from a Project object without executing queries
|
|
2036
2125
|
* Use this for pre-flight checks before generation
|
|
2037
2126
|
*/
|
|
2038
|
-
async function
|
|
2127
|
+
async function validateProjectFromConfig(project, projectDir) {
|
|
2039
2128
|
const errors = [];
|
|
2040
|
-
const projectDir = resolve(dirname(projectPath));
|
|
2041
|
-
let project;
|
|
2042
|
-
try {
|
|
2043
|
-
project = parseProjectConfig(projectPath);
|
|
2044
|
-
} catch (e) {
|
|
2045
|
-
if (e instanceof SqgError) return {
|
|
2046
|
-
valid: false,
|
|
2047
|
-
errors: [{
|
|
2048
|
-
code: e.code,
|
|
2049
|
-
message: e.message,
|
|
2050
|
-
suggestion: e.suggestion,
|
|
2051
|
-
context: e.context
|
|
2052
|
-
}]
|
|
2053
|
-
};
|
|
2054
|
-
return {
|
|
2055
|
-
valid: false,
|
|
2056
|
-
errors: [{
|
|
2057
|
-
code: "UNKNOWN_ERROR",
|
|
2058
|
-
message: String(e)
|
|
2059
|
-
}]
|
|
2060
|
-
};
|
|
2061
|
-
}
|
|
2062
2129
|
const sqlFiles = [];
|
|
2063
2130
|
const generators = [];
|
|
2064
2131
|
for (const sql of project.sql) for (const sqlFile of sql.files) {
|
|
@@ -2072,15 +2139,15 @@ async function validateProject(projectPath) {
|
|
|
2072
2139
|
});
|
|
2073
2140
|
for (const gen of sql.gen) {
|
|
2074
2141
|
generators.push(gen.generator);
|
|
2075
|
-
if (!
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2142
|
+
if (!isValidGenerator(gen.generator)) {
|
|
2143
|
+
const similar = findSimilarGenerators(gen.generator);
|
|
2144
|
+
errors.push({
|
|
2145
|
+
code: "INVALID_GENERATOR",
|
|
2146
|
+
message: `Invalid generator '${gen.generator}'`,
|
|
2147
|
+
suggestion: similar.length > 0 ? `Did you mean '${similar[0]}'?` : `Valid generators: ${SHORT_GENERATOR_NAMES.join(", ")}`,
|
|
2148
|
+
context: { generator: gen.generator }
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2084
2151
|
}
|
|
2085
2152
|
}
|
|
2086
2153
|
return {
|
|
@@ -2095,53 +2162,96 @@ async function validateProject(projectPath) {
|
|
|
2095
2162
|
};
|
|
2096
2163
|
}
|
|
2097
2164
|
/**
|
|
2098
|
-
*
|
|
2165
|
+
* Validate project configuration from a YAML file without executing queries
|
|
2166
|
+
* Use this for pre-flight checks before generation
|
|
2099
2167
|
*/
|
|
2100
|
-
async function
|
|
2168
|
+
async function validateProject(projectPath) {
|
|
2101
2169
|
const projectDir = resolve(dirname(projectPath));
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
});
|
|
2137
|
-
}
|
|
2170
|
+
let project;
|
|
2171
|
+
try {
|
|
2172
|
+
project = parseProjectConfig(projectPath);
|
|
2173
|
+
} catch (e) {
|
|
2174
|
+
if (e instanceof SqgError) return {
|
|
2175
|
+
valid: false,
|
|
2176
|
+
errors: [{
|
|
2177
|
+
code: e.code,
|
|
2178
|
+
message: e.message,
|
|
2179
|
+
suggestion: e.suggestion,
|
|
2180
|
+
context: e.context
|
|
2181
|
+
}]
|
|
2182
|
+
};
|
|
2183
|
+
return {
|
|
2184
|
+
valid: false,
|
|
2185
|
+
errors: [{
|
|
2186
|
+
code: "UNKNOWN_ERROR",
|
|
2187
|
+
message: String(e)
|
|
2188
|
+
}]
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
return await validateProjectFromConfig(project, projectDir);
|
|
2192
|
+
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Process a project configuration and generate code from a Project object
|
|
2195
|
+
*/
|
|
2196
|
+
async function processProjectFromConfig(project, projectDir, writeToStdout = false) {
|
|
2197
|
+
const originalLevel = consola.level;
|
|
2198
|
+
if (writeToStdout) consola.level = LogLevels.silent;
|
|
2199
|
+
try {
|
|
2200
|
+
const extraVariables = createExtraVariables(project.sources ?? [], writeToStdout);
|
|
2201
|
+
const files = [];
|
|
2202
|
+
for (const sql of project.sql) {
|
|
2203
|
+
const gensByEngine = /* @__PURE__ */ new Map();
|
|
2138
2204
|
for (const gen of sql.gen) {
|
|
2139
|
-
const
|
|
2140
|
-
|
|
2205
|
+
const engine = getGeneratorEngine(gen.generator);
|
|
2206
|
+
if (!gensByEngine.has(engine)) gensByEngine.set(engine, []);
|
|
2207
|
+
gensByEngine.get(engine).push(gen);
|
|
2208
|
+
}
|
|
2209
|
+
for (const sqlFile of sql.files) {
|
|
2210
|
+
const fullPath = join(projectDir, sqlFile);
|
|
2211
|
+
if (!existsSync(fullPath)) throw new FileNotFoundError(fullPath, projectDir);
|
|
2212
|
+
let queries;
|
|
2213
|
+
let tables;
|
|
2214
|
+
try {
|
|
2215
|
+
const parseResult = parseSQLQueries(fullPath, extraVariables);
|
|
2216
|
+
queries = parseResult.queries;
|
|
2217
|
+
tables = parseResult.tables;
|
|
2218
|
+
} catch (e) {
|
|
2219
|
+
if (e instanceof SqgError) throw e;
|
|
2220
|
+
throw SqgError.inFile(`Failed to parse SQL file: ${e.message}`, "SQL_PARSE_ERROR", sqlFile, { suggestion: "Check SQL syntax and annotation format" });
|
|
2221
|
+
}
|
|
2222
|
+
for (const [engine, gens] of gensByEngine) {
|
|
2223
|
+
try {
|
|
2224
|
+
const dbEngine = getDatabaseEngine(engine);
|
|
2225
|
+
await dbEngine.initializeDatabase(queries);
|
|
2226
|
+
await dbEngine.executeQueries(queries);
|
|
2227
|
+
if (tables.length > 0) await dbEngine.introspectTables(tables);
|
|
2228
|
+
validateQueries(queries);
|
|
2229
|
+
await dbEngine.close();
|
|
2230
|
+
} catch (e) {
|
|
2231
|
+
if (e instanceof SqgError) throw e;
|
|
2232
|
+
throw new SqgError(`Database error processing ${sqlFile}: ${e.message}`, "DATABASE_ERROR", `Check that the SQL is valid for engine '${engine}'`, {
|
|
2233
|
+
file: sqlFile,
|
|
2234
|
+
engine
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
for (const gen of gens) {
|
|
2238
|
+
const outputPath = await writeGeneratedFile(projectDir, gen, getGenerator(gen.generator), sqlFile, queries, tables, engine, writeToStdout);
|
|
2239
|
+
if (outputPath !== null) files.push(outputPath);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2141
2242
|
}
|
|
2142
2243
|
}
|
|
2244
|
+
return files;
|
|
2245
|
+
} finally {
|
|
2246
|
+
if (writeToStdout) consola.level = originalLevel;
|
|
2143
2247
|
}
|
|
2144
|
-
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Process a project configuration and generate code from a YAML file
|
|
2251
|
+
*/
|
|
2252
|
+
async function processProject(projectPath) {
|
|
2253
|
+
const projectDir = resolve(dirname(projectPath));
|
|
2254
|
+
return await processProjectFromConfig(parseProjectConfig(projectPath), projectDir, false);
|
|
2145
2255
|
}
|
|
2146
2256
|
|
|
2147
2257
|
//#endregion
|
|
@@ -2150,14 +2260,10 @@ async function processProject(projectPath) {
|
|
|
2150
2260
|
* SQG Project Initialization - Creates new SQG projects with example files
|
|
2151
2261
|
*/
|
|
2152
2262
|
/**
|
|
2153
|
-
* Get the default generator for
|
|
2263
|
+
* Get the default generator for a language preference
|
|
2154
2264
|
*/
|
|
2155
|
-
function getDefaultGenerator(
|
|
2156
|
-
return
|
|
2157
|
-
sqlite: "typescript/better-sqlite3",
|
|
2158
|
-
duckdb: "typescript/duckdb",
|
|
2159
|
-
postgres: "java/jdbc"
|
|
2160
|
-
}[engine];
|
|
2265
|
+
function getDefaultGenerator() {
|
|
2266
|
+
return "typescript/sqlite";
|
|
2161
2267
|
}
|
|
2162
2268
|
/**
|
|
2163
2269
|
* Generate example SQL content based on engine
|
|
@@ -2355,21 +2461,8 @@ DELETE FROM posts WHERE id = \${id};
|
|
|
2355
2461
|
/**
|
|
2356
2462
|
* Generate sqg.yaml configuration
|
|
2357
2463
|
*/
|
|
2358
|
-
function getConfigYaml(
|
|
2359
|
-
|
|
2360
|
-
const config = {
|
|
2361
|
-
version: 1,
|
|
2362
|
-
name: "my-project",
|
|
2363
|
-
sql: [{
|
|
2364
|
-
engine,
|
|
2365
|
-
files: ["queries.sql"],
|
|
2366
|
-
gen: [{
|
|
2367
|
-
generator,
|
|
2368
|
-
output: output.endsWith("/") ? output : `${output}/`
|
|
2369
|
-
}]
|
|
2370
|
-
}]
|
|
2371
|
-
};
|
|
2372
|
-
if (generator.startsWith("java/")) config.sql[0].gen[0].config = { package: "generated" };
|
|
2464
|
+
function getConfigYaml(generator, output) {
|
|
2465
|
+
const isJava = parseGenerator(generator).language === "java";
|
|
2373
2466
|
return `# SQG Configuration
|
|
2374
2467
|
# Generated by: sqg init
|
|
2375
2468
|
# Documentation: https://sqg.dev
|
|
@@ -2378,12 +2471,11 @@ version: 1
|
|
|
2378
2471
|
name: my-project
|
|
2379
2472
|
|
|
2380
2473
|
sql:
|
|
2381
|
-
-
|
|
2382
|
-
files:
|
|
2474
|
+
- files:
|
|
2383
2475
|
- queries.sql
|
|
2384
2476
|
gen:
|
|
2385
2477
|
- generator: ${generator}
|
|
2386
|
-
output: ${output.endsWith("/") ? output : `${output}/`}${
|
|
2478
|
+
output: ${output.endsWith("/") ? output : `${output}/`}${isJava ? `
|
|
2387
2479
|
config:
|
|
2388
2480
|
package: generated` : ""}
|
|
2389
2481
|
`;
|
|
@@ -2392,18 +2484,13 @@ sql:
|
|
|
2392
2484
|
* Initialize a new SQG project
|
|
2393
2485
|
*/
|
|
2394
2486
|
async function initProject(options) {
|
|
2395
|
-
const
|
|
2487
|
+
const generator = options.generator || getDefaultGenerator();
|
|
2396
2488
|
const output = options.output || "./generated";
|
|
2397
|
-
if (!
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
throw new InvalidGeneratorError(options.generator, Object.keys(SUPPORTED_GENERATORS), similar.length > 0 ? similar[0] : void 0);
|
|
2403
|
-
}
|
|
2404
|
-
generator = options.generator;
|
|
2405
|
-
if (!SUPPORTED_GENERATORS[generator].compatibleEngines.includes(engine)) throw new SqgError(`Generator '${generator}' is not compatible with engine '${engine}'`, "GENERATOR_ENGINE_MISMATCH", `For '${engine}', use one of: ${Object.entries(SUPPORTED_GENERATORS).filter(([_, info]) => info.compatibleEngines.includes(engine)).map(([name]) => name).join(", ")}`);
|
|
2406
|
-
} else generator = getDefaultGenerator(engine);
|
|
2489
|
+
if (!isValidGenerator(generator)) {
|
|
2490
|
+
const similar = findSimilarGenerators(generator);
|
|
2491
|
+
throw new InvalidGeneratorError(generator, [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES], similar.length > 0 ? similar[0] : void 0);
|
|
2492
|
+
}
|
|
2493
|
+
const engine = parseGenerator(generator).engine;
|
|
2407
2494
|
const configPath = "sqg.yaml";
|
|
2408
2495
|
const sqlPath = "queries.sql";
|
|
2409
2496
|
if (!options.force) {
|
|
@@ -2414,16 +2501,16 @@ async function initProject(options) {
|
|
|
2414
2501
|
mkdirSync(output, { recursive: true });
|
|
2415
2502
|
consola.success(`Created output directory: ${output}`);
|
|
2416
2503
|
}
|
|
2417
|
-
writeFileSync(configPath, getConfigYaml(
|
|
2504
|
+
writeFileSync(configPath, getConfigYaml(generator, output));
|
|
2418
2505
|
consola.success(`Created ${configPath}`);
|
|
2419
2506
|
writeFileSync(sqlPath, getExampleSql(engine));
|
|
2420
2507
|
consola.success(`Created ${sqlPath}`);
|
|
2421
2508
|
consola.box(`
|
|
2422
2509
|
SQG project initialized!
|
|
2423
2510
|
|
|
2424
|
-
Engine: ${engine}
|
|
2425
2511
|
Generator: ${generator}
|
|
2426
|
-
|
|
2512
|
+
Engine: ${engine}
|
|
2513
|
+
Output: ${output}
|
|
2427
2514
|
|
|
2428
2515
|
Next steps:
|
|
2429
2516
|
1. Edit queries.sql to add your SQL queries
|
|
@@ -2436,46 +2523,81 @@ Documentation: https://sqg.dev
|
|
|
2436
2523
|
|
|
2437
2524
|
//#endregion
|
|
2438
2525
|
//#region src/sqg.ts
|
|
2439
|
-
const version = process.env.npm_package_version ?? "0.
|
|
2526
|
+
const version = process.env.npm_package_version ?? "0.5.0";
|
|
2440
2527
|
const description = process.env.npm_package_description ?? "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)";
|
|
2441
2528
|
consola.level = LogLevels.info;
|
|
2442
2529
|
const program = new Command().name("sqg").description(`${description}
|
|
2443
2530
|
|
|
2444
2531
|
Generate type-safe database access code from annotated SQL files.
|
|
2445
2532
|
|
|
2446
|
-
Supported Engines:
|
|
2447
|
-
${formatEnginesHelp()}
|
|
2448
|
-
|
|
2449
2533
|
Supported Generators:
|
|
2450
|
-
${formatGeneratorsHelp()}`).version(version, "-v, --version", "output the version number").option("--verbose", "Enable debug logging (shows SQL execution details)").option("--format <format>", "Output format: text (default) or json", "text").option("--validate", "Validate configuration without generating code").
|
|
2451
|
-
|
|
2534
|
+
${formatGeneratorsHelp()}`).version(version, "-v, --version", "output the version number").option("--verbose", "Enable debug logging (shows SQL execution details)").option("--format <format>", "Output format: text (default) or json", "text").option("--validate", "Validate configuration without generating code").option("--generator <generator>", `Code generation generator (${SHORT_GENERATOR_NAMES.join(", ")})`).option("--file <file>", "SQL file path (can be repeated)", (val, prev = []) => {
|
|
2535
|
+
prev.push(val);
|
|
2536
|
+
return prev;
|
|
2537
|
+
}).option("--output <path>", "Output file or directory path (optional, if omitted writes to stdout)").option("--name <name>", "Project name (optional, defaults to 'generated')").showHelpAfterError().showSuggestionAfterError();
|
|
2538
|
+
program.argument("[project]", "Path to the project YAML config (sqg.yaml) or omit to use CLI options").hook("preAction", (thisCommand) => {
|
|
2452
2539
|
const opts = thisCommand.opts();
|
|
2453
2540
|
if (opts.verbose) consola.level = LogLevels.debug;
|
|
2454
2541
|
if (opts.format === "json") consola.level = LogLevels.silent;
|
|
2455
2542
|
}).action(async (projectPath, options) => {
|
|
2456
2543
|
try {
|
|
2457
|
-
if (
|
|
2458
|
-
|
|
2459
|
-
if (options.
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2544
|
+
if (!projectPath) {
|
|
2545
|
+
if (!options.generator) throw new SqgError("Missing required option: --generator", "CONFIG_VALIDATION_ERROR", `Specify a code generation generator: ${SHORT_GENERATOR_NAMES.join(", ")}`);
|
|
2546
|
+
if (!options.file || options.file.length === 0) throw new SqgError("Missing required option: --file", "CONFIG_VALIDATION_ERROR", "Specify at least one SQL file with --file <path>");
|
|
2547
|
+
const project = buildProjectFromCliOptions({
|
|
2548
|
+
generator: options.generator,
|
|
2549
|
+
files: options.file,
|
|
2550
|
+
output: options.output,
|
|
2551
|
+
name: options.name
|
|
2552
|
+
});
|
|
2553
|
+
const projectDir = process.cwd();
|
|
2554
|
+
const writeToStdout = !options.output;
|
|
2555
|
+
if (options.validate) {
|
|
2556
|
+
const result = await validateProjectFromConfig(project, projectDir);
|
|
2557
|
+
if (options.format === "json") console.log(JSON.stringify(result, null, 2));
|
|
2558
|
+
else if (result.valid) {
|
|
2559
|
+
consola.success("Configuration is valid");
|
|
2560
|
+
consola.info(`Project: ${result.project?.name}`);
|
|
2561
|
+
consola.info(`SQL files: ${result.sqlFiles?.join(", ")}`);
|
|
2562
|
+
consola.info(`Generators: ${result.generators?.join(", ")}`);
|
|
2563
|
+
} else {
|
|
2564
|
+
consola.error("Validation failed");
|
|
2565
|
+
for (const error of result.errors || []) {
|
|
2566
|
+
consola.error(` ${error.message}`);
|
|
2567
|
+
if (error.suggestion) consola.info(` Suggestion: ${error.suggestion}`);
|
|
2568
|
+
}
|
|
2470
2569
|
}
|
|
2570
|
+
exit(result.valid ? 0 : 1);
|
|
2471
2571
|
}
|
|
2472
|
-
|
|
2572
|
+
const files = await processProjectFromConfig(project, projectDir, writeToStdout);
|
|
2573
|
+
if (options.format === "json" && !writeToStdout) console.log(JSON.stringify({
|
|
2574
|
+
status: "success",
|
|
2575
|
+
generatedFiles: files
|
|
2576
|
+
}));
|
|
2577
|
+
} else {
|
|
2578
|
+
if (options.validate) {
|
|
2579
|
+
const result = await validateProject(projectPath);
|
|
2580
|
+
if (options.format === "json") console.log(JSON.stringify(result, null, 2));
|
|
2581
|
+
else if (result.valid) {
|
|
2582
|
+
consola.success("Configuration is valid");
|
|
2583
|
+
consola.info(`Project: ${result.project?.name}`);
|
|
2584
|
+
consola.info(`SQL files: ${result.sqlFiles?.join(", ")}`);
|
|
2585
|
+
consola.info(`Generators: ${result.generators?.join(", ")}`);
|
|
2586
|
+
} else {
|
|
2587
|
+
consola.error("Validation failed");
|
|
2588
|
+
for (const error of result.errors || []) {
|
|
2589
|
+
consola.error(` ${error.message}`);
|
|
2590
|
+
if (error.suggestion) consola.info(` Suggestion: ${error.suggestion}`);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
exit(result.valid ? 0 : 1);
|
|
2594
|
+
}
|
|
2595
|
+
const files = await processProject(projectPath);
|
|
2596
|
+
if (options.format === "json") console.log(JSON.stringify({
|
|
2597
|
+
status: "success",
|
|
2598
|
+
generatedFiles: files
|
|
2599
|
+
}));
|
|
2473
2600
|
}
|
|
2474
|
-
const files = await processProject(projectPath);
|
|
2475
|
-
if (options.format === "json") console.log(JSON.stringify({
|
|
2476
|
-
status: "success",
|
|
2477
|
-
generatedFiles: files
|
|
2478
|
-
}));
|
|
2479
2601
|
} catch (err) {
|
|
2480
2602
|
if (options.format === "json") console.log(JSON.stringify(formatErrorForOutput(err)));
|
|
2481
2603
|
else if (err instanceof SqgError) {
|
|
@@ -2486,7 +2608,7 @@ program.argument("<project>", "Path to the project YAML config (sqg.yaml)").hook
|
|
|
2486
2608
|
exit(1);
|
|
2487
2609
|
}
|
|
2488
2610
|
});
|
|
2489
|
-
program.command("init").description("Initialize a new SQG project with example configuration").option("-
|
|
2611
|
+
program.command("init").description("Initialize a new SQG project with example configuration").option("-t, --generator <generator>", `Code generation generator (${SHORT_GENERATOR_NAMES.join(", ")})`, "typescript/sqlite").option("-o, --output <dir>", "Output directory for generated files", "./generated").option("-f, --force", "Overwrite existing files").action(async (options) => {
|
|
2490
2612
|
const parentOpts = program.opts();
|
|
2491
2613
|
try {
|
|
2492
2614
|
await initProject(options);
|
|
@@ -69,7 +69,7 @@ public class {{className}} {
|
|
|
69
69
|
public record {{rowType}}({{#each columns}}{{type}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
|
|
70
70
|
{{else}}
|
|
71
71
|
public record {{rowType}}(PreparedStatement statement, RootAllocator allocator, ArrowReader reader,
|
|
72
|
-
{{#each columns}}{{mapType this}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) implements AutoCloseable {
|
|
72
|
+
{{#each columns}}{{{mapType this}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) implements AutoCloseable {
|
|
73
73
|
public boolean loadNextBatch() throws IOException {
|
|
74
74
|
return reader.loadNextBatch();
|
|
75
75
|
}
|
|
@@ -99,7 +99,7 @@ int
|
|
|
99
99
|
{{#*inline "readVectors"}}
|
|
100
100
|
var root = reader.getVectorSchemaRoot();
|
|
101
101
|
|
|
102
|
-
return new {{rowType}}(stmt, allocator, reader, {{#each columns}}({{mapType this}})root.getVector("{{name}}"){{#unless @last}}, {{/unless}}{{/each}});
|
|
102
|
+
return new {{rowType}}(stmt, allocator, reader, {{#each columns}}({{{mapType this}}})root.getVector("{{name}}"){{#unless @last}}, {{/unless}}{{/each}});
|
|
103
103
|
|
|
104
104
|
{{/inline~}}
|
|
105
105
|
|